← Back to Blog

New app_fr.arb Not Working? Fix Flutter gen-l10n Locales

flutterarbgen-l10nlocalizationi18n

New app_fr.arb Not Working? Fix Flutter gen-l10n Locales

You added lib/l10n/app_fr.arb, hot restarted, and nothing happened. AppLocalizations still has no French, or the app cheerfully renders English on a French device. The symptom is identical in three completely different failures, which is exactly why this wastes an afternoon.

Here is the decision tree, with the actual Flutter tool behaviour behind each branch.

The 30-second triage

  1. Does the generated app_localizations_fr.dart file exist? If no → it's failure mode 1 (stale build) or 2 (@@locale). Run flutter gen-l10n in a terminal and read the output — it will either produce the file or print the real error, which the flutter run output often swallows.
  2. The file exists, but the app still shows English at runtime? → failure mode 3: your supportedLocales list.
  3. The app throws AppLocalizations.delegate failed to load unsupported locale? → also mode 3, but inverted: you listed a locale that was never generated.

Failure 1: hot restart never regenerates a new ARB file

This is the one nearly everyone hits, and it isn't your fault. flutter run sets up a build step that watches the ARB files it found at startup. Editing a string inside an existing app_en.arb does re-run gen-l10n on hot reload/restart. Creating a new file does not — the build system doesn't notice inputs that didn't exist when the run began.

That's tracked as flutter/flutter#110714, still open and labelled P3 as of this writing. (An older report, #58183, covered the same "newly written arb files not detected" symptom but was closed as not planned — its actual root cause turned out to be the @@locale issue in the next section. So don't read #58183 as a fix landing; the new-file watching gap is #110714.)

The rule to internalise: new ARB file → full stop and restart, or an explicit generate.

# Option A: regenerate without touching the running app
flutter gen-l10n

# Option B: stop the run entirely (q in the terminal), then
flutter run

Hot restart (R) is not enough. Neither is flutter pub get in some setups. If you use VS Code or Android Studio, "Restart" on the toolbar is a hot restart — kill the debug session instead.

If flutter gen-l10n succeeds but your IDE still can't resolve the new getter, the analysis server is holding a stale view: restart the Dart Analysis Server (VS Code: Dart: Restart Analysis Server).

Failure 2: the @@locale mismatch that silently kills a locale

gen-l10n determines a file's locale from two sources: the @@locale key inside the JSON, and the suffix on the filename. The rules are strict:

  • Either source alone is fine.
  • If both are present and they disagree, generation fails with:

The locale specified in @@locale and the arb filename do not match. Please make sure that they match, since this prevents any confusion with which locale to use. Otherwise, specify the locale in either the filename or the @@locale key only.

  • If neither yields a parseable locale, you get:

The following .arb file's locale could not be determined: … Make sure that the locale is specified in the file's '@@locale' property or as part of the filename (e.g. file_en.arb)

So french.arb with no @@locale is dead on arrival. And app_fr.arb containing "@@locale": "fr_FR" is a hard error — you cannot declare a narrower locale than the filename says.

The pt_BR case specifically

Regional variants trip people because there are three ways to write the same thing and only one that gen-l10n accepts. The filename suffix and @@locale must be byte-identical, using an underscore and the conventional uppercase region:

lib/l10n/app_pt_BR.arb

{
  "@@locale": "pt_BR",
  "helloWorld": "Olá, mundo!",
  "@helloWorld": {
    "description": "Greeting on the home screen"
  },
  "itemCount": "{count, plural, =0{Nenhum item} one{{count} item} other{{count} itens}}",
  "@itemCount": {
    "description": "Number of items in the cart",
    "placeholders": {
      "count": { "type": "int" }
    }
  }
}

Not pt-BR, not pt_br, not @@locale: "pt" in a file named app_pt_BR.arb. (Older Flutter versions had a parsing bug where the BR suffix was read as Breton, producing the baffling "Current filename extension: br" message — #138609. That's fixed on current stable, but the matching rule still applies.)

Also note: if you ship both app_pt.arb and app_pt_BR.arb, gen-l10n generates two classes and a Brazilian device gets the pt_BR one, with pt as the fallback for other Portuguese regions. That's the intended pattern — keep region files thin and let the base language carry the bulk.

The l10n.yaml that goes with it

Since Flutter 3.32, generated localizations land in your source tree rather than the old synthetic package:flutter_gen. A minimal, current config:

# l10n.yaml (project root)
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
nullable-getter: false
# pubspec.yaml
flutter:
  generate: true

And import the real file, not the synthetic package:

// from lib/main.dart, with arb-dir: lib/l10n
import 'l10n/app_localizations.dart';

If you're still on import 'package:flutter_gen/gen_l10n/app_localizations.dart';, that's a separate migration — see our walkthrough of the flutter_gen import error and the full l10n.yaml reference.

Failure 3: generated fine, but never reaches supportedLocales

The class exists. AppLocalizationsPtBr is right there in lib/l10n/app_localizations_pt_br.dart. And the app still shows English.

Flutter resolves the device locale against MaterialApp.supportedLocales. If Brazilian Portuguese isn't in that list, the framework picks the first supported locale instead — silently, no error. This is what happens when someone hardcoded the list six months ago:

// ❌ Every new .arb file requires editing this by hand — and everyone forgets.
supportedLocales: const [
  Locale('en'),
  Locale('fr'),
],

Don't maintain that list. gen-l10n already generates one that is, by construction, exactly the set of locales you have ARB files for:

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'l10n/app_localizations.dart';

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      // ✅ Generated from your .arb files — adding app_pt_BR.arb is enough.
      supportedLocales: AppLocalizations.supportedLocales,
      home: const HomePage(),
    );
  }
}

The inverse mistake produces a loud failure instead of a quiet one: list Locale('pt', 'BR') by hand without an app_pt_BR.arb, and the delegate throws AppLocalizations.delegate failed to load unsupported locale the moment a Brazilian device opens the app. Same root cause — a hand-maintained list drifting from the ARB directory.

Testing it without changing your device language

Force the locale to prove generation and resolution both work:

MaterialApp(
  locale: const Locale('pt', 'BR'), // remove once verified
  localizationsDelegates: const [
    AppLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: AppLocalizations.supportedLocales,
  home: const HomePage(),
)

If the forced locale renders Portuguese, generation is correct and your problem was device-locale resolution. If it still renders English, go back to failure 1 or 2.

The checklist, in order

  1. flutter gen-l10n in a terminal — read every line of output.
  2. Filename suffix and @@locale match exactly (app_pt_BR.arb"pt_BR").
  3. Kill the flutter run session; never trust hot restart for a new ARB file.
  4. supportedLocales: AppLocalizations.supportedLocales — never a hand-written list.
  5. Restart the Dart Analysis Server if the getter exists on disk but the IDE disagrees.

Stop hand-editing raw ARB JSON

Most of these failures are file-hygiene problems: a typo in a locale header, a file that never got its @@locale, a plural category dropped when a translator copied app_en.arb into a new language. FlutterLocalisation is an ARB editor and translation-management platform built for exactly this: edit app_<locale>.arb files in a UI instead of raw JSON, manage many locales side by side, and get ICU plural-syntax validation that flags a locale missing a plural category its language actually needs — the dropped few/many in Arabic, Polish or Russian that compiles fine and reads wrong.

Try FlutterLocalisation free and add your next language without the @@locale archaeology.