feat(inventory): additive CSV import for bulk digitization
Import a spreadsheet list (the same 21-column schema the CSV export already produces) as new varieties/lots. Tolerant reader: columns matched by header name in any order, only variety_label required, unknown enums fall back to a safe default, rows sharing variety_id collapse into one variety with several lots. Species re-linked by scientific name. Additive by design (fresh ids + HLC), so it never overwrites existing rows; JSON stays the canonical id-reconciling import. Adds a hand-rolled RFC 4180 parser (no new dependency), an importCsv path in VarietyRepository/ExportImportService, an 'Import from CSV' tile in the backup section, and en/es strings. Note: i18n .g.dart also carries pre-existing About/intro strings from the working tree (regenerated by slang).
This commit is contained in:
parent
21072633b5
commit
89b61bc9e4
11 changed files with 1070 additions and 2 deletions
|
|
@ -51,12 +51,17 @@
|
|||
"exportJsonSubtitle": "Complete copy, can be imported back",
|
||||
"importJson": "Import from JSON",
|
||||
"importJsonSubtitle": "Merge a saved copy into this inventory",
|
||||
"importCsv": "Import from CSV",
|
||||
"importCsvSubtitle": "Add a list from a spreadsheet",
|
||||
"importConfirmTitle": "Import a saved copy?",
|
||||
"importConfirmBody": "Entries merge with your inventory; when the same entry exists on both sides, the newest version wins. Nothing gets duplicated.",
|
||||
"importCsvConfirmTitle": "Import a CSV list?",
|
||||
"importCsvConfirmBody": "Every row is added as a new entry. This does not merge or replace, so importing the same file twice adds it twice.",
|
||||
"importAction": "Import",
|
||||
"exportSaved": "Copy saved",
|
||||
"cancelled": "Cancelled",
|
||||
"importDone": "Imported: {added} new, {updated} updated",
|
||||
"importCsvDone": "Imported {count} from CSV",
|
||||
"importFailed": "This file could not be read as a Tanemaki copy",
|
||||
"failed": "Something went wrong"
|
||||
},
|
||||
|
|
@ -73,6 +78,34 @@
|
|||
"openSourceLicenses": "Open-source licenses",
|
||||
"openSourceLicensesSubtitle": "Third-party libraries and their licenses"
|
||||
},
|
||||
"intro": {
|
||||
"skip": "Skip",
|
||||
"next": "Next",
|
||||
"start": "Get started",
|
||||
"menuEntry": "How Tanemaki works",
|
||||
"slides": {
|
||||
"welcome": {
|
||||
"title": "The seed that brought you here",
|
||||
"body": "Every traditional seed is a letter written by thousands of generations, passed from hand to hand. We are what we are thanks to that sharing."
|
||||
},
|
||||
"inventory": {
|
||||
"title": "Your seed bank, in your pocket",
|
||||
"body": "Note what you have, from which year, how much and where it came from — with the name you use. A photo and a name are enough to start."
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Yours, and only yours",
|
||||
"body": "No account, no internet, no trackers. Your data lives encrypted on your device, and only what you choose is ever shared."
|
||||
},
|
||||
"share": {
|
||||
"title": "Share, the way it's always been done",
|
||||
"body": "Offer what you have spare — gift, swap or sell — and let someone nearby find it. Only an approximate distance is shown, never your address; you close the deal in person."
|
||||
},
|
||||
"plantare": {
|
||||
"title": "To sow is to multiply",
|
||||
"body": "With a Plantare, whoever receives seed promises to return some later. And since returning it means growing it, every loan multiplies the commons."
|
||||
}
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"title": "Inventory",
|
||||
"searchHint": "Search seeds",
|
||||
|
|
|
|||
|
|
@ -51,12 +51,17 @@
|
|||
"exportJsonSubtitle": "Copia completa, se puede volver a importar",
|
||||
"importJson": "Importar desde JSON",
|
||||
"importJsonSubtitle": "Fusiona una copia guardada con este inventario",
|
||||
"importCsv": "Importar desde CSV",
|
||||
"importCsvSubtitle": "Añade una lista desde una hoja de cálculo",
|
||||
"importConfirmTitle": "¿Importar una copia guardada?",
|
||||
"importConfirmBody": "Las entradas se fusionan con tu inventario; si una entrada existe en ambos lados, gana la versión más reciente. No se duplica nada.",
|
||||
"importCsvConfirmTitle": "¿Importar una lista CSV?",
|
||||
"importCsvConfirmBody": "Cada fila se añade como una entrada nueva. No fusiona ni reemplaza, así que importar el mismo fichero dos veces lo añade dos veces.",
|
||||
"importAction": "Importar",
|
||||
"exportSaved": "Copia guardada",
|
||||
"cancelled": "Cancelado",
|
||||
"importDone": "Importado: {added} nuevas, {updated} actualizadas",
|
||||
"importCsvDone": "Importadas {count} desde CSV",
|
||||
"importFailed": "Este fichero no se pudo leer como una copia de Tanemaki",
|
||||
"failed": "Algo ha salido mal"
|
||||
},
|
||||
|
|
@ -73,6 +78,34 @@
|
|||
"openSourceLicenses": "Licencias de código abierto",
|
||||
"openSourceLicensesSubtitle": "Bibliotecas de terceros y sus licencias"
|
||||
},
|
||||
"intro": {
|
||||
"skip": "Saltar",
|
||||
"next": "Siguiente",
|
||||
"start": "Empezar",
|
||||
"menuEntry": "Cómo funciona Tanemaki",
|
||||
"slides": {
|
||||
"welcome": {
|
||||
"title": "La semilla que te trajo hasta aquí",
|
||||
"body": "Cada semilla tradicional es una carta escrita por miles de generaciones, que pasó de mano en mano. Somos lo que somos gracias a ese intercambio."
|
||||
},
|
||||
"inventory": {
|
||||
"title": "Tu banco de semillas, en el bolsillo",
|
||||
"body": "Apunta qué tienes, de qué año, cuánto y de dónde vino, con el nombre que tú usas. Una foto y un nombre bastan para empezar."
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Tuyo y solo tuyo",
|
||||
"body": "Sin cuenta, sin internet, sin rastreadores. Tus datos viven cifrados en tu dispositivo y solo se comparte lo que tú decidas."
|
||||
},
|
||||
"share": {
|
||||
"title": "Compartir, como siempre se hizo",
|
||||
"body": "Ofrece lo que te sobra —regalo, trueque o venta— y que alguien cerca lo encuentre. Solo se muestra la distancia aproximada, nunca tu dirección; el trato lo cierras en persona."
|
||||
},
|
||||
"plantare": {
|
||||
"title": "Sembrar es multiplicar",
|
||||
"body": "Con el Plantare, quien recibe promete devolver semilla más adelante. Y como para devolverla hay que cultivarla, cada préstamo multiplica el común."
|
||||
}
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"title": "Inventario",
|
||||
"searchHint": "Buscar semillas",
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 2
|
||||
/// Strings: 366 (183 per locale)
|
||||
/// Strings: 404 (202 per locale)
|
||||
///
|
||||
/// Built on 2026-07-09 at 10:41 UTC
|
||||
/// Built on 2026-07-09 at 12:34 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class Translations with BaseTranslations<AppLocale, Translations> {
|
|||
late final Translations$settings$en settings = Translations$settings$en.internal(_root);
|
||||
late final Translations$backup$en backup = Translations$backup$en.internal(_root);
|
||||
late final Translations$about$en about = Translations$about$en.internal(_root);
|
||||
late final Translations$intro$en intro = Translations$intro$en.internal(_root);
|
||||
late final Translations$inventory$en inventory = Translations$inventory$en.internal(_root);
|
||||
late final Translations$quickAdd$en quickAdd = Translations$quickAdd$en.internal(_root);
|
||||
late final Translations$detail$en detail = Translations$detail$en.internal(_root);
|
||||
|
|
@ -239,12 +240,24 @@ class Translations$backup$en {
|
|||
/// en: 'Merge a saved copy into this inventory'
|
||||
String get importJsonSubtitle => 'Merge a saved copy into this inventory';
|
||||
|
||||
/// en: 'Import from CSV'
|
||||
String get importCsv => 'Import from CSV';
|
||||
|
||||
/// en: 'Add a list from a spreadsheet'
|
||||
String get importCsvSubtitle => 'Add a list from a spreadsheet';
|
||||
|
||||
/// en: 'Import a saved copy?'
|
||||
String get importConfirmTitle => 'Import a saved copy?';
|
||||
|
||||
/// en: 'Entries merge with your inventory; when the same entry exists on both sides, the newest version wins. Nothing gets duplicated.'
|
||||
String get importConfirmBody => 'Entries merge with your inventory; when the same entry exists on both sides, the newest version wins. Nothing gets duplicated.';
|
||||
|
||||
/// en: 'Import a CSV list?'
|
||||
String get importCsvConfirmTitle => 'Import a CSV list?';
|
||||
|
||||
/// en: 'Every row is added as a new entry. This does not merge or replace, so importing the same file twice adds it twice.'
|
||||
String get importCsvConfirmBody => 'Every row is added as a new entry. This does not merge or replace, so importing the same file twice adds it twice.';
|
||||
|
||||
/// en: 'Import'
|
||||
String get importAction => 'Import';
|
||||
|
||||
|
|
@ -257,6 +270,9 @@ class Translations$backup$en {
|
|||
/// en: 'Imported: {added} new, {updated} updated'
|
||||
String importDone({required Object added, required Object updated}) => 'Imported: ${added} new, ${updated} updated';
|
||||
|
||||
/// en: 'Imported {count} from CSV'
|
||||
String importCsvDone({required Object count}) => 'Imported ${count} from CSV';
|
||||
|
||||
/// en: 'This file could not be read as a Tanemaki copy'
|
||||
String get importFailed => 'This file could not be read as a Tanemaki copy';
|
||||
|
||||
|
|
@ -306,6 +322,29 @@ class Translations$about$en {
|
|||
String get openSourceLicensesSubtitle => 'Third-party libraries and their licenses';
|
||||
}
|
||||
|
||||
// Path: intro
|
||||
class Translations$intro$en {
|
||||
Translations$intro$en.internal(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'Skip'
|
||||
String get skip => 'Skip';
|
||||
|
||||
/// en: 'Next'
|
||||
String get next => 'Next';
|
||||
|
||||
/// en: 'Get started'
|
||||
String get start => 'Get started';
|
||||
|
||||
/// en: 'How Tanemaki works'
|
||||
String get menuEntry => 'How Tanemaki works';
|
||||
|
||||
late final Translations$intro$slides$en slides = Translations$intro$slides$en.internal(_root);
|
||||
}
|
||||
|
||||
// Path: inventory
|
||||
class Translations$inventory$en {
|
||||
Translations$inventory$en.internal(this._root);
|
||||
|
|
@ -631,6 +670,20 @@ class Translations$unit$en {
|
|||
late final Translations$unit$count$en count = Translations$unit$count$en.internal(_root);
|
||||
}
|
||||
|
||||
// Path: intro.slides
|
||||
class Translations$intro$slides$en {
|
||||
Translations$intro$slides$en.internal(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
late final Translations$intro$slides$welcome$en welcome = Translations$intro$slides$welcome$en.internal(_root);
|
||||
late final Translations$intro$slides$inventory$en inventory = Translations$intro$slides$inventory$en.internal(_root);
|
||||
late final Translations$intro$slides$privacy$en privacy = Translations$intro$slides$privacy$en.internal(_root);
|
||||
late final Translations$intro$slides$share$en share = Translations$intro$slides$share$en.internal(_root);
|
||||
late final Translations$intro$slides$plantare$en plantare = Translations$intro$slides$plantare$en.internal(_root);
|
||||
}
|
||||
|
||||
// Path: unit.handful
|
||||
class Translations$unit$handful$en {
|
||||
Translations$unit$handful$en.internal(this._root);
|
||||
|
|
@ -991,6 +1044,81 @@ class Translations$unit$count$en {
|
|||
String get plural => 'seeds';
|
||||
}
|
||||
|
||||
// Path: intro.slides.welcome
|
||||
class Translations$intro$slides$welcome$en {
|
||||
Translations$intro$slides$welcome$en.internal(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'The seed that brought you here'
|
||||
String get title => 'The seed that brought you here';
|
||||
|
||||
/// en: 'Every traditional seed is a letter written by thousands of generations, passed from hand to hand. We are what we are thanks to that sharing.'
|
||||
String get body => 'Every traditional seed is a letter written by thousands of generations, passed from hand to hand. We are what we are thanks to that sharing.';
|
||||
}
|
||||
|
||||
// Path: intro.slides.inventory
|
||||
class Translations$intro$slides$inventory$en {
|
||||
Translations$intro$slides$inventory$en.internal(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'Your seed bank, in your pocket'
|
||||
String get title => 'Your seed bank, in your pocket';
|
||||
|
||||
/// en: 'Note what you have, from which year, how much and where it came from — with the name you use. A photo and a name are enough to start.'
|
||||
String get body => 'Note what you have, from which year, how much and where it came from — with the name you use. A photo and a name are enough to start.';
|
||||
}
|
||||
|
||||
// Path: intro.slides.privacy
|
||||
class Translations$intro$slides$privacy$en {
|
||||
Translations$intro$slides$privacy$en.internal(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'Yours, and only yours'
|
||||
String get title => 'Yours, and only yours';
|
||||
|
||||
/// en: 'No account, no internet, no trackers. Your data lives encrypted on your device, and only what you choose is ever shared.'
|
||||
String get body => 'No account, no internet, no trackers. Your data lives encrypted on your device, and only what you choose is ever shared.';
|
||||
}
|
||||
|
||||
// Path: intro.slides.share
|
||||
class Translations$intro$slides$share$en {
|
||||
Translations$intro$slides$share$en.internal(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'Share, the way it's always been done'
|
||||
String get title => 'Share, the way it\'s always been done';
|
||||
|
||||
/// en: 'Offer what you have spare — gift, swap or sell — and let someone nearby find it. Only an approximate distance is shown, never your address; you close the deal in person.'
|
||||
String get body => 'Offer what you have spare — gift, swap or sell — and let someone nearby find it. Only an approximate distance is shown, never your address; you close the deal in person.';
|
||||
}
|
||||
|
||||
// Path: intro.slides.plantare
|
||||
class Translations$intro$slides$plantare$en {
|
||||
Translations$intro$slides$plantare$en.internal(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'To sow is to multiply'
|
||||
String get title => 'To sow is to multiply';
|
||||
|
||||
/// en: 'With a Plantare, whoever receives seed promises to return some later. And since returning it means growing it, every loan multiplies the commons.'
|
||||
String get body => 'With a Plantare, whoever receives seed promises to return some later. And since returning it means growing it, every loan multiplies the commons.';
|
||||
}
|
||||
|
||||
/// The flat map containing all translations for locale <en>.
|
||||
/// Only for edge cases! For simple maps, use the map function of this library.
|
||||
///
|
||||
|
|
@ -1038,12 +1166,17 @@ extension on Translations {
|
|||
'backup.exportJsonSubtitle' => 'Complete copy, can be imported back',
|
||||
'backup.importJson' => 'Import from JSON',
|
||||
'backup.importJsonSubtitle' => 'Merge a saved copy into this inventory',
|
||||
'backup.importCsv' => 'Import from CSV',
|
||||
'backup.importCsvSubtitle' => 'Add a list from a spreadsheet',
|
||||
'backup.importConfirmTitle' => 'Import a saved copy?',
|
||||
'backup.importConfirmBody' => 'Entries merge with your inventory; when the same entry exists on both sides, the newest version wins. Nothing gets duplicated.',
|
||||
'backup.importCsvConfirmTitle' => 'Import a CSV list?',
|
||||
'backup.importCsvConfirmBody' => 'Every row is added as a new entry. This does not merge or replace, so importing the same file twice adds it twice.',
|
||||
'backup.importAction' => 'Import',
|
||||
'backup.exportSaved' => 'Copy saved',
|
||||
'backup.cancelled' => 'Cancelled',
|
||||
'backup.importDone' => ({required Object added, required Object updated}) => 'Imported: ${added} new, ${updated} updated',
|
||||
'backup.importCsvDone' => ({required Object count}) => 'Imported ${count} from CSV',
|
||||
'backup.importFailed' => 'This file could not be read as a Tanemaki copy',
|
||||
'backup.failed' => 'Something went wrong',
|
||||
'about.title' => 'About',
|
||||
|
|
@ -1057,6 +1190,20 @@ extension on Translations {
|
|||
'about.website' => 'Website',
|
||||
'about.openSourceLicenses' => 'Open-source licenses',
|
||||
'about.openSourceLicensesSubtitle' => 'Third-party libraries and their licenses',
|
||||
'intro.skip' => 'Skip',
|
||||
'intro.next' => 'Next',
|
||||
'intro.start' => 'Get started',
|
||||
'intro.menuEntry' => 'How Tanemaki works',
|
||||
'intro.slides.welcome.title' => 'The seed that brought you here',
|
||||
'intro.slides.welcome.body' => 'Every traditional seed is a letter written by thousands of generations, passed from hand to hand. We are what we are thanks to that sharing.',
|
||||
'intro.slides.inventory.title' => 'Your seed bank, in your pocket',
|
||||
'intro.slides.inventory.body' => 'Note what you have, from which year, how much and where it came from — with the name you use. A photo and a name are enough to start.',
|
||||
'intro.slides.privacy.title' => 'Yours, and only yours',
|
||||
'intro.slides.privacy.body' => 'No account, no internet, no trackers. Your data lives encrypted on your device, and only what you choose is ever shared.',
|
||||
'intro.slides.share.title' => 'Share, the way it\'s always been done',
|
||||
'intro.slides.share.body' => 'Offer what you have spare — gift, swap or sell — and let someone nearby find it. Only an approximate distance is shown, never your address; you close the deal in person.',
|
||||
'intro.slides.plantare.title' => 'To sow is to multiply',
|
||||
'intro.slides.plantare.body' => 'With a Plantare, whoever receives seed promises to return some later. And since returning it means growing it, every loan multiplies the commons.',
|
||||
'inventory.title' => 'Inventory',
|
||||
'inventory.searchHint' => 'Search seeds',
|
||||
'inventory.empty' => 'No seeds yet. Tap + to add your first.',
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class TranslationsEs extends Translations with BaseTranslations<AppLocale, Trans
|
|||
@override late final _Translations$settings$es settings = _Translations$settings$es._(_root);
|
||||
@override late final _Translations$backup$es backup = _Translations$backup$es._(_root);
|
||||
@override late final _Translations$about$es about = _Translations$about$es._(_root);
|
||||
@override late final _Translations$intro$es intro = _Translations$intro$es._(_root);
|
||||
@override late final _Translations$inventory$es inventory = _Translations$inventory$es._(_root);
|
||||
@override late final _Translations$quickAdd$es quickAdd = _Translations$quickAdd$es._(_root);
|
||||
@override late final _Translations$detail$es detail = _Translations$detail$es._(_root);
|
||||
|
|
@ -159,12 +160,17 @@ class _Translations$backup$es extends Translations$backup$en {
|
|||
@override String get exportJsonSubtitle => 'Copia completa, se puede volver a importar';
|
||||
@override String get importJson => 'Importar desde JSON';
|
||||
@override String get importJsonSubtitle => 'Fusiona una copia guardada con este inventario';
|
||||
@override String get importCsv => 'Importar desde CSV';
|
||||
@override String get importCsvSubtitle => 'Añade una lista desde una hoja de cálculo';
|
||||
@override String get importConfirmTitle => '¿Importar una copia guardada?';
|
||||
@override String get importConfirmBody => 'Las entradas se fusionan con tu inventario; si una entrada existe en ambos lados, gana la versión más reciente. No se duplica nada.';
|
||||
@override String get importCsvConfirmTitle => '¿Importar una lista CSV?';
|
||||
@override String get importCsvConfirmBody => 'Cada fila se añade como una entrada nueva. No fusiona ni reemplaza, así que importar el mismo fichero dos veces lo añade dos veces.';
|
||||
@override String get importAction => 'Importar';
|
||||
@override String get exportSaved => 'Copia guardada';
|
||||
@override String get cancelled => 'Cancelado';
|
||||
@override String importDone({required Object added, required Object updated}) => 'Importado: ${added} nuevas, ${updated} actualizadas';
|
||||
@override String importCsvDone({required Object count}) => 'Importadas ${count} desde CSV';
|
||||
@override String get importFailed => 'Este fichero no se pudo leer como una copia de Tanemaki';
|
||||
@override String get failed => 'Algo ha salido mal';
|
||||
}
|
||||
|
|
@ -189,6 +195,20 @@ class _Translations$about$es extends Translations$about$en {
|
|||
@override String get openSourceLicensesSubtitle => 'Bibliotecas de terceros y sus licencias';
|
||||
}
|
||||
|
||||
// Path: intro
|
||||
class _Translations$intro$es extends Translations$intro$en {
|
||||
_Translations$intro$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||
|
||||
final TranslationsEs _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get skip => 'Saltar';
|
||||
@override String get next => 'Siguiente';
|
||||
@override String get start => 'Empezar';
|
||||
@override String get menuEntry => 'Cómo funciona Tanemaki';
|
||||
@override late final _Translations$intro$slides$es slides = _Translations$intro$slides$es._(_root);
|
||||
}
|
||||
|
||||
// Path: inventory
|
||||
class _Translations$inventory$es extends Translations$inventory$en {
|
||||
_Translations$inventory$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||
|
|
@ -382,6 +402,20 @@ class _Translations$unit$es extends Translations$unit$en {
|
|||
@override late final _Translations$unit$count$es count = _Translations$unit$count$es._(_root);
|
||||
}
|
||||
|
||||
// Path: intro.slides
|
||||
class _Translations$intro$slides$es extends Translations$intro$slides$en {
|
||||
_Translations$intro$slides$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||
|
||||
final TranslationsEs _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override late final _Translations$intro$slides$welcome$es welcome = _Translations$intro$slides$welcome$es._(_root);
|
||||
@override late final _Translations$intro$slides$inventory$es inventory = _Translations$intro$slides$inventory$es._(_root);
|
||||
@override late final _Translations$intro$slides$privacy$es privacy = _Translations$intro$slides$privacy$es._(_root);
|
||||
@override late final _Translations$intro$slides$share$es share = _Translations$intro$slides$share$es._(_root);
|
||||
@override late final _Translations$intro$slides$plantare$es plantare = _Translations$intro$slides$plantare$es._(_root);
|
||||
}
|
||||
|
||||
// Path: unit.handful
|
||||
class _Translations$unit$handful$es extends Translations$unit$handful$en {
|
||||
_Translations$unit$handful$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||
|
|
@ -646,6 +680,61 @@ class _Translations$unit$count$es extends Translations$unit$count$en {
|
|||
@override String get plural => 'semillas';
|
||||
}
|
||||
|
||||
// Path: intro.slides.welcome
|
||||
class _Translations$intro$slides$welcome$es extends Translations$intro$slides$welcome$en {
|
||||
_Translations$intro$slides$welcome$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||
|
||||
final TranslationsEs _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get title => 'La semilla que te trajo hasta aquí';
|
||||
@override String get body => 'Cada semilla tradicional es una carta escrita por miles de generaciones, que pasó de mano en mano. Somos lo que somos gracias a ese intercambio.';
|
||||
}
|
||||
|
||||
// Path: intro.slides.inventory
|
||||
class _Translations$intro$slides$inventory$es extends Translations$intro$slides$inventory$en {
|
||||
_Translations$intro$slides$inventory$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||
|
||||
final TranslationsEs _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get title => 'Tu banco de semillas, en el bolsillo';
|
||||
@override String get body => 'Apunta qué tienes, de qué año, cuánto y de dónde vino, con el nombre que tú usas. Una foto y un nombre bastan para empezar.';
|
||||
}
|
||||
|
||||
// Path: intro.slides.privacy
|
||||
class _Translations$intro$slides$privacy$es extends Translations$intro$slides$privacy$en {
|
||||
_Translations$intro$slides$privacy$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||
|
||||
final TranslationsEs _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get title => 'Tuyo y solo tuyo';
|
||||
@override String get body => 'Sin cuenta, sin internet, sin rastreadores. Tus datos viven cifrados en tu dispositivo y solo se comparte lo que tú decidas.';
|
||||
}
|
||||
|
||||
// Path: intro.slides.share
|
||||
class _Translations$intro$slides$share$es extends Translations$intro$slides$share$en {
|
||||
_Translations$intro$slides$share$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||
|
||||
final TranslationsEs _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get title => 'Compartir, como siempre se hizo';
|
||||
@override String get body => 'Ofrece lo que te sobra —regalo, trueque o venta— y que alguien cerca lo encuentre. Solo se muestra la distancia aproximada, nunca tu dirección; el trato lo cierras en persona.';
|
||||
}
|
||||
|
||||
// Path: intro.slides.plantare
|
||||
class _Translations$intro$slides$plantare$es extends Translations$intro$slides$plantare$en {
|
||||
_Translations$intro$slides$plantare$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||
|
||||
final TranslationsEs _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get title => 'Sembrar es multiplicar';
|
||||
@override String get body => 'Con el Plantare, quien recibe promete devolver semilla más adelante. Y como para devolverla hay que cultivarla, cada préstamo multiplica el común.';
|
||||
}
|
||||
|
||||
/// The flat map containing all translations for locale <es>.
|
||||
/// Only for edge cases! For simple maps, use the map function of this library.
|
||||
///
|
||||
|
|
@ -693,12 +782,17 @@ extension on TranslationsEs {
|
|||
'backup.exportJsonSubtitle' => 'Copia completa, se puede volver a importar',
|
||||
'backup.importJson' => 'Importar desde JSON',
|
||||
'backup.importJsonSubtitle' => 'Fusiona una copia guardada con este inventario',
|
||||
'backup.importCsv' => 'Importar desde CSV',
|
||||
'backup.importCsvSubtitle' => 'Añade una lista desde una hoja de cálculo',
|
||||
'backup.importConfirmTitle' => '¿Importar una copia guardada?',
|
||||
'backup.importConfirmBody' => 'Las entradas se fusionan con tu inventario; si una entrada existe en ambos lados, gana la versión más reciente. No se duplica nada.',
|
||||
'backup.importCsvConfirmTitle' => '¿Importar una lista CSV?',
|
||||
'backup.importCsvConfirmBody' => 'Cada fila se añade como una entrada nueva. No fusiona ni reemplaza, así que importar el mismo fichero dos veces lo añade dos veces.',
|
||||
'backup.importAction' => 'Importar',
|
||||
'backup.exportSaved' => 'Copia guardada',
|
||||
'backup.cancelled' => 'Cancelado',
|
||||
'backup.importDone' => ({required Object added, required Object updated}) => 'Importado: ${added} nuevas, ${updated} actualizadas',
|
||||
'backup.importCsvDone' => ({required Object count}) => 'Importadas ${count} desde CSV',
|
||||
'backup.importFailed' => 'Este fichero no se pudo leer como una copia de Tanemaki',
|
||||
'backup.failed' => 'Algo ha salido mal',
|
||||
'about.title' => 'Acerca de',
|
||||
|
|
@ -712,6 +806,20 @@ extension on TranslationsEs {
|
|||
'about.website' => 'Sitio web',
|
||||
'about.openSourceLicenses' => 'Licencias de código abierto',
|
||||
'about.openSourceLicensesSubtitle' => 'Bibliotecas de terceros y sus licencias',
|
||||
'intro.skip' => 'Saltar',
|
||||
'intro.next' => 'Siguiente',
|
||||
'intro.start' => 'Empezar',
|
||||
'intro.menuEntry' => 'Cómo funciona Tanemaki',
|
||||
'intro.slides.welcome.title' => 'La semilla que te trajo hasta aquí',
|
||||
'intro.slides.welcome.body' => 'Cada semilla tradicional es una carta escrita por miles de generaciones, que pasó de mano en mano. Somos lo que somos gracias a ese intercambio.',
|
||||
'intro.slides.inventory.title' => 'Tu banco de semillas, en el bolsillo',
|
||||
'intro.slides.inventory.body' => 'Apunta qué tienes, de qué año, cuánto y de dónde vino, con el nombre que tú usas. Una foto y un nombre bastan para empezar.',
|
||||
'intro.slides.privacy.title' => 'Tuyo y solo tuyo',
|
||||
'intro.slides.privacy.body' => 'Sin cuenta, sin internet, sin rastreadores. Tus datos viven cifrados en tu dispositivo y solo se comparte lo que tú decidas.',
|
||||
'intro.slides.share.title' => 'Compartir, como siempre se hizo',
|
||||
'intro.slides.share.body' => 'Ofrece lo que te sobra —regalo, trueque o venta— y que alguien cerca lo encuentre. Solo se muestra la distancia aproximada, nunca tu dirección; el trato lo cierras en persona.',
|
||||
'intro.slides.plantare.title' => 'Sembrar es multiplicar',
|
||||
'intro.slides.plantare.body' => 'Con el Plantare, quien recibe promete devolver semilla más adelante. Y como para devolverla hay que cultivarla, cada préstamo multiplica el común.',
|
||||
'inventory.title' => 'Inventario',
|
||||
'inventory.searchHint' => 'Buscar semillas',
|
||||
'inventory.empty' => 'Aún no hay semillas. Toca + para añadir la primera.',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue