-
Notifications
You must be signed in to change notification settings - Fork 0
Dev Localization ARB
The app ships in 23 languages and auto-switches based on the device locale. Strings live in ARB fragments (lib/l10n/_fragments/*.arb) that are assembled into the per-locale top-level ARBs (lib/l10n/app_*.arb), then compiled to Dart via Flutter's gen-l10n.
bg cs da de el en es et fi fr hr hu it lt lv nb nl pl pt ro sk sl sv
Plus the en_XA pseudo-locale — an internal text-expansion locale (auto-generated from en by widening every string ~40 %) used by widget tests to catch overflow bugs before they ship in real translations (#1699). It is regenerated by dart tool/gen_pseudo_arb.dart and never shown to end users.
English (en) is the canonical source — every key must exist there.
Three commands, run in order, after any ARB-fragment edit:
dart tool/build_arb.dart # concatenates _fragments/*.arb → app_*.arb
dart tool/gen_pseudo_arb.dart # regenerates app_en_XA.arb from app_en.arb
flutter gen-l10n # generates app_localizations*.dartThe first call is what you'll run most often: add the new key + its translation to every relevant fragment under lib/l10n/_fragments/, then re-run build_arb.dart to fold them into the top-level locale files. CI's codegen-drift gate fails the PR if you forget to commit the regenerated files.
Text(AppLocalizations.of(context)?.searchButton ?? 'Search')Always fall back to an English literal if the locale can't resolve (defensive against ARB gaps). The static scan in test/i18n/arb_key_parity_test.dart (#729) fails CI if any locale is missing keys that exist in English.
- Add the key to
lib/l10n/app_en.arb:{ "searchButton": "Search", "@searchButton": { "description": "Primary action on the search screen" } } - Add translations to every other
app_*.arb— at minimumde,fr,it,es,pt,dafor user-facing strings. - Run the generator:
flutter gen-l10n # or it runs automatically on `flutter run` - Use in code:
Text(AppLocalizations.of(context)?.searchButton ?? 'Search')
- Add a widget test that asserts the English fallback is used when the locale is forced to something unsupported.
Never ship a key that only exists in English. Options:
- Commission a translation from the community via an issue.
- Translate yourself (even via machine translation) and mark with
@@descriptionnoting it needs a native review. - Postpone the string — use a technical fallback that isn't user-facing.
ICU syntax:
{
"stationsFound": "{count, plural, =0{No stations} =1{1 station} other{{count} stations}}",
"@stationsFound": {
"placeholders": {
"count": {"type": "int"}
}
}
}Used as: l10n.stationsFound(results.length).
Use UnitFormatter and intl — not raw Dart formatting:
UnitFormatter.formatPricePerUnit(1.659) // "1,659 €/L" in de_DE, "1.659 €/L" in en_US
UnitFormatter.formatDistance(2.34) // "2,3 km" in de_DE
intl.DateFormat.yMd(l10n.localeName).format(dt)activeLanguageProvider (lib/core/language/active_language_provider.dart) is keep-alive. Changing it rebuilds the entire widget tree — the MaterialApp is keyed on the locale code so there's no stale AppLocalizations in any subtree.
Override in tests:
await tester.pumpWidget(
pumpApp(
overrides: [activeLanguageProvider.overrideWithValue(const Locale('fr'))],
child: const SearchScreen(),
),
);The key-parity test catches missing keys. Two more rules enforced manually:
-
No HTML in ARB. Use separate keys for structural fragments; don't embed
<b>or<br>. - Keys are camelCase, no dots, no prefixes (the ARB file is already namespaced by language).
- Create
lib/l10n/app_ja.arb(for Japanese) with every key translated. - Add to
l10n.yaml's supported list (if not auto-discovered). - Add
const Locale('ja')tosupportedLocalesinapp.dart. - Add the language option to Settings → Language (the list auto-populates from
supportedLocales). - Run
flutter gen-l10nto regenerateAppLocalizations. - Add tests for the new locale rendering.
Two CI tests guard ARB hygiene:
-
test/i18n/arb_key_parity_test.dart(#729) — iterates everyapp_*.arb, diffs againstapp_en.arb, fails if any locale misses a key or has orphaned keys. -
test/l10n/localization_completeness_test.dart— asserts that every translated locale resolves every getter at runtime (catches keys present in the ARB but missing fromAppLocalizations<Locale>).
Orphans are fine during a transition (stale keys from removed features) but they should be cleaned up in a follow-up PR.
Machine translation is OK for a first pass on non-critical strings (help text, tooltips). Native review is required for:
- Primary CTAs (Search, Save, Delete, Confirm)
- Error messages (users quote these when asking for help)
- Privacy-policy-adjacent strings
- Legal disclaimers
When you commit machine-translated strings, mark the keys with @@description: "MT — needs native review" so reviewers can track what's provisional.
- Adding a Country — localisation for country-specific strings
- Contributing — how to submit a translation PR
👤 User Guide
🛠️ Developer Guide
Architecture
Code patterns
Quality
Deep dives
Reference
Workflow