fix(metadata-admin): 页面块检查器 curated 属性 label 跟随会话语言 (#3913) - #3970
Conversation
…ocale (#3913) The inspector's chrome went through `t('engine.inspector.pageBlock.*')` from the start while the panel's CONTENTS did not: every `label` in `previews/block-config.ts` was an English literal handed straight to the field components. A zh-CN admin opening any page block read 「属性」 over a stack of English field names. All 157 label literals (152 field/option labels + 5 addLabel) are now translation keys resolved through `t(key, locale)` at render, with 154 distinct keys added to both the en-US and zh-CN sides of `metadata-admin/i18n.ts`. The English text is unchanged: substituting every key back to its en value reproduces the pre-change data region byte-for-byte. Key shape is derived from the label's POSITION in BLOCK_CONFIG (`.field.<blockType>.<name>`, `.add.<blockType>.<name>`, `.option.<fieldName>.<value>`) and a test re-derives it, because these keys differ by one segment: copy-pasting a neighbouring block's key yields one that EXISTS in both locales and renders a plausible label for the wrong property, so an existence-only check is green for it. Positional keys also let one English word take different Chinese per block (`element:button.label` is 「按钮文字」, `page:tabs.items.label` is 「标签」). `addLabel` becomes REQUIRED on the array variant: it was optional and the inspector fell back to a bare English 'Add' that no locale table could reach. Requiring it deletes the fallback rather than translating it. Option labels are translated too, including the ones ColorVariantPicker renders only as aria-label/title.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
补:等到共享锁了,上面标为「尚未实测」的四项全部跑出,两个反向验证与预判逐条吻合开 PR 时这些还卡在共享 verify 锁上(容器内 4 个以上并行 agent 排队)。现已全部取得实测输出。 1. 改正后的四个受影响文件跑的是新增两个钉 + 两个既有受影响文件( 2. type-check(仓根)3. 两张 i18n 门 + 控制字节两个 baseline 文件确认未被改动。注意 4. 反向验证 A —— 删 1 个 zh 键预判:zh 完整性断言精确翻红并指名该键;「zh 不等于 en」断言保持绿;渲染钉翻红。 逐条吻合。只有 2 条红,「zh 不等于 en」不在其中 —— 正如预判: 5. 反向验证 B —— 把 label 指向邻块一个已存在的键预判:两条完整性断言都保持绿(键确实存在),只有推导断言翻红。 逐条吻合,而且这一条是本 PR 里最值钱的一次测量:红的只有 1 条,两条完整性断言全绿,渲染钉也全绿(两个键的英文都是 两个变异均已回退, Generated by Claude Code |
|
PM 验收(session_01GTRjn8xBqp75dk7kFupVRt):通过,转 ready 并挂 auto-merge。#3913 落地。 核验记录(head
154 条 zh 译文为非母语审校(dev 已披露)—— 维护者若调词只动 i18n.ts 单点。out-of-scope #3963 由 PM 分诊(另评)。 Generated by Claude Code |
Fixes #3913
症状与机制
PageBlockInspector的外围 chrome 从一开始就走翻译表(t('engine.inspector.pageBlock.properties')等),但面板的内容 ——previews/block-config.ts里每个块类型的 curated 字段 label —— 是英文字面量,renderField原样交给字段组件。于是 zh-CN 管理员打开任意页面块,看到的是「属性」标题下面一摞英文字段名:同一面板两种语言,任意块、无需特殊数据即可复现。改法:label 存翻译键,渲染处过 t()
157 个字面量(152 个字段/选项 label + 5 个
addLabel)全部改为翻译键,t(key, locale)在渲染处解析;154 个去重后的键同时补进views/metadata-admin/i18n.ts的 en-US 与 zh-CN 两侧(该表只有这两种语言)。t()未命中时原样返回键,所以漏译在每种语言里都是显眼的键串,而不是在一种语言里静默显示英文。键形状由 label 在
BLOCK_CONFIG里的位置决定,这一点是承重的而非命名偏好:engine.inspector.pageBlock.field.{blockType}.{name}engine.inspector.pageBlock.field.{blockType}.{array}.{name}engine.inspector.pageBlock.add.{blockType}.{name}engine.inspector.pageBlock.option.{fieldName}.{value}选项键故意不带块类型,所以
format的 number/currency/percent 在object-metric与element:number之间共享一份翻译(154 = 157 去掉 3 个这样的重复);字段键故意带块类型,因为同一个英文词在不同块里需要不同中文 ——element:button.label是按钮文案「按钮文字」,page:tabs.items.label是标签页标题「标签」;Add section在这里是「添加分区」,而表单布局画布的designer.canvas.addSection仍是「添加分组」。一键一义,这些区分才表达得出来。en 逐字节未变(已实测,非抽样)
en-US 是本仓基准语言。把全部 157 个键按 en 值反向代回,与改动前的文件做字节比对,数据区完全一致:
addLabel由可选改为必填addLabel原是可选的,array 分支在缺失时渲染裸英文'Add'—— 一个任何翻译表都碰不到的字面量。这里删掉这个兜底而不是翻译它:类型改成必填,新增 array 字段不写 add 按钮键就编译不过。这条比加一个?? 'Add'的默认键更符合契约优先 —— 宽容的消费端正是漏译藏身之处。钉子
previews/__tests__/block-config-i18n.test.ts(结构)+inspectors/PageBlockInspector.i18n.test.tsx(渲染),两者钉的是不同的事实:一个是表数据,一个是屏幕。renderField有 11 处读f.label(8 处作label=属性、3 处作 Label 元素的子节点),外加两处选项列表,每一处都是一次「忘记过 t()」的机会 —— 键全对的表照样能在屏幕上渲染出裸键。结构钉的关键一条不是「键在不在」,而是键等于位置推导出的那个键:这些键之间只差一个 segment,新增块时现实的失误不是拼错,而是从邻块复制粘贴 —— 那样的键在两种语言里都存在、渲染出一个看起来合理但属于另一个属性的 label,只查存在性的断言对此全绿。所以测试从
BLOCK_CONFIG的结构重新推导四种键形并与表里存的比对。渲染钉覆盖 zh-CN/en-US 同块对照、嵌套 item label、select 选项、以及
ColorVariantPicker只以aria-label/title渲染的色板选项(文本查询看不见,恰是漏译藏身处)。反向验证(方向先预判,再跑;两条逐条吻合。完整输出见本 PR 首条评论)
两个变异的预判方向在写代码时就已记录,变异不提交,跑完均已回退:
field.record:alert.dismissible)—— 预判:zh 完整性断言精确翻红并指名该键;「zh 不等于 en」断言保持绿(t()兜底返回键本身,与英文值不同);渲染钉在中文文本与裸键守卫两处翻红。record:alert.title指向page:card.title)—— 预判:两条完整性断言都保持绿(该键在两语里确实存在),只有推导断言翻红。这一条是本 PR 里推导断言存在的全部理由:它是「只查键在不在」结构上看不见的那类失误。实测结果与两条预判逐条吻合:
× every key resolves in zh-CN精确指名field.record:alert.dismissible,渲染钉找不到「可关闭」),Tests 2 failed | 15 passed。「zh 不等于 en」未翻红 —— 正如预判,t()兜底返回键本身、与英文值不同,那条断言无从触发;这也说明完整性必须单独断一条,不能靠它间接覆盖。× stores exactly the key its position implies,消息里同时给出存的键与位置应得的键),Tests 1 failed | 16 passed。两条完整性断言全绿,渲染钉也全绿 —— 两个键的英文都是Title、中文都是「标题」,屏幕上看不出异常。即:只断言「键在两语里都存在」时,这个把 label 指到别的属性上的失误会完全静默。推导断言的存在理由由此是实测,而非论证。边界
BlockPropField没有格式/校验能力位 —— 所有标识符类字段(sectionname、tabkey、accordionvalue)只能靠 placeholder 陈述约定 #3912(同文件另一缺陷:BlockPropField的格式/校验能力位)。designer.canvas.addSection保持原状。PageBlockInspector.tsx自身 JSX 里还有一批硬编码英文(列表行的Add按钮、Remove/Remove item的 aria-label、Invalid JSON、两个 placeholder)—— 从来不是block-config的 label,另一条机制,已另开 PageBlockInspector 自身的 chrome 字面量未过 t() —— 列表「Add」按钮、Remove aria-label、Invalid JSON、两个 placeholder 在 zh-CN 下仍是英文 #3963,未夹带。这正是首轮全量跑里唯一那条失败断言暴露的:笔者原本写了queryByText('Add')为空,过头了。现在渲染钉里有一条用例断言这些英文仍然存在,所以 PageBlockInspector 自身的 chrome 字面量未过 t() —— 列表「Add」按钮、Remove aria-label、Invalid JSON、两个 placeholder 在 zh-CN 下仍是英文 #3963 落地时它会精确翻红并被收紧,两个修复不会静默重叠。校验记录(全部已实测)
pnpm exec vitest run packages/app-shell/src/views/metadata-admin(仓根,--maxWorkers=2,持共享 verify 锁,NODE_OPTIONS=--max-old-space-size=4096)—— 首轮 1350 passed / 1 failed,那一条即上述写过头的断言,已改正。Test Files 4 passed (4)/Tests 41 passed (41)(含PageBlockInspector.sectionName.test.tsx在 en-US 下对Add section的现成断言,未改一行仍绿 —— 这本身就是 en 未漂移的一个钉)。pnpm exec turbo run type-check --concurrency=2:78 successful, 78 total。pnpm check:i18n-keysexit=0(2316/2316 literal keys resolve;本表计入1076 module-local table,按设计跳过),pnpm check:i18n-driftexit=0(0 en value(s) changed),两个 baseline 文件均未改动。node scripts/check-control-bytes.mjsOK,另对 7 个改动文件做了grep -naP控制字节自扫(gate 有已知盲区),干净。关于两张 i18n 门:
pnpm check:i18n-keys把metadata-admin/i18n.ts明确列在EXCLUDED_TRANSLATORS(模块内私有 label 表,按设计不进 locale pack,forwardedScope覆盖整棵 metadata-admin 子树),pnpm check:i18n-drift只读packages/i18n/src/locales/*.ts—— 两张门都不覆盖这张表,所以它们的绿是「未被本改动破坏」,不是「本表已被门保护」;表内的完整性由上面那两个钉负责。复用 spec 侧翻译面的取舍(PM 要求论证)
查过复用路径,结论是维持包内新增键。
packages/i18n/src/locales/en.ts里确有字面重合的条目(groupByField: 'Group by field'、striped: 'Striped rows'),但它们在console.*命名空间下、属于视图设计器那个面,与页面块检查器只是词面撞车;复用会让这里的 label 随另一个面板的措辞改动而变,且只覆盖 154 个键里的少数几个,剩下的仍要新建 —— 反而做出一张 split-brain 的表。跨进 i18next pack 还会把 metadata-admin 子树的一部分拖进check-i18n-call-site-keys的面(而EXCLUDED_TRANSLATORS声明整棵子树是本地表),并要求给设计器内部文案凑齐 10 种语言。浏览器验证为何跳过
localeprop 在真实 zh-CN 会话里确实到位这件事,由本 issue 的症状本身证明 —— 报告说 chrome 的「属性」是中文而内容是英文,两者读的是同一个locale变量,所以管线无需浏览器再确认,变的只有内容。渲染钉用真实组件跑了 7 个块类型,含浏览器截图也看不见的aria-label色板选项。