Skip to content

[Decision] the default locale's text — is the authored label the default-locale source (a default-locale request stops there), or must every supported locale ship a bundle? os i18n check says the former, localeChain does the latter; the resolver's literal ['en'] default rides on the answer #15711

Description

@claude

Related: #14882 · #15694

Filed by the domain:spec execution seat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T03:56Z) on the triage seat's routing note on #14882 (5545589675), which pinned the bug card to the config-honouring half and reserved this question, quoted verbatim:

「每个 supported locale 是否都必须出 bundle」是契约问题,不是本卡的 bug 半边。⇒ 若实现时发现不回答它就改不动,停下来拆卡送决策箱,⛔ 不要在一个 bug 修复里顺手定契约。

and on the dev report for PR #15707 (5549139634, open_questions), which delivered the bug half without answering it and pinned the exposed case unchanged. Labels: needs-user-decision + i18n; domain:* routing is the triage seat's once ruled (the landing point depends on the option). ⛔ Not dispatchable: the decision is the maintainer's. Sequencing: PR #15707 (#14882, in flight) is NOT blocked by this card and does not decide it.

The question

A workspace authors its metadata labels in its default locale (i18n.defaultLocale: 'zh-CN', inline label: '填报单'). Is the authored label the default locale's text — so a request for the default locale stops at the authored label before any other locale's bundle is consulted — or must every supported locale, the default included, ship a translation bundle? Today the platform answers both ways:

  • The coverage gate says "authored label IS the default-locale text". os i18n check on the reporter's stack (comment 5522341222 on i18n: metadata label lookup falls through to the en bundle on a zh-CN workspace — localeChain defaults fallbackChain to ['en'] and ignores i18n.fallbackLocale, so an authored Chinese label loses to a courtesy English bundle #14882): 1252 warning(s) across 2 locale(s) · ⚠ Non-default locales have gaps but the default locale is fully covered. — zero errors with no zh-CN bundle at all. Re-check the message site: git grep -n "default locale is fully covered" origin/main -- packages/cli/src/commands/i18n/check.ts (control: the same file must hit Threshold). Measured at 581d8f84c: check.ts:177, printed whenever report.totals.errors === 0.
  • The runtime says "walk the chain first". The @objectstack/spec/system label resolvers walk requested locale → fallbackChain → authored label, and the chain is consulted BEFORE the authored label: re-check git grep -n "fallbackChain ?? \['en'\]" origin/main -- packages/spec/src/system/i18n-resolver.ts (control: the same file hits function localeChain). Measured at 581d8f84c: i18n-resolver.ts:285.

What PR #15707 changes, and what it leaves exposed

#15707 threads the DECLARED fallback into the chain (II18nService.getFallbackLocale()fallbackChain: [fallback] at every REST metadata seam). Both boot paths collapse the declaration to fallbackLocale || defaultLocale || 'en' before constructing the service (packages/cli/src/commands/serve.ts:3022, packages/plugins/plugin-dev/src/dev-i18n.ts:247 at 581d8f84c), so after #15707 a workspace that declares defaultLocale: 'zh-CN' and no fallbackLocale resolves zh-CN → zh-CN → authored and the reporter's case is fixed without this ruling.

Still exposed after #15707, pinned there AS IT BEHAVES TODAY so this card can rule it:

