← Back to Blog

Fix Flutter 3.38 gen-l10n Deleting AppLocalizations

fluttergen-l10nlocalizationflutter-3.38applocalizationsdart-3.10

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-l10n runs implicitly during pub 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-recreates app_localizations.dart, the analyzer can cache the deleted state and surface Target of URI doesn't exist even 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: false is now the only supported mode — set it explicitly so nothing tries the old package:flutter_gen path.
  • output-dir is required when synthetic-package is false. Putting the generated code in a dedicated gen/ subfolder (not directly in lib/l10n) keeps the ARB files and the generated Dart in separate, predictable places.
  • nullable-getter: false gives you AppLocalizations.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

  1. Add the explicit l10n.yaml with synthetic-package: false + output-dir.
  2. Wire scripts/gen_l10n.sh into CI and use it locally instead of relying on pub get.
  3. Commit the generated lib/l10n/gen/ folder to stop the disappearing-file churn.
  4. 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.