Skip to content

[finding] marketplace.action.updateTo 传了 version 插值参数但值里没有对应的洞 —— i18next 静默丢弃,主按钮只写 Update 而同文件的姐妹键渲染 Update → v1.2.3(全仓 1/1055) #3845

Description

@yinlianghui

#3546 切片五(回填 marketplace + preview 命名空间)顺手量出,不在该 PR 范围内。观察级:传进去的参数是休眠的,按钮上的 Update 本身是个正常标签,今天没有任何东西坏掉。

现场

packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx:555(云端安装分支的主按钮):

label: installing
  ? t('marketplace.action.installing')
  : cloudInstalledVersion
    ? (cloudUpdateAvailable
        ? t('marketplace.action.updateTo', { defaultValue: 'Update', version: latestVersion })
        : t('marketplace.action.installed', { defaultValue: 'Installed' }))
    : t('marketplace.action.installToCloud'),

version: latestVersion 被传进去,但 defaultValueUpdate —— 没有 {{version}} 的洞。i18next 对没有对应洞的插值参数静默丢弃,所以这个参数从来没有渲染过任何东西。

对照:同一个文件:852(环境下拉里每个环境后面的版本提示)确实渲染版本:

t('marketplace.install.updateTo', { defaultValue: 'Update → v{{version}}', version: latestVersion })

两个 key 名字都叫 updateTo、都传 version、都在同一个包详情页上,只有一个有洞。作者意图看着是"主按钮也该显示要升到哪个版本",但那只是推测。

全仓规模:1/1055,就是这一处

对全仓所有 t('key', { … }) 形态的调用点(key 在 en 能解析的,共 1055 个)做 AST 抽取,取出顶层参数名(剔除 i18next 保留名 defaultValue/count/context/ns/lng/… 与嵌套调用的参数),与 en 值里的 {{hole}} 名字集合比对:

checked 1055 pack-backed t(key, {…}) call sites whose key resolves in en
call sites passing an option with no matching {{hole}}: 1
distinct keys: 1
  packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx:555
      marketplace.action.updateTo  inert=[version]
      en = "Update"

这个类是单例。第一遍扫描曾报 2 处,第二处 packages/fields/src/widgets/ImageField.tsx:105误报 —— 它的外层 t('fields.image.enlarge', { name: … }) 的参数对象里嵌了另一个 t() 调用(t('fields.image.imageAlt', { index: idx + 1 })),我的正则把内层的 index: 当成了外层参数名。剔除嵌套括号跨度后复测为 1。记在这里是因为任何将来实现这道门禁的人会踩同一个坑:参数名提取必须先消掉嵌套调用。

#3546 切片五做了什么(以及为什么不修)

切片五把 marketplace.action.updateTo 补进十个语言包。硬纪律是 en 值与调用点内联 defaultValue 逐字节相同(36/36),所以 en 落的是 Update,九包落各自的"更新 / Aktualisieren / Mettre à jour / …"(复用 form.update 既有译文行)。

没有顺手给 en{{version}},两个理由:

  1. 那会改变用户今天看到的字串(UpdateUpdate → v2.1.0),而这条纪律的整个意义就是回填不改变任何英文渲染;
  2. 该改不改是产品裁量 —— 主按钮要不要带版本号是设计问题,不是 i18n 问题。按钮宽度、与 Installed / Installing… 三态的视觉一致性都在里面。

切片五的 PR 里有一条断言把这个选择钉住,免得下一个读者以为是漏了:

expect(src).toContain("t('marketplace.action.updateTo', { defaultValue: 'Update', version: latestVersion })");
for (const lang of LANGS) expect(at(builtInLocales[lang], 'marketplace.action.updateTo')).not.toContain('{{');
expect(at(builtInLocales.en, 'marketplace.install.updateTo')).toContain('{{version}}');

也就是说:要么删掉那个休眠参数,要么给十包都加上洞 —— 无论哪条,这条断言都会红,迫使做决定的人正面处理。

三道 i18n 门禁为什么看不见

反向的错(值里有洞、调用点不传参数 → 用户直接看到 {{name}} 花括号)同样没人管。本次扫描顺带确认了这一支目前为 0,但那是运气,不是保证。

收口方向(未裁决)

倾向 C 落门禁 + A 清存量(A 而非 B,因为 B 是产品决定,不该由一道门禁的存量清理顺手带上)。若维护者认为主按钮该显示版本,那就是 B,并且门禁照样成立。

关联:#3546(切片五量出本条并钉住选择)、#3810(同一位置的另一道门禁提案:内联 defaultValue 必须等于 en 值;这两条判据应当同批实现)、#3530(守卫本体,已有现成 AST)、#3650(en-drift 门禁)。

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