Extract strings

This commit is contained in:
Pierre-Yves Nicolas
2025-07-12 17:39:10 +02:00
parent 8c356038b5
commit 62f82d4df8
9 changed files with 98 additions and 47 deletions

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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))
}
}

View File

@@ -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()
}
}

View File

@@ -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
)

View File

@@ -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<Boolean>) {
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 },

View File

@@ -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))
}
}
)

View File

@@ -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)
}

View File

@@ -1,5 +1,39 @@
<resources>
<string name="about">About</string>
<string name="add_page">Add page</string>
<string name="app_name">MyScan</string>
<string name="app_tagline">A simple and respectful application to scan your documents.</string>
<string name="back">Back</string>
<string name="camera_permission_denied">Camera permission was denied</string>
<string name="cancel">Cancel</string>
<string name="close">Close</string>
<string name="delete_page">Delete page</string>
<string name="document">Document</string>
<string name="error">Error: %1$s</string>
<string name="error_no_document">No document detected</string>
<string name="error_no_pdf_app">No app found to open PDF</string>
<string name="error_save">Failed to save PDF</string>
<string name="export_pdf">Export PDF</string>
<string name="filename">Filename</string>
<string name="libraries">Libraries</string>
<string name="libraries_intro">This application uses several open-source libraries, including:</string>
<string name="libraries_open_source">Open-source libraries</string>
<string name="license">License</string>
<string name="licensed_under">This application is licensed under the GNU General Public License v3.0.</string>
<string name="new_document">New document</string>
<string name="new_document_warning">The current document will be lost if you haven\'t saved it. Do you want to continue?</string>
<string name="open">Open</string>
<string name="open_pdf">Open PDF</string>
<string name="pdf_saved_to">PDF saved to %1$s</string>
<string name="restart">Restart</string>
<string name="save">Save</string>
<string name="share">Share</string>
<string name="share_pdf">Share PDF</string>
<string name="unknown_size">Unknown size</string>
<string name="version">Version</string>
<string name="view_the_full_license">View the full license</string>
<string name="view_full_list">View full list</string>
<string name="yes">Yes</string>
<plurals name="page_count">
<item quantity="one">%d page</item>
<item quantity="other">%d pages</item>