Skip to content

258 个 t() 调用点引用的 key 在任何语言包里都不存在(#3530 守卫首跑实测),其中 8 处直接把 raw key 渲染给用户 #3546

Description

@yinlianghui

由 #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 这张模块内标签表,不在本单范围)。

项 数
包驱动调用点的字面量 key 2319
能在 en 解析 2043
解析不到(缺 key) 276 处 / 258 个不重复 key
模板 key 静态前缀在 en 一条都不匹配(整族缺失) 5 处 / 4 个前缀
动态拼 key(仅计数,不判定) 62

all-locales-key-parity.test.ts 对这 258 个 key 全绿 —— 十个包一致地都没有,包际一致性成立。这正是 #3530 记录的门禁盲区。

按命名空间(不重复 key 数)

organization 90、console 43、auth 26、marketplace 19、preview 18、oauth 16、acceptInvitation 12、perm 9、detail 5、home 5、common 4、empty 3、layout 3,以及 dashboard/gantt/kanban/wizard/workspace 各 1。

按包(调用点数):packages/app-shell 204、apps/console 54、packages/plugin-detail 14,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 处连兜底都没有:

packages/app-shell/src/views/ObjectView.tsx:854:25   console.objectView.cannotEditMetaView
packages/app-shell/src/views/ObjectView.tsx:872:25   console.objectView.cannotDeleteMetaView
packages/app-shell/src/views/ObjectView.tsx:906:25   console.objectView.cannotEditMetaView
packages/app-shell/src/views/ObjectView.tsx:923:17   console.objectView.cannotEditMetaView
packages/app-shell/src/views/ObjectView.tsx:976:17   console.objectView.cannotEditMetaView
packages/plugin-detail/src/RecordActivityTimeline.tsx:486:30   detail.viewSource
packages/plugin-form/src/WizardForm.tsx:403:11   wizard.missingRequired
packages/plugin-gantt/src/GanttView.tsx:3269:27   gantt.toolbar.refresh

两个已核实的现场:

  • 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 以这些前缀开头,所以插值取什么值都是缺的:

packages/app-shell/src/console/ai/ConversationsSidebar.tsx:277:20            console.ai.group.
packages/app-shell/src/console/marketplace/PluginDisclosure.tsx:70:14        marketplace.disclosure.runtime.
packages/app-shell/src/console/organizations/manage/InvitationsPage.tsx:161  organization.invitations.status.
packages/app-shell/src/console/organizations/manage/InvitationsPage.tsx:206  organization.invitations.status.
packages/plugin-gantt/src/GanttView.tsx:4577:60                             gantt.linkEnd.

修复方式与验收

对每个 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(发现路径)。

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: pm:queue. No domain:* — objectui is a whole-repo seat, routing is the repo:* axis and this issue is already in the repo it lands in. Nothing here is feature-level, cross-repo, or touching packages/spec, so no transfer to the objectstack backlog: the whole fix is packages/i18n/src/locales/en.ts plus 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:854 shows console.objectView.cannotEditMetaView in a toast (i18next returns the key itself, so the || 'Built-in views…' guard is dead — a truthy value never falls through), and GanttView.tsx:3269 gives screen-reader users gantt.toolbar.refresh as 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 / createSafeTranslation defaults maps make these call sites green in provider-less unit tests and broken in the real console — where a provider is mounted, so i18next's t wins 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:

    No duplicate found.

    Splitting: deliberately not done, and here is why, since the body proposes it. The suggested axis (namespace: organization 90 / console 43 / auth-family 54 / marketplace 19 / preview 18) is a good review axis but a bad parallel-dispatch axis — every one of those sub-issues appends to the same en.ts and the same i18n-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:v17 from 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 — the aria-label one is the strongest argument against my own grading, and it is a cheap override if the maintainer disagrees.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 7, 2026
  3. yinlianghui commented on Aug 7, 2026

    @yinlianghui
    CollaboratorAuthor

    认领(切片一)(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

  4. yinlianghui commented on Aug 7, 2026

    @yinlianghui
    CollaboratorAuthor

    切片一验收:ACCEPT — PR #3583(Fixes-part-of,本单保持 open,余 253 key + 4 前缀家族)。

    核过 GitHub 实物(16 文件恰在围栏)。裁定要点:

    1. 严重度框架修正,采纳(premise_still_valid: false 部分证伪):8 处里 6 处真渲染裸 key;WizardForm(probe key 恰是缺失 key,探针正确失败转英文)与 Gantt aria-label(useGanttTranslation 逐 key 兜底)实为 [i18n] form.createTargetOrg 在十个语言包里全部缺失,组织写入目标徽标在所有非英文语言下都渲染英文兜底 #3517 类(英文可见、十语不可译)。分诊座位当时自我标注的"aria-label 是反对我定级的最强论据"被实测证实 —— 修复范围不变,后续切片的严重度叙述以此为准。
    2. 翻译逐包邻键实证(fr 冒号前排版空格、de 半角破折号、ru ё 正字法、ar 占位符居尾、zh 全角标点);en 文案与各组件既有 defaults map 逐字节一致 —— provider 两路不会分叉。
    3. 测试设计的两处防伪背书:非-en-复制断言(抓"九包抄英文"这种 parity 看不见的假回填);discrimination check 的失败文本 expected 'Refresh' to be '刷新' 本身就是前提修正的直接证据。
    4. 死兜底 5 处全在 ObjectView 且已删;cannotEditMetaView 一 key 服务四操作(四条从未渲染过的不同英文死句)—— 是否要按操作分 key 属维护者裁量,已在 PR 正文立此存照,不阻塞。
    5. 棘轮 -5 精确(脚本断言 removed===5),双向逆验证按预告命中。
    6. 新类缺陷 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

  5. yinlianghui commented on Aug 7, 2026

    @yinlianghui
    CollaboratorAuthor

    切片二认领 — 会话 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

  6. yinlianghui commented on Aug 7, 2026

    @yinlianghui
    CollaboratorAuthor

    切片二验收: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

  7. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    CollaboratorAuthor

    切片三认领 — 会话 session_01GTRjn8xBqp75dk7kFupVRt,分支 claude/issue-3546-slice3-auth-family。

    范围:auth 家族三命名空间 = auth 26 + oauth 16 + acceptInvitation 12 ≈ 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

  8. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    CollaboratorAuthor

    切片四认领 — 会话 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

  9. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    CollaboratorAuthor

    切片五认领 — 会话 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

  10. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    CollaboratorAuthor

    切片六认领 — 会话 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

  11. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    CollaboratorAuthor

    🔒 认领 切片七(终章:清空棘轮残余)(objectui shard PM,session session_01GTRjn8xBqp75dk7kFupVRt)


    Generated by Claude Code

  12. yinlianghui commented on Aug 9, 2026

    @yinlianghui
    CollaboratorAuthor

    🏁 关单(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):

    1. 切片一 PR fix(i18n,app-shell): 补齐 8 处无兜底裸 key 调用点的十语言包文案 (#3546 切片一) #3583 —— 8 处无 defaultValue 裸 key(含 ObjectView toast、gantt aria-label)
    2. 切片二 PR fix(i18n): 回填 auth 家族 54 个缺失语言 key,十包补齐 (#3546 切片三) #3814 —— auth + oauth + acceptInvitation 54 key(es 敬称邻域裁定先例)
    3. 切片三 PR fix(i18n): 回填 auth 家族 54 个缺失语言 key,十包补齐 (#3546 切片三) #3814 前序 organization 90 key
    4. 切片四 PR fix(i18n): 回填 console 命名空间 41 个缺失语言 key + console.ai.group. 前缀家族,十包补齐 (#3546 切片四) #3839 —— console 41 key + console.ai.group. 前缀族
    5. 切片五 PR fix(i18n): 回填 marketplace + preview 两命名空间 37 个缺失语言 key 及 marketplace.disclosure.runtime. 前缀家族,十包补齐 (#3546 切片五) #3847 —— marketplace + preview 37 key + marketplace.disclosure.runtime. 前缀族
    6. 切片六 PR fix(i18n): 回填 perm + home 两命名空间 14 个缺失语言 key,十包补齐(#3546 切片六) #3868 —— perm 9 + home 5(复数族形状 A 先例)
    7. 切片七 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

  13. 3 remaining items

  14. added a commit that references this issue on Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions