From 2aa671d5428b8a7fd77fd9e4d8c4ca8ca344654f Mon Sep 17 00:00:00 2001 From: PawiX25 Date: Mon, 20 Apr 2026 01:16:32 +0200 Subject: [PATCH 1/4] feat: add CLDR plural form support to localization system - Track current locale via _currentLocale static field - Add _pluralCategory() implementing CLDR plural rules for 14 languages - When getTransalation() receives a num value, resolve CLDR category and look for suffixed key (key.one, key.few, key.many) before fallback - Add plural form keys for transaction counts in 11 language files - Update l10n integrity test to handle plural suffix keys --- assets/l10n/ar.json | 12 +++++- assets/l10n/be_BY.json | 8 +++- assets/l10n/cs_CZ.json | 6 ++- assets/l10n/de_DE.json | 4 +- assets/l10n/en.json | 4 +- assets/l10n/es_ES.json | 4 +- assets/l10n/fr_FR.json | 4 +- assets/l10n/it_IT.json | 4 +- assets/l10n/pl_PL.json | 8 +++- assets/l10n/ru_RU.json | 8 +++- assets/l10n/uk_UA.json | 8 +++- lib/l10n/flow_localizations.dart | 63 ++++++++++++++++++++++++++++-- test/l10n/json_integrity_test.dart | 51 ++++++++++++++++++++++-- 13 files changed, 166 insertions(+), 18 deletions(-) diff --git a/assets/l10n/ar.json b/assets/l10n/ar.json index fed177bf..27b5a0bc 100644 --- a/assets/l10n/ar.json +++ b/assets/l10n/ar.json @@ -623,6 +623,11 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "نسخ احتياطي موثوق ومجاني لبياناتك", "tabs.home.totalBalance": "الرصيد الإجمالي", "tabs.home.transactionsCount": "{count} معاملة", + "tabs.home.transactionsCount.zero": "{count} معاملات", + "tabs.home.transactionsCount.one": "{count} معاملة", + "tabs.home.transactionsCount.two": "{count} معاملتين", + "tabs.home.transactionsCount.few": "{count} معاملات", + "tabs.home.transactionsCount.many": "{count} معاملة", "tabs.profile": "الملف الشخصي", "tabs.profile.backup": "النسخ الاحتياطي", "tabs.profile.community": "المجتمع", @@ -734,6 +739,11 @@ "transactions.batch.importN": "استيراد {n} من المعاملات", "transactions.batch.review": "يرجى مراجعة المعاملات", "transactions.count": "{} معاملة", + "transactions.count.zero": "{} معاملات", + "transactions.count.one": "{} معاملة", + "transactions.count.two": "{} معاملتين", + "transactions.count.few": "{} معاملات", + "transactions.count.many": "{} معاملة", "transactions.pending": "المعاملات المعلقة", "transactions.query.clearAll": "مسح الفلاتر", "transactions.query.clearSelection": "مسح الاختيارات", @@ -768,4 +778,4 @@ "transactions.query.noResult": "لا توجد معاملات للعرض", "transactions.query.noResult.description": "حاول تحديث الفلاتر", "visitGitHubRepo": "زيارة المستودع على جيثب" -} \ No newline at end of file +} diff --git a/assets/l10n/be_BY.json b/assets/l10n/be_BY.json index 925aa33c..8e41ecc4 100644 --- a/assets/l10n/be_BY.json +++ b/assets/l10n/be_BY.json @@ -623,6 +623,9 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "Надзейна і бясплатна захоўвайце свае даныя", "tabs.home.totalBalance": "Агульны баланс", "tabs.home.transactionsCount": "{count} транзакцый", + "tabs.home.transactionsCount.one": "{count} транзакцыя", + "tabs.home.transactionsCount.few": "{count} транзакцыі", + "tabs.home.transactionsCount.many": "{count} транзакцый", "tabs.profile": "Профіль", "tabs.profile.backup": "Рэзервовая копія", "tabs.profile.community": "Супольнасць", @@ -734,6 +737,9 @@ "transactions.batch.importN": "Імпартаваць {n} транзакцый", "transactions.batch.review": "Калі ласка, праверце транзакцыі", "transactions.count": "{} транзакцый", + "transactions.count.one": "{} транзакцыя", + "transactions.count.few": "{} транзакцыі", + "transactions.count.many": "{} транзакцый", "transactions.pending": "Транзакцыі ў чаканні", "transactions.query.clearAll": "Ачысціць фільтры", "transactions.query.clearSelection": "Ачысціць выбар", @@ -768,4 +774,4 @@ "transactions.query.noResult": "Няма транзакцый для паказу", "transactions.query.noResult.description": "Паспрабуйце абнавіць фільтры", "visitGitHubRepo": "Наведаць рэпазіторый на GitHub" -} \ No newline at end of file +} diff --git a/assets/l10n/cs_CZ.json b/assets/l10n/cs_CZ.json index e82083df..b8c8120b 100644 --- a/assets/l10n/cs_CZ.json +++ b/assets/l10n/cs_CZ.json @@ -623,6 +623,8 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "Spolehlivě a zdarma zálohujte svá data.", "tabs.home.totalBalance": "Celkový zůstatek", "tabs.home.transactionsCount": "{count} transakcí", + "tabs.home.transactionsCount.one": "{count} transakce", + "tabs.home.transactionsCount.few": "{count} transakce", "tabs.profile": "Profil", "tabs.profile.backup": "Zálohování a synchronizace", "tabs.profile.community": "Komunita", @@ -734,6 +736,8 @@ "transactions.batch.importN": "Importovat {n} transakcí", "transactions.batch.review": "Prosím zkontrolujte transakce", "transactions.count": "{count} transakcí", + "transactions.count.one": "{} transakce", + "transactions.count.few": "{} transakce", "transactions.pending": "Čekající transakce", "transactions.query.clearAll": "Vymazat filtry", "transactions.query.clearSelection": "Zrušit výběr", @@ -768,4 +772,4 @@ "transactions.query.noResult": "Nebyly nalezeny žádné transakce.", "transactions.query.noResult.description": "Zkuste upravit filtry.", "visitGitHubRepo": "Navštivte repozitář na GitHubu" -} \ No newline at end of file +} diff --git a/assets/l10n/de_DE.json b/assets/l10n/de_DE.json index 3f1e7725..ba32252c 100644 --- a/assets/l10n/de_DE.json +++ b/assets/l10n/de_DE.json @@ -623,6 +623,7 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "Zuverlässige, kostenlose Datensicherung", "tabs.home.totalBalance": "Gesamt-Kontostand", "tabs.home.transactionsCount": "{count} Buchungen", + "tabs.home.transactionsCount.one": "{count} Buchung", "tabs.profile": "Profil", "tabs.profile.backup": "Sicherung", "tabs.profile.community": "Community", @@ -734,6 +735,7 @@ "transactions.batch.importN": "{n} Transaktionen importieren", "transactions.batch.review": "Bitte überprüfen Sie die Transaktionen", "transactions.count": "{} Buchungen", + "transactions.count.one": "{} Buchung", "transactions.pending": "Ausstehende Buchungen", "transactions.query.clearAll": "Filter löschen", "transactions.query.clearSelection": "Auswahl löschen", @@ -768,4 +770,4 @@ "transactions.query.noResult": "Keine Buchungen zum Anzeigen.", "transactions.query.noResult.description": "Versuche, die Filter anzupassen.", "visitGitHubRepo": "Repo auf GitHub besuchen" -} \ No newline at end of file +} diff --git a/assets/l10n/en.json b/assets/l10n/en.json index cb864eb6..e66d85e0 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -623,6 +623,7 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "Reliably back up your data for free", "tabs.home.totalBalance": "Total balance", "tabs.home.transactionsCount": "{count} transactions", + "tabs.home.transactionsCount.one": "{count} transaction", "tabs.profile": "Profile", "tabs.profile.backup": "Backup", "tabs.profile.community": "Community", @@ -734,6 +735,7 @@ "transactions.batch.importN": "Import {n} transactions", "transactions.batch.review": "Please review the transactions", "transactions.count": "{} transactions", + "transactions.count.one": "{} transaction", "transactions.pending": "Pending transactions", "transactions.query.clearAll": "Clear filters", "transactions.query.clearSelection": "Clear selections", @@ -768,4 +770,4 @@ "transactions.query.noResult": "No transactions to show", "transactions.query.noResult.description": "Try updating the filters", "visitGitHubRepo": "Visit repo on GitHub" -} \ No newline at end of file +} diff --git a/assets/l10n/es_ES.json b/assets/l10n/es_ES.json index 81c235e8..afac4ad5 100644 --- a/assets/l10n/es_ES.json +++ b/assets/l10n/es_ES.json @@ -623,6 +623,7 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "Realiza copias de seguridad de tus datos de forma fiable y gratis", "tabs.home.totalBalance": "Saldo total", "tabs.home.transactionsCount": "{count} transacciones", + "tabs.home.transactionsCount.one": "{count} transacción", "tabs.profile": "Perfil", "tabs.profile.backup": "Copia de seguridad", "tabs.profile.community": "Comunidad", @@ -734,6 +735,7 @@ "transactions.batch.importN": "Importar {n} transacciones", "transactions.batch.review": "Por favor, revise las transacciones", "transactions.count": "{} transacciones", + "transactions.count.one": "{} transacción", "transactions.pending": "Transacciones pendientes", "transactions.query.clearAll": "Borrar filtros", "transactions.query.clearSelection": "Borrar selecciones", @@ -768,4 +770,4 @@ "transactions.query.noResult": "No hay transacciones para mostrar", "transactions.query.noResult.description": "Intenta actualizar los filtros", "visitGitHubRepo": "Visitar repositorio en GitHub" -} \ No newline at end of file +} diff --git a/assets/l10n/fr_FR.json b/assets/l10n/fr_FR.json index fcc61f06..be34f669 100644 --- a/assets/l10n/fr_FR.json +++ b/assets/l10n/fr_FR.json @@ -623,6 +623,7 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "Sauvegardez vos données en toute fiabilité, gratuitement", "tabs.home.totalBalance": "Solde total", "tabs.home.transactionsCount": "{count} transactions", + "tabs.home.transactionsCount.one": "{count} transaction", "tabs.profile": "Profil", "tabs.profile.backup": "Sauvegarde", "tabs.profile.community": "Communauté", @@ -734,6 +735,7 @@ "transactions.batch.importN": "Importer {n} transactions", "transactions.batch.review": "Veuillez vérifier les transactions", "transactions.count": "{} transactions", + "transactions.count.one": "{} transaction", "transactions.pending": "Transactions en attente", "transactions.query.clearAll": "Effacer les filtres", "transactions.query.clearSelection": "Effacer les sélections", @@ -768,4 +770,4 @@ "transactions.query.noResult": "Aucune transaction à afficher", "transactions.query.noResult.description": "Essayez de mettre à jour les filtres", "visitGitHubRepo": "Visitez le dépôt sur GitHub" -} \ No newline at end of file +} diff --git a/assets/l10n/it_IT.json b/assets/l10n/it_IT.json index 2d96be14..9daacbcb 100644 --- a/assets/l10n/it_IT.json +++ b/assets/l10n/it_IT.json @@ -623,6 +623,7 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "Esegui il backup dei tuoi dati in modo affidabile e gratuitamente", "tabs.home.totalBalance": "Saldo totale", "tabs.home.transactionsCount": "{count} transazioni", + "tabs.home.transactionsCount.one": "{count} transazione", "tabs.profile": "Profilo", "tabs.profile.backup": "Backup", "tabs.profile.community": "Comunità", @@ -734,6 +735,7 @@ "transactions.batch.importN": "Importa {n} transazioni", "transactions.batch.review": "Rivedi le transazioni", "transactions.count": "{count} transazioni", + "transactions.count.one": "{} transazione", "transactions.pending": "Transazioni in sospeso", "transactions.query.clearAll": "Cancella filtri", "transactions.query.clearSelection": "Cancella selezioni", @@ -768,4 +770,4 @@ "transactions.query.noResult": "Nessuna transazione da mostrare", "transactions.query.noResult.description": "Prova ad aggiornare i filtri", "visitGitHubRepo": "Visita la repo su GitHub" -} \ No newline at end of file +} diff --git a/assets/l10n/pl_PL.json b/assets/l10n/pl_PL.json index 93684e3c..f88b84ea 100644 --- a/assets/l10n/pl_PL.json +++ b/assets/l10n/pl_PL.json @@ -623,6 +623,9 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "Niezawodnie twórz darmowe kopie zapasowe danych", "tabs.home.totalBalance": "Łączne saldo", "tabs.home.transactionsCount": "{count} transakcji", + "tabs.home.transactionsCount.one": "{count} transakcja", + "tabs.home.transactionsCount.few": "{count} transakcje", + "tabs.home.transactionsCount.many": "{count} transakcji", "tabs.profile": "Profil", "tabs.profile.backup": "Kopie zapasowe", "tabs.profile.community": "Społeczność", @@ -734,6 +737,9 @@ "transactions.batch.importN": "Importuj {n} transakcji", "transactions.batch.review": "Przejrzyj swoje transakcje przed zapisaniem", "transactions.count": "{} transakcji", + "transactions.count.one": "{} transakcja", + "transactions.count.few": "{} transakcje", + "transactions.count.many": "{} transakcji", "transactions.pending": "Oczekujące transakcje", "transactions.query.clearAll": "Wyczyść filtry", "transactions.query.clearSelection": "Odznacz wszystko", @@ -768,4 +774,4 @@ "transactions.query.noResult": "Brak transakcji do wyświetlenia", "transactions.query.noResult.description": "Spróbuj zaktualizować filtry", "visitGitHubRepo": "Odwiedź repozytorium GitHub" -} \ No newline at end of file +} diff --git a/assets/l10n/ru_RU.json b/assets/l10n/ru_RU.json index 3c095294..064897e5 100644 --- a/assets/l10n/ru_RU.json +++ b/assets/l10n/ru_RU.json @@ -623,6 +623,9 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "Надежное бесплатное резервное копирование данных", "tabs.home.totalBalance": "Общий баланс", "tabs.home.transactionsCount": "{count} транзакций", + "tabs.home.transactionsCount.one": "{count} транзакция", + "tabs.home.transactionsCount.few": "{count} транзакции", + "tabs.home.transactionsCount.many": "{count} транзакций", "tabs.profile": "Профиль", "tabs.profile.backup": "Резервное копирование", "tabs.profile.community": "Сообщество", @@ -734,6 +737,9 @@ "transactions.batch.importN": "Импортировать {n} транзакций", "transactions.batch.review": "Пожалуйста, проверьте транзакции", "transactions.count": "{} транзакций", + "transactions.count.one": "{} транзакция", + "transactions.count.few": "{} транзакции", + "transactions.count.many": "{} транзакций", "transactions.pending": "Ожидающие транзакции", "transactions.query.clearAll": "Очистить фильтры", "transactions.query.clearSelection": "Очистить выделение", @@ -768,4 +774,4 @@ "transactions.query.noResult": "Нет транзакций для отображения", "transactions.query.noResult.description": "Попробуйте обновить фильтры", "visitGitHubRepo": "Посетить репозиторий на GitHub" -} \ No newline at end of file +} diff --git a/assets/l10n/uk_UA.json b/assets/l10n/uk_UA.json index 2e2e8aa0..85988f8b 100644 --- a/assets/l10n/uk_UA.json +++ b/assets/l10n/uk_UA.json @@ -623,6 +623,9 @@ "tabs.home.reminders.turnOnICloudSync.subtitle": "Надійно й безкоштовно створюйте резервні копії своїх даних", "tabs.home.totalBalance": "Загальний баланс", "tabs.home.transactionsCount": "{count} транзакцій", + "tabs.home.transactionsCount.one": "{count} транзакція", + "tabs.home.transactionsCount.few": "{count} транзакції", + "tabs.home.transactionsCount.many": "{count} транзакцій", "tabs.profile": "Профіль", "tabs.profile.backup": "Резервне копіювання", "tabs.profile.community": "Спільнота", @@ -734,6 +737,9 @@ "transactions.batch.importN": "Імпортувати {n} транзакцій", "transactions.batch.review": "Будь ласка, перегляньте транзакції", "transactions.count": "{} транзакцій", + "transactions.count.one": "{} транзакція", + "transactions.count.few": "{} транзакції", + "transactions.count.many": "{} транзакцій", "transactions.pending": "Очікувані транзакції", "transactions.query.clearAll": "Очистити фільтри", "transactions.query.clearSelection": "Очистити виділення", @@ -768,4 +774,4 @@ "transactions.query.noResult": "Немає транзакцій для відображення", "transactions.query.noResult.description": "Спробуйте оновити фільтри", "visitGitHubRepo": "Відвідати репозиторій на GitHub" -} \ No newline at end of file +} diff --git a/lib/l10n/flow_localizations.dart b/lib/l10n/flow_localizations.dart index d6047d34..085ebd59 100644 --- a/lib/l10n/flow_localizations.dart +++ b/lib/l10n/flow_localizations.dart @@ -16,6 +16,7 @@ class FlowLocalizations { final Locale locale; static Map _localizedValues = {}; static Map _enUS = {}; + static Locale? _currentLocale; FlowLocalizations(this.locale); @@ -29,6 +30,7 @@ class FlowLocalizations { Future load() async { _localizedValues = await _loadLocale(locale); + _currentLocale = locale; if (_enUS.isEmpty) { if (locale.code == "en") { @@ -52,6 +54,47 @@ class FlowLocalizations { return text; } + /// Returns the CLDR plural category for [n] in the given [langCode]. + /// + /// See: https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html + static String _pluralCategory(num n, String langCode) { + final int i = n.toInt(); + switch (langCode) { + case 'pl': + if (i == 1) return 'one'; + if (i % 10 >= 2 && i % 10 <= 4 && (i % 100 < 12 || i % 100 > 14)) { + return 'few'; + } + return 'many'; + case 'ru': + case 'uk': + case 'be': + if (i % 10 == 1 && i % 100 != 11) return 'one'; + if (i % 10 >= 2 && i % 10 <= 4 && (i % 100 < 12 || i % 100 > 14)) { + return 'few'; + } + return 'many'; + case 'cs': + if (i == 1) return 'one'; + if (i >= 2 && i <= 4) return 'few'; + return 'other'; + case 'ar': + if (i == 0) return 'zero'; + if (i == 1) return 'one'; + if (i == 2) return 'two'; + if (i % 100 >= 3 && i % 100 <= 10) return 'few'; + if (i % 100 >= 11) return 'many'; + return 'other'; + case 'fr': + case 'fa': + if (i == 0 || i == 1) return 'one'; + return 'other'; + default: + // en, de, it, tr, es, mn and others: one (n=1), other + return i == 1 ? 'one' : 'other'; + } + } + static String getTransalation(String? key, {dynamic replace}) { if (key == null) return ""; if (_localizedValues.isEmpty) return ""; @@ -64,10 +107,22 @@ class FlowLocalizations { RegExp(r"{[^}]*}"), singleValue, ), - num singleValue => translatedText.replaceAll( - RegExp(r"{[^}]*}"), - singleValue.toString(), - ), + num singleValue => () { + String text = translatedText; + final String? langCode = _currentLocale?.languageCode; + if (langCode != null) { + final String category = _pluralCategory(singleValue, langCode); + final String? pluralText = + _localizedValues['$key.$category'] ?? _enUS['$key.$category']; + if (pluralText != null) { + text = pluralText; + } + } + return text.replaceAll( + RegExp(r"{[^}]*}"), + singleValue.toString(), + ); + }(), Map lookupTable => _fillFromTable(lookupTable, translatedText), _ => translatedText, }; diff --git a/test/l10n/json_integrity_test.dart b/test/l10n/json_integrity_test.dart index d611d946..764b219e 100644 --- a/test/l10n/json_integrity_test.dart +++ b/test/l10n/json_integrity_test.dart @@ -10,6 +10,21 @@ List getKeys(File file) { return jsonMap.keys.toList(); } +const _pluralSuffixes = [".zero", ".one", ".two", ".few", ".many", ".other"]; + +bool _isPluralVariant(String key) { + return _pluralSuffixes.any((suffix) => key.endsWith(suffix)); +} + +String _baseKey(String key) { + for (final suffix in _pluralSuffixes) { + if (key.endsWith(suffix)) { + return key.substring(0, key.length - suffix.length); + } + } + return key; +} + void main() { final Directory directory = Directory("assets/l10n"); @@ -30,16 +45,46 @@ void main() { expect(uniqueKeys.length, keys.length); }); + final List baseKeys = + keys.where((k) => !_isPluralVariant(k)).toList(); + final Set baseKeySet = baseKeys.toSet(); + for (final entry in directory.listSync()) { if (entry is! File) continue; if (!entry.path.endsWith(".json")) continue; if (entry.path == baseFile.path) continue; - test("File ${basename(entry.path)} has all keys in same order", () { + final String name = basename(entry.path); + + test("File $name has all base keys in same order", () { final languageKeys = getKeys(entry); + final languageBaseKeys = + languageKeys.where((k) => !_isPluralVariant(k)).toList(); + + expect(languageBaseKeys.length, baseKeys.length, + reason: "Key count mismatch"); + for (int i = 0; i < baseKeys.length; i++) { + expect(languageBaseKeys[i], baseKeys[i]); + } + }); + + test("File $name plural keys have valid base keys and ordering", () { + final languageKeys = getKeys(entry); + + for (int i = 0; i < languageKeys.length; i++) { + final key = languageKeys[i]; + if (!_isPluralVariant(key)) continue; + + final base = _baseKey(key); + expect(baseKeySet.contains(base), true, + reason: "Plural key '$key' has no base key '$base' in en.json"); - for (int i = 0; i < keys.length; i++) { - expect(languageKeys[i], keys[i]); + final baseIndex = languageKeys.indexOf(base); + expect(baseIndex, isNot(-1), + reason: "Plural key '$key' missing base key '$base' in file"); + expect(baseIndex, lessThan(i), + reason: + "Plural key '$key' should come after its base key '$base'"); } }); } From bfde3f2a59cc1aeaa7508899393df7efb24ef72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= <81538700+PawiX25@users.noreply.github.com> Date: Mon, 20 Apr 2026 02:45:12 +0200 Subject: [PATCH 2/4] fix: use correct localization key for external source setting The code used `preferences.transactions.listTile.transactionListTileShowExternalSource` but the localization files use `preferences.transactions.listTile.showExternalSource`. Fixes #708 --- .../transaction_list_item_appearance_preferences_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/preferences/transaction_list_item_appearance_preferences_page.dart b/lib/routes/preferences/transaction_list_item_appearance_preferences_page.dart index e36d68ae..1e00ee59 100644 --- a/lib/routes/preferences/transaction_list_item_appearance_preferences_page.dart +++ b/lib/routes/preferences/transaction_list_item_appearance_preferences_page.dart @@ -92,7 +92,7 @@ class _TransactionListItemAppearancePreferencesPageState ), SwitchListTile( title: Text( - "preferences.transactions.listTile.transactionListTileShowExternalSource" + "preferences.transactions.listTile.showExternalSource" .t(context), ), value: transactionListTileShowExternalSource, From 0236c1d1465c501ffe0ebac4eb80a39b958cf8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= <81538700+PawiX25@users.noreply.github.com> Date: Mon, 20 Apr 2026 02:45:54 +0200 Subject: [PATCH 3/4] fix: stabilize preview transaction dates in settings Move example transaction generation to `didChangeDependencies()` so they're created once and reused across rebuilds. Use `Random(42)` with a fixed seed to produce deterministic dates. Previously, toggling any setting called `setState()` which regenerated the preview transactions with new random dates, causing the displayed times to jump. Fixes #709 --- ...list_item_appearance_preferences_page.dart | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/routes/preferences/transaction_list_item_appearance_preferences_page.dart b/lib/routes/preferences/transaction_list_item_appearance_preferences_page.dart index e36d68ae..aa8517c2 100644 --- a/lib/routes/preferences/transaction_list_item_appearance_preferences_page.dart +++ b/lib/routes/preferences/transaction_list_item_appearance_preferences_page.dart @@ -24,6 +24,14 @@ class TransactionListItemAppearancePreferencesPage extends StatefulWidget { class _TransactionListItemAppearancePreferencesPageState extends State { + late final List _exampleTransactions; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _exampleTransactions = _buildExampleTransactions(); + } + @override Widget build(BuildContext context) { final bool useCategoryNameForUntitledTransactions = @@ -50,7 +58,7 @@ class _TransactionListItemAppearancePreferencesPageState "preferences.transactions.listTile.preview".t(context), ), const SizedBox(height: 8.0), - ...getExampleTransactions().map( + ..._exampleTransactions.map( (transaction) => IgnorePointer( child: TransactionListTile( transaction: transaction, @@ -158,7 +166,9 @@ class _TransactionListItemAppearancePreferencesPageState ); } - List getExampleTransactions() { + List _buildExampleTransactions() { + final Random random = Random(42); + final Account payPalExample = Account.preset( iconCode: FlowIconData.icon(SimpleIcons.paypal).toString(), uuid: "f38c7ea5-1726-4557-800d-d445bd30745f", @@ -176,7 +186,7 @@ class _TransactionListItemAppearancePreferencesPageState Transaction( uuid: "71011fa3-c2c5-4767-962a-b965873e6acc", transactionDate: - DateTime.now() - Duration(days: Random().nextInt(1000)), + DateTime.now() - Duration(days: random.nextInt(1000)), amount: -6.99, currency: "USD", ) @@ -186,18 +196,10 @@ class _TransactionListItemAppearancePreferencesPageState uuid: "8fea726e-997f-4e19-8012-75d8f9920a33", title: "Adbasoi ", transactionDate: - DateTime.now() - Duration(days: Random().nextInt(1000)), + DateTime.now() - Duration(days: random.nextInt(1000)), amount: -1.27, currency: "USD", )..setAccount(payPalExample), ]; - - // return Transaction( - // uuid: "3953dd66-d770-4426-9e96-d9c93707a200", - // title: "", - // transactionDate: DateTime.now(), - // amount: -6.7, - // currency: "EUR", - // )..setAccount()..setCategory(); } } From 14b28d31058c816ad7bd99d2f8b83a66da7c8b4e Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Tue, 21 Apr 2026 18:58:52 +0800 Subject: [PATCH 4/4] RC3 for 0.21.0 --- CHANGELOG.md | 2 ++ lib/l10n/flow_localizations.dart | 59 +++++++++++++++----------------- pubspec.yaml | 2 +- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6278555b..0245e238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ Special thanks to [@PawiX25](https://github.com/PawiX25) for the new widget! ### New features * Added summary home screen widget showing monthly income and expenses by [@PawiX25](https://github.com/PawiX25) +* Now we support plurals in the localization, [#707](https://github.com/flow-mn/flow/pull/707) by [@PawiX25](https://github.com/PawiX25) ### Fixes * [Android] Two Entry Last now shows the correct buttons, fixed by [@PawiX25](https://github.com/PawiX25) +* Other UX/QoL fixes by [@PawiX25](https://github.com/PawiX25) ## 0.20.0 diff --git a/lib/l10n/flow_localizations.dart b/lib/l10n/flow_localizations.dart index 085ebd59..2414b715 100644 --- a/lib/l10n/flow_localizations.dart +++ b/lib/l10n/flow_localizations.dart @@ -60,38 +60,38 @@ class FlowLocalizations { static String _pluralCategory(num n, String langCode) { final int i = n.toInt(); switch (langCode) { - case 'pl': - if (i == 1) return 'one'; + case "pl": + if (i == 1) return "one"; if (i % 10 >= 2 && i % 10 <= 4 && (i % 100 < 12 || i % 100 > 14)) { - return 'few'; + return "few"; } - return 'many'; - case 'ru': - case 'uk': - case 'be': - if (i % 10 == 1 && i % 100 != 11) return 'one'; + return "many"; + case "ru": + case "uk": + case "be": + if (i % 10 == 1 && i % 100 != 11) return "one"; if (i % 10 >= 2 && i % 10 <= 4 && (i % 100 < 12 || i % 100 > 14)) { - return 'few'; + return "few"; } - return 'many'; - case 'cs': - if (i == 1) return 'one'; - if (i >= 2 && i <= 4) return 'few'; - return 'other'; - case 'ar': - if (i == 0) return 'zero'; - if (i == 1) return 'one'; - if (i == 2) return 'two'; - if (i % 100 >= 3 && i % 100 <= 10) return 'few'; - if (i % 100 >= 11) return 'many'; - return 'other'; - case 'fr': - case 'fa': - if (i == 0 || i == 1) return 'one'; - return 'other'; + return "many"; + case "cs": + if (i == 1) return "one"; + if (i >= 2 && i <= 4) return "few"; + return "other"; + case "ar": + if (i == 0) return "zero"; + if (i == 1) return "one"; + if (i == 2) return "two"; + if (i % 100 >= 3 && i % 100 <= 10) return "few"; + if (i % 100 >= 11) return "many"; + return "other"; + case "fr": + case "fa": + if (i == 0 || i == 1) return "one"; + return "other"; default: // en, de, it, tr, es, mn and others: one (n=1), other - return i == 1 ? 'one' : 'other'; + return i == 1 ? "one" : "other"; } } @@ -113,15 +113,12 @@ class FlowLocalizations { if (langCode != null) { final String category = _pluralCategory(singleValue, langCode); final String? pluralText = - _localizedValues['$key.$category'] ?? _enUS['$key.$category']; + _localizedValues["$key.$category"] ?? _enUS["$key.$category"]; if (pluralText != null) { text = pluralText; } } - return text.replaceAll( - RegExp(r"{[^}]*}"), - singleValue.toString(), - ); + return text.replaceAll(RegExp(r"{[^}]*}"), singleValue.toString()); }(), Map lookupTable => _fillFromTable(lookupTable, translatedText), _ => translatedText, diff --git a/pubspec.yaml b/pubspec.yaml index 44ae5068..20dc8d62 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A personal finance managing app publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: "0.21.0+341" +version: "0.21.0+342" environment: sdk: ">=3.10.0 <4.0.0"