Symptom
GET /api/v1/i18n/locales returns four locale descriptors — en, zh-CN, ja-JP, es-ES — on the showcase app, whose artifact declares i18n.supportedLocales = ['en','zh-CN']. The envelope half is correct ({success,data:{locales}} with isDefault, the #3636 shape); the set is a superset. ja-JP and es-ES are real and servable here (200) but the app never opted into them. This is the FAIL of surface-matrix clause 6. Reproduced on two boots.
Root cause
Located, and verified still present on origin/main as of this filing. Nothing narrows the i18n service's locale set to the app's declared supportedLocales.
packages/runtime/src/app-plugin.ts loadTranslations() reads i18nConfig.defaultLocale only (it calls setDefaultLocale), then loads every locale key of every bundle. It never consults i18nConfig.supportedLocales.
- Each platform plugin independently pushes its own 4-locale bundle at
kernel:ready — packages/platform-objects/src/plugin.ts (~206-230), plus service-settings, service-storage, service-messaging, service-realtime, plugin-security, plugin-sharing, plugin-webhooks — each shipping en/zh-CN/ja-JP/es-ES.
getLocales() then returns the loaded key set (packages/core/src/fallbacks/memory-i18n.ts; same in packages/services/service-i18n/src/file-i18n-adapter.ts:192), and both the dispatcher /i18n domain and the service-i18n route map that set straight to the response.
Fix site / routing. The app's supportedLocales is only visible at the runtime app-plugin layer — loadTranslations() already reads i18nConfig and sets the default locale there. core's getLocales() has no knowledge of the app's declared set, so the narrowing must originate in packages/runtime (thread supportedLocales the way defaultLocale is already threaded, and narrow what getLocales() reports). Routed domain:cli on that basis.
Consequence. Any client building a locale picker from this route — including the platform's own Settings > Localization select, which already offers all four — offers locales in which only sys_* objects are translated. Picking ja-JP or es-ES guarantees a mixed-language session for app-owned metadata.
Related but distinct: #5418 (closed) covered the console's hardcoded language menu — a different layer (the console's own list), not this server-route superset.
Reproduction
Boot the showcase on a fresh file DB, sign in, then GET /api/v1/i18n/locales.
- Expected: descriptors for the artifact's declared
i18n.supportedLocales = ['en','zh-CN'].
- Actual: four descriptors —
en, zh-CN, ja-JP, es-ES.
Source
Extracted from the QA run #7640 (framework 92f26f7, console 09987b680).
Symptom
GET /api/v1/i18n/localesreturns four locale descriptors —en,zh-CN,ja-JP,es-ES— on the showcase app, whose artifact declaresi18n.supportedLocales = ['en','zh-CN']. The envelope half is correct ({success,data:{locales}}withisDefault, the #3636 shape); the set is a superset.ja-JPandes-ESare real and servable here (200) but the app never opted into them. This is the FAIL ofsurface-matrixclause 6. Reproduced on two boots.Root cause
Located, and verified still present on
origin/mainas of this filing. Nothing narrows the i18n service's locale set to the app's declaredsupportedLocales.packages/runtime/src/app-plugin.tsloadTranslations()readsi18nConfig.defaultLocaleonly (it callssetDefaultLocale), then loads every locale key of every bundle. It never consultsi18nConfig.supportedLocales.kernel:ready—packages/platform-objects/src/plugin.ts(~206-230), plus service-settings, service-storage, service-messaging, service-realtime, plugin-security, plugin-sharing, plugin-webhooks — each shippingen/zh-CN/ja-JP/es-ES.getLocales()then returns the loaded key set (packages/core/src/fallbacks/memory-i18n.ts; same inpackages/services/service-i18n/src/file-i18n-adapter.ts:192), and both the dispatcher/i18ndomain and the service-i18n route map that set straight to the response.Fix site / routing. The app's
supportedLocalesis only visible at the runtime app-plugin layer —loadTranslations()already readsi18nConfigand sets the default locale there.core'sgetLocales()has no knowledge of the app's declared set, so the narrowing must originate inpackages/runtime(threadsupportedLocalesthe waydefaultLocaleis already threaded, and narrow whatgetLocales()reports). Routeddomain:clion that basis.Consequence. Any client building a locale picker from this route — including the platform's own Settings > Localization select, which already offers all four — offers locales in which only
sys_*objects are translated. Pickingja-JPores-ESguarantees a mixed-language session for app-owned metadata.Related but distinct: #5418 (closed) covered the console's hardcoded language menu — a different layer (the console's own list), not this server-route superset.
Reproduction
Boot the showcase on a fresh file DB, sign in, then
GET /api/v1/i18n/locales.i18n.supportedLocales=['en','zh-CN'].en,zh-CN,ja-JP,es-ES.Source
Extracted from the QA run #7640 (framework 92f26f7, console 09987b680).