Fix "Cannot Generate Localizations in a Workspace"
You add resolution: workspace to your app's pubspec, run flutter pub get, and the monorepo that worked yesterday now refuses to build:
`generate: true` is not supported within workspaces.
So you delete the flag. Then flutter gen-l10n bails out instead:
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.
That is the whole bug in two error messages: one tool demanded the flag, the other refused it. It was tracked as flutter/flutter#164864 and, on the Dart side, dart-lang/pub#4471 — which was closed as not planned, which is why so many blog posts still tell you the situation is unfixable.
It is fixed. But the fix is the opposite of what most of those posts say, so read the next section before you copy anything.
Why the deadlock existed — and why it's gone
The flutter: generate: true flag never actually meant "generate localizations." It was the trigger for injecting the synthetic package:flutter_gen into .dart_tool/package_config.json. A pub workspace has exactly one shared package_config.json at the root, so the tool could not safely stitch a per-package synthetic entry into it, and it hard-exited instead.
flutter/flutter#165838 narrowed that check to only fire when synthetic packages were actually in play. Then the synthetic package was removed outright — see the official breaking change, Localized messages are generated into source, not a synthetic package, which landed on the 3.32 stable line. On today's stable (3.47) the workspace check is gone from the tool entirely, because there is no synthetic package left to conflict with.
The practical consequence, and the part people get wrong:
- Keep
flutter: generate: true. It is still required.flutter gen-l10nreads your manifest and tool-exits without it, workspace or not. - Delete
synthetic-packagefroml10n.yamlcompletely. Notfalse— gone. In current Flutter,synthetic-package: trueis a hard error ("Cannot enable, this feature has been removed") andsynthetic-package: falseprints a deprecation warning that it "no longer has any effect and should be removed."
If you are still on a 3.29–3.31 SDK where the deadlock is real, upgrade. The stopgap on those versions was flutter config --explicit-package-dependencies, which disabled synthetic packages and let the flag through.
The working monorepo layout
my_monorepo/
pubspec.yaml # workspace root + melos config
apps/
customer_app/
pubspec.yaml
l10n.yaml
lib/l10n/arb/app_en.arb
packages/
shared_l10n/ # strings shared by every app
pubspec.yaml
l10n.yaml
lib/src/l10n/arb/shared_en.arb
1. The workspace root
Pub workspaces need every member on an SDK constraint of ^3.6.0 or higher. Glob entries such as packages/* require Dart 3.11+; list paths explicitly if you're older.
name: _
publish_to: none
environment:
sdk: ^3.9.0
workspace:
- apps/customer_app
- packages/shared_l10n
dev_dependencies:
melos: ^8.6.0
melos:
scripts:
l10n:
exec:
command: flutter gen-l10n
concurrency: 4
packageFilters:
fileExists: l10n.yaml
One melos run l10n now regenerates every package that owns an l10n.yaml, in its own directory, with its own config. (Melos 8 requires the command under exec: command:; the old top-level run: key was dropped.)
2. Every package that owns ARB files
A shared localization package is a real Flutter package with its own manifest — and it needs the generate flag just like an app does:
name: shared_l10n
publish_to: none
version: 1.0.0
environment:
sdk: ^3.9.0
resolution: workspace
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.20.3
flutter:
generate: true
flutter_localizations is not optional here: the generated class exposes a localizationsDelegates list that references the global Material/Cupertino/Widgets delegates.
3. One l10n.yaml per package, fully explicit
Spell out every path. Defaults are resolved relative to whichever package directory the command runs in, and in a monorepo you do not want to be guessing.
packages/shared_l10n/l10n.yaml:
arb-dir: lib/src/l10n/arb
output-dir: lib/src/l10n/generated
template-arb-file: shared_en.arb
output-localization-file: shared_localizations.dart
output-class: SharedLocalizations
nullable-getter: false
apps/customer_app/l10n.yaml:
arb-dir: lib/l10n/arb
output-dir: lib/l10n/generated
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
nullable-getter: false
Two details that save you an afternoon:
- Different
output-classper package. TwoAppLocalizationsclasses in one workspace compile fine but read terribly at the call site, and any accidental double export collides. nullable-getter: falsegives youSharedLocalizations.of(context)instead ofSharedLocalizations.of(context)!everywhere.
The l10n.yaml goes next to the package's pubspec.yaml, never inside lib/.
4. Export one barrel from the shared package
Feature packages should never reach into src/, and they should certainly never import a synthetic path. Add packages/shared_l10n/lib/shared_l10n.dart:
export 'src/l10n/generated/shared_localizations.dart';
Consumers get a clean import:
import 'package:shared_l10n/shared_l10n.dart';
Text(SharedLocalizations.of(context).retryButton);
Commit the generated files. flutter gen-l10n only runs automatically for the app currently being built, so on a fresh clone your shared package's generated/ folder would otherwise be missing and the analyzer would light up every consumer before anyone runs melos run l10n.
Wiring the delegates together
The shared package and the app each produce their own delegate. Both have to be registered, and the app's supportedLocales should be the intersection you actually ship.
In the app's pubspec.yaml, depend on the shared package by path:
dependencies:
shared_l10n:
path: ../../packages/shared_l10n
Then merge:
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:shared_l10n/shared_l10n.dart';
import 'l10n/generated/app_localizations.dart';
class CustomerApp extends StatelessWidget {
const CustomerApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
AppLocalizations.delegate,
SharedLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
home: const HomePage(),
);
}
}
Order matters only for lookup of the same type, and these are distinct types, so both resolve independently. What does bite people: if shared_l10n ships fr but the app's ARB set does not, fr is absent from AppLocalizations.supportedLocales, the framework never selects it, and your shared French strings silently never appear. Keep the locale sets aligned across packages — that is the single most common "the shared package isn't translating" report.
Quick triage table
| Symptom | Cause | Fix |
|---|---|---|
Attempted to generate localizations code without having the flutter: generate flag turned on |
Package missing flutter: generate: true |
Add it to that package's pubspec |
Cannot enable "synthetic-package", this feature has been removed |
synthetic-package: true still in l10n.yaml |
Delete the key |
Warning: synthetic-package no longer has any effect |
synthetic-package: false left behind |
Delete the key |
generate: true is not supported within workspaces |
Pre-3.32 SDK | Upgrade, or flutter config --explicit-package-dependencies |
Target of URI doesn't exist: package:flutter_gen/gen_l10n/... |
Old synthetic import | Import the real generated path — see Fix the flutter_gen gen_l10n import error |
| Nothing regenerates in CI | Only the built app runs gen-l10n | Run melos run l10n before flutter build |
Keep the ARB files themselves correct
Once generation works, the failures move up a layer: a shared package with fifteen locales is fifteen ARB files that have to stay structurally identical, and a missing plural category doesn't fail the build — it ships. FlutterLocalisation gives you an ARB editor for app_<locale>.arb files so translators aren't editing raw JSON, translation management across all your locales, and ICU plural validation that flags a locale missing a category its language genuinely needs — a dropped few for Polish, a missing many for Arabic — before it reaches a device.
It works the same whether your strings live in one app or in a shared package feeding six. See pricing, or try FlutterLocalisation free and get your monorepo's ARB files under control today.