Skip to content

feat(i18n): localize tool display names in TUI and web-shell badges - #5220

Merged
wenshao merged 6 commits into
QwenLM:mainfrom
wenshao:feat/i18n-tool-display-names
Jun 18, 2026
Merged

feat(i18n): localize tool display names in TUI and web-shell badges#5220
wenshao merged 6 commits into
QwenLM:mainfrom
wenshao:feat/i18n-tool-display-names

Conversation

@wenshao

@wenshao wenshao commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Tool-call badges in the chat stream — the TodoWrite, Shell, ReadFile… labels shown above each tool's output — were always rendered with their raw English display names, even when the rest of the UI was switched to Chinese. This PR routes those badge labels through a locale-aware lookup in both the TUI and the web-shell so they render in the active language, while keeping the English display name as a fallback for any tool that has no translation.

Why it's needed

The surrounding chat UI (task panels, status lines, dialogs) is already localized, so a hard-coded English TodoWrite / Shell badge on top of an otherwise-Chinese conversation looked inconsistent, and these implementation-flavored identifiers were leaking into a user-facing surface. Making them translatable closes that gap and gives a single, namespaced place to localize tool labels going forward. The lookup is namespaced (toolDisplayName.<name> in the TUI, toolName.<wire> in the web-shell) specifically so a tool label never collides with a same-spelled standalone UI string (e.g. a Shell label that intentionally stays English).

Reviewer Test Plan

How to verify

  1. Run the CLI (or web-shell) and switch the UI language to Chinese (/language ui zh-CN in the TUI).
  2. Trigger any tool call (e.g. read a file, run a shell command, create a todo list).
  3. Confirm the badge reads e.g. 读取文件 / 终端 / 任务清单 instead of ReadFile / Shell / TodoWrite.
  4. Switch back to English and confirm the badges read ReadFile / Shell / TodoWrite again — English is unchanged, and untranslated/unknown tools fall back to the English display name.

Automated (all run on macOS):

  • npm run typecheck --workspace=packages/cli — clean.
  • npx vitest run --root packages/cli src/i18n — 30 passed, incl. new localizeToolDisplayName cases (Chinese translation, English fallback, unknown-tool passthrough, and non-collision with same-spelled generic UI keys).
  • npx vitest run --root packages/cli ToolMessage CompactToolGroupDisplay InlineParallelAgentsDisplay — 50 passed.
  • npm run test --workspace=packages/web-shell — 389 passed, incl. new toolFormatting cases.

Evidence (Before & After)

Text-label-only change. Before: a Chinese-UI chat shows TodoWrite / Shell / ReadFile badges. After: 任务清单 / 终端 / 读取文件; the English UI is unchanged.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Unit tests + typecheck on macOS.

Risk & Scope

  • Main risk or tradeoff: purely the user-facing badge label; the on-the-wire tool names and all logic that keys off display names (e.g. shell-tool detection via name === 'Shell') are untouched — translation happens only at the final render text.
  • Not validated / out of scope: only zh / zh-CN translations are added; other locales fall back to English by design. No live TUI screenshots captured (text-label change verified by unit tests + locale-key resolution).
  • Breaking changes / migration notes: none.

Linked Issues

N/A

中文说明

这个 PR 做了什么

聊天流里的工具调用徽标——也就是每个工具输出上方显示的 TodoWriteShellReadFile 等标签——一直显示原始英文名,即便界面已切换到中文。本 PR 在 TUI 和 web-shell 两端都让这些徽标走本地化查找,按当前语言显示;对没有翻译的工具,回退到英文显示名。

为什么需要

周围的聊天界面(任务面板、状态栏、对话框)已经本地化了,所以在一段中文对话上盖着写死的英文 TodoWrite / Shell 徽标显得不一致,而且这些偏实现细节的标识符泄漏到了面向用户的界面。让它们可翻译填补了这个缺口,也给工具标签的本地化提供了一个统一、带命名空间的入口。查找特意使用了命名空间(TUI 用 toolDisplayName.<name>,web-shell 用 toolName.<wire>),以确保工具标签永远不会和同名的独立 UI 文案串味(例如某处刻意保留英文的 Shell 文案)。

审阅者测试计划

