Skip to content

[Decision] One locale list, one completeness rule — the long-term i18n regime across Console, platform metadata, cloud and mobile #29349

Open

Description

@hotlong

维护者速读

  • 对「默认只写英文、其他语言定期按规则国际化」的直接回答:今天没有这条规范,现行制度恰好相反 —— Console 十个语言包和平台元数据四个语言包都要求同一 PR 跟随(all-locales-key-parity.test.ts 缺 key 即红、check-i18n-en-drift.mjs en 改值九包不跟即红、check:i18n 生成包不同步即红),而且译文由 AI 在同一 PR 写([Decision] Repeater row-property localisation: 604 catalog leaves across four locales — who authors the translated copy? #17508 裁决原话「AI负责翻译就行」)。实测:并入本仓后 8 天内 88 个碰语言包的提交,84 个同时改了全部十包,86/88 由 fleet bot 提交。
  • 真正的缺口不是「谁翻、何时翻」,而是没有一份正式的语言清单:UI 十种(短码 zh)、元数据四种(zh-CN)、邮件模板底座 en-US、cloud AI 文案两种(en/zh)、营销站 zh-Hans、mobile 三种(zh);Console 的语言菜单实际只列应用 supportedLocales 声明的(showcase = 2 种),十包在多数部署里根本不可达。
  • 推荐 E:在 spec 立一份带「等级」的权威语言表(full = 全面语言,ui = 仅界面语言),同 PR 跟随规则不变,现有门禁改读这一份表,⛔ 不走「英文先行 + 定期批量翻译」(B)—— 它与 en-drift 门禁自述的设计原则直接冲突(原文见下)。
  • 升级一种语言从 ui 到 full 是一次性批量 PR(AI 翻、按包一 PR,先例 PR feat(spec): a repeater property panel names its columns in every locale, not only English #19345 +1839/−25),凭实测拉动另立卡,不是周期性任务。
  • 请回一个字母:E(推荐)/ A / B / C / D。

背景

维护者问:「objectstack 国际化有规范吗?比如默认只写英文,其他语言定期按照规则国际化?」并要求「立一张决策卡,使用 fable 综合评估整个项目国际化的长期方案」。本卡逐面实测现行制度(谁、何时写哪种语言,CI 强制什么),列出缺口,给出长期方案选项与推荐。所有数字来自命令实测,量不到的地方明说。

现行制度,逐面实测(2026-11-02,objectstack origin/main = 5780fa49,cloud = 21a54a11,mobile = 8c6f13a9)

面 语言 谁写、何时写 CI 强制
Console UI 字符串 ui/packages/i18n/src/locales/ 10:en zh ja ko de fr es pt ru ar(短码;ar RTL,rtl.ts)。十包源码共 2,863,011 字节(zh 231 KB … ru 371 KB) 同 PR、AI 写。并入本仓(2026-10-25 subtree 导入 fd408314)后至今 88 个提交碰语言包:84 个同时改十包,3 个只改一包(全是 zh 措辞修正),1 个是导入本身;每提交语言包行数中位 +30(最小 0,最大 468);作者 86 × fleet bot、2 × 人工。同期 ui/ 共 627 提交 ⇒ 14% 的 UI 提交碰语言包,其中 95% 十包齐改 all-locales-key-parity.test.ts(每包全 en key、无多余)、ui/scripts/check-i18n-call-site-keys.mjs(ci.yml:3611)、check-i18n-en-drift.mjs(ci.yml:3614;豁免账本 6 条,WAIVER_CEILING = 6)、check-i18n-dead-keys.mjs、check-eager-locale-catalogues.mjs。__tests__/ 97 个测试文件,其中 63 个是按 issue 编号钉措辞的(= 落地后被发现的措辞缺陷数下限)
平台元数据翻译 packages/**/translations/*.generated.ts 4:en zh-CN ja-JP es-ES。10 个目录(platform-objects + 5 plugins + 4 services;AGENTS.md 写「nine packages … three services」,实测四个 services,文档漂移)。en 生成叶子合计 3,648(platform-objects objects 1,591 + metadata-forms 1,193 + 其余 8 包 864),手写 apps 段 en 136 / zh-CN 129 / ja-JP 127 / es-ES 128 pnpm i18n:extract … --locales=zh-CN,ja-JP,es-ES --fill=default --source-hashes:缺口先用英文原文填,旁写 source-hash 伴生文件(30 个);译文由 AI 手写(#17508)。zh-CN/ja-JP/es-ES 与 en 逐字相同的叶子:objects 16/17/23,metadata-forms 0/0/0,且每条都记在 objects-*-echo-decisions.test.ts 账本里 check:i18n(lint.yml:6363)、check:i18n-coverage(:6380;基线 showcase 320、todo 106、crm 0、平台包全 0)、check:i18n-walk-parity(:6403)、check:app-nav-i18n(:6423)、check:i18n-stale-fill(:2385;基线 {},UNSERVED_PROVENANCE = {})
客户应用元数据 应用自己声明 i18n.supportedLocales:app-crm ['en','zh-CN']、app-showcase ['en','zh-CN']、app-todo ['en','zh-CN','ja-JP'] 应用作者 os lint(i18n/missing-* 默认 warning,--i18n-strict 升 error,packages/cli/src/commands/lint.ts:834);仓内 check:i18n-coverage 单向棘轮冻结示例债
服务端消息与邮件 内置邮件模板 8 个 × en-US/zh-CN/ja-JP/es-ES;底座 EMAIL_TEMPLATE_FLOOR_LOCALE = 'en-US'(email-template.zod.ts:63)。按收件人 sys_user.locale 投递(#13881)。REST:extractLocale( 15 处调用、Accept-Language 读取 67 处、i18n.t( rest 1 / services 12 / plugins 2 同 PR 走 check:i18n 四语言同步;无针对错误消息的 i18n 门禁
文档站 仅 en:apps/docs/lib/i18n.ts languages: ['en'],注释「English-only by decision (2026-07)」;0 个翻译 mdx — check:docs-locale-catch-all 只守路由,不守内容
Cloud service-tenant 翻译 en/zh-CN/ja-JP/es-ES(objectstack.config.ts 声明同四种;zh-CN 459 叶);service-ai 文案表 EN_* / ZH_* 两种(authoring-copy.ts、chart-copy.ts);营销站 MARKETING_SITE_LOCALES = ['zh-Hans'];控制面邮件英文字面(purge-warning-email.ts:54);AI 轮次语言 = Accept-Language 否则工作区 localization.locale(cloud#3072) 同 PR 无:cloud AGENTS.md 零 i18n 条目、package.json 零 i18n 脚本、.github 零 i18n 门禁;仅 cloud-control.translation.test.ts 一个测试。open 缺陷 objectstack-ai/cloud#2988:AI 写的讨论行在 zh-CN 工作区是英文
Mobile 3:en zh ar,各 25 键;detectDeviceLanguage 跟手机,不支持即 en — __tests__/lib/i18n.test.ts 钉的是「缺 key 回落英文」与「含 en/zh/ar」,无 key 对齐门禁。objectstack-ai/mobile#159 要求对齐 Console 十种;其 Blocked-by: mobile#150 已 closed,卡已解封

语言代码与可达性(缺口的根)

  • 同一语言四种拼法:UI 包键 zh,元数据 zh-CN,邮件底座 en-US,营销站 zh-Hans,mobile zh。桥接靠两处回退:matchLanguageTag(registry.ts:112,区域码→基码)与 resolveLocale(memory-i18n.ts:47,精确→忽略大小写→基码)。open 缺陷 i18n: a translation saved with locale 'zh-cn' answers 200 but is never merged for a zh-CN session — #25822 left case unjudged on a runtime case-fold that does not happen #28993:存成 zh-cn 的翻译 200 但永不合并进 zh-CN 会话。
  • Console 语言菜单 = GET /api/v1/i18n/locales ∩ 本地可解析(provider.tsx:784 offerableLanguages);该端点答应用声明的 supportedLocales,未声明时答已加载 bundle 的语言(file-i18n-adapter.ts:244)。⇒ showcase 部署菜单只有 en/zh-CN 两项;未声明的部署四项;ko/de/fr/pt/ru/ar 六包只有在应用自己声明时才可达,而一旦声明,内置对象/字段标签全部回落英文(元数据只有四种)。

Governing text:

  • AGENTS.md:647(objectstack):「Translated-locale values (zh-CN / ja-JP / es-ES) are hand-written: editing one of those strings is fine, adding or dropping keys is drift. pnpm check:i18n gates all nine in CI, and pnpm check:i18n-coverage ratchets untranslated declared labels.」
  • ui/scripts/check-i18n-en-drift.mjs 头注:「When an en string CHANGES, the other nine packs must change with it.」「That is the invariant both defects violated at birth, and it is checkable exactly once — in the PR that edits en, while the author still knows what the sentence is supposed to say. A day later it is archaeology.」「scripts/i18n-en-drift-baseline.json. It ships EMPTY, and unlike objectui#3547's baseline it can never hold a backlog, because this gate has no backlog to hold: it judges an event in a diff, not a state in the tree.」
  • ui/scripts/i18n-en-drift-baseline.json note:「This file ships EMPTY and holds no backlog … So there is nothing to write down here at landing.」
  • content/docs/protocol/kernel/i18n-standard.mdx:243-247:「Every locale code (a bundle key, an item's locale, i18n.defaultLocale, supportedLocales and fallbackLocale) is a BCP-47 language tag with - between subtags (en, zh-CN, zh-Hans-CN). LocaleSchema refuses anything else at parse」;同文末尾:「Planned (design intent, not yet implemented): … translation-service auto-translate hooks.」
  • scripts/check-i18n-coverage.mjs:11-15:「Turning it on as-is would paint CI red on day one and get switched back off, which is how a gate stops being a gate. This is the shippable middle: the debt is FROZEN, and the build fails the moment it grows.」
  • apps/docs/lib/i18n.ts:「The docs are English-only by decision (2026-07).」
  • [Decision] Repeater row-property localisation: 604 catalog leaves across four locales — who authors the translated copy? #17508 裁决(2026-09-12,维护者原话):「17508 A AI负责翻译就行。其他同意」⇒「AI authors the translated copy … in the same PR; quality bar: a native-reading column head, consistent with the existing terms in each locale's catalog」。
  • 「默认只写英文、定期国际化」的规范:none found。检索式:git grep -n -i -E 'i18n|locale|translat|国际化|多语言' origin/main -- AGENTS.md ui/AGENTS.md(objectstack,命中 4 行,均为上文),cloud AGENTS.md 同式 0 命中,mobile 无 AGENTS.md;gh api -X GET search/issues -f q='repo:objectstack-ai/objectstack is:issue "机器翻译" OR "machine translation" OR "machine-translated"' 14 命中,无一裁「定期批翻」。

协议声明 / 是否改协议

  • spec 已声明:TranslationConfigSchema(translation.zod.ts:2416)的 defaultLocale / supportedLocales / fallbackLocale,LocaleSchema 只收 BCP-47 - 拼法(18.x 转换 translation-locale-tag-canonicalized);II18nService.setSupportedLocales + normalizeSupportedLocales(i18n-resolver.ts:3693)。spec 没有声明「平台自身支持哪些语言」——没有任何 PLATFORM_LOCALES 之类的符号;十包清单住在 ui/packages/i18n/src/locales/registry.ts,四语言清单住在 package.json 的 --locales= 参数里。
  • 本卡裁的是制度与一份新声明(平台语言表及其等级),不改既有 LocaleSchema/TranslationConfigSchema 形状。EMAIL_TEMPLATE_FLOOR_LOCALE = 'en-US' 与 UI en 的拼法分歧是改协议事项,⛔ 不作本卡选项,裁后另立卡。

前提(带 re-check 命令)

  1. UI 十包、元数据四种,键对齐同 PR 强制 —— git ls-tree --name-only origin/main ui/packages/i18n/src/locales/;git ls-tree -r --name-only origin/main | grep -E 'translations/[^/]+\.generated\.ts$' | xargs -n1 basename | sed -E 's/\..*//' | sort | uniq -c;git grep -n 'check:i18n-drift\|check:i18n-keys' origin/main -- .github/workflows/ci.yml。
  2. 并入后 88 个语言包提交中 84 个十包齐改、86 个由 bot 提交 —— gh api --paginate "repos/objectstack-ai/objectstack/commits?path=ui/packages/i18n/src/locales&since=2026-09-03T12:18:36Z&per_page=100",逐 sha gh api repos/objectstack-ai/objectstack/commits/<sha> --jq '[.files[].filename|select(test("locales/(en|zh|ja|ko|de|fr|es|pt|ru|ar)\\.ts$"))]|length'。(并入前 objectui 仓历史未测:本次无权读该仓。)
  3. en-drift 账本 6 条、上限 6 —— git show origin/main:ui/scripts/i18n-en-drift-baseline.json | jq '.waivers|length';git grep -n 'WAIVER_CEILING =' origin/main -- ui/scripts/__tests__/check-i18n-en-drift.test.ts。
  4. 元数据用英文填缺口并记 source-hash,stale-fill 基线为空、全部 bundle set 读伴生文件 —— git show origin/main:package.json | grep '"i18n:extract"';git show origin/main:scripts/i18n-stale-fill-baseline.json;git grep -n 'const UNSERVED_PROVENANCE' origin/main -- scripts/check-i18n-stale-fill.mjs;git ls-tree -r --name-only origin/main | grep -c 'source-hashes.generated.ts$' → 30。
  5. 语言菜单 = 应用声明 ∩ 可解析 —— git grep -n -A10 'const offerableLanguages' origin/main -- ui/packages/i18n/src/provider.tsx;git grep -n -A5 'getLocales(): string\[\]' origin/main -- packages/services/service-i18n/src/file-i18n-adapter.ts;git grep -n supportedLocales origin/main -- 'examples/*/objectstack.config.ts'。
  6. 文档仅英文、cloud 无 i18n 门禁、mobile 三语无对齐门禁 —— git show origin/main:apps/docs/lib/i18n.ts | head -20;(cloud)git show origin/main:package.json | grep -c i18n → 0,git grep -c -i i18n origin/main -- .github → 0;(mobile)git ls-tree --name-only origin/main locales/,git show origin/main:__tests__/lib/i18n.test.ts | grep -n 'it('。
  7. 邮件底座 en-US,内置模板四语言各 8 —— git grep -n 'EMAIL_TEMPLATE_FLOOR_LOCALE =' origin/main -- packages/spec/src;git grep -o -E "locale: '[a-zA-Z-]+'" origin/main -- 'packages/plugins/*/src/**/*.ts' 'packages/services/*/src/**/*.ts' 'packages/platform-objects/src/**/*.ts' ':!**/*.test.ts' ':!**/translations/**' | awk -F'locale: ' '{print $2}' | sort | uniq -c。
  8. 先裁已定「AI 写译文、同 PR 落」—— gh api repos/objectstack-ai/objectstack/issues/17508/comments --jq '.[0].body' | head -5。
  9. 无同题 open 决策卡;mobile#159 的阻塞卡已关 —— gh api -X GET search/issues -f q='repo:objectstack-ai/objectstack is:issue is:open label:needs-user-decision i18n OR locale OR translation' → 0;gh api repos/objectstack-ai/mobile/issues/150 --jq .state → closed。

一句话问题

一个韩文/德文/法文/葡文/俄文/阿拉伯文用户在 Console 里看到按钮和菜单是母语,但对象名、字段名、邮件通知、AI 回复全是英文(或根本切不到那门语言),而没有任何地方写明哪些语言是「平台正式支持」、哪些只是「界面有翻译」。

选项 × 真实代价 × 工作量

选项 做什么 客户可感知后果 工作量(车道 · 阶段/PR · 先例)
A 同 PR 到底,元数据扩到十种 现行规则不动;i18n:extract --locales 加 ko/de/fr/pt/ru/ar,十个包 + 邮件模板 + cloud service-tenant 全部补齐六种;mobile 补七种 十种语言在每个面都完整;元数据层每条新字段从 4 份译文变 10 份,每个碰元数据的 PR 多 6 个生成文件 domain:spec/metadata × 10 包各 1 PR + cli 1 PR + cloud 1 PR + mobile#159;新译叶 6 × (3,648 + 136) = 22,704(objectstack)+ 8 × 6 邮件模板 + 459 × 6(cloud)。先例 PR #19345(604 叶、4 语言、AI 写)+1839/−25 ⇒ 粗估 6–7 万行译文;生成包同步先例:i18n:extract 现成,加语言无先例
B 英文先行 + 定期批翻(维护者所提) 新 key 只写 en;其余语言允许缺;一条可见账本记未翻 key;按周/月由 agent 车道批量补 两次批翻之间,非英文用户看到混语界面;改 en 不跟译文的 objectui#3582/#3625 类缺陷重现(八包继续说旧句子) domain:ui:重写 all-locales-key-parity.test.ts 为带基线的棘轮、新账本、en-drift 账本改成可积压(与其设计原则相反)、新增定时车道;无先例(仓内所有棘轮都是「冻结旧债、禁止增长」,没有「允许增长、定期清零」的)
C 混合:同 PR 保键结构 + 机器填值标「machine/unreviewed」+ 定期人工/agent 复审升级 键缺即红不变;值可先机器填,带来源标记;复审通过后去标 用户立刻看到母语(机翻质量),复审前后措辞可能变;标记对用户不可见 元数据层已有一半(--fill=default 英文填 + source-hash 伴生 + echo 账本 + stale-fill,先例 PR #8877 +1097/−13、PR #12067 +541/−1);UI 层无任何来源机制:十包各加伴生表 + 新门禁(④ 新增门禁默认否)+ 复审车道;但今天「机器」就是同 PR 里的 AI dev(#17508 已裁),C 实际只多一个「未复审」标记与一条复审车道;先例仅元数据半边
D 收缩:正式语言减到四种(en zh-CN ja-JP es-ES),其余六包转社区包 删 ko/de/fr/pt/ru/ar 六包(−1.7 MB 源码),parity 测试改四包,mobile 对齐四种;社区包走 config.resources / loadLanguage 已有门 六种语言用户失去界面翻译;每个 UI PR 从改 10 文件降到 4 domain:ui 1–2 PR(删除先例 PR #26158 +1126/−1078、92 键十包同删)+ mobile 1 PR;未计:README/mobile#159/营销承诺的「10 built-in locales」回收
E 一份清单、两个等级、同 PR 规则不变(推荐) spec 导出平台语言表:每行 { tag: 'zh-CN', pack: 'zh', tier: 'full'|'ui', rtl, label };full = en zh-CN ja-JP es-ES(今天各面已完整的四种),ui = ko de fr pt ru ar(仅 Console 字符串);现有门禁改读这张表(parity 读十包、i18n:extract --locales 读 full、mobile 测试读全表);语言菜单对 ui 级标「界面语言」;升级一门语言 = 凭实测拉动另立卡 + A 式一次性批量 PR 用户看到的今天不变,但产品能诚实地说「正式支持 4 种,界面另有 6 种」;升级后该语言在每个面同时完整 domain:spec 1 PR(表 + 文档;无先例,最近邻 PR #15386 +697/−16「一种拼法」)+ domain:ui 1 PR(registry/rtl/parity/README 改读表;量级 PR #25978 +682/−30)+ domain:cli 1 PR(extract/bundles 读 full)+ mobile#159 原卡(十种,按表)+ cloud 1 卡(service-ai 文案 en/zh → full 四种,cloud#2988 同车道);不新增门禁

业务含义直译

  • A = Salesforce「Fully Supported Languages」一刀切:十种语言全是正式语言,每条新字段十份译文——相当于把本地化团队的工作量按 2.5 倍常设。
  • B = 传统 TMS/Crowdin 流:开发只写源语言,翻译按 sprint 批处理——代价是发布窗口里的混语界面,与我们已经为此修过两次的缺陷类重逢。
  • C = 机翻先上线、人工后审(DeepL + 复审队列)——在元数据层我们已经做了一半,在 UI 层等于给今天的 AI 译文再加一个「待审」章,章盖给谁看是问题。
  • D = 砍到四种正式语言,其余「社区维护」——Odoo 式,省 PR 成本,但收回已经写进 README 和 mobile 需求的「10 built-in」承诺。
  • E = Salesforce 的三档语言模型(Fully supported / End-user / Platform-only)、ServiceNow 的「语言插件」:正式支持是一张公开表,每门语言的档位写明,升档是一次性项目——我们用一张 spec 表把今天事实上的两档写成契约。

四轴(业务立场)

① 项目长远合理性 ≥50%。 今天的特例是五张互不相知的清单(UI 10 / 元数据 4 / 邮件 en-US 底座 / cloud 2 / mobile 3)外加四种拼法,每条桥接(matchLanguageTag、resolveLocale 三段回退、preferredLocaleFromHeader)都是一次契约增生,#28993 的 zh-cn 永不合并就是最新一例。A 和 D 都把清单归一(十或四),E 把清单归一并把档位写成声明——这是主流平台两年后的样子(Salesforce 语言分档、ServiceNow 语言插件、i18next 的 supportedLngs 一处声明);B 和 C 不动清单,只动流程,特例数不变。E 缩小特例最多且不增生契约(它把两个已存在的事实写成一行表,而不是新造能力)。

② 实际业务拉动。 实测拉动只在四种:示例应用声明 en/zh-CN(todo 加 ja-JP),cloud 托管应用未声明(⇒ 菜单四种),hotcrm 发布四种(#13881 实测),mobile 三种。ko/de/fr/pt/ru/ar 零部署证据——六包存在是 objectui 并入的历史,不是客户拉的。按「零拉动 ⇒ 荐不扩散」,A 把 22,704 条无人读的译文变成永久义务,不取;D 删六包是对零拉动的直接回应,但它销毁的是已发布面(README「10 Built-in Locales」、mobile#159 维护者原话「看一下 objectstack 官方支持那些语言,要能对应上」),且 UI 层维持六包的边际成本已实测很低(每 PR 中位 +30 行、全由 bot 写)。E 让六包留在 ui 档等拉动,升档需证据——与 ② 同向。

③ 防 AI 犯错。 今天 UI 层的强项正是「响亮拒绝」:缺一个 key、en 改一句不跟,PR 当场红;十包的措辞缺陷靠 63 个事后钉子兜底,说明值的质量没有门禁,但结构有。B 把这两条拒绝改成「允许积压」,是把 objectui#3625(八包齐整、全绿、却是过期句子的母语译文)重新请回来——en-drift 头注写得很清楚:「checkable exactly once — in the PR that edits en … A day later it is archaeology」。C 的「unreviewed」标记若无消费者就是静默标签(谁看?菜单不显示,门禁不读),出错时用户看到的是机翻句子而没人知道它未审。E 下出错方式是:一门语言声明 full 却在某包缺 bundle ⇒ check:i18n/parity 红(现有门禁改读表后自动覆盖);声明 ui 的语言元数据回落英文是声明过的行为,不再是静默泄露。D 同样闭合但以删除换取。

④ 创业阶段不扩散。 A 是最大扩散(六门语言 × 十包 × 永久跟随);B 新增账本 + 定时车道 + 改两个门禁为可积压;C 新增来源表与一条新门禁(默认否,维护者未点名);D 是最小表面但要收回公开承诺;E 不新增门禁、不新增语言、不删语言,只把「哪些语言算正式」从 package.json 参数和 registry.ts 常量搬进 spec 一张表,现有门禁改读它。E 是四轴里唯一同时满足「不扩散」和「不收回承诺」的。

os-decision-facets
① 项目长远合理性:E 把五张互不相知的语言清单与四种拼法收成 spec 一张带档位的表,现有门禁改读它;特例从 5 缩到 1,契约零增生;A/D 归一但分别扩散/销毁,B/C 清单不动。
② 实际业务拉动:实测只有 en/zh-CN/ja-JP/es-ES 四种有部署(示例、cloud、hotcrm、mobile);ko/de/fr/pt/ru/ar 零部署证据 ⇒ 不扩元数据(否 A),六包留 ui 档等拉动;升档凭证据另裁。
③ 防 AI 犯错:保留「缺 key / en 不跟即红」的响亮拒绝(否 B 的可积压账本);full 档缺 bundle 由现有 check:i18n/parity 红;ui 档元数据回落英文成为声明过的行为而非静默泄露;C 的「unreviewed」标无消费者即静默。
④ 创业阶段不扩散:E 不新增门禁、不新增语言、不删语言;A 扩散 22,704 叶永久义务;B 新增账本+定时车道;C 新增门禁(默认否);D 最小但收回 README/mobile#159 的「10 built-in」承诺。
Prior rulings read: i18n Ruled → 1243 hits(读 6:#17508 A「AI负责翻译就行」· #12069 A source-hash 伴生扩到生成包 · #8765 B source-hash · #15711 默认语言文本=作者标签 · #14788 D 一种拼法 · #13881 sys_user.locale);"[Decision]" locale in:title → 6 hits;ADR none(147 篇无 i18n 标题;ADR-0098 拼音列、ADR-0012/0030 模板 topic×channel×locale 为相关);thread: none

推荐 + 置信缺口

推荐 E。 两年后的样子:spec 导出一张平台语言表,每门语言一行写明档位(full / ui)、BCP-47 标签、UI 包基码、RTL;Console 语言菜单、i18n:extract、邮件模板种子、mobile 壳和 cloud 文案表都从这一行读;「正式支持 N 种语言」在文档里是从表生成的句子而不是手写数字——对标 Salesforce 的 Fully-supported / End-user / Platform-only 三档与 ServiceNow 的语言插件清单,区别只在我们的译文由 AI 在同一 PR 写(#17508 已裁),所以没有 Crowdin 式的翻译队列,也不需要。

对维护者原问的回答写进规范:「默认只写英文」不成立,默认是「写 en 的同一 PR 把该语言档位要求的每一份译文一并写完」;「定期国际化」只存在于升档——一门语言从 ui 升 full 是一次性批量 PR,不是周期任务。

其余各一句不取:A 为零拉动的六种语言背 22,704 叶永久义务;B 与 en-drift 门禁的设计原则(「can never hold a backlog」)正面冲突且重开 objectui#3625 类缺陷;C 在 UI 层增一张没人读的来源表和一条维护者未点名的新门禁;D 收回已发布的「10 built-in」承诺,而维持六包的边际成本实测很低。

置信缺口(明说,不另荐):(1) 并入前 objectui 仓的语言包历史未测,「95% 十包齐改」只覆盖并入后 8 天 88 个提交;(2) ko/de/fr/pt/ru/ar「零部署」是仓内与 cloud 配置的读数,未查 cloud 生产库里各工作区的 localization.locale;(3) 63 个措辞钉子是缺陷数下限,ja/ko/ar 的译文质量没有任何人工复审记录(两次人工修正都是 zh),E 不解决质量问题,只把它限定在声明过的档位里;(4) E 的表若放在 spec,ui 档六包的基码→标签映射(ko→ko-KR?)需要在落表时定,本卡不替维护者定区域码。

自检行

只看①选 E;②③④ 是否翻转:否。

裁后执行段(裁 E 时)

  1. objectstack · domain:spec(1 卡,p2):packages/spec/src/system 导出平台语言表(建议符号 PLATFORM_LOCALES,行形 { tag, pack, tier: 'full'|'ui', rtl, label };full = en zh-CN ja-JP es-ES,ui = ko de fr pt ru ar;ui 档区域码由维护者在该卡上定);i18n-standard.mdx 加档位表与「同 PR 跟随、升档另裁」两句规范;AGENTS.md:647 顺手改「nine packages / three services」为实测的十目录/四 services。前提:spec 中尚无此符号(git grep -n PLATFORM_LOCALES origin/main -- packages/spec/src → 0)。
  2. objectstack · domain:ui(1 卡,p2,Blocked-by: 上卡):locales/registry.ts、rtl.ts、all-locales-key-parity.test.ts、README 的「10」改读表;语言菜单给 ui 档一个「界面语言」提示(文案由该卡写,十包同 PR)。⛔ 不新增门禁,只改现有门禁的读数来源。
  3. objectstack · domain:cli(1 卡,p3,Blocked-by: 1):package.json i18n:extract --locales= 与 scripts/check-i18n-bundles.mjs 从表的 full 档读;邮件模板种子的语言集同源。EMAIL_TEMPLATE_FLOOR_LOCALE = 'en-US' 与表的 en 拼法是否统一 ⇒ 改协议,单独立决策卡,不在此卡。
  4. Languages: follow the phone's language in the platform's 11 locales (ruling F of objectstack#29349) mobile#159(已解封,按表执行):壳的语言集 = 表全部十行,代码取 pack 列;补 key 对齐测试(壳内三包→十包,量 25 键 × 7)。
  5. cloud(1 卡,p3):service-ai 的 EN_*/ZH_* 文案表扩到 full 四档,objectstack-ai/cloud#2988 并入同车道;控制面邮件是否本地化由 cloud 产品另裁,本卡不涉。
  6. 升档流程写入 pm-dispatch 触发条件:「某语言出现实测部署(工作区 localization.locale / 应用 supportedLocales 声明)」⇒ 立 [Decision] 升档 <语言> 卡,裁后按 A 的形状一次性批量 PR(每包一 PR,AI 写,先例 PR feat(spec): a repeater property panel names its columns in every locale, not only English #19345)。

裁 A:执行 1、3 后按包 10 PR + cloud + mobile;裁 B:domain:ui 立「parity 改棘轮 + 账本 + 定时车道」卡并先撤 en-drift 头注的两句原则(改协议卡);裁 C:元数据半边无事,UI 半边需维护者点名新增门禁后立卡;裁 D:domain:ui 删六包卡(先例 PR #26158)+ mobile#159 改为四种 + README/营销面回收卡。

相关单与 PR

Analysis-tier: claude-fable-5-1

Activity

  1. zhuangjianguo commented on Nov 2, 2026

    @zhuangjianguo
    Collaborator

    Brief-claimed-by: session_01QsR2jV5sBSzCYNEDgpYLJw · batch #457 · 2026-11-02T14:27Z


    Generated by Claude Code

  2. hotlong commented on Nov 2, 2026

    @hotlong
    ContributorAuthor

    新增选项 F(单档一步到位)与维护者裁定

    Ruled: F — 维护者 2026-11-02 会话内原话:

    「国际化为什么不一步到位,设计好平台应该支持的语言。为什么要分两种」
    「同意,写进决策卡,cloud 价格页也开卡,官网也补全这些语言」
    「mobile 也需要支持这些语言」

    选项 F

    在 spec 里定一份平台语言表,只有一档:表内每种语言在 Console 界面、平台元数据、内置邮件、cloud、mobile 和官网都完整。新增语言就是改表,再一次性批量补齐;删语言同理。同 PR 跟随规则不变。现有门禁改为从这张表读语言列表,不新增门禁。

    为什么不要 E 的两档: 译文本来就是 AI 在同一 PR 里写的(#17508)。补齐只是一次性成本,大约 10–12 个 PR、AI 写。两档的代价却是永久的:多一个「档位」概念,语言菜单要加提示,客户要理解「支持」分两种,还要维护升档流程。为省一次性成本而背一个永久特例,不划算(④)。

    语言表(11 种)

    依据全球使用人数,再对照企业软件市场。官网 objectstack-ai/www.objectos.ai 的 src/lib/i18n.ts 已有其中 8 种。

    语言 标签 来源
    English en 源语言
    简体中文 zh-Hans 官网 / UI / 元数据
    繁體中文 zh-Hant 官网有;UI 和元数据缺。可由 zh-Hans 生成(官网先例 pnpm gen:zh-hant)
    Español es 官网 / UI / 元数据
    العربية ar UI 有(RTL 已完成);官网和元数据缺
    Français fr 官网 / UI
    Português (Brasil) pt-BR UI 有;官网和元数据缺
    Русский ru UI 有;官网和元数据缺
    Deutsch de 官网 / UI
    日本語 ja 官网 / UI / 元数据
    한국어 ko 官网 / UI

    暂不纳入印地语、孟加拉语、乌尔都语、印尼语:这些市场的企业软件通常直接用英文。按「改表 + 一次性补齐」流程,有部署再加。

    标签写法:

    • 中文按文字区分(zh-Hans / zh-Hant),不按区域。区域码判断不了繁简,例如新加坡用简体、香港用繁体。
    • 葡萄牙语用 pt-BR,巴葡和欧葡差异明显。
    • 其余用基础码。
    • 现有的 zh / zh-CN / zh-TW / ja-JP / es-ES / en-US 等写法作为读入别名保留,已存的 sys_user.locale 与 bundle 不失效;新写入一律用表内标签。

    四棱(F)

    os-decision-facets
    ① 项目长远合理性:F 把五张语言清单与四种拼法收成 spec 一张单档表,所有面从它读;不引入档位概念,特例从 5 缩到 0,契约只增一张表。
    ② 实际业务拉动:官网已在推 8 种(含繁体),产品端缺繁体会让官网的繁体访客注册后看到简体或英文;维护者明确要求「要能对应上」,mobile 也要支持同一套。
    ③ 防 AI 犯错:表内任一语言在任一面缺 bundle 或缺 key,都由现有 check:i18n / parity 当场红,响亮拒绝;不存在「声明支持但只翻了一半」的静默状态。
    ④ 创业阶段不扩散:不新增门禁、不新增档位;一次性补齐约 2.3 万+ 元数据叶,由 AI 批量 PR 完成;之后每个改元数据的 PR 多写约 7 份译文(由 AI 在同一 PR 写)。
    Prior rulings read: 同本卡正文;thread: 本会话

    自检:只看①选 F;②③④ 是否翻转:否。

    裁后执行(已立卡)

    • objectstack:平台执行总卡(spec 语言表 → Console 加 zh-Hant 并改用表内标签 → 元数据与内置邮件补齐到 11 种 → 文档),见下条评论里的卡号。
    • objectstack-ai/cloud:价格页跳转语言与官网对齐;service-tenant 翻译与 service-ai 文案补齐到 11 种(objectstack-ai/cloud#2988 并入)。
    • objectstack-ai/www.objectos.ai:官网补齐 11 种,包括营销页、术语表和博客的现有缺口。
    • Languages: follow the phone's language in the platform's 11 locales (ruling F of objectstack#29349) mobile#159:改为 11 种,用表内标签。

    Generated by Claude Code

  3. hotlong commented on Nov 2, 2026

    @hotlong
    ContributorAuthor

    Execution cards for ruling F:

    Still to file: cloud service-tenant translations and service-ai copy to 11, folding in objectstack-ai/cloud#2988. That happens once phase 1 of #29428 exports the table.

    needs-user-decision removed: the card is ruled. Close it when #29428 phase 1 lands.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:i18nThe customer's own language, across UI, metadata and notificationsdomain:specpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions