Translate the entire repo to English (UI strings, README, CI, comments)

Prep for public distribution. All user-facing strings, the README, the Gitea
workflow step names/comments and the keystore example are now English.
Follow-up i18n (string resources + locale files) is tracked in #5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 18:52:01 +02:00
parent 953efaf168
commit 3c77e9ab3e
11 changed files with 140 additions and 133 deletions

View File

@@ -115,7 +115,7 @@ class ScanViewModel(app: Application) : AndroidViewModel(app) {
val s = settings.value
val token = settingsRepo.getToken()
if (s.paperlessUrl.isBlank() || token.isNullOrBlank()) {
return PaperlessClient.Result.Failure("Paperless is nog niet ingesteld (URL + token).")
return PaperlessClient.Result.Failure("Paperless isn't set up yet (URL + token).")
}
return PaperlessClient(s.paperlessUrl, token).upload(pdf, title)
}
@@ -133,7 +133,7 @@ class ScanViewModel(app: Application) : AndroidViewModel(app) {
suspend fun testPaperless(url: String, token: String): PaperlessClient.Result {
if (url.isBlank() || token.isBlank()) {
return PaperlessClient.Result.Failure("Vul eerst URL en token in.")
return PaperlessClient.Result.Failure("Enter a URL and token first.")
}
return PaperlessClient(url.trim(), token.trim()).testConnection()
}

View File

@@ -53,11 +53,11 @@ class PaperlessClient(
client.newCall(request).execute().use { resp ->
when {
resp.isSuccessful -> Result.Success
resp.code == 401 || resp.code == 403 -> Result.Failure("Ongeldig token (${resp.code}).")
else -> Result.Failure("Server antwoordde met ${resp.code}.")
resp.code == 401 || resp.code == 403 -> Result.Failure("Invalid token (${resp.code}).")
else -> Result.Failure("Server responded with ${resp.code}.")
}
}
}.getOrElse { Result.Failure(it.message ?: "Verbinding mislukt.") }
}.getOrElse { Result.Failure(it.message ?: "Connection failed.") }
}
/**
@@ -85,12 +85,12 @@ class PaperlessClient(
val posted: kotlin.Result<String?> = withContext(NonCancellable) {
runCatching {
client.newCall(request).execute().use { resp ->
if (!resp.isSuccessful) error("Upload mislukt: ${resp.code} ${resp.message}")
if (!resp.isSuccessful) error("Upload failed: ${resp.code} ${resp.message}")
resp.body?.string()?.trim()?.trim('"')?.takeIf { it.isNotBlank() }
}
}
}
val taskId = posted.getOrElse { return@withContext Result.Failure(it.message ?: "Upload mislukt.") }
val taskId = posted.getOrElse { return@withContext Result.Failure(it.message ?: "Upload failed.") }
// Older servers may not return a usable id; then we can only confirm acceptance.
if (taskId == null || taskId.equals("OK", ignoreCase = true)) return@withContext Result.Pending
@@ -119,7 +119,7 @@ class PaperlessClient(
"FAILURE" -> {
val msg = task.result.orEmpty()
return if (msg.contains("duplicate", ignoreCase = true)) Result.Duplicate
else Result.Failure(msg.ifBlank { "Paperless kon het document niet verwerken." })
else Result.Failure(msg.ifBlank { "Paperless could not process the document." })
}
// PENDING / STARTED / RETRY / unknown → keep waiting.
}

View File

@@ -21,7 +21,7 @@ object BitmapIo {
val opts = BitmapFactory.Options().apply { inSampleSize = sample }
val bitmap = BitmapFactory.decodeFile(file.absolutePath, opts)
?: error("Kon afbeelding niet decoderen: ${file.name}")
?: error("Could not decode image: ${file.name}")
val orientation = ExifInterface(file.absolutePath)
.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)

View File

@@ -77,13 +77,13 @@ private fun PermissionRequest(onRequest: () -> Unit) {
Box(Modifier.fillMaxSize().padding(32.dp), contentAlignment = Alignment.Center) {
androidx.compose.foundation.layout.Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
"PaperScan heeft cameratoegang nodig om documenten te scannen. " +
"Er wordt niets naar de cloud gestuurd.",
"PaperScan needs camera access to scan documents. " +
"Nothing is sent to the cloud.",
color = Color.White,
textAlign = TextAlign.Center,
)
androidx.compose.foundation.layout.Spacer(Modifier.size(16.dp))
Button(onClick = onRequest) { Text("Toegang geven") }
Button(onClick = onRequest) { Text("Grant access") }
}
}
}
@@ -143,7 +143,7 @@ private fun CameraContent(vm: ScanViewModel, navController: androidx.navigation.
onClick = { navController.navigate(Routes.SETTINGS) },
modifier = Modifier.align(Alignment.TopEnd).statusBarsPadding().padding(8.dp),
) {
Icon(Icons.Filled.Settings, contentDescription = "Instellingen", tint = Color.White)
Icon(Icons.Filled.Settings, contentDescription = "Settings", tint = Color.White)
}
// Bottom controls.
@@ -204,7 +204,7 @@ private fun CameraContent(vm: ScanViewModel, navController: androidx.navigation.
) {
Icon(
Icons.Filled.Check,
contentDescription = "Klaar",
contentDescription = "Done",
tint = if (pages.isEmpty()) Color.Gray else Color.White,
)
}

View File

@@ -135,9 +135,9 @@ fun CropScreen(vm: ScanViewModel, navController: NavController, pageId: String)
Modifier.fillMaxWidth().padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
FilterOption("Kleur", filter == ScanFilter.COLOR) { filter = ScanFilter.COLOR }
FilterOption("Grijs", filter == ScanFilter.GRAYSCALE) { filter = ScanFilter.GRAYSCALE }
FilterOption("Zwart-wit", filter == ScanFilter.BLACK_WHITE) { filter = ScanFilter.BLACK_WHITE }
FilterOption("Color", filter == ScanFilter.COLOR) { filter = ScanFilter.COLOR }
FilterOption("Grayscale", filter == ScanFilter.GRAYSCALE) { filter = ScanFilter.GRAYSCALE }
FilterOption("Black & white", filter == ScanFilter.BLACK_WHITE) { filter = ScanFilter.BLACK_WHITE }
}
Row(
@@ -145,7 +145,7 @@ fun CropScreen(vm: ScanViewModel, navController: NavController, pageId: String)
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
OutlinedButton(onClick = { vm.rotate(pageId) }, modifier = Modifier.weight(1f)) {
Text("Draaien")
Text("Rotate")
}
OutlinedButton(
onClick = {
@@ -153,7 +153,7 @@ fun CropScreen(vm: ScanViewModel, navController: NavController, pageId: String)
full.forEachIndexed { i, p -> corners[i].value = PointF(p.x, p.y) }
},
modifier = Modifier.weight(1f),
) { Text("Herstel") }
) { Text("Reset") }
Button(
onClick = {
vm.updateCorners(pageId, corners.map { PointF(it.value.x, it.value.y) })
@@ -161,7 +161,7 @@ fun CropScreen(vm: ScanViewModel, navController: NavController, pageId: String)
navController.popBackStack()
},
modifier = Modifier.weight(1f),
) { Text("Toepassen") }
) { Text("Apply") }
}
}
}

View File

@@ -70,7 +70,7 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
fun mode() = if (a4) PageSizeMode.A4 else PageSizeMode.ORIGINAL
Scaffold(
topBar = { TopAppBar(title = { Text("Exporteren (${pages.size} pagina's)") }) },
topBar = { TopAppBar(title = { Text("Export (${pages.size} pages)") }) },
) { padding ->
Column(
Modifier.fillMaxSize().padding(padding).padding(16.dp),
@@ -79,7 +79,7 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
OutlinedTextField(
value = fileName,
onValueChange = { fileName = it },
label = { Text("Bestandsnaam") },
label = { Text("File name") },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
@@ -89,12 +89,12 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
selected = a4,
onClick = { a4 = true },
shape = SegmentedButtonDefaults.itemShape(0, 2),
) { Text("A4-pagina") }
) { Text("A4 page") }
SegmentedButton(
selected = !a4,
onClick = { a4 = false },
shape = SegmentedButtonDefaults.itemShape(1, 2),
) { Text("Originele grootte") }
) { Text("Original size") }
}
Button(
@@ -113,9 +113,9 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(Intent.createChooser(share, "PDF delen"))
context.startActivity(Intent.createChooser(share, "Share PDF"))
} catch (e: Exception) {
status = "Fout bij exporteren: ${e.message}"
status = "Export failed: ${e.message}"
} finally {
busy = false
}
@@ -126,7 +126,7 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
) {
Icon(Icons.Filled.Share, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text("Delen / opslaan")
Text("Share / save")
}
Button(
@@ -140,19 +140,19 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
val pdf = vm.exportPdf(fileName, mode())
when (val r = vm.uploadToPaperless(pdf, fileName)) {
is PaperlessClient.Result.Success ->
status = "Toegevoegd aan Paperless ✓"
status = "Added to Paperless ✓"
is PaperlessClient.Result.Duplicate ->
status = "Al aanwezig in Paperless (duplicaat) — niet opnieuw toegevoegd."
status = "Already in Paperless (duplicate) — not added again."
is PaperlessClient.Result.Pending ->
status = "Geüpload — Paperless is het nog aan het verwerken"
status = "Uploaded — Paperless is still processing"
is PaperlessClient.Result.Failure ->
status = r.message
}
} catch (e: CancellationException) {
// User tapped "Niet wachten": the document was already sent.
status = "Geüpload — Paperless verwerkt het verder op de achtergrond."
// User tapped "Don't wait": the document was already sent.
status = "Uploaded — Paperless will finish processing in the background."
} catch (e: Exception) {
status = "Fout: ${e.message}"
status = "Error: ${e.message}"
} finally {
busy = false
uploading = false
@@ -169,24 +169,24 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
if (!settings.paperlessUrl.isNotBlank() || !settings.hasToken) {
Text(
"Configureer eerst Paperless (server-URL + token) in de instellingen om te kunnen uploaden.",
"Configure Paperless (server URL + token) in settings first to upload.",
style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
color = Color(0xFF9A9A9A),
)
OutlinedButton(
onClick = { navController.navigate(Routes.SETTINGS) },
modifier = Modifier.fillMaxWidth(),
) { Text("Paperless instellen") }
) { Text("Set up Paperless") }
}
if (busy) {
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
CircularProgressIndicator(Modifier.height(24.dp))
Spacer(Modifier.width(12.dp))
Text(if (uploading) "Uploaden" else "Bezig…")
Text(if (uploading) "Uploading" else "Working…")
if (uploading) {
Spacer(Modifier.width(12.dp))
OutlinedButton(onClick = { uploadJob?.cancel() }) { Text("Niet wachten") }
OutlinedButton(onClick = { uploadJob?.cancel() }) { Text("Don't wait") }
}
}
}

View File

@@ -53,12 +53,12 @@ fun PagesScreen(vm: ScanViewModel, navController: NavController) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Pagina's (${pages.size})") },
title = { Text("Pages (${pages.size})") },
actions = {
IconButton(
onClick = { if (pages.isNotEmpty()) navController.navigate(Routes.EXPORT) },
) {
Icon(Icons.Filled.PictureAsPdf, contentDescription = "Exporteer PDF")
Icon(Icons.Filled.PictureAsPdf, contentDescription = "Export PDF")
}
},
)
@@ -67,7 +67,7 @@ fun PagesScreen(vm: ScanViewModel, navController: NavController) {
ExtendedFloatingActionButton(
onClick = { navController.navigate(Routes.CAMERA) },
icon = { Icon(Icons.Filled.Add, contentDescription = null) },
text = { Text("Pagina toevoegen") },
text = { Text("Add page") },
)
},
) { padding ->
@@ -122,24 +122,24 @@ private fun PageRow(
bitmap?.let {
Image(
bitmap = it.asImageBitmap(),
contentDescription = "Pagina ${index + 1}",
contentDescription = "Page ${index + 1}",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
)
}
}
Spacer(Modifier.width(12.dp))
Text("Pagina ${index + 1}", style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f))
Text("Page ${index + 1}", style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f))
Column {
IconButton(onClick = onUp, enabled = !isFirst) {
Icon(Icons.Filled.ArrowUpward, contentDescription = "Omhoog")
Icon(Icons.Filled.ArrowUpward, contentDescription = "Move up")
}
IconButton(onClick = onDown, enabled = !isLast) {
Icon(Icons.Filled.ArrowDownward, contentDescription = "Omlaag")
Icon(Icons.Filled.ArrowDownward, contentDescription = "Move down")
}
}
IconButton(onClick = onDelete) {
Icon(Icons.Filled.Delete, contentDescription = "Verwijderen", tint = Color(0xFFD32F2F))
Icon(Icons.Filled.Delete, contentDescription = "Delete", tint = Color(0xFFD32F2F))
}
}
}

View File

@@ -59,10 +59,10 @@ fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
TopAppBar(
title = { Text("Instellingen") },
title = { Text("Settings") },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Terug")
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
},
)
@@ -81,22 +81,22 @@ fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
OutlinedTextField(
value = url,
onValueChange = { url = it; testResult = null },
label = { Text("Server-URL (bv. https://paperless.thuis.lan)") },
label = { Text("Server URL (e.g. https://paperless.home.lan)") },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
OutlinedTextField(
value = token,
onValueChange = { token = it; testResult = null },
label = { Text("API-token") },
label = { Text("API token") },
singleLine = true,
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth(),
)
if (url.startsWith("http://")) {
Text(
"Je gebruikt http:// — je token en documenten gaan onversleuteld over het " +
"netwerk. Gebruik https:// als het even kan.",
"You're using http:// — your token and documents travel unencrypted over the " +
"network. Use https:// when you can.",
color = Color(0xFFB26A00),
style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
)
@@ -109,55 +109,55 @@ fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
testResult = null
scope.launch {
testResult = when (val r = vm.testPaperless(url, token)) {
is PaperlessClient.Result.Success -> "Verbinding OK ✓"
is PaperlessClient.Result.Success -> "Connection OK ✓"
is PaperlessClient.Result.Failure -> r.message
else -> "Onbekend antwoord van de server."
else -> "Unexpected response from the server."
}
testing = false
}
},
enabled = !testing,
modifier = Modifier.weight(1f),
) { Text("Verbinding testen") }
) { Text("Test connection") }
Button(
onClick = {
scope.launch {
vm.savePaperless(url, token)
snackbarHostState.showSnackbar("Instellingen opgeslagen")
snackbarHostState.showSnackbar("Settings saved")
}
},
modifier = Modifier.weight(1f),
) { Text("Opslaan") }
) { Text("Save") }
}
if (testing) {
Row(verticalAlignment = Alignment.CenterVertically) {
CircularProgressIndicator(Modifier.width(20.dp))
Spacer(Modifier.width(8.dp))
Text("Testen")
Text("Testing")
}
}
testResult?.let { Text(it, color = Color(0xFF1F6FEB)) }
Text(
"Standaardfilter",
"Default filter",
style = androidx.compose.material3.MaterialTheme.typography.titleMedium,
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
FilterChip(
selected = settings.defaultFilter == ScanFilter.COLOR,
onClick = { scope.launch { vm.setDefaultFilter(ScanFilter.COLOR) } },
label = { Text("Kleur") },
label = { Text("Color") },
)
FilterChip(
selected = settings.defaultFilter == ScanFilter.GRAYSCALE,
onClick = { scope.launch { vm.setDefaultFilter(ScanFilter.GRAYSCALE) } },
label = { Text("Grijs") },
label = { Text("Grayscale") },
)
FilterChip(
selected = settings.defaultFilter == ScanFilter.BLACK_WHITE,
onClick = { scope.launch { vm.setDefaultFilter(ScanFilter.BLACK_WHITE) } },
label = { Text("Zwart-wit") },
label = { Text("Black & white") },
)
}
}