Repository navigation
ui: 诊断页金融提供方行把 {{list}} 占位符原样渲染,列表被拼在第二个冒号之后 #294
Description
问题与复现
设置 → 诊断页的「金融提供方(Financial providers)」区块,每个提供方卡片会渲染两行覆盖范围。这两行把 {{list}} 占位符原样打印给用户,并把真正的列表拼在自己的第二个冒号之后:
packages/ui/src/components/settings/DiagnosticsTab.tsx:210 / :213(行号基于 3a17eca6):
{t('diagnostics.markets')}: {provider.coverage.markets.join(', ') || '—'}
{t('diagnostics.capabilities')}: {provider.coverage.capabilities.join(', ') || '—'}而这两个键的文案本身已经带了占位符与分隔符:
packages/i18n/src/locales/en-US/diagnostics.ts:40='Markets: {{list}}',:41='Capabilities: {{list}}'packages/i18n/src/locales/zh-CN/diagnostics.ts:41='市场:{{list}}',:42='能力:{{list}}'
调用点没有传插值参数,i18next 因此保留原样占位符,再被调用点自己补一个 : + 列表。实测渲染结果(用一个 markets: ['US','HK']、capabilities: ['market.quote','market.kline'] 的提供方):
en-US: … Financial providers longbridge connected
Markets: {{list}}: US, HK
Capabilities: {{list}}: market.quote, market.kline
zh-CN: … 金融提供方 longbridge connected
市场:{{list}}: US, HK
能力:{{list}}: market.quote
覆盖范围为空时同样带着占位符:Markets: {{list}}: —。
根因是类型把修复路径堵死了:FinancialProviderRow 把 t 的 prop 类型收窄成 t: (key: string) => string,而父组件传进来的是真实的 TFunction(DiagnosticsTab.tsx:138 的 <FinancialProviderRow … t={t} />)。收窄后的签名不允许传第二个参数,于是调用点既没法传 { list },author 只能手工拼字符串 —— 缺陷被类型系统保护住了。
全仓枚举(不是孤例,也不是普遍问题)
用脚本把两件事对齐后枚举:
- 从
packages/i18n/src/locales/en-US/*.ts抽出所有文案,得到 637 个键,其中 67 个含{{…}}占位符; - 在
packages/{ui,shared,core,skill-hub,pi-runtime}/src+apps/electron/src里正则找所有t('字面量键')(不带第二个参数)的调用点。
结果:未传插值参数却命中占位符键的调用点全仓恰好 2 处,都在同一个函数里(就是上面两行);同一批键里正确传参的调用点有 61 处。这 61 处里就有 8 处在本文件内部:
DiagnosticsTab.tsx:239 diagnostics.summaryFolio
DiagnosticsTab.tsx:244 diagnostics.summaryAgentRuntime
DiagnosticsTab.tsx:248 diagnostics.summaryLlm
DiagnosticsTab.tsx:252 diagnostics.summaryBroker
DiagnosticsTab.tsx:258 diagnostics.summaryLongbridgeCli
DiagnosticsTab.tsx:259 diagnostics.summarySkills
DiagnosticsTab.tsx:263 diagnostics.summaryResources
DiagnosticsTab.tsx:267 diagnostics.summaryRecentErrors
也就是说同一份文件、同一种 {{name}} 写法,8 处对、2 处错 —— 这两行是漏网,不是口径分歧。(脚本里另有 31 行动态键 t(\…`)` 无法静态判定,本问题不涉及。)
预期与修复方向
把插值交给 i18next,并让类型允许这样做:
t: TFunction; // 原来是 t: (key: string) => string
…
{t('diagnostics.markets', { list: provider.coverage.markets.join(', ') || '—' })}
{t('diagnostics.capabilities', { list: provider.coverage.capabilities.join(', ') || '—' })}删掉调用点自带的 : (分隔符已经写在文案里)。TFunction 从 i18next 导入,与仓库既有写法一致(components/today/TodayView.tsx:5,42 的 formatWhen(timestamp, t: TFunction))。不改任何 locale 文案、不新增键,i18n:check 的 1487 ≡ 1487 保持不变。
复现证据
- 环境:Windows(
10.0.26200.9457,x64)、Bun1.4.2、main3a17eca6(2026-10-03T16:16:35Z)。 - 新增守卫
packages/ui/src/components/settings/DiagnosticsTab.test.tsx(3 个用例:en-US 插值、空覆盖的—回退、zh-CN 插值)。用真实资源包(createSyncI18n)渲染真实组件,只把client.diagnostics.collect换成固定 bundle。 - 修复前(把
DiagnosticsTab.tsx还原为3a17eca6的原始字节后重跑):
$ bun test packages/ui/src/components/settings/DiagnosticsTab.test.tsx
0 pass / 3 fail
Expected to contain: "Markets: US, HK"
Expected to contain: "Markets: —"
Expected to contain: "市场:US, HK"
(实际渲染里出现 `Markets: {{list}}: US, HK`、`市场:{{list}}: US, HK`)
- 修复后:
3 pass / 0 fail。 - 工作区回归:
bun test packages/ui --isolate= 352 pass / 0 fail(65 个文件,无任何失败项)。其中 10 个用例是本 PR(3 个)与姊妹 PR ui: 8 个组件内联 toLocale* 渲染日期/时间,输出跟随宿主 OS 区域而非应用语言 #293(7 个)新增的守卫。 tsc --noEmit(packages/ui)exit 0;bun run i18n:check⇒key parity: en-US 1487 keys ≡ zh-CN 1487 keys (0 issues)。- 上游
PR checks(.github/workflows/pr.yml)当前state=disabled_manually,新建 PR 不会产生 check run,故以上均为本地等价命令结果,非 CI 结论。 - 未运行:
apps/electron的 e2e 套件(本机无法跑,见 issue e2e: 平台专属的 Electron 二进制路径与 POSIX shell 清理,让 harness 在 Windows 上无法运行 #223)。
范围
- 只改「插值有没有被传递」与限制它的 prop 类型;两行文案的形状与位置不变(除了修掉重复的
:)。 - 不动
ErrorRow的同类 prop 类型(它调用的键都不含占位符,收窄不影响;避免无关改动)。 - 与 fix(i18n): 一处键差异被 checkKeyParity 报成两条,且 extra 的 locale 标签指向错误一侧 #209 无关:fix(i18n): 一处键差异被 checkKeyParity 报成两条,且 extra 的 locale 标签指向错误一侧 #209 是
checkKeyParity的计数与标签方向,不涉及插值;与 fix(diagnostics): 诊断包恒报券商「已连接、1 个账户」,与是否登录无关 #245 无关(诊断包券商账户事实)。 - 附带记录(不在本 PR 内):
packages/ui/src/client/diagnostics.ts的DiagnosticsBundle镜像缺少packages/shared/src/diagnostics/types.ts里的evaluation字段,而该镜像的 docstring 自称「keep these two in sync」。组件当前不读该字段,无可复现的用户可见影响,故未并入。
Activity
wxrbyte commentedon Oct 6, 2026
/claim
github-actions commentedon Oct 6, 2026
@wxrbyte 已认领该 Issue,并已自动设为 Assignee。提交 PR 时请在描述中写
Closes #294。