i18n: RTL smoke tests, Portuguese locale and a translation guide
- Add rtl_smoke_test covering inventory, detail (lot sheet) and settings under Directionality.rtl, with an Arabic label and vernacular name. - Ship pt.i18n.json (Portuguese) as proof the locale pipeline scales beyond es/en; wire its language tile in Settings. - docs/i18n.md documents the Weblate-ready flow for adding locales.
This commit is contained in:
parent
d6781870d9
commit
5d2b41a110
11 changed files with 1736 additions and 6 deletions
|
|
@ -39,6 +39,7 @@
|
|||
"systemLanguage": "System language",
|
||||
"langEs": "Español",
|
||||
"langEn": "English",
|
||||
"langPt": "Português",
|
||||
"about": "About",
|
||||
"aboutText": "Local-first, encrypted inventory for traditional seeds. AGPL-3.0.",
|
||||
"aboutOpen": "About Tanemaki"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
"systemLanguage": "Idioma del sistema",
|
||||
"langEs": "Español",
|
||||
"langEn": "English",
|
||||
"langPt": "Português",
|
||||
"about": "Acerca de",
|
||||
"aboutText": "Inventario local y cifrado para semillas tradicionales. AGPL-3.0.",
|
||||
"aboutOpen": "Acerca de Tanemaki"
|
||||
|
|
|
|||
327
apps/app_seeds/lib/i18n/pt.i18n.json
Normal file
327
apps/app_seeds/lib/i18n/pt.i18n.json
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
{
|
||||
"app": {
|
||||
"title": "Tanemaki"
|
||||
},
|
||||
"common": {
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Eliminar",
|
||||
"edit": "Editar",
|
||||
"type": "Tipo",
|
||||
"comingSoon": "Em breve"
|
||||
},
|
||||
"home": {
|
||||
"tagline": "Partilha e cultiva sementes locais",
|
||||
"openMarket": "Mercado aberto",
|
||||
"openMarketSubtitle": "Descobre e troca",
|
||||
"yourInventory": "O teu inventário",
|
||||
"yourInventorySubtitle": "Gere as tuas sementes"
|
||||
},
|
||||
"photo": {
|
||||
"camera": "Tirar uma foto",
|
||||
"gallery": "Escolher da galeria",
|
||||
"setAsCover": "Usar como capa",
|
||||
"isCover": "Foto de capa",
|
||||
"deleteConfirm": "Eliminar esta foto?"
|
||||
},
|
||||
"menu": {
|
||||
"tagline": "o teu banco de sementes",
|
||||
"inventory": "Inventário",
|
||||
"market": "Mercado",
|
||||
"profile": "O teu perfil",
|
||||
"chat": "Conversas",
|
||||
"wishlist": "Lista de desejos",
|
||||
"following": "A seguir",
|
||||
"settings": "Definições"
|
||||
},
|
||||
"settings": {
|
||||
"language": "Idioma",
|
||||
"systemLanguage": "Idioma do sistema",
|
||||
"langEs": "Español",
|
||||
"langEn": "English",
|
||||
"langPt": "Português",
|
||||
"about": "Acerca de",
|
||||
"aboutText": "Inventário local e cifrado para sementes tradicionais. AGPL-3.0.",
|
||||
"aboutOpen": "Acerca do Tanemaki"
|
||||
},
|
||||
"backup": {
|
||||
"title": "Cópia de segurança",
|
||||
"exportJson": "Guardar uma cópia de segurança",
|
||||
"exportJsonSubtitle": "Uma cópia completa para guardar em segurança, restaurar depois ou levar para outro aparelho",
|
||||
"importJson": "Restaurar uma cópia",
|
||||
"importJsonSubtitle": "Recupera uma cópia guardada — nada fica duplicado",
|
||||
"exportCsv": "Exportar para uma folha de cálculo",
|
||||
"exportCsvSubtitle": "Uma lista simples para Excel ou LibreOffice — sem fotos",
|
||||
"importCsv": "Importar uma lista",
|
||||
"importCsvSubtitle": "Acrescenta entradas a partir de uma folha de cálculo",
|
||||
"importConfirmTitle": "Restaurar uma cópia?",
|
||||
"importConfirmBody": "As entradas fundem-se com o teu inventário; quando a mesma entrada existe dos dois lados, vence a versão mais recente. Nada fica duplicado.",
|
||||
"importCsvConfirmTitle": "Importar uma lista?",
|
||||
"importCsvConfirmBody": "Cada linha é acrescentada como uma entrada nova. Não funde nem substitui, por isso importar o mesmo ficheiro duas vezes acrescenta-o duas vezes.",
|
||||
"importAction": "Importar",
|
||||
"exportSaved": "Cópia guardada",
|
||||
"cancelled": "Cancelado",
|
||||
"importDone": "Importado: {added} novas, {updated} atualizadas",
|
||||
"importCsvDone": "{count} entradas acrescentadas",
|
||||
"importFailed": "Este ficheiro não pôde ser lido como uma cópia do Tanemaki",
|
||||
"failed": "Algo correu mal",
|
||||
"recoveryTitle": "O teu código de recuperação",
|
||||
"recoverySubtitle": "Imprime-o e guarda-o bem: abre as tuas cópias noutro aparelho",
|
||||
"recoveryIntro": "Este código abre as tuas cópias guardadas e recupera o teu banco em qualquer aparelho. Guarda duas cópias em papel em sítios seguros, como a tua melhor semente. Quem o tiver pode ler as tuas cópias, por isso não o partilhes com ninguém.",
|
||||
"recoveryCopy": "Copiar",
|
||||
"recoverySave": "Guardar a folha",
|
||||
"recoverySheetTitle": "Tanemaki — a tua folha de recuperação",
|
||||
"recoveryPromptTitle": "Escreve o teu código de recuperação",
|
||||
"recoveryPromptBody": "Esta cópia foi guardada com outro código. Escreve o código da tua folha de recuperação para a abrir.",
|
||||
"recoveryWrongCode": "Esse código não abre esta cópia"
|
||||
},
|
||||
"about": {
|
||||
"title": "Acerca de",
|
||||
"kanji": "種まき",
|
||||
"tagline": "Uma aplicação local e descentralizada para gerir e partilhar sementes e plântulas tradicionais.",
|
||||
"intro": "O Tanemaki (種まき, \"semear / espalhar sementes\" em japonês; nome curto Tane, 種 \"semente\") ajuda pessoas e coletivos a manter um inventário amigável do seu banco de sementes, decidir o que oferecem e partilhá-lo localmente — sem um intermediário central que possa controlar, censurar ou ser multado por isso. O objetivo é prático e político ao mesmo tempo: apoiar as variedades tradicionais e fazer frente ao monopólio das sementes.",
|
||||
"heritage": "O nome honra as antigas tradições japonesas de ajuda mútua à volta do arroz — yui (trabalho comunitário partilhado) e tanomoshi (fundos de reciprocidade) — que inspiraram o Plantare em papel, a \"moeda comunitária de troca de sementes\" (BAH-Semillero, 2009, CC-BY-SA). O Tanemaki é o Plantare digital.",
|
||||
"version": "Versão",
|
||||
"license": "Licença",
|
||||
"licenseValue": "AGPL-3.0",
|
||||
"website": "Sítio web",
|
||||
"openSourceLicenses": "Licenças de código aberto",
|
||||
"openSourceLicensesSubtitle": "Bibliotecas de terceiros e as suas licenças"
|
||||
},
|
||||
"intro": {
|
||||
"skip": "Saltar",
|
||||
"next": "Seguinte",
|
||||
"start": "Começar",
|
||||
"menuEntry": "Como funciona o Tanemaki",
|
||||
"slides": {
|
||||
"welcome": {
|
||||
"title": "A semente que te trouxe até aqui",
|
||||
"body": "Cada semente tradicional é uma carta escrita por milhares de gerações, passada de mão em mão. Somos o que somos graças a essa partilha."
|
||||
},
|
||||
"inventory": {
|
||||
"title": "O teu banco de sementes, no bolso",
|
||||
"body": "Anota o que tens, de que ano, quanto e de onde veio — com o nome que tu usas. Uma foto e um nome chegam para começar."
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Teu, e só teu",
|
||||
"body": "Sem conta, sem internet, sem rastreadores. Os teus dados vivem cifrados no teu aparelho, e só o que tu escolheres é partilhado."
|
||||
},
|
||||
"share": {
|
||||
"title": "Partilhar, como sempre se fez",
|
||||
"body": "Oferece o que te sobra — dar, trocar ou vender — e deixa que alguém perto de ti o encontre. Só se mostra uma distância aproximada, nunca a tua morada; o acordo fecha-se em pessoa."
|
||||
},
|
||||
"plantare": {
|
||||
"title": "Semear é multiplicar",
|
||||
"body": "Com um Plantare, quem recebe semente promete devolver uma parte mais tarde. E como devolvê-la implica cultivá-la, cada empréstimo multiplica o comum."
|
||||
}
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"title": "Inventário",
|
||||
"searchHint": "Procurar sementes",
|
||||
"empty": "Ainda não há sementes. Toca em + para adicionar a primeira.",
|
||||
"noMatches": "Nenhuma semente corresponde aos teus filtros.",
|
||||
"clearFilters": "Limpar filtros",
|
||||
"uncategorized": "Sem categoria",
|
||||
"needsReproductionFilter": "Para reproduzir"
|
||||
},
|
||||
"draft": {
|
||||
"capture": "Capturar fotos",
|
||||
"captured": "{n} capturadas para catalogar",
|
||||
"triageTitle": "Para catalogar",
|
||||
"triageCount": "{n} para catalogar",
|
||||
"untitled": "Sem nome",
|
||||
"nameField": "Dá um nome a esta semente",
|
||||
"nameHint": "O que é?",
|
||||
"suggestFromPhoto": "Sugerir nome a partir da foto",
|
||||
"discard": "Descartar"
|
||||
},
|
||||
"quickAdd": {
|
||||
"title": "Adicionar uma semente",
|
||||
"labelField": "Nome",
|
||||
"labelRequired": "Dá-lhe um nome",
|
||||
"addPhoto": "Adicionar foto",
|
||||
"quantity": "Quanto?",
|
||||
"more": "Adicionar mais…",
|
||||
"save": "Guardar",
|
||||
"saveAndAddAnother": "Guardar e adicionar outra",
|
||||
"addedCount": "{n} adicionadas",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"detail": {
|
||||
"notFound": "Esta semente já não está aqui.",
|
||||
"lots": "Lotes",
|
||||
"noLots": "Ainda não há lotes.",
|
||||
"names": "Também conhecida como",
|
||||
"addName": "Adicionar nome",
|
||||
"links": "Ligações",
|
||||
"addLink": "Adicionar ligação",
|
||||
"linkUrl": "URL",
|
||||
"linkTitle": "Título (opcional)",
|
||||
"notes": "Notas",
|
||||
"addLot": "Adicionar lote",
|
||||
"editLot": "Editar lote",
|
||||
"deleteConfirm": "Eliminar esta semente?",
|
||||
"year": "Ano {year}",
|
||||
"noYear": "Ano desconhecido"
|
||||
},
|
||||
"germination": {
|
||||
"title": "Germinação",
|
||||
"add": "Adicionar teste",
|
||||
"sampleSize": "Tamanho da amostra",
|
||||
"germinated": "Germinadas",
|
||||
"none": "Ainda não há testes de germinação.",
|
||||
"result": "{percent}%"
|
||||
},
|
||||
"viability": {
|
||||
"expiringSoon": "Usa ou reproduz nesta época",
|
||||
"expiringSoonYears": "Usa ou reproduz nesta época · dura ~{years} anos",
|
||||
"expired": "Passou a viabilidade típica — reproduzir",
|
||||
"expiredYears": "Passou a viabilidade típica (~{years} anos) — reproduzir"
|
||||
},
|
||||
"editVariety": {
|
||||
"title": "Editar semente",
|
||||
"name": "Nome",
|
||||
"category": "Categoria",
|
||||
"notes": "Notas",
|
||||
"species": "Espécie (do catálogo)",
|
||||
"speciesHint": "Procura uma espécie…",
|
||||
"organic": "Biológica",
|
||||
"organicHint": "Cultivada em modo biológico"
|
||||
},
|
||||
"addLot": {
|
||||
"title": "Adicionar lote",
|
||||
"year": "Data de colheita",
|
||||
"quantity": "Quanto?",
|
||||
"amount": "Quantidade"
|
||||
},
|
||||
"harvest": {
|
||||
"pickTitle": "Escolhe mês / ano",
|
||||
"anyMonth": "Qualquer mês",
|
||||
"noDate": "Definir data de colheita",
|
||||
"monthNames": [
|
||||
"janeiro", "fevereiro", "março", "abril", "maio", "junho",
|
||||
"julho", "agosto", "setembro", "outubro", "novembro", "dezembro"
|
||||
]
|
||||
},
|
||||
"lotType": {
|
||||
"seed": "Sementes",
|
||||
"plant": "Planta",
|
||||
"seedling": "Plântula",
|
||||
"tree": "Árvore / arbusto",
|
||||
"bulb": "Bolbo / tubérculo",
|
||||
"cutting": "Estaca"
|
||||
},
|
||||
"presentation": {
|
||||
"title": "Apresentação",
|
||||
"none": "Sem indicar",
|
||||
"pot": "Vaso",
|
||||
"tray": "Tabuleiro",
|
||||
"plug": "Alvéolo",
|
||||
"bareRoot": "Raiz nua",
|
||||
"rootBall": "Torrão"
|
||||
},
|
||||
"provenance": {
|
||||
"section": "De onde vem",
|
||||
"seedsFrom": "Sementes de",
|
||||
"seedsFromHint": "Quem as cultivou ou ofereceu",
|
||||
"place": "Lugar",
|
||||
"placeHint": "De onde vêm (com a região)",
|
||||
"addSeedsFrom": "Sementes de",
|
||||
"addPlace": "Lugar"
|
||||
},
|
||||
"abundance": {
|
||||
"add": "Quanta tenho",
|
||||
"title": "Quanta tenho",
|
||||
"none": "Sem indicar",
|
||||
"plentyToShare": "De sobra para partilhar",
|
||||
"enoughToShare": "Bastante, para partilhar com moderação",
|
||||
"enoughForMe": "Suficiente para mim",
|
||||
"runningLow": "Resta pouca"
|
||||
},
|
||||
"share": {
|
||||
"add": "Partilhas esta?",
|
||||
"title": "Partilhas esta?",
|
||||
"nudge": "Tens de sobra: podias partilhar um pouco.",
|
||||
"private": "Só para mim",
|
||||
"gift": "Para dar",
|
||||
"exchange": "Para trocar",
|
||||
"sell": "À venda",
|
||||
"filterChip": "Partilho",
|
||||
"printCatalog": "Imprimir o que partilho",
|
||||
"catalogTitle": "O que partilho",
|
||||
"catalogSaved": "Catálogo guardado",
|
||||
"cancelled": "Cancelado"
|
||||
},
|
||||
"cropCalendar": {
|
||||
"add": "Calendário de cultivo",
|
||||
"title": "Calendário de cultivo",
|
||||
"sow": "Sementeira",
|
||||
"transplant": "Transplante",
|
||||
"flowering": "Floração",
|
||||
"fruiting": "Frutificação",
|
||||
"seedHarvest": "Colheita de semente",
|
||||
"unset": "—"
|
||||
},
|
||||
"needsReproduction": {
|
||||
"label": "Reproduzir nesta época",
|
||||
"hint": "Cultiva-a antes que a semente acabe",
|
||||
"badge": "Por reproduzir"
|
||||
},
|
||||
"preservation": {
|
||||
"add": "Como está guardada",
|
||||
"title": "Como está guardada",
|
||||
"none": "Sem indicar",
|
||||
"jarWithDesiccant": "Frasco com agente secante",
|
||||
"glassJar": "Frasco de vidro",
|
||||
"paperEnvelope": "Envelope de papel",
|
||||
"paperBag": "Saco de papel",
|
||||
"plasticBag": "Saco de plástico"
|
||||
},
|
||||
"conditionCheck": {
|
||||
"advanced": "Armazenamento e detalhes de banco de sementes",
|
||||
"title": "Verificações de armazenamento",
|
||||
"add": "Adicionar verificação",
|
||||
"containers": "Frascos / recipientes",
|
||||
"desiccant": "Agente secante",
|
||||
"none": "Ainda não há verificações de armazenamento.",
|
||||
"summary": "{count} frasco(s) · {state}"
|
||||
},
|
||||
"desiccant": {
|
||||
"none": "Nenhum",
|
||||
"add": "Adicionar",
|
||||
"replace": "Substituir",
|
||||
"dry": "Azul — seco",
|
||||
"fresh": "Acabado de renovar"
|
||||
},
|
||||
"unit": {
|
||||
"aFew": "algumas",
|
||||
"some": "bastantes",
|
||||
"plenty": "muitas",
|
||||
"pinch": "uma pitada",
|
||||
"handful": { "singular": "mão-cheia", "plural": "mãos-cheias" },
|
||||
"teaspoon": { "singular": "colher de chá", "plural": "colheres de chá" },
|
||||
"spoon": { "singular": "colher", "plural": "colheres" },
|
||||
"cup": { "singular": "chávena", "plural": "chávenas" },
|
||||
"jar": { "singular": "frasco", "plural": "frascos" },
|
||||
"sack": { "singular": "saco", "plural": "sacos" },
|
||||
"packet": { "singular": "pacote", "plural": "pacotes" },
|
||||
"cob": { "singular": "maçaroca", "plural": "maçarocas" },
|
||||
"pod": { "singular": "vagem", "plural": "vagens" },
|
||||
"ear": { "singular": "espiga", "plural": "espigas" },
|
||||
"head": { "singular": "cabeça", "plural": "cabeças" },
|
||||
"fruit": { "singular": "fruto", "plural": "frutos" },
|
||||
"bulb": { "singular": "bolbo", "plural": "bolbos" },
|
||||
"tuber": { "singular": "tubérculo", "plural": "tubérculos" },
|
||||
"seedHead": { "singular": "capítulo", "plural": "capítulos" },
|
||||
"bunch": { "singular": "molho", "plural": "molhos" },
|
||||
"plant": { "singular": "planta", "plural": "plantas" },
|
||||
"pot": { "singular": "vaso", "plural": "vasos" },
|
||||
"tray": { "singular": "tabuleiro", "plural": "tabuleiros" },
|
||||
"seedling": { "singular": "plântula", "plural": "plântulas" },
|
||||
"tree": { "singular": "árvore", "plural": "árvores" },
|
||||
"cutting": { "singular": "estaca", "plural": "estacas" },
|
||||
"grams": { "singular": "grama", "plural": "gramas" },
|
||||
"count": { "singular": "semente", "plural": "sementes" }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,10 @@
|
|||
/// Source: lib/i18n
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 2
|
||||
/// Strings: 572 (286 per locale)
|
||||
/// Locales: 3
|
||||
/// Strings: 861 (287 per locale)
|
||||
///
|
||||
/// Built on 2026-07-09 at 20:28 UTC
|
||||
/// Built on 2026-07-09 at 20:35 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
|
@ -19,6 +19,7 @@ import 'package:slang_flutter/slang_flutter.dart';
|
|||
export 'package:slang_flutter/slang_flutter.dart';
|
||||
|
||||
import 'strings_es.g.dart' as l_es;
|
||||
import 'strings_pt.g.dart' as l_pt;
|
||||
part 'strings_en.g.dart';
|
||||
|
||||
/// Supported locales.
|
||||
|
|
@ -29,7 +30,8 @@ part 'strings_en.g.dart';
|
|||
/// - if (LocaleSettings.currentLocale == AppLocale.en) // locale check
|
||||
enum AppLocale with BaseAppLocale<AppLocale, Translations> {
|
||||
en(languageCode: 'en'),
|
||||
es(languageCode: 'es');
|
||||
es(languageCode: 'es'),
|
||||
pt(languageCode: 'pt');
|
||||
|
||||
const AppLocale({
|
||||
required this.languageCode,
|
||||
|
|
@ -73,6 +75,12 @@ enum AppLocale with BaseAppLocale<AppLocale, Translations> {
|
|||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
case AppLocale.pt:
|
||||
return l_pt.TranslationsPt(
|
||||
overrides: overrides,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -211,6 +211,9 @@ class Translations$settings$en {
|
|||
/// en: 'English'
|
||||
String get langEn => 'English';
|
||||
|
||||
/// en: 'Português'
|
||||
String get langPt => 'Português';
|
||||
|
||||
/// en: 'About'
|
||||
String get about => 'About';
|
||||
|
||||
|
|
@ -1508,6 +1511,7 @@ extension on Translations {
|
|||
'settings.systemLanguage' => 'System language',
|
||||
'settings.langEs' => 'Español',
|
||||
'settings.langEn' => 'English',
|
||||
'settings.langPt' => 'Português',
|
||||
'settings.about' => 'About',
|
||||
'settings.aboutText' => 'Local-first, encrypted inventory for traditional seeds. AGPL-3.0.',
|
||||
'settings.aboutOpen' => 'About Tanemaki',
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ class _Translations$settings$es extends Translations$settings$en {
|
|||
@override String get systemLanguage => 'Idioma del sistema';
|
||||
@override String get langEs => 'Español';
|
||||
@override String get langEn => 'English';
|
||||
@override String get langPt => 'Português';
|
||||
@override String get about => 'Acerca de';
|
||||
@override String get aboutText => 'Inventario local y cifrado para semillas tradicionales. AGPL-3.0.';
|
||||
@override String get aboutOpen => 'Acerca de Tanemaki';
|
||||
|
|
@ -956,6 +957,7 @@ extension on TranslationsEs {
|
|||
'settings.systemLanguage' => 'Idioma del sistema',
|
||||
'settings.langEs' => 'Español',
|
||||
'settings.langEn' => 'English',
|
||||
'settings.langPt' => 'Português',
|
||||
'settings.about' => 'Acerca de',
|
||||
'settings.aboutText' => 'Inventario local y cifrado para semillas tradicionales. AGPL-3.0.',
|
||||
'settings.aboutOpen' => 'Acerca de Tanemaki',
|
||||
|
|
|
|||
1221
apps/app_seeds/lib/i18n/strings_pt.g.dart
Normal file
1221
apps/app_seeds/lib/i18n/strings_pt.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -33,6 +33,11 @@ class SettingsScreen extends StatelessWidget {
|
|||
selected: current == 'en',
|
||||
onTap: () => LocaleSettings.setLocale(AppLocale.en),
|
||||
),
|
||||
_LanguageTile(
|
||||
label: t.settings.langPt,
|
||||
selected: current == 'pt',
|
||||
onTap: () => LocaleSettings.setLocale(AppLocale.pt),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.smartphone_outlined),
|
||||
title: Text(t.settings.systemLanguage),
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ Widget wrapScreen({
|
|||
required VarietyRepository repository,
|
||||
required Widget child,
|
||||
AppLocale locale = AppLocale.en,
|
||||
TextDirection? textDirection,
|
||||
}) {
|
||||
LocaleSettings.setLocaleSync(locale);
|
||||
return TranslationProvider(
|
||||
|
|
@ -75,7 +76,9 @@ Widget wrapScreen({
|
|||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
home: child,
|
||||
home: textDirection == null
|
||||
? child
|
||||
: Directionality(textDirection: textDirection, child: child),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -88,6 +91,7 @@ Widget wrapDetail({
|
|||
required String varietyId,
|
||||
SpeciesRepository? species,
|
||||
AppLocale locale = AppLocale.en,
|
||||
TextDirection? textDirection,
|
||||
}) {
|
||||
LocaleSettings.setLocaleSync(locale);
|
||||
return TranslationProvider(
|
||||
|
|
@ -106,7 +110,12 @@ Widget wrapDetail({
|
|||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
home: const VarietyDetailScreen(),
|
||||
home: textDirection == null
|
||||
? const VarietyDetailScreen()
|
||||
: Directionality(
|
||||
textDirection: textDirection,
|
||||
child: const VarietyDetailScreen(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
99
apps/app_seeds/test/ui/rtl_smoke_test.dart
Normal file
99
apps/app_seeds/test/ui/rtl_smoke_test.dart
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import 'package:commons_core/commons_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tane/db/database.dart';
|
||||
import 'package:tane/db/enums.dart';
|
||||
import 'package:tane/ui/inventory_list_screen.dart';
|
||||
import 'package:tane/ui/settings_screen.dart';
|
||||
|
||||
import '../support/test_support.dart';
|
||||
|
||||
/// RTL is a first-class requirement (CLAUDE.md): the main screens must build
|
||||
/// and lay out under right-to-left without overflowing or misplacing
|
||||
/// directional affordances. Overflow errors fail these tests automatically.
|
||||
void main() {
|
||||
late AppDatabase db;
|
||||
|
||||
setUp(() => db = newTestDatabase());
|
||||
tearDown(() => db.close());
|
||||
|
||||
testWidgets('the inventory list renders under RTL', (tester) async {
|
||||
final repo = newTestRepository(db);
|
||||
final id = await repo.addQuickVariety(
|
||||
label: 'طماطم بلدية', // an Arabic label, as a keeper would write it
|
||||
category: 'Solanaceae',
|
||||
);
|
||||
await repo.addLot(
|
||||
varietyId: id,
|
||||
harvestYear: 2024,
|
||||
quantity: const Quantity(kind: QuantityKind.handful),
|
||||
offerStatus: OfferStatus.shared,
|
||||
);
|
||||
await repo.addQuickVariety(label: 'Maize', category: 'Poaceae');
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrapScreen(
|
||||
repository: repo,
|
||||
textDirection: TextDirection.rtl,
|
||||
child: const InventoryListScreen(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('طماطم بلدية'), findsOneWidget);
|
||||
expect(find.byKey(const Key('inventory.filter.sharing')), findsOneWidget);
|
||||
await disposeTree(tester);
|
||||
});
|
||||
|
||||
testWidgets('the variety detail (lots, calendar, names) renders under RTL', (
|
||||
tester,
|
||||
) async {
|
||||
final repo = newTestRepository(db);
|
||||
final id = await repo.addQuickVariety(
|
||||
label: 'Grandma tomato',
|
||||
category: 'Solanaceae',
|
||||
);
|
||||
await repo.addVernacularName(id, 'طماطم');
|
||||
await repo.addLot(
|
||||
varietyId: id,
|
||||
harvestYear: 2023,
|
||||
quantity: const Quantity(kind: QuantityKind.packet, count: 3),
|
||||
abundance: Abundance.plentyToShare,
|
||||
offerStatus: OfferStatus.exchange,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrapDetail(
|
||||
repository: repo,
|
||||
varietyId: id,
|
||||
species: newTestSpeciesRepository(db),
|
||||
textDirection: TextDirection.rtl,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Grandma tomato'), findsOneWidget);
|
||||
expect(find.text('طماطم'), findsOneWidget);
|
||||
|
||||
// The lot sheet (the busiest form) must also lay out under RTL.
|
||||
await tester.tap(find.byKey(const Key('detail.addLot')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('addLot.save')), findsOneWidget);
|
||||
await disposeTree(tester);
|
||||
});
|
||||
|
||||
testWidgets('settings renders under RTL', (tester) async {
|
||||
final repo = newTestRepository(db);
|
||||
await tester.pumpWidget(
|
||||
wrapScreen(
|
||||
repository: repo,
|
||||
textDirection: TextDirection.rtl,
|
||||
child: const SettingsScreen(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Português'), findsOneWidget);
|
||||
await disposeTree(tester);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue