Fix Flutter 3.38 gen-l10n Deleting AppLocalizations
You upgraded to Flutter 3.38.1 / Dart 3.10.0, ran flutter pub get, and now your app won't build. The analyzer lights up with Target of URI doesn't exist: 'app_localizations.dart' and Undefined class 'AppLocalizations'. You run flutter clean, it compiles once, then on the next hot restart or pub get the generated file vanishes again. Nothing in your pubspec.yaml, l10n.yaml, ARB files, or imports changed.
This is not the generic "AppLocalizations is null" delegate problem. It's a real, non-deterministic regression introduced in Flutter 3.38.1, tracked upstream as flutter/flutter#178617 (gen-l10n intermittently breaks after upgrading) and #178529 (AppLocalizations getting deleted every time on web in debug mode). Here's why it happens and how to make your build deterministic today.
Why the generated file keeps disappearing
Starting with the first 2025 stable release, Flutter stopped generating a synthetic package:flutter_gen and now writes app_localizations.dart (plus the per-locale files) directly into your source tree. By 3.38 the synthetic package is gone entirely — the file is a real file on disk that the tool creates, overwrites, and sometimes deletes.
That design change is good long-term, but it exposes a timing race in 3.38.1:
- Source-generation timing.
gen-l10nruns implicitly duringpub get,run, and hot restart. When it regenerates, it deletes the old output first and rewrites it. If anything reads the directory mid-write, the file is momentarily missing. - IDE analysis race. The Dart Analysis Server watches your
lib/folder. When the tool deletes-then-recreatesapp_localizations.dart, the analyzer can cache the deleted state and surfaceTarget of URI doesn't existeven though the file is back a split second later. On Flutter web debug builds this is worse because the file gets rewritten on every restart (#178529).
The result is genuinely non-deterministic: the exact same repo builds for a colleague and fails for you, or fails only every third pub get.
Step 1: Pin the output with an explicit l10n.yaml
Don't rely on defaults. Give the tool an unambiguous, stable output location so it stops writing into a directory the analyzer is actively scanning for changing sources. Drop this in your project root as l10n.yaml:
# l10n.yaml — deterministic gen-l10n for Flutter 3.38.1
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
output-dir: lib/l10n/gen
synthetic-package: false
nullable-getter: false
Key points:
synthetic-package: falseis now the only supported mode — set it explicitly so nothing tries the oldpackage:flutter_genpath.output-diris required whensynthetic-packageis false. Putting the generated code in a dedicatedgen/subfolder (not directly inlib/l10n) keeps the ARB files and the generated Dart in separate, predictable places.nullable-getter: falsegives youAppLocalizations.of(context)returning a non-null instance, which removes a whole second class of null-safety errors.
Make sure your pubspec.yaml still opts in:
flutter:
generate: true
Your import then becomes the concrete file path, not a synthetic package:
import 'package:my_app/l10n/gen/app_localizations.dart';
// In MaterialApp:
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const HomePage(),
);
Step 2: A deterministic build script for local + CI
The implicit generation during pub get is the trigger. Neutralize it by always running a clean, ordered sequence and treating gen-l10n as an explicit step rather than a side effect. Save this as scripts/gen_l10n.sh:
#!/usr/bin/env bash
set -euo pipefail
# Deterministic localization generation for Flutter 3.38.1
flutter clean
flutter pub get
flutter gen-l10n
echo "✅ AppLocalizations generated at lib/l10n/gen/app_localizations.dart"
Make it executable (chmod +x scripts/gen_l10n.sh) and wire it into CI before flutter analyze and flutter build:
# GitHub Actions snippet
- uses: subosito/flutter-action@v2
with:
flutter-version: 3.38.1
- run: ./scripts/gen_l10n.sh
- run: flutter analyze
- run: flutter build apk --release
Because flutter clean wipes stale artifacts and gen-l10n runs as the last, explicit step, the file is guaranteed present and fully written before the analyzer or compiler touches it. This alone stops the intermittent CI failures.
Step 3: Fix the .gitignore churn
Many projects add generated l10n files to .gitignore. Combined with the delete-and-rewrite bug, that's the worst case: the file isn't tracked, the tool deletes it, and your IDE sees nothing on disk — instant red squiggles.
For 3.38.1, the most robust mitigation is to commit the generated files so a valid app_localizations.dart always exists on disk regardless of tool timing. Remove any ignore rules like:
# ❌ Remove these while the 3.38.1 regression is unfixed
lib/l10n/gen/
**/app_localizations*.dart
Commit the lib/l10n/gen/ folder. Now even when gen-l10n deletes and rewrites, git (and the analyzer's on-disk view) always has a version to fall back on, and identical regenerations produce no diff — the churn stops. When you do change ARB strings, run the build script and commit the regenerated output as a normal, reviewable change.
If your team policy forbids committing generated code, the alternative is to keep it ignored but never let the implicit generation run — always go through the script above and add the folder as an analyzer exclude so it isn't watched:
# analysis_options.yaml
analyzer:
exclude:
- lib/l10n/gen/**
Step 4: Fallback — pin or downgrade until the fix lands
Both issues target the Flutter tool, and until a patched stable ships, pinning is a legitimate escape hatch. Use FVM so you pin per-project without touching your global SDK:
dart pub global activate fvm
fvm install 3.35.5 # last stable series before the 3.38.1 regression
fvm use 3.35.5
# then run everything through fvm:
fvm flutter gen-l10n
Commit the resulting .fvmrc (or .fvm/fvm_config.json) so CI and every teammate use the identical, known-good SDK. When a fixed 3.38.x point release arrives, bump the pin and delete the workaround. Watch #178617 for the fix version.
Recommended order of operations
- Add the explicit
l10n.yamlwithsynthetic-package: false+output-dir. - Wire
scripts/gen_l10n.shinto CI and use it locally instead of relying onpub get. - Commit the generated
lib/l10n/gen/folder to stop the disappearing-file churn. - If it's still flaky on a specific machine, pin to 3.35.x via FVM until the patch lands.
Managing the ARB files themselves — keeping placeholders, plurals, and locale coverage consistent so regeneration never fails on a malformed key — is where a dedicated editor pays off. FlutterLocalisation's ARB editor validates placeholders and catches missing translations before they reach gen-l10n, and our l10n.yaml configuration guide covers every option in depth. See pricing for team plans.
Try FlutterLocalisation free — edit your ARB files, catch localization errors early, and ship deterministic builds on Flutter 3.38.