You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
i18n: @objectstack/core's exported resolveLocale is a verbatim second copy of @objectstack/spec's resolveBundleLocale — one rule, two places to edit #16085
@objectstack/core ships a second, behaviourally identical copy of the locale-negotiation rule that @objectstack/spec owns.
Measured on origin/main at merge base 31403453d:
packages/spec/src/system/i18n-resolver.ts exports resolveBundleLocale(bundle, requested) — the rule the platform treats as authoritative. Four steps: exact match, case-insensitive match, base-language match, variant expansion. pickData calls it, and every document translator goes through pickData; packages/spec/src/system/validation-message.ts and packages/spec/src/system/operation-message.ts both call it directly for their built-in catalogs.
packages/core/src/fallbacks/memory-i18n.ts exports resolveLocale(requestedLocale, availableLocales) — the same four steps, in the same order, with the same comments naming the same examples (zh-cn to zh-CN, zh-CN to zh, zh to zh-CN). It is list-shaped where the spec one is record-shaped; that is the whole difference.
Why it is filed as an observation, not a defect
The two produce the same answer for every input this repo can construct, so nothing is wrong today. It is a maintenance hazard, not a live bug:
The duplicate is exported, so it is reachable by anything depending on @objectstack/core, and a future consumer picking it up would be adopting the copy rather than the rule.
A change to the negotiation rule — a new step, a change to which variant wins when several match, an ordering fix — has to be made twice, and nothing fails if only one is edited. Neither file references the other, so there is no signal at the edit site.
packages/core/src/fallbacks/memory-i18n.ts — resolveLocale, and its two call sites in resolveTranslations.
packages/spec/src/system/i18n-resolver.ts — resolveBundleLocale, the rule it duplicates.
Suggested shape, if it is taken
Have resolveLocale delegate to resolveBundleLocale rather than re-derive the steps, or retire it in favour of a call to the spec rule. Either way the check to keep is that the two never diverge; a delegating one-liner makes divergence impossible rather than merely unlikely.
Found while implementing #15757, which routes the write path's validation-message bridge through resolveBundleLocale so that path stops being a second negotiation rule of its own. This is the third copy that card's premise implies, spotted on the way; it is out of that card's scope and is not changed by it.
Observation
@objectstack/coreships a second, behaviourally identical copy of the locale-negotiation rule that@objectstack/specowns.Measured on
origin/mainat merge base31403453d:packages/spec/src/system/i18n-resolver.tsexportsresolveBundleLocale(bundle, requested)— the rule the platform treats as authoritative. Four steps: exact match, case-insensitive match, base-language match, variant expansion.pickDatacalls it, and every document translator goes throughpickData;packages/spec/src/system/validation-message.tsandpackages/spec/src/system/operation-message.tsboth call it directly for their built-in catalogs.packages/core/src/fallbacks/memory-i18n.tsexportsresolveLocale(requestedLocale, availableLocales)— the same four steps, in the same order, with the same comments naming the same examples (zh-cntozh-CN,zh-CNtozh,zhtozh-CN). It is list-shaped where the spec one is record-shaped; that is the whole difference.Why it is filed as an observation, not a defect
The two produce the same answer for every input this repo can construct, so nothing is wrong today. It is a maintenance hazard, not a live bug:
@objectstack/core, and a future consumer picking it up would be adopting the copy rather than the rule.i18n.fallbackLocale— its t() falls back to defaultLocale only, so one declaration answers differently under the memory fallback and under service-i18n #15694 is about the SAME file, but about a different mechanism — the memory fallback never receiving the declaredi18n.fallbackLocale. Collapsing the duplicate resolver would not fix that, and fixing that would not collapse this, so this is not inside i18n: the core in-memory i18n fallback never receives the declaredi18n.fallbackLocale— its t() falls back to defaultLocale only, so one declaration answers differently under the memory fallback and under service-i18n #15694's completion scope.Where
packages/core/src/fallbacks/memory-i18n.ts—resolveLocale, and its two call sites inresolveTranslations.packages/spec/src/system/i18n-resolver.ts—resolveBundleLocale, the rule it duplicates.Suggested shape, if it is taken
Have
resolveLocaledelegate toresolveBundleLocalerather than re-derive the steps, or retire it in favour of a call to the spec rule. Either way the check to keep is that the two never diverge; a delegating one-liner makes divergence impossible rather than merely unlikely.Found while implementing #15757, which routes the write path's validation-message bridge through
resolveBundleLocaleso that path stops being a second negotiation rule of its own. This is the third copy that card's premise implies, spotted on the way; it is out of that card's scope and is not changed by it.Generated by Claude Code