Repository navigation
Description
维护者速读
- 对「默认只写英文、其他语言定期按规则国际化」的直接回答:今天没有这条规范,现行制度恰好相反 —— Console 十个语言包和平台元数据四个语言包都要求同一 PR 跟随(
all-locales-key-parity.test.ts缺 key 即红、check-i18n-en-drift.mjsen 改值九包不跟即红、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,mobilezh。桥接靠两处回退: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:784offerableLanguages);该端点答应用声明的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:i18ngates all nine in CI, andpnpm check:i18n-coverageratchets untranslated declared labels.」ui/scripts/check-i18n-en-drift.mjs头注:「When anenstring 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 editsen, 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.jsonnote:「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'slocale,i18n.defaultLocale,supportedLocalesandfallbackLocale) is a BCP-47 language tag with-between subtags (en,zh-CN,zh-Hans-CN).LocaleSchemarefuses 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 行,均为上文),cloudAGENTS.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'与 UIen的拼法分歧是改协议事项,⛔ 不作本卡选项,裁后另立卡。
前提(带 re-check 命令)
- 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。 - 并入后 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",逐 shagh 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 仓历史未测:本次无权读该仓。) - 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。 - 元数据用英文填缺口并记 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。 - 语言菜单 = 应用声明 ∩ 可解析 ——
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'。 - 文档仅英文、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('。 - 邮件底座
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。 - 先裁已定「AI 写译文、同 PR 落」——
gh api repos/objectstack-ai/objectstack/issues/17508/comments --jq '.[0].body' | head -5。 - 无同题 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 时)
- 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)。 - objectstack · domain:ui(1 卡,p2,Blocked-by: 上卡):
locales/registry.ts、rtl.ts、all-locales-key-parity.test.ts、README 的「10」改读表;语言菜单给ui档一个「界面语言」提示(文案由该卡写,十包同 PR)。⛔ 不新增门禁,只改现有门禁的读数来源。 - objectstack · domain:cli(1 卡,p3,Blocked-by: 1):
package.jsoni18n:extract --locales=与scripts/check-i18n-bundles.mjs从表的full档读;邮件模板种子的语言集同源。EMAIL_TEMPLATE_FLOOR_LOCALE = 'en-US'与表的en拼法是否统一 ⇒ 改协议,单独立决策卡,不在此卡。 - Languages: follow the phone's language in the platform's 11 locales (ruling F of objectstack#29349) mobile#159(已解封,按表执行):壳的语言集 = 表全部十行,代码取
pack列;补 key 对齐测试(壳内三包→十包,量 25 键 × 7)。 - cloud(1 卡,p3):service-ai 的
EN_*/ZH_*文案表扩到full四档,objectstack-ai/cloud#2988 并入同车道;控制面邮件是否本地化由 cloud 产品另裁,本卡不涉。 - 升档流程写入
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
- Languages: follow the phone's language in the platform's 11 locales (ruling F of objectstack#29349) mobile#159(对齐 Console 十种;
Blocked-by: mobile#150已 closed)· [Release gate] Mobile connection flow v1: the maintainer closes this card to release its cards for dispatch mobile#150(closed) - [Decision] Repeater row-property localisation: 604 catalog leaves across four locales — who authors the translated copy? #17508(裁:AI 写译文,同 PR;PR feat(spec): a repeater property panel names its columns in every locale, not only English #19345 +1839/−25)· [Decision] 把 #8765 的 source-hash sidecar 扩展到生成的 i18n bundle —— #9672 写明的升级条件已满足 #12069(裁 A:source-hash 伴生扩到生成包)· i18n: a source-string edit still leaves zh-CN / ja-JP / es-ES silently stale — and pinning
ento the source makes the asymmetry sharper, not smaller (needs a maintainer decision) #8765(裁 B;PR fix(platform-objects): stale hand-authored translations fall back to the source string (#8765) #8877 +1097/−13)· [finding]check:i18nverifies key presence, not that an untranslated leaf still matches the source string it was filled from — and the drift is sticky #11671 / PR feat(gate): detect translation leaves stranded on a previous source revision #12067(+541/−1,stale-fill 门禁)· [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 checksays the former,localeChaindoes the latter; the resolver's literal['en']default rides on the answer #15711(默认语言文本;PR feat(spec,rest): the authored label is the default locale's text — ResolveOptions.defaultLocale skips the fallback chain, and a chain-less caller no longer falls to a literal en (#15711) #15888 +318/−83)· [Decision] One spelling for the signed-in user's language on the published surface —SessionUserSchema.language(unproduced),sys_user.locale(#13881) and/auth/me/localization.locale(request/deployment) coexist #14788(一种拼法;PR feat(spec,hono-server): retire SessionUser.language — /auth/me/localization is the one read face for the user's language (#14788) #15386 +697/−16)· [Decision] Per-user notification locale —— 2026-08-13 裁决所等的「实测拉力」已到(hotcrm:4 个已发布语言 × 16 个 notify 节点 × 0 可本地化) #13881(sys_user.locale,per-recipient 投递) - 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永不合并进zh-CN)· objectstack-ai/cloud#2988(AI 讨论行在 zh-CN 工作区是英文) - objectui#3650 / Template tokens in filter values and notification templates are not evaluated at runtime #3582 / feat(client): close the 9 metadata-family REST gaps (#3587 batch 1/5) #3625(en-drift 门禁与它修的两个缺陷)· objectui#7479(十包懒加载)· objectui#11326(区域码回落基码)· objectui#4039(
/i18n/locales驱动菜单) - PR chore(i18n): delete 92 message keys nothing reads from all ten locale packs #26158(+1126/−1078,十包同删 92 键)· PR fix(i18n): localise the console's round-3 leftovers — data table titles, chart fallback, react pages, References columns, Flow Runs diagnostics #25978(+682/−30,Console 漏翻清扫)· PR feat(console): Setup → Integrations → Connector Credentials, and a credential step after a marketplace install #28200(+2493/−13,单 PR 语言包 +468 行的上限样本)
- 文档决定:
apps/docs/lib/i18n.ts「English-only by decision (2026-07)」
Analysis-tier: claude-fable-5-1
Activity
zhuangjianguo commentedon Nov 2, 2026
Brief-claimed-by: session_01QsR2jV5sBSzCYNEDgpYLJw · batch #457 · 2026-11-02T14:27Z
Generated by Claude Code
hotlong commentedon Nov 2, 2026
新增选项 F(单档一步到位)与维护者裁定
Ruled: F — 维护者 2026-11-02 会话内原话:
选项 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 种。enzh-Hanszh-Hantzh-Hans生成(官网先例pnpm gen:zh-hant)esarfrpt-BRrudejako暂不纳入印地语、孟加拉语、乌尔都语、印尼语:这些市场的企业软件通常直接用英文。按「改表 + 一次性补齐」流程,有部署再加。
标签写法:
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;②③④ 是否翻转:否。
裁后执行(已立卡)
zh-Hant并改用表内标签 → 元数据与内置邮件补齐到 11 种 → 文档),见下条评论里的卡号。Generated by Claude Code
hotlong commentedon Nov 2, 2026
Execution cards for ruling F:
zh-Hantand table tags, backfilling metadata and the built-in emails to 11, and the language menu./pricingredirect will reach every marketing-site locale. The current English target/pricinglikely has no page on the site, which serves/en/pricing/.pt-BR,ruandar(with RTL) and fills the existing gaps. Today pages and the glossary exist only in en and zh-Hans, and the blog is 45 of 68 posts in the other locales.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-decisionremoved: the card is ruled. Close it when #29428 phase 1 lands.Generated by Claude Code