Skip to content

ui: 设置→评估页 10 处错误提示显示 IPC 原始英文 message,而按 code 出本地化文案的 errors.* 机制全仓无人调用 #296

Description

@wxrbyte

问题与复现

设置 → 评估(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 的导出。没有任何生产代码调用它。

顺带两处文件自述与实际不符:

  1. error-messages.ts 的 docstring 说「translateErrorMessage looks up the mapping」,但该文件里没有这个函数(只有 translateErrorCode);
  2. 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 结论。

范围

Activity

  1. wxrbyte commented on Oct 6, 2026

    @wxrbyte
    ContributorAuthor

    /claim

  2. added
    claimedClaimed by a contributor and currently in progress
    on Oct 6, 2026
  3. github-actions commented on Oct 6, 2026

    @github-actions

    @wxrbyte 已认领该 Issue,并已自动设为 Assignee。提交 PR 时请在描述中写 Closes #296。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

claimedClaimed by a contributor and currently in progress

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions