Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

表单渲染器内建 select 分支的空状态文案仍是硬编码英文(form.tsx:1843) #3263

Closed

Description

@xuyushun441-sys

在 objectui#3231 / PR objectui#3262 期间发现,该单已按 scope fence 明确不修(内建分支是另一条独立活路径,PM 要求不要顺手统一)。

现象

packages/components/src/renderers/form/form.tsx 的内建(未注册)select 分支:

if (!options || options.length === 0) {
  return (
    <div className="text-sm text-muted-foreground">
      {emptyHint || 'No options available'}
    </div>
  );
}

'No options available' 是硬编码英文字面量,而同一个组件里 const { t } = useSafeFormTranslation(); 早已可用(form.tsx 顶部的 createSafeTranslation 工厂)。

为什么现在才值得单独处理

objectui#3231 刚把四个已注册选项 widget 的同一句文案接到了 i18n 上,并新增了共享 key:

  • fields.options.empty = No options available
  • fields.options.selectFirst = Select {{fields}} first

两个 key 已经补齐十个语言包,并且 fields.options.selectFirst 已被 form.tsx 的门控提示(gatedHint)使用 —— 也就是说这个文件里已经有一半走了 i18n,另一半没有。内建分支的这句 'No options available' 是同一个文件里唯一剩下的硬编码副本。

建议

把它改成 t('fields.options.empty'),并在 useSafeFormTranslation 的 defaults map 里补上同名英文默认值(与 fields.options.selectFirst 的做法一致,这样无 provider 时行为不变)。

预期风险很低:createSafeTranslation 在没有翻译时回落到 defaults map,英文渲染结果与现状逐字相同,现有断言(如 form-cascading-select.test.tsx)不受影响。需要补一条测试钉住 zh locale 下渲染中文。

注意

这条不要顺手把内建分支和 OptionsEmptyState(packages/fields)统一 —— 两者是不同包、不同渲染路径,共享的应当只是 i18n key,不是组件。

关联:objectui#3231、PR objectui#3262。

Activity

  1. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    ContributorAuthor

    认领:PM 循环第 4 轮
    会话:session_01NVPjPzmmAJ2Ngtvgg5MSRa(objectui 分片 PM,账号 xuyushun441-sys)
    分支:claude/issue-3263-builtin-select-empty-i18n
    Worktree:objectui-issue-3263

    前置已解除,且本单是 #3231 刻意留下的最后一块

    #3231(PR #3262)已落在 main(825bbe33c),它补的两个 key —— fields.options.empty 与 fields.options.selectFirst —— 已在十个语言包里就位,而且 fields.options.selectFirst 已经被 form.tsx 的门控提示(gatedHint)使用。

    所以现在 form.tsx 里是「一半走了 i18n、一半没走」,内建 select 分支那句 'No options available' 是同一个文件里唯一剩下的硬编码副本。这是当时 scope fence 明确留的口子,不是遗漏。

    派发范围(小且明确)

    1. form.tsx 内建分支改用 t('fields.options.empty');
    2. 在 useSafeFormTranslation 的 defaults map 补同名英文默认值 —— 与 fields.options.selectFirst 已有的做法一致,这样无 provider 时英文渲染逐字不变;
    3. 补一条测试钉住 zh locale 下渲染中文。

    两条边界

    ⛔ 不要把内建分支与 packages/fields 的 OptionsEmptyState 统一成一个组件。原单说得对:两者是不同包、不同渲染路径,该共享的只有 i18n key,不是组件。内建分支走的是 BUILTIN_FIELD_TYPES 命中时的内联渲染,已注册 widget 走注册表 —— 把它们并起来会把一条路径的约束强加给另一条。

    ⚠️ 现有断言(如 form-cascading-select.test.tsx)预期英文,defaults map 落回后应当逐字不变;若有测试因此转红,说明 defaults 没接对,不要去改断言。


    Generated by Claude Code

  2. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    ContributorAuthor

    PM 验收:ACCEPT — PR #3271,已入合并队列

    CI 16 项全 success;4 个文件,全在 packages/components(packages/i18n 未动 —— 两个 key 由 #3231 补齐,它核对过 en.ts/zh.ts 才确认不需要改)。边界遵守:没有把内建分支与 packages/fields 的 OptionsEmptyState 做组件级统一,只共享 i18n key。

    它发现了一个我和 issue 都没预见的约束

    不能直接在原地调 t():renderFieldComponent 是普通 helper 而不是组件,它在"已注册 widget"那条路径上会提前 return —— 在那里调 hook 就是条件调用(rules-of-hooks)。所以空状态被提成同文件里的小组件 BuiltinSelectEmptyState。

    更要紧的是它顺带守住了一处无障碍接线:该组件必须转发 ...rest,因为 FormControl 是 Radix Slot,控件的 id / aria-describedby / aria-invalid 是它注入的 —— 丢掉 rest props 会让该字段 label 的 for 指向一个不存在的 id。这一点有测试钉住,而且变异验证的 DOM dump 里顺便确认了 id="_r_4_-form-item" 与 aria-describedby 仍落在那个 div 上。一个只关心文案的改动,很容易在这里静默弄坏 label 关联。

    缺陷的真实形态比 issue 描述的更难看

    PR 正文点出来了:同一个 widget 在 zh 会话里会中途换语言 —— 被 dependsOn 门控时显示「请先选择Country」,用户把父字段填成一个匹配不到选项的值之后,同一个框立刻变成英文 "No options available"。不是"有一句没翻译",是一个控件在一次交互里跨了两种语言。

    测试也钉住了这条:门控解除后两句话仍在同一种语言里。

    现有英文断言全绿,没被改动

    form-cascading-select.test.tsx、plugin-form/selectOptions.test.tsx 未改且通过 —— 说明 defaults 回落接对了。我在派发时说过"若它们转红,说明接线错了,去修接线不要改断言",没有触发。

    越界发现 #3272,已入队

    form.tsx 里还有一批用户可见的硬编码英文:FullscreenTextarea 整个全屏编辑对话框('Edit text' 标题、sr-only 描述、Cancel/Done 按钮)、aria-label="required",以及内建 select 分支里一处够不到的死兜底 placeholder || 'Select an option'(唯一调用点已经传了 t('common.selectOption'))。

    其中 submitLabel='Submit' / cancelLabel='Cancel' 是 schema 默认值,它标注了这一条牵涉「默认值 vs 渲染时回落」的语义选择,需要定方向 —— 没有自行决定,对。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions