feat(i18n): localize tool display names in TUI and web-shell badges - #5220
Conversation
DragonnZhang
left a comment
There was a problem hiding this comment.
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.
— qwen3-coder via Qwen Code /review
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.
988688b to
a79be46
Compare
|
@qwen-code /triage |
|
Thanks for the review! Addressed in CI Lint failure — root cause was "Bundles unrelated changes" — the file diff was always only the i18n changes; what you saw was a merge commit dragging recent Also handled the two inline suggestions: extended the background-view siblings ( 中文感谢 review!已在 CI Lint 失败 — 根因是 「捆绑无关改动」 — 文件 diff 一直只有 i18n 改动;你看到的是 merge commit 把最近的 另外也处理了两条内联建议:把 background-view 兄弟组件( |
|
Thanks for the PR! Template looks good ✓ On direction: this closes a real, visible gap — localized chat UI around non-localized tool badges ( On approach: the scope is tight and well-justified. One small helper ( Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 方向:填补了一个真实可见的缺口——已本地化的聊天 UI 里混着未本地化的工具徽标( 方案:范围紧凑合理。CLI 和 web-shell 各一个 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
2a. Code reviewIndependent proposal (before reading the diff): I'd add a single The PR's approach matches this almost exactly. Two observations:
No critical issues found. No AGENTS.md violations — the code is minimal, follows existing conventions, and lives in the right packages. Typecheck: Unit tests:
2b. Real-scenario testingRan interactive TUI sessions on both main (before) and PR branch (after) with Before (main branch, zh-CN UI)Badge shows After (this PR, zh-CN UI)Badge shows 中文说明2a. 代码审查独立方案(读 diff 前):加一个 PR 的方案与此几乎完全一致。两个观察:
无关键问题,无 AGENTS.md 违规。 类型检查: 单元测试:
2b. 真实场景测试在 main(改前)和 PR 分支(改后)上,用 改前:徽标显示 — Qwen Code · qwen3.7-max |
|
Stepping back: the PR does exactly what it promises, and the before/after tmux capture bears it out. The installed build leaves English 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: 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 里显示英文 我的独立方案(Stage 2a)与 PR 方案几乎一致——每端一个工具函数、命名空间 key、回退逻辑、守护测试。没有遗漏更简路径。 代码直白: diff 中每个改动都服务于目标——没有顺手重构、没有范围蔓延。418 行新增绝大多数是翻译条目。如果六个月后要维护,我会感谢作者清晰的命名空间和能捕获新工具缺翻译的守护测试。 LGTM — 批准 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
Three low-confidence suggestions for human review (not posted as inline comments):
-
Web-shell
TOOL_DISPLAY_NAMESmissing 7 entries (toolFormatting.ts:3-35) —enter_plan_mode,task_create,task_update,task_list,team_create,team_delete,workflowhave ZH translations but are absent from the English fallback map, so English users see raw wire names. Pre-existing gap widened by this PR. -
Missing component integration tests — CompactToolGroupDisplay, InlineParallelAgentsDisplay, BackgroundTasksDialog, LiveAgentPanel, ToolGroup, and TasksStatusMessage call
localizeToolDisplayNamebut lack locale-specific render assertions. -
Silent fallback (
index.ts:283,toolFormatting.ts:49) — no CI or runtime signal when a new tool lacks locale entries. Thecheck-i18nscript only catches statically-referenced keys, not dynamically-constructedtoolDisplayName.*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.
|
Thanks for the follow-up pass. Addressed in
All checks were green on the previous commit (Lint, Tests ×3, CodeQL); this commit only touches the web-shell map and a test. 中文感谢二次 review。已在
上一个提交所有检查均已通过(Lint、三平台 Test、CodeQL);本提交只动了 web-shell 映射和一个测试。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
[Suggestion] Three additional call sites still render raw tool names without localization:
-
packages/web-shell/client/components/messages/AskUserQuestion.tsx:394— hardcoded<span className={styles.toolName}>AskUserQuestion</span>bypasseslocalizeToolDisplayName. The zh translation'toolName.ask_user_question': '询问用户'exists but is unused. -
packages/web-shell/client/components/messages/toolFormatting.ts:409—getAgentCurrentToolHintuseslast.toolName(raw wire name likerun_shell_command) for the parenthetical agent hint shown inParallelAgentsGroup.tsx. Threadingtthrough and wrapping withlocalizeToolDisplayNamewould localize it. -
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
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).
|
Thanks — all three addressed in
While sweeping I also found and localized One I intentionally left:
中文三处都已在
顺手在排查中也本地化了 有一处我特意没改:
|
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.
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Review SummaryVerdict: Approve / Ship it. The scope is clear — pure render-layer badge labels, no wire-protocol or tool-detection logic touched. The namespace isolation ( What this PR doesRoutes tool-call badge labels ( 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 ( Points worth noting
Risk assessment
中文说明评审总结结论:可以 merge。 改动范围明确——纯渲染层标签文本,不碰 wire 协议名和任何工具检测逻辑。namespace 隔离设计合理,风险极低。 这个 PR 做了什么把聊天流里工具调用徽标( 代码是否简洁简洁。核心函数 ~10 行:查 namespaced key → 命中返回翻译 → 未命中回退原名。所有调用点是机械包装,没引入新抽象。完整性测试(遍历所有 值得注意的点
风险评估
— Qwen Code |
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.
0122df4
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
[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
| // 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, |
There was a problem hiding this comment.
[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.
| (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 () => { |
There was a problem hiding this comment.
[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
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running. — qwen3.7-max via Qwen Code /review
|
@qwen-code /trige |
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
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/Shellbadge 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. aShelllabel that intentionally stays English).Reviewer Test Plan
How to verify
/language ui zh-CNin the TUI).读取文件/终端/任务清单instead ofReadFile/Shell/TodoWrite.ReadFile/Shell/TodoWriteagain — 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. newlocalizeToolDisplayNamecases (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. newtoolFormattingcases.Evidence (Before & After)
Text-label-only change. Before: a Chinese-UI chat shows
TodoWrite/Shell/ReadFilebadges. After:任务清单/终端/读取文件; the English UI is unchanged.Tested on
Environment (optional)
Unit tests + typecheck on macOS.
Risk & Scope
name === 'Shell') are untouched — translation happens only at the final render text.Linked Issues
N/A
中文说明
这个 PR 做了什么
聊天流里的工具调用徽标——也就是每个工具输出上方显示的
TodoWrite、Shell、ReadFile等标签——一直显示原始英文名,即便界面已切换到中文。本 PR 在 TUI 和 web-shell 两端都让这些徽标走本地化查找,按当前语言显示;对没有翻译的工具,回退到英文显示名。为什么需要
周围的聊天界面(任务面板、状态栏、对话框)已经本地化了,所以在一段中文对话上盖着写死的英文
TodoWrite/Shell徽标显得不一致,而且这些偏实现细节的标识符泄漏到了面向用户的界面。让它们可翻译填补了这个缺口,也给工具标签的本地化提供了一个统一、带命名空间的入口。查找特意使用了命名空间(TUI 用toolDisplayName.<name>,web-shell 用toolName.<wire>),以确保工具标签永远不会和同名的独立 UI 文案串味(例如某处刻意保留英文的Shell文案)。审阅者测试计划
如何验证
/language ui zh-CN)。读取文件/终端/任务清单,而不是ReadFile/Shell/TodoWrite。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 上的单元测试 + 类型检查。
风险与范围
name === 'Shell'检测 shell 工具)都未改动——翻译只发生在最终渲染文本处。关联 Issue
N/A