2 Commits

Author SHA1 Message Date
sam
e02e69052d Add German, French, Spanish, Italian and Portuguese translations; v0.3.1
All checks were successful
Build APK / build (push) Successful in 6m9s
Seven UI languages now: English, Dutch, German, French, Spanish, Italian,
Portuguese. Each values-xx/strings.xml carries the full string set (verified
lint-clean). Bump to versionCode 6 / versionName 0.3.1.

Fixes #7

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 20:02:47 +02:00
sam
898ae76526 Internationalization: string resources + Dutch translation; v0.3.0
All checks were successful
Build APK / build (push) Successful in 5m55s
Move all user-facing strings into res/values/strings.xml (English base) and
add res/values-nl/strings.xml (Dutch). Composables use stringResource;
coroutine-set and non-composable strings (ViewModel, PaperlessClient) resolve
via Context.getString. Mark app_name translatable=false. Bump to 0.3.0.

Fixes #5

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 19:37:46 +02:00
16 changed files with 569 additions and 66 deletions

BIN
.claude/i18n.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -24,8 +24,8 @@ android {
applicationId = "eu.geyskens.pdfscan"
minSdk = 26
targetSdk = 35
versionCode = 4
versionName = "0.2.0"
versionCode = 6
versionName = "0.3.1"
// OpenCV native libs are large; ship only the ABIs real phones use.
ndk {

View File

@@ -115,9 +115,11 @@ 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 isn't set up yet (URL + token).")
return PaperlessClient.Result.Failure(
getApplication<Application>().getString(R.string.err_paperless_not_setup)
)
}
return PaperlessClient(s.paperlessUrl, token).upload(pdf, title)
return PaperlessClient(s.paperlessUrl, token, getApplication<Application>()).upload(pdf, title)
}
// --- Settings pass-throughs (SettingsScreen) ---
@@ -133,9 +135,11 @@ class ScanViewModel(app: Application) : AndroidViewModel(app) {
suspend fun testPaperless(url: String, token: String): PaperlessClient.Result {
if (url.isBlank() || token.isBlank()) {
return PaperlessClient.Result.Failure("Enter a URL and token first.")
return PaperlessClient.Result.Failure(
getApplication<Application>().getString(R.string.err_enter_url_token)
)
}
return PaperlessClient(url.trim(), token.trim()).testConnection()
return PaperlessClient(url.trim(), token.trim(), getApplication<Application>()).testConnection()
}
fun clearSession() {

View File

@@ -1,5 +1,7 @@
package eu.geyskens.pdfscan.paperless
import android.content.Context
import eu.geyskens.pdfscan.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
@@ -22,6 +24,7 @@ import java.util.concurrent.TimeUnit
class PaperlessClient(
private val baseUrl: String,
private val token: String,
private val context: Context,
) {
private val client = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
@@ -53,11 +56,12 @@ class PaperlessClient(
client.newCall(request).execute().use { resp ->
when {
resp.isSuccessful -> Result.Success
resp.code == 401 || resp.code == 403 -> Result.Failure("Invalid token (${resp.code}).")
else -> Result.Failure("Server responded with ${resp.code}.")
resp.code == 401 || resp.code == 403 ->
Result.Failure(context.getString(R.string.err_invalid_token, resp.code))
else -> Result.Failure(context.getString(R.string.err_server_responded, resp.code))
}
}
}.getOrElse { Result.Failure(it.message ?: "Connection failed.") }
}.getOrElse { Result.Failure(it.message ?: context.getString(R.string.err_connection_failed)) }
}
/**
@@ -85,12 +89,16 @@ class PaperlessClient(
val posted: kotlin.Result<String?> = withContext(NonCancellable) {
runCatching {
client.newCall(request).execute().use { resp ->
if (!resp.isSuccessful) error("Upload failed: ${resp.code} ${resp.message}")
if (!resp.isSuccessful) {
error(context.getString(R.string.err_upload_failed_code, resp.code, resp.message))
}
resp.body?.string()?.trim()?.trim('"')?.takeIf { it.isNotBlank() }
}
}
}
val taskId = posted.getOrElse { return@withContext Result.Failure(it.message ?: "Upload failed.") }
val taskId = posted.getOrElse {
return@withContext Result.Failure(it.message ?: context.getString(R.string.err_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 +127,7 @@ class PaperlessClient(
"FAILURE" -> {
val msg = task.result.orEmpty()
return if (msg.contains("duplicate", ignoreCase = true)) Result.Duplicate
else Result.Failure(msg.ifBlank { "Paperless could not process the document." })
else Result.Failure(msg.ifBlank { context.getString(R.string.err_paperless_process) })
}
// PENDING / STARTED / RETRY / unknown → keep waiting.
}

View File

@@ -51,6 +51,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.concurrent.futures.await
@@ -58,6 +59,7 @@ import androidx.core.content.ContextCompat
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import eu.geyskens.pdfscan.R
import eu.geyskens.pdfscan.Routes
import eu.geyskens.pdfscan.ScanViewModel
import eu.geyskens.pdfscan.scan.DocumentAnalyzer
@@ -84,13 +86,12 @@ private fun PermissionRequest(onRequest: () -> Unit) {
Box(Modifier.fillMaxSize().padding(32.dp), contentAlignment = Alignment.Center) {
androidx.compose.foundation.layout.Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
"PaperScan needs camera access to scan documents. " +
"Nothing is sent to the cloud.",
stringResource(R.string.camera_permission_rationale),
color = Color.White,
textAlign = TextAlign.Center,
)
androidx.compose.foundation.layout.Spacer(Modifier.size(16.dp))
Button(onClick = onRequest) { Text("Grant access") }
Button(onClick = onRequest) { Text(stringResource(R.string.grant_access)) }
}
}
}
@@ -198,7 +199,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 = "Settings", tint = Color.White)
Icon(Icons.Filled.Settings, contentDescription = stringResource(R.string.settings), tint = Color.White)
}
// Bottom controls.
@@ -259,7 +260,7 @@ private fun CameraContent(vm: ScanViewModel, navController: androidx.navigation.
) {
Icon(
Icons.Filled.Check,
contentDescription = "Done",
contentDescription = stringResource(R.string.done),
tint = if (pages.isEmpty()) Color.Gray else Color.White,
)
}

View File

@@ -34,9 +34,11 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import eu.geyskens.pdfscan.R
import eu.geyskens.pdfscan.ScanViewModel
import eu.geyskens.pdfscan.data.ScanFilter
import androidx.compose.foundation.Canvas
@@ -135,9 +137,9 @@ fun CropScreen(vm: ScanViewModel, navController: NavController, pageId: String)
Modifier.fillMaxWidth().padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
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 }
FilterOption(stringResource(R.string.filter_color), filter == ScanFilter.COLOR) { filter = ScanFilter.COLOR }
FilterOption(stringResource(R.string.filter_grayscale), filter == ScanFilter.GRAYSCALE) { filter = ScanFilter.GRAYSCALE }
FilterOption(stringResource(R.string.filter_bw), filter == ScanFilter.BLACK_WHITE) { filter = ScanFilter.BLACK_WHITE }
}
Row(
@@ -145,7 +147,7 @@ fun CropScreen(vm: ScanViewModel, navController: NavController, pageId: String)
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
OutlinedButton(onClick = { vm.rotate(pageId) }, modifier = Modifier.weight(1f)) {
Text("Rotate")
Text(stringResource(R.string.rotate))
}
OutlinedButton(
onClick = {
@@ -153,7 +155,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("Reset") }
) { Text(stringResource(R.string.reset)) }
Button(
onClick = {
vm.updateCorners(pageId, corners.map { PointF(it.value.x, it.value.y) })
@@ -161,7 +163,7 @@ fun CropScreen(vm: ScanViewModel, navController: NavController, pageId: String)
navController.popBackStack()
},
modifier = Modifier.weight(1f),
) { Text("Apply") }
) { Text(stringResource(R.string.apply)) }
}
}
}

View File

@@ -36,8 +36,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import eu.geyskens.pdfscan.R
import eu.geyskens.pdfscan.Routes
import eu.geyskens.pdfscan.ScanViewModel
import eu.geyskens.pdfscan.paperless.PaperlessClient
@@ -70,7 +72,7 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
fun mode() = if (a4) PageSizeMode.A4 else PageSizeMode.ORIGINAL
Scaffold(
topBar = { TopAppBar(title = { Text("Export (${pages.size} pages)") }) },
topBar = { TopAppBar(title = { Text(stringResource(R.string.export_title, pages.size)) }) },
) { padding ->
Column(
Modifier.fillMaxSize().padding(padding).padding(16.dp),
@@ -79,7 +81,7 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
OutlinedTextField(
value = fileName,
onValueChange = { fileName = it },
label = { Text("File name") },
label = { Text(stringResource(R.string.file_name)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
@@ -89,12 +91,12 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
selected = a4,
onClick = { a4 = true },
shape = SegmentedButtonDefaults.itemShape(0, 2),
) { Text("A4 page") }
) { Text(stringResource(R.string.page_size_a4)) }
SegmentedButton(
selected = !a4,
onClick = { a4 = false },
shape = SegmentedButtonDefaults.itemShape(1, 2),
) { Text("Original size") }
) { Text(stringResource(R.string.page_size_original)) }
}
Button(
@@ -113,9 +115,9 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(Intent.createChooser(share, "Share PDF"))
context.startActivity(Intent.createChooser(share, context.getString(R.string.share_pdf)))
} catch (e: Exception) {
status = "Export failed: ${e.message}"
status = context.getString(R.string.export_failed, e.message ?: "")
} finally {
busy = false
}
@@ -126,7 +128,7 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
) {
Icon(Icons.Filled.Share, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text("Share / save")
Text(stringResource(R.string.share_save))
}
Button(
@@ -140,19 +142,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 = "Added to Paperless ✓"
status = context.getString(R.string.upload_added)
is PaperlessClient.Result.Duplicate ->
status = "Already in Paperless (duplicate) — not added again."
status = context.getString(R.string.upload_duplicate)
is PaperlessClient.Result.Pending ->
status = "Uploaded — Paperless is still processing…"
status = context.getString(R.string.upload_pending)
is PaperlessClient.Result.Failure ->
status = r.message
}
} catch (e: CancellationException) {
// User tapped "Don't wait": the document was already sent.
status = "Uploaded — Paperless will finish processing in the background."
status = context.getString(R.string.upload_background)
} catch (e: Exception) {
status = "Error: ${e.message}"
status = context.getString(R.string.error_generic, e.message ?: "")
} finally {
busy = false
uploading = false
@@ -164,29 +166,29 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
) {
Icon(Icons.Filled.CloudUpload, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text("Upload naar Paperless")
Text(stringResource(R.string.upload_to_paperless))
}
if (!settings.paperlessUrl.isNotBlank() || !settings.hasToken) {
Text(
"Configure Paperless (server URL + token) in settings first to upload.",
stringResource(R.string.paperless_not_configured),
style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
color = Color(0xFF9A9A9A),
)
OutlinedButton(
onClick = { navController.navigate(Routes.SETTINGS) },
modifier = Modifier.fillMaxWidth(),
) { Text("Set up Paperless") }
) { Text(stringResource(R.string.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) "Uploading" else "Working")
Text(if (uploading) stringResource(R.string.uploading) else stringResource(R.string.working))
if (uploading) {
Spacer(Modifier.width(12.dp))
OutlinedButton(onClick = { uploadJob?.cancel() }) { Text("Don't wait") }
OutlinedButton(onClick = { uploadJob?.cancel() }) { Text(stringResource(R.string.dont_wait)) }
}
}
}

View File

@@ -39,8 +39,10 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import eu.geyskens.pdfscan.R
import eu.geyskens.pdfscan.Routes
import eu.geyskens.pdfscan.ScanViewModel
import eu.geyskens.pdfscan.data.Page
@@ -53,12 +55,12 @@ fun PagesScreen(vm: ScanViewModel, navController: NavController) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Pages (${pages.size})") },
title = { Text(stringResource(R.string.pages_title, pages.size)) },
actions = {
IconButton(
onClick = { if (pages.isNotEmpty()) navController.navigate(Routes.EXPORT) },
) {
Icon(Icons.Filled.PictureAsPdf, contentDescription = "Export PDF")
Icon(Icons.Filled.PictureAsPdf, contentDescription = stringResource(R.string.export_pdf))
}
},
)
@@ -67,7 +69,7 @@ fun PagesScreen(vm: ScanViewModel, navController: NavController) {
ExtendedFloatingActionButton(
onClick = { navController.navigate(Routes.CAMERA) },
icon = { Icon(Icons.Filled.Add, contentDescription = null) },
text = { Text("Add page") },
text = { Text(stringResource(R.string.add_page)) },
)
},
) { padding ->
@@ -122,24 +124,24 @@ private fun PageRow(
bitmap?.let {
Image(
bitmap = it.asImageBitmap(),
contentDescription = "Page ${index + 1}",
contentDescription = stringResource(R.string.page_number, index + 1),
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
)
}
}
Spacer(Modifier.width(12.dp))
Text("Page ${index + 1}", style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f))
Text(stringResource(R.string.page_number, index + 1), style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f))
Column {
IconButton(onClick = onUp, enabled = !isFirst) {
Icon(Icons.Filled.ArrowUpward, contentDescription = "Move up")
Icon(Icons.Filled.ArrowUpward, contentDescription = stringResource(R.string.move_up))
}
IconButton(onClick = onDown, enabled = !isLast) {
Icon(Icons.Filled.ArrowDownward, contentDescription = "Move down")
Icon(Icons.Filled.ArrowDownward, contentDescription = stringResource(R.string.move_down))
}
}
IconButton(onClick = onDelete) {
Icon(Icons.Filled.Delete, contentDescription = "Delete", tint = Color(0xFFD32F2F))
Icon(Icons.Filled.Delete, contentDescription = stringResource(R.string.delete), tint = Color(0xFFD32F2F))
}
}
}

View File

@@ -35,9 +35,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import eu.geyskens.pdfscan.R
import eu.geyskens.pdfscan.ScanViewModel
import eu.geyskens.pdfscan.data.ScanFilter
import eu.geyskens.pdfscan.paperless.PaperlessClient
@@ -47,6 +50,7 @@ import kotlinx.coroutines.launch
@Composable
fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val settings by vm.settings.collectAsState()
var url by remember { mutableStateOf(settings.paperlessUrl) }
@@ -59,10 +63,10 @@ fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
TopAppBar(
title = { Text("Settings") },
title = { Text(stringResource(R.string.settings)) },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
}
},
)
@@ -76,27 +80,26 @@ fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Text("Paperless-ngx", style = androidx.compose.material3.MaterialTheme.typography.titleMedium)
Text(stringResource(R.string.paperless_header), style = androidx.compose.material3.MaterialTheme.typography.titleMedium)
OutlinedTextField(
value = url,
onValueChange = { url = it; testResult = null },
label = { Text("Server URL (e.g. https://paperless.home.lan)") },
label = { Text(stringResource(R.string.server_url_label)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
OutlinedTextField(
value = token,
onValueChange = { token = it; testResult = null },
label = { Text("API token") },
label = { Text(stringResource(R.string.api_token_label)) },
singleLine = true,
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth(),
)
if (url.startsWith("http://")) {
Text(
"⚠ You're using http:// — your token and documents travel unencrypted over the " +
"network. Use https:// when you can.",
stringResource(R.string.http_warning),
color = Color(0xFFB26A00),
style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
)
@@ -109,55 +112,55 @@ fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
testResult = null
scope.launch {
testResult = when (val r = vm.testPaperless(url, token)) {
is PaperlessClient.Result.Success -> "Connection OK ✓"
is PaperlessClient.Result.Success -> context.getString(R.string.connection_ok)
is PaperlessClient.Result.Failure -> r.message
else -> "Unexpected response from the server."
else -> context.getString(R.string.unexpected_response)
}
testing = false
}
},
enabled = !testing,
modifier = Modifier.weight(1f),
) { Text("Test connection") }
) { Text(stringResource(R.string.test_connection)) }
Button(
onClick = {
scope.launch {
vm.savePaperless(url, token)
snackbarHostState.showSnackbar("Settings saved")
snackbarHostState.showSnackbar(context.getString(R.string.settings_saved))
}
},
modifier = Modifier.weight(1f),
) { Text("Save") }
) { Text(stringResource(R.string.save)) }
}
if (testing) {
Row(verticalAlignment = Alignment.CenterVertically) {
CircularProgressIndicator(Modifier.width(20.dp))
Spacer(Modifier.width(8.dp))
Text("Testing")
Text(stringResource(R.string.testing))
}
}
testResult?.let { Text(it, color = Color(0xFF1F6FEB)) }
Text(
"Default filter",
stringResource(R.string.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("Color") },
label = { Text(stringResource(R.string.filter_color)) },
)
FilterChip(
selected = settings.defaultFilter == ScanFilter.GRAYSCALE,
onClick = { scope.launch { vm.setDefaultFilter(ScanFilter.GRAYSCALE) } },
label = { Text("Grayscale") },
label = { Text(stringResource(R.string.filter_grayscale)) },
)
FilterChip(
selected = settings.defaultFilter == ScanFilter.BLACK_WHITE,
onClick = { scope.launch { vm.setDefaultFilter(ScanFilter.BLACK_WHITE) } },
label = { Text("Black & white") },
label = { Text(stringResource(R.string.filter_bw)) },
)
}
}

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Camera -->
<string name="camera_permission_rationale">PaperScan benötigt Kamerazugriff, um Dokumente zu scannen. Es wird nichts in die Cloud gesendet.</string>
<string name="grant_access">Zugriff erlauben</string>
<string name="settings">Einstellungen</string>
<string name="done">Fertig</string>
<!-- Pages -->
<string name="pages_title">Seiten (%1$d)</string>
<string name="add_page">Seite hinzufügen</string>
<string name="export_pdf">PDF exportieren</string>
<string name="page_number">Seite %1$d</string>
<string name="move_up">Nach oben</string>
<string name="move_down">Nach unten</string>
<string name="delete">Löschen</string>
<!-- Crop -->
<string name="filter_color">Farbe</string>
<string name="filter_grayscale">Graustufen</string>
<string name="filter_bw">Schwarzweiß</string>
<string name="rotate">Drehen</string>
<string name="reset">Zurücksetzen</string>
<string name="apply">Anwenden</string>
<!-- Export -->
<string name="export_title">Export (%1$d Seiten)</string>
<string name="file_name">Dateiname</string>
<string name="page_size_a4">A4-Seite</string>
<string name="page_size_original">Originalgröße</string>
<string name="share_save">Teilen / speichern</string>
<string name="share_pdf">PDF teilen</string>
<string name="upload_to_paperless">Zu Paperless hochladen</string>
<string name="set_up_paperless">Paperless einrichten</string>
<string name="paperless_not_configured">Richte zuerst Paperless (Server-URL + Token) in den Einstellungen ein, um hochzuladen.</string>
<string name="uploading">Wird hochgeladen…</string>
<string name="working">Wird bearbeitet…</string>
<string name="dont_wait">Nicht warten</string>
<string name="export_failed">Export fehlgeschlagen: %1$s</string>
<string name="error_generic">Fehler: %1$s</string>
<string name="upload_added">Zu Paperless hinzugefügt ✓</string>
<string name="upload_duplicate">Bereits in Paperless (Duplikat) — nicht erneut hinzugefügt.</string>
<string name="upload_pending">Hochgeladen — Paperless verarbeitet es noch…</string>
<string name="upload_background">Hochgeladen — Paperless verarbeitet es im Hintergrund weiter.</string>
<!-- Settings -->
<string name="back">Zurück</string>
<string name="paperless_header">Paperless-ngx</string>
<string name="server_url_label">Server-URL (z. B. https://paperless.home.lan)</string>
<string name="api_token_label">API-Token</string>
<string name="http_warning">⚠ Du verwendest http:// — dein Token und deine Dokumente werden unverschlüsselt übertragen. Verwende nach Möglichkeit https://.</string>
<string name="test_connection">Verbindung testen</string>
<string name="save">Speichern</string>
<string name="testing">Wird getestet…</string>
<string name="settings_saved">Einstellungen gespeichert ✓</string>
<string name="connection_ok">Verbindung OK ✓</string>
<string name="unexpected_response">Unerwartete Antwort vom Server.</string>
<string name="default_filter">Standardfilter</string>
<!-- Paperless client / errors -->
<string name="err_invalid_token">Ungültiges Token (%1$d).</string>
<string name="err_server_responded">Server antwortete mit %1$d.</string>
<string name="err_connection_failed">Verbindung fehlgeschlagen.</string>
<string name="err_upload_failed_code">Upload fehlgeschlagen: %1$d %2$s</string>
<string name="err_upload_failed">Upload fehlgeschlagen.</string>
<string name="err_paperless_process">Paperless konnte das Dokument nicht verarbeiten.</string>
<string name="err_paperless_not_setup">Paperless ist noch nicht eingerichtet (URL + Token).</string>
<string name="err_enter_url_token">Gib zuerst eine URL und ein Token ein.</string>
</resources>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Camera -->
<string name="camera_permission_rationale">PaperScan necesita acceso a la cámara para escanear documentos. No se envía nada a la nube.</string>
<string name="grant_access">Conceder acceso</string>
<string name="settings">Ajustes</string>
<string name="done">Listo</string>
<!-- Pages -->
<string name="pages_title">Páginas (%1$d)</string>
<string name="add_page">Añadir página</string>
<string name="export_pdf">Exportar PDF</string>
<string name="page_number">Página %1$d</string>
<string name="move_up">Subir</string>
<string name="move_down">Bajar</string>
<string name="delete">Eliminar</string>
<!-- Crop -->
<string name="filter_color">Color</string>
<string name="filter_grayscale">Escala de grises</string>
<string name="filter_bw">Blanco y negro</string>
<string name="rotate">Girar</string>
<string name="reset">Restablecer</string>
<string name="apply">Aplicar</string>
<!-- Export -->
<string name="export_title">Exportar (%1$d páginas)</string>
<string name="file_name">Nombre del archivo</string>
<string name="page_size_a4">Página A4</string>
<string name="page_size_original">Tamaño original</string>
<string name="share_save">Compartir / guardar</string>
<string name="share_pdf">Compartir PDF</string>
<string name="upload_to_paperless">Subir a Paperless</string>
<string name="set_up_paperless">Configurar Paperless</string>
<string name="paperless_not_configured">Configura primero Paperless (URL del servidor + token) en los ajustes para subir.</string>
<string name="uploading">Subiendo…</string>
<string name="working">Procesando…</string>
<string name="dont_wait">No esperar</string>
<string name="export_failed">Error al exportar: %1$s</string>
<string name="error_generic">Error: %1$s</string>
<string name="upload_added">Añadido a Paperless ✓</string>
<string name="upload_duplicate">Ya está en Paperless (duplicado) — no se añadió de nuevo.</string>
<string name="upload_pending">Subido — Paperless aún lo está procesando…</string>
<string name="upload_background">Subido — Paperless terminará de procesarlo en segundo plano.</string>
<!-- Settings -->
<string name="back">Atrás</string>
<string name="paperless_header">Paperless-ngx</string>
<string name="server_url_label">URL del servidor (p. ej. https://paperless.home.lan)</string>
<string name="api_token_label">Token de API</string>
<string name="http_warning">⚠ Estás usando http:// — tu token y tus documentos viajan sin cifrar por la red. Usa https:// si puedes.</string>
<string name="test_connection">Probar conexión</string>
<string name="save">Guardar</string>
<string name="testing">Probando…</string>
<string name="settings_saved">Ajustes guardados ✓</string>
<string name="connection_ok">Conexión correcta ✓</string>
<string name="unexpected_response">Respuesta inesperada del servidor.</string>
<string name="default_filter">Filtro predeterminado</string>
<!-- Paperless client / errors -->
<string name="err_invalid_token">Token no válido (%1$d).</string>
<string name="err_server_responded">El servidor respondió %1$d.</string>
<string name="err_connection_failed">Error de conexión.</string>
<string name="err_upload_failed_code">Error al subir: %1$d %2$s</string>
<string name="err_upload_failed">Error al subir.</string>
<string name="err_paperless_process">Paperless no pudo procesar el documento.</string>
<string name="err_paperless_not_setup">Paperless aún no está configurado (URL + token).</string>
<string name="err_enter_url_token">Introduce primero una URL y un token.</string>
</resources>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Camera -->
<string name="camera_permission_rationale">PaperScan a besoin d\'accéder à la caméra pour numériser des documents. Rien n\'est envoyé vers le cloud.</string>
<string name="grant_access">Autoriser l\'accès</string>
<string name="settings">Paramètres</string>
<string name="done">Terminé</string>
<!-- Pages -->
<string name="pages_title">Pages (%1$d)</string>
<string name="add_page">Ajouter une page</string>
<string name="export_pdf">Exporter le PDF</string>
<string name="page_number">Page %1$d</string>
<string name="move_up">Monter</string>
<string name="move_down">Descendre</string>
<string name="delete">Supprimer</string>
<!-- Crop -->
<string name="filter_color">Couleur</string>
<string name="filter_grayscale">Niveaux de gris</string>
<string name="filter_bw">Noir et blanc</string>
<string name="rotate">Pivoter</string>
<string name="reset">Réinitialiser</string>
<string name="apply">Appliquer</string>
<!-- Export -->
<string name="export_title">Exporter (%1$d pages)</string>
<string name="file_name">Nom du fichier</string>
<string name="page_size_a4">Page A4</string>
<string name="page_size_original">Taille d\'origine</string>
<string name="share_save">Partager / enregistrer</string>
<string name="share_pdf">Partager le PDF</string>
<string name="upload_to_paperless">Envoyer vers Paperless</string>
<string name="set_up_paperless">Configurer Paperless</string>
<string name="paperless_not_configured">Configurez d\'abord Paperless (URL du serveur + jeton) dans les paramètres pour envoyer.</string>
<string name="uploading">Envoi en cours…</string>
<string name="working">Traitement…</string>
<string name="dont_wait">Ne pas attendre</string>
<string name="export_failed">Échec de l\'export : %1$s</string>
<string name="error_generic">Erreur : %1$s</string>
<string name="upload_added">Ajouté à Paperless ✓</string>
<string name="upload_duplicate">Déjà dans Paperless (doublon) — non ajouté de nouveau.</string>
<string name="upload_pending">Envoyé — Paperless traite encore le document…</string>
<string name="upload_background">Envoyé — Paperless terminera le traitement en arrière-plan.</string>
<!-- Settings -->
<string name="back">Retour</string>
<string name="paperless_header">Paperless-ngx</string>
<string name="server_url_label">URL du serveur (ex. https://paperless.home.lan)</string>
<string name="api_token_label">Jeton d\'API</string>
<string name="http_warning">⚠ Vous utilisez http:// — votre jeton et vos documents circulent sans chiffrement sur le réseau. Utilisez https:// si possible.</string>
<string name="test_connection">Tester la connexion</string>
<string name="save">Enregistrer</string>
<string name="testing">Test en cours…</string>
<string name="settings_saved">Paramètres enregistrés ✓</string>
<string name="connection_ok">Connexion OK ✓</string>
<string name="unexpected_response">Réponse inattendue du serveur.</string>
<string name="default_filter">Filtre par défaut</string>
<!-- Paperless client / errors -->
<string name="err_invalid_token">Jeton invalide (%1$d).</string>
<string name="err_server_responded">Le serveur a répondu %1$d.</string>
<string name="err_connection_failed">Échec de la connexion.</string>
<string name="err_upload_failed_code">Échec de l\'envoi : %1$d %2$s</string>
<string name="err_upload_failed">Échec de l\'envoi.</string>
<string name="err_paperless_process">Paperless n\'a pas pu traiter le document.</string>
<string name="err_paperless_not_setup">Paperless n\'est pas encore configuré (URL + jeton).</string>
<string name="err_enter_url_token">Saisissez d\'abord une URL et un jeton.</string>
</resources>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Camera -->
<string name="camera_permission_rationale">PaperScan ha bisogno dell\'accesso alla fotocamera per scansionare documenti. Non viene inviato nulla al cloud.</string>
<string name="grant_access">Concedi accesso</string>
<string name="settings">Impostazioni</string>
<string name="done">Fatto</string>
<!-- Pages -->
<string name="pages_title">Pagine (%1$d)</string>
<string name="add_page">Aggiungi pagina</string>
<string name="export_pdf">Esporta PDF</string>
<string name="page_number">Pagina %1$d</string>
<string name="move_up">Sposta su</string>
<string name="move_down">Sposta giù</string>
<string name="delete">Elimina</string>
<!-- Crop -->
<string name="filter_color">Colore</string>
<string name="filter_grayscale">Scala di grigi</string>
<string name="filter_bw">Bianco e nero</string>
<string name="rotate">Ruota</string>
<string name="reset">Ripristina</string>
<string name="apply">Applica</string>
<!-- Export -->
<string name="export_title">Esporta (%1$d pagine)</string>
<string name="file_name">Nome file</string>
<string name="page_size_a4">Pagina A4</string>
<string name="page_size_original">Dimensione originale</string>
<string name="share_save">Condividi / salva</string>
<string name="share_pdf">Condividi PDF</string>
<string name="upload_to_paperless">Carica su Paperless</string>
<string name="set_up_paperless">Configura Paperless</string>
<string name="paperless_not_configured">Configura prima Paperless (URL del server + token) nelle impostazioni per caricare.</string>
<string name="uploading">Caricamento…</string>
<string name="working">Elaborazione…</string>
<string name="dont_wait">Non attendere</string>
<string name="export_failed">Esportazione non riuscita: %1$s</string>
<string name="error_generic">Errore: %1$s</string>
<string name="upload_added">Aggiunto a Paperless ✓</string>
<string name="upload_duplicate">Già presente in Paperless (duplicato) — non aggiunto di nuovo.</string>
<string name="upload_pending">Caricato — Paperless lo sta ancora elaborando…</string>
<string name="upload_background">Caricato — Paperless completerà l\'elaborazione in background.</string>
<!-- Settings -->
<string name="back">Indietro</string>
<string name="paperless_header">Paperless-ngx</string>
<string name="server_url_label">URL del server (es. https://paperless.home.lan)</string>
<string name="api_token_label">Token API</string>
<string name="http_warning">⚠ Stai usando http:// — il tuo token e i tuoi documenti viaggiano non cifrati sulla rete. Usa https:// se puoi.</string>
<string name="test_connection">Prova connessione</string>
<string name="save">Salva</string>
<string name="testing">Verifica in corso…</string>
<string name="settings_saved">Impostazioni salvate ✓</string>
<string name="connection_ok">Connessione OK ✓</string>
<string name="unexpected_response">Risposta inattesa dal server.</string>
<string name="default_filter">Filtro predefinito</string>
<!-- Paperless client / errors -->
<string name="err_invalid_token">Token non valido (%1$d).</string>
<string name="err_server_responded">Il server ha risposto %1$d.</string>
<string name="err_connection_failed">Connessione non riuscita.</string>
<string name="err_upload_failed_code">Caricamento non riuscito: %1$d %2$s</string>
<string name="err_upload_failed">Caricamento non riuscito.</string>
<string name="err_paperless_process">Paperless non è riuscito a elaborare il documento.</string>
<string name="err_paperless_not_setup">Paperless non è ancora configurato (URL + token).</string>
<string name="err_enter_url_token">Inserisci prima un URL e un token.</string>
</resources>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Camera -->
<string name="camera_permission_rationale">PaperScan heeft cameratoegang nodig om documenten te scannen. Er wordt niets naar de cloud gestuurd.</string>
<string name="grant_access">Toegang geven</string>
<string name="settings">Instellingen</string>
<string name="done">Klaar</string>
<!-- Pages -->
<string name="pages_title">Pagina\'s (%1$d)</string>
<string name="add_page">Pagina toevoegen</string>
<string name="export_pdf">Exporteer PDF</string>
<string name="page_number">Pagina %1$d</string>
<string name="move_up">Omhoog</string>
<string name="move_down">Omlaag</string>
<string name="delete">Verwijderen</string>
<!-- Crop -->
<string name="filter_color">Kleur</string>
<string name="filter_grayscale">Grijs</string>
<string name="filter_bw">Zwart-wit</string>
<string name="rotate">Draaien</string>
<string name="reset">Herstel</string>
<string name="apply">Toepassen</string>
<!-- Export -->
<string name="export_title">Exporteren (%1$d pagina\'s)</string>
<string name="file_name">Bestandsnaam</string>
<string name="page_size_a4">A4-pagina</string>
<string name="page_size_original">Originele grootte</string>
<string name="share_save">Delen / opslaan</string>
<string name="share_pdf">PDF delen</string>
<string name="upload_to_paperless">Upload naar Paperless</string>
<string name="set_up_paperless">Paperless instellen</string>
<string name="paperless_not_configured">Configureer eerst Paperless (server-URL + token) in de instellingen om te kunnen uploaden.</string>
<string name="uploading">Uploaden…</string>
<string name="working">Bezig…</string>
<string name="dont_wait">Niet wachten</string>
<string name="export_failed">Fout bij exporteren: %1$s</string>
<string name="error_generic">Fout: %1$s</string>
<string name="upload_added">Toegevoegd aan Paperless ✓</string>
<string name="upload_duplicate">Al aanwezig in Paperless (duplicaat) — niet opnieuw toegevoegd.</string>
<string name="upload_pending">Geüpload — Paperless is het nog aan het verwerken…</string>
<string name="upload_background">Geüpload — Paperless verwerkt het verder op de achtergrond.</string>
<!-- Settings -->
<string name="back">Terug</string>
<string name="paperless_header">Paperless-ngx</string>
<string name="server_url_label">Server-URL (bv. https://paperless.thuis.lan)</string>
<string name="api_token_label">API-token</string>
<string name="http_warning">⚠ Je gebruikt http:// — je token en documenten gaan onversleuteld over het netwerk. Gebruik https:// als het even kan.</string>
<string name="test_connection">Verbinding testen</string>
<string name="save">Opslaan</string>
<string name="testing">Testen…</string>
<string name="settings_saved">Instellingen opgeslagen ✓</string>
<string name="connection_ok">Verbinding OK ✓</string>
<string name="unexpected_response">Onbekend antwoord van de server.</string>
<string name="default_filter">Standaardfilter</string>
<!-- Paperless client / errors -->
<string name="err_invalid_token">Ongeldig token (%1$d).</string>
<string name="err_server_responded">Server antwoordde met %1$d.</string>
<string name="err_connection_failed">Verbinding mislukt.</string>
<string name="err_upload_failed_code">Upload mislukt: %1$d %2$s</string>
<string name="err_upload_failed">Upload mislukt.</string>
<string name="err_paperless_process">Paperless kon het document niet verwerken.</string>
<string name="err_paperless_not_setup">Paperless is nog niet ingesteld (URL + token).</string>
<string name="err_enter_url_token">Vul eerst URL en token in.</string>
</resources>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Camera -->
<string name="camera_permission_rationale">O PaperScan precisa de acesso à câmera para digitalizar documentos. Nada é enviado para a nuvem.</string>
<string name="grant_access">Conceder acesso</string>
<string name="settings">Configurações</string>
<string name="done">Concluído</string>
<!-- Pages -->
<string name="pages_title">Páginas (%1$d)</string>
<string name="add_page">Adicionar página</string>
<string name="export_pdf">Exportar PDF</string>
<string name="page_number">Página %1$d</string>
<string name="move_up">Mover para cima</string>
<string name="move_down">Mover para baixo</string>
<string name="delete">Excluir</string>
<!-- Crop -->
<string name="filter_color">Cor</string>
<string name="filter_grayscale">Escala de cinza</string>
<string name="filter_bw">Preto e branco</string>
<string name="rotate">Girar</string>
<string name="reset">Redefinir</string>
<string name="apply">Aplicar</string>
<!-- Export -->
<string name="export_title">Exportar (%1$d páginas)</string>
<string name="file_name">Nome do arquivo</string>
<string name="page_size_a4">Página A4</string>
<string name="page_size_original">Tamanho original</string>
<string name="share_save">Compartilhar / salvar</string>
<string name="share_pdf">Compartilhar PDF</string>
<string name="upload_to_paperless">Enviar para o Paperless</string>
<string name="set_up_paperless">Configurar o Paperless</string>
<string name="paperless_not_configured">Configure primeiro o Paperless (URL do servidor + token) nas configurações para enviar.</string>
<string name="uploading">Enviando…</string>
<string name="working">Processando…</string>
<string name="dont_wait">Não esperar</string>
<string name="export_failed">Falha ao exportar: %1$s</string>
<string name="error_generic">Erro: %1$s</string>
<string name="upload_added">Adicionado ao Paperless ✓</string>
<string name="upload_duplicate">Já está no Paperless (duplicado) — não adicionado novamente.</string>
<string name="upload_pending">Enviado — o Paperless ainda está processando…</string>
<string name="upload_background">Enviado — o Paperless concluirá o processamento em segundo plano.</string>
<!-- Settings -->
<string name="back">Voltar</string>
<string name="paperless_header">Paperless-ngx</string>
<string name="server_url_label">URL do servidor (ex.: https://paperless.home.lan)</string>
<string name="api_token_label">Token de API</string>
<string name="http_warning">⚠ Você está usando http:// — seu token e seus documentos trafegam sem criptografia na rede. Use https:// se possível.</string>
<string name="test_connection">Testar conexão</string>
<string name="save">Salvar</string>
<string name="testing">Testando…</string>
<string name="settings_saved">Configurações salvas ✓</string>
<string name="connection_ok">Conexão OK ✓</string>
<string name="unexpected_response">Resposta inesperada do servidor.</string>
<string name="default_filter">Filtro padrão</string>
<!-- Paperless client / errors -->
<string name="err_invalid_token">Token inválido (%1$d).</string>
<string name="err_server_responded">O servidor respondeu %1$d.</string>
<string name="err_connection_failed">Falha na conexão.</string>
<string name="err_upload_failed_code">Falha no envio: %1$d %2$s</string>
<string name="err_upload_failed">Falha no envio.</string>
<string name="err_paperless_process">O Paperless não conseguiu processar o documento.</string>
<string name="err_paperless_not_setup">O Paperless ainda não está configurado (URL + token).</string>
<string name="err_enter_url_token">Insira primeiro uma URL e um token.</string>
</resources>

View File

@@ -1,4 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">PaperScan</string>
<string name="app_name" translatable="false">PaperScan</string>
<!-- Camera -->
<string name="camera_permission_rationale">PaperScan needs camera access to scan documents. Nothing is sent to the cloud.</string>
<string name="grant_access">Grant access</string>
<string name="settings">Settings</string>
<string name="done">Done</string>
<!-- Pages -->
<string name="pages_title">Pages (%1$d)</string>
<string name="add_page">Add page</string>
<string name="export_pdf">Export PDF</string>
<string name="page_number">Page %1$d</string>
<string name="move_up">Move up</string>
<string name="move_down">Move down</string>
<string name="delete">Delete</string>
<!-- Crop -->
<string name="filter_color">Color</string>
<string name="filter_grayscale">Grayscale</string>
<string name="filter_bw">Black &amp; white</string>
<string name="rotate">Rotate</string>
<string name="reset">Reset</string>
<string name="apply">Apply</string>
<!-- Export -->
<string name="export_title">Export (%1$d pages)</string>
<string name="file_name">File name</string>
<string name="page_size_a4">A4 page</string>
<string name="page_size_original">Original size</string>
<string name="share_save">Share / save</string>
<string name="share_pdf">Share PDF</string>
<string name="upload_to_paperless">Upload to Paperless</string>
<string name="set_up_paperless">Set up Paperless</string>
<string name="paperless_not_configured">Configure Paperless (server URL + token) in settings first to upload.</string>
<string name="uploading">Uploading…</string>
<string name="working">Working…</string>
<string name="dont_wait">Don\'t wait</string>
<string name="export_failed">Export failed: %1$s</string>
<string name="error_generic">Error: %1$s</string>
<string name="upload_added">Added to Paperless ✓</string>
<string name="upload_duplicate">Already in Paperless (duplicate) — not added again.</string>
<string name="upload_pending">Uploaded — Paperless is still processing…</string>
<string name="upload_background">Uploaded — Paperless will finish processing in the background.</string>
<!-- Settings -->
<string name="back">Back</string>
<string name="paperless_header">Paperless-ngx</string>
<string name="server_url_label">Server URL (e.g. https://paperless.home.lan)</string>
<string name="api_token_label">API token</string>
<string name="http_warning">⚠ You\'re using http:// — your token and documents travel unencrypted over the network. Use https:// when you can.</string>
<string name="test_connection">Test connection</string>
<string name="save">Save</string>
<string name="testing">Testing…</string>
<string name="settings_saved">Settings saved ✓</string>
<string name="connection_ok">Connection OK ✓</string>
<string name="unexpected_response">Unexpected response from the server.</string>
<string name="default_filter">Default filter</string>
<!-- Paperless client / errors -->
<string name="err_invalid_token">Invalid token (%1$d).</string>
<string name="err_server_responded">Server responded with %1$d.</string>
<string name="err_connection_failed">Connection failed.</string>
<string name="err_upload_failed_code">Upload failed: %1$d %2$s</string>
<string name="err_upload_failed">Upload failed.</string>
<string name="err_paperless_process">Paperless could not process the document.</string>
<string name="err_paperless_not_setup">Paperless isn\'t set up yet (URL + token).</string>
<string name="err_enter_url_token">Enter a URL and token first.</string>
</resources>