← Back to Blog

Fix: Flutter 3.38 Deletes AppLocalizations Every Run

flutterl10ngen-l10nflutter-3-38troubleshooting

Fix: Flutter 3.38 Deletes AppLocalizations Every Run

You upgrade to Flutter 3.38.x, run flutter pub get, and lib/l10n/app_localizations.dart shows up exactly where it should. You press run. The file is gone, and every widget that imported it turns red:

Target of URI doesn't exist: 'l10n/app_localizations.dart'
Undefined class 'AppLocalizations'
The name 'AppLocalizations' isn't a type

Run again and the file comes back. Run again and it vanishes. flutter clean, deleting .dart_tool/, and re-running flutter gen-l10n all buy you a few minutes before it happens again.

Here is the short version: this is a confirmed Flutter tool regression (flutter#178529, labelled P1), it has nothing to do with generate: true, and it is fixed in Flutter 3.38.4.

flutter upgrade
flutter --version     # must report 3.38.4 (2025-12-03) or newer
flutter pub get

If you're pinned to 3.38.0–3.38.3 by CI or a shared FVM config, keep reading — the mechanism matters, because most of the workarounds circulating for this bug actively break your project.

What's actually deleting the file

Flutter's build system has a garbage-collection step called trackSharedBuildDirectory. When a builder runs, it writes two things:

  • build/.last_build_id — a hash identifying that builder's configuration
  • build/<that-hash>/outputs.json — the list of files that builder produced

On the next build, the tool reads .last_build_id, opens the previous outputs.json, and deletes every file in that list that isn't an output of the current builder. The idea is to clean up artifacts left behind when you switch build configurations.

In Flutter 3.38, two different builders started sharing that one output directory:

  1. flutter pub get runs the gen_localizations target. It writes its build ID to build/.last_build_id, and its outputs.json lists app_localizations.dart, app_localizations_en.dart, and friends.
  2. flutter run then runs the DartBuild builder for native asset hooks — added by the data-assets change (#174685) that first shipped in stable 3.38.0.

DartBuild finds the localization builder's .last_build_id and outputs.json, sees that your generated Dart files aren't in its output set, and deletes them. Flutter engineer Jason Simmons bisected and documented this exactly on the issue.

That single detail explains every strange thing about the bug:

  • Why running twice "fixes" it. The second flutter run sees a matching build ID, so nothing gets deleted — and the gen_localizations target regenerates the files.
  • Why it looks web-and-debug-specific. Web debug hits the hook path most reliably, but reporters saw it on iOS and Android simulators too. It is not web-only.
  • Why flutter build is unaffected. Release builds don't interleave the two builders the same way.
  • Why deleting l10n.yaml "works." GenerateLocalizationsTarget.canSkip() returns true when l10n.yaml doesn't exist, so the target never runs and never writes an outputs list to be reaped.

The fix, commit d438df3, gives the native hook build its own native_hooks output subdirectory so it can't clobber the localization builder's bookkeeping. It landed on main on 2025-11-21 and was cherry-picked into stable 3.38.4.

Confirm it in 60 seconds

Run this against a project that reproduces the problem:

# 1. Generate, and confirm the files land on disk
flutter pub get
ls lib/l10n/app_localizations*.dart

# 2. Look at what the localization builder claimed as its outputs
cat build/.last_build_id
grep -o 'app_localizations[^"]*' "build/$(cat build/.last_build_id)/outputs.json"

# 3. Run the app, then look again
flutter run -d chrome
ls lib/l10n/app_localizations*.dart   # No such file or directory
cat build/.last_build_id              # a DIFFERENT hash than in step 2

If step 2 lists your app_localizations files and step 3 shows a changed .last_build_id with the files gone, you have this exact bug. No further investigation needed — upgrade to 3.38.4+.

Three "fixes" that make it worse

Because the symptom looks like a config problem, a lot of bad advice has accumulated. All three of these are wrong on current Flutter:

1. "Remove generate: true from pubspec.yaml." This breaks localization entirely. generate: true is required, and flutter gen-l10n hard-exits without it:

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.

2. "Add synthetic-package: false to l10n.yaml." That flag is dead. The synthetic package:flutter_gen was removed after 3.32, and on 3.38 the tool tells you so directly — synthetic-package: false prints The argument "synthetic-package" no longer has any effect and should be removed, and synthetic-package: true is a hard tool exit. Generating into real source under arb-dir/output-dir is now the only behaviour. Delete the key.

3. "Just run flutter clean before every run." It doesn't address the deletion, it just resets the build IDs so the next run happens to survive. You'll be doing it forever.

If you're stuck below 3.38.4

Pick one, in order of preference:

  • Commit the generated Dart. Remove lib/l10n/*.dart from .gitignore and check the files in. Deletion still happens, but git checkout lib/l10n restores it in a second and CI never sees a missing file. This is the highest-value mitigation for a team.
  • Regenerate after run starts. Add flutter gen-l10n as a post-launch step in your run script; the second build won't delete it.
  • Temporarily rename l10n.yaml and pass the same options as CLI flags to flutter gen-l10n. This disables the in-run target entirely. It's ugly, and you lose your config file, so treat it as a last resort.

The analyzer-only variant: a 60-second checklist

A second cluster of reports (flutter#178617, closed as a duplicate) describes the errors flickering in VS Code while the files exist on disk. Work through this before blaming the analyzer:

  1. ls lib/l10n/app_localizations.dart. If it's missing, this is the deletion bug above, not an analyzer issue.
  2. Check the import is a correct relative path from the importing file. From lib/main.dart it's import 'l10n/app_localizations.dart';; from lib/src/ui/home.dart it's import '../../l10n/app_localizations.dart';.
  3. Grep for synthetic-package leftovers: grep -rn "package:flutter_gen" lib/ and grep -n "flutter_gen" pubspec.yaml. An old dart fix run famously injected flutter_gen: any as a dependency (flutter#148949); that dependency and any package:flutter_gen/gen_l10n/... imports must go.
  4. Restart the analysis server — in VS Code, Cmd+Shift+PDart: Restart Analysis Server. The server caches a file that was deleted out from under it.
  5. flutter pub get, then re-check step 1.

The config you actually want

On 3.38.4+, this is the complete, current setup. No synthetic-package, no package:flutter_gen:

# pubspec.yaml
flutter:
  generate: true

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

output-dir is optional — omit it and files are generated into arb-dir. Then wire it up:

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

void main() => runApp(const MyApp());

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) {
    // With nullable-getter: false this never returns null.
    final l10n = AppLocalizations.of(context);
    return Scaffold(body: Center(child: Text(l10n.helloWorld)));
  }
}

If you're still on nullable-getter: true (the default), that call is AppLocalizations.of(context)!. Setting it to false and dropping the bang operator is worth doing while you're in here. For the full option reference, see our l10n.yaml configuration guide, and our roundup of common AppLocalizations errors for the failures that aren't this regression.

Keep the ARB layer boring

The silver lining of a bug like this is that it forces the question: how much of your localization pain is toolchain, and how much is the ARB files themselves? Once the generator stops eating your output, the remaining failures are almost always content problems — a placeholder that exists in app_en.arb but not app_pl.arb, or an ICU plural block missing the few category that Polish and Russian actually require, which fails at generation time with a message that points at a line number instead of a language rule.

FlutterLocalisation is an ARB editor and translation-management platform built for exactly that layer: edit app_<locale>.arb files in a UI instead of hand-patching JSON, manage many locales side by side, and catch missing ICU plural categories before flutter gen-l10n does. See pricing, or try FlutterLocalisation free and get your ARB files clean while the SDK sorts itself out.