feat(backup): sealed backups + printable recovery sheet
'Save a backup' now writes a sealed .tanemaki file (AES-256-GCM under a key HKDF-derived from the root seed; format TANEBK v1, open and stable). Restoring on the same identity is silent; a copy sealed by another identity asks for the printed recovery code (TANE1 base32, typo-tolerant) and adopts that seed — recovering your bank recovers you. Legacy plain exports still restore. Settings gains a recovery sheet (QR + code PDF). BackupBox/RecoveryCode live in commons_core, pure Dart, TDD.
This commit is contained in:
parent
ba87bf2719
commit
d6781870d9
19 changed files with 1016 additions and 81 deletions
|
|
@ -63,7 +63,16 @@
|
|||
"importDone": "Imported: {added} new, {updated} updated",
|
||||
"importCsvDone": "Added {count} entries",
|
||||
"importFailed": "This file could not be read as a Tanemaki copy",
|
||||
"failed": "Something went wrong"
|
||||
"failed": "Something went wrong",
|
||||
"recoveryTitle": "Your recovery code",
|
||||
"recoverySubtitle": "Print it and keep it safe — it opens your copies on a new device",
|
||||
"recoveryIntro": "This code opens your saved copies and brings your bank back on any device. Keep two paper copies in safe places, like your best seed. Anyone holding it can read your copies, so share it with no one.",
|
||||
"recoveryCopy": "Copy",
|
||||
"recoverySave": "Save the sheet",
|
||||
"recoverySheetTitle": "Tanemaki — your recovery sheet",
|
||||
"recoveryPromptTitle": "Enter your recovery code",
|
||||
"recoveryPromptBody": "This copy was saved with another code. Type the code from your recovery sheet to open it.",
|
||||
"recoveryWrongCode": "That code doesn't open this copy"
|
||||
},
|
||||
"about": {
|
||||
"title": "About",
|
||||
|
|
|
|||
|
|
@ -63,7 +63,16 @@
|
|||
"importDone": "Importado: {added} nuevas, {updated} actualizadas",
|
||||
"importCsvDone": "Añadidas {count} entradas",
|
||||
"importFailed": "Este fichero no se pudo leer como una copia de Tanemaki",
|
||||
"failed": "Algo ha salido mal"
|
||||
"failed": "Algo ha salido mal",
|
||||
"recoveryTitle": "Tu código de recuperación",
|
||||
"recoverySubtitle": "Imprímelo y guárdalo bien: abre tus copias en otro dispositivo",
|
||||
"recoveryIntro": "Este código abre tus copias guardadas y recupera tu banco en cualquier dispositivo. Guarda dos copias en papel en sitios seguros, como tu mejor semilla. Quien lo tenga puede leer tus copias, así que no lo compartas con nadie.",
|
||||
"recoveryCopy": "Copiar",
|
||||
"recoverySave": "Guardar la hoja",
|
||||
"recoverySheetTitle": "Tanemaki — tu hoja de recuperación",
|
||||
"recoveryPromptTitle": "Escribe tu código de recuperación",
|
||||
"recoveryPromptBody": "Esta copia se guardó con otro código. Escribe el código de tu hoja de recuperación para abrirla.",
|
||||
"recoveryWrongCode": "Ese código no abre esta copia"
|
||||
},
|
||||
"about": {
|
||||
"title": "Acerca de",
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 2
|
||||
/// Strings: 554 (277 per locale)
|
||||
/// Strings: 572 (286 per locale)
|
||||
///
|
||||
/// Built on 2026-07-09 at 20:14 UTC
|
||||
/// Built on 2026-07-09 at 20:28 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
|
|
|||
|
|
@ -288,6 +288,33 @@ class Translations$backup$en {
|
|||
|
||||
/// en: 'Something went wrong'
|
||||
String get failed => 'Something went wrong';
|
||||
|
||||
/// en: 'Your recovery code'
|
||||
String get recoveryTitle => 'Your recovery code';
|
||||
|
||||
/// en: 'Print it and keep it safe — it opens your copies on a new device'
|
||||
String get recoverySubtitle => 'Print it and keep it safe — it opens your copies on a new device';
|
||||
|
||||
/// en: 'This code opens your saved copies and brings your bank back on any device. Keep two paper copies in safe places, like your best seed. Anyone holding it can read your copies, so share it with no one.'
|
||||
String get recoveryIntro => 'This code opens your saved copies and brings your bank back on any device. Keep two paper copies in safe places, like your best seed. Anyone holding it can read your copies, so share it with no one.';
|
||||
|
||||
/// en: 'Copy'
|
||||
String get recoveryCopy => 'Copy';
|
||||
|
||||
/// en: 'Save the sheet'
|
||||
String get recoverySave => 'Save the sheet';
|
||||
|
||||
/// en: 'Tanemaki — your recovery sheet'
|
||||
String get recoverySheetTitle => 'Tanemaki — your recovery sheet';
|
||||
|
||||
/// en: 'Enter your recovery code'
|
||||
String get recoveryPromptTitle => 'Enter your recovery code';
|
||||
|
||||
/// en: 'This copy was saved with another code. Type the code from your recovery sheet to open it.'
|
||||
String get recoveryPromptBody => 'This copy was saved with another code. Type the code from your recovery sheet to open it.';
|
||||
|
||||
/// en: 'That code doesn't open this copy'
|
||||
String get recoveryWrongCode => 'That code doesn\'t open this copy';
|
||||
}
|
||||
|
||||
// Path: about
|
||||
|
|
@ -1504,6 +1531,15 @@ extension on Translations {
|
|||
'backup.importCsvDone' => ({required Object count}) => 'Added ${count} entries',
|
||||
'backup.importFailed' => 'This file could not be read as a Tanemaki copy',
|
||||
'backup.failed' => 'Something went wrong',
|
||||
'backup.recoveryTitle' => 'Your recovery code',
|
||||
'backup.recoverySubtitle' => 'Print it and keep it safe — it opens your copies on a new device',
|
||||
'backup.recoveryIntro' => 'This code opens your saved copies and brings your bank back on any device. Keep two paper copies in safe places, like your best seed. Anyone holding it can read your copies, so share it with no one.',
|
||||
'backup.recoveryCopy' => 'Copy',
|
||||
'backup.recoverySave' => 'Save the sheet',
|
||||
'backup.recoverySheetTitle' => 'Tanemaki — your recovery sheet',
|
||||
'backup.recoveryPromptTitle' => 'Enter your recovery code',
|
||||
'backup.recoveryPromptBody' => 'This copy was saved with another code. Type the code from your recovery sheet to open it.',
|
||||
'backup.recoveryWrongCode' => 'That code doesn\'t open this copy',
|
||||
'about.title' => 'About',
|
||||
'about.kanji' => '種まき',
|
||||
'about.tagline' => 'A local-first, decentralized app for managing and sharing traditional seeds and seedlings.',
|
||||
|
|
|
|||
|
|
@ -183,6 +183,15 @@ class _Translations$backup$es extends Translations$backup$en {
|
|||
@override String importCsvDone({required Object count}) => 'Añadidas ${count} entradas';
|
||||
@override String get importFailed => 'Este fichero no se pudo leer como una copia de Tanemaki';
|
||||
@override String get failed => 'Algo ha salido mal';
|
||||
@override String get recoveryTitle => 'Tu código de recuperación';
|
||||
@override String get recoverySubtitle => 'Imprímelo y guárdalo bien: abre tus copias en otro dispositivo';
|
||||
@override String get recoveryIntro => 'Este código abre tus copias guardadas y recupera tu banco en cualquier dispositivo. Guarda dos copias en papel en sitios seguros, como tu mejor semilla. Quien lo tenga puede leer tus copias, así que no lo compartas con nadie.';
|
||||
@override String get recoveryCopy => 'Copiar';
|
||||
@override String get recoverySave => 'Guardar la hoja';
|
||||
@override String get recoverySheetTitle => 'Tanemaki — tu hoja de recuperación';
|
||||
@override String get recoveryPromptTitle => 'Escribe tu código de recuperación';
|
||||
@override String get recoveryPromptBody => 'Esta copia se guardó con otro código. Escribe el código de tu hoja de recuperación para abrirla.';
|
||||
@override String get recoveryWrongCode => 'Ese código no abre esta copia';
|
||||
}
|
||||
|
||||
// Path: about
|
||||
|
|
@ -970,6 +979,15 @@ extension on TranslationsEs {
|
|||
'backup.importCsvDone' => ({required Object count}) => 'Añadidas ${count} entradas',
|
||||
'backup.importFailed' => 'Este fichero no se pudo leer como una copia de Tanemaki',
|
||||
'backup.failed' => 'Algo ha salido mal',
|
||||
'backup.recoveryTitle' => 'Tu código de recuperación',
|
||||
'backup.recoverySubtitle' => 'Imprímelo y guárdalo bien: abre tus copias en otro dispositivo',
|
||||
'backup.recoveryIntro' => 'Este código abre tus copias guardadas y recupera tu banco en cualquier dispositivo. Guarda dos copias en papel en sitios seguros, como tu mejor semilla. Quien lo tenga puede leer tus copias, así que no lo compartas con nadie.',
|
||||
'backup.recoveryCopy' => 'Copiar',
|
||||
'backup.recoverySave' => 'Guardar la hoja',
|
||||
'backup.recoverySheetTitle' => 'Tanemaki — tu hoja de recuperación',
|
||||
'backup.recoveryPromptTitle' => 'Escribe tu código de recuperación',
|
||||
'backup.recoveryPromptBody' => 'Esta copia se guardó con otro código. Escribe el código de tu hoja de recuperación para abrirla.',
|
||||
'backup.recoveryWrongCode' => 'Ese código no abre esta copia',
|
||||
'about.title' => 'Acerca de',
|
||||
'about.kanji' => '種まき',
|
||||
'about.tagline' => 'Una app local-first y descentralizada para gestionar y compartir semillas y plantones tradicionales.',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue