问题与复现
设置 → 评估(Evaluation)页的失败提示,把这 10 处都写成「优先用 IPC 信封里的原始 message」:
packages/ui/src/components/settings/EvaluationSettingsTab.tsx(行号基于 3a17eca6):
:90 setLoadError(result?.error.message ?? t('settings.evaluation.failedToLoad'));
:118 setSaveError(result?.error.message ?? t('settings.evaluation.couldNotUpdateTracing'));
:137 setSaveError(result?.error.message ?? t('settings.evaluation.couldNotSaveSettings'));
:161 setTestResult({ ok: false, message: result?.error.message ?? t('settings.evaluation.connectionTestFailed') });
:181 setKeyError(result?.error.message ?? t('settings.evaluation.couldNotSaveApiKey'));
:194 setKeyError(result?.error.message ?? t('settings.evaluation.couldNotRemoveApiKey'));
:207 setSaveError(result?.error.message ?? t('settings.evaluation.couldNotUpdateTracing'));
:228 message: result?.error.message ?? t('settings.evaluation.connectionTestFailed'),
:251 setLangfuseKeyError(result?.error.message ?? t('settings.evaluation.couldNotSaveApiKey'));
:265 setLangfuseKeyError(result?.error.message ?? t('settings.evaluation.couldNotRemoveApiKey'));
因为 error.message 通常都在,?? t(...) 那一侧几乎永远不生效 —— 本地化文案被架空。实测(把 evaluation.getSettings 桩成返回 { ok:false, error:{ code:'STORAGE_WRITE_FAILED', message:'STORAGE_WRITE_FAILED: connection is locked' } }、界面语言 zh-CN):
Expected to contain: "无法保存更改" ← errors.storageWriteFailed 的 zh-CN 文案
Received: "STORAGE_WRITE_FAILED: connection is locked" ← IPC 原始英文,连 code 一起显示给用户
机制现状:errors.* 这套按 code 出文案的设计全仓没有任何调用方
仓库其实已经为「按稳定 code 出本地化文案」建好了完整机制:
packages/i18n/src/locales/{en-US,zh-CN}/errors.ts:34 条用户可见错误文案,docstring 写明
「User-facing error copy keyed by stable ApiError.code … Resolution: translateErrorCode(code) → this table」;
packages/i18n/src/error-messages.ts:ERROR_CODE_TO_KEY(33 条 code → key 静态表)+ errorKeyForCode + translateErrorCode,
docstring 写明「falls back to a sanitized technical message when no code matches — raw stack traces are never shown to users」。
但全仓检索(packages / apps / docs / scripts,含测试)后,translateErrorCode / errorKeyForCode /
ERROR_CODE_TO_KEY 的引用只有三处,全部在机制自己内部:定义本身、它自己的单测 packages/i18n/src/i18n.test.ts、
以及 barrel packages/i18n/src/index.ts 的导出。没有任何生产代码调用它。
顺带两处文件自述与实际不符:
error-messages.ts 的 docstring 说「translateErrorMessage looks up the mapping」,但该文件里没有这个函数(只有 translateErrorCode);
errors.* 里 34 条文案中,只有 ErrorBoundary.tsx 直接用了 sectionUnexpected / sectionUnexpectedDetail /
sectionOpenDiagnostics 三条,其余全部只被那个无人调用的解析器引用。
量级对齐(脚本枚举,packages / apps / scripts 下所有 code: 'SCREAMING_SNAKE'、
createCodeError('…')、code = '…' 字面量):
| 项 |
数量 |
| 全仓发出的 SCREAMING_SNAKE 形态 code |
99 |
ERROR_CODE_TO_KEY 条目 |
33 |
| 表中覆盖到的已发出 code |
31 |
| 表中有、但全仓从未发出 |
2(STORAGE_READ_FAILED、SYNTHESIS_TIMEOUT) |
即:「按 code 出本地化文案」这条设计路径,从表到解析器都齐备,却没有任何一个渲染面接上它;而 UI 侧大量页面
(不止本页)直接渲染 result.error.message。本 issue 只处理本页这 10 处。
预期与修复方向
让本页的失败提示按「注册过的 code → 本地化文案;未注册的 code → 保留 sanitized message;两者都没有 → 页面自己的兜底文案」
解析,即把已经存在但没人调用的 translateErrorCode 真正接上:
function errorCopy(t: TFunction, error: ApiError | undefined, fallbackKey: string): string {
return translateErrorCode(error?.code, t) ?? error?.message ?? t(fallbackKey);
}
- 10 处调用点改为
errorCopy(t, result?.error, '<原有的 settings.evaluation.* 键>');
- 不删除未注册 code 的原始 message(那是有用的诊断信息),只是不再让它抢占已注册 code 的本地化文案;
- 零新增 i18n 键(
errors.* 与 settings.evaluation.* 都已存在),i18n:check 的 1487 ≡ 1487 不变。
复现证据
- 环境:Windows(
10.0.26200.9457,x64)、Bun 1.4.2、main 3a17eca6。
- 在既有
packages/ui/src/components/evaluation/evaluation.test.tsx 追加 2 个用例(用 client.prefs.get 把界面语言钉成 zh-CN,
再把 evaluation.getSettings 桩成失败):
- 「注册过的 code 走
errors.* 本地化文案」;
- 「未注册的 code 仍保留原始 message」(防止修复过度)。
- 修复前:该文件 6 pass / 1 fail,唯一失败即上文的
Expected "无法保存更改" / Received "STORAGE_WRITE_FAILED: connection is locked"
(另一个新用例本来就通过,说明现有实现「保留原始 message」这一点是对的,缺的只是「已注册 code 没被解析」)。
- 修复后:该文件 7 pass / 0 fail。
- 工作区回归:
bun test packages/ui --isolate = 344 pass / 0 fail(63 个文件);其中 2 个是本 PR 新增的守卫用例,
其余为既有用例且全绿(无既有失败项)。
tsc --noEmit(packages/ui)exit 0。
- 上游
PR checks(.github/workflows/pr.yml)当前 state=disabled_manually,新建 PR 不会产生 check run,
故以上均为本地等价命令结果,非 CI 结论。
范围
问题与复现
设置 → 评估(Evaluation)页的失败提示,把这 10 处都写成「优先用 IPC 信封里的原始
message」:packages/ui/src/components/settings/EvaluationSettingsTab.tsx(行号基于3a17eca6):因为
error.message通常都在,?? t(...)那一侧几乎永远不生效 —— 本地化文案被架空。实测(把evaluation.getSettings桩成返回{ ok:false, error:{ code:'STORAGE_WRITE_FAILED', message:'STORAGE_WRITE_FAILED: connection is locked' } }、界面语言 zh-CN):机制现状:
errors.*这套按 code 出文案的设计全仓没有任何调用方仓库其实已经为「按稳定 code 出本地化文案」建好了完整机制:
packages/i18n/src/locales/{en-US,zh-CN}/errors.ts:34 条用户可见错误文案,docstring 写明「User-facing error copy keyed by stable
ApiError.code… Resolution:translateErrorCode(code)→ this table」;packages/i18n/src/error-messages.ts:ERROR_CODE_TO_KEY(33 条 code → key 静态表)+errorKeyForCode+translateErrorCode,docstring 写明「falls back to a sanitized technical message when no code matches — raw stack traces are never shown to users」。
但全仓检索(
packages/apps/docs/scripts,含测试)后,translateErrorCode/errorKeyForCode/ERROR_CODE_TO_KEY的引用只有三处,全部在机制自己内部:定义本身、它自己的单测packages/i18n/src/i18n.test.ts、以及 barrel
packages/i18n/src/index.ts的导出。没有任何生产代码调用它。顺带两处文件自述与实际不符:
error-messages.ts的 docstring 说「translateErrorMessagelooks up the mapping」,但该文件里没有这个函数(只有translateErrorCode);errors.*里 34 条文案中,只有ErrorBoundary.tsx直接用了sectionUnexpected/sectionUnexpectedDetail/sectionOpenDiagnostics三条,其余全部只被那个无人调用的解析器引用。量级对齐(脚本枚举,
packages/apps/scripts下所有code: 'SCREAMING_SNAKE'、createCodeError('…')、code = '…'字面量):ERROR_CODE_TO_KEY条目STORAGE_READ_FAILED、SYNTHESIS_TIMEOUT)即:「按 code 出本地化文案」这条设计路径,从表到解析器都齐备,却没有任何一个渲染面接上它;而 UI 侧大量页面
(不止本页)直接渲染
result.error.message。本 issue 只处理本页这 10 处。预期与修复方向
让本页的失败提示按「注册过的 code → 本地化文案;未注册的 code → 保留 sanitized message;两者都没有 → 页面自己的兜底文案」
解析,即把已经存在但没人调用的
translateErrorCode真正接上:errorCopy(t, result?.error, '<原有的 settings.evaluation.* 键>');errors.*与settings.evaluation.*都已存在),i18n:check的1487 ≡ 1487不变。复现证据
10.0.26200.9457,x64)、Bun1.4.2、main3a17eca6。packages/ui/src/components/evaluation/evaluation.test.tsx追加 2 个用例(用client.prefs.get把界面语言钉成 zh-CN,再把
evaluation.getSettings桩成失败):errors.*本地化文案」;Expected "无法保存更改"/Received "STORAGE_WRITE_FAILED: connection is locked"(另一个新用例本来就通过,说明现有实现「保留原始 message」这一点是对的,缺的只是「已注册 code 没被解析」)。
bun test packages/ui --isolate= 344 pass / 0 fail(63 个文件);其中 2 个是本 PR 新增的守卫用例,其余为既有用例且全绿(无既有失败项)。
tsc --noEmit(packages/ui)exit 0。PR checks(.github/workflows/pr.yml)当前state=disabled_manually,新建 PR 不会产生 check run,故以上均为本地等价命令结果,非 CI 结论。
范围
result.error.message(例如ModelsTab.tsx、AgentPanel.tsx、EvaluationCenter.tsx、CaseDetail.tsx、ExperimentDetail.tsx、EventsView.tsx、AppShell.tsx等),那是同一个更大的范围,未并入本 PR;本 PR 先用一页把机制接通并留下可复制的判据。
CODE_KIND_TABLE漏了ACCESS_DENIED(影响降级血缘文案);本条是 i18n 侧「code → 用户可见文案」的解析器没有调用方。两者文件与层次都不同,但同属「枚举表漏项/未接线」家族,特此披露。
:39附近的日期本地化(formatDate),本条改的是:90之后的 10 处错误文案,两处相距 50 行、无重叠上下文,可独立合并。