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.media.MediaScannerConnection
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@@ -30,7 +29,6 @@ import androidx.activity.viewModels
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.net.toFile import androidx.core.net.toFile
import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineScope 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.DocumentScreen
import org.mydomain.myscan.view.LibrariesScreen import org.mydomain.myscan.view.LibrariesScreen
import org.opencv.android.OpenCVLoader import org.opencv.android.OpenCVLoader
import java.io.File
private const val PDF_MIME_TYPE = "application/pdf" private const val PDF_MIME_TYPE = "application/pdf"
@@ -123,7 +120,7 @@ class MainActivity : ComponentActivity() {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 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) val resInfoList = packageManager.queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY)
for (resInfo in resInfoList) { for (resInfo in resInfoList) {
val packageName = resInfo.activityInfo.packageName val packageName = resInfo.activityInfo.packageName
@@ -151,7 +148,8 @@ class MainActivity : ComponentActivity() {
} catch (e: Exception) { } catch (e: Exception) {
Log.e("MyScan", "Failed to save PDF", e) Log.e("MyScan", "Failed to save PDF", e)
withContext(Dispatchers.Main) { 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) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
} }
try { try {
startActivity(Intent.createChooser(openIntent, "Open PDF")) startActivity(Intent.createChooser(openIntent, getString(R.string.open_pdf)))
} catch (_: ActivityNotFoundException) { } 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.mydomain.myscan.BuildConfig import org.mydomain.myscan.BuildConfig
@@ -63,10 +64,11 @@ fun AboutScreen(onBack: () -> Unit, onViewLibraries: () -> Unit) {
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { Text("About") }, title = { Text(stringResource(R.string.about)) },
navigationIcon = { navigationIcon = {
IconButton(onClick = onBack) { 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()) .verticalScroll(rememberScrollState())
) { ) {
Text( Text(
"MyScan", stringResource(R.string.app_name),
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall
) )
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
Text( Text(
"A simple and respectful application to scan your documents.", stringResource(R.string.app_tagline),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
@@ -107,7 +109,7 @@ fun AboutContent(
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Text( Text(
"Version", stringResource(R.string.version),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
Text(BuildConfig.VERSION_NAME) Text(BuildConfig.VERSION_NAME)
@@ -115,15 +117,15 @@ fun AboutContent(
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Text( Text(
"License", stringResource(R.string.license),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
Text( Text(
"This application is licensed under the GNU General Public License v3.0.", stringResource(R.string.licensed_under),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
Text( Text(
text = "View the full license", text = stringResource(R.string.view_the_full_license),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.clickable { showLicenseDialog.value = true }, modifier = Modifier.clickable { showLicenseDialog.value = true },
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary
@@ -132,15 +134,15 @@ fun AboutContent(
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Text( Text(
"Libraries", stringResource(R.string.libraries),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
Text( Text(
"This application uses several open-source libraries, including:\n" + stringResource(R.string.libraries_intro) +
"• CameraX\n• Jetpack Compose\n• LiteRT\n• OpenCV\n• PDFBox", "\n• CameraX\n• Jetpack Compose\n• LiteRT\n• OpenCV\n• PDFBox",
style = MaterialTheme.typography.bodyMedium) style = MaterialTheme.typography.bodyMedium)
Text( Text(
text = "View full list", text = stringResource(R.string.view_full_list),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.clickable(onClick = onViewLibraries), modifier = Modifier.clickable(onClick = onViewLibraries),
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary

View File

@@ -29,7 +29,9 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.mydomain.myscan.R
@Composable @Composable
fun MainActionButton( fun MainActionButton(
@@ -81,7 +83,7 @@ fun AboutScreenNavButton(
) { ) {
Icon( Icon(
imageVector = Icons.Outlined.Info, imageVector = Icons.Outlined.Info,
contentDescription = "About", contentDescription = stringResource(R.string.about),
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)) 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 com.google.common.util.concurrent.ListenableFuture
import org.mydomain.myscan.LiveAnalysisState import org.mydomain.myscan.LiveAnalysisState
import org.mydomain.myscan.Point import org.mydomain.myscan.Point
import org.mydomain.myscan.R
import org.mydomain.myscan.scaledTo import org.mydomain.myscan.scaledTo
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
@@ -74,7 +75,8 @@ fun CameraPreview(
ActivityResultContracts.RequestPermission() ActivityResultContracts.RequestPermission()
) { isGranted: Boolean -> ) { isGranted: Boolean ->
if (!isGranted) { 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.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -79,6 +80,7 @@ import kotlinx.coroutines.delay
import org.mydomain.myscan.LiveAnalysisState import org.mydomain.myscan.LiveAnalysisState
import org.mydomain.myscan.MainViewModel import org.mydomain.myscan.MainViewModel
import org.mydomain.myscan.MainViewModel.CaptureState import org.mydomain.myscan.MainViewModel.CaptureState
import org.mydomain.myscan.R
import org.mydomain.myscan.Screen import org.mydomain.myscan.Screen
import org.mydomain.myscan.ui.theme.MyScanTheme import org.mydomain.myscan.ui.theme.MyScanTheme
@@ -203,7 +205,9 @@ private fun CameraScreenScaffold(
) { ) {
CameraPreviewWithOverlay(cameraPreview, cameraUiState, Modifier.align(Alignment.BottomCenter)) CameraPreviewWithOverlay(cameraPreview, cameraUiState, Modifier.align(Alignment.BottomCenter))
Box( Box(
modifier = Modifier.fillMaxSize().padding(innerPadding) modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
) { ) {
AboutScreenNavButton( AboutScreenNavButton(
onClick = toAboutScreen, onClick = toAboutScreen,
@@ -356,7 +360,7 @@ private fun CameraPreviewWithOverlay(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = "No document detected", text = stringResource(R.string.error_no_document),
color = Color.White, color = Color.White,
fontSize = 16.sp fontSize = 16.sp
) )

View File

@@ -59,6 +59,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -67,6 +68,7 @@ import net.engawapg.lib.zoomable.rememberZoomState
import net.engawapg.lib.zoomable.zoomable import net.engawapg.lib.zoomable.zoomable
import org.mydomain.myscan.Navigation import org.mydomain.myscan.Navigation
import org.mydomain.myscan.PdfGenerationActions import org.mydomain.myscan.PdfGenerationActions
import org.mydomain.myscan.R
import org.mydomain.myscan.ui.PdfGenerationUiState import org.mydomain.myscan.ui.PdfGenerationUiState
import org.mydomain.myscan.ui.theme.MyScanTheme import org.mydomain.myscan.ui.theme.MyScanTheme
@@ -102,10 +104,11 @@ fun DocumentScreen(
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
titleContentColor = MaterialTheme.colorScheme.onSurface, titleContentColor = MaterialTheme.colorScheme.onSurface,
), ),
title = { Text("Document") }, title = { Text(stringResource(R.string.document)) },
navigationIcon = { navigationIcon = {
IconButton(onClick = navigation.toCameraScreen) { IconButton(onClick = navigation.toCameraScreen) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") Icon(Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back))
} }
}, },
actions = { actions = {
@@ -161,7 +164,9 @@ private fun DocumentPreview(
LaunchedEffect(imageId) { LaunchedEffect(imageId) {
zoomState.reset() zoomState.reset()
} }
Box(modifier = Modifier.fillMaxSize(0.92f).align(Alignment.Center)) { Box(modifier = Modifier
.fillMaxSize(0.92f)
.align(Alignment.Center)) {
Image( Image(
bitmap = imageBitmap, bitmap = imageBitmap,
contentDescription = null, contentDescription = null,
@@ -174,7 +179,7 @@ private fun DocumentPreview(
} }
SecondaryActionButton( SecondaryActionButton(
Icons.Outlined.Delete, Icons.Outlined.Delete,
contentDescription = "Delete page", contentDescription = stringResource(R.string.delete_page),
onClick = { onDeleteImage(imageId) }, onClick = { onDeleteImage(imageId) },
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
@@ -212,7 +217,7 @@ private fun PageList(
SecondaryActionButton( SecondaryActionButton(
icon = Icons.Default.Add, icon = Icons.Default.Add,
onClick = toCameraScreen, onClick = toCameraScreen,
contentDescription = "Add page", contentDescription = stringResource(R.string.add_page),
modifier = Modifier modifier = Modifier
.align(Alignment.CenterEnd) .align(Alignment.CenterEnd)
.padding(8.dp) .padding(8.dp)
@@ -236,12 +241,12 @@ private fun BottomBar(
MainActionButton( MainActionButton(
onClick = { showPdfDialog.value = true }, onClick = { showPdfDialog.value = true },
icon = Icons.Default.PictureAsPdf, icon = Icons.Default.PictureAsPdf,
text = "Export PDF", text = stringResource(R.string.export_pdf),
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
SecondaryActionButton( SecondaryActionButton(
icon = Icons.Default.RestartAlt, icon = Icons.Default.RestartAlt,
contentDescription = "Restart", contentDescription = stringResource(R.string.restart),
onClick = { showNewDocDialog.value = true }, onClick = { showNewDocDialog.value = true },
modifier = Modifier.padding(vertical = 8.dp) modifier = Modifier.padding(vertical = 8.dp)
) )
@@ -252,19 +257,19 @@ private fun BottomBar(
@Composable @Composable
fun NewDocumentDialog(onConfirm: () -> Unit, showDialog: MutableState<Boolean>) { fun NewDocumentDialog(onConfirm: () -> Unit, showDialog: MutableState<Boolean>) {
AlertDialog( AlertDialog(
title = { Text("New document") }, title = { Text(stringResource(R.string.new_document)) },
text = { Text("The current document will be lost if you haven't saved it. Do you want to continue?") }, text = { Text(stringResource(R.string.new_document_warning)) },
confirmButton = { confirmButton = {
TextButton (onClick = { TextButton (onClick = {
showDialog.value = false showDialog.value = false
onConfirm() onConfirm()
}) { }) {
Text("Yes", fontWeight = FontWeight.Bold) Text(stringResource(R.string.yes), fontWeight = FontWeight.Bold)
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showDialog.value = false }) { TextButton(onClick = { showDialog.value = false }) {
Text("Cancel", fontWeight = FontWeight.Bold) Text(stringResource(R.string.cancel), fontWeight = FontWeight.Bold)
} }
}, },
onDismissRequest = { showDialog.value = false }, onDismissRequest = { showDialog.value = false },

View File

@@ -29,6 +29,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
import com.mikepenz.aboutlibraries.ui.compose.android.rememberLibraries import com.mikepenz.aboutlibraries.ui.compose.android.rememberLibraries
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
@@ -40,10 +41,11 @@ fun LibrariesScreen(onBack: () -> Unit) {
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { Text("Open-source libraries") }, title = { Text(stringResource(R.string.libraries_open_source)) },
navigationIcon = { navigationIcon = {
IconButton(onClick = onBack) { 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.net.toUri import androidx.core.net.toUri
import org.mydomain.myscan.GeneratedPdf import org.mydomain.myscan.GeneratedPdf
import org.mydomain.myscan.PdfGenerationActions import org.mydomain.myscan.PdfGenerationActions
import org.mydomain.myscan.R
import org.mydomain.myscan.ui.PdfGenerationUiState import org.mydomain.myscan.ui.PdfGenerationUiState
import org.mydomain.myscan.ui.theme.MyScanTheme import org.mydomain.myscan.ui.theme.MyScanTheme
import java.io.File import java.io.File
@@ -124,7 +126,7 @@ fun PdfGenerationBottomSheet(
.size(34.dp) .size(34.dp)
.padding(end = 8.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)) Spacer(Modifier.height(16.dp))
@@ -132,7 +134,7 @@ fun PdfGenerationBottomSheet(
OutlinedTextField( OutlinedTextField(
value = filename, value = filename,
onValueChange = onFilenameChange, onValueChange = onFilenameChange,
label = { Text("Filename") }, label = { Text(stringResource(R.string.filename)) },
singleLine = true, singleLine = true,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -180,16 +182,16 @@ private fun MainActions(
onClick = onShare, onClick = onShare,
enabled = pdf != null, enabled = pdf != null,
icon = Icons.Default.Share, icon = Icons.Default.Share,
iconDescription = "Share", iconDescription = stringResource(R.string.share),
text = "Share", text = stringResource(R.string.share),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
MainActionButton( MainActionButton(
onClick = onSave, onClick = onSave,
enabled = pdf != null, enabled = pdf != null,
icon = Icons.Default.Download, icon = Icons.Default.Download,
iconDescription = "Save", iconDescription = stringResource(R.string.save),
text = "Save", text = stringResource(R.string.save),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
} }
@@ -206,12 +208,12 @@ private fun SavePdfBar(onOpen: () -> Unit, saveDirectoryName: String) {
.padding(vertical = 8.dp, horizontal = 16.dp), .padding(vertical = 8.dp, horizontal = 16.dp),
) { ) {
Text( Text(
text = "PDF saved to $saveDirectoryName", text = stringResource(R.string.pdf_saved_to, saveDirectoryName),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
MainActionButton( MainActionButton(
onClick = onOpen, onClick = onOpen,
text = "Open", text = stringResource(R.string.open),
icon = Icons.AutoMirrored.Filled.OpenInNew, icon = Icons.AutoMirrored.Filled.OpenInNew,
) )
} }
@@ -220,7 +222,7 @@ private fun SavePdfBar(onOpen: () -> Unit, saveDirectoryName: String) {
@Composable @Composable
private fun ErrorBar(errorMessage: String) { private fun ErrorBar(errorMessage: String) {
Text( Text(
text = "Error: $errorMessage", text = stringResource(R.string.error, errorMessage),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error, color = MaterialTheme.colorScheme.error,
modifier = Modifier modifier = Modifier
@@ -239,7 +241,7 @@ private fun CloseButton(onDismiss: () -> Unit) {
) { ) {
Icon( Icon(
imageVector = Icons.Default.Close, 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 { 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) else Formatter.formatShortFileSize(context, sizeInBytes)
} }

View File

@@ -1,5 +1,39 @@
<resources> <resources>
<string name="about">About</string>
<string name="add_page">Add page</string>
<string name="app_name">MyScan</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"> <plurals name="page_count">
<item quantity="one">%d page</item> <item quantity="one">%d page</item>
<item quantity="other">%d pages</item> <item quantity="other">%d pages</item>