feat: resolve consumer locales against shipped translation bundles#491
Closed
feat: resolve consumer locales against shipped translation bundles#491
Conversation
Move locale resolution into the library so consumers stop having to mirror the `SUPPORTED_LOCALES` table to avoid silently falling back to English. The resolver runs full-tag → language-only → `en` against a manifest emitted by the JS build, and is exposed on each platform's configuration builder via a new `Locale` overload. Fixes #490
Contributor
Author
|
Splitting into separate iOS and Android PRs for independent review/landing. Replacement PRs incoming — iOS carries the foundational build manifest + JS resolver, Android stacks on top. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
en— implemented identically on JS, iOS, and Android, fed by a build-emittedsupported-locales.jsonmanifest so the "what do we ship?" answer has exactly one source of truth.setLocale(_ locale: Locale)(iOS) andsetLocale(context: Context, locale: Locale)(Android) overloads onEditorConfigurationbuilders. The wire format stays a string; only the consumer-facing API gains type safety.localization.jsnow applies the same resolution chain instead of hoping the consumer's tag matches a shipped filename exactly.Fixes #490.
Root Cause
EditorConfiguration.localeis an opaque string. Consumers (WP-iOS, WP-Android, the upcoming WP-Android preloader in wordpress-mobile/WordPress-Android#22579) historically passed whatever the platform handed back — oftenLocale.getLanguage()on Android, ISO 639-1 only — andsrc/utils/localization.jsdid a single-level dynamicimportthat silently fell back to English on any miss. The supported list lived only inbin/prep-translations.js, so each client had to mirror that table to do the right thing — and historically hadn't.Changes
Manifest
vite.config.js: New
emitSupportedLocalesManifestplugin scanssrc/translations/at build time and emitsdist/supported-locales.json(sorted array of locale tags). The existingcopy-dist-{ios,android}Make targets ship it into both bundles.Resolution chain
For an input
xx-yy, normalised to lowercase with_→-:xx-yy) — match if shipped ✅xx) — match if shipped ✅en✅Implemented in:
resolveLocale(input, supported?). Default supported set comes fromimport.meta.glob('../translations/*.json'), so JS is statically analysable and gets the same single source of truth without a separate fetch.LocaleResolverstruct, fed byGutenbergKitResources.loadSupportedLocales()reading the manifest fromBundle.module.LocaleResolverclass with afromAssets(context)factory that reads the manifest from the library'sassets/.New consumer APIs
The existing
setLocale(String)overloads stay untouched as a power-user / test escape hatch.What We Explored
assets/— Vite hashes the chunk filenames (pt-br-UCkBcRdR.js), and the prefixes can collide (nl-be-...vsnl-...), so a parser would have to re-encode the SUPPORTED_LOCALES list anyway. A manifest is simpler and unambiguous.androidx.startup/ hidden static Context — would letsetLocale(Locale)skip thecontext:parameter on Android. Rejected for now: adds a dependency and lifecycle surprise for one parameter; consumers calling this from an editor activity already have aContextin hand.Behaviour change for the cases the issue called out
pt_BRdevice localept→ matches → ✅ Brazilianpt-br→ matches → ✅ Brazilianpt_BRviaLocale.getLanguage()pt→ matches → ✅ BrazilianLocale)pt-br→ matches → ✅ Braziliannl_BEnl→ matches → 🇳🇱 Dutchnl-be→ matches → 🇧🇪 Belgian Dutchfr_CA(no regional bundle)fr-CA→ ❌ Englishfr→ ✅ FrenchLocale("zh","CN")zh-CN→ ❌ Englishzh-cn→ ✅ Simplified Chinesezh(language-only, no zh bundle)zhisn't shipped)Test plan
npx vitest run src/utils/localization.test.js— 4/4 passswift test --filter "EditorConfigurationBuilderTests|LocaleResolverTests"— 37/37 pass./android/gradlew :Gutenberg:testDebugUnitTest --tests "org.wordpress.gutenberg.model.LocaleResolverTest"— 5/5 pass./android/gradlew :Gutenberg:testDebugUnitTest --tests "org.wordpress.gutenberg.model.EditorConfigurationBuilderTest"— existing suite still greennpm run lint:js -- src/utils/localization.js src/utils/localization.test.js vite.config.js— clean./android/gradlew detekt— cleanLocale("pt","BR")/Locale("nl","BE")/Locale("fr","CA"), confirm the editor UI renders in the expected language.Out of scope
SUPPORTED_LOCALESlist itself.EditorConfiguration.locale— still an opaque string on the JS side.Related