fix(i18n): 回填 marketplace + preview 两命名空间 37 个缺失语言 key 及 marketplace.disclosure.runtime. 前缀家族,十包补齐 (#3546 切片五) - #3847
Conversation
…disclosure.runtime. 前缀家族,十包补齐 (#3546 切片五) `scripts/check-i18n-call-site-keys.mjs`(#3530 的守卫)实测:37 个不重复 key 在 37 个调用点上被 `t()` 引用而十个语言包一个都没有,外加 1 个整族缺失前缀 (`marketplace.disclosure.runtime.`,静态头在 en 一条都不匹配,所以插值取任何 值都是缺的)。分母以脚本实测为准不手数 —— 本切片罕见地是 1:1(前四切片都有 一 key 多站点,切片二预测 90 实为 93)。 37 处全部带内联 `defaultValue`,无一渲染裸 key(那类由切片一 PR #3583 收), 所以严重度是 #3517 类:英文照常渲染,十种语言全部卡在英文上。zh 会话下实际 看到的英文:应用市场「Your organization」区块与其 Install / Installing… / Installed 按钮、版本升级提示;ADR-0025 PD4 的**安装前权限披露**整块 —— This package contains code、信任层级徽章、Reviewed & approved / Not yet reviewed / Signed、四个权限组标签(平台服务/生命周期钩子/网络/文件系统)与用户勾选的 同意复选框;ADR-0045 未发布应用横幅及其发布 toast;ADR-0067 构建历史面板 整套 —— 标题、说明、每条提交的标签、Revert 按钮与它的两个结果 toast。 前缀家族按**封闭枚举**处理,不通配:取值面是 spec 的 `PluginRuntimeSchema` = `z.enum(['node', 'sandbox', 'worker'])`(ADR-0025 §3.6),故枚举三个成员, 该家族离开棘轮 `missingPrefixes`(3 → 2)。测试读组件自己的 `RUNTIME_FALLBACK` 兜底表(spec 在姐妹仓,本仓测试读不到),第四个层级加进来就红 —— 那正是棘轮 里那条 prefix 条目原本的职责,从棘轮搬进测试。 en 值与被替换的内联 `defaultValue` 逐字节相同(36 个字面量站点 36/36); 第 37 个站点的 `defaultValue` 是模板串 `` `Installed ${pkg.display_name}` ``, 逐字节比对结构上不可能,取其静态头加 i18next 的洞 `Installed {{name}}` —— `name` 参数该站点本来就在传。所以用户今天看到的英文一字未改。 九包实译逐包取邻键证据,en 字符串已存在的一律复用邻键既有译文而不另造 (10 对,含 form.update / marketplace.installedBadge / preview.draftBar.publish / home.pendingDrafts.publishFailed / detail.history),使一句英文不会在同一 语言里渲染成两句。两个命名空间在 zh 里合法地取**不同**第二人称 (marketplace 你 3:0,preview 您 2:0),es 的 marketplace 邻域一致 usted 9:0。 零组件改动:对 marketplace/preview 全部 172 个调用点(11 文件)做 AST 扫描, 量出 16 处 `t(key) || '英文'` 死兜底,10 个 key 在 en 全都有值、且一处也不属 本切片的 37 个 key,故一处未动(已回填 #3810)。 棘轮按行删除而非重序列化:38 deletions,零插入。 顺手量出、未在本 PR 修:#3844(es preview.draftBar 同一横幅两种敬称)、 #3845(marketplace.action.updateTo 传了 version 但值里没有洞,全仓 1/1055)、 #3846(marketplaceApi 把 runtime 声明为开放 string 而 spec 是封闭枚举)。 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
自查 ja 包尾冒号分布时发现本切片自己写错了一处:`marketplace.disclosure.grantsIntro` 以谓语 `付与されます` 结尾,却给了全角 `:`。 ja 包在这件事上不是不一致,而是**有条件**的: - **谓语结尾 → 半角 `:`**,12 例中 10 例(五个 connectAgent 正文、 `oauth.consent.willAllow`、`auth.login.devAdminHint.body`、 `grid.bulk.affectedRecords`、`connectAgent.apiKey.showOnce`、 `preview.changes.loadFailed`); - **名词标签结尾 → 全角 `:`**,5 例中 4 例(`grid.import.mappingTemplate`、 `grid.import.savedMapping`、`auth.verifyEmail.sentTo`、 `preview.changes.detailChangedKeys`)。 本切片两个 ja 冒号都是谓语结尾,故都取半角。`preview.history.loadFailed` 本来就对(它另外还与同名兄弟键 `preview.changes.loadFailed` 逐字节相同); `grantsIntro` 没有同名兄弟,只能靠这条分布裁,之前裁反了。 同时把这条约定连同 zh(全包全角标点,13:6)与 ko(全包 16 处半角、0 处全角) 的反向对照写成断言,免得下一个读者以为 ja/zh 在同一个字串上取不同冒号是笔误, 也免得同一个错再犯一次。 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
追加一次提交:
|
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
`CommitTimeline.tsx:142` 渲染的是 `{c.itemCount} {t('preview.history.items')}` ——
数字由组件给,包只供单位词。这与 `common.itemCount`(en `{{count}} items`)
是**同一种「计数 + 单位」相邻结构**,而后者本仓十包早已译好。之前自造了括号
复数记法,两处错:
1. **ru 包全 2832 个值里一次也没用过 `(s)` 记法** —— 它要么改写句式
(`marketplace.detail.purgeSuccess` = `Удалено … записей: {{count}}.`),
要么缩写(`fields.relativeDate.overdue` = `Просрочено на {{count}} дн.`)。
`элемент(ов)` 是凭空引进的形状。
2. **`item(ns)` 在 pt 自己的追加式约定下根本得不到 `itens`**。该包的记法是
`linha(s)` / `registro(s)` / `coluna(s)` 这种词干加后缀,变干的情形写成
`alteração(ões)`(`home.pendingDrafts.message`)—— 无论按哪种读法,
`item(ns)` 都不成立。
改为直接取 `common.itemCount` 去掉 `{{count}} ` 前缀后的单位词:
zh 项 ja 件 de Elemente fr éléments es elementos
pt itens ru элементов ar عناصر
**ko 是唯一刻意的偏离**:`common.itemCount` ko 是 `{{count}}개 항목`,量词 개
紧贴数词不带空格;而本调用点在数字与单位之间强制插一个空格,照搬会渲染成
`3 개 항목`(空格落在数词—量词之间)。故舍量词留名词,渲染为 `3 항목`。
**没找到这个邻键的原因值得记下来**:本切片的复用清单是按 **en 逐字节相同**
搜邻键的,而 `common.itemCount` 的 en 是 `{{count}} items` 而不是 `item(s)`,
所以"同一概念、不同英文写法"的邻键对那个搜索是不可见的。已把这条连同上面的
裁定写成断言(并双向验证 `common.itemCount` 确实就是那个单位词),避免复发。
en 值不变(仍是调用点的 `item(s)`,逐字节纪律不动),故 en-drift、
en↔defaultValue 的 36/36、棘轮与前缀家族处理一概不受影响。
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
追加第三次提交:
|
| 包 | common.itemCount |
本 key 新值 |
|---|---|---|
| zh | {{count}} 项 |
项 |
| ja | {{count}} 件 |
件 |
| de | {{count}} Elemente |
Elemente |
| fr | {{count}} éléments |
éléments |
| es | {{count}} elementos |
elementos |
| pt | {{count}} itens |
itens |
| ru | {{count}} элементов |
элементов |
| ar | {{count}} عناصر |
عناصر |
包里另有 common.itemCountOne({{count}} item → элемент / item / Element …)承担单数,所以这个包本来就是用「裸复数 + 另一个单数 key」解决这件事的,不是括号记法。本调用点没有单数分支,取复数。ru 也因此干净收口 —— 不需要缩写,элементов 就是该包自己的答案。
ko 是唯一刻意的偏离:common.itemCount ko 是 {{count}}개 항목,量词 개 紧贴数词、不带空格;而本调用点在数字与单位之间强制插一个空格,照搬会渲染成 3 개 항목(空格落进数词—量词之间)。故舍量词留名词,渲染 3 항목。偏离与理由都写进了断言。
方法性的洞,记下来给后续切片
为什么之前没找到这个邻键:本切片(以及切片二/三/四)的复用清单是按 en 逐字节相同搜邻键的 —— 那条规则本身是对的,它保证"一句英文不会在同一语言里渲染成两句"。但它只看得见英文拼写相同的邻键。common.itemCount 的 en 是 {{count}} items,本 key 的 en 是 item(s),同一概念、不同英文写法,对那个搜索完全不可见。
所以逐字节搜邻键是必要但不充分的:凡 en 是"裸单位词 / 裸标签"这类由组件拼装句子的 key,都要额外按概念找一次邻键。这条建议后续切片(以及 #3810 的门禁设计)采纳。
新增断言把裁定和它的前提都钉住(双向:既断言本 key 的值,也断言 common.itemCount 确实就是那个单位词,所以邻键一改本条就红),并加一条"任何包不得在此重新引入 (…) 记法"。
复验
flock … vitest run packages/i18n scripts/__tests__/check-i18n-{call-site-keys,en-drift}.test.ts --maxWorkers=2
→ Test Files 31 passed (31) | Tests 536 passed (536) (首版 534 → 535 → 536,两条新增断言)
node scripts/check-i18n-call-site-keys.mjs → Every in-scope call-site key resolves against the en pack (2876 keys).
node scripts/check-i18n-en-drift.mjs → No en value changed in this range.
node scripts/check-control-bytes.mjs → OK (scanned 3755 tracked text file(s))
npx eslint 十个语言包 + 本切片测试文件 → 0 problems
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 八个改动文件 → 零命中
git log --format=%B origin/main..HEAD | grep -ci fable → 0
en 值未变(改的是九包),所以 en↔内联 defaultValue 的 36/36、check-i18n-en-drift 的 0 changed、棘轮 68 → 31、missingPrefixes 3 → 2、分母 37 key / 37 站点 / 1 前缀、以及三方向反向验证的结论全部不受影响。围栏仍是那 16 个文件。
反抄袭空集也仍然成立(新单位词无一与 en 的 item(s) 相同),零插值断言不变。
Generated by Claude Code
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
|
✅ 切片五验收:ACCEPT — PR #3847( 裁定要点:(1) 分母六轮如一(37+1 前缀,1:1 仍先量证明);模板串 defaultValue 的等价处置(静态头 + i18next 洞 + 读源码断言钉等价)正确;(2) 敬称「同一规则不同答案」的量化取证(zh 你 3:0 / 您 2:0;es preview 邻域 2:2 分裂时按结构孪生裁)是该纪律迄今最严格执行;(3) 反抄袭空集 0/360 配三条防空转(外来词反面对照证明零是真零)—— 优于前四轮;(4) 两处自误自查改正如实入档采纳(ja 尾冒号约定系有条件、复数记法自造被包内证据推翻)—— 并由此浮出方法性盲区入方法库:按 en 逐字节搜邻键看不见「同概念不同英文写法」,裸单位词/裸标签类 key 须额外按概念找邻键(后续切片与 #3810 门禁设计采纳);(5) 假绿监视第 N 例照例弃用改 MCP 核实。19/19 CI 全绿;棘轮 68→31 三测试同步;衍生 #3844(bug 待分诊)/#3845/#3846(finding)+ #3810 累计量化回填(~600/2320 站点)边界合规。undraft + auto-merge。 Generated by Claude Code |
Fixes-part-of #3546— 切片五。本单不关闭 #3546:回填后仍余 31 个缺失 key + 2 个整族缺失前缀。基线
origin/main@d3e738af8(merge-base;main其后前进一个提交 #3841,与本围栏零文件相交,已实测)。分母:脚本实测,不手数
node scripts/check-i18n-call-site-keys.mjs(#3530 的守卫)的analyze()实测本切片两个命名空间:marketplace19 +preview18)missing-prefixmarketplace.disclosure.runtime.,棘轮三个前缀家族里唯一属这两个命名空间的)多站点 key 数 = 0。切片二"预测 90 实为 93"的教训这次同样先量了 —— 结论恰好是 1:1,所以脚本实测在这里的价值不是抓多站点,而是证明没有:
守卫读数,前后对照(同一命令):
解析数 +37(= 37 个调用点)、en key 数 +40(37 + 前缀家族 3),与清单精确相等。
棘轮 ASSERT 输出:
棘轮按行删除而非重序列化(切片四量过:重序列化会把 diff 变成全文件重排)。最终 diff:
38 deletions(-),零插入。严重度:全部是"英文可见、十语不可译"
按切片一验收修正后的框架叙述。37 处全部带内联
defaultValue,无一渲染裸 key(那类由切片一 PR #3583 收)。zh会话下实际看到的英文:Install/Installing…/Installed/ 安装成功提示;包详情页主按钮的Update,环境下拉里的Update → v1.2.3与Installed v1.2.3。This package contains code、信任层级徽章、Reviewed & approved/Not yet reviewed/Signed、On install, this package will be granted:与四个权限组标签(平台服务、生命周期钩子、网络访问、文件系统访问)、Requests no special permissions.,以及用户勾选的同意复选框I understand this package runs code and grants the permissions above.History/Publish/Publishing…按钮,以及发布成功/失败两个 toast。Build history、说明、Reverted a change/Build change行标签、revert徽章、item(s)、Revert按钮、Revert failed与Reverted — the change has been undone.两个结果 toast、加载/失败/空态三句。en ↔ 内联 defaultValue:36/36 逐字节相同,第 37 处结构上不可能
第 37 处(
MarketplacePage.tsx:143)的defaultValue是模板串而不是静态字面量:逐字节比对对模板串结构上不可能。取的等价关系是:模板串的静态头 + 被插值的标识符 === 包值的静态头 + i18next 的洞,而驱动那个洞的
name参数该站点本来就在传。所以渲染文本一字不变,只是换成 i18next 来插值。这条等价关系有断言钉住(读源码正则取出模板串与参数名,再比 en 值),站点一移动就红。无 defaultValue 的站点:0 处 —— 本切片不需要从组件语境取文案。
前缀家族
marketplace.disclosure.runtime.:按封闭枚举,不通配调用点(
PluginDisclosure.tsx:70):取值面不是开放字符串,而是 spec 的封闭枚举 —— objectstack
packages/spec/src/kernel/manifest.zod.ts:84:所以修法是枚举三个成员,不是补通配前缀。与切片四
console.ai.group.的差别值得记一笔:那里的封闭联合在同一个文件里(ConversationGroupKey),这里的权威在姐妹仓,本仓测试读不到。故测试的仓内权威取组件自己的RUNTIME_FALLBACK表(它也正是defaultValue实际读的那张表),并在注释里引 spec 枚举作最终依据。钉两件事:en的marketplace.disclosure.runtime子对象键集合 ===RUNTIME_FALLBACK键集合 ===['node','sandbox','worker'](第四个层级一旦加入,这条红 —— 正是棘轮里那条 prefix 条目原本的职责,从棘轮搬进测试);en值与RUNTIME_FALLBACK的英文逐字节相同(包在时 i18next 答、包不在时兜底表答,用户不能分辨跑的是哪条)。"key 可达性 vs 值判定"是两件不同的事,分别断言。棘轮
missingPrefixes3 → 2,切片三/四写的"恰 3 条"钉子同步改为 2 条并加一条not.toContain('marketplace.disclosure.runtime.')。marketplaceApi.ts:157把runtime声明为开放string,由此长出兜底表的Record< string, string >、?? version.runtime(未知层级时把 wire 原值当界面文案渲染到权限面板上)与as any。死兜底:先量再动,本切片 key 为 0(并顺带量出 16 处别人的)
对
marketplace.*+preview.*的全部 172 个调用点(11 个文件,不只本切片的 37 个)做 AST 扫描,找t(key) || '…'/?? '…'形态:其中三条是"误导读者"的完整形态而不只是冗余:
moreOptions包值More install options/ 死句More options;purgeSuccess包值用{{count}}插值 / 死句用模板串;reseedLocalSuccess连句式都不同。切片一(PR #3583)删的是同类里 key 缺失的 5 处(那时||因 i18next miss 返回 key 恒真而失效);这 16 处的 key 存在,从来没进过棘轮,谁也扫不到。已回填 #3810(该单选项 B 的门禁若只判defaultValue会整支漏掉||;||存量现在是 21 处而非 5 处)。所以本 PR 零组件改动。同一次扫描顺带给 #3810 交了它正文点名"最该先做"的普查数据:这两个命名空间已存在 key 的 29 个带 defaultValue 站点与 en 零分歧(65/65 含新增),是该单目前第一个零分歧命名空间,与 auth 家族的 8/68 形成对照 —— 支持该单选项 B 落硬门禁而不必配基线。
九包实译:逐包邻键取证
每包都在同一个包的
marketplace/preview邻域取证(不跨包套用),证据是量出来的而不是印象:marketplace.subtitle用 你(该命名空间 3:0);preview.draftBar.*用 您(2:0);preview.changes.loadFailed=无法加载待发布变更:(全角冒号,全包 13:6 全角占优);preview.draftBar.message长破折号写——你的组织;发布后即可让您的用户看到;无法加载历史记录:;已撤销 —— 该变更已还原。preview.changes.loadFailed),名词标签结尾用全角(5 例中 4 例,含preview.changes.detailChangedKeys)。本切片两处都是谓语结尾故都取半角(初版把grantsIntro写成全角,e01dff775已改正);home.pendingDrafts.published把!换成。履歴を読み込めませんでした:;…付与されます:;公開しました。アプリがユーザーに表示されます。:结尾、0 处全角설치하면 이 패키지에 다음 권한이 부여됩니다:;{{name}} 설치됨preview.draftBar.sampleDataBody/message一致 Sie;KI-Nutzung式连字符复合Veröffentlichen Sie sie, damit Ihre Benutzer sie sehen.;Sandbox-isoliert、Lifecycle-Hooks:与!前留空格(6:0 / 49:0);preview.changes.confirmNote用 paquetImpossible de charger l'historique :;Publié ! L'application…;Ce paquet contient du codemarketplace.*一致 usted(9:0);home.pendingDrafts.published=¡Publicado! Sus cambios están activos.Su organización、Publíquela para que sus usuarios la vean.、¡Publicado! La aplicación ya es visible para sus usuarios.preview.draftBar.*一致 você;计数单位取common.itemCount={{count}} itens(初版自造item(ns),在该包追加式约定下得不到itens,c3e30870c已改正)Publique-o para que seus usuários o vejam.;itensgrid.import.undoConfirm的обновлённые);全包 0 处使用括号复数记法(改写句式或缩写,如{{count}} дн.)Ещё не проверено(有断言钉 ё);элементов(取common.itemCount)marketplace.browseLink保留→;进行式用جارٍ …;全包只有 30 处以拉丁字母开头جارٍ تحميل السجل…;40 个值无一以拉丁字母开头(全量断言)en 相同的字符串一律复用邻键既有译文而不另造,10 对,有断言双向钉住(先验证两边 en 真的同字符串,再验证十包同值):
marketplace.action.updateTo←form.update(不是layout.activityFeed.typeUpdate,后者 ru 是名词Обновление而这是按钮);marketplace.install.installedVersion←marketplace.installedBadge;marketplace.org.install/installedBadge/installing←marketplace.action.install/marketplace.installed/marketplace.action.installing;preview.unpublishedBar.publish/publishing← 同命名空间的preview.draftBar.*;publishFailed←home.pendingDrafts.publishFailed;preview.history.button←detail.history。两处刻意不复用,也有断言钉住:
marketplace.action.installed的 es/ar 是与aplicación一致的阴性/带符变体(Instalada/مثبَّت)而 org 徽章标的是 package,取marketplace.installed的Instalado/مثبت;home.pendingDrafts.publishing的 ja/ko 与同命名空间的preview.draftBar.publishing不同,同命名空间优先。逐字节搜邻键的一个盲区(
c3e30870c暴露,建议后续切片采纳):上面那条复用规则按 en 逐字节相同搜邻键,所以看不见"同一概念、不同英文写法"的邻键。preview.history.items的 en 是item(s),而本仓早已译好的common.itemCount的 en 是{{count}} items—— 同一种「计数 + 单位」相邻结构(数字都由组件给、包只供单位词),却因英文拼写不同而搜不到,于是九包自造了括号复数记法。凡 en 是裸单位词 / 裸标签这类由组件拼装句子的 key,都要额外按概念找一次邻键。现取common.itemCount的单位词:项/件/Elemente/éléments/elementos/itens/элементов/عناصر;ko 是唯一刻意偏离 ——common.itemCountko 是{{count}}개 항목(量词紧贴数词无空格),而本调用点强制插空格,照搬会渲染3 개 항목,故舍量词留항목。一 key 多句英文的裁量:本切片无此情形,但有一条邻近的
切片四的
console.ai.dock.open(同一按钮两句英文)在本切片没有对应物 —— 37 个站点 1:1,不存在同 key 两站点。需要裁量的是另一件:
marketplace.action.updateTo传了version参数,而defaultValue是裸Update(没有洞),i18next 静默丢弃;同一个文件的marketplace.install.updateTo却渲染Update → v{{version}}。按切片四先例的取向(取无障碍/平台中立句)在这里不适用,因为这不是两句英文的取舍,而是"要不要新增信息"。裁定:en 落
Update,不加洞。 两条理由:(1) 加洞会改变用户今天看到的字串,而本切片的硬纪律就是 en 逐字节等于内联 defaultValue;(2) 主按钮要不要带版本号是产品裁量(三态按钮宽度、与Installed/Installing…的视觉一致性),不是 i18n 决定。已立 #3845(全仓实测该类仅此 1 处 / 1055),并在测试里立此存照:无论将来是删参数还是十包加洞,这条都会红,迫使做决定的人正面处理而不是以为漏了。
反抄袭断言:精确集合是空集,靠三条防空转顶住
实测"与 en 逐字节相同"的
lang :: key对:0 / 360。这比切片二(12)、三(18)、四(1)都干净 —— 本切片确实没有一个合法同形词。空集的集合相等断言是最容易空转的形状(切片二 B1 暴露的问题:包为空时同样"绿"),所以配了三条防空转,而不是切片四的一条:
it.each);另加四条本切片特有的形状断言:
install.updateTo/install.installedVersion带{{version}}、org.installed带{{name}},其余 37 个十包一律不许有洞。判据按 key 具名列出,写错哪一个都读得出来。…(U+2026)结尾、1 个带·(U+00B7,随marketplace.detail.installedV)、1 个带→(U+2192,随marketplace.browseLink),十包逐一镜像。revert与按钮Revert,zh/ar无大小写必然同串,而ja/ko同样无拉丁大小写却用名词/动词形拉开(取り消し/取り消す、되돌림/되돌리기)。钉精确集合['ar','zh'],免得 review 把 zh/ar 的重复读成复制粘贴失误。provider-mounted 抽样钉扎(discrimination check)
五个组件(
MarketplacePage/MarketplacePackagePage/PluginDisclosure/CommitTimeline/UnpublishedAppBar)都是裸useObjectTranslation(),没有createSafeTranslationdefaults map 挡在前面 —— 所以挂 provider 不是仪式。这条前提本身也断言了(读五个文件的源码,确认仍 importuseObjectTranslation且不含createSafeTranslation),而不是假设。抽样(不是 40 key 全测):en/zh各解析 6 个代表 key(每组件/每子区一个),断言not.toBe(key)且等于包内值;Update → v2.1.0/Installed v1.4.2/Installed Acme CRM,zh 得更新 → v2.1.0/已安装 Acme CRM;history.title、dedisclosure.noPermissions、eshistory.empty、ptdisclosure.fs、rudisclosure.unreviewed、jahistory.revertFailed、kodisclosure.grantsIntro、arunpublishedBar.published;تحديث → v2.1.0仍不以拉丁开头;反向验证:三处方向跑前先判,三处全中
方向在跑之前就定了。本切片是"包里没有 → 补上",没有被删的死枝、没有计数类下游门禁、没有 canonical-first 的
??链,所以不存在先例里的"诊断变多"或"反转"情形:第 1、2 条应为常规的红,第 3 条应为守卫绿而 parity/本测试红(#3530 记录的门禁分工,不是漏检)。en.ts(棘轮保持已删)→ 守卫报 38 条 unexpected / 38 distinct,退出码 1,解析数同步回落 2283 → 2246:zh.ts(en 保留)→ 守卫仍然绿(它只看 en),而 parity 与本切片测试红,失败文本就是前提本身:fallbackLng: 'en'这个藏了这些 key 数月的机制直接摆出来:zh 缺 key 时t()返回的是en 的值(In-process · full trust),不是 key、不报错 —— 所以只补 en 不补九包这种半成品,守卫看不见,parity 看得见。顺手量出、未在本 PR 修的事
preview.draftBar同一条横幅两种敬称:message用 tú(estás、publiques)、messageClean与sampleDataBody用 usted(ve、Está、Publíquela)。西语用户发布掉手上草稿之后,横幅从 tú 切到 usted。本切片给 19 个preview新 key 裁定 usted,依据写进断言:es 全包默认 usted(102:30,tú 集中在 auth/report/organizations 邻域 —— 切片二当时正确裁定的那几处)、marketplace.*邻域 9:0 usted、且最强的一条 ——preview.unpublishedBar.published的结构孪生home.pendingDrafts.published就是 usted(Sus cambios)。既有的两个 key 不在本切片围栏内,未动。marketplace.action.updateTo的休眠version参数(见上文裁量)。全仓实测 1 / 1055,是个单例。附了实现该门禁的一个陷阱:参数名提取必须先消掉嵌套的t()调用 —— 第一遍扫描就在ImageField.tsx:105上误报过一次(外层enlarge的参数对象里嵌了内层imageAlt的index:),剔除嵌套括号跨度后复测为 1。本 PR 报的所有数字都是复测后的数字。marketplaceApi.ts:157把runtime声明为开放string,而生产方 spec 的PluginRuntimeSchema是三成员封闭枚举;由此长出Record< string, string >兜底表、?? version.runtime(把 wire 原值当文案渲染到权限披露面板上)与as any。contract-first 的反面:消费方比生产方宽松。||死兜底 + 这两个命名空间已存在 key 的零分歧普查数据(该单正文自陈"规模未量",累计已量约 600 / 2320 站点)。验证
all-locales-key-parity.test.ts含在上面 31 个文件内,已绿(它是补完 en 后立刻要求九包补齐的那道门禁)。@object-ui/i18n无工作区内依赖(只依赖 i18next / react-i18next),所以它自己不需要先建依赖;@object-ui/react需要,已按 §9 先建。@object-ui/react只是type TranslationKeys的转出口(TranslationKeys = typeof en),全仓无消费方把它当必需形状声明,所以加 key 不会窄化谁 —— 已 grep 确认并实跑该包 type-check。测试命令按 AGENTS.md §9 的唯一正确写法(仓根
pnpm exec vitest run加相对仓根的路径),不用pnpm --filter 包名 test(陷阱一)、不把路径挂在--后(陷阱二);--workspace-concurrency一律在--filter之前。测试文件自身的两处 bug 在首次提交前被自己的套件抓到并修掉,记在这里因为第一条是会静默漏检的那种:(1) 用带
/g的正则做.test()放在filter里,lastIndex跨调用残留,隔一个漏一个 —— 现在拆成HOLES(/g,给.match())与HAS_HOLE(无/g,给.test()),并把原因写在注释里;(2) 复用清单里误把preview.history.revertAction当成"en 同字符串"(en 是Revert,grid.bulk.undo是Undo),已移出该清单、单独立一条断言并把更弱的那个主张说清楚 —— 复用的是各包既有的 undo 动词而非同一句英文,因为九包都没有区别于 undo 的 revert 专词,另造一个会让同一个按钮在用户那里有两个名字;en 若将来真的区分 revert/undo,那条断言就是必须回头看的地方。围栏
16 文件(相对 merge-base
d3e738af8实测:1357 insertions(+), 55 deletions(-)):packages/i18n/src/locales/*.ts(十包,仅新增本切片 key)、scripts/i18n-call-site-key-baseline.json(仅删本切片 38 条)、packages/i18n/src/__tests__/marketplace-preview-namespace-3546.test.tsx(新增)、auth-namespace-3546.test.tsx+console-namespace-3546.test.tsx+organization-namespace-3546.test.tsx(仅棘轮总数 68 → 31,前两者另同步missingPrefixes3 → 2 —— 那个计数器每切片动一次且只降)、changeset(@object-ui/i18npatch)。零组件改动(本切片 key 的死兜底量出为 0)。与在飞 #3808(plugin-detail/console/components)零相交;main其后落地的 #3841 与本围栏实测零文件相交。未动content/docs/releases/**。