Repository navigation
fix: sanitize non-BCP-47 browser locales that crash the frontend - #10318
Conversation
Some environments (e.g. Chromium under Playwright/Docker) report `navigator.language` as `en-US@posix`, which `Intl` rejects with `RangeError: Incorrect locale information provided`, crashing the app before any notebook renders. Add a pure `core/i18n/locale.ts` module that normalizes browser/config locale tags (strip `@modifier`/`.charset`, `_`->`-`, language-subtag fallback) and validate via `Intl.Locale`. Route the `LocaleProvider` and the data-table default formatting locale through it. Closes #9938 Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Thanks for driving this to the finish, @Light2Dark — happy to see #9938 getting a solid fix, and I like the extra edge cases you caught. One small, humble ask on attribution: this lands very close to the approach in my (now-closed) #10219 — the pure |
|
Thanks @Light2Dark for picking this up and shipping a cleaner version of the #9938 fix — especially the pure If it’s easy, a short note that #10219 / @Bartok9 explored the same approach (normalize |
There was a problem hiding this comment.
Pull request overview
Fixes a frontend crash caused by non‑BCP‑47 navigator.language values (e.g. en-US@posix) being passed into Intl via react-aria’s I18nProvider, by normalizing/validating both configured and browser locales before use.
Changes:
- Added a dedicated, pure i18n locale helper module that normalizes POSIX/ICU/charset/underscore locale strings and validates via
Intl.Locale. - Updated
LocaleProviderto use the sharedsafeLocale()resolution chain instead of passing through rawnavigator.language. - Sanitized the data-table column formatting default locale and added focused unit tests for the new normalization logic and provider behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/core/i18n/locale.ts | Introduces locale normalization + Intl.Locale validation with safe fallbacks. |
| frontend/src/core/i18n/locale-provider.tsx | Routes react-aria I18nProvider locale through safeLocale() to prevent crashes. |
| frontend/src/core/i18n/tests/locale.test.ts | Adds unit tests for validation/normalization + browser/config fallback behavior. |
| frontend/src/core/i18n/tests/locale-provider.test.tsx | Ensures LocaleProvider always supplies a safe, concrete locale (incl. POSIX cases). |
| frontend/src/components/data-table/column-formatting/feature.ts | Uses browserLocale() instead of raw navigator.language for formatting defaults. |
There was a problem hiding this comment.
No issues found across 5 files
Architecture diagram
sequenceDiagram
participant Browser as Browser (Navigator)
participant State as User Config (Jotai)
participant Provider as LocaleProvider
participant Logic as i18n/locale.ts
participant Aria as I18nProvider (React Aria)
participant Table as DataTable Feature
Note over Provider, Logic: App Initialization Flow
Provider->>State: Read localeAtom
State-->>Provider: Configured locale (e.g., "en_US")
Provider->>Logic: NEW: safeLocale(configValue)
Logic->>Logic: NEW: normalizeLocale(configValue)
Logic->>Logic: Validate via new Intl.Locale()
alt Config is invalid or null
Logic->>Browser: NEW: browserLocale()
Browser-->>Logic: navigator.language (e.g., "en-US@posix")
Logic->>Logic: NEW: normalizeLocale(browserValue)
Note right of Logic: Strips @posix, .UTF-8, etc.
end
Logic-->>Provider: Valid BCP-47 string (e.g., "en-US")
Provider->>Aria: CHANGED: Mount with sanitized locale
Aria-->>Provider: Provide i18n context to tree
Note over Table, Logic: Data Table Initialization
Table->>Logic: NEW: browserLocale()
Logic->>Browser: Access navigator.language
Browser-->>Logic: "en-US@posix"
Logic->>Logic: normalizeLocale()
Logic-->>Table: "en-US"
Note over Table: Safe for Intl.NumberFormat values
Coverage Report for ./frontend
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
Got it @Bartok9 , adding in the PR. Thank you. |
|
Appreciate it, @Light2Dark — saw the Acknowledgement land on the PR body. Thanks for the credit and for shipping the extra edge cases. |
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.16-dev3 |
…imo-team#10318) ## 📝 Summary Closes marimo-team#9938 Some environments — notably Chromium under Playwright/Docker — report `navigator.language` as `en-US@posix`. That value is a POSIX locale string, not a valid [BCP 47](https://www.rfc-editor.org/info/rfc5646) tag, so passing it into `Intl` (via react-aria's `I18nProvider`, which constructs `Intl.Locale`) throws `RangeError: Incorrect locale information provided` and the frontend crashes before any notebook renders — including the stock `intro.py` tutorial. The previous `safeLocale` validated the *configured* locale but returned the raw `navigator.language` on both fallback paths, so with the default (unset) config the crash tag flowed straight through. ### Changes - Add a pure `frontend/src/core/i18n/locale.ts` module: - `normalizeLocale(tag)` strips non-BCP-47 modifiers (`@posix`, `.UTF-8`), converts `_`→`-`, falls back to the language subtag, and returns `null` when nothing usable remains. - `safeLocale(config)` becomes a clean precedence chain: `normalizeLocale(config) ?? browserLocale() ?? FALLBACK_LOCALE`. - Validation uses `new Intl.Locale(tag)` — the exact constructor that throws — rather than `supportedLocalesOf`, which would additionally remap legitimate-but-uncommon locales to `en-US` and is runtime/CLDR-dependent. - Route `LocaleProvider` through the module (config and browser tags now share one normalizer). - Sanitize the data-table default formatting locale (`column-formatting/feature.ts`), the other site that fed raw `navigator.language` into `Intl.*`. ### Why this approach Downstream formatting reads its locale from react-aria's `useLocale()`, which flows from `I18nProvider` — so fixing the provider fixes those consumers automatically. Keeping the helpers in a pure module avoids pulling React/jotai into the data-table feature, and validating with `Intl.Locale` targets the crash precisely without silently changing valid user locales. ### Acknowledgement Thanks to @Bartok9, whose earlier (now-closed) marimo-team#10219 explored the same overall approach — a pure `locale.ts` module, `normalizeLocale` (strip `@posix`/`.UTF-8`, `_`→`-`, language-subtag fallback) plus a `safeLocale` precedence chain, `Intl.Locale` validation over `supportedLocalesOf`, and sanitizing the `column-formatting/feature.ts` default locale. ## 📋 Pre-Review Checklist - [x] For large changes, or changes that affect the public API: this change was discussed or approved through an issue — see marimo-team#9938. - [x] Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it. - [ ] Video or media evidence is provided for any visual changes (optional). ## ✅ Merge Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [x] Documentation has been updated where applicable, including docstrings for API changes. - [x] Tests have been added for the changes made. Made with [Cursor](https://cursor.com) Co-authored-by: Cursor <cursoragent@cursor.com>
📝 Summary
Closes #9938
Some environments — notably Chromium under Playwright/Docker — report
navigator.languageasen-US@posix. That value is a POSIX locale string, not a valid BCP 47 tag, so passing it intoIntl(via react-aria'sI18nProvider, which constructsIntl.Locale) throwsRangeError: Incorrect locale information providedand the frontend crashes before any notebook renders — including the stockintro.pytutorial.The previous
safeLocalevalidated the configured locale but returned the rawnavigator.languageon both fallback paths, so with the default (unset) config the crash tag flowed straight through.Changes
frontend/src/core/i18n/locale.tsmodule:normalizeLocale(tag)strips non-BCP-47 modifiers (@posix,.UTF-8), converts_→-, falls back to the language subtag, and returnsnullwhen nothing usable remains.safeLocale(config)becomes a clean precedence chain:normalizeLocale(config) ?? browserLocale() ?? FALLBACK_LOCALE.new Intl.Locale(tag)— the exact constructor that throws — rather thansupportedLocalesOf, which would additionally remap legitimate-but-uncommon locales toen-USand is runtime/CLDR-dependent.LocaleProviderthrough the module (config and browser tags now share one normalizer).column-formatting/feature.ts), the other site that fed rawnavigator.languageintoIntl.*.Why this approach
Downstream formatting reads its locale from react-aria's
useLocale(), which flows fromI18nProvider— so fixing the provider fixes those consumers automatically. Keeping the helpers in a pure module avoids pulling React/jotai into the data-table feature, and validating withIntl.Localetargets the crash precisely without silently changing valid user locales.Acknowledgement
Thanks to @Bartok9, whose earlier (now-closed) #10219 explored the same overall approach — a pure
locale.tsmodule,normalizeLocale(strip@posix/.UTF-8,_→-, language-subtag fallback) plus asafeLocaleprecedence chain,Intl.Localevalidation oversupportedLocalesOf, and sanitizing thecolumn-formatting/feature.tsdefault locale.📋 Pre-Review Checklist
✅ Merge Checklist
Made with Cursor