← Back to Blog

Flutter 3.38: Fix AppLocalizations Not Found After Upgrade

flutterlocalizationgen-l10narbmigration

Flutter 3.38: Fix AppLocalizations Not Found After Upgrade

You upgraded to Flutter 3.38.x, ran the app, and got one of these:

Target of URI doesn't exist: 'package:flutter_gen/gen_l10n/app_localizations.dart'
Undefined class 'AppLocalizations'
The name 'AppLocalizations' isn't a type, so it can't be used as a type argument

Nothing in your code changed. The ARB files are still there. And the maddening part: sometimes the build works, then the same errors come back after a flutter pub get or an IDE restart.

Here is what actually happened, and the exact migration.

What Flutter removed, and when

For years flutter gen-l10n wrote AppLocalizations into a synthetic package called package:flutter_gen. It never existed on disk. The Flutter tool injected an entry for it into .dart_tool/package_config.json during pub get, and your imports resolved against that fake entry.

The Flutter team deprecated that trick because it broke build_runner, confused the analyzer and dart fix, and could not be understood by any tool that wasn't the Flutter CLI. The official breaking change lays out the timeline:

  • Landed in 3.28.0-0.0.pre, shipped by default on the 3.32.0 stable line: generated messages go into your source tree, not package:flutter_gen.
  • The tool no longer generates a synthetic package and no longer modifies your package_config.json.
  • On current stable (3.47.x), the flag is a hard error: l10n.yaml: Cannot enable "synthetic-package", this feature has been removed. and flutter gen-l10n --help says DEPRECATED. This flag cannot be enabled and should be removed.

So strictly speaking the default flipped before 3.38 (released 12 November 2025 with Dart 3.10). 3.38 is simply the release where a huge number of teams upgrading from an older pinned SDK hit it at once, plus it shipped a regression of its own that made the symptom look random. More on that below.

Step 1: the l10n.yaml that works today

Delete synthetic-package entirely. Do not set it to false either. On modern stable it is inert, and on the line where it is rejected, false is tolerated but noisy. The clean config:

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

What each key does:

  • arb-dir — where your app_en.arb, app_fr.arb, app_ar.arb live.
  • output-dir — where the Dart goes. If you omit it, output lands in arb-dir. It must be inside lib/, otherwise nothing can import it. If you'd rather keep generated code away from hand-edited ARB, use arb-dir: lib/l10n with output-dir: lib/src/generated/l10n.
  • output-localization-file — the file name, so lib/l10n/app_localizations.dart plus one app_localizations_<locale>.dart per locale.
  • output-class — the class name, AppLocalizations by default. If your old code used a custom name, keep it here so you don't have to touch call sites.
  • nullable-getter: false — optional, but it turns AppLocalizations.of(context)! into AppLocalizations.of(context). Worth doing while you're already editing imports.

And generate: true is still required in pubspec.yaml:

flutter:
  generate: true

Step 2: the import swap

Old:

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

New, relative from lib/main.dart:

import 'l10n/app_localizations.dart';

Or the absolute form, which I'd prefer because it's identical in every file and in test/:

import 'package:my_app/l10n/app_localizations.dart';

One command for the whole repo (macOS sed needs the empty -i ''; on Linux use plain -i):

grep -rl 'package:flutter_gen/gen_l10n/app_localizations.dart' lib test \
  | xargs sed -i '' \
    's|package:flutter_gen/gen_l10n/app_localizations.dart|package:my_app/l10n/app_localizations.dart|g'

The usage itself does not change at all:

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      home: const HomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context); // no `!` with nullable-getter: false
    return Scaffold(
      appBar: AppBar(title: Text(l10n.helloWorld)),
      body: Center(child: Text(l10n.itemCount(3))),
    );
  }
}

If you had a package or plugin that imported package:flutter_gen/..., that import can never resolve again. It has to import the app's real path, or the package needs its own l10n.yaml generating into its own lib/.

Step 3: why it's intermittent, and the rebuild that actually fixes it

This is the part that wastes the most time. Two separate causes produce the same flaky symptom.

Stale .dart_tool state. Your existing .dart_tool/package_config.json may still contain the flutter_gen entry written by your previous SDK. The new tool no longer writes it, but it also doesn't hunt it down. So the old import resolves fine until something rewrites that file, and then it stops. Same story in reverse: the analysis server caches resolution, so VS Code can show red squiggles on a file the compiler builds happily, or the opposite.

The 3.38.x regression. On 3.38.1 with Dart 3.10.0, people reported generated app_localizations*.dart files being deleted underneath them, most reproducibly when running Flutter web in debug (#178529, with #178617 and #178950 filed as duplicates). It was triaged P1 as a regression and fixed after 3.38.1, so if you're pinned exactly there, take the latest hotfix on the line or move up a release.

The sequence that clears both:

flutter clean
rm -rf .dart_tool
flutter pub get
flutter gen-l10n
dart analyze

Then restart the analysis server, or the errors you just fixed will still be on screen: in VS Code, Dart: Restart Analysis Server from the command palette; in Android Studio, File → Invalidate Caches / Restart.

A useful sanity check afterwards:

grep -r flutter_gen .dart_tool/package_config.json pubspec.yaml l10n.yaml lib test || echo "clean"

If that prints clean and dart analyze is quiet, the migration is done.

Commit the generated files, or gitignore them?

Both work. I commit them, and for app repos I'd recommend it:

  • A fresh clone analyzes cleanly with no build step. CI, a reviewer, and a new teammate all see the same tree.
  • pubspec.yaml, analysis_options.yaml and IDE tooling stop racing each other over whether the file exists yet.
  • The diff is genuinely reviewable. A dropped ICU plural branch or an accidentally removed key shows up in the PR, not in production.
  • The 3.38.x deletion bug above was far less painful for teams who had the file in git.

The cost is diff noise on every ARB edit. If your team finds that intolerable, gitignore it and make generation explicit and mandatory:

# .gitignore — only if generation runs in CI *before* analyze
/lib/l10n/app_localizations*.dart

If you go that route, flutter gen-l10n must run in CI before dart analyze and before flutter test, otherwise every pipeline fails on a missing import. Add it explicitly rather than relying on pub get to do it for you. Exclude the generated files from lints either way:

# analysis_options.yaml
analyzer:
  exclude:
    - lib/l10n/app_localizations*.dart

One thing to decide as a team and write down: generated files in the tree mean someone will eventually edit app_localizations.dart by hand and lose it on the next build. The ARB files are the source of truth, always.

While you're in there

This migration touches every ARB file you own, which makes it a good moment to check they're actually correct. The classic silent bug is a locale that dropped an ICU plural category its language requires: Arabic needs zero/one/two/few/many/other, Polish and Russian need few and many, and English only needs one/other, so an English-speaking author will never notice the gap. gen-l10n will happily generate Dart from it, and the wrong string ships.

FlutterLocalisation is an ARB editor and translation-management platform built for exactly this: edit app_<locale>.arb in a UI instead of raw JSON, manage many locales side by side, and get ICU plural-syntax validation that flags locales missing a plural category the language genuinely needs. If you want the older version of this error, we wrote it up separately in Fix the flutter_gen gen_l10n import error, and the /blog cluster covers the rest of Flutter i18n. Plans are on /pricing, and the features are listed at /features.

Try FlutterLocalisation free and get your ARB files checked while you migrate.