fix(web-shell): localize built-in command and skill descriptions in the slash menu - #6326
Conversation
…he slash menu The slash-command menu mixed languages in a zh-CN session: the local fallback commands were translated, but daemon-advertised built-in commands (/bug, /directory, /effort, …) and bundled/project skills (/dataviz, /bugfix, …) showed the daemon's English descriptions. The daemon fills descriptions from its own process language, which is independent of the web-shell UI language, so the menu can only match the UI language by re-localizing on the client. - localizeBuiltinDescriptions() re-localizes built-in commands by name, guarded by source === 'builtin-command' so custom commands keep their own description. - Skills are localized by name in the skill-tagging step (keyed off connection.skills), so it also works on the welcome screen before a session exists — skills only carry a reliable source once a session is created. - Covers 20 daemon-only built-in commands and 27 skills (9 bundled + 18 project). Display-only: the model still receives the daemon's canonical English text. Unknown/user skills keep their authored descriptions.
|
Thanks for the PR, @wenshao! Template looks good ✓ Problem: Real observed issue — the slash-menu mixes languages when the web-shell UI is set to zh-CN because daemon-advertised command/skill descriptions come from the daemon process locale, independent of the browser UI locale. Before/after screenshots in the PR body demonstrate the fix. Direction: Aligned with web-shell i18n goals. The web-shell already had a translated fallback command list; this closes the gap for daemon-advertised entries. No CHANGELOG reference in Claude Code (this is Qwen-specific infra), but the area is clearly within scope. Approach: Clean and minimal. Curated name→i18n-key maps for 20 built-in commands + 27 skills (9 bundled + 18 project), applied client-side. Guarded by One observation: the skill localization keys off skill name rather than Moving on to code review. 🔍 中文说明感谢贡献,@wenshao! 模板完整 ✓ 问题: 真实存在的 bug —— web-shell UI 设置为 zh-CN 时,斜杠菜单出现中英混杂。原因是 daemon 广播的命令/技能描述来自 daemon 进程的语言,和浏览器 UI 语言无关。PR 正文中的 before/after 截图证明了问题及修复效果。 方向: 与 web-shell i18n 目标一致。web-shell 已有本地化的 fallback 命令列表,这次补齐了 daemon 广播条目的缺口。Claude Code 没有对应 CHANGELOG 条目(这是 Qwen 特有基础设施),但该方向明确在范围内。 方案: 简洁且最小化。为 20 个内置命令 + 27 个技能(9 bundled + 18 项目)建立 name→i18n key 映射,在客户端应用。用 一个观察:技能本地化是按 skill 名字而非 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal before reading the diff: add name→i18n-key maps for daemon-advertised commands and known skills, apply as a post-merge step guarded by The PR's approach matches this exactly. Two clean Reuse check: nothing in the repo already provides this — the gap between No critical blockers. No AGENTS.md violations. Test ResultsNew unit tests (
Full web-shell suite — 986/986 passing across 65 test files (3.6s). No regressions. Typecheck — Real-Scenario Testing
SummaryClean, minimal i18n fix with strong test coverage. No correctness, security, or regression concerns. The only verification gap is that triage cannot independently drive a browser to confirm the rendered menu, but the unit tests comprehensively cover the transformation pipeline and the author's live Playwright evidence is credible. 中文说明代码审查读 diff 前我的独立方案:为 daemon 广播的命令和已知技能建立 name→i18n-key 映射,作为 merge 后的一个步骤应用,用 PR 的方案与我的完全一致。两个干净的 复用检查:仓库内没有现成的方案 —— 无关键阻塞问题。无 AGENTS.md 违规。 测试结果新单测 (
完整 web-shell 测试套件 — 65 个文件 986/986 通过(3.6s)。无回归。 类型检查 — 真实场景测试
总结简洁、最小的 i18n 修复,测试覆盖强。无正确性、安全或回归问题。唯一的验证缺口是三态无法独立驱动浏览器确认渲染后的菜单,但单测全面覆盖了转换 pipeline,作者的 Playwright 实机证据可信。 — Qwen Code · qwen3.7-max |
ReflectionStepping back: this is a straightforward, well-scoped i18n fix that solves a real user-visible problem — the slash-menu language mix in zh-CN sessions. The motivation is genuine, the approach matches what I would have proposed independently, and the test coverage is strong. A few reflection points:
The one verification gap — triage cannot drive a browser to confirm the rendered slash menu — is honestly flagged in Stage 2, and the author's Playwright evidence is the appropriate substitute. Unit tests comprehensively cover the pipeline. Approving. ✅ 中文说明反思退一步看:这是一个直接、范围明确的 i18n 修复,解决了一个真实可见的用户问题 —— zh-CN 会话下斜杠菜单语言混杂。动机真实,方案与我独立提出的方案一致,测试覆盖强。 几个反思维度:
唯一的验证缺口 —— 三态无法驱动浏览器确认渲染后的斜杠菜单 —— 已在 Stage 2 中诚实说明,作者的 Playwright 证据是恰当的替代。单测全面覆盖了 pipeline。 批准。✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
| expect(byName.get('bugfix')).toBe('按先复现流程修复 GitHub issue 中的 bug'); // project skill | ||
| expect(byName.get('my-skill')).toBe('my custom skill'); // unknown skill untouched | ||
| expect(byName.get('deploy')).toBe('ship it'); // custom command untouched | ||
| }); |
There was a problem hiding this comment.
[Suggestion] Consider adding a test that validates every i18n key in BUILTIN_COMMAND_DESCRIPTION_KEYS and SKILL_DESCRIPTION_KEYS resolves to a real translation in both EN and ZH dictionaries. Currently, if a future contributor adds a map entry but forgets the matching local.* or skilldesc.* key in i18n.tsx, the getTranslator fallback chain (messages[key] ?? EN[key] ?? key) silently shows the raw key string (e.g. "skilldesc.newSkill") in the slash menu — a user-visible regression with no error signal.
A simple test would catch this:
import { BUILTIN_COMMAND_DESCRIPTION_KEYS, SKILL_DESCRIPTION_KEYS } from './localCommands';
const allKeys = [
...Object.values(BUILTIN_COMMAND_DESCRIPTION_KEYS),
...Object.values(SKILL_DESCRIPTION_KEYS),
];
it('every i18n key resolves in both EN and ZH', () => {
for (const key of allKeys) {
expect(en(key), `EN missing: ${key}`).not.toBe(key);
expect(zh(key), `ZH missing: ${key}`).not.toBe(key);
}
});This makes the three-way sync (map → EN → ZH) self-enforcing via CI.
— qwen3.7-max via Qwen Code /review
ytahdn
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.
Suggestions — commit
|
| File | Issue | Suggested fix |
|---|---|---|
packages/web-shell/client/constants/localCommands.ts:197 |
Prototype chain pollution in skillDescriptionKey(). SKILL_DESCRIPTION_KEYS and BUILTIN_COMMAND_DESCRIPTION_KEYS are plain {} objects inheriting from Object.prototype. A skill or command named constructor, valueOf, hasOwnProperty, or toString returns the inherited method instead of undefined. For constructor/valueOf, t() calls the inherited method which returns {} — an object that React cannot render as a child, crashing the component. For hasOwnProperty, t() returns "false" (the result of calling hasOwnProperty(undefined)). Exploitability is low (requires a skill with a prototype-method name), but the fix is two lines. |
Use Object.hasOwn(): return Object.hasOwn(SKILL_DESCRIPTION_KEYS, name) ? SKILL_DESCRIPTION_KEYS[name] : undefined; — or Object.create(null) for both maps. |
packages/web-shell/client/App.tsx:3399 |
Missing fallback when t(skillKey) returns a raw key string. If an i18n key is missing from both EN and ZH dictionaries, t() returns the raw key (e.g., "skilldesc.newSkill"). The code unconditionally uses t(skillKey) when the skill is known, discarding the daemon's English description. Pre-PR behavior was better: `command.description |
|
packages/web-shell/client/constants/localCommands.ts:135-200 |
No automated check for the 3-way sync between key maps (BUILTIN_COMMAND_DESCRIPTION_KEYS, SKILL_DESCRIPTION_KEYS), EN translations, and ZH translations in i18n.tsx. Messages is Record<string, MessageValue>, so TypeScript won't flag a typo or missing entry. A mismatch silently shows the raw key string to users. |
Add a unit test that iterates both maps and asserts every key resolves in EN and ZH to something other than the raw key string. Export the maps or test via skillDescriptionKey + localizeBuiltinDescriptions. |
packages/web-shell/client/App.tsx:3396 |
Hardcoded skill names override user-authored descriptions by name. SKILL_DESCRIPTION_KEYS contains common words (review, batch, loop, simplify, bugfix). Any user skill sharing these names silently gets the bundled skill's localized description instead of the user's own. The PR acknowledges the tradeoff (name-only matching is needed for the pre-session welcome screen where source is unreliable). |
When command.source IS available, gate on it: only localize when source is 'bundled-skill' or matches the project-skill source pattern. Fall through to the authored description for unknown sources. |
— qwen3.7-max via Qwen Code /review
ytahdn
left a comment
There was a problem hiding this comment.
LGTM! ✅
— qwen3.7-max via Qwen Code /review
What this PR does
Re-localizes the descriptions shown in the web-shell slash-command menu so a Chinese (
zh-CN) session no longer mixes languages. Built-in command descriptions the daemon advertises (/bug,/directory,/effort, …) and skill descriptions (/dataviz,/bugfix, …) are now shown in the active web-shell UI language instead of the daemon's English. This is display-only — the model still receives the daemon's canonical English text, so skill invocation is unaffected.Why it's needed
The web-shell already translated its local fallback command list, but every command and skill the daemon advertises carries a description filled from the daemon process language, which is independent of the web-shell UI language (they are separate concepts — a daemon serves any client). So in a Chinese session the menu came out half-Chinese, half-English. Because the two languages can't be assumed equal, the menu can only be made to match the UI language by re-localizing on the client.
Reviewer Test Plan
How to verify
npm run dev:daemonand open the web-shell with UI languagezh-CN./to open the slash menu./bug,/compress,/config,/diff,/directory,/docs,/doctor,/export,/init,/lsp,/effort, …) show Chinese descriptions./dataviz,/batch,/review,/stuck, …) and this repo's project skills (/bugfix,/codegraph,/triage, …) — show Chinese descriptions, including on the welcome screen before any message is sent (skills only carry a reliablesourceonce a session exists, so localization is keyed offconnection.skills/ skill name).Unit tests:
npm test -w packages/web-shell -- client/constants/localCommands.test.ts(covers commands, bundled + project skills, the no-session pipeline, and the untouched-custom/unknown cases).Evidence (Before & After)
Built-in commands — before (mixed) vs after (all Chinese):
Skills on the welcome screen (no session) — all Chinese, bundled + project:
Verified live end-to-end: real
dev:daemon+ a Chromium (Playwright) session inzh-CN, reading the actual rendered menu DOM.Tested on
Environment (optional)
npm run dev:daemon(daemon from source via tsx + web-shell vite dev), Chromium via Playwright.Risk & Scope
SKILL.mdfrontmatter i18n mechanism (core + daemon + web-shell) and is left as a follow-up. Windows/Linux rely on CI.Linked Issues
N/A
中文说明
这个 PR 做了什么
修复 web-shell 斜杠命令菜单在中文(
zh-CN)会话下描述中英混杂的问题。daemon 广播的内置命令(/bug、/directory、/effort…)和技能(/dataviz、/bugfix…)的描述,现在按 web-shell 当前 UI 语言显示,而不是 daemon 的英文。仅显示层替换 —— 模型仍收到 daemon 的英文规范描述,不影响技能调用。为什么需要
web-shell 本地 fallback 命令列表已翻译,但 daemon 广播的每个命令/技能,其描述来自 daemon 进程 语言,和 浏览器 UI 语言无关(两者是独立概念:一个 daemon 可服务任意客户端)。所以中文会话下菜单一半中文一半英文。既然两种语言不能假定相等,只能在客户端按 UI 语言重新本地化。
关键点
localizeBuiltinDescriptions()按名重译内置命令,source === 'builtin-command'守卫,自定义命令保留原描述。connection.skills),欢迎屏无会话时也生效 —— 技能只有建了会话才带可靠source。dev:daemon+ Playwright(zh-CN + 真实会话/欢迎屏)读菜单 DOM。