flutter gen-l10n Runs But Generates Nothing: Fix It
Here is the whole problem in four lines. This is the complete output of a successful flutter gen-l10n run on Flutter 3.47.4 stable:
$ flutter gen-l10n
Because l10n.yaml exists, the options defined there will be used instead.
To use the command line arguments, delete the l10n.yaml file in the Flutter project.
$ echo $?
0
That's it. No "wrote 3 files", no file list, no summary. A run that generated a full AppLocalizations tree and a run that quietly did nothing useful print the same thing. So when your build starts failing with Target of URI doesn't exist or Undefined class 'AppLocalizations', the command gives you nothing to go on.
Below is the decision tree. Four causes, each verified against Flutter 3.47.4 / Dart 3.13.3, with the exact tool output so you can tell which one you hit.
Step 0: the 30-second probe
Before guessing, make the tool prove it read your ARB files. Add one line to l10n.yaml:
arb-dir: lib/l10n
output-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
untranslated-messages-file: l10n_missing.txt
Delete l10n_missing.txt, run flutter gen-l10n, and look:
$ cat l10n_missing.txt
{
"fr": [
"cartItems"
]
}
If that file reappears, the generator ran and parsed your ARBs. The problem is where the output went, or what was in the ARB. If the file does not reappear, the generator bailed before reading anything, and you are in cause 2 or 3.
Also run the real search instead of trusting your IDE tree:
find . -name 'app_localizations*.dart' -not -path './build/*'
Cause 1: you are looking in .dart_tool/flutter_gen (it is gone)
This is the number one cause now, and it is the one behind most "gen-l10n fails silently on Windows" reports. Flutter 3.32 stopped generating the synthetic package:flutter_gen. Output goes into your real source tree instead. A developer who learned the old flow checks .dart_tool/flutter_gen/gen_l10n/, sees an empty or missing directory, and concludes nothing was generated. Meanwhile the files are sitting in lib/l10n/.
It is not platform-specific, but it reads as a Windows bug because the Windows tutorial crowd is the one still following pre-3.32 instructions.
If your l10n.yaml still carries the old flag, you get a hard error:
$ flutter gen-l10n
l10n.yaml: Cannot enable "synthetic-package", this feature has been removed.
See http://flutter.dev/to/flutter-gen-deprecation.
Note that synthetic-package: false is accepted and silently ignored, so half-migrated configs look fine and tell you nothing. Delete the key entirely.
The correct modern config:
# l10n.yaml
arb-dir: lib/l10n
output-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
And the import changes from the synthetic package to a real relative path:
// before (3.31 and earlier)
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
// after
import 'l10n/app_localizations.dart';
If you omit output-dir, it defaults to arb-dir, so lib/l10n either way. We cover the full key list in our l10n.yaml configuration guide, and the import side of this migration in fixing the flutter_gen import error.
Cause 2: generate: true is missing from pubspec.yaml
Without the flag, the generator refuses to write, and the message does not mention files at all:
$ flutter gen-l10n
Because l10n.yaml exists, the options defined there will be used instead.
To use the command line arguments, delete the l10n.yaml file in the Flutter project.
Attempted to generate localizations code without having the flutter: generate
flag turned on.
Check pubspec.yaml and ensure that flutter: generate: true has been added and
rebuild the project. Otherwise, the localizations source code will not be
importable.
Exit code is 1 on current stable, but it is easy to miss: the text reads like advice, not a failure, and a CI step that pipes output through tail or a quiet logger swallows it. Nothing gets written, so your previously generated file (if any) stays on disk, and your build keeps compiling yesterday's AppLocalizations.
The flag goes under flutter:, not at the root, and not under dependencies::
flutter:
uses-material-design: true
generate: true
Indentation is the classic slip here. generate: true one level out is a different key and is ignored. This flag is also what makes flutter run and flutter build regenerate automatically, so a CI job that only calls flutter build and never flutter gen-l10n depends entirely on it.
Cause 3: arb-dir casing (silent locally, loud in CI, or the reverse)
macOS and Windows default to case-insensitive filesystems. Linux CI runners do not. So this l10n.yaml:
arb-dir: lib/L10N
pointing at a folder actually named lib/l10n generates perfectly on your laptop, exit 0, files written. On a Linux runner the same commit gives:
The 'arb-dir' directory, 'LocalDirectory: '/builds/app/lib/L10N'', does not exist.
Make sure that the correct path was provided.
The mirror image also bites: a path that works in CI but whose casing drifted in a Windows checkout. And a related one, where the directory resolves but the template does not:
The 'template-arb-file', LocalFile: '/builds/app/lib/l10n/app_en.arb', does not exist.
That second message is what you get when arb-dir points somewhere real but empty, which happens constantly in CI when lib/l10n/*.arb is inside a .gitignore rule meant for generated Dart files. Check with git ls-files lib/l10n, not ls.
Fix the casing in both the YAML and on disk, and use git mv so the rename actually lands in the index on a case-insensitive filesystem:
git mv lib/L10n lib/l10n_tmp && git mv lib/l10n_tmp lib/l10n
Cause 4: the ARB parses, but as empty
This is the truly silent one. A template ARB with no actual messages generates a complete, valid, useless AppLocalizations:
{
"@@locale": "en",
"@helloWorld": { "description": "The greeting" }
}
Every key starting with @ is metadata. Nothing here is a message. Result: exit 0, app_localizations.dart written, zero getters in it. Your build then fails with The getter 'helloWorld' isn't defined for the class 'AppLocalizations', which sends you hunting for a generation bug that does not exist.
Confirm it in one line:
grep -c 'String get' lib/l10n/app_localizations.dart
If that prints 0, your template ARB is the problem, not the tool. A merge that resolved a conflict by keeping only the metadata block does this, and so does a sync script that writes descriptions before values.
Worth knowing what is not silent, so you can rule it out: genuinely malformed JSON is loud. A trailing comma gives
The arb file /app/lib/l10n/app_en.arb has the following formatting issue:
FormatException: Unexpected character (at line 4, character 1)
}
^
and a UTF-16LE file, which is what PowerShell 5.1's > redirect produces, fails at character 2:
FormatException: Unexpected character (at character 2)
{
^
That last one is a real Windows-only trap: generate an ARB with echo ... > app_en.arb in Windows PowerShell and you get UTF-16. Use Set-Content -Encoding utf8. A UTF-8 BOM, by contrast, is tolerated fine.
What --verbose actually gives you
Lower your expectations. flutter gen-l10n --verbose does not list written files. The useful part is the tail:
[ +5 ms] Because l10n.yaml exists, the options defined there will be used instead.
To use the command line arguments, delete the l10n.yaml file in the Flutter project.
[ +96 ms] "flutter gen-l10n" took 124ms.
[ ] Running 1 shutdown hook
[ ] exiting with code 0
took 124ms with exit 0 means it did the work. A run that bails on config validation comes back in a few milliseconds with a non-zero code. So in CI, log the timing and assert on the artifact rather than on the exit code:
flutter pub get
flutter gen-l10n
test -f lib/l10n/app_localizations.dart || { echo "gen-l10n produced nothing"; exit 1; }
grep -q 'String get' lib/l10n/app_localizations.dart || { echo "AppLocalizations has no messages"; exit 1; }
Those two assertions catch all four causes above. Add them once and this class of bug stops reaching your build logs.
One more trap: nullable getters
Once generation works, remember AppLocalizations.of(context) returns a nullable type by default:
static AppLocalizations? of(BuildContext context) { ... }
So AppLocalizations.of(context)!.helloWorld, or set nullable-getter: false in l10n.yaml and drop the bang. That is a compile error, not a silent one, but it is the next thing people hit after fixing the generation.
Stop editing ARB files by hand
Every cause above except the casing one traces back to an ARB file being hand-edited or script-written into a state the generator accepts but cannot use. A metadata-only template, a dropped plural category, a trailing comma from a bad merge.
FlutterLocalisation is an ARB editor and translation manager for exactly this. You edit app_<locale>.arb in a UI, so the JSON stays valid and the message keys stay present across every locale. It also runs ICU plural-syntax validation, flagging a locale that is missing a plural category its language actually needs, like a dropped few or many for Arabic, Polish or Russian, which gen-l10n will happily generate around without complaint.
Try FlutterLocalisation free and let the generator fail loudly in CI instead of quietly on your machine.