Skip to content

fix(metadata-admin): block-config 的 placeholder 提示随语言切换(#3979) - #3993

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3979-block-config-placeholder-i18n
Aug 9, 2026
Merged

fix(metadata-admin): block-config 的 placeholder 提示随语言切换(#3979)#3993
yinlianghui merged 1 commit into
mainfrom
claude/issue-3979-block-config-placeholder-i18n

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3979

#3913(PR #3970)把 block-config.tslabel / addLabel / option label 搬成翻译键,停在了相邻的一列。18 个 placeholder 里有 8 个是语言内容,至今仍是显示文本:zh-CN 管理员打开 page:header,「图标」下面的输入框提示还是 lucide icon name;record:details 的「名称(i18n 键)」下面是 snake_case, e.g. contact_info —— 正好在 #3913 / #3963 刚修好的那个面板里。

本 PR 把这 8 个改为 engine.inspector.pageBlock.placeholder. 后接 blockType 与字段路径的键,渲染处过 t(),en-US / zh-CN 双语补齐。en 值与被替换的字面量逐字节相等(含 Text… 的 U+2026 省略号,已用脚本按 hex 比对),所以英文面板没有任何变化。

逐条判定表(18 条,判定理由在最后一列)

# 位置 判定 理由
1 object-grid.pageSize 20 字面 默认行数,是要输入的
2 object-form.columns 2 字面 网格列数
3 object-metric.icon lucide icon name 描述"该输入什么"的散文,不是值本身;lucide 作为库名在 zh 里保留
4 grid.columns 3 字面 网格列数
5 grid.gap 4 字面 间距档位
6 element:text.content Text… 名词性提示文案;zh 为「文本…」
7 element:image.src https://… 字面 URL scheme,任何语言下值都这样开头;翻译它会误导
8 element:definition-list.columns 1 字面 列数
9 element:repeater.limit 10 字面 条数上限
10 element:button.icon lucide icon name #3
11 element:button.action { "type": "url", "target": "/environments" } 字面 作者要照抄的 JSON 样例;键被"翻译"后是 InlineActionSchema 会拒的元数据
12 page:header.icon lucide icon name #3(issue 标题点名的那一处)
13 record:related_list.limit 10 字面 条数上限
14 record:details.sections.name snake_case, e.g. contact_info (边缘条目) 「格式说明 + 示例」混合面:主体是散文,snake_casecontact_info 两个 token 在 zh 值里原样保留(snake_case,例如:contact_info)。仓内已有同形先例:engine.inspector.view.objectPlaceholder = e.g. crm_lead → 「例如:crm_lead」,engine.inspector.report.namePlaceholder = e.g. pipeline_by_stage → 「例如:pipeline_by_stage」
15 record:details.sections.columns 2 字面 列数
16 record:alert.icon lucide icon name #3
17 record:quick_actions.actionNames action name 散文提示;zh 「动作名称」
18 ai:input.agentName agent name 散文提示;zh 「智能体名称」

合计 8 键 / 10 字面,与 issue 正文的 8/10 拆分一致。

⚠️ 与派发单括注的一处分歧(4 个 lucide 图标名)

派发提示写的是「lucide 图标名 ×4 ... 保持字面」,但 issue 正文的命中表把这 4 条列在「8 个有语言内容」之内,而且 issue 标题与「影响」段正是拿它举证(「打开 page:header,字段名「图标」是中文,它下面输入框的提示是 lucide icon name」)。逐条判定的结论站正文一边,理由是:

若维护者仍要求 4 条保持字面,回退面很小:4 行 { key: … } 改回 { literal: … }、删 8 行译文、把 families.placeholder 由 8 改 4、并把这 4 条加进字面台账。

机制:混合面写进类型,而不是"约定 + 豁免清单"

issue 正文把这条列为留给实施者论证的契约面小决定(两个选项:显式豁免台账,或在 BlockPropField 上拆成两个字段)。本 PR 取第三种、也是两者的严格上界 —— 一个可辨识联合:

export type PlaceholderSpec = { key: string; literal?: never } | { literal: string; key?: never };
  • 没有照抄 label 那条改必填:placeholder 保持可选(无提示的字段是合法的);只有 addLabel页面块检查器的 PROPERTIES 面在 zh-CN 会话下是英文的 —— curated 字段 label 走字面量、只有外围章节标题走 t() #3913 里改了必填。
  • 裸字符串不再可赋值:placeholder: 'lucide icon name'(本文件在此前的写法,也是任何生成新字段的东西最先伸手去写的写法)现在是类型错误。这比那 8 个字符串本身更要紧 —— type-check 在每条流水线和编辑器里都跑,错误在写下的地方就被拦住,而不是变成中文面板里的一个英文框、等某个评审者注意到。
  • 互斥的 never 让"两个都填"也编译不过,渲染处不需要在两者之间挑赢家。
  • 渲染处四个 placeholder= 站点(number / json / string-list / 默认 text)收敛到一个 placeholderText() 解析器 —— 面板内容当初之所以在中文面板里是英文,就是因为有一列绕过了访问器。

键形沿 #3913 的位置推导惯例(engine.inspector.pageBlock.placeholder. + blockType + 字段路径),因此并入了那条结构钉的推导断言,成为第五个键族;推导测试不需要新族的例外分支,只多了一次 walk 里的采集。4 个 lucide 提示是 4 个不同的键、英文文本相同 —— 与 field 键携带 blockType 同理:一个键不能说两件事,将来某个块想换措辞不必先拆键。

钉子

  1. 走键的一半并入 block-config-i18n.test.ts:families.placeholder 精确等于 8(像 add 那样精确,新增的散文 placeholder 缺翻译即红);en 侧 8 个键全部en-US labels are unchanged 样本(不是抽样 —— 这一列是手工迁移的,en 是"键能解析"与"键解析出原来那句话"之间唯一的东西);zh≠en、两语齐备、位置推导相等由既有的 5 条断言自动覆盖。
  2. 保持字面的一半钉成逐条带理由的台账(10 条,because 必须 > 10 字符)+ 一条"字面不得是散文"的正则哨兵(相邻两个字母词 = 句子的签名;{ literal: 'lucide icon name' } 会被它抓住,单词提示不会 —— 所以台账是穷举而非启发式)。理由:"顺手把另外十个也翻掉"是最可能的下一次编辑。
  3. 渲染面PageBlockInspector.i18n.test.tsx 加了成对断言:散文随语言变、值不变(同一个 record:details fixture 里 sections.name 变、旁边的 sections.columns = 2 不变)。另补 expectNoRawKeyPlaceholders() —— placeholder 是属性,textContent 看不见它,既有的 expectNoRawKeys() 结构上抓不到裸键进 placeholder,这正是 PageBlockInspector 自身的 chrome 字面量未过 t() —— 列表「Add」按钮、Remove aria-label、Invalid JSON、两个 placeholder 在 zh-CN 下仍是英文 #3963 那两个 placeholder 躲过 页面块检查器的 PROPERTIES 面在 zh-CN 会话下是英文的 —— curated 字段 label 走字面量、只有外围章节标题走 t() #3913 评审的缝。
  4. fixture 三态处置:block-config.test.tsexpect(nameField.placeholder).toMatch(/snake_case/) 属"钉住了被改掉的形态"——改判为断言解析后的提示且两语都断言(与同一测试里 页面块检查器的 PROPERTIES 面在 zh-CN 会话下是英文的 —— curated 字段 label 走字面量、只有外围章节标题走 t() #3913 已经这么做的 label 断言同形)。这是更强的说法:snake_case 这个 token 必须活过翻译,而不只是在英文里存在。json placeholder 那条从"存在"加强到"必须是 literal"。PageBlockInspector.sectionName.test.tsxgetAllByPlaceholderText(/snake_case/i) 无需改动 —— 它跑 en-US,而 en 值逐字节不变(反向验证里它是最响的一处红)。

反向验证(先预判方向,再跑;变异提交)

M1 —— 渲染处不再调 t()(placeholderText 直接返回 p.key):

M2 —— 把一个字面值改成键(element:button.action 的 JSON 样例改 { key: … },不加译文):

  • 预判:block-config.test.ts 的 json-must-be-literal 红;block-config-i18n.test.tsfamilies.placeholder(8→9)、字面台账等值、en/zh 解析三条红;渲染面 passes example VALUES through untranslated 红。
  • 实测:Tests 8 failed | 45 passed,红的正是上述 8 条(3 个文件都红)。方向与预判一致

两次变异后 git checkout -- 复原,git status 干净,树等于提交 622ff385

验证

边界


Generated by Claude Code

#3913 把 block-config 的 label/addLabel/option label 搬成翻译键就停在了
相邻的一列。18 个 placeholder 里有 8 个是语言内容,至今仍是显示文本:
zh-CN 管理员打开 page:header,「图标」下面的输入框提示还是
`lucide icon name`;record:details 的「名称(i18n 键)」下面是
`snake_case, e.g. contact_info` —— 正好在 #3913 / #3963 刚修好的面板里。

这 8 个改为 `engine.inspector.pageBlock.placeholder.<blockType>.<字段路径>`,
渲染处过 t();en 值与被替换的字面量逐字节相等(含 U+2026 省略号)。

placeholder 不能照抄 label 那条:它是**混合面**。另外 10 个是要精确输入的
**值** —— 行数 `20`、`https://…`、内联动作样例
`{ "type": "url", "target": "/environments" }` —— 翻译它们是缺陷而不是善意:
被"翻译"过的 JSON 样例是 InlineActionSchema 会拒的元数据。

所以两种含义写进类型,而不是靠约定加豁免清单:

    placeholder?: { key: string } | { literal: string }

裸字符串不再可赋值,`placeholder: 'lucide icon name'` 现在是类型错误 ——
type-check 在每条流水线和编辑器里都跑,错误在写下的地方就被拦住,而不是
变成中文面板里的英文框等人看见。placeholder 保持可选(无提示的字段是合法的),
只有 addLabel 那条按 #3913 改了必填。

钉子:走键的一半并入 #3913 的位置推导断言,成为第五个键族(families.placeholder
= 8,新增的 prose placeholder 缺翻译即红);保持字面的一半钉成逐条带理由的
台账 + "字面不得是散文"的哨兵,因为"顺手把另外十个也翻掉"是最可能的下一次编辑;
渲染面在 PageBlockInspector.i18n.test.tsx 加了成对断言(prose 随语言变、值不变),
并补了 placeholder 属性专用的裸键扫描 —— textContent 看不见属性,这正是 #3963
那两个 placeholder 躲过 #3913 评审的缝。

block-config.test.ts 的 snake_case 断言从原始 placeholder 移到**解析后**的提示
且两语都断言,这是更强的说法:snake_case 这个 token 必须活过翻译。

Fixes #3979

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@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 9:39pm

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-tTboOh2F.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.99KB 56.47KB
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.82KB 10.79KB
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.80KB 59.48KB
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.97KB 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。#3979 落地 —— PageBlockInspector i18n 三部曲(#3913 label / #3963 chrome / #3979 placeholder)收官。

核验记录(head 622ff3859,基 6bd6a4d76,实物核验 + CI 亲读):

  1. 判定表 18 条逐条、划线原则清晰:「要输入的值」(数字/URL/JSON 样例)保持字面并钉台账,「描述该输入什么的散文」走键;边缘条目(snake_case 混合面)有仓内同形先例背书,token 在译文中保留。
  2. 与我派发词括注的分歧,dev 判定正确、PM 采纳:括注「lucide ×4 保持字面」与 issue 正文命中表矛盾且算术自洽不了 —— lucide icon name 是描述性名词短语非输入值,且 PageBlockInspector 自身的 chrome 字面量未过 t() —— 列表「Add」按钮、Remove aria-label、Invalid JSON、两个 placeholder 在 zh-CN 下仍是英文 #3963 已把同面板同类提示(objectPlaceholder/fieldPlaceholder)翻了,保持字面会造成一半中英。站正文一边正确;回退面已写明备用。
  3. 契约面取法比 issue 选项更强且论证充分:可辨识联合 { key } | { literal }(互斥 never)把「裸英文散文」从测试期提前到编译期 —— declared = enforced,无会腐烂的豁免约定层。实施侧小决定的边界判断正确,未滥用升级。
  4. 钉子四层:第五键族并入位置推导 walk(无例外分支)、字面台账带理由+散文哨兵正则、渲染面成对断言(散文变/值不变)、新增 expectNoRawKeyPlaceholders(补上 placeholder 属性面 —— 正是 PageBlockInspector 自身的 chrome 字面量未过 t() —— 列表「Add」按钮、Remove aria-label、Invalid JSON、两个 placeholder 在 zh-CN 下仍是英文 #3963 那两条躲过评审的缝)。en 值 hex 级逐字节等值(含 U+2026)。
  5. 反向验证两变异:M1(渲染不过 t)11 红分布逐条吻合含「表钉看不见渲染」的分工确认;M2(JSON 样例改键)8 红精确。fixture 改判一处(toMatch 加强为两语断言)论证成立。
  6. 门与规程:metadata-admin 全量 1373 + 整包 2880 测试绿;i18n 双门绿;type-check 78/78;控制字节门 + 自扫;changeset patch;fable 0;⛔ releases/ 未触碰。
  7. CI 亲读终态:20/20 全 completed、0 失败(Test shard×4 至 21:45:06Z)。

concern 中 actionNames 译文与 label 近重复为忠实翻译 —— 同意,措辞调整属维护者口味留原样。out-of-scope #3994(检查器共享字段组件无可访问名,16 文件消费面)由 PM 分诊(另评)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 9, 2026 21:55
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit 65d6c07 Aug 9, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3979-block-config-placeholder-i18n branch August 9, 2026 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants