Repository navigation
Description
Observation
@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.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:
- 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.
- Related but distinct: i18n: the core in-memory i18n fallback never receives the declared
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 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.
Generated by Claude Code
Activity
os-zhuang commentedon Sep 7, 2026
Triage: closing
not planned— outside the three admission classes (#16351, as refined by PR #16557), on the card's own measurement: ⛔ "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."Against each class: ⛔ (a) no defect — the two agree on every constructible input; ⛔ (b) nothing declares "one rule, one place" for this; ⛔ (c) not applicable. Under the boundary now in the rule (「不完整 vs 错误」) a verbatim duplicate that agrees is neither.
⭐ The identification is thorough and worth keeping: same four steps, same order, same comments naming the same examples (
zh-cn→zh-CN,zh-CN→zh,zh→zh-CN), differing only in being list-shaped vs record-shaped. ⇒ Not a resemblance — a copy, established at the level of its comments.承接者: ⛔ none — and I checked both candidates rather than assuming.
accept-language: zhrefuses in English on the same response whose dataset labels are Chinese #15757 is fenced: 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."i18n.fallbackLocale— its t() falls back to defaultLocale only, so one declaration answers differently under the memory fallback and under service-i18n #15694 looked like the natural vehicle — same file,packages/core/src/fallbacks/memory-i18n.ts.completed, by merged PR fix(core,runtime,spec): the in-memory i18n fallback learns the declared i18n.fallbackLocale #16088 (2026-09-06). ⇒ The file was opened, the fallback learnedfallbackLocale, and the duplicate was correctly left alone as out of scope. The carrier existed, was used for something else, and is gone.⇒ That is the eighth closure in this sweep where the acceptance-notes fallback has no carrier, and it is the clearest illustration of why: a card being open on the same file is not a carrier, because a well-fenced dispatch does not widen. ⛔ The emptiness is structural, not an oversight by anyone.
What I am uncertain about, stated because it is the one thing that could make this the wrong call: the duplicate is exported from
@objectstack/core, so it is reachable by anything depending on that package, and a future consumer picking it up would be adopting the copy rather than the rule. ⛔ Not measured whether any consumer does today, in this repo or inobjectui/cloud. If one does, the maintenance hazard acquires a named victim and the grade changes.Reopen conditions (any, no approval needed):
resolveBundleLocaleshould be sent here.resolveLocalefrom@objectstack/core.memory-i18n.tsfor another reason and can take the delegating one-liner as a free rider — ⛔ not work anyone should schedule, but the right moment if it arrives.Reversibility: fully reversible; nothing decays while the rule is unchanged. ⭐ And the suggested shape survives here for whenever it is taken: have
resolveLocaledelegate toresolveBundleLocalerather than re-derive the steps — a delegating one-liner makes divergence impossible rather than merely unlikely, which is the right form and is strictly better than adding a test that the two agree.⛔ Correctly distinct from #15694 (same file, different mechanism — the memory fallback never receiving
i18n.fallbackLocale): collapsing the duplicate would not have fixed that, and fixing that did not collapse this. ⛔ Neither is a duplicate of the other.Generated by Claude Code