如何验证

  1. 运行 CLI(或 web-shell),把界面语言切到中文(TUI 里 /language ui zh-CN)。
  2. 触发任意工具调用(例如读文件、跑 shell 命令、创建任务清单)。
  3. 确认徽标显示为 读取文件 / 终端 / 任务清单,而不是 ReadFile / Shell / TodoWrite
  4. 切回英文,确认徽标恢复为 ReadFile / Shell / TodoWrite——英文不变,未翻译/未知工具回退到英文显示名。

自动化(均在 macOS 上运行):

  • npm run typecheck --workspace=packages/cli——通过。
  • npx vitest run --root packages/cli src/i18n——30 通过,含新增的 localizeToolDisplayName 用例(中文翻译、英文回退、未知工具透传、与同名通用 UI key 不串味)。
  • npx vitest run --root packages/cli ToolMessage CompactToolGroupDisplay InlineParallelAgentsDisplay——50 通过。
  • npm run test --workspace=packages/web-shell——389 通过,含新增的 toolFormatting 用例。

证据(前后对比)

纯文本标签改动。之前:中文界面下显示 TodoWrite / Shell / ReadFile 徽标。之后:任务清单 / 终端 / 读取文件;英文界面不变。

测试平台

系统 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

运行环境(可选)

macOS 上的单元测试 + 类型检查。

风险与范围

  • 主要风险/取舍:只改了面向用户的徽标标签;on-the-wire 工具名以及所有基于显示名的逻辑判断(例如通过 name === 'Shell' 检测 shell 工具)都未改动——翻译只发生在最终渲染文本处。
  • 未验证/范围外:仅新增 zh / zh-CN 翻译,其他语言按设计回退英文。未截取真实 TUI 截图(纯文本标签改动,已通过单元测试 + locale key 解析验证)。
  • 破坏性变更/迁移说明:无。

关联 Issue

N/A

image

@DragonnZhang DragonnZhang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No review findings on the i18n-specific changes. Downgraded from Approve to Comment: CI failing (Lint).

The localizeToolDisplayName function with namespaced toolDisplayName.* keys is a clean approach to avoid collision with generic UI strings. Fallback chain (locale → English display name → raw name) is correct. Good test coverage.

⚠️ This PR bundles many unrelated changes (schemaValidator refactor, desktop-release workflow, CodeColorizer, etc.) beyond its stated scope of i18n tool display names. Consider splitting into focused PRs for easier review.

— qwen3-coder via Qwen Code /review

Comment thread packages/cli/src/i18n/index.test.ts
Chat-stream tool badges (TodoWrite, Shell, ReadFile…) showed raw English display names even under a Chinese UI, while the surrounding panels were already localized. Route badge rendering through a locale-aware lookup so they translate, falling back to the English display name for untranslated/unknown tools.

- CLI: add localizeToolDisplayName() keyed by 'toolDisplayName.<DisplayName>' (namespaced so tool labels never collide with same-spelled generic UI strings, e.g. a standalone 'Shell'); use it in ToolMessage, CompactToolGroupDisplay, InlineParallelAgentsDisplay, and the background-view LiveAgentPanel + BackgroundTasksDialog. Add zh + zh-TW translations and the matching en.js parity keys.

- web-shell: add localizeToolDisplayName(toolName, t) keyed by 'toolName.<wire>' with English fallback; use it in ToolGroup and TasksStatusMessage; add zh-CN translations.

- Tests: unit coverage for both helpers (translation, English fallback, unknown passthrough, generic-UI-key non-collision) plus a zh-locale render assertion for the ToolMessage badge.
@wenshao
wenshao force-pushed the feat/i18n-tool-display-names branch from 988688b to a79be46 Compare June 17, 2026 05:46
@wenshao

wenshao commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@wenshao

wenshao commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review! Addressed in a79be46:

CI Lint failure — root cause was check-i18n strict key-parity, not ESLint (the 15 ESLint items are pre-existing curly warnings). The new namespaced toolDisplayName.* keys existed only in zh.js, but zh/zh-TW are strictParity locales. Fixed by adding the keys to en.js (self-mapped, per this repo's key-is-default-text convention — localizeToolDisplayName detects the self-mapping and returns the bare English name) and to zh-TW.js (Traditional, Taiwan vocabulary). check-i18n now passes locally.

"Bundles unrelated changes" — the file diff was always only the i18n changes; what you saw was a merge commit dragging recent origin/main commits into the Commits tab. I've flattened the branch onto origin/main into a single clean commit, so the PR is now 1 commit / 17 files, all i18n-scoped.

Also handled the two inline suggestions: extended the background-view siblings (LiveAgentPanel, BackgroundTasksDialog) to use localizeToolDisplayName(), and added a zh-locale render assertion for the ToolMessage badge.

中文

感谢 review!已在 a79be46 处理:

CI Lint 失败 — 根因是 check-i18n 的严格 key 对齐,而非 ESLint(那 15 条是既有的 curly 警告)。新增的命名空间 key toolDisplayName.* 只加在了 zh.js,但 zh/zh-TWstrictParity locale。已通过在 en.js(自映射 key=value,符合本仓库「key 即默认英文文案」的约定——localizeToolDisplayName 检测到自映射后返回裸英文名)和 zh-TW.js(繁体、台湾用词)补齐 key 修复。check-i18n 本地已通过。

「捆绑无关改动」 — 文件 diff 一直只有 i18n 改动;你看到的是 merge commit 把最近的 origin/main 提交带进了 Commits 标签页。我已把分支重整到 origin/main 上、压成单个干净提交,现在 PR 是 1 commit / 17 文件,全部属于 i18n 范围。

另外也处理了两条内联建议:把 background-view 兄弟组件(LiveAgentPanelBackgroundTasksDialog)也接入 localizeToolDisplayName(),并为 ToolMessage 徽标补了一个 zh locale 的渲染断言。

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

On direction: this closes a real, visible gap — localized chat UI around non-localized tool badges (TodoWrite / Shell / ReadFile) is a clear inconsistency, and the fix is squarely within qwen-code's i18n scope. Straightforward alignment with the project's localization story.

On approach: the scope is tight and well-justified. One small helper (localizeToolDisplayName) on each side (CLI uses English display-name keys, web-shell uses wire-name keys), with namespaced i18n keys to prevent collisions with same-spelled generic UI strings. Every change in the diff is needed for the stated goal — no drive-by refactors, no scope creep. The 418 additions are mostly translation entries, not logic. Clean.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

方向:填补了一个真实可见的缺口——已本地化的聊天 UI 里混着未本地化的工具徽标(TodoWrite / Shell / ReadFile),明显不一致,修复方向完全在 qwen-code 的 i18n 范围内。

方案:范围紧凑合理。CLI 和 web-shell 各一个 localizeToolDisplayName 小工具函数(CLI 用英文显示名做 key,web-shell 用 wire name 做 key),用命名空间防止与同名通用 UI 文案冲突。diff 中的每个改动都是目标所需的,没有顺手重构或范围蔓延。418 行新增主要是翻译条目,不是逻辑代码。干净。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

2a. Code review

Independent proposal (before reading the diff): I'd add a single localizeToolDisplayName function that maps a tool's display name to a namespaced i18n key, falls back to the original name when no translation exists, and wire it into every render path that shows tool badges. On the CLI side, key by the English display name from ToolDisplayNames in core; on the web-shell side, key by the wire name from TOOL_DISPLAY_NAMES in toolFormatting.ts. Add zh translations, write unit tests for translation + fallback + unknown-tool passthrough, and add a guard test ensuring every core tool has a zh entry.

The PR's approach matches this almost exactly. Two observations:

  • en.js self-mapping entries (e.g. 'toolDisplayName.Edit': 'toolDisplayName.Edit'): these are technically redundant since the t() function returns the key when no entry exists, and localizeToolDisplayName detects that self-mapping to return the bare name. However, they follow the en.js file's established convention ("key is both key and default text") and serve as useful documentation of which tool names exist. Not a blocker — it's consistent with the surrounding code.

  • getAgentCurrentToolHint signature change (web-shell): the function now requires a t parameter. All three call sites (ToolGroup.tsx, ParallelAgentsGroup.tsx, and the function itself) were correctly updated. Clean propagation.

No critical issues found. No AGENTS.md violations — the code is minimal, follows existing conventions, and lives in the right packages.

Typecheck: npm run typecheck --workspace=packages/cli — clean ✅

Unit tests:

  • packages/cli i18n: 31 passed ✅ (incl. 3 new localizeToolDisplayName tests)
  • packages/cli components: 52 passed ✅ (ToolMessage, CompactToolGroupDisplay, InlineParallelAgentsDisplay — incl. 2 new localized badge tests)
  • packages/web-shell: 396 passed ✅ (incl. 6 new localizeToolDisplayName tests)

2b. Real-scenario testing

Ran interactive TUI sessions on both main (before) and PR branch (after) with QWEN_CODE_LANG=zh-CN and --approval-mode yolo, prompting "Read the file package.json and tell me the name field".

Before (main branch, zh-CN UI)

  ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
  │ ✓  ReadFile package.json (first 10 lines)                                                                                                                                                        │
  │    Read lines 1-10 of 168 from package.json                                                                                                                                                      │
  ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
  Thought for 0s

  ✦ The name field is @qwen-code/qwen-code.

Badge shows ReadFile (English) inside an otherwise-Chinese UI.

After (this PR, zh-CN UI)

  ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
  │ ✓  读取文件 package.json (first 10 lines)                                                                                                                                                        │
  │    Read lines 1-10 of 168 from package.json                                                                                                                                                      │
  ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
  Thought for 0s

  ✦ The name field is `@qwen-code/qwen-code`.

Badge shows 读取文件 (Chinese) — exactly as intended.

中文说明

2a. 代码审查

独立方案(读 diff 前):加一个 localizeToolDisplayName 函数,把工具显示名映射到带命名空间的 i18n key,无翻译时回退原名。CLI 端用 core 的英文显示名做 key,web-shell 端用 wire name 做 key。加中文翻译、写单测覆盖翻译/回退/未知工具,加守护测试确保每个核心工具都有中文条目。

PR 的方案与此几乎完全一致。两个观察:

  • en.js 自映射条目(如 'toolDisplayName.Edit': 'toolDisplayName.Edit'):技术上可省略(t() 无对应条目时返回 key 本身),但遵循了 en.js 文件的既有惯例("key 即默认文本"),且起到了文档作用。不是阻塞项。

  • getAgentCurrentToolHint 签名变更:新增 t 参数,三个调用点都已正确更新。干净。

无关键问题,无 AGENTS.md 违规。

类型检查: npm run typecheck --workspace=packages/cli — 通过 ✅

单元测试:

  • CLI i18n:31 通过 ✅(含 3 个新增)
  • CLI 组件:52 通过 ✅(含 2 个新增本地化徽标测试)
  • web-shell:396 通过 ✅(含 6 个新增)

2b. 真实场景测试

在 main(改前)和 PR 分支(改后)上,用 QWEN_CODE_LANG=zh-CN + --approval-mode yolo 运行交互式 TUI,提示"Read the file package.json and tell me the name field"。

改前:徽标显示 ReadFile(英文)。改后:徽标显示 读取文件(中文)。符合预期。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Stepping back: the PR does exactly what it promises, and the before/after tmux capture bears it out. The installed build leaves English ReadFile badges sitting inside a Chinese UI; this PR swaps them to 读取文件 with a clean, minimal mechanism.

My independent proposal (Stage 2a) matched the PR's approach almost exactly — one helper function per side, namespaced keys, fallback logic, guard tests. Nothing materially simpler was missed.

The code is straightforward: localizeToolDisplayName is a 3-line function, the call-site changes are mechanical ({name}{localizeToolDisplayName(name)}), and the test coverage is thorough (translation, fallback, unknown-tool passthrough, collision avoidance, and a guard ensuring every core tool has a zh entry). The en.js self-mapping entries are a stylistic choice consistent with the file's convention, not a problem.

Every change in the diff serves the stated goal — no drive-by refactors, no scope creep. The 418 additions are overwhelmingly translation entries. If I had to maintain this in six months, I'd thank the author for the clear namespacing and the guard test that catches missing translations when new tools land.

LGTM — approving. ✅

中文说明

总结:PR 完全兑现了承诺,tmux 前后对比清晰可见。安装版本在中文 UI 里显示英文 ReadFile 徽标;本 PR 用简洁的机制将其替换为 读取文件

我的独立方案(Stage 2a)与 PR 方案几乎一致——每端一个工具函数、命名空间 key、回退逻辑、守护测试。没有遗漏更简路径。

代码直白:localizeToolDisplayName 是一个 3 行函数,调用点的修改是机械性的({name}{localizeToolDisplayName(name)}),测试覆盖充分(翻译、回退、未知工具透传、防冲突、守护测试)。en.js 的自映射条目是与文件惯例一致的风格选择,不是问题。

diff 中每个改动都服务于目标——没有顺手重构、没有范围蔓延。418 行新增绝大多数是翻译条目。如果六个月后要维护,我会感谢作者清晰的命名空间和能捕获新工具缺翻译的守护测试。

LGTM — 批准 ✅

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No review findings. Downgraded from Approve to Comment: CI still running.

Three low-confidence suggestions for human review (not posted as inline comments):

  1. Web-shell TOOL_DISPLAY_NAMES missing 7 entries (toolFormatting.ts:3-35) — enter_plan_mode, task_create, task_update, task_list, team_create, team_delete, workflow have ZH translations but are absent from the English fallback map, so English users see raw wire names. Pre-existing gap widened by this PR.

  2. Missing component integration tests — CompactToolGroupDisplay, InlineParallelAgentsDisplay, BackgroundTasksDialog, LiveAgentPanel, ToolGroup, and TasksStatusMessage call localizeToolDisplayName but lack locale-specific render assertions.

  3. Silent fallback (index.ts:283, toolFormatting.ts:49) — no CI or runtime signal when a new tool lacks locale entries. The check-i18n script only catches statically-referenced keys, not dynamically-constructed toolDisplayName.* keys.

— qwen3.7-max via Qwen Code /review

…uard

Addresses review of QwenLM#5220: 7 wire names (enter_plan_mode, task_create/update/list, team_create/delete, workflow) had zh translations but were absent from web-shell's English TOOL_DISPLAY_NAMES, so English users saw raw wire names. Add the English entries to keep the fallback map aligned.

Also add a CLI test that iterates core ToolDisplayNames and asserts each has a zh toolDisplayName.* entry — check-i18n can't catch this because the keys are built dynamically, not as t() string literals.
@wenshao

wenshao commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the follow-up pass. Addressed in dc70552:

  1. Web-shell English fallback (real bug — fixed). Good catch: the 7 wire names (enter_plan_mode, task_create/task_update/task_list, team_create/team_delete, workflow) had zh translations but were missing from the English TOOL_DISPLAY_NAMES map, so English users would have seen raw wire names. Added the 7 English entries so en and zh stay aligned.

  2. Per-component locale render tests. Kept ToolMessage.test.tsx as the representative render assertion rather than duplicating near-identical zh/en cases across all six components: the badge text path is identical (localizeToolDisplayName(name)), the helper itself is exhaustively unit-tested, and the new guard in (3) covers completeness. Happy to add more if you'd prefer per-component coverage.

  3. No signal for an untranslated new tool (guard added). Added a CLI test that iterates core ToolDisplayNames and asserts every entry resolves to a translated zh string. This is the signal check-i18n can't give, since the toolDisplayName.* keys are built dynamically rather than as t('...') string literals — a new tool without a translation now fails CI.

All checks were green on the previous commit (Lint, Tests ×3, CodeQL); this commit only touches the web-shell map and a test.

中文

感谢二次 review。已在 dc70552 处理:

  1. web-shell 英文回退(真 bug,已修) 好发现:7 个 wire name(enter_plan_modetask_create/task_update/task_listteam_create/team_deleteworkflow)有 zh 翻译,却不在英文 TOOL_DISPLAY_NAMES 映射里,英文用户会看到原始 wire name。已补上这 7 个英文条目,让 en 与 zh 对齐。

  2. 逐组件 locale 渲染测试 保留 ToolMessage.test.tsx 作为代表性渲染断言,而非在六个组件里重复几乎相同的 zh/en 用例:徽标文本路径完全一致(localizeToolDisplayName(name)),helper 本身已有充分单测,(3) 的新守护测试覆盖了完整性。如果你更希望逐组件覆盖,我可以补。

  3. 新工具漏翻无信号(已加守护) 新增了一个 CLI 测试:遍历 core ToolDisplayNames,断言每一项在 zh 下都能解析为已翻译字符串。由于 toolDisplayName.* key 是动态拼接、并非 t('...') 字面量,check-i18n 无法捕获——现在新工具若漏翻会直接让 CI 失败。

上一个提交所有检查均已通过(Lint、三平台 Test、CodeQL);本提交只动了 web-shell 映射和一个测试。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Three additional call sites still render raw tool names without localization:

  1. packages/web-shell/client/components/messages/AskUserQuestion.tsx:394 — hardcoded <span className={styles.toolName}>AskUserQuestion</span> bypasses localizeToolDisplayName. The zh translation 'toolName.ask_user_question': '询问用户' exists but is unused.

  2. packages/web-shell/client/components/messages/toolFormatting.ts:409getAgentCurrentToolHint uses last.toolName (raw wire name like run_shell_command) for the parenthetical agent hint shown in ParallelAgentsGroup.tsx. Threading t through and wrapping with localizeToolDisplayName would localize it.

  3. packages/web-shell/client/components/messages/StatsMessage.tsx:467 — the stats table renders {e.name} (raw wire name) without localization.

— qwen3.7-max via Qwen Code /review

Comment thread packages/web-shell/client/components/messages/toolFormatting.ts
Addresses ci-bot review of QwenLM#5220 — three more call sites rendered raw tool names: AskUserQuestion's hardcoded 'AskUserQuestion' label, getAgentCurrentToolHint (parallel-agent activity hint in ToolGroup + ParallelAgentsGroup), and the StatsMessage tool table. Route all through localizeToolDisplayName, plus the SubAgentPanel sub-tool rows found in the same sweep. localizeToolDisplayName degrades to the English display name (then the raw name), so English output is unchanged or improved (raw wire names in the stats table now resolve to display names).
@wenshao

wenshao commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks — all three addressed in 8dccb17:

  1. AskUserQuestion — replaced the hardcoded AskUserQuestion label with localizeToolDisplayName('ask_user_question', t), so the existing 'toolName.ask_user_question': '询问用户' is now used.
  2. getAgentCurrentToolHint — threaded t through and wrapped the wire name with localizeToolDisplayName; updated both callers (ToolGroup and ParallelAgentsGroup).
  3. StatsMessage — the tool table now resolves e.name through localizeToolDisplayName, so it shows display names (and zh translations) instead of raw wire names.

While sweeping I also found and localized SubAgentPanel's sub-tool rows (TaskToolCallLine, same raw-wire-name pattern).

One I intentionally left: ToolApproval.tsx:222 renders a display name parsed from the ACP title (e.g. ReadFile), not a raw wire name, so it's not a leak; localizing it correctly would mean switching to request.toolName, a behavior change I'd rather not fold into this PR.

localizeToolDisplayName degrades to the English display name (then the raw name), so English output is unchanged — except the stats table, which improves from raw wire names to display names. Verified: web-shell typecheck clean (the 2 remaining errors are pre-existing in untouched test files), 393 web-shell tests pass, prettier clean.

中文

三处都已在 8dccb17 处理:

  1. AskUserQuestion — 把写死的 AskUserQuestion 标签换成 localizeToolDisplayName('ask_user_question', t),现已用上既有的 'toolName.ask_user_question': '询问用户'
  2. getAgentCurrentToolHint — 把 t 透传进去并用 localizeToolDisplayName 包住 wire 名;两个调用方(ToolGroupParallelAgentsGroup)都已更新。
  3. StatsMessage — 工具表的 e.name 现在走 localizeToolDisplayName,显示显示名(及中文翻译)而非原始 wire 名。

顺手在排查中也本地化了 SubAgentPanel 的子工具行TaskToolCallLine,同样的原始 wire 名问题)。

有一处我特意没改:ToolApproval.tsx:222 渲染的是从 ACP title 解析出的显示名(如 ReadFile),并非原始 wire 名,所以不算泄漏;要正确本地化需改用 request.toolName,属于行为变更,不想塞进本 PR。

localizeToolDisplayName 会回退到英文显示名(再回退到原始名),所以英文输出不变——只有 stats 表从原始 wire 名改善为显示名。已验证:web-shell typecheck 干净(剩余 2 个报错是未触碰的测试文件里既有的),393 个 web-shell 测试通过,prettier 干净。

Comment thread packages/web-shell/client/components/messages/toolFormatting.ts
Comment thread packages/web-shell/client/components/messages/toolFormatting.ts
Addresses ci-bot review of QwenLM#5220: mirror the CLI completeness test on the web-shell side (iterate TOOL_DISPLAY_NAMES, assert every tool resolves to a zh translation) and add a getAgentCurrentToolHint test verifying it localizes the sub-tool wire name (zh + English). Export TOOL_DISPLAY_NAMES for the test.
@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@pomelo-nwu

Copy link
Copy Markdown
Collaborator

Review Summary

Verdict: Approve / Ship it. The scope is clear — pure render-layer badge labels, no wire-protocol or tool-detection logic touched. The namespace isolation (toolDisplayName.* in CLI vs toolName.* in web-shell) is a sound design choice. Risk is very low.

What this PR does

Routes tool-call badge labels (TodoWrite, Shell, ReadFile…) through locale-aware lookup in both the TUI and web-shell, so they render in the active language (e.g. 任务清单 / 终端 / 读取文件 in Chinese). Falls back to the English display name for untranslated/unknown tools.

Is the code simple?

Yes. The core function is ~10 lines — look up a namespaced key, return translation if found, else fall back to the English display name. All call-site changes are mechanical wrapping (namelocalizeToolDisplayName(name)). No new abstractions introduced. The completeness test (iterating all ToolDisplayNames and asserting each has a zh translation) is genuinely useful — guards against new tools landing without translations.

Points worth noting

  • en.js self-mapping entries are unnecessary. The t() function already returns the key itself when a translation is missing (translations[key] ?? key). So 'toolDisplayName.Edit': 'toolDisplayName.Edit' (38 entries, all key === value) adds zero behavioral difference — localizeToolDisplayName detects the self-mapping and falls back to the bare display name either way. If check-i18n requires these entries to exist in en.js, they should be 'toolDisplayName.Edit': 'Edit' (value is the English display text, not the full key). Otherwise, just delete them.
  • Two different key conventions across packages. CLI uses toolDisplayName.<DisplayName> (e.g. toolDisplayName.TodoWrite), web-shell uses toolName.<wireName> (e.g. toolName.todo_write). This is understandable given the existing data structures in each package (CLI works with ToolDisplayNames, web-shell with TOOL_DISPLAY_NAMES), but means a translator must maintain two separate maps.
  • 5 commits / 3 fixup rounds for a text-label change. The initial sweep missed several render sites (7 web-shell tools lacked English fallback, 3 more render sites were unlocalized). All addressed now, but worth noting for future sweeps of this nature.

Risk assessment

  • No wire-protocol changes — tool names on the wire are untouched; only the final render text changes.
  • No logic impactname === 'Shell' type detection is not affected since translation happens only at the display layer.
  • English UI is unchanged by design.
  • Other locales fall back to English by design (only zh / zh-CN / zh-TW translations added).
中文说明

评审总结

结论:可以 merge。 改动范围明确——纯渲染层标签文本,不碰 wire 协议名和任何工具检测逻辑。namespace 隔离设计合理,风险极低。

这个 PR 做了什么

把聊天流里工具调用徽标(TodoWrite/Shell/ReadFile 等标签)从硬编码英文改为走 i18n 本地化查找,中文界面下显示 任务清单/终端/读取文件。未翻译/未知工具回退到英文显示名。

代码是否简洁

简洁。核心函数 ~10 行:查 namespaced key → 命中返回翻译 → 未命中回退原名。所有调用点是机械包装,没引入新抽象。完整性测试(遍历所有 ToolDisplayNames 断言每个都有 zh 翻译)很有价值,能防止新工具遗漏翻译。

值得注意的点

  • en.js 的 38 条自映射条目是多余的。 t() 函数在 key 不存在时已返回 key 本身(translations[key] ?? key),所以 'toolDisplayName.Edit': 'toolDisplayName.Edit' 对行为零影响。如果 check-i18n 要求必须列出来,应该写成 'toolDisplayName.Edit': 'Edit'(value 是英文显示文本,不是完整 key);否则直接删掉。
  • CLI 和 web-shell 采用了不同的 key 命名规范。 CLI 用 toolDisplayName.<DisplayName>,web-shell 用 toolName.<wireName>。由于两个包各自的数据结构不同,这样选择是合理的,但维护翻译时需要注意维护两套映射。
  • 5 个 commit / 3 轮修复,针对纯文本标签改动偏多。初始 sweep 遗漏了多个渲染位点,后续 review 补齐。当前 PR 已完整。