stack config request bundles today, and after #15707
defaultLocale: 'zh-CN', fallbackLocale: 'en' zh-CN en only zh-CN → en → authoredEntry Sheet — the declared en outranks the author's 填报单 (pinned: i18n-resolver.test.ts "a chain that DECLARES en still consults en before the authored label"; meta-i18n-declared-fallback-chain.test.ts §5)
any caller that passes NO fallbackChain (a host outside this repo; the core in-memory i18n fallback, which declares nothing — #15694) zh-CN en only zh-CN → en → authoredEntry Sheet — the resolver's literal ['en'] default (pinned: "a caller that declares NO chain keeps the resolver's literal en default")

Production callers of these resolvers at 581d8f84c: packages/rest/src/rest-server.ts only (four seams). Nothing else in this repo calls them outside tests (git grep -ln "translateMetadataDocument" origin/main -- packages | grep -v "packages/spec/\|packages/rest/" answers two test files and two CHANGELOGs); the dev measured that objectui does not call them (its resolveActionLabel is a local helper with its own formatter config). The serving layer already knows the default locale at the seam: a request with no Accept-Language and no ?locale= resolves to i18n.getDefaultLocale() (rest-server.ts:3181 at 581d8f84c).

Options × real cost

option what real cost
A — the authored label IS the default-locale text (rule in spec) ResolveOptions gains defaultLocale?: string; localeChain does not walk the chain when locale === defaultLocale (the authored label answers); the serving layer passes i18n.getDefaultLocale(). fallbackLocale keeps its full meaning for every NON-default request (fr → fr bundle → en bundle → authored). one optional key on a published face (@objectstack/spec minor) + one rule in localeChain + the REST seam passing one more value; the gate's claim becomes true at runtime; the reporter's os i18n extract --locales=zh-CN workaround becomes optional; two pins flip
A′ — the same rule, in the serving layer only REST passes fallbackChain: [] when the resolved request locale equals getDefaultLocale(); spec text states the rule, the resolver stays mechanical zero new contract surface; but the rule lives in packages/rest — any other host calling the resolvers must re-implement it, and the spec cannot pin it
B — every supported locale must ship a bundle runtime unchanged; os i18n check reports a missing default-locale bundle as an ERROR (severity flip); docs say the authored label is not consulted while another declared bundle exists every app that authors labels inline in its default locale (the documented authoring style) turns red at the gate until it ships a generated bundle that duplicates its own source text — the stale-translation class this repo already closed cards on (#9672, #10026)
C — document the divergence nothing changes; docs state that a declared fallbackLocale is consulted before the authored label and that the gate counts authored text as coverage zero code; the trap stays for fallbackLocale: 'en' on a non-en default (a reflexive line in AI-authored configs) and the gate keeps claiming coverage the runtime does not honour

Second facet, rides on the first — the resolver's literal default. localeChain answers ['en'] when a caller passes no fallbackChain at all. Dev recommendation (report 5549139634): change it to [] — "requested locale, then the authored label" — in a follow-up once the first facet is ruled, so "declared but not executed" cannot recur through a chain-less caller; blast radius = callers passing only { locale }, none left in production in this repo after #15707, one pin flips. It is a behaviour change on a published @objectstack/spec/system default (which text answers moves; nothing starts failing) — the changeset level is part of the ruling. Under A the default locale is unaffected either way; the facet then only decides what a chain-less NON-default request does.

四维分析(从业务角度)

一句话问题:一个用中文写标签、顺手附了一份英文包的应用,中文用户看到的该是作者写的中文,还是必须再另出一份中文包才能保住中文?

推荐

A(规则住 spec:ResolveOptions.defaultLocale,serving 层从 getDefaultLocale() 传入,localeChain 对默认语言不走链),第二面与 A 同批裁:荐改 [](未声明即无回退),以显式 changeset 记行为变化;若维护者不愿动已发布默认,保留 ['en'] 与 A 亦相容(只影响不传链调用方的非默认语言请求)。⛔ B 不荐(扩散 + 漂移类);C 只在维护者认定「显式 fallbackLocale 字面优先」是刻意契约时成立。

若裁 A:一张 domain:spec 卡(resolver 规则 + 选项 + 两个 pin 翻转 + i18n-resolver.ts 文档),REST 侧传值在同一 PR(#15707 已建好 translateOptionsFor 这个单一缝);os i18n check 无需改动。若裁 B:一张 domain:cli 卡(严重度翻转)+ 文档卡。若裁 C:一张文档卡。第二面若裁改 []:并入 A 的卡。

置信缺口:objectstack 之外的宿主是否调用这些 resolver 未测(本仓只读到 rest 一个生产调用方);os i18n check 判「默认语言已覆盖」的具体规则未逐行读(只读了报告输出与消息位置 check.ts:177);objectui 侧的 label 消费链只采信 dev 报告的读数,本席未复核。

os-decision-facets

  • ① 长远合理性(≥50%):defaultLocale 声明的是作者语言,门禁已按此读 ⇒ A(规则住 spec);A′ 规则错位;B 与内联作者写法相悖并引入漂移类生成包。
  • ② 实际业务拉动:中等、真实 —— 一个真实部署撞上;fix(i18n): metadata label lookup honours the declared fallbackLocale / defaultLocale instead of the literal en chain (#14882) #15707 后剩余面 = 显式 fallbackLocale: 'en' 配非 en 默认语言 + 不传链的调用方。
  • ③ 防 AI 犯错:指向 A + 第二面改 [] —— 反射式 fallbackLocale: 'en' 与不传链都不再被字面 'en' 静默改写。
  • ④ 创业阶段不扩散:A′ 最紧、A 次紧(一个可选键 + 一条规则);B 扩散到每个应用;C 零。
  • 推荐:A,第二面同批裁、荐 [](显式 changeset);⛔ B 不荐;C 仅当「显式 fallbackLocale 字面优先」被认定为刻意契约。
  • 置信缺口:仓外宿主调用面未测;os i18n check 覆盖判定规则未逐行读;objectui 侧 label 链未复核。

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions