Summary
Consumers currently pass locale to EditorConfiguration as an opaque string, and src/utils/localization.js does a single-level dynamic import. If the tag doesn't match a shipped translations/*.json exactly, the editor silently falls back to English. The supported-locales list lives in bin/prep-translations.js; consumers don't see it, and have no way to ask "what's the closest bundle you actually ship?". This means each client has to mirror that table to do the right thing — and historically hasn't.
Concretely on WP-Android (today): PerAppLocaleManager.getCurrentLocaleLanguageCode() returns Locale.getLanguage() — ISO 639-1 only — so a Brazilian user sends pt (matches), a Chinese user sends zh (no zh-cn/zh-tw match → English), and a user who picked en_GB / nl_BE / pt_BR in the language picker loses their region variant before we ever reach GutenbergKit.
Proposed shape
Move locale resolution into the library so every client gets correct behaviour without re-implementing the table:
1. Platform-native setLocale overloads
// Android
setLocale(locale: Locale) // new — resolves internally
setLocale(locale: String) // existing — power-user / tests
// iOS
func setLocale(_ locale: Locale) // new
func setLocale(_ identifier: String) // existing
The new overloads call toLanguageTag().lowercased() (or Locale.identifier on iOS) and run the resolution chain before storing the string for serialization. The wire format stays unchanged — only the consumer-facing API gains type safety.
2. Resolution chain
For an input tag xx-yy:
- Full tag (
xx-yy) — match if shipped
- Language-only tag (
xx) — match if shipped
- Fall back to
en
Determined at runtime against the actually-bundled translation files, not a hard-coded list — that way the resolver stays correct when SUPPORTED_LOCALES in bin/prep-translations.js changes without anyone remembering to update a duplicate.
3. JS-side fallback becomes defensive
localization.js's catch-and-warn stays as a defense-in-depth net but should rarely fire in practice once the Android/iOS resolver narrows to a known-shipped tag.
Out of scope
- Changing the
SUPPORTED_LOCALES list itself.
- Changing the wire format of
EditorConfiguration.locale.
- Pluralization / RTL handling — separate concerns.
Why this matters
Two clients today (WP-Android, WP-iOS) and a third on the way (the GutenbergKit preloader being added in WP-Android #22579, where every preloaded session has to remember to overlay the locale or it ships English regardless of device language). One resolver in the library closes all three at once and any future client by default.
Related
Summary
Consumers currently pass
localetoEditorConfigurationas an opaque string, andsrc/utils/localization.jsdoes a single-level dynamic import. If the tag doesn't match a shippedtranslations/*.jsonexactly, the editor silently falls back to English. The supported-locales list lives inbin/prep-translations.js; consumers don't see it, and have no way to ask "what's the closest bundle you actually ship?". This means each client has to mirror that table to do the right thing — and historically hasn't.Concretely on WP-Android (today):
PerAppLocaleManager.getCurrentLocaleLanguageCode()returnsLocale.getLanguage()— ISO 639-1 only — so a Brazilian user sendspt(matches), a Chinese user sendszh(nozh-cn/zh-twmatch → English), and a user who pickeden_GB/nl_BE/pt_BRin the language picker loses their region variant before we ever reach GutenbergKit.Proposed shape
Move locale resolution into the library so every client gets correct behaviour without re-implementing the table:
1. Platform-native
setLocaleoverloadsThe new overloads call
toLanguageTag().lowercased()(orLocale.identifieron iOS) and run the resolution chain before storing the string for serialization. The wire format stays unchanged — only the consumer-facing API gains type safety.2. Resolution chain
For an input tag
xx-yy:xx-yy) — match if shippedxx) — match if shippedenDetermined at runtime against the actually-bundled translation files, not a hard-coded list — that way the resolver stays correct when
SUPPORTED_LOCALESinbin/prep-translations.jschanges without anyone remembering to update a duplicate.3. JS-side fallback becomes defensive
localization.js's catch-and-warn stays as a defense-in-depth net but should rarely fire in practice once the Android/iOS resolver narrows to a known-shipped tag.Out of scope
SUPPORTED_LOCALESlist itself.EditorConfiguration.locale.Why this matters
Two clients today (WP-Android, WP-iOS) and a third on the way (the GutenbergKit preloader being added in WP-Android #22579, where every preloaded session has to remember to overlay the locale or it ships English regardless of device language). One resolver in the library closes all three at once and any future client by default.
Related