风险评估

  • 不改 wire 协议 — 工具名在传输层不变,翻译只发生在最终渲染文本
  • 不影响逻辑判断name === 'Shell' 等检测不受影响
  • 英文界面不变
  • 其他语言回退英文(仅新增 zh / zh-CN / zh-TW 翻译)

— Qwen Code

pomelo-nwu
pomelo-nwu previously approved these changes Jun 18, 2026
Per review of the Chinese badge labels: keep proper tool names / acronyms in English (Agent, Grep, Glob, LSP); fix Shell (was 终端/terminal, which is the device not the interpreter) to 运行命令 and Shell Command to Shell 命令; keep the git term in Enter/ExitWorktree (进入/退出 Worktree) instead of the awkward 工作树. Applies to zh.js, zh-TW.js and web-shell zh-CN; tests and the completeness guards updated with the intentionally-English allowlist.
@wenshao
wenshao dismissed stale reviews from pomelo-nwu and qwen-code-ci-bot via 0122df4 June 18, 2026 02:43

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Approve to Comment: CI still running.

[Suggestion] scripts/check-i18n.ts — The 35 new toolDisplayName.* keys are constructed dynamically via template literal (`toolDisplayName.${displayName}`), so extractUsedKeys cannot detect them as used. This adds 35 false-positive "unused key" warnings to CI output and slightly deflates non-Chinese locale coverage percentages. Consider adding a prefix-based exclusion for toolDisplayName.* in findUnusedKeys.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/i18n/index.test.ts Outdated
// string. check-i18n can't catch this because the keys are built
// dynamically, never as `t('toolDisplayName.X')` string literals.
const untranslated = Object.values(ToolDisplayNames).filter(
(name) => localizeToolDisplayName(name) === name,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Shell Command is not in core ToolDisplayNames — it's a separate UI constant (SHELL_COMMAND_NAME in constants.ts:17). Yet it passes through localizeToolDisplayName(name) in ToolMessage.tsx:804. Deleting zh.js's 'toolDisplayName.Shell Command': '终端命令' would not be caught by this test.

Suggested change
(name) => localizeToolDisplayName(name) === name,
const { ToolDisplayNames } = await import('@qwen-code/qwen-code-core');
const { SHELL_COMMAND_NAME } = await import('../../ui/constants.js');
await setLanguageAsync('zh');
// Guards against a new tool landing without a `toolDisplayName.*` entry:
// every English display name must resolve to a different (translated) zh
// string. check-i18n can't catch this because the keys are built
// dynamically, never as `t('toolDisplayName.X')` string literals.
const names = [...Object.values(ToolDisplayNames), SHELL_COMMAND_NAME];
const untranslated = names.filter(
(name) => localizeToolDisplayName(name) === name,
);

— qwen3.7-max via Qwen Code /review

expect(localizeToolDisplayName('MysteryTool')).toBe('MysteryTool');
});

it('has a zh translation for every core tool display name', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The completeness test validates zh but not zh-TW, which is a distinct supported locale (strictParity: true) with 35 independent toolDisplayName.* entries in zh-TW.js. A missing zh-TW entry would silently fall back to English with no test signal.

Consider adding a parallel test:

it('has a zh-TW translation for every core tool display name', async () => {
  const { setLanguageAsync, localizeToolDisplayName } = await import('./index.js');
  const { ToolDisplayNames } = await import('@qwen-code/qwen-code-core');
  const { SHELL_COMMAND_NAME } = await import('../../ui/constants.js');
  await setLanguageAsync('zh-TW');
  const names = [...Object.values(ToolDisplayNames), SHELL_COMMAND_NAME];
  const untranslated = names.filter(
    (name) => localizeToolDisplayName(name) === name,
  );
  expect(untranslated).toEqual([]);
});

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No review findings. Downgraded from Approve to Comment: CI still running. — qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /trige

@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@wenshao
wenshao merged commit c35aec3 into QwenLM:main Jun 18, 2026
44 checks passed

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants