localisation: onboarding

This commit is contained in:
2026-06-08 18:13:56 +02:00
parent dfbf1f0360
commit 7b26bc21c4
7 changed files with 151 additions and 73 deletions
@@ -146,7 +146,7 @@ class MainActivity : AppCompatActivity() {
OnboardingScreen( OnboardingScreen(
secretStore = secretStore, secretStore = secretStore,
syncStore = syncStore, syncStore = syncStore,
onRequestNotificationPermission = { requestNotificationPermissionIfNeeded() }, // ← new onRequestNotificationPermission = { requestNotificationPermissionIfNeeded() },
onComplete = {} onComplete = {}
) )
} }
@@ -317,7 +317,11 @@ val EnHomeStrings = AppStrings(
serverUrl = "Server URL", serverUrl = "Server URL",
serverUrlHint = "https://bitcointxoko.org", serverUrlHint = "https://bitcointxoko.org",
invoiceKey = "Invoice Key (read-only)", invoiceKey = "Invoice Key (read-only)",
invoiceKeySubtitle = "Used to generate payment requests.",
pasteInvoiceKey = "Paste invoice key",
adminKey = "Admin Key (for sending)", adminKey = "Admin Key (for sending)",
adminKeySubtitle = "Required to send payments.",
pasteAdminKey = "Paste admin key",
hideAdminKey = "Hide", hideAdminKey = "Hide",
showAdminKey = "Show", showAdminKey = "Show",
fieldsRequired = "All fields are required.", fieldsRequired = "All fields are required.",
@@ -331,7 +335,22 @@ val EnHomeStrings = AppStrings(
batterySubtitle = "Allow Gudari Wallet to run unrestricted so notifications arrive even when the app is closed.", batterySubtitle = "Allow Gudari Wallet to run unrestricted so notifications arrive even when the app is closed.",
openSettings = "Open Settings", openSettings = "Open Settings",
batteryNote = "Tap 'Battery' → 'Unrestricted'. You can skip this and change it later.", batteryNote = "Tap 'Battery' → 'Unrestricted'. You can skip this and change it later.",
batteryAlreadyUnrestricted = "You're all set — no restrictions detected." batteryAlreadyUnrestricted = "You're all set — no restrictions detected.",
paymentHistory = "Payment history",
paymentHistorySubtitle = "Download your past payments so they're available offline.",
syncNow = "Sync now",
skipForNow = "Skip for now",
syncScheduled = "Sync scheduled — will run when your device is online. You will get a notification when the sync is done. You can already start using the app.",
continueToApp = "Continue to app →",
connecting = "Connecting...",
paymentsFetchedTotal = { fetched, total -> "$fetched / $total payments" },
paymentsSynced = { total -> "$total payments synced"},
syncComplete = "✓ Sync complete",
syncFailed = "Sync failed. You can retry from Settings.",
scheduleForLater = "Schedule for later",
scheduleSync = "Schedule sync",
schedule = "Schedule",
cancel = "Cancel"
) )
) )
@@ -309,28 +309,47 @@ val EsHomeStrings = AppStrings(
onboarding = OnboardingStrings( onboarding = OnboardingStrings(
back = "Back", back = "Atrás",
next = "Next", next = "Siguiente",
done = "Done", done = "Hecho",
title = "Connect to LNbits", title = "Conectar a LNbits",
subtitle = "Enter your LNbits server URL and API keys.", subtitle = "Introduce la URL de tu servidor LNbits y las claves de API.",
serverUrl = "Server URL", serverUrl = "URL del servidor",
serverUrlHint = "https://bitcointxoko.org", serverUrlHint = "https://bitcointxoko.org",
invoiceKey = "Invoice Key (read-only)", invoiceKey = "Clave de factura (solo lectura)",
adminKey = "Admin Key (for sending)", invoiceKeySubtitle = "Se usa para generar solicitudes de pago.",
hideAdminKey = "Hide", pasteInvoiceKey = "Pegar clave de factura",
showAdminKey = "Show", adminKey = "Clave de administrador (para enviar)",
fieldsRequired = "All fields are required.", adminKeySubtitle = "Necesaria para enviar pagos.",
connect = "Connect", pasteAdminKey = "Pegar clave de administrador",
notificationsTitle = "Stay in the loop", hideAdminKey = "Ocultar",
notificationsSubtitle = "Get notified instantly when you receive a payment.", showAdminKey = "Mostrar",
allowNotifications = "Allow Notifications", fieldsRequired = "Todos los campos son obligatorios.",
notificationsNote = "You can change this later in system Settings.", connect = "Conectar",
notificationsAutoGranted = "Notifications are enabled automatically on your device.", notificationsTitle = "Mantente al día",
batteryTitle = "Background activity", notificationsSubtitle = "Recibe notificaciones al instante cuando recibas un pago.",
batterySubtitle = "Allow Gudari Wallet to run unrestricted so notifications arrive even when the app is closed.", allowNotifications = "Permitir notificaciones",
openSettings = "Open Settings", notificationsNote = "Puedes cambiar esto más tarde en los Ajustes del sistema.",
batteryNote = "Tap 'Battery' → 'Unrestricted'. You can skip this and change it later.", notificationsAutoGranted = "Las notificaciones se activan automáticamente en tu dispositivo.",
batteryAlreadyUnrestricted = "You're all set — no restrictions detected." batteryTitle = "Actividad en segundo plano",
batterySubtitle = "Permite que Gudari Wallet funcione sin restricciones para que las notificaciones lleguen incluso cuando la aplicación esté cerrada.",
openSettings = "Abrir Ajustes",
batteryNote = "Pulsa 'Batería' → 'Sin restricciones'. Puedes omitir esto y cambiarlo más tarde.",
batteryAlreadyUnrestricted = "Todo listo — no se han detectado restricciones.",
paymentHistory = "Historial de transacciones",
paymentHistorySubtitle = "Descarga tus transacciones anteriores para tenerlas disponibles sin conexión.",
syncNow = "Sincronizar ahora",
skipForNow = "Omitir por ahora",
syncScheduled = "Sincronización programada — se ejecutará cuando tu dispositivo esté en línea. Recibirás una notificación cuando la sincronización haya finalizado. Ya puedes empezar a usar la aplicación.",
continueToApp = "Ir a la aplicación →",
connecting = "Conectando...",
paymentsFetchedTotal = { fetched, total -> "$fetched / $total transacciones" },
paymentsSynced = { total -> "$total transacciones sincronizadas"},
syncComplete = "✓ Sincronización completada",
syncFailed = "Error en la sincronización. Puedes volver a intentarlo desde Ajustes.",
scheduleForLater = "Programar para más tarde",
scheduleSync = "Programar sincronización",
schedule = "Programar",
cancel = "Cancelar"
) )
) )
@@ -309,28 +309,47 @@ val EuStrings = AppStrings(
onboarding = OnboardingStrings( onboarding = OnboardingStrings(
back = "Back", back = "Atzera",
next = "Next", next = "Hurrengoa",
done = "Done", done = "Eginda",
title = "Connect to LNbits", title = "Konektatu LNbits-era",
subtitle = "Enter your LNbits server URL and API keys.", subtitle = "Sartu zure LNbits zerbitzariaren URLa eta API gakoak.",
serverUrl = "Server URL", serverUrl = "Zerbitzariaren URLa",
serverUrlHint = "https://bitcointxoko.org", serverUrlHint = "https://bitcointxoko.org",
invoiceKey = "Invoice Key (read-only)", invoiceKey = "Faktura gakoa (irakurtzeko soilik)",
adminKey = "Admin Key (for sending)", invoiceKeySubtitle = "Ordainketa eskaerak sortzeko erabiltzen da.",
hideAdminKey = "Hide", pasteInvoiceKey = "Itsatsi faktura gakoa",
showAdminKey = "Show", adminKey = "Administrazio gakoa (bidaltzeko)",
fieldsRequired = "All fields are required.", adminKeySubtitle = "Beharrezkoa da transakzioak bidaltzeko.",
connect = "Connect", pasteAdminKey = "Itsatsi administrazio gakoa",
notificationsTitle = "Stay in the loop", hideAdminKey = "Ezkutatu",
notificationsSubtitle = "Get notified instantly when you receive a payment.", showAdminKey = "Erakutsi",
allowNotifications = "Allow Notifications", fieldsRequired = "Eremu guztiak beharrezkoak dira.",
notificationsNote = "You can change this later in system Settings.", connect = "Konektatu",
notificationsAutoGranted = "Notifications are enabled automatically on your device.", notificationsTitle = "Egon eguneratuta",
batteryTitle = "Background activity", notificationsSubtitle = "Jaso jakinarazpenak berehala transakzio bat jasotzen duzunean.",
batterySubtitle = "Allow Gudari Wallet to run unrestricted so notifications arrive even when the app is closed.", allowNotifications = "Baimendu jakinarazpenak",
openSettings = "Open Settings", notificationsNote = "Hau geroago alda dezakezu sistemaren Ezarpenetan.",
batteryNote = "Tap 'Battery' → 'Unrestricted'. You can skip this and change it later.", notificationsAutoGranted = "Jakinarazpenak automatikoki gaituta daude zure gailuan.",
batteryAlreadyUnrestricted = "You're all set — no restrictions detected." batteryTitle = "Atzeko planoko jarduera",
batterySubtitle = "Baimendu Gudari Wallet murrizketarik gabe funtzionatzea, jakinarazpenak aplikazioa itxita dagoenean ere irits daitezen.",
openSettings = "Ireki Ezarpenak",
batteryNote = "Sakatu 'Bateria' → 'Murrizketarik gabe'. Hau saltatu eta geroago alda dezakezu.",
batteryAlreadyUnrestricted = "Dena prest dago — ez da murrizketarik hauteman.",
paymentHistory = "Transakzioen historia",
paymentHistorySubtitle = "Deskargatu iraganeko transakzioak konexiorik gabe erabilgarri izateko.",
syncNow = "Sinkronizatu orain",
skipForNow = "Saltatu oraingoz",
syncScheduled = "Sinkronizazioa programatuta dago — gailua sarean dagoenean exekutatuko da. Sinkronizazioa amaitutakoan jakinarazpen bat jasoko duzu. Dagoeneko has zaitezke aplikazioa erabiltzen.",
continueToApp = "Joan aplikaziora →",
connecting = "Konektatzen...",
paymentsFetchedTotal = { fetched, total -> "$fetched / $total transakzio" },
paymentsSynced = { total -> "$total transakzio sinkronizatuta"},
syncComplete = "✓ Sinkronizazioa osatuta",
syncFailed = "Sinkronizazioak huts egin du. Berriro saia zaitezke Ezarpenetatik.",
scheduleForLater = "Programatu geroago",
scheduleSync = "Programatu sinkronizazioa",
schedule = "Programatu",
cancel = "Utzi"
) )
) )
@@ -313,7 +313,11 @@ data class OnboardingStrings(
val serverUrl: String, val serverUrl: String,
val serverUrlHint: String, val serverUrlHint: String,
val invoiceKey: String, val invoiceKey: String,
val invoiceKeySubtitle: String,
val pasteInvoiceKey: String,
val adminKey: String, val adminKey: String,
val adminKeySubtitle: String,
val pasteAdminKey: String,
val showAdminKey: String, val showAdminKey: String,
val hideAdminKey: String, val hideAdminKey: String,
val connect: String, val connect: String,
@@ -330,5 +334,20 @@ data class OnboardingStrings(
val batterySubtitle: String, val batterySubtitle: String,
val openSettings: String, val openSettings: String,
val batteryNote: String, val batteryNote: String,
val batteryAlreadyUnrestricted: String val batteryAlreadyUnrestricted: String,
val paymentHistory: String,
val paymentHistorySubtitle: String,
val syncNow: String,
val skipForNow: String,
val syncScheduled: String,
val continueToApp: String,
val connecting: String,
val paymentsFetchedTotal: (fetched: Int, total: Int) -> String,
val paymentsSynced: (total: Int) -> String,
val syncComplete: String,
val syncFailed: String,
val scheduleForLater: String,
val scheduleSync: String,
val schedule: String,
val cancel: String
) )
@@ -140,7 +140,7 @@ val scope = rememberCoroutineScope()
OnboardingPageScaffold( OnboardingPageScaffold(
title = strings.onboarding.invoiceKey, title = strings.onboarding.invoiceKey,
subtitle = "Used to generate payment requests." subtitle = strings.onboarding.invoiceKeySubtitle // "Used to generate payment requests."
) { ) {
OutlinedTextField( OutlinedTextField(
value = invoiceKey, value = invoiceKey,
@@ -161,7 +161,7 @@ val scope = rememberCoroutineScope()
}) { }) {
Icon( Icon(
imageVector = Icons.Default.ContentPaste, imageVector = Icons.Default.ContentPaste,
contentDescription = "Paste invoice key" contentDescription = strings.onboarding.pasteInvoiceKey // "Paste invoice key"
) )
} }
} }
@@ -185,7 +185,7 @@ internal fun AdminKeyPage(
OnboardingPageScaffold( OnboardingPageScaffold(
title = strings.onboarding.adminKey, title = strings.onboarding.adminKey,
subtitle = "Required to send payments." subtitle = strings.onboarding.adminKeySubtitle // "Required to send payments."
) { ) {
OutlinedTextField( OutlinedTextField(
value = adminKey, value = adminKey,
@@ -209,7 +209,7 @@ internal fun AdminKeyPage(
}) { }) {
Icon( Icon(
imageVector = Icons.Default.ContentPaste, imageVector = Icons.Default.ContentPaste,
contentDescription = "Paste admin key" contentDescription = strings.onboarding.pasteAdminKey // "Paste admin key"
) )
} }
IconButton(onClick = onToggleVis) { IconButton(onClick = onToggleVis) {
@@ -333,7 +333,8 @@ fun SyncHistoryPage(
syncIsDeferred: Boolean, syncIsDeferred: Boolean,
onSyncNow : () -> Unit, onSyncNow : () -> Unit,
onSchedule : (delayMillis: Long) -> Unit, onSchedule : (delayMillis: Long) -> Unit,
onContinue : () -> Unit onContinue : () -> Unit,
strings : AppStrings
) { ) {
// Derive UI state from WorkInfo // Derive UI state from WorkInfo
val state = when { val state = when {
@@ -364,8 +365,8 @@ fun SyncHistoryPage(
} }
OnboardingPageScaffold( OnboardingPageScaffold(
title = "Payment history", title = strings.onboarding.paymentHistory, // ← "Payment history"
subtitle = "Download your past payments so they're available offline." subtitle = strings.onboarding.paymentHistorySubtitle // ← "Download your past payments so they're available offline."
) { ) {
Icon( Icon(
imageVector = Icons.Default.History, imageVector = Icons.Default.History,
@@ -387,23 +388,23 @@ fun SyncHistoryPage(
onClick = onSyncNow, onClick = onSyncNow,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Sync now") Text(strings.onboarding.syncNow) // ← "Sync now"
} }
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
ScheduleLaterPicker(onSchedule = onSchedule) ScheduleLaterPicker(onSchedule, strings)
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
TextButton( TextButton(
onClick = onContinue, onClick = onContinue,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Skip for now") Text(strings.onboarding.skipForNow) // ← "Skip for now"
} }
} }
// ── Scheduled (deferred, waiting for delay + network) ───────────── // ── Scheduled (deferred, waiting for delay + network) ─────────────
SyncUiState.Scheduled -> { SyncUiState.Scheduled -> {
Text( Text(
text = "Sync scheduled — will run when your device is online. You will get a notification when the sync is done. You can already start using the app.", text = strings.onboarding.syncScheduled, // ← "Sync scheduled — will run when your device is online. You will get a notification when the sync is done. You can already start using the app."
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center textAlign = TextAlign.Center
@@ -413,7 +414,7 @@ fun SyncHistoryPage(
onClick = onContinue, onClick = onContinue,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Continue to app →") Text(strings.onboarding.continueToApp) // ← "Continue to app →"
} }
} }
@@ -427,7 +428,7 @@ fun SyncHistoryPage(
) )
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
Text( Text(
text = "${state.fetched} / ${state.total} payments", text = strings.onboarding.paymentsFetchedTotal(state.fetched, state.total), // ← "${state.fetched} / ${state.total} payments"
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -436,7 +437,7 @@ fun SyncHistoryPage(
LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) // indeterminate LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) // indeterminate
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
Text( Text(
text = "Connecting…", text = strings.onboarding.connecting, // ← "Connecting…"
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -447,7 +448,7 @@ fun SyncHistoryPage(
onClick = onContinue, onClick = onContinue,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Continue to app →") Text(strings.onboarding.continueToApp) // ← "Continue to app →"
} }
} }
@@ -455,9 +456,9 @@ fun SyncHistoryPage(
is SyncUiState.Done -> { is SyncUiState.Done -> {
Text( Text(
text = if (state.total > 0) text = if (state.total > 0)
"${state.total} payments synced" strings.onboarding.paymentsSynced(state.total) // ← "✓ ${state.total} payments synced"
else else
"✓ Sync complete", strings.onboarding.syncComplete, // ← "✓ Sync complete"
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
textAlign = TextAlign.Center textAlign = TextAlign.Center
@@ -467,14 +468,14 @@ fun SyncHistoryPage(
onClick = onContinue, onClick = onContinue,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Continue to app →") Text(strings.onboarding.continueToApp) // ← "Continue to app →"
} }
} }
// ── Failed ──────────────────────────────────────────────────────── // ── Failed ────────────────────────────────────────────────────────
SyncUiState.Failed -> { SyncUiState.Failed -> {
Text( Text(
text = "Sync failed. You can retry from Settings.", text = strings.onboarding.syncFailed, // ← "Sync failed. You can retry from Settings."
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error, color = MaterialTheme.colorScheme.error,
textAlign = TextAlign.Center textAlign = TextAlign.Center
@@ -484,7 +485,7 @@ fun SyncHistoryPage(
onClick = onContinue, onClick = onContinue,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Continue to app →") Text(strings.onboarding.continueToApp) // ← "Continue to app →"
} }
} }
} }
@@ -503,18 +504,18 @@ private sealed interface SyncUiState {
// ── "Schedule for later" time picker ───────────────────────────────────────── // ── "Schedule for later" time picker ─────────────────────────────────────────
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun ScheduleLaterPicker(onSchedule: (delayMillis: Long) -> Unit) { private fun ScheduleLaterPicker(onSchedule: (delayMillis: Long) -> Unit, strings: AppStrings) {
var showPicker by remember { mutableStateOf(false) } var showPicker by remember { mutableStateOf(false) }
TextButton(onClick = { showPicker = true }) { TextButton(onClick = { showPicker = true }) {
Text("Schedule for later") Text(strings.onboarding.scheduleForLater) // ← "Schedule for later"
} }
if (showPicker) { if (showPicker) {
val timePickerState = rememberTimePickerState(is24Hour = true) val timePickerState = rememberTimePickerState(is24Hour = true)
AlertDialog( AlertDialog(
onDismissRequest = { showPicker = false }, onDismissRequest = { showPicker = false },
title = { Text("Schedule sync") }, title = { Text(strings.onboarding.scheduleSync) }, // ← Schedule sync
text = { text = {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
TimePicker(state = timePickerState) TimePicker(state = timePickerState)
@@ -525,10 +526,10 @@ private fun ScheduleLaterPicker(onSchedule: (delayMillis: Long) -> Unit) {
showPicker = false showPicker = false
val delayMs = timePickerState.toDelayMillis() val delayMs = timePickerState.toDelayMillis()
onSchedule(delayMs) onSchedule(delayMs)
}) { Text("Schedule") } }) { Text(strings.onboarding.schedule) } // ← "Schedule"
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showPicker = false }) { Text("Cancel") } TextButton(onClick = { showPicker = false }) { Text(strings.onboarding.cancel) } // ← "Cancel"
} }
) )
} }
@@ -130,7 +130,8 @@ fun OnboardingScreen(
secretStore.setOnboarded() // moved here from PAGE_BATTERY secretStore.setOnboarded() // moved here from PAGE_BATTERY
onComplete() onComplete()
} }
} },
strings
) )
} }
} }