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(
secretStore = secretStore,
syncStore = syncStore,
onRequestNotificationPermission = { requestNotificationPermissionIfNeeded() }, // ← new
onRequestNotificationPermission = { requestNotificationPermissionIfNeeded() },
onComplete = {}
)
}
@@ -317,7 +317,11 @@ val EnHomeStrings = AppStrings(
serverUrl = "Server URL",
serverUrlHint = "https://bitcointxoko.org",
invoiceKey = "Invoice Key (read-only)",
invoiceKeySubtitle = "Used to generate payment requests.",
pasteInvoiceKey = "Paste invoice key",
adminKey = "Admin Key (for sending)",
adminKeySubtitle = "Required to send payments.",
pasteAdminKey = "Paste admin key",
hideAdminKey = "Hide",
showAdminKey = "Show",
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.",
openSettings = "Open Settings",
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(
back = "Back",
next = "Next",
done = "Done",
title = "Connect to LNbits",
subtitle = "Enter your LNbits server URL and API keys.",
serverUrl = "Server URL",
back = "Atrás",
next = "Siguiente",
done = "Hecho",
title = "Conectar a LNbits",
subtitle = "Introduce la URL de tu servidor LNbits y las claves de API.",
serverUrl = "URL del servidor",
serverUrlHint = "https://bitcointxoko.org",
invoiceKey = "Invoice Key (read-only)",
adminKey = "Admin Key (for sending)",
hideAdminKey = "Hide",
showAdminKey = "Show",
fieldsRequired = "All fields are required.",
connect = "Connect",
notificationsTitle = "Stay in the loop",
notificationsSubtitle = "Get notified instantly when you receive a payment.",
allowNotifications = "Allow Notifications",
notificationsNote = "You can change this later in system Settings.",
notificationsAutoGranted = "Notifications are enabled automatically on your device.",
batteryTitle = "Background activity",
batterySubtitle = "Allow Gudari Wallet to run unrestricted so notifications arrive even when the app is closed.",
openSettings = "Open Settings",
batteryNote = "Tap 'Battery' → 'Unrestricted'. You can skip this and change it later.",
batteryAlreadyUnrestricted = "You're all set — no restrictions detected."
invoiceKey = "Clave de factura (solo lectura)",
invoiceKeySubtitle = "Se usa para generar solicitudes de pago.",
pasteInvoiceKey = "Pegar clave de factura",
adminKey = "Clave de administrador (para enviar)",
adminKeySubtitle = "Necesaria para enviar pagos.",
pasteAdminKey = "Pegar clave de administrador",
hideAdminKey = "Ocultar",
showAdminKey = "Mostrar",
fieldsRequired = "Todos los campos son obligatorios.",
connect = "Conectar",
notificationsTitle = "Mantente al día",
notificationsSubtitle = "Recibe notificaciones al instante cuando recibas un pago.",
allowNotifications = "Permitir notificaciones",
notificationsNote = "Puedes cambiar esto más tarde en los Ajustes del sistema.",
notificationsAutoGranted = "Las notificaciones se activan automáticamente en tu dispositivo.",
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(
back = "Back",
next = "Next",
done = "Done",
title = "Connect to LNbits",
subtitle = "Enter your LNbits server URL and API keys.",
serverUrl = "Server URL",
back = "Atzera",
next = "Hurrengoa",
done = "Eginda",
title = "Konektatu LNbits-era",
subtitle = "Sartu zure LNbits zerbitzariaren URLa eta API gakoak.",
serverUrl = "Zerbitzariaren URLa",
serverUrlHint = "https://bitcointxoko.org",
invoiceKey = "Invoice Key (read-only)",
adminKey = "Admin Key (for sending)",
hideAdminKey = "Hide",
showAdminKey = "Show",
fieldsRequired = "All fields are required.",
connect = "Connect",
notificationsTitle = "Stay in the loop",
notificationsSubtitle = "Get notified instantly when you receive a payment.",
allowNotifications = "Allow Notifications",
notificationsNote = "You can change this later in system Settings.",
notificationsAutoGranted = "Notifications are enabled automatically on your device.",
batteryTitle = "Background activity",
batterySubtitle = "Allow Gudari Wallet to run unrestricted so notifications arrive even when the app is closed.",
openSettings = "Open Settings",
batteryNote = "Tap 'Battery' → 'Unrestricted'. You can skip this and change it later.",
batteryAlreadyUnrestricted = "You're all set — no restrictions detected."
invoiceKey = "Faktura gakoa (irakurtzeko soilik)",
invoiceKeySubtitle = "Ordainketa eskaerak sortzeko erabiltzen da.",
pasteInvoiceKey = "Itsatsi faktura gakoa",
adminKey = "Administrazio gakoa (bidaltzeko)",
adminKeySubtitle = "Beharrezkoa da transakzioak bidaltzeko.",
pasteAdminKey = "Itsatsi administrazio gakoa",
hideAdminKey = "Ezkutatu",
showAdminKey = "Erakutsi",
fieldsRequired = "Eremu guztiak beharrezkoak dira.",
connect = "Konektatu",
notificationsTitle = "Egon eguneratuta",
notificationsSubtitle = "Jaso jakinarazpenak berehala transakzio bat jasotzen duzunean.",
allowNotifications = "Baimendu jakinarazpenak",
notificationsNote = "Hau geroago alda dezakezu sistemaren Ezarpenetan.",
notificationsAutoGranted = "Jakinarazpenak automatikoki gaituta daude zure gailuan.",
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 serverUrlHint: String,
val invoiceKey: String,
val invoiceKeySubtitle: String,
val pasteInvoiceKey: String,
val adminKey: String,
val adminKeySubtitle: String,
val pasteAdminKey: String,
val showAdminKey: String,
val hideAdminKey: String,
val connect: String,
@@ -330,5 +334,20 @@ data class OnboardingStrings(
val batterySubtitle: String,
val openSettings: 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(
title = strings.onboarding.invoiceKey,
subtitle = "Used to generate payment requests."
subtitle = strings.onboarding.invoiceKeySubtitle // "Used to generate payment requests."
) {
OutlinedTextField(
value = invoiceKey,
@@ -161,7 +161,7 @@ val scope = rememberCoroutineScope()
}) {
Icon(
imageVector = Icons.Default.ContentPaste,
contentDescription = "Paste invoice key"
contentDescription = strings.onboarding.pasteInvoiceKey // "Paste invoice key"
)
}
}
@@ -185,7 +185,7 @@ internal fun AdminKeyPage(
OnboardingPageScaffold(
title = strings.onboarding.adminKey,
subtitle = "Required to send payments."
subtitle = strings.onboarding.adminKeySubtitle // "Required to send payments."
) {
OutlinedTextField(
value = adminKey,
@@ -209,7 +209,7 @@ internal fun AdminKeyPage(
}) {
Icon(
imageVector = Icons.Default.ContentPaste,
contentDescription = "Paste admin key"
contentDescription = strings.onboarding.pasteAdminKey // "Paste admin key"
)
}
IconButton(onClick = onToggleVis) {
@@ -333,7 +333,8 @@ fun SyncHistoryPage(
syncIsDeferred: Boolean,
onSyncNow : () -> Unit,
onSchedule : (delayMillis: Long) -> Unit,
onContinue : () -> Unit
onContinue : () -> Unit,
strings : AppStrings
) {
// Derive UI state from WorkInfo
val state = when {
@@ -364,8 +365,8 @@ fun SyncHistoryPage(
}
OnboardingPageScaffold(
title = "Payment history",
subtitle = "Download your past payments so they're available offline."
title = strings.onboarding.paymentHistory, // ← "Payment history"
subtitle = strings.onboarding.paymentHistorySubtitle // ← "Download your past payments so they're available offline."
) {
Icon(
imageVector = Icons.Default.History,
@@ -387,23 +388,23 @@ fun SyncHistoryPage(
onClick = onSyncNow,
modifier = Modifier.fillMaxWidth()
) {
Text("Sync now")
Text(strings.onboarding.syncNow) // ← "Sync now"
}
Spacer(Modifier.height(8.dp))
ScheduleLaterPicker(onSchedule = onSchedule)
ScheduleLaterPicker(onSchedule, strings)
Spacer(Modifier.height(8.dp))
TextButton(
onClick = onContinue,
modifier = Modifier.fillMaxWidth()
) {
Text("Skip for now")
Text(strings.onboarding.skipForNow) // ← "Skip for now"
}
}
// ── Scheduled (deferred, waiting for delay + network) ─────────────
SyncUiState.Scheduled -> {
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,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
@@ -413,7 +414,7 @@ fun SyncHistoryPage(
onClick = onContinue,
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))
Text(
text = "${state.fetched} / ${state.total} payments",
text = strings.onboarding.paymentsFetchedTotal(state.fetched, state.total), // ← "${state.fetched} / ${state.total} payments"
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@@ -436,7 +437,7 @@ fun SyncHistoryPage(
LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) // indeterminate
Spacer(Modifier.height(8.dp))
Text(
text = "Connecting…",
text = strings.onboarding.connecting, // ← "Connecting…"
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@@ -447,7 +448,7 @@ fun SyncHistoryPage(
onClick = onContinue,
modifier = Modifier.fillMaxWidth()
) {
Text("Continue to app →")
Text(strings.onboarding.continueToApp) // ← "Continue to app →"
}
}
@@ -455,9 +456,9 @@ fun SyncHistoryPage(
is SyncUiState.Done -> {
Text(
text = if (state.total > 0)
"${state.total} payments synced"
strings.onboarding.paymentsSynced(state.total) // ← "✓ ${state.total} payments synced"
else
"✓ Sync complete",
strings.onboarding.syncComplete, // ← "✓ Sync complete"
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary,
textAlign = TextAlign.Center
@@ -467,14 +468,14 @@ fun SyncHistoryPage(
onClick = onContinue,
modifier = Modifier.fillMaxWidth()
) {
Text("Continue to app →")
Text(strings.onboarding.continueToApp) // ← "Continue to app →"
}
}
// ── Failed ────────────────────────────────────────────────────────
SyncUiState.Failed -> {
Text(
text = "Sync failed. You can retry from Settings.",
text = strings.onboarding.syncFailed, // ← "Sync failed. You can retry from Settings."
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
textAlign = TextAlign.Center
@@ -484,7 +485,7 @@ fun SyncHistoryPage(
onClick = onContinue,
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 ─────────────────────────────────────────
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ScheduleLaterPicker(onSchedule: (delayMillis: Long) -> Unit) {
private fun ScheduleLaterPicker(onSchedule: (delayMillis: Long) -> Unit, strings: AppStrings) {
var showPicker by remember { mutableStateOf(false) }
TextButton(onClick = { showPicker = true }) {
Text("Schedule for later")
Text(strings.onboarding.scheduleForLater) // ← "Schedule for later"
}
if (showPicker) {
val timePickerState = rememberTimePickerState(is24Hour = true)
AlertDialog(
onDismissRequest = { showPicker = false },
title = { Text("Schedule sync") },
title = { Text(strings.onboarding.scheduleSync) }, // ← Schedule sync
text = {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
TimePicker(state = timePickerState)
@@ -525,10 +526,10 @@ private fun ScheduleLaterPicker(onSchedule: (delayMillis: Long) -> Unit) {
showPicker = false
val delayMs = timePickerState.toDelayMillis()
onSchedule(delayMs)
}) { Text("Schedule") }
}) { Text(strings.onboarding.schedule) } // ← "Schedule"
},
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
onComplete()
}
}
},
strings
)
}
}