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'"); } }); }