Skip to content

fix(metadata-admin): 页面块检查器自身的 chrome 跟随会话语言 (#3963) - #3980

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3963-inspector-chrome-i18n
Aug 9, 2026
Merged

fix(metadata-admin): 页面块检查器自身的 chrome 跟随会话语言 (#3963)#3980
yinlianghui merged 1 commit into
mainfrom
claude/issue-3963-inspector-chrome-i18n

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3963

基:5bfaabde0(含 #3913 的落地 62c644168,已 git merge-base --is-ancestor 核验)。

症状与机制

#3913(PR #3970)修的是 previews/block-config.ts 的 curated 属性 label —— 表数据。本单是另一条机制:PageBlockInspector.tsx 自己 JSX 里的硬编码英文,从来不是 block-config 的 label,任何翻译表都碰不到。#3913 之后症状恰好反了过来:zh-CN 管理员打开 record:details 检查器,「分区」「字段」都是中文,每个字段列表下面的添加按钮却是英文 Add

字面量清扫(逐处 grep,含处置)

worktree 行 位置 原字面量 处置
62 ObjectPickerField 自由文本兜底 placeholder="snake_case object" 新键 …pageBlock.objectPlaceholder
100 FieldListField 自由文本行 placeholder="field name" 新键 …pageBlock.fieldPlaceholder
104 FieldListField 行删除 aria-label="Remove" 新键 …pageBlock.list.remove
112 FieldListField 行添加 Add 新键 …pageBlock.list.add
171 InspectorJsonField 解析错误 setError('Invalid JSON') 复用现成键 engine.form.invalidJson(不新增)
466 string-list 分支行删除 aria-label="Remove" …pageBlock.list.remove
473 string-list 分支行添加 Add …pageBlock.list.add
492 array 分支 item 卡片删除 aria-label="Remove item" 新键 …pageBlock.list.removeItem

清扫方式:aria-label=/title=/alt=/placeholder= 属性全量列举 + 自闭合标签后的裸文本节点扫描。零残留,唯一剩下的属性字面量是 SelectValue placeholder="—"(全角破折号,无语言内容,不译)。阳性反查:对同一文件 grep Add|Remove|Invalid|field name|snake_case,命中只剩标识符(InspectorRemoveButtonsetInvalid)与注释,证明模式确实找得到这些词、而不是模式失效。

分诊评论给的 main 锚点(61/101/147/440/465)逐条对上,只有行号随 #3913 漂移。

键命名:共享键,且不放在 .field.

分诊席把「共享 addRow 键 vs 逐点键」留给 PR 决定。选共享键:两个添加按钮语义完全相同(给这个标量列表加一行),两处本就没有 per-field 文案。逐点键会造出两个值相同、译者无从区分的键 —— 「一键一义」在反方向失效。

同时对 issue 建议的键名 engine.inspector.pageBlock.field.addRow 做了一处偏离:不放进 .field. 命名空间。.field.* / .add.* / .option.* 三支的契约是「由 label 在 BLOCK_CONFIG 里的位置推导」,previews/__tests__/block-config-i18n.test.ts 正是照这个形状反推的;chrome 键混进去会长得像一个叫 addRow 的块类型,而这恰是那条推导断言在防的复制粘贴失误。所以 chrome 键与 kind/close/properties/advanced/remove 平级:

engine.inspector.pageBlock.list.add          Add        / 添加
engine.inspector.pageBlock.list.remove       Remove     / 删除
engine.inspector.pageBlock.list.removeItem   Remove item/ 删除项
engine.inspector.pageBlock.objectPlaceholder snake_case object / snake_case 对象名
engine.inspector.pageBlock.fieldPlaceholder  field name / 字段名

list.* 这个子命名空间照抄 engine.inspector.flowNode.list.* —— 兄弟面板里同一种列表编辑器的现成惯例;…Placeholder 后缀照抄同表已有的 engine.inspector.widget.datasetPlaceholder。zh 取值与既有惯例对齐(删除engine.form.remove,删除项flowNode.list.remove)。

没有复用 engine.form.add/engine.form.remove(值恰好也是 Add/添加):那是 SchemaForm 终端用户表单的面,复用会让设计器检查器的措辞随另一个面板改动而变 —— PR #3970 对同一面板已论证过这条 split-brain 取舍。Invalid JSON 反过来必须复用:engine.form.invalidJson 已存在,且共享的 translateValidationMessage() 已经把 'invalid json' 映射到它,这里原本是把现成键绕过去了,新增第二个键就是给同一个事实造第二种措辞。

en 侧五个新键的值逐字等于原来的字面量,所以英文面无漂移(下面 en 钉与 check:i18n-drift 双向印证)。

顺带修正:错误消息不再冻结语言

InspectorJsonField 原来把已翻译的字符串存进 state。改成存布尔事实、渲染时解析 —— 否则解析失败后切换语言,消息会停在失败当时那门语言。state 存事实、渲染管措辞。

PR #3970 边界钉的收紧

PR #3970 里那条 leaves only the known non-block-config chrome in English (objectui#3963) 断言这些英文仍然存在 —— 设计好的接力信号。它按预期精确翻红,收紧而非删除:原位置留下一段注释说明「边界移动了、没有消失」,断言本身变成新的 describe,钉同一批站点现在经由目录解析、两语齐备。

钉子

  1. PageBlockInspector's own chrome follows the locale (#3963)(5 例)—— zh-CN 下八处站点全中文 + 英文原文逐条 queryBy… 为空;en-US 下逐条仍是原英文(基准语言未动);string-list 分支单独一例(它与 FieldListField 是同一套 chrome 的两份独立拷贝,上面的 describe 没有任何 fixture 渲染到它);object picker 的 placeholder;JSON 解析错误在两语下的解析值。两个 aria-label 与两个 placeholder 对文本查询和截图都不可见,所以断言走 getByLabelText / getByPlaceholderText —— 这也正是它们能躲过 页面块检查器的 PROPERTIES 面在 zh-CN 会话下是英文的 —— curated 字段 label 走字面量、只有外围章节标题走 t() #3913 那轮 review 的原因。
  2. chrome keys resolve in both locales (#3963)(4 例)—— 形状照 页面块检查器的 PROPERTIES 面在 zh-CN 会话下是英文的 —— curated 字段 label 走字面量、只有外围章节标题走 t() #3913 的结构钉,但适配了唯一的差别:chrome 键没有「位置」可推导,它们是一个文件里的 t('…') 调用点。所以键表不是手抄进测试,而是从组件源码读回来(readFileSync + 正则取字面量键,动态 t(f.label) 自然落在 block-config 那条钉里),于是将来谁加一个只有 en 译文的 chrome 键,这里直接翻红,不依赖谁记得来扩列表。完整性照 页面块检查器的 PROPERTIES 面在 zh-CN 会话下是英文的 —— curated 字段 label 走字面量、只有外围章节标题走 t() #3913 的写法用 t() 度量(未命中原样返回键,t(k,l) === k 恰好就是「这门语言没有条目」)。另有 LOCALE_INVARIANT 台账一条:engine.inspector.pageBlock.id(ID 是缩写,zh 不译)—— 台账而非放宽,其余全部保持红。

反向验证(方向先预判、写在 scratchpad 再跑;两个变异均已回退,不提交)

变异 A —— 还原 aria-label="Remove item" 字面量。 预判:zh 例翻红、键扫描例翻红,而 en 例保持绿(还原的字面量就是 en 值 —— en 钉是漂移守卫,不是回归探测器,这个方向是反的,如实记录)。实测 Tests 2 failed | 14 passed,两处预判的翻红点逐条命中(Unable to find a label with the text of: 删除项;expected [ …(16) ] to include 'engine.inspector.pageBlock.list.remov…'),en 例如预判保持绿。与预判的偏差:我原写「预计 3 条红」,把同一个用例里的两句断言数成了两条 —— 首句断言抛出后第二句不再执行,用例数是 2。方向全部吻合,计数是我数错了。

变异 B —— 删掉 zh 侧 list.add 一条。 预判:every key resolves in zh-CN 精确指名该键;zh-CN is actually translated, not a copy of en-US 保持绿(t() 兜底返回键本身,与 Add 不同,那条断言无从触发 —— 与 PR #3970 对自己那个变异测到的方向一致);两条渲染例翻红。实测 Tests 5 failed | 11 passed,AssertionError: … + [ "engine.inspector.pageBlock.list.add" ] 精确指名,「zh 不等于 en」如预判保持绿。比预判多两条红:#3913 原有的两条 zh 渲染例(renders curated field labels…translates the array add button…)也翻了 —— 它们的 expectNoRawKeys() 扫的是整个面板 body,于是也看见了 chrome 漏键渲染出的裸键串。这是「诊断变多而非变少」的方向,我低估了爆炸半径,如实记录。

消费半径清扫

按规则不只扫改动包:全仓 grep 断言这些字面量的测试/fixture/e2e,以及渲染 PageBlockInspector 的全部测试文件 —— 命中只在 packages/app-shell/src/views/metadata-admin 内(block-config-i18npage-block-pathvisibleWhensectionName、本文件),已被下面的全量跑覆盖;其它包没有 fixture 喂给这个组件。

校验记录(全部实测)

  • pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build(持共享锁)—— 通过。
  • 仓根 pnpm exec vitest run packages/app-shell/src/views/metadata-admin --maxWorkers=2(持共享锁,NODE_OPTIONS=--max-old-space-size=4096)—— Test Files 135 passed (135) / Tests 1360 passed | 1 skipped (1361),exit=0。
  • 仓根 pnpm exec turbo run type-check --concurrency=2 —— 78 successful, 78 total,exit=0。
  • pnpm check:i18n-keys exit=0(2316/2316 literal keys resolve;module-local table 计数 1076 → 1084,恰好等于本 PR 新增的 8 个 t() 调用点)。pnpm check:i18n-drift exit=0(0 en value(s) changed)。两张门都不覆盖 metadata-admin/i18n.ts 本表(它在 EXCLUDED_TRANSLATORS / 门只读 locale pack),所以它们的绿是「未被本改动破坏」,表内完整性由上面第 2 条钉负责。
  • node scripts/check-control-bytes.mjs OK(3874 文件);另对 3 个改动文件做了 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 自扫(gate 有已知盲区),干净。
  • pnpm exec eslint 三个改动文件:0 errors(33 条既存 any/react-refresh warning,与本改动无关)。
  • node scripts/check-changeset-presence.mjs exit=0;changeset @object-ui/app-shell patch。

边界


Generated by Claude Code

…3963)

The panel's own JSX carried English that no translation table could reach —
the row-adder of every field list, the remove aria-labels, the JSON parse
error and two fallback placeholders. All resolve through the catalog now:
five new `engine.inspector.pageBlock.*` keys in both locales, plus the
existing `engine.form.invalidJson` reused rather than duplicated.

`InspectorJsonField` now stores the parse failure as a boolean and resolves
the message at render, so it follows a later locale switch instead of
freezing the language that was active when the parse failed.

#3913's boundary pin (which asserted these literals were STILL English) is
tightened into a describe that asserts the same sites resolve through the
catalog in both locales, plus a completeness pin that reads the key list back
out of the component source.
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 9, 2026 8:15pm

Request Review

@github-actions github-actions Bot added the tests label Aug 9, 2026
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-DAG1iC_Q.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.66KB 3.13KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 483.34KB 106.60KB
core (index.js) 3.00KB 1.14KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 139.61KB 35.99KB
fields (index.js) 227.92KB 56.45KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.54KB 10.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.23KB 12.45KB
plugin-charts (index.js) 61.49KB 17.48KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 118.39KB 30.67KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 237.03KB 59.17KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.29KB 27.16KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 187.90KB 49.79KB
plugin-kanban (index.js) 48.53KB 13.38KB
plugin-list (index.js) 109.73KB 26.55KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 23.71KB 7.95KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.23KB 0.66KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

PM 验收(session_01GTRjn8xBqp75dk7kFupVRt):通过,转 ready 并挂 auto-merge。#3963 落地 —— PageBlockInspector 的 i18n 双单(#3913 表数据 + #3963 chrome)至此闭环。

核验记录(head 11e510a67,基 5bfaabde0,实物核验 + CI 亲读):

  1. 8 处清扫齐 + 阳性反查:5 新键 + engine.form.invalidJson 复用(不给同一事实造第二措辞 —— 正确);唯一残留 placeholder="—" 无语言内容的豁免成立;placeholder={f.plaeholder} 表数据面正确划界立 block-config.ts 的 8 个 placeholder 仍是英文显示文本 —— #3913 只搬了 label/addLabel/option,zh-CN 面板里的输入框提示还是 "lucide icon name" #3979
  2. 键命名偏离 issue 建议的论证成立,PM 采纳:chrome 键不入 .field.(那族契约是位置推导,混入即造出长得像块类型的键 —— 恰是推导断言在防的失误);list.* 照抄兄弟面板惯例;不复用 engine.form.add/remove(split-brain)但必须复用 invalidJson(translateValidationMessage 已映射)—— 两个方向的取舍都对。
  3. 边界钉接力精确执行:PR3970 的钉按设计翻红并收紧为「同站点经目录解析、两语齐备」,原位留迁移注释未删除。
  4. 顺带修正有理:InspectorJsonField error state 字符串→布尔(否则解析失败后切语言消息冻结在旧语言 —— 与本单同类缺陷),3 行,已披露。
  5. 反向验证诚实:变异 A 计数偏差(同用例两断言数成两条)如实记录、方向全对;变异 B 比预判多两条红(页面块检查器的 PROPERTIES 面在 zh-CN 会话下是英文的 —— curated 字段 label 走字面量、只有外围章节标题走 t() #3913 的 expectNoRawKeys 全 body 扫描也看见 chrome 裸键)—— 「诊断变多」方向,爆炸半径低估已如实记录并在 PR 写明机制。
  6. 门与规程:metadata-admin 全量 1361 绿;i18n 双门绿(module-local 计数 +8 恰等于新增调用点);type-check 78/78;控制字节门;changeset patch;fable 0;⛔ releases/ 未触碰;正则抽键的前提弱点已在测试注释写明。
  7. CI 亲读终态:20/20 全 completed、0 失败(Test shard×4 至 20:21:13Z)。

out-of-scope #3979(block-config placeholder 混合面)由 PM 分诊(候选晋级,「不能照抄 label 改必填”的取舍提醒已记入正文)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 9, 2026 20:37
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit 708aaf8 Aug 9, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3963-inspector-chrome-i18n branch August 9, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants