From 62f82d4df867325abcb6f12a932a1cc5ce0873c1 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas <6371790+pynicolas@users.noreply.github.com> Date: Sat, 12 Jul 2025 17:39:10 +0200 Subject: [PATCH] Extract strings --- .../java/org/mydomain/myscan/MainActivity.kt | 12 +++---- .../org/mydomain/myscan/view/AboutScreen.kt | 26 +++++++------- .../java/org/mydomain/myscan/view/Buttons.kt | 4 ++- .../org/mydomain/myscan/view/CameraPreview.kt | 4 ++- .../org/mydomain/myscan/view/CameraScreen.kt | 8 +++-- .../mydomain/myscan/view/DocumentScreen.kt | 27 +++++++++------ .../mydomain/myscan/view/LibrariesScreen.kt | 6 ++-- .../myscan/view/PdfGenerationBottomSheet.kt | 24 +++++++------ app/src/main/res/values/strings.xml | 34 +++++++++++++++++++ 9 files changed, 98 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/org/mydomain/myscan/MainActivity.kt b/app/src/main/java/org/mydomain/myscan/MainActivity.kt index d82b867..b342a47 100644 --- a/app/src/main/java/org/mydomain/myscan/MainActivity.kt +++ b/app/src/main/java/org/mydomain/myscan/MainActivity.kt @@ -20,7 +20,6 @@ import android.content.pm.PackageManager import android.media.MediaScannerConnection import android.net.Uri import android.os.Bundle -import android.os.Environment import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity @@ -30,7 +29,6 @@ import androidx.activity.viewModels import androidx.compose.runtime.getValue import androidx.core.content.FileProvider import androidx.core.net.toFile -import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.CoroutineScope @@ -44,7 +42,6 @@ import org.mydomain.myscan.view.CameraScreen import org.mydomain.myscan.view.DocumentScreen import org.mydomain.myscan.view.LibrariesScreen import org.opencv.android.OpenCVLoader -import java.io.File private const val PDF_MIME_TYPE = "application/pdf" @@ -123,7 +120,7 @@ class MainActivity : ComponentActivity() { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } - val chooser = Intent.createChooser(shareIntent, "Share PDF") + val chooser = Intent.createChooser(shareIntent, getString(R.string.share_pdf)) val resInfoList = packageManager.queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY) for (resInfo in resInfoList) { val packageName = resInfo.activityInfo.packageName @@ -151,7 +148,8 @@ class MainActivity : ComponentActivity() { } catch (e: Exception) { Log.e("MyScan", "Failed to save PDF", e) withContext(Dispatchers.Main) { - Toast.makeText(context, "Failed to save PDF", Toast.LENGTH_SHORT).show() + Toast.makeText(context, + getString(R.string.error_save), Toast.LENGTH_SHORT).show() } } } @@ -169,9 +167,9 @@ class MainActivity : ComponentActivity() { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } try { - startActivity(Intent.createChooser(openIntent, "Open PDF")) + startActivity(Intent.createChooser(openIntent, getString(R.string.open_pdf))) } catch (_: ActivityNotFoundException) { - Toast.makeText(this, "No app found to open PDF", Toast.LENGTH_SHORT).show() + Toast.makeText(this, getString(R.string.error_no_pdf_app), Toast.LENGTH_SHORT).show() } } diff --git a/app/src/main/java/org/mydomain/myscan/view/AboutScreen.kt b/app/src/main/java/org/mydomain/myscan/view/AboutScreen.kt index f4f1791..416a756 100644 --- a/app/src/main/java/org/mydomain/myscan/view/AboutScreen.kt +++ b/app/src/main/java/org/mydomain/myscan/view/AboutScreen.kt @@ -49,6 +49,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.mydomain.myscan.BuildConfig @@ -63,10 +64,11 @@ fun AboutScreen(onBack: () -> Unit, onViewLibraries: () -> Unit) { Scaffold( topBar = { TopAppBar( - title = { Text("About") }, + title = { Text(stringResource(R.string.about)) }, navigationIcon = { IconButton(onClick = onBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + Icon(Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.back)) } }, ) @@ -92,13 +94,13 @@ fun AboutContent( .verticalScroll(rememberScrollState()) ) { Text( - "MyScan", + stringResource(R.string.app_name), style = MaterialTheme.typography.headlineSmall ) Spacer(Modifier.height(8.dp)) Text( - "A simple and respectful application to scan your documents.", + stringResource(R.string.app_tagline), style = MaterialTheme.typography.bodyMedium ) @@ -107,7 +109,7 @@ fun AboutContent( Spacer(Modifier.height(16.dp)) Text( - "Version", + stringResource(R.string.version), style = MaterialTheme.typography.titleMedium ) Text(BuildConfig.VERSION_NAME) @@ -115,15 +117,15 @@ fun AboutContent( Spacer(Modifier.height(16.dp)) Text( - "License", + stringResource(R.string.license), style = MaterialTheme.typography.titleMedium ) Text( - "This application is licensed under the GNU General Public License v3.0.", + stringResource(R.string.licensed_under), style = MaterialTheme.typography.bodyMedium ) Text( - text = "View the full license", + text = stringResource(R.string.view_the_full_license), style = MaterialTheme.typography.bodyMedium, modifier = Modifier.clickable { showLicenseDialog.value = true }, color = MaterialTheme.colorScheme.primary @@ -132,15 +134,15 @@ fun AboutContent( Spacer(Modifier.height(16.dp)) Text( - "Libraries", + stringResource(R.string.libraries), style = MaterialTheme.typography.titleMedium ) Text( - "This application uses several open-source libraries, including:\n" + - "• CameraX\n• Jetpack Compose\n• LiteRT\n• OpenCV\n• PDFBox", + stringResource(R.string.libraries_intro) + + "\n• CameraX\n• Jetpack Compose\n• LiteRT\n• OpenCV\n• PDFBox", style = MaterialTheme.typography.bodyMedium) Text( - text = "View full list", + text = stringResource(R.string.view_full_list), style = MaterialTheme.typography.bodyMedium, modifier = Modifier.clickable(onClick = onViewLibraries), color = MaterialTheme.colorScheme.primary diff --git a/app/src/main/java/org/mydomain/myscan/view/Buttons.kt b/app/src/main/java/org/mydomain/myscan/view/Buttons.kt index d443502..f9d493e 100644 --- a/app/src/main/java/org/mydomain/myscan/view/Buttons.kt +++ b/app/src/main/java/org/mydomain/myscan/view/Buttons.kt @@ -29,7 +29,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import org.mydomain.myscan.R @Composable fun MainActionButton( @@ -81,7 +83,7 @@ fun AboutScreenNavButton( ) { Icon( imageVector = Icons.Outlined.Info, - contentDescription = "About", + contentDescription = stringResource(R.string.about), tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)) } } diff --git a/app/src/main/java/org/mydomain/myscan/view/CameraPreview.kt b/app/src/main/java/org/mydomain/myscan/view/CameraPreview.kt index 7ae37fa..95feb52 100644 --- a/app/src/main/java/org/mydomain/myscan/view/CameraPreview.kt +++ b/app/src/main/java/org/mydomain/myscan/view/CameraPreview.kt @@ -58,6 +58,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import com.google.common.util.concurrent.ListenableFuture import org.mydomain.myscan.LiveAnalysisState import org.mydomain.myscan.Point +import org.mydomain.myscan.R import org.mydomain.myscan.scaledTo import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -74,7 +75,8 @@ fun CameraPreview( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> if (!isGranted) { - Toast.makeText(context, "Camera permission was denied", Toast.LENGTH_SHORT).show() + Toast.makeText(context, + context.getString(R.string.camera_permission_denied), Toast.LENGTH_SHORT).show() } } diff --git a/app/src/main/java/org/mydomain/myscan/view/CameraScreen.kt b/app/src/main/java/org/mydomain/myscan/view/CameraScreen.kt index 88d2578..1b79d91 100644 --- a/app/src/main/java/org/mydomain/myscan/view/CameraScreen.kt +++ b/app/src/main/java/org/mydomain/myscan/view/CameraScreen.kt @@ -71,6 +71,7 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -79,6 +80,7 @@ import kotlinx.coroutines.delay import org.mydomain.myscan.LiveAnalysisState import org.mydomain.myscan.MainViewModel import org.mydomain.myscan.MainViewModel.CaptureState +import org.mydomain.myscan.R import org.mydomain.myscan.Screen import org.mydomain.myscan.ui.theme.MyScanTheme @@ -203,7 +205,9 @@ private fun CameraScreenScaffold( ) { CameraPreviewWithOverlay(cameraPreview, cameraUiState, Modifier.align(Alignment.BottomCenter)) Box( - modifier = Modifier.fillMaxSize().padding(innerPadding) + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) ) { AboutScreenNavButton( onClick = toAboutScreen, @@ -356,7 +360,7 @@ private fun CameraPreviewWithOverlay( .padding(16.dp) ) { Text( - text = "No document detected", + text = stringResource(R.string.error_no_document), color = Color.White, fontSize = 16.sp ) diff --git a/app/src/main/java/org/mydomain/myscan/view/DocumentScreen.kt b/app/src/main/java/org/mydomain/myscan/view/DocumentScreen.kt index e9bc3cb..e4f5914 100644 --- a/app/src/main/java/org/mydomain/myscan/view/DocumentScreen.kt +++ b/app/src/main/java/org/mydomain/myscan/view/DocumentScreen.kt @@ -59,6 +59,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -67,6 +68,7 @@ import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable import org.mydomain.myscan.Navigation import org.mydomain.myscan.PdfGenerationActions +import org.mydomain.myscan.R import org.mydomain.myscan.ui.PdfGenerationUiState import org.mydomain.myscan.ui.theme.MyScanTheme @@ -102,10 +104,11 @@ fun DocumentScreen( containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, titleContentColor = MaterialTheme.colorScheme.onSurface, ), - title = { Text("Document") }, + title = { Text(stringResource(R.string.document)) }, navigationIcon = { IconButton(onClick = navigation.toCameraScreen) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + Icon(Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.back)) } }, actions = { @@ -161,7 +164,9 @@ private fun DocumentPreview( LaunchedEffect(imageId) { zoomState.reset() } - Box(modifier = Modifier.fillMaxSize(0.92f).align(Alignment.Center)) { + Box(modifier = Modifier + .fillMaxSize(0.92f) + .align(Alignment.Center)) { Image( bitmap = imageBitmap, contentDescription = null, @@ -174,7 +179,7 @@ private fun DocumentPreview( } SecondaryActionButton( Icons.Outlined.Delete, - contentDescription = "Delete page", + contentDescription = stringResource(R.string.delete_page), onClick = { onDeleteImage(imageId) }, modifier = Modifier .align(Alignment.TopEnd) @@ -212,7 +217,7 @@ private fun PageList( SecondaryActionButton( icon = Icons.Default.Add, onClick = toCameraScreen, - contentDescription = "Add page", + contentDescription = stringResource(R.string.add_page), modifier = Modifier .align(Alignment.CenterEnd) .padding(8.dp) @@ -236,12 +241,12 @@ private fun BottomBar( MainActionButton( onClick = { showPdfDialog.value = true }, icon = Icons.Default.PictureAsPdf, - text = "Export PDF", + text = stringResource(R.string.export_pdf), ) Spacer(modifier = Modifier.width(8.dp)) SecondaryActionButton( icon = Icons.Default.RestartAlt, - contentDescription = "Restart", + contentDescription = stringResource(R.string.restart), onClick = { showNewDocDialog.value = true }, modifier = Modifier.padding(vertical = 8.dp) ) @@ -252,19 +257,19 @@ private fun BottomBar( @Composable fun NewDocumentDialog(onConfirm: () -> Unit, showDialog: MutableState) { AlertDialog( - title = { Text("New document") }, - text = { Text("The current document will be lost if you haven't saved it. Do you want to continue?") }, + title = { Text(stringResource(R.string.new_document)) }, + text = { Text(stringResource(R.string.new_document_warning)) }, confirmButton = { TextButton (onClick = { showDialog.value = false onConfirm() }) { - Text("Yes", fontWeight = FontWeight.Bold) + Text(stringResource(R.string.yes), fontWeight = FontWeight.Bold) } }, dismissButton = { TextButton(onClick = { showDialog.value = false }) { - Text("Cancel", fontWeight = FontWeight.Bold) + Text(stringResource(R.string.cancel), fontWeight = FontWeight.Bold) } }, onDismissRequest = { showDialog.value = false }, diff --git a/app/src/main/java/org/mydomain/myscan/view/LibrariesScreen.kt b/app/src/main/java/org/mydomain/myscan/view/LibrariesScreen.kt index b6add86..cb756e2 100644 --- a/app/src/main/java/org/mydomain/myscan/view/LibrariesScreen.kt +++ b/app/src/main/java/org/mydomain/myscan/view/LibrariesScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults import com.mikepenz.aboutlibraries.ui.compose.android.rememberLibraries import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer @@ -40,10 +41,11 @@ fun LibrariesScreen(onBack: () -> Unit) { Scaffold( topBar = { TopAppBar( - title = { Text("Open-source libraries") }, + title = { Text(stringResource(R.string.libraries_open_source)) }, navigationIcon = { IconButton(onClick = onBack) { - Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back") + Icon(Icons.AutoMirrored.Default.ArrowBack, + contentDescription = stringResource(R.string.back)) } } ) diff --git a/app/src/main/java/org/mydomain/myscan/view/PdfGenerationBottomSheet.kt b/app/src/main/java/org/mydomain/myscan/view/PdfGenerationBottomSheet.kt index 1efb4fc..0c8df3c 100644 --- a/app/src/main/java/org/mydomain/myscan/view/PdfGenerationBottomSheet.kt +++ b/app/src/main/java/org/mydomain/myscan/view/PdfGenerationBottomSheet.kt @@ -51,11 +51,13 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.net.toUri import org.mydomain.myscan.GeneratedPdf import org.mydomain.myscan.PdfGenerationActions +import org.mydomain.myscan.R import org.mydomain.myscan.ui.PdfGenerationUiState import org.mydomain.myscan.ui.theme.MyScanTheme import java.io.File @@ -124,7 +126,7 @@ fun PdfGenerationBottomSheet( .size(34.dp) .padding(end = 8.dp) ) - Text("Export PDF", style = MaterialTheme.typography.headlineSmall) + Text(stringResource(R.string.export_pdf), style = MaterialTheme.typography.headlineSmall) } Spacer(Modifier.height(16.dp)) @@ -132,7 +134,7 @@ fun PdfGenerationBottomSheet( OutlinedTextField( value = filename, onValueChange = onFilenameChange, - label = { Text("Filename") }, + label = { Text(stringResource(R.string.filename)) }, singleLine = true, modifier = Modifier.fillMaxWidth() ) @@ -180,16 +182,16 @@ private fun MainActions( onClick = onShare, enabled = pdf != null, icon = Icons.Default.Share, - iconDescription = "Share", - text = "Share", + iconDescription = stringResource(R.string.share), + text = stringResource(R.string.share), modifier = Modifier.weight(1f) ) MainActionButton( onClick = onSave, enabled = pdf != null, icon = Icons.Default.Download, - iconDescription = "Save", - text = "Save", + iconDescription = stringResource(R.string.save), + text = stringResource(R.string.save), modifier = Modifier.weight(1f) ) } @@ -206,12 +208,12 @@ private fun SavePdfBar(onOpen: () -> Unit, saveDirectoryName: String) { .padding(vertical = 8.dp, horizontal = 16.dp), ) { Text( - text = "PDF saved to $saveDirectoryName", + text = stringResource(R.string.pdf_saved_to, saveDirectoryName), style = MaterialTheme.typography.bodyMedium ) MainActionButton( onClick = onOpen, - text = "Open", + text = stringResource(R.string.open), icon = Icons.AutoMirrored.Filled.OpenInNew, ) } @@ -220,7 +222,7 @@ private fun SavePdfBar(onOpen: () -> Unit, saveDirectoryName: String) { @Composable private fun ErrorBar(errorMessage: String) { Text( - text = "Error: $errorMessage", + text = stringResource(R.string.error, errorMessage), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error, modifier = Modifier @@ -239,7 +241,7 @@ private fun CloseButton(onDismiss: () -> Unit) { ) { Icon( imageVector = Icons.Default.Close, - contentDescription = "Close" + contentDescription = stringResource(R.string.close) ) } } @@ -251,7 +253,7 @@ fun defaultFilename(): String { } fun formatFileSize(sizeInBytes: Long?, context: Context): String { - return if (sizeInBytes == null) "Unknown size" + return if (sizeInBytes == null) context.getString(R.string.unknown_size) else Formatter.formatShortFileSize(context, sizeInBytes) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 705697b..7acd9bf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,39 @@ + About + Add page MyScan + A simple and respectful application to scan your documents. + Back + Camera permission was denied + Cancel + Close + Delete page + Document + Error: %1$s + No document detected + No app found to open PDF + Failed to save PDF + Export PDF + Filename + Libraries + This application uses several open-source libraries, including: + Open-source libraries + License + This application is licensed under the GNU General Public License v3.0. + New document + The current document will be lost if you haven\'t saved it. Do you want to continue? + Open + Open PDF + PDF saved to %1$s + Restart + Save + Share + Share PDF + Unknown size + Version + View the full license + View full list + Yes %d page %d pages