Repository navigation
Description
Steps to reproduce
A Flutter web app fails to start at all — permanently blank page, no error UI, no partial render — if the browser reports a single language tag that Intl.Locale rejects, e.g. en-US@posix.
This is not a contrived value: Chromium reports navigator.language === "en-US@posix" on Linux when LANG is unset, which is the default in many containers (including the official mcr.microsoft.com/playwright:*-jammy images). It took an entire CI suite down for us before we traced it.
Minimal reproduction
-
flutter create locale_repro && cd locale_repro -
flutter build web --release -
Serve
build/web(e.g.python3 -m http.server 8000 -d build/web) -
Load it in a browser reporting the bad tag. Either:
(a) Naturally — run Chromium on Linux with no
LANGset:docker run --rm -it --network host mcr.microsoft.com/playwright:v1.60.0-jammy \ node -e "require('playwright').chromium.launch({args:['--no-sandbox']}).then(async b => { const p = await (await b.newContext()).newPage(); p.on('pageerror', e => console.log('PAGEERROR:', String(e))); await p.goto('http://localhost:8000'); console.log('navigator.language =', await p.evaluate(() => navigator.language)); console.log('glass pane:', await p.locator('flt-glass-pane, flutter-view').first() .waitFor({state:'attached', timeout:20000}).then(() => 'ATTACHED').catch(() => 'MISSING')); await b.close(); })"
(b) Deterministically, independent of host locale — inject the tag before page scripts run:
await page.addInitScript(() => { const bad = ['en-US@posix']; Object.defineProperty(navigator, 'languages', { configurable: true, get: () => bad }); Object.defineProperty(navigator, 'language', { configurable: true, get: () => bad[0] }); }); await page.goto('http://localhost:8000');
(Note: setting Playwright's
locale: 'en-US@posix'option is not a valid reproduction — an invalid--langalso breaks the dynamicimport()of CanvasKit fromgstatic.com, so the app fails to boot for an unrelated reason. Use injection.)
Expected results
An unrecognised or malformed locale tag degrades to a sensible default. The app starts. Ideally invalid entries are skipped and the remaining valid ones are honoured.
Actual results
The engine never mounts. No flt-glass-pane / flutter-view element is ever attached, so the page stays blank forever with a single uncaught error:
Error: Invalid argument(s): Incorrect locale information provided
Dart stack trace:
RangeError: Incorrect locale information provided
Because this happens during engine bootstrap, before main() runs, no application-level error handling, runZonedGuarded, or custom loader can intercept or recover from it. The user simply gets a white screen.
Root cause
EnginePlatformDispatcher.parseBrowserLanguages() in lib/web_ui/lib/src/engine/platform_dispatcher.dart constructs a DomLocale (i.e. new Intl.Locale(...)) for every entry of navigator.languages, with no try/catch and no validation:
for (final String language in languages) {
final domLocale = DomLocale(language);
locales.add(
ui.Locale.fromSubtags(
languageCode: domLocale.language,
scriptCode: domLocale.script,
countryCode: domLocale.region,
),
);
}and it is called from a field initializer:
PlatformConfiguration configuration = PlatformConfiguration(
locales: parseBrowserLanguages(),
...
);so one rejected tag throws while the dispatcher is being constructed. Verified unchanged on master at time of filing.
Isolated the throwing call by wrapping every locale-sensitive JS API before the bundle loads; it is specifically new Intl.Locale("en-US@posix"). Worth noting the message differs from other Intl entry points — the constructors reject the same tag with Invalid language tag: en-US@posix, while Intl.Locale reports Incorrect locale information provided, which makes the error hard to search for.
Suggested fix
Skip tags the runtime rejects rather than letting them escape, falling back to _defaultLocale only if none survive:
final locales = <ui.Locale>[];
for (final String language in languages) {
try {
final domLocale = DomLocale(language);
locales.add(ui.Locale.fromSubtags(
languageCode: domLocale.language,
scriptCode: domLocale.script,
countryCode: domLocale.region,
));
} catch (_) {
// A tag Intl.Locale rejects (e.g. `en-US@posix`, which Chromium reports on
// Linux with LANG unset) must not prevent the engine from starting.
}
}
if (locales.isEmpty) {
return const <ui.Locale>[_defaultLocale];
}This preserves the existing "never return an empty list" contract the function already documents.
Workaround for anyone hitting this meanwhile: sanitize navigator.languages / navigator.language in web/index.html before flutter_bootstrap.js loads, validating each tag with new Intl.Locale(tag) in a try/catch and only overriding when something is actually invalid.
Flutter Doctor output
Flutter 3.41.7 • channel stable • https://github.com/flutter/flutter.git
Framework • revision cc0734ac71 (4 months ago) • 2026-04-15 21:21:08 -0700
Engine • hash 7a53c052bc4b472cf780b199087e1368e4a9aa8c (revision 59aa584fdf) • 2026-04-16
Tools • Dart 3.11.5 • DevTools 2.54.2
Reproduced with a --release web build (CanvasKit) in Chromium 1.60.0-jammy. The offending code is identical on master today.
Activity
mbcorona commentedon Aug 4, 2026
Confirmed from source (still present on 3.44.8): parseBrowserLanguages constructs DomLocale(language) per tag with no try/catch, from a field initializer, so a tag Intl.Locale rejects (e.g. en-US@posix) throws during engine bootstrap and the page stays blank. Clear repro and a sound suggested fix. Routing to the web team.
fix(web): guard parseBrowserLanguages against invalid locale tags
fix(web): guard parseBrowserLanguages against invalid locale tags (fl…
github-actions commentedon Oct 2, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.