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.

createSafeTranslation 的无 provider 兜底从不读调用点的内联 defaultValue —— 只认自己那张 defaults 表,表里没有就把 raw key 渲染给用户(useDetailTranslation 实测 16 个 key) #3865

Closed

Description

@yinlianghui

在 #3546 切片六(PR 回填 perm + home 14 个缺失 key)核实"这些 key 在两条路径上分别怎么渲染"时量出,不在该 PR 围栏内(修法要动 packages/i18n 的 helper 或各组件的 defaults 表,与本切片"只补语言包"的纪律相斥),单独记录。

机制

packages/i18n/src/useSafeTranslation.ts:20 的无 provider 兜底:

const fallbackT = (key: string, options?: Record< string, unknown >) => {
  let value = defaults[key] || key;          // ← 只查 defaults 表,查不到就是 key 本身
  if (options) {
    for (const [k, v] of Object.entries(options)) {
      value = value.split(`{{${k}}}`).join(String(v));   // ← defaultValue 在这里被当成插值变量
    }
  }
  return value;
};

于是对 t('perm.facet.none', { defaultValue: 'None' }) 这种带内联 defaultValue 的调用点:

  • 挂了 provider(真实 console):i18next 的 t 接手 → 包里有值用包值,没值用内联 defaultValue → 英文可见。
  • 没挂 provider(独立嵌入宿主 / 单测):fallbackT 接手 → defaults['perm.facet.none'] 不存在 → 返回 perm.facet.none。{ defaultValue: 'None' } 只是被当作一次 {{defaultValue}} 替换(无洞可替),兜底文案完全没被读。

注意这正好是 #3546 正文记录的陷阱的镜像:正文说的是"map 里有、包里没有 → 真实 console 退化成 raw key,而无 provider 单测是绿的";这里是"map 里没有 → 无 provider 才退化成 raw key,而挂了 provider 的单测是绿的"。两个方向都有,而且都被门禁的分工照不到。

规模(AST 实测,只扫了一个 hook)

packages/plugin-detail/src/useDetailTranslation.ts 的 DETAIL_DEFAULT_TRANSLATIONS 有 146 条;经该 hook 的 t() 字面量 key 里有 16 个不在表内:

perm.facet.none / more / objects / fields / rls / tabs / adminScope
                / designInStudio / designInStudioHint      (9)  renderers/PermissionFacetLink.tsx
detail.add                          RelatedList.tsx:1130, 1300
detail.concurrentUpdateRecordLabel  InlineEditSaveBar.tsx:144
detail.deleteRowTitle               RelatedList.tsx:1266
detail.history                      DetailView.tsx:1387
detail.historyEmpty                 DetailView.tsx:1453
detail.saving                       InlineEditSaveBar.tsx:316
detail.showEmptyRelated             renderers/record-reference-rail.tsx:350

这 16 个在无 provider 宿主上全部渲染 raw key。其中:

createSafeTranslation 全仓有多个消费方(plugin-gantt、plugin-kanban、data-table 等各有自己的 defaults 表),只量了 plugin-detail 这一个,总量未知 —— 这是本单最该先做的事。

为什么值得修而不是记一笔

无 provider 嵌入是本仓明确支持的场景(createSafeTranslation 存在的全部理由就是它,注释写着 "e.g. in tests or standalone usage")。在那个场景下,一张 146 条的手工表决定了 162 个调用点里哪些会把 key 露给用户,而"漏一条"没有任何门禁看得见。

可能的收口方向(未裁决)

倾向 A(小、语义正确、消灭双源),长期看 C;B 不建议单独做。

关联:#3546(正文记录了同一陷阱的另一个方向;切片六实测本条)、#3810(内联 defaultValue 与 en 包值漂移 —— 选项 C 的前提在本单)、#3512(createSafeTranslation 无 provider 插值只认 {{name}},同一 helper 的另一处缺陷)、#3863(同一 hook 下 detail.showEmptyRelated 的复数族缺基础 key)。

Activity

  1. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    Triage: pm:queue (in-repo objectui card — no routing label by design).

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 11, 2026
  3. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    ContributorAuthor

    CLAIM + 代决 — session session_017Qqyix2QcnpUC9XeYVDzx3(objectui whole-repo PM seat;否决窗口开放)。Branch: claude/issue-3865-safe-translation-defaultvalue。

    裁决:A(卡片倾向 + triage 默认方向,采纳)——fallbackT 读取字符串型 options.defaultValue(顺位:defaults[key] → defaultValue → key,与 i18next 语义一致:表值即包值的替身,恒胜内联兜底),并把 defaultValue 从插值循环剔除为保留名(i18next 本就如此保留)。一处 helper 改动,162 个调用点中凡带内联兜底的立刻不再露 key,双路径分叉消灭。C(取消 defaults 表)记为长期方向不在本卡执行——A 落地后各表变冗余无害,届时是一张 dead-surface sweep 卡;B 不做。

    附带任务(卡片自名的第一步):普查其余 createSafeTranslation 消费方(plugin-gantt/plugin-kanban/data-table 等)的表外 key 规模,数字进 PR 正文;并把"内联 defaultValue 此前在两条路径上都是死的"这一事实记到 #3810(其选项 C 的前提)。#3512(同 helper 插值缺陷)与 #3863(同 hook 复数族,在飞)不并入,无文件重叠。


    Generated by Claude Code

  4. added a commit that references this issue on Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions