Skip to content

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

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.tsDETAIL_DEFAULT_TRANSLATIONS146 条;经该 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)。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions