← Back to Blog

Fix "Cannot Generate Localizations in a Workspace"

flutterl10nmonorepomelosgen-l10n

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-l10n reads your manifest and tool-exits without it, workspace or not.
  • Delete synthetic-package from l10n.yaml completely. Not false — gone. In current Flutter, synthetic-package: true is a hard error ("Cannot enable, this feature has been removed") and synthetic-package: false prints 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-class per package. Two AppLocalizations classes in one workspace compile fine but read terribly at the call site, and any accidental double export collides.
  • nullable-getter: false gives you SharedLocalizations.of(context) instead of SharedLocalizations.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.