Migrate Flutter l10n to synthetic-package: false
If you upgraded to Flutter 3.32 or 3.35 and your localizations suddenly stopped generating — or you're seeing warnings about package:flutter_gen — you've hit the synthetic-package removal. The fix is small and mechanical, but the failure mode is nasty: flutter gen-l10n can silently produce no files, leaving your import 'package:flutter_gen/...' lines dangling with no obvious error.
This post is the concrete before/after: the exact l10n.yaml diff, the pubspec.yaml flag you must not forget, the import rewrite, and a copy-paste codemod so a large app migrates in minutes instead of one file at a time.
What actually changed
Historically, Flutter's gen-l10n tool generated your AppLocalizations class into a synthetic package called package:flutter_gen. It never existed on disk in your lib/ — the tool injected it into .dart_tool/package_config.json at build time, and you imported it like this:
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
That synthetic package caused a long tail of problems: broken package_config.json states, tooling that couldn't resolve the import, IDEs that lost the symbols, and monorepo/workspace setups that fell over. So the Flutter team is retiring it.
The timeline, from the official breaking-change notice:
- Landed: 3.28.0-0.0.pre
- Stable: 3.32.0 — generating into source is the documented path
- Removal:
package:flutter_gensupport goes away in a subsequent stable release
The new model is simple and honest: your localizations are generated into real files in your source tree (flutter localization generated into source), and you import them from your own package path like any other Dart file. No magic, no synthetic package, no package_config.json surgery.
The failure modes you'll actually see
Depending on your setup, upgrading surfaces one of these:
Silent no-file generation.
flutter gen-l10nexits 0, writes nothing, and your existingpackage:flutter_genimports become unresolved. This is the confusing one — nothing errors loudly.The generate-flag error:
Attempted to generate localizations code without having the
flutter: generateflag turned on.This means
generate: trueis missing frompubspec.yaml(it's now required when generating l10n into source).
Both are fixed by the same short migration.
Step 1 — Update pubspec.yaml
The generate flag is required. If it's missing, generation is a no-op or an error. Make sure this stanza exists:
flutter:
generate: true
uses-material-design: true
Step 2 — The l10n.yaml diff
This is the heart of the migration. Here's the before/after.
Before (relying on the synthetic package — often just this):
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
After (synthetic-package false flutter — generate into your real source tree):
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
# The three lines that do the migration:
synthetic-package: false
output-dir: lib/l10n
nullable-getter: false
What each added line does:
synthetic-package: false— the switch. Stops generatingpackage:flutter_genand writes real.dartfiles instead. This is theflutter gen-l10n migrate source directorystep.output-dir: lib/l10n— where the generated files land. If you omit it, files go intoarb-dir. Putting the generated code next to your.arbfiles keeps things tidy; some teams preferlib/src/generated/l10nto signal "don't hand-edit these." Whatever you pick, it must live insidelib/so it's importable.nullable-getter: false(optional but recommended) — by defaultAppLocalizations.of(context)returns a nullable value (AppLocalizations?) for backwards compatibility, forcing!at every call site. Setting it tofalsemakes the getter non-nullable, soAppLocalizations.of(context).helloWorldjust works. If you keep the defaulttrue, leave your existing!usages alone.
Now regenerate:
flutter gen-l10n
You should see real files appear at lib/l10n/app_localizations.dart (plus app_localizations_en.dart, etc.). Commit them — they're source now, not build artifacts. (You can still let flutter run/build regenerate them; checking them in just keeps CI and fresh clones happy.)
Step 3 — Rewrite the imports (the codemod)
Every file that imported the synthetic package needs its import repointed to your new source path. The rule:
- Old:
package:flutter_gen/gen_l10n/app_localizations.dart - New:
package:<your_package>/<output-dir minus lib/>/app_localizations.dart
If your package is myapp (the name: in pubspec.yaml) and output-dir: lib/l10n, the new import is:
import 'package:myapp/l10n/app_localizations.dart';
Note that the lib/ prefix is dropped in a package: URI — lib/l10n becomes l10n.
Doing this by hand across a large app is where people burn an afternoon. Don't. Run a codemod-style find/replace across lib/ and test/:
# macOS / BSD sed (note the '' after -i)
grep -rl "package:flutter_gen/gen_l10n/" lib test \
| xargs sed -i '' 's|package:flutter_gen/gen_l10n/|package:myapp/l10n/|g'
# Linux / GNU sed
grep -rl "package:flutter_gen/gen_l10n/" lib test \
| xargs sed -i 's|package:flutter_gen/gen_l10n/|package:myapp/l10n/|g'
Swap myapp for your real package name and l10n for your output-dir (minus lib/). That's the whole rewrite — one command turns hundreds of imports.
Then verify nothing references the old package anymore:
grep -rn "package:flutter_gen" lib test ; echo "exit: $?"
An exit code of 1 (no matches) means you're clean.
Step 4 — Confirm and clean up
Run the analyzer and a build to catch stragglers:
flutter gen-l10n
flutter analyze
flutter run
A few gotchas to check:
- Workspaces / melos monorepos: on 3.32+ you may need
explicit-package-dependenciesenabled alongsidesynthetic-package: false, orgenerate: truethrows an unsupported-in-workspace error. If you're not in a Pub workspace, ignore this. .gitignore: if you previously ignored generated l10n, un-ignore your newoutput-dirso the checked-in files persist.- The old
flutter_gendependency: the syntheticpackage:flutter_genis unrelated to theflutter_gen/flutter_gen_runnerpub packages for assets. Don't remove those unless you were actually using them for assets.
Why the source-based approach is better
Beyond just un-breaking the build, generating into source means your AppLocalizations class is a normal file: your IDE resolves it instantly, "go to definition" works, code coverage sees it, and there's no hidden package_config.json state to corrupt. It's strictly less magic.
For a full reference on every l10n.yaml key — output-class, preferred-supported-locales, use-deferred-loading, and more — see our complete l10n.yaml configuration guide.
Keep your ARB files sane while you're in here
Migrating is a good moment to check your actual translation content. A common silent bug: a locale that's missing a plural category its language requires (Arabic needs few/many, Polish and Russian have their own rules), which crashes or mis-renders at runtime. FlutterLocalisation's ARB editor lets you edit app_<locale>.arb files in a UI instead of raw JSON and runs ICU plural-syntax validation that flags exactly those dropped categories across every locale — so a regenerate-into-source migration doesn't quietly ship a broken plural.
Manage all your locales in one place, catch plural gaps before your users do, and skip the raw-JSON grind. Try FlutterLocalisation free.