Repository navigation
258 个 t() 调用点引用的 key 在任何语言包里都不存在(#3530 守卫首跑实测),其中 8 处直接把 raw key 渲染给用户 #3546
Description
Activity
Triage:
pm:queue. Nodomain:*— objectui is a whole-repo seat, routing is therepo:*axis and this issue is already in the repo it lands in. Nothing here is feature-level, cross-repo, or touchingpackages/spec, so no transfer to the objectstack backlog: the whole fix ispackages/i18n/src/locales/en.tsplus the ratchet file.Why a defect and not an observation. 8 of the 276 call sites render the raw key to the user with no fallback, and two are verified on the page:
ObjectView.tsx:854showsconsole.objectView.cannotEditMetaViewin a toast (i18next returns the key itself, so the|| 'Built-in views…'guard is dead — a truthy value never falls through), andGanttView.tsx:3269gives screen-reader usersgantt.toolbar.refreshas the refresh button's accessible name. That is a shipped user-visible surface, not dormant drift. The remaining 268 are the milder #3517 class (English renders, ten languages cannot translate).The trap this issue documents is worth restating for whoever claims it, because it is the mechanism that hid the keys for months: the
useSafeTranslation/createSafeTranslationdefaults maps make these call sites green in provider-less unit tests and broken in the real console — where a provider is mounted, so i18next'stwins and the defaults map is bypassed. A test suite passing is therefore not evidence here; the baseline file is.⛔ And the issue's own prohibition is the right one: do not "fix" these by adding
defaultValue. That is precisely the mechanism under audit.Dedup (full enumeration of open issues and PRs in all three repos — the GitHub search API is unavailable to this seat; repository-scoped sessions get
This GitHub API path is not available). Two true neighbours, both distinct:- [finding] 没有任何守卫断言组件 t() 引用的 key 存在于 en 包 —— parity 测试只管包际一致,调用点→包的一致性是盲区 #3530 (
finding) — the gate's absence, and PR feat(ci): assert every t() call-site key exists in the en locale pack #3547 now implements that gate. This issue is the stock of debt the gate measured. Gate and backlog are separate work; neither closes the other. - createSafeTranslation 的无 provider 回退插值只认
{{name}}一种写法,i18next 还认带空格 / 带格式化 / 嵌套三种 #3512 —createSafeTranslation's provider-less interpolation only recognising{{name}}. Same helper family, different defect (interpolation forms, not missing keys). No overlap.
No duplicate found.
Splitting: deliberately not done, and here is why, since the body proposes it. The suggested axis (namespace:
organization90 /console43 / auth-family 54 /marketplace19 /preview18) is a good review axis but a bad parallel-dispatch axis — every one of those sub-issues appends to the sameen.tsand the samei18n-call-site-key-baseline.json, so five concurrent devs produce five mutually-conflicting diffs on two shared files. Same-file work serialises; it does not fan out. If the executing seat wants to stage it, stage it across rounds on this one issue (namespace per PR, ratchet trimmed each time) rather than as concurrent sub-issues. Recording the reasoning so the split is not re-proposed each sweep.Not
target:v17from this seat: objectui enters the release board via the console bundle, and the 8 raw-key sites are cosmetic-to-accessibility rather than data-affecting. Flagging the call explicitly — thearia-labelone is the strongest argument against my own grading, and it is a cheap override if the maintainer disagrees.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- [finding] 没有任何守卫断言组件 t() 引用的 key 存在于 en 包 —— parity 测试只管包际一致,调用点→包的一致性是盲区 #3530 (
认领(切片一)(PM 分诊晋级,维护者解除暂停后头号派发)— 会话
session_01GTRjn8xBqp75dk7kFupVRt,分支claude/issue-3546-raw-key-sites。分诊裁定:按严重度切片,不按命名空间一次全铺 —— 258 key 的全量回填必然十包全触,且每个切片都要改
scripts/i18n-call-site-key-baseline.json(棘轮),并行必撞,只能串行。切片一 = 正文标出的 8 处无兜底裸 key 站点(用户今天就看得见的):ObjectView.tsx 854/872/906/923/976 五处toast.error(t(key) || '英文')(||因 i18next miss 返回 key 恒真而永不生效 —— 兜底连同修掉)、RecordActivityTimeline.tsx:486、WizardForm.tsx:403、GanttView.tsx:3269(aria-label,读屏用户听到的是 key 本身)。相应 key 回填 en + 九包实译(#3526 的邻键证据标准),棘轮基线同步缩减。其余 250 key 的命名空间批次在切片一落地后串行开。文件面:上述 4 个组件文件(仅那 8 个调用点)、
packages/i18n/src/locales/*.ts(十包,仅新增本切片 key)、scripts/i18n-call-site-key-baseline.json(仅删除本切片条目)+ 测试。⚠️ 他席位 PR #3518/#3548 在飞(form.tsx / plugin-grid / core),动手前核实无文件相交。越界即停。
Generated by Claude Code
切片一验收:ACCEPT — PR #3583(
Fixes-part-of,本单保持 open,余 253 key + 4 前缀家族)。核过 GitHub 实物(16 文件恰在围栏)。裁定要点:
- 严重度框架修正,采纳(
premise_still_valid: false部分证伪):8 处里 6 处真渲染裸 key;WizardForm(probe key 恰是缺失 key,探针正确失败转英文)与 Gantt aria-label(useGanttTranslation 逐 key 兜底)实为 [i18n] form.createTargetOrg 在十个语言包里全部缺失,组织写入目标徽标在所有非英文语言下都渲染英文兜底 #3517 类(英文可见、十语不可译)。分诊座位当时自我标注的"aria-label 是反对我定级的最强论据"被实测证实 —— 修复范围不变,后续切片的严重度叙述以此为准。 - 翻译逐包邻键实证(fr 冒号前排版空格、de 半角破折号、ru ё 正字法、ar 占位符居尾、zh 全角标点);en 文案与各组件既有 defaults map 逐字节一致 —— provider 两路不会分叉。
- 测试设计的两处防伪背书:非-en-复制断言(抓"九包抄英文"这种 parity 看不见的假回填);discrimination check 的失败文本
expected 'Refresh' to be '刷新'本身就是前提修正的直接证据。 - 死兜底 5 处全在 ObjectView 且已删;
cannotEditMetaView一 key 服务四操作(四条从未渲染过的不同英文死句)—— 是否要按操作分 key 属维护者裁量,已在 PR 正文立此存照,不阻塞。 - 棘轮 -5 精确(脚本断言 removed===5),双向逆验证按预告命中。
- 新类缺陷 console.objectView 的 2 个 key 在 8 个语言包里是英文原文,其中 systemViewReadonly 还是与 en 不一致的旧文案(全部门禁皆绿) #3582(key 在、值为英文 + 内容过期,全仓仅 2 例,守卫可绿落地)已核存在,下轮分诊;
⚠️ 与本 PR 同触语言包,fix(i18n,app-shell): 补齐 8 处无兜底裸 key 调用点的十语言包文案 (#3546 切片一) #3583 合并前不派。
切片二(organization 90 key)在本 PR 落 main 后开(en.ts + 棘轮共享)。PR 转 ready + auto-merge。
Generated by Claude Code
- 严重度框架修正,采纳(
切片二认领 — 会话
session_01GTRjn8xBqp75dk7kFupVRt,分支claude/issue-3546-slice2-organization。范围:
organization命名空间 90 个缺失 key(按 #3547 测量报告的命名空间轴,最大一批;PR #3583 已落 main,en.ts/棘轮基线空闲)。同规矩:en 回填 + 九包实译(邻键证据标准)、⛔ 无 defaultValue、棘轮恰减 90、provider-mounted 测试抽样钉扎(不必 90 key 全测,按 #3583 先例选代表性调用点)、Fixes-part-of不关单。严重度按切片一修正后的框架叙述(这批全部是"英文可见、十语不可译"类 —— 268 个带内联 defaultValue 站点的主体)。文件面:十包 organization 段、涉及组件仅当有死兜底需删(先量再动)、棘轮基线(仅 organization 条目)、测试。
⚠️ 与在飞 #3363(types/mobile)、#3584(CONTRIBUTING)、#3578/#3575(runner)零相交。越界即停。
Generated by Claude Code
切片二验收:ACCEPT — PR #3605(13 文件恰在围栏;
Fixes-part-of,本单继续 open,余 163 key + 4 前缀家族)。裁定要点:(1) 93 个调用点的 en 值与内联 defaultValue 逐字节相同(脚本比对 93/93)—— 双路径不可分叉的硬保证;组件零改动有测量支撑(无死构造)。(2) 预测失误(+90 vs 实际 +93,三 key 双调用点)如实上报并修正分母模型 —— 记录进后续切片的预测方法。(3) 反抄袭断言的规模化处理背书:12 对真借词/同形词以带注释的精确集合相等钉住 —— 既不为凑空集扭曲翻译,也不让清单腐化(第 13 个同形会红,改良 12 之一也会红迫使出列);B1 还诚实暴露了该断言单独存在时的空转性(presence 断言补位)。(4) es 敬称按邻域裁定(organizations/workspace 邻域一致非正式)—— "同一规则,不同答案"的正确应用。(5) 棘轮 -90 精确;
organization.invitations.status.前缀家族有断言防遗忘。切片三候选(#3605 落 main 后):console 43 / auth 家族 54 / marketplace 19 / preview 18 / oauth 16 或 4 前缀家族 —— 下轮定。PR 转 ready + auto-merge。
Generated by Claude Code
切片三认领 — 会话
session_01GTRjn8xBqp75dk7kFupVRt,分支claude/issue-3546-slice3-auth-family。范围:auth 家族三命名空间 =
auth26 +oauth16 +acceptInvitation12 ≈ 54 key(切片候选中最大剩余块;PR #3605 已落 main,en.ts/棘轮基线空闲,无切片在飞)。同规矩:en 回填 + 九包实译(邻键证据标准)、⛔ 无 defaultValue、棘轮恰减本切片条目数(分母以脚本实测为准,不手数 —— 切片二 +90 实为 +93 的教训,一 key 多调用点)、provider-mounted 抽样钉扎、反抄袭断言按切片二范式(真借词/同形词带注释精确集合)、Fixes-part-of不关单。死兜底先量再动。文件面:十包 auth/oauth/acceptInvitation 段、
scripts/i18n-call-site-key-baseline.json(仅本切片条目)、测试;涉组件仅当量出死兜底。⚠️ 与在飞 #3482(app-shell 环境工具栏)、#3491(plugin-chatbot)、#3797(components inputs)零相交;environment.*命名空间不在本切片。越界即停。
Generated by Claude Code
切片四认领 — 会话
session_01GTRjn8xBqp75dk7kFupVRt,分支claude/issue-3546-slice4-console。范围:
console命名空间 ≈ 41 key(切片三验收后剩余账最大块;PR #3814 已落 main @7864f0340,en.ts/棘轮基线空闲,无切片在飞)。同规矩:分母以脚本实测为准不手数、en 回填 + 九包实译(邻键证据标准)、⛔ 无 defaultValue、棘轮恰减实测数、provider-mounted 抽样钉扎、反抄袭断言带 presence 防空转、Fixes-part-of不关单;切片三把棘轮总数钉在 109,本切片同步改一次(设计上每切片动一次且只降的计数器);死兜底先量再动;push 前自查 trailer 无模型标识。文件面:十包 console 段、棘轮基线(仅本切片条目)、测试;与在飞 #3755+#3759(create-plugin)/#3812(components/action)/#3813(data-objectstack)/#3762(plugin-grid)零相交。前缀家族
console.ai.group.属 console 命名空间 —— 本切片一并处理并从 missingPrefixes 摘除(4→3),测试同步改钉。
Generated by Claude Code
切片五认领 — 会话
session_01GTRjn8xBqp75dk7kFupVRt,分支claude/issue-3546-slice5-marketplace-preview。范围:
marketplace(19)+preview(18)两命名空间 ≈ 37 key(切片四验收后剩余账两个最大块合并一片;PR #3839 已落 main @f5f874491,en.ts/棘轮空闲,无切片在飞)。同规矩(五轮先例):分母脚本实测不手数、⛔ 无 defaultValue、en↔内联 defaultValue 逐字节 N/N、九包邻键取证 + 反抄袭精确集合 + presence 防空转、provider-mounted 抽样钉扎、死兜底先量再动、切片四把棘轮总数钉在 68 —— 本切片同步改一次且只降、marketplace.disclosure.runtime.前缀家族若属本切片按封闭枚举处理并同步 missingPrefixes 钉子(3→2)、push 前 trailer 自查、Fixes-part-of不关单。文件面:十包两命名空间段、棘轮基线、测试;与在飞 #3808(返工中,plugin-detail/console/components)零相交。
Generated by Claude Code
切片六认领 — 会话
session_01GTRjn8xBqp75dk7kFupVRt,分支claude/issue-3546-slice6-perm-home。范围:
perm(9)+home(5)两命名空间 ≈ 14 key(切片五验收后剩余账两个最大块;PR #3847 已落 main @844d17fc9,无切片在飞)。六轮先例规矩全套:分母脚本实测、⛔ 无 defaultValue、en↔defaultValue 逐字节 N/N、九包邻键取证(含按概念找邻键新规 —— 裸单位词/裸标签类 key 在 en 逐字节搜索外额外按概念找)、反抄袭精确集合 + presence 防空转、provider-mounted 抽样钉扎、死兜底先量再动、切片五把棘轮总数钉在 31 —— 本切片同步改一次且只降、push 前 trailer 自查、末次编辑后 CI 同命令全仓 type-check、Fixes-part-of不关单。#2458 第 4 条点名的Your apps/Needs your attention属 home 欠账,本切片覆盖后在该单追评。文件面:十包两命名空间段、棘轮基线、测试;与在飞 #3827(packages/cli)零相交。
Generated by Claude Code
🔒 认领 切片七(终章:清空棘轮残余)(objectui shard PM,session
session_01GTRjn8xBqp75dk7kFupVRt)- 分支:
claude/issue-3546-slice7-final-residue - 前序:切片六 PR fix(i18n): 回填 perm + home 两命名空间 14 个缺失语言 key,十包补齐(#3546 切片六) #3868 已落 main(
e64a52ec3,棘轮 31→17)。 - 范围:
scripts/i18n-call-site-key-baseline.json全部剩余条目 —— 17 个 key(empty.* / kanban.columns / layout.systemNav.* / workspace.multiOrgDisabled 等,以脚本实测为准)+ 2 个前缀族(gantt.linkEnd.、organization.invitations.status.);目标棘轮 17→0、missingPrefixes 2→0。 - 六轮规矩全套沿用;敬称/复数族/按概念找邻键按切片五/六先例;普查正则按 [finding] PR #3847 的「括号复数记法」普查用了 ASCII
\w,所以 ru 的结论(0 处)是错的(实为 13 处),同一写法的断言对全部非拉丁语言包空转 #3866 教训用\p{L}+u。 - 并飞声明:es 语言包的 preview.draftBar 同一条横幅两种敬称 —— messageClean 用 usted、message 用 tú,西语用户按「有无待发布变更」看到不同人称 #3844(es.ts
preview.draftBar两值语域对齐)同批在飞 —— 本切片只新增 key,⛔ 不动任何既有 es 值;AiChatPage 的 planBuildingLabel 按「对话语言」而非 UI 语言取值 —— 英文界面 + 中文会话时,方案卡上唯一一个中文标签,且 zh 包该 key 永远读不到 #3837 押后至本切片落 main(其修法与本切片同文件 console-namespace-3546.test.tsx)。 - 与在飞 ActionRunner.execute 的 disabled 门把「空谓词」当已禁用,拦掉执行(实测 handler 不跑),且与渲染器判定不一致 #3848(packages/core)零相交。
Generated by Claude Code
- 分支:
🏁 关单(PM,session
session_01GTRjn8xBqp75dk7kFupVRt)—— DoD 达成,258 key 存量七个切片还清main(
f9faa7d62起)实证:scripts/i18n-call-site-key-baseline.json的missingKeys {}/missingPrefixes {}双空,守卫 2320/2320 全解析(en 包 2918 keys),正文验收标准「基线只剩空的 missingKeys/missingPrefixes」逐字达成。基线文件保留为承重终态 —— 任何新的未解析调用点 key 对空基线即红。七个切片(全部 MERGED):
- 切片一 PR fix(i18n,app-shell): 补齐 8 处无兜底裸 key 调用点的十语言包文案 (#3546 切片一) #3583 —— 8 处无 defaultValue 裸 key(含 ObjectView toast、gantt aria-label)
- 切片二 PR fix(i18n): 回填 auth 家族 54 个缺失语言 key,十包补齐 (#3546 切片三) #3814 —— auth + oauth + acceptInvitation 54 key(es 敬称邻域裁定先例)
- 切片三 PR fix(i18n): 回填 auth 家族 54 个缺失语言 key,十包补齐 (#3546 切片三) #3814 前序 organization 90 key
- 切片四 PR fix(i18n): 回填 console 命名空间 41 个缺失语言 key + console.ai.group. 前缀家族,十包补齐 (#3546 切片四) #3839 —— console 41 key + console.ai.group. 前缀族
- 切片五 PR fix(i18n): 回填 marketplace + preview 两命名空间 37 个缺失语言 key 及 marketplace.disclosure.runtime. 前缀家族,十包补齐 (#3546 切片五) #3847 —— marketplace + preview 37 key + marketplace.disclosure.runtime. 前缀族
- 切片六 PR fix(i18n): 回填 perm + home 两命名空间 14 个缺失语言 key,十包补齐(#3546 切片六) #3868 —— perm 9 + home 5(复数族形状 A 先例)
- 切片七 PR fix(i18n): 回填棘轮最后 17 个缺失语言 key 与两个模板 key 家族,调用点欠账清零(#3546 切片七) #3885 —— 终章 17 key(23 调用点)+ gantt.linkEnd. / organization.invitations.status. 两前缀族封闭枚举
沿路立起的门禁与纪律:#3530 调用点守卫 + 棘轮、en-drift 门(#3650)、总数钉家族、按概念找邻键、反抄袭精确集合、复数族基础 key 兜底、prefix 职责入测试。衍生单(#3810/#3837/#3844→已修/#3845/#3863/#3865/#3866/#3875/#3876/#3877/#3878/#3880 等)各自独立跟踪,不阻本单关闭。
Generated by Claude Code
3 remaining items
- added 7 commits that reference this issue
on Aug 10, 2026 - added 4 commits that reference this issue
on Aug 17, 2026 - added a commit that references this issue
on Aug 24, 2026 - added a commit that references this issue
on Sep 1, 2026
由 #3530 的守卫(
scripts/check-i18n-call-site-keys.mjs)在main@a2c8f2a29上首次全量扫描量出。本单是存量缺 key 的账,守卫本身以 baseline 落地(不修包,越界),这里立单跟踪修复。量出来的数
扫描面:
packages/**+apps/**的非测试.ts/.tsx(1456 个文件),共 3485 个t()/tt()调用点,其中 2370 个是包驱动的(绑定来自use*Translation/use*Translate/use*T钩子;另有 1074 个走packages/app-shell/src/views/metadata-admin/i18n.ts这张模块内标签表,不在本单范围)。en解析en一条都不匹配(整族缺失)all-locales-key-parity.test.ts对这 258 个 key 全绿 —— 十个包一致地都没有,包际一致性成立。这正是 #3530 记录的门禁盲区。按命名空间(不重复 key 数)
organization90、console43、auth26、marketplace19、preview18、oauth16、acceptInvitation12、perm9、detail5、home5、common4、empty3、layout3,以及dashboard/gantt/kanban/wizard/workspace各 1。按包(调用点数):
packages/app-shell204、apps/console54、packages/plugin-detail14,plugin-dashboard/plugin-form/plugin-gantt/plugin-kanban各 1。建议的拆单轴 = 命名空间:
organization(90)、console(43)、auth+oauth+acceptInvitation(54)、marketplace(19)、preview(18)是五坨可以并行、互不冲突地补进en.ts的工作;剩下的零散 key 一单收尾。补进en.ts之后all-locales-key-parity.test.ts会立刻要求另外九包补齐 —— 那是正确的顺序,不是额外负担。严重度不均:8 处没有内联 defaultValue,用户直接看到 raw key
其余 268 处都写了
t(key, { defaultValue: '英文' }),英文照常渲染、只是十种语言都翻不了(#3517 那一类,静默数月)。下面这 8 处连兜底都没有:两个已核实的现场:
ObjectView.tsx:854写的是toast.error(t('console.objectView.cannotEditMetaView') || 'Built-in views cannot be renamed.')。i18next 缺 key 时返回 key 本身(真值),||兜底永远不触发 —— toast 上显示的就是console.objectView.cannotEditMetaView这行字。GanttView.tsx:3269是aria-label={t('gantt.toolbar.refresh')}。该 key 在packages/plugin-gantt/src/useGanttTranslation.ts:39的 defaults map 里有,但 defaults map 只在没有 provider 时生效;console 里 provider 是挂着的,于是useGanttTranslation交出 i18next 的t,刷新按钮的无障碍名就是gantt.toolbar.refresh。屏幕阅读器用户听到的是这个。useSafeTranslation/createSafeTranslation这一族的 defaults map 都有同样的语义陷阱:map 里有、包里没有的 key,恰恰在真实 console 里退化成 raw key,而在无 provider 的单测里是绿的。4 个整族缺失的模板 key
en里没有任何 key 以这些前缀开头,所以插值取什么值都是缺的:修复方式与验收
对每个 key:补进
packages/i18n/src/locales/en.ts,再从scripts/i18n-call-site-key-baseline.json删掉对应条目(该文件是棘轮:条目已修好却还留着,守卫同样会红)。补完en后all-locales-key-parity.test.ts会要求另外九包补齐。⛔ 不要用加defaultValue的方式"修" —— 那正是让这 258 个 key 藏了几个月的机制。全部清零后,
scripts/i18n-call-site-key-baseline.json应当只剩空的missingKeys/missingPrefixes。关联:#3530(门禁本体)、#3517 / PR #3526(单个实例与修复)、#3469 / PR #3516(发现路径)。