diff --git a/app/src/main/java/be/scri/data/remote/DynamicDbHelper.kt b/app/src/main/java/be/scri/data/remote/DynamicDbHelper.kt index c0bb1205c..3fc46aef6 100644 --- a/app/src/main/java/be/scri/data/remote/DynamicDbHelper.kt +++ b/app/src/main/java/be/scri/data/remote/DynamicDbHelper.kt @@ -61,6 +61,7 @@ class DynamicDbHelper( db.setTransactionSuccessful() } catch (e: SQLiteException) { Log.e("SCRIBE_DB", "Error during insert: ${e.message}") + throw e } finally { db.endTransaction() db.close() diff --git a/app/src/main/java/be/scri/helpers/StringUtils.kt b/app/src/main/java/be/scri/helpers/StringUtils.kt index 920ba33a1..a5475b389 100644 --- a/app/src/main/java/be/scri/helpers/StringUtils.kt +++ b/app/src/main/java/be/scri/helpers/StringUtils.kt @@ -43,4 +43,27 @@ object StringUtils { } return result } + + /** + * Replaces placeholder variables in a template string with provided parameters. + * + * This helper function works with template strings that use {variable_name} placeholder syntax, + * replacing them in order with the provided parameters. + * + * @param template The template string (e.g., "Network error: {error}") + * @param params Variable number of string parameters to replace placeholders with. + * Placeholders are replaced in the order they appear in the string. + * + * @return The formatted string with all placeholders replaced by the provided parameters. + */ + fun formatStringWithParams( + template: String, + vararg params: String, + ): String { + var result = template + params.forEach { param -> + result = result.replaceFirst(Regex("""\{[^}]+\}"""), param) + } + return result + } } diff --git a/app/src/main/java/be/scri/ui/screens/SelectLanguageScreen.kt b/app/src/main/java/be/scri/ui/screens/SelectLanguageScreen.kt index 9578e2d4b..9cf62761f 100644 --- a/app/src/main/java/be/scri/ui/screens/SelectLanguageScreen.kt +++ b/app/src/main/java/be/scri/ui/screens/SelectLanguageScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.content.edit import be.scri.R +import be.scri.helpers.StringUtils import be.scri.ui.common.ScribeBaseScreen import be.scri.ui.common.appcomponents.ConfirmationDialog @@ -129,13 +130,20 @@ fun SelectTranslationSourceLanguageScreen( } if (showDialog.value) { + val localizedSelectedLang = getDisplayLanguageName(selectedLanguage.value) + val localizedSavedLang = getDisplayLanguageName(savedLanguage.value) ConfirmationDialog( text = - "You've changed your source translation language. " + - "Would you like to download new data so that you can translate " + - "from ${selectedLanguage.value}?", - textConfirm = "Download data", - textChange = "Keep ${savedLanguage.value}", + StringUtils.stringResourceWithParams( + R.string.i18n_app_settings_keyboard_translation_change_source_tooltip_download_warning, + localizedSelectedLang, + ), + textConfirm = stringResource(R.string.i18n_app__global_download_data), + textChange = + StringUtils.stringResourceWithParams( + R.string.i18n_app_settings_keyboard_translation_change_source_tooltip_keep_source_language, + localizedSavedLang, + ), onConfirm = { // User confirmed - save the new selection permanently. savedLanguage.value = selectedLanguage.value diff --git a/app/src/main/java/be/scri/ui/screens/download/ConjugateDataDownloadViewModel.kt b/app/src/main/java/be/scri/ui/screens/download/ConjugateDataDownloadViewModel.kt index 15ab0ee23..637e3d43a 100644 --- a/app/src/main/java/be/scri/ui/screens/download/ConjugateDataDownloadViewModel.kt +++ b/app/src/main/java/be/scri/ui/screens/download/ConjugateDataDownloadViewModel.kt @@ -10,9 +10,11 @@ import android.widget.Toast import androidx.compose.runtime.mutableStateMapOf import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import be.scri.R import be.scri.data.remote.ConjugateDynamicDbHelper import be.scri.data.remote.RetrofitClient import be.scri.helpers.LanguageMappingConstants +import be.scri.helpers.StringUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.TimeoutCancellationException @@ -106,7 +108,12 @@ class ConjugateDataDownloadViewModel( } if (currentState == DownloadState.Completed) { - Toast.makeText(getApplication(), "$displayLang conjugate data is already up to date", Toast.LENGTH_SHORT).show() + val template = + getApplication().getString( + R.string.i18n_app_download_menu_ui_conjugate_data_already_up_to_date, + ) + val msg = StringUtils.formatStringWithParams(template, displayLang) + Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() return } } @@ -144,23 +151,51 @@ class ConjugateDataDownloadViewModel( withContext(Dispatchers.Main) { downloadStates[key] = DownloadState.Completed - Toast.makeText(getApplication(), "Download $displayLang conjugate data finished!", Toast.LENGTH_SHORT).show() + val template = + getApplication().getString( + R.string.i18n_app_download_menu_ui_conjugate_data_download_success, + ) + val msg = StringUtils.formatStringWithParams(template, displayLang) + Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() } } else { // Already up to date: Skip the DB work. withContext(Dispatchers.Main) { downloadStates[key] = DownloadState.Completed - Toast.makeText(getApplication(), "Already up to date!", Toast.LENGTH_SHORT).show() + val msg = + getApplication().getString( + R.string.i18n_app_download_menu_ui_download_data_generic_already_up_to_date, + ) + Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() } } } catch (e: IOException) { - updateErrorState(key, "Network Error: ${e.message}") + val template = + getApplication().getString( + R.string.i18n_app_download_error_network, + ) + val errorMsg = StringUtils.formatStringWithParams(template, e.message ?: "") + updateErrorState(key, errorMsg) } catch (e: SQLiteException) { - updateErrorState(key, "Database Error: ${e.message}") + val template = + getApplication().getString( + R.string.i18n_app_download_error_database, + ) + val errorMsg = StringUtils.formatStringWithParams(template, e.message ?: "") + updateErrorState(key, errorMsg) } catch (e: HttpException) { - updateErrorState(key, "Server Error: ${e.code()}") + val template = + getApplication().getString( + R.string.i18n_app_download_error_server, + ) + val errorMsg = StringUtils.formatStringWithParams(template, e.code().toString()) + updateErrorState(key, errorMsg) } catch (e: TimeoutCancellationException) { - updateErrorState(key, "Download timed out") + val errorMsg = + getApplication().getString( + R.string.i18n_app_download_error_timeout, + ) + updateErrorState(key, errorMsg) throw e } finally { // Clean up the job reference when done. diff --git a/app/src/main/java/be/scri/ui/screens/download/ConjugateDownloadDataScreen.kt b/app/src/main/java/be/scri/ui/screens/download/ConjugateDownloadDataScreen.kt index e32e19cd1..46875a66b 100644 --- a/app/src/main/java/be/scri/ui/screens/download/ConjugateDownloadDataScreen.kt +++ b/app/src/main/java/be/scri/ui/screens/download/ConjugateDownloadDataScreen.kt @@ -143,7 +143,7 @@ fun ConjugateDownloadDataScreen( ) { Column(Modifier.padding(vertical = 10.dp, horizontal = 4.dp)) { Text( - text = "Update all", + text = stringResource(R.string.i18n_app_download_menu_ui_download_data_update_all), color = colorResource(R.color.dark_scribe_blue), fontSize = 20.sp, fontWeight = FontWeight.Medium, diff --git a/app/src/main/java/be/scri/ui/screens/download/DataDownloadScreen.kt b/app/src/main/java/be/scri/ui/screens/download/DataDownloadScreen.kt index 33ea5acd1..733b66d35 100644 --- a/app/src/main/java/be/scri/ui/screens/download/DataDownloadScreen.kt +++ b/app/src/main/java/be/scri/ui/screens/download/DataDownloadScreen.kt @@ -295,7 +295,7 @@ private fun LanguagesListSection( ) { Column(Modifier.padding(vertical = 10.dp, horizontal = 4.dp)) { Text( - text = "Update all", + text = stringResource(R.string.i18n_app_download_menu_ui_download_data_update_all), color = colorResource(R.color.dark_scribe_blue), fontSize = 20.sp, fontWeight = FontWeight.Medium, diff --git a/app/src/main/java/be/scri/ui/screens/download/DataDownloadViewModel.kt b/app/src/main/java/be/scri/ui/screens/download/DataDownloadViewModel.kt index 93045dca9..36a057e83 100644 --- a/app/src/main/java/be/scri/ui/screens/download/DataDownloadViewModel.kt +++ b/app/src/main/java/be/scri/ui/screens/download/DataDownloadViewModel.kt @@ -10,9 +10,11 @@ import android.widget.Toast import androidx.compose.runtime.mutableStateMapOf import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import be.scri.R import be.scri.data.remote.DynamicDbHelper import be.scri.data.remote.RetrofitClient import be.scri.helpers.LanguageMappingConstants +import be.scri.helpers.StringUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.TimeoutCancellationException @@ -106,7 +108,12 @@ class DataDownloadViewModel( } if (currentState == DownloadState.Completed) { - Toast.makeText(getApplication(), "$displayLang data is already up to date", Toast.LENGTH_SHORT).show() + val template = + getApplication().getString( + R.string.i18n_app_download_menu_ui_download_data_already_up_to_date, + ) + val msg = StringUtils.formatStringWithParams(template, displayLang) + Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() return } } @@ -144,23 +151,51 @@ class DataDownloadViewModel( withContext(Dispatchers.Main) { downloadStates[key] = DownloadState.Completed - Toast.makeText(getApplication(), "Download $displayLang data finished!", Toast.LENGTH_SHORT).show() + val template = + getApplication().getString( + R.string.i18n_app_download_menu_ui_download_data_download_success, + ) + val msg = StringUtils.formatStringWithParams(template, displayLang) + Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() } } else { // Already up to date: Skip the DB work. withContext(Dispatchers.Main) { downloadStates[key] = DownloadState.Completed - Toast.makeText(getApplication(), "Already up to date!", Toast.LENGTH_SHORT).show() + val msg = + getApplication().getString( + R.string.i18n_app_download_menu_ui_download_data_generic_already_up_to_date, + ) + Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() } } } catch (e: IOException) { - updateErrorState(key, "Network Error: ${e.message}") + val template = + getApplication().getString( + R.string.i18n_app_download_error_network, + ) + val errorMsg = StringUtils.formatStringWithParams(template, e.message ?: "") + updateErrorState(key, errorMsg) } catch (e: SQLiteException) { - updateErrorState(key, "Database Error: ${e.message}") + val template = + getApplication().getString( + R.string.i18n_app_download_error_database, + ) + val errorMsg = StringUtils.formatStringWithParams(template, e.message ?: "") + updateErrorState(key, errorMsg) } catch (e: HttpException) { - updateErrorState(key, "Server Error: ${e.code()}") + val template = + getApplication().getString( + R.string.i18n_app_download_error_server, + ) + val errorMsg = StringUtils.formatStringWithParams(template, e.code().toString()) + updateErrorState(key, errorMsg) } catch (e: TimeoutCancellationException) { - updateErrorState(key, "Download timed out") + val errorMsg = + getApplication().getString( + R.string.i18n_app_download_error_timeout, + ) + updateErrorState(key, errorMsg) throw e } finally { // Clean up the job reference when done. diff --git a/app/src/test/kotlin/be/scri/helpers/StringUtilsTest.kt b/app/src/test/kotlin/be/scri/helpers/StringUtilsTest.kt index 9701d1143..275bc8585 100644 --- a/app/src/test/kotlin/be/scri/helpers/StringUtilsTest.kt +++ b/app/src/test/kotlin/be/scri/helpers/StringUtilsTest.kt @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later package be.scri.helpers +import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test @@ -35,4 +36,18 @@ class StringUtilsTest { fun isWordCapitalized_returnsFalse_forNumberStartingString() { assertFalse(StringUtils.isWordCapitalized("1hello")) } + + @Test + fun formatStringWithParams_replacesPlaceholdersInOrder() { + val template = "Hello {name}, welcome to {place}." + val result = StringUtils.formatStringWithParams(template, "Alice", "Wonderland") + assertEquals("Hello Alice, welcome to Wonderland.", result) + } + + @Test + fun formatStringWithParams_doesNotChangeTemplate_ifNoPlaceholders() { + val template = "Hello World" + val result = StringUtils.formatStringWithParams(template, "Alice") + assertEquals("Hello World", result) + } }