A Flutter grocery app localized with gen-l10n into English, German and Polish shows German to French-speaking users and English strings to some Polish users; what is wrong?
answer
- supportedLocales order matters
- generated list is alphabetical
- preferred-supported-locales
- missing keys filled from template
- untranslated-messages-file in CI
basics
~20 sThe generated supportedLocales list is alphabetical (de, en, pl), and an unmatched device locale falls back to the first entry, German; set preferred-supported-locales: [en]. English text in Polish means keys missing from app_pl.arb were filled from the template.
solid answer
~40 sTwo separate gen-l10n behaviours. First, `AppLocalizations.supportedLocales` lists the ARB locales in alphabetical order, so it is `[de, en, pl]`; when a French-only device matches none of them, Flutter's default resolution returns the first supported locale, German. Put `preferred-supported-locales: [en]` in `l10n.yaml` so English leads the list. Second, when a key exists in `app_en.arb` but not in `app_pl.arb`, the generator fills the Polish class with the template text and only prints "pl: N untranslated message(s)"; the build still succeeds. Add `untranslated-messages-file` so the tool writes a JSON report per locale and fail CI when it is non-empty. While there, check that `MaterialApp` receives `AppLocalizations.localizationsDelegates`, not only `AppLocalizations.delegate`, or Polish and German have no Material translations.
code
yaml · 5 linesarb-dir: lib/l10n
template-arb-file: app_en.arb
preferred-supported-locales: [en]
untranslated-messages-file: build/untranslated.json
required-resource-attributes: truego deeper
Remember that supportedLocales has an order and the first entry is the fallback, and that untranslated keys show template text.
Explain that the generated supportedLocales list is alphabetical, how preferred-supported-locales reorders it, and how the untranslated report is produced.
Diagnose wrong-language and mixed-language screens from configuration, add a CI gate on the untranslated report, and pin fallback and Material localization with widget tests.
Decide the product's default language per market and whether releases may ship with untranslated keys, then encode that policy in configuration and CI.
## Symptom 1: French users see German **Cause.** gen-l10n builds `AppLocalizations.supportedLocales` from the ARB files it finds, sorted **alphabetically** by file: `app_de.arb`, `app_en.arb`, `app_pl.arb` give `[Locale('de'), Locale('en'), Locale('pl')]`. `MaterialApp` receives that list, and when none of the user's preferred locales matches by language or country, Flutter's default resolution returns **`supportedLocales.first`**. A device set only to French therefore gets German. **Fix.** Tell the generator which locale leads: - `preferred-supported-locales: [en]` in `l10n.yaml` moves `en` to the front of the generated list, so the fallback is English. - The listed locale must have an ARB file; otherwise generation fails with an error saying the preferred locale cannot be added. - Custom resolution logic (a `localeResolutionCallback`) is another route, but for "which language is the default" the l10n option is the smallest change and keeps the default resolution intact. ## Symptom 2: Polish users see some English strings **Cause.** Developers add keys to the template `app_en.arb` faster than translations arrive. For every key missing from `app_pl.arb`, gen-l10n **uses the template's message** in the generated Polish class and records the key as untranslated. Nothing fails: the console shows a summary such as `"pl": 3 untranslated message(s).` and suggests the report option. **Fix.** Make the gap visible and blocking: 1. Add `untranslated-messages-file: build/untranslated.json` (any path) to `l10n.yaml`. The tool writes a JSON object mapping each locale to its missing message ids, or `{}` when complete. 2. In CI, run `flutter gen-l10n` and fail the job when that file is not `{}`. 3. Optionally set `required-resource-attributes: true`, so every message must carry an `@` metadata entry, the natural home for a translator description. Regional files follow the same rule with one refinement: a key missing from `app_de_AT.arb` but present in `app_de.arb` is inherited from the German class and not reported. ## Symptom 3 (often found at the same time): Material widgets fail in German and Polish If the app wires only `AppLocalizations.delegate`, your strings are Polish but nothing supplies `MaterialLocalizations` for `pl`. The framework's built-in `DefaultMaterialLocalizations` supports only English, so in a debug build Flutter reports *"This application's locale, pl, is not supported by all of its localization delegates"*, and widgets that need localized labels (the back button tooltip, date pickers, text-selection menus) fail with *"No MaterialLocalizations found."* **Fix.** Pass `AppLocalizations.localizationsDelegates`, which already includes `GlobalMaterialLocalizations.delegate`, `GlobalCupertinoLocalizations.delegate` and `GlobalWidgetsLocalizations.delegate`. Delegate order matters only when two delegates serve the same type: the first one that supports the locale wins. Two related checks belong in the same review: - **Regional files.** If marketing later adds `app_de_AT.arb`, the generated list gains `de_AT`, and Austrian devices resolve to it; make sure it only overrides what differs and inherits the rest from `app_de.arb`. - **iOS language list.** The App Store listing reads the languages declared in the Xcode project, which gen-l10n does not edit; add German and Polish there as well. ## Diagnosing in order | Check | How | What a healthy app shows | |---|---|---| | Order of `supportedLocales` | Read the generated `app_localizations.dart` | The intended default first | | Untranslated keys | `flutter gen-l10n` output or the JSON report | No untranslated messages | | Framework delegates | `MaterialApp.localizationsDelegates` | The generated list, or a superset | | Runtime locale | `Localizations.localeOf(context)` in a debug overlay | The locale you expect per device | ## Testing it - A widget test can pump `MaterialApp` with `locale: const Locale('fr')` and the generated lists, then assert English text, which pins the fallback. - Another can pump `Locale('pl')` and a `showDatePicker` call to prove Material labels load. - Keep a CI step that fails on a non-empty untranslated report, so the next new key cannot ship half-translated. ## Why it matters for a store release Users judge a localized app by its worst screen. Falling back to the wrong language, or mixing English into Polish checkout screens, reads as a broken app; both issues are configuration, and both are cheap to lock down with the two `l10n.yaml` options and one CI check.
- Why does a device set to French and then English get English even without preferred-supported-locales?Default resolution walks the user's preferred locales in order and returns the first supported match. French matches nothing, English matches `en`, so English wins. The alphabetical-first fallback only applies when no preferred locale matches at all.
- Does a key missing from app_de_AT.arb but present in app_de.arb get reported as untranslated?No. The Austrian subclass extends the German class, so the message is inherited from `app_de.arb`, and the generator reports a regional gap only when the base language is missing the key too.
- How would a widget test pin the fallback behaviour?Pump `MaterialApp` with `locale: const Locale('fr')`, `localizationsDelegates: AppLocalizations.localizationsDelegates` and `supportedLocales: AppLocalizations.supportedLocales`, then `expect(find.text('Your cart'), findsOneWidget)`. If the generated list order changes, the test fails.
saying these in an interview costs you the question
- An unsupported device locale always falls back to the template language.
- gen-l10n fails the build when a translation is missing.
- supportedLocales order does not matter because Flutter picks the best match.
- AppLocalizations.delegate alone also localizes Material date pickers.
- Missing Polish keys make AppLocalizations.of(context) return null.