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" applicationId = "eu.geyskens.pdfscan"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 4 versionCode = 6
versionName = "0.2.0" versionName = "0.3.1"
// OpenCV native libs are large; ship only the ABIs real phones use. // OpenCV native libs are large; ship only the ABIs real phones use.
ndk { ndk {

View File

@@ -115,9 +115,11 @@ class ScanViewModel(app: Application) : AndroidViewModel(app) {
val s = settings.value val s = settings.value
val token = settingsRepo.getToken() val token = settingsRepo.getToken()
if (s.paperlessUrl.isBlank() || token.isNullOrBlank()) { 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) --- // --- Settings pass-throughs (SettingsScreen) ---
@@ -133,9 +135,11 @@ class ScanViewModel(app: Application) : AndroidViewModel(app) {
suspend fun testPaperless(url: String, token: String): PaperlessClient.Result { suspend fun testPaperless(url: String, token: String): PaperlessClient.Result {
if (url.isBlank() || token.isBlank()) { 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() { fun clearSession() {

View File

@@ -1,5 +1,7 @@
package eu.geyskens.pdfscan.paperless package eu.geyskens.pdfscan.paperless
import android.content.Context
import eu.geyskens.pdfscan.R
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -22,6 +24,7 @@ import java.util.concurrent.TimeUnit
class PaperlessClient( class PaperlessClient(
private val baseUrl: String, private val baseUrl: String,
private val token: String, private val token: String,
private val context: Context,
) { ) {
private val client = OkHttpClient.Builder() private val client = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS)
@@ -53,11 +56,12 @@ class PaperlessClient(
client.newCall(request).execute().use { resp -> client.newCall(request).execute().use { resp ->
when { when {
resp.isSuccessful -> Result.Success resp.isSuccessful -> Result.Success
resp.code == 401 || resp.code == 403 -> Result.Failure("Invalid token (${resp.code}).") resp.code == 401 || resp.code == 403 ->
else -> Result.Failure("Server responded with ${resp.code}.") 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) { val posted: kotlin.Result<String?> = withContext(NonCancellable) {
runCatching { runCatching {
client.newCall(request).execute().use { resp -> 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() } 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. // 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 if (taskId == null || taskId.equals("OK", ignoreCase = true)) return@withContext Result.Pending
@@ -119,7 +127,7 @@ class PaperlessClient(
"FAILURE" -> { "FAILURE" -> {
val msg = task.result.orEmpty() val msg = task.result.orEmpty()
return if (msg.contains("duplicate", ignoreCase = true)) Result.Duplicate 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. // 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.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.concurrent.futures.await 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.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import eu.geyskens.pdfscan.R
import eu.geyskens.pdfscan.Routes import eu.geyskens.pdfscan.Routes
import eu.geyskens.pdfscan.ScanViewModel import eu.geyskens.pdfscan.ScanViewModel
import eu.geyskens.pdfscan.scan.DocumentAnalyzer import eu.geyskens.pdfscan.scan.DocumentAnalyzer
@@ -84,13 +86,12 @@ private fun PermissionRequest(onRequest: () -> Unit) {
Box(Modifier.fillMaxSize().padding(32.dp), contentAlignment = Alignment.Center) { Box(Modifier.fillMaxSize().padding(32.dp), contentAlignment = Alignment.Center) {
androidx.compose.foundation.layout.Column(horizontalAlignment = Alignment.CenterHorizontally) { androidx.compose.foundation.layout.Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text( Text(
"PaperScan needs camera access to scan documents. " + stringResource(R.string.camera_permission_rationale),
"Nothing is sent to the cloud.",
color = Color.White, color = Color.White,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )
androidx.compose.foundation.layout.Spacer(Modifier.size(16.dp)) 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) }, onClick = { navController.navigate(Routes.SETTINGS) },
modifier = Modifier.align(Alignment.TopEnd).statusBarsPadding().padding(8.dp), 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. // Bottom controls.
@@ -259,7 +260,7 @@ private fun CameraContent(vm: ScanViewModel, navController: androidx.navigation.
) { ) {
Icon( Icon(
Icons.Filled.Check, Icons.Filled.Check,
contentDescription = "Done", contentDescription = stringResource(R.string.done),
tint = if (pages.isEmpty()) Color.Gray else Color.White, 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.ContentScale
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import eu.geyskens.pdfscan.R
import eu.geyskens.pdfscan.ScanViewModel import eu.geyskens.pdfscan.ScanViewModel
import eu.geyskens.pdfscan.data.ScanFilter import eu.geyskens.pdfscan.data.ScanFilter
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
@@ -135,9 +137,9 @@ fun CropScreen(vm: ScanViewModel, navController: NavController, pageId: String)
Modifier.fillMaxWidth().padding(12.dp), Modifier.fillMaxWidth().padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
FilterOption("Color", filter == ScanFilter.COLOR) { filter = ScanFilter.COLOR } FilterOption(stringResource(R.string.filter_color), filter == ScanFilter.COLOR) { filter = ScanFilter.COLOR }
FilterOption("Grayscale", filter == ScanFilter.GRAYSCALE) { filter = ScanFilter.GRAYSCALE } FilterOption(stringResource(R.string.filter_grayscale), filter == ScanFilter.GRAYSCALE) { filter = ScanFilter.GRAYSCALE }
FilterOption("Black & white", filter == ScanFilter.BLACK_WHITE) { filter = ScanFilter.BLACK_WHITE } FilterOption(stringResource(R.string.filter_bw), filter == ScanFilter.BLACK_WHITE) { filter = ScanFilter.BLACK_WHITE }
} }
Row( Row(
@@ -145,7 +147,7 @@ fun CropScreen(vm: ScanViewModel, navController: NavController, pageId: String)
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
OutlinedButton(onClick = { vm.rotate(pageId) }, modifier = Modifier.weight(1f)) { OutlinedButton(onClick = { vm.rotate(pageId) }, modifier = Modifier.weight(1f)) {
Text("Rotate") Text(stringResource(R.string.rotate))
} }
OutlinedButton( OutlinedButton(
onClick = { 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) } full.forEachIndexed { i, p -> corners[i].value = PointF(p.x, p.y) }
}, },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) { Text("Reset") } ) { Text(stringResource(R.string.reset)) }
Button( Button(
onClick = { onClick = {
vm.updateCorners(pageId, corners.map { PointF(it.value.x, it.value.y) }) 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() navController.popBackStack()
}, },
modifier = Modifier.weight(1f), 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.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import eu.geyskens.pdfscan.R
import eu.geyskens.pdfscan.Routes import eu.geyskens.pdfscan.Routes
import eu.geyskens.pdfscan.ScanViewModel import eu.geyskens.pdfscan.ScanViewModel
import eu.geyskens.pdfscan.paperless.PaperlessClient 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 fun mode() = if (a4) PageSizeMode.A4 else PageSizeMode.ORIGINAL
Scaffold( Scaffold(
topBar = { TopAppBar(title = { Text("Export (${pages.size} pages)") }) }, topBar = { TopAppBar(title = { Text(stringResource(R.string.export_title, pages.size)) }) },
) { padding -> ) { padding ->
Column( Column(
Modifier.fillMaxSize().padding(padding).padding(16.dp), Modifier.fillMaxSize().padding(padding).padding(16.dp),
@@ -79,7 +81,7 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
OutlinedTextField( OutlinedTextField(
value = fileName, value = fileName,
onValueChange = { fileName = it }, onValueChange = { fileName = it },
label = { Text("File name") }, label = { Text(stringResource(R.string.file_name)) },
singleLine = true, singleLine = true,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
@@ -89,12 +91,12 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
selected = a4, selected = a4,
onClick = { a4 = true }, onClick = { a4 = true },
shape = SegmentedButtonDefaults.itemShape(0, 2), shape = SegmentedButtonDefaults.itemShape(0, 2),
) { Text("A4 page") } ) { Text(stringResource(R.string.page_size_a4)) }
SegmentedButton( SegmentedButton(
selected = !a4, selected = !a4,
onClick = { a4 = false }, onClick = { a4 = false },
shape = SegmentedButtonDefaults.itemShape(1, 2), shape = SegmentedButtonDefaults.itemShape(1, 2),
) { Text("Original size") } ) { Text(stringResource(R.string.page_size_original)) }
} }
Button( Button(
@@ -113,9 +115,9 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
putExtra(Intent.EXTRA_STREAM, uri) putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 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) { } catch (e: Exception) {
status = "Export failed: ${e.message}" status = context.getString(R.string.export_failed, e.message ?: "")
} finally { } finally {
busy = false busy = false
} }
@@ -126,7 +128,7 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
) { ) {
Icon(Icons.Filled.Share, contentDescription = null) Icon(Icons.Filled.Share, contentDescription = null)
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Text("Share / save") Text(stringResource(R.string.share_save))
} }
Button( Button(
@@ -140,19 +142,19 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
val pdf = vm.exportPdf(fileName, mode()) val pdf = vm.exportPdf(fileName, mode())
when (val r = vm.uploadToPaperless(pdf, fileName)) { when (val r = vm.uploadToPaperless(pdf, fileName)) {
is PaperlessClient.Result.Success -> is PaperlessClient.Result.Success ->
status = "Added to Paperless ✓" status = context.getString(R.string.upload_added)
is PaperlessClient.Result.Duplicate -> is PaperlessClient.Result.Duplicate ->
status = "Already in Paperless (duplicate) — not added again." status = context.getString(R.string.upload_duplicate)
is PaperlessClient.Result.Pending -> is PaperlessClient.Result.Pending ->
status = "Uploaded — Paperless is still processing…" status = context.getString(R.string.upload_pending)
is PaperlessClient.Result.Failure -> is PaperlessClient.Result.Failure ->
status = r.message status = r.message
} }
} catch (e: CancellationException) { } catch (e: CancellationException) {
// User tapped "Don't wait": the document was already sent. // 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) { } catch (e: Exception) {
status = "Error: ${e.message}" status = context.getString(R.string.error_generic, e.message ?: "")
} finally { } finally {
busy = false busy = false
uploading = false uploading = false
@@ -164,29 +166,29 @@ fun ExportScreen(vm: ScanViewModel, navController: NavController) {
) { ) {
Icon(Icons.Filled.CloudUpload, contentDescription = null) Icon(Icons.Filled.CloudUpload, contentDescription = null)
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Text("Upload naar Paperless") Text(stringResource(R.string.upload_to_paperless))
} }
if (!settings.paperlessUrl.isNotBlank() || !settings.hasToken) { if (!settings.paperlessUrl.isNotBlank() || !settings.hasToken) {
Text( Text(
"Configure Paperless (server URL + token) in settings first to upload.", stringResource(R.string.paperless_not_configured),
style = androidx.compose.material3.MaterialTheme.typography.bodySmall, style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
color = Color(0xFF9A9A9A), color = Color(0xFF9A9A9A),
) )
OutlinedButton( OutlinedButton(
onClick = { navController.navigate(Routes.SETTINGS) }, onClick = { navController.navigate(Routes.SETTINGS) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { Text("Set up Paperless") } ) { Text(stringResource(R.string.set_up_paperless)) }
} }
if (busy) { if (busy) {
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
CircularProgressIndicator(Modifier.height(24.dp)) CircularProgressIndicator(Modifier.height(24.dp))
Spacer(Modifier.width(12.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) { if (uploading) {
Spacer(Modifier.width(12.dp)) 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.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import eu.geyskens.pdfscan.R
import eu.geyskens.pdfscan.Routes import eu.geyskens.pdfscan.Routes
import eu.geyskens.pdfscan.ScanViewModel import eu.geyskens.pdfscan.ScanViewModel
import eu.geyskens.pdfscan.data.Page import eu.geyskens.pdfscan.data.Page
@@ -53,12 +55,12 @@ fun PagesScreen(vm: ScanViewModel, navController: NavController) {
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { Text("Pages (${pages.size})") }, title = { Text(stringResource(R.string.pages_title, pages.size)) },
actions = { actions = {
IconButton( IconButton(
onClick = { if (pages.isNotEmpty()) navController.navigate(Routes.EXPORT) }, 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( ExtendedFloatingActionButton(
onClick = { navController.navigate(Routes.CAMERA) }, onClick = { navController.navigate(Routes.CAMERA) },
icon = { Icon(Icons.Filled.Add, contentDescription = null) }, icon = { Icon(Icons.Filled.Add, contentDescription = null) },
text = { Text("Add page") }, text = { Text(stringResource(R.string.add_page)) },
) )
}, },
) { padding -> ) { padding ->
@@ -122,24 +124,24 @@ private fun PageRow(
bitmap?.let { bitmap?.let {
Image( Image(
bitmap = it.asImageBitmap(), bitmap = it.asImageBitmap(),
contentDescription = "Page ${index + 1}", contentDescription = stringResource(R.string.page_number, index + 1),
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) )
} }
} }
Spacer(Modifier.width(12.dp)) 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 { Column {
IconButton(onClick = onUp, enabled = !isFirst) { 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) { 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) { 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color 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.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import eu.geyskens.pdfscan.R
import eu.geyskens.pdfscan.ScanViewModel import eu.geyskens.pdfscan.ScanViewModel
import eu.geyskens.pdfscan.data.ScanFilter import eu.geyskens.pdfscan.data.ScanFilter
import eu.geyskens.pdfscan.paperless.PaperlessClient import eu.geyskens.pdfscan.paperless.PaperlessClient
@@ -47,6 +50,7 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun SettingsScreen(vm: ScanViewModel, navController: NavController) { fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current
val settings by vm.settings.collectAsState() val settings by vm.settings.collectAsState()
var url by remember { mutableStateOf(settings.paperlessUrl) } var url by remember { mutableStateOf(settings.paperlessUrl) }
@@ -59,10 +63,10 @@ fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { Text("Settings") }, title = { Text(stringResource(R.string.settings)) },
navigationIcon = { navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) { 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()), .verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp), 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( OutlinedTextField(
value = url, value = url,
onValueChange = { url = it; testResult = null }, 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, singleLine = true,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
OutlinedTextField( OutlinedTextField(
value = token, value = token,
onValueChange = { token = it; testResult = null }, onValueChange = { token = it; testResult = null },
label = { Text("API token") }, label = { Text(stringResource(R.string.api_token_label)) },
singleLine = true, singleLine = true,
visualTransformation = PasswordVisualTransformation(), visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
if (url.startsWith("http://")) { if (url.startsWith("http://")) {
Text( Text(
"⚠ You're using http:// — your token and documents travel unencrypted over the " + stringResource(R.string.http_warning),
"network. Use https:// when you can.",
color = Color(0xFFB26A00), color = Color(0xFFB26A00),
style = androidx.compose.material3.MaterialTheme.typography.bodySmall, style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
) )
@@ -109,55 +112,55 @@ fun SettingsScreen(vm: ScanViewModel, navController: NavController) {
testResult = null testResult = null
scope.launch { scope.launch {
testResult = when (val r = vm.testPaperless(url, token)) { 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 is PaperlessClient.Result.Failure -> r.message
else -> "Unexpected response from the server." else -> context.getString(R.string.unexpected_response)
} }
testing = false testing = false
} }
}, },
enabled = !testing, enabled = !testing,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) { Text("Test connection") } ) { Text(stringResource(R.string.test_connection)) }
Button( Button(
onClick = { onClick = {
scope.launch { scope.launch {
vm.savePaperless(url, token) vm.savePaperless(url, token)
snackbarHostState.showSnackbar("Settings saved") snackbarHostState.showSnackbar(context.getString(R.string.settings_saved))
} }
}, },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) { Text("Save") } ) { Text(stringResource(R.string.save)) }
} }
if (testing) { if (testing) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
CircularProgressIndicator(Modifier.width(20.dp)) CircularProgressIndicator(Modifier.width(20.dp))
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Text("Testing") Text(stringResource(R.string.testing))
} }
} }
testResult?.let { Text(it, color = Color(0xFF1F6FEB)) } testResult?.let { Text(it, color = Color(0xFF1F6FEB)) }
Text( Text(
"Default filter", stringResource(R.string.default_filter),
style = androidx.compose.material3.MaterialTheme.typography.titleMedium, style = androidx.compose.material3.MaterialTheme.typography.titleMedium,
) )
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
FilterChip( FilterChip(
selected = settings.defaultFilter == ScanFilter.COLOR, selected = settings.defaultFilter == ScanFilter.COLOR,
onClick = { scope.launch { vm.setDefaultFilter(ScanFilter.COLOR) } }, onClick = { scope.launch { vm.setDefaultFilter(ScanFilter.COLOR) } },
label = { Text("Color") }, label = { Text(stringResource(R.string.filter_color)) },
) )
FilterChip( FilterChip(
selected = settings.defaultFilter == ScanFilter.GRAYSCALE, selected = settings.defaultFilter == ScanFilter.GRAYSCALE,
onClick = { scope.launch { vm.setDefaultFilter(ScanFilter.GRAYSCALE) } }, onClick = { scope.launch { vm.setDefaultFilter(ScanFilter.GRAYSCALE) } },
label = { Text("Grayscale") }, label = { Text(stringResource(R.string.filter_grayscale)) },
) )
FilterChip( FilterChip(
selected = settings.defaultFilter == ScanFilter.BLACK_WHITE, selected = settings.defaultFilter == ScanFilter.BLACK_WHITE,
onClick = { scope.launch { vm.setDefaultFilter(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"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <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> </resources>