-
Notifications
You must be signed in to change notification settings - Fork 6
fix(i18n): locale fallback, auto-detection, and service-i18n ESM export fix #914
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6f33892
f5d86ad
32e46d0
8e31dd9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
|
|
||||
| ## [Unreleased] | ||||
|
|
||||
| ### Added | ||||
| - **Locale code fallback** — New `resolveLocale()` helper in `@objectstack/core` that resolves | ||||
| locale codes through a 4-step fallback chain: exact match → case-insensitive match | ||||
| (`zh-cn` → `zh-CN`) → base language match (`zh-CN` → `zh`) → variant expansion | ||||
| (`zh` → `zh-CN`). Used by `createMemoryI18n`, `HttpDispatcher.handleI18n()`, and | ||||
| `I18nServicePlugin` route handlers. | ||||
| - **Auto-detection of I18nServicePlugin** — Both `DevPlugin` and CLI `serve` command now | ||||
| automatically detect `translations`/`i18n` config in the stack definition and register | ||||
| `I18nServicePlugin` from `@objectstack/service-i18n` when available. Falls back to | ||||
| the core in-memory i18n (with locale resolution) if the package is not installed. | ||||
| - **Enhanced i18n diagnostics** — `AppPlugin` now emits clear warnings when: | ||||
| - Translations exist but no i18n service is registered (guides user to add the plugin). | ||||
| - Translations are loaded into a fallback/stub i18n service (recommends production plugin). | ||||
| - **i18n locale fallback in REST routes** — `HttpDispatcher.handleI18n()` translations and labels | ||||
| endpoints now resolve locale codes via fallback when exact match returns empty translations. | ||||
| The response includes `requestedLocale` when a fallback was used. | ||||
|
|
||||
| ### Changed | ||||
| - **DevPlugin i18n stub** — Replaced the duplicate `createI18nStub()` in `DevPlugin` with | ||||
| `createMemoryI18n` from `@objectstack/core`, ensuring locale fallback works consistently | ||||
| in dev mode. DevPlugin now tries `I18nServicePlugin` before the stub when stack has translations. | ||||
| - `createMemoryI18n` now uses `resolveLocale()` internally for `t()` and `getTranslations()`, | ||||
| enabling locale fallback (e.g. `zh` → `zh-CN`) without any plugin changes. | ||||
| - CLI `serve` command now auto-registers `I18nServicePlugin` when config has translations/i18n, | ||||
| mirroring DevPlugin's auto-detection behavior for production environments. | ||||
|
|
||||
| ### Changed | ||||
|
||||
| ### Changed |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -196,12 +196,44 @@ export default class Serve extends Command { | |||||||||||||||||||||||||||||||||||||||||
| // and startup failures (e.g. plugin.app.dev-workspace). | ||||||||||||||||||||||||||||||||||||||||||
| if (!isHostConfig(config) && (config.objects || config.manifest || config.apps)) { | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| const { AppPlugin } = await import('@objectstack/runtime'); | ||||||||||||||||||||||||||||||||||||||||||
| await kernel.use(new AppPlugin(config)); | ||||||||||||||||||||||||||||||||||||||||||
| trackPlugin('App'); | ||||||||||||||||||||||||||||||||||||||||||
| const { AppPlugin } = await import('@objectstack/runtime'); | ||||||||||||||||||||||||||||||||||||||||||
| await kernel.use(new AppPlugin(config)); | ||||||||||||||||||||||||||||||||||||||||||
| trackPlugin('App'); | ||||||||||||||||||||||||||||||||||||||||||
| } catch (e: any) { | ||||||||||||||||||||||||||||||||||||||||||
| // silent | ||||||||||||||||||||||||||||||||||||||||||
| // silent | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // 3b. Auto-register I18nServicePlugin if config contains translations/i18n | ||||||||||||||||||||||||||||||||||||||||||
| // This ensures i18n REST routes work out of the box without manual plugin registration. | ||||||||||||||||||||||||||||||||||||||||||
| const hasI18nPlugin = plugins.some( | ||||||||||||||||||||||||||||||||||||||||||
| (p: any) => p.name === 'com.objectstack.service.i18n' | ||||||||||||||||||||||||||||||||||||||||||
| || p.constructor?.name === 'I18nServicePlugin' | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+209
to
+212
|
||||||||||||||||||||||||||||||||||||||||||
| const hasI18nPlugin = plugins.some( | |
| (p: any) => p.name === 'com.objectstack.service.i18n' | |
| || p.constructor?.name === 'I18nServicePlugin' | |
| ); | |
| const hasI18nPlugin = Array.isArray(plugins) && plugins.some((p: any) => { | |
| if (!p) return false; | |
| // String plugin declaration, e.g. '@objectstack/service-i18n' | |
| if (typeof p === 'string') { | |
| const normalized = p.toLowerCase(); | |
| return ( | |
| normalized === '@objectstack/service-i18n' || | |
| normalized.includes('service-i18n') | |
| ); | |
| } | |
| // Instantiated plugin object | |
| return ( | |
| p.name === 'com.objectstack.service.i18n' || | |
| p.constructor?.name === 'I18nServicePlugin' | |
| ); | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changelog entry claims
resolveLocale()is used byI18nServicePluginroute handlers, but the currentI18nServicePlugin.registerI18nRoutes()implementation returns translations for the requested locale without any fallback. Either update the plugin routes to useresolveLocale(and document behavior), or adjust this changelog text so it matches the actual implementation.