From c94b29073ffd331b7b877c09cb7e761c3ae85b1d Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:50:25 +0100 Subject: [PATCH] implement conditional AI generator UI and improve "No Connection" handling --- .../translator/view/NoConncectionScreen.kt | 11 +- .../view/dictionary/MainDictionaryScreen.kt | 9 - .../view/library/LibraryComponents.kt | 16 ++ .../translator/view/library/LibraryScreen.kt | 1 + .../view/translation/MainTranslationScreen.kt | 11 +- .../view/vocabulary/ExplorePacksScreen.kt | 6 - .../view/vocabulary/NewWordScreen.kt | 238 +++++++++++------- app/src/main/res/values-de-rDE/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 8 - app/src/main/res/values/strings.xml | 30 +-- 10 files changed, 194 insertions(+), 137 deletions(-) diff --git a/app/src/main/java/eu/gaudian/translator/view/NoConncectionScreen.kt b/app/src/main/java/eu/gaudian/translator/view/NoConncectionScreen.kt index 4d1ef7c..6fb538c 100644 --- a/app/src/main/java/eu/gaudian/translator/view/NoConncectionScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/NoConncectionScreen.kt @@ -16,13 +16,18 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.navigation.NavController import eu.gaudian.translator.R import eu.gaudian.translator.view.composable.AppButton - +import eu.gaudian.translator.view.composable.AppTopAppBar @Composable -fun NoConnectionScreen(onSettingsClick: () -> Unit) { +fun NoConnectionScreen(onSettingsClick: () -> Unit, navController: NavController) { + AppTopAppBar( + title = "No Connection", + onNavigateBack = {navController.popBackStack()}, + ) Column( modifier = Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.Center, @@ -41,7 +46,7 @@ fun NoConnectionScreen(onSettingsClick: () -> Unit) { textAlign = TextAlign.Center ) AppButton(onClick = onSettingsClick, modifier = Modifier.padding(top = 16.dp)) { - Text(text = stringResource(id = R.string.settings_title_connection)) + Text(text = "Configure Connection") } } } \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt index 26ebe63..0b58695 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt @@ -17,13 +17,10 @@ import androidx.navigation.compose.rememberNavController import eu.gaudian.translator.R import eu.gaudian.translator.ui.theme.ThemePreviews import eu.gaudian.translator.utils.findActivity -import eu.gaudian.translator.view.LocalConnectionConfigured -import eu.gaudian.translator.view.NoConnectionScreen import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppTabLayout import eu.gaudian.translator.view.composable.Screen import eu.gaudian.translator.view.composable.TabItem -import eu.gaudian.translator.view.settings.SettingsRoutes import eu.gaudian.translator.viewmodel.CorrectionViewModel import eu.gaudian.translator.viewmodel.DictionaryViewModel import eu.gaudian.translator.viewmodel.LanguageViewModel @@ -52,12 +49,6 @@ fun MainDictionaryScreen( val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity) val dictionaryTabs = getDictionaryTabs() - val connectionConfigured = LocalConnectionConfigured.current - - if (!connectionConfigured) { - NoConnectionScreen(onSettingsClick = {navController.navigate(SettingsRoutes.API_KEY)}) - return - } var selectedTab by remember { mutableStateOf(dictionaryTabs[0]) } Column { diff --git a/app/src/main/java/eu/gaudian/translator/view/library/LibraryComponents.kt b/app/src/main/java/eu/gaudian/translator/view/library/LibraryComponents.kt index c1c9e56..8eabfd5 100644 --- a/app/src/main/java/eu/gaudian/translator/view/library/LibraryComponents.kt +++ b/app/src/main/java/eu/gaudian/translator/view/library/LibraryComponents.kt @@ -337,6 +337,7 @@ fun AllCardsView( onItemLongClick: (VocabularyItem) -> Unit, onDeleteClick: (VocabularyItem) -> Unit, listState: LazyListState, + onAddClick: (() -> Unit)? = null, modifier: Modifier = Modifier ) { if (vocabularyItems.isEmpty()) { @@ -359,6 +360,21 @@ fun AllCardsView( color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center ) + if (onAddClick != null) { + Spacer(modifier = Modifier.height(24.dp)) + androidx.compose.material3.Button( + onClick = onAddClick, + modifier = Modifier.fillMaxWidth(0.6f) + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = stringResource(R.string.label_add_vocabulary)) + } + } } } else { LazyColumn( diff --git a/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt b/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt index 8aaf560..530a03b 100644 --- a/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt @@ -266,6 +266,7 @@ fun LibraryScreen( selection = selection, stageMapping = stageMapping, listState = lazyListState, + onAddClick = { navController.navigate(NavigationRoutes.NEW_WORD) }, onItemClick = { item -> if (isInSelectionMode) { selection = if (selection.contains(item.id.toLong())) { diff --git a/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt b/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt index 14866ad..91488b1 100644 --- a/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt @@ -6,6 +6,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.content.res.Configuration import androidx.compose.animation.Crossfade import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.RepeatMode @@ -58,6 +59,7 @@ import eu.gaudian.translator.view.LocalConnectionConfigured import eu.gaudian.translator.view.NoConnectionScreen import eu.gaudian.translator.view.composable.AppCard import eu.gaudian.translator.view.composable.AppOutlinedCard +import eu.gaudian.translator.view.composable.Screen import eu.gaudian.translator.view.dialogs.AddVocabularyDialog import eu.gaudian.translator.view.hints.HintDefinition import eu.gaudian.translator.view.settings.SettingsRoutes @@ -88,7 +90,10 @@ fun TranslationScreen( if (isInitializationComplete && !connectionConfigured) { - NoConnectionScreen(onSettingsClick = { navController.navigate(SettingsRoutes.API_KEY) }) + NoConnectionScreen( + onSettingsClick = { navController.navigate(SettingsRoutes.API_KEY) }, + navController = navController + ) return } @@ -108,7 +113,7 @@ fun TranslationScreen( onSettingsClick = onSettingsClick, onNavigateBack = { if (!navController.popBackStack()) { - navController.navigate(eu.gaudian.translator.view.composable.Screen.Home.route) { + navController.navigate(Screen.Home.route) { launchSingleTop = true restoreState = false } @@ -182,7 +187,7 @@ private fun LoadedTranslationContent( AppCard(modifier = Modifier.padding(8.dp, end = 8.dp, bottom = 8.dp, top = 0.dp)) { val configuration = LocalConfiguration.current - val isLandscape = configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE + val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE if (isLandscape) { Row(modifier = Modifier.fillMaxSize()) { diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/ExplorePacksScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/ExplorePacksScreen.kt index bdedaf1..c7f7ed8 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/ExplorePacksScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/ExplorePacksScreen.kt @@ -563,12 +563,6 @@ fun ExplorePacksScreen( fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(8.dp)) - Text( - manifestError ?: "", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center - ) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = { vocabPacksViewModel.loadManifest() }) { Text(stringResource(R.string.label_retry)) diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt index 3f0a80c..bb4b3a7 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AutoAwesome import androidx.compose.material.icons.filled.DriveFolderUpload import androidx.compose.material.icons.filled.EditNote +import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.AlertDialog import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu @@ -56,6 +57,7 @@ import eu.gaudian.translator.model.VocabularyItem import eu.gaudian.translator.utils.StatusMessageId import eu.gaudian.translator.utils.StatusMessageService import eu.gaudian.translator.utils.findActivity +import eu.gaudian.translator.view.LocalConnectionConfigured import eu.gaudian.translator.view.NavigationRoutes import eu.gaudian.translator.view.composable.AppButton import eu.gaudian.translator.view.composable.AppCard @@ -71,6 +73,7 @@ import eu.gaudian.translator.view.composable.SourceLanguageDropdown import eu.gaudian.translator.view.composable.TargetLanguageDropdown import eu.gaudian.translator.view.hints.HintDefinition import eu.gaudian.translator.view.library.VocabularyCard +import eu.gaudian.translator.view.settings.SettingsRoutes import eu.gaudian.translator.viewmodel.LanguageViewModel import eu.gaudian.translator.viewmodel.VocabularyViewModel import kotlinx.coroutines.launch @@ -247,6 +250,7 @@ fun NewWordScreen( } } }, + navController = navController, ) Spacer(modifier = Modifier.height(24.dp)) @@ -437,108 +441,156 @@ fun AIGeneratorCard( languageViewModel: LanguageViewModel, isGenerating: Boolean, onGenerate: () -> Unit, + navController: NavHostController, modifier: Modifier = Modifier ) { - val icon = Icons.Default.AutoAwesome - val hints = stringArrayResource(R.array.vocabulary_hints) - AppCard( - modifier = modifier.fillMaxWidth(), - title = stringResource(R.string.label_ai_generator), - icon = icon, - hintContent = HintDefinition.VOCABULARY_GENERATE_AI.hint(), - ) { - Column(modifier = Modifier.padding(8.dp)) { + val connectionConfigured = LocalConnectionConfigured.current - Text( - text = stringResource(R.string.text_search_term), - style = MaterialTheme.typography.labelLarge, - fontWeight = FontWeight.SemiBold - ) - Spacer(modifier = Modifier.height(8.dp)) - InspiringSearchField( - value = category, - hints = hints, - onValueChange = onCategoryChange - ) - Spacer(modifier = Modifier.height(24.dp)) - Text( - text = stringResource(R.string.text_select_languages), - style = MaterialTheme.typography.labelLarge, - fontWeight = FontWeight.SemiBold - ) - Spacer(modifier = Modifier.height(8.dp)) - Row( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(16.dp)) - .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f)) - .padding(12.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - SourceLanguageDropdown( - modifier = Modifier.weight(1f), - languageViewModel = languageViewModel, - iconEnabled = false, - noBorder = true + if (connectionConfigured) { + // Show the normal AI generator card + val icon = Icons.Default.AutoAwesome + val hints = stringArrayResource(R.array.vocabulary_hints) + AppCard( + modifier = modifier.fillMaxWidth(), + title = stringResource(R.string.label_ai_generator), + icon = icon, + hintContent = HintDefinition.VOCABULARY_GENERATE_AI.hint(), + ) { + Column(modifier = Modifier.padding(8.dp)) { + + Text( + text = stringResource(R.string.text_search_term), + style = MaterialTheme.typography.labelLarge, + fontWeight = FontWeight.SemiBold ) - TargetLanguageDropdown( - modifier = Modifier.weight(1f), - languageViewModel = languageViewModel, - iconEnabled = false, - noBorder = true + Spacer(modifier = Modifier.height(8.dp)) + InspiringSearchField( + value = category, + hints = hints, + onValueChange = onCategoryChange ) - } - Spacer(modifier = Modifier.height(24.dp)) - Text( - text = stringResource(R.string.text_select_amount), - style = MaterialTheme.typography.labelLarge, - fontWeight = FontWeight.SemiBold - ) - Spacer(modifier = Modifier.height(8.dp)) - AppSlider( - value = amount, - onValueChange = onAmountChange, - valueRange = 1f..25f, - steps = 24, - modifier = Modifier.fillMaxWidth() - ) - Text( - text = stringResource(R.string.text_amount_2d, amount.toInt()), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(top = 8.dp) - ) - Spacer(modifier = Modifier.height(32.dp)) - - if (isGenerating) { + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(R.string.text_select_languages), + style = MaterialTheme.typography.labelLarge, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(8.dp)) Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f)) + .padding(12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp) ) { - CircularProgressIndicator() - } - Spacer(modifier = Modifier.height(16.dp)) - } - - AppButton( - onClick = onGenerate, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - enabled = category.isNotBlank() && !isGenerating - ) { - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Icon(imageVector = Icons.Default.AutoAwesome, contentDescription = null, modifier = Modifier.size(20.dp)) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.text_generate), - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + SourceLanguageDropdown( + modifier = Modifier.weight(1f), + languageViewModel = languageViewModel, + iconEnabled = false, + noBorder = true ) + TargetLanguageDropdown( + modifier = Modifier.weight(1f), + languageViewModel = languageViewModel, + iconEnabled = false, + noBorder = true + ) + } + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(R.string.text_select_amount), + style = MaterialTheme.typography.labelLarge, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(8.dp)) + AppSlider( + value = amount, + onValueChange = onAmountChange, + valueRange = 1f..25f, + steps = 24, + modifier = Modifier.fillMaxWidth() + ) + Text( + text = stringResource(R.string.text_amount_2d, amount.toInt()), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(top = 8.dp) + ) + Spacer(modifier = Modifier.height(32.dp)) + + if (isGenerating) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + CircularProgressIndicator() + } + Spacer(modifier = Modifier.height(16.dp)) + } + + AppButton( + onClick = onGenerate, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + enabled = category.isNotBlank() && !isGenerating + ) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Icon(imageVector = Icons.Default.AutoAwesome, contentDescription = null, modifier = Modifier.size(20.dp)) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.text_generate), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + } + } + } + } + } else { + // Show the "configure connection" card when not configured + AppCard( + modifier = modifier.fillMaxWidth(), + title = stringResource(R.string.label_ai_generator), + icon = Icons.Default.AutoAwesome, + hintContent = HintDefinition.VOCABULARY_GENERATE_AI.hint(), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.text_ai_generator_requires_configuration), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Spacer(modifier = Modifier.height(16.dp)) + AppButton( + onClick = { navController.navigate(SettingsRoutes.API_KEY) }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + ) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Icon(imageVector = Icons.Default.Settings, contentDescription = null, modifier = Modifier.size(20.dp)) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.label_configure), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + } } } } diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 40e2b26..80b75b7 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -547,7 +547,6 @@ Vokabeln sortieren " (optional)" Verfügbarkeit prüfen - Keine gültige API-Konfiguration gefunden. Bitte konfiguriere zuerst einen API-Anbieter in den Einstellungen. Zuerst Wiktionary versuchen Versuche zuerst, das Wort auf Wiktionary zu finden, bevor eine KI-Antwort generiert wird. Frage %1$d von %2$d diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index be75d99..056260c 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -8,7 +8,6 @@ Traduzir Adicionar Fechar - Cancelar Confirmar Selecionar Concluído @@ -90,7 +89,6 @@ Atividade Semanal Carregando… Mostrar Carregamento - Cancelar Carregamento Mostrar Mensagem Informativa Mostrar Mensagem de Erro Resetar Introdução @@ -543,7 +541,6 @@ Organização de Vocabulário " (opcional)" Verificar disponibilidade - Nenhuma configuração de API válida foi encontrada. Antes de usar o app, configure pelo menos um provedor de API. Tentar Wikcionário Primeiro Tente primeiro encontrar a palavra no Wikcionário antes de gerar uma resposta de IA Pergunta %1$d de %2$d @@ -620,11 +617,9 @@ Quer minimizar o app? Erro ao excluir dicionários: %1$s Erro ao excluir dicionário: %1$s - Erro ao deletar arquivo órfão: %1$s Erro ao baixar dicionário: %1$s Erro ao carregar valores armazenados: %1$s Erro ao salvar entrada: %1$s - Falha ao deletar dicionário: %1$s Falhou ao excluir arquivo órfão: %1$s Falhou ao excluir alguns dicionários Falhou ao baixar dicionário: %1$s @@ -771,8 +766,6 @@ Usar dicionário baixado Assistir ao Vídeo Novamente Tem certeza que deseja excluir o provedor "%1$s"? Isso também removerá todos os modelos associados a este provedor. Esta ação não pode ser desfeita. - Deletar Modelo - Tem certeza que deseja deletar o modelo "%1$s" de %2$s? Essa ação não pode ser desfeita. Atribuições do Modelo de Tarefa Configurar qual modelo de IA usar para cada tipo de tarefa Personalizado @@ -785,7 +778,6 @@ A tradução vai aparecer aqui Apagar Chave Tem certeza que quer apagar a Chave desse Fornecedor? - Deletar todos os provedores e modelos Conteúdo do Dicionário Definição de IA Baixado diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fdf9b5d..a4aa002 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,7 +128,7 @@ Existing Item (ID: %1$d) Experimental Features - Enable experimental features that are not yet ready for production. + Enable experimental features that aren’t yet ready for production. Export Vocabulary Data @@ -238,6 +238,8 @@ Adverb AI Configuration AI Generator + In order to generate vocabulary with AI, you have to configure a connection to an AI service. + Configure AI Model All Cards @@ -511,7 +513,7 @@ API Key is missing or invalid. Error removing articles: %1$s Error updating category: %1$s - Excel is not supported. Use CSV instead. + Excel isn’t supported. Use CSV instead. Save File Launcher not initialized. File save cancelled or failed. Error saving file: %1$s @@ -869,7 +871,7 @@ Bring the letters into the right order Assign a different language to these items. Assign these items: - Authentication is required and has failed or has not yet been provided. + Authentication is required and has failed or hasn’t yet been provided. Automatically discover models from %1$s Available Dictionaries Available Models: @@ -915,8 +917,8 @@ Set a model for generating dictionary content and give optional instructions. Developed by Jonas Gaudian\n Are you sure you want to delete the Key for this Provider? - Are you sure you want to delete the model \"%1$s\" from %2$s? This action cannot be undone. - Are you sure you want to delete the provider \"%1$s\"? This will also remove all models associated with this provider. This action cannot be undone. + Are you sure you want to delete the model \"%1$s\" from %2$s? This action can’t be undone. + Are you sure you want to delete the provider \"%1$s\"? This will also remove all models associated with this provider. This action can’t be undone. Dictionary deleted successfully Dictionary downloaded successfully You can download dictionaries for certain languages which can be used insteaf of AI generation for dictionary content. @@ -987,7 +989,7 @@ Clear language pair selection to choose a direction. You can set an optional preference which language should come first or second. Language Options - Set what languages you want to use in the app. Languages that are not activated will not appear in this app. You can also add your own language to the list, or change an existing language (region/locale) + Set what languages you want to use in the app. Languages that aren’t activated won’t appear in this app. You can also add your own language to the list, or change an existing language (region/locale) Last 7 Days Light List @@ -1011,7 +1013,7 @@ No Key No models found No packs match your search. - No valid API configuration could be found in the settings. Before using this app, you have to configure at least one API provider. + No valid LLM API configuration could be found in the settings. Before using this function, you have to configure at least one API provider. No vocabulary available. No Vocabulary Due Today None @@ -1020,7 +1022,7 @@ " (optional)" Optional: Describe what this model is good for Orphaned file deleted successfully - This file exists locally but is not in the server manifest or missing assets. It may be from an older version or a failed download. + This file exists locally but isn’t in the server manifest or missing assets. It may be from an older version or a failed download. Paste or open a YouTube link to see its subtitles here. Please select a dictionary language first. Question @@ -1064,7 +1066,7 @@ Shuffle card order Shuffle Card Order Shuffle Languages - Shuffle what language comes first. Does not affect language direction preferences. + Shuffle what language comes first. Doesn’t affect language direction preferences. Disable language direction preference to enable shuffling. Some items are in the wrong category. Stage %1$s @@ -1076,9 +1078,9 @@ The correct sentence was: %1$s The correct translation is: %1$s Theme Preview - These files exist locally but are not in the server manifest. They may be from older versions. + These files exist locally but aren’t in the server manifest. They may be from older versions. This must match the provider\'s model name exactly - This will remove all configured API providers, models, and stored API keys. This action cannot be undone. + This will remove all configured API providers, models, and stored API keys. This action can’t be undone. Too Many Requests: The user has sent too many requests in a given amount of time. Total Learned Words Training Mode @@ -1104,13 +1106,13 @@ YouTube Link The request was successful. - The requested resource could not be found. - The server could not understand the request. + The requested resource couldn’t be found. + The server couldn’t understand the request. The server understood the request, but is refusing to authorize it. This is a sample output text. This is the content inside the card. - This mode will not affect your progress in stages. + This mode won’t affect your progress in stages. Timeout