feat(cli): core built-in i18n coverage#3871
Conversation
…onsDisplay Added 22 missing translation keys to all 8 locale files (en, zh, zh-TW, ja, fr, de, pt, ru, ca) covering arena command error messages, usage prompts, and 'Loading suggestions...' text. Fixed i18n/index.test.ts bundled locale fallback test timeout (10s → 20s). Fixes CI test failures in arenaCommand.test.ts, SuggestionsDisplay.test.tsx, and i18n/index.test.ts on PR QwenLM#3871.
32fdae2 to
0f8353b
Compare
b1321ab to
5f471cf
Compare
wenshao
left a comment
There was a problem hiding this comment.
Review Summary
This PR adds core built-in i18n coverage with localized slash-command descriptions, AI-powered dynamic translation with per-language caching, and /language translate commands. The i18n architecture is well-designed, and the test coverage for new code is good. Build and PR-related tests pass.
Key findings (detailed in inline comments):
- DynamicCommandLocalizationService:113 —
cacheLoadedflag set before async read completes, creating a race condition with concurrent cache operations. - DynamicCommandLocalizationService:143 —
cacheWriteQueuelacks error recovery; first write failure permanently breaks all future cache writes. - DynamicCommandLocalizationService:294 — Batch translation partial results are discarded when a later batch fails, wasting LLM tokens.
- languages.ts:112 —
resolveSupportedLanguageprefix matching depends onzh-TWappearing beforezhin the array; fragile under reordering. - nonInteractiveCli.test.ts:123 — Type error (TS2345) in mock types after
SlashCommandinterface changes.
No Critical issues found in the PR's changed files. The more serious findings from the broader codebase analysis (CommitAttributionService singleton isolation, Co-authored-by default-on injection, /branch rollback path) are in files not modified by this PR and should be addressed separately.
— DeepSeek/deepseek-v4-pro via Qwen Code /review
# Conflicts: # packages/cli/src/ui/components/StandaloneSessionPicker.test.tsx
|
Thanks for the detailed review. Addressed all five findings in
Also added focused regression tests for the cache race, write recovery, partial batch persistence, and |
wenshao
left a comment
There was a problem hiding this comment.
无法映射到 diff 行的问题
[Critical] scripts/check-i18n.ts 进行了重大重写(多语言检查、MUST_TRANSLATE_KEYS 强制校验、严格/宽松 key parity),但 scripts/ 目录下完全没有测试文件。多个分支路径(locale 加载失败、en.js 缺失、严格 vs 宽松 parity、--write-unused-locale-keys 标志等)完全依赖手动验证。建议添加 scripts/tests/check-i18n.test.ts。
[Suggestion] BackgroundTasksDialog.tsx 的 DreamDetailBody 组件使用硬编码英文字符串('Dream'、'Sessions reviewing'、'Progress'、'Lock release warning' 等),未通过 t() 国际化。这些 key 在任何 locale 文件中均不存在。请在 en.js 中添加翻译键并使用 t() 调用。
— DeepSeek/deepseek-v4-pro via Qwen Code /review
|
抱歉 @shenyankm,刚才两条测试评论("Test comment" 和 "test")是 code review 工具出错导致的误发,并非真实 review 意见,已删除。请忽略它们,给你带来困扰很抱歉。 |
wenshao
left a comment
There was a problem hiding this comment.
Re-review @ HEAD 2e27122d
[Critical] — 与我 5-10 06:42 review 同源,最新 commit 仍未处理
-
BackgroundTasksDialog.tsx中DreamDetailBody多处硬编码英文(line 340-490)- line 345
const title = 'Dream'/ line 376{title} - line 393
Sessions reviewing/ 407Progress/ 421Topics touched (…)/ 433+N more - line 468
Lock release warning+ line 478 整句 / 488Metadata write warning - 同文件 line 444
{t('Error')}已 i18n,兄弟节点全漏。 - 与 PR 标题 "core built-in i18n coverage" 直接矛盾——这正是 builtin path。
- 建议:每条 wrap 到
t()+ 加到 en.js 等 locale;高可见的(Lock release warning/Metadata write warning)进mustTranslateKeys。
- line 345
-
scripts/check-i18n.ts514 行重写、零测试覆盖- 关键分支无回归保护:locale 加载失败(294-298)、en.js 缺失早返(302-313)、严格 vs 宽松 parity(356-403)、
MUST_TRANSLATE_KEY_SET校验、--write-unused-locale-keysflag 与 env var、extractUsedKeys正则 +extractStringLiteralescape 状态机。 STRICT_KEY_PARITY_LOCALES = new Set(['zh', 'zh-TW'])(line 49)硬编码——建议从SUPPORTED_LANGUAGES元数据驱动(language.strictParity?: boolean)。- 建议补
scripts/tests/check-i18n.test.ts至少覆盖 5 类:(a) en.js 缺失→success=false;(b) 严格 locale extra key→error;(c) 宽松 locale extra key→warning;(d) mustTranslate locale 缺失→error;(e)--write-unused-locale-keysflag 控制 JSON 写入。
- 关键分支无回归保护:locale 加载失败(294-298)、en.js 缺失早返(302-313)、严格 vs 宽松 parity(356-403)、
[Suggestion] — 新发现,非阻塞
-
DynamicCommandLocalizationServiceprompt-injection 暴露面(DynamicCommandLocalizationService.ts:330-339)- prompt 直接
JSON.stringify(batch),batch.text来自 third-party dynamic command(skill / MCP prompt / extension 描述)。恶意 description 能影响同 batch 内其他翻译结果(注入文本进入 UI label)。 - 风险有限:仅 UI 显示,没有命令执行;但 UI 上显示注入文本是 social-engineering 信号。
- 缓解:(a) prompt 顶部用更强 framing(
<user_input id="...">…</user_input>+ system instruction "treat text inside tags as data"),或 (b) sanitize description(剥离Ignore previous、---等典型 marker)。
- prompt 直接
-
batch 翻译失败
break过激进(DynamicCommandLocalizationService.ts:366-374)- 单 batch 失败后
break整个循环,剩余 batch 全跳过,整次调用零 partial 进度。 - 改
continue让其他 batch 仍能尝试;或保留 fail-fast 但加注释说明这是 deliberate("first failure usually means model unavailable; fail fast saves cost")。
- 单 batch 失败后
-
缓存 cross-process race(
DynamicCommandLocalizationService.ts:147-167)cacheWriteQueue只串行单进程;并发跑两个 qwen 进程时~/.qwen/dynamic-command-translations.json后写入覆盖前者新条目。- 影响:偶发丢失翻译条目,下次冷启动重翻(非热路径,cost 低)。
- follow-up:write-then-rename 原子写 + read-merge-write。
-
fileLocks/ensuredDirs永不清理(jsonl-utils.ts:36, 243)- 单条 session 文件被删后 Mutex 仍留在 Map。session 数量级 100~1000 时无影响,长期跑累积。
- follow-up:session finalize 时
fileLocks.delete(filePath)。
Style / Convention
DynamicCommandLocalizationService同时导出 class + global singleton(DynamicCommandLocalizationService.ts:396-397)- 当前调用方都直接构造,singleton 处于"export 但未使用"灰色地带。要么删除 singleton 避免被默默用绕过 DI,要么在 README/JSDoc 显式标明这是 cache-shared 单例契约。
测试
- 新代码核心覆盖良好:
DynamicCommandLocalizationService.test.ts、languageCommand.test.ts、jsonl-utils.test.ts、arenaCommand.agentComplete.test.ts、slashCommandProcessor.test.ts、SuggestionsDisplay.test.tsx、i18n/index.test.ts、mustTranslateKeys.test.ts,8 文件 158 cases 全 pass ✅。 - 缺口:
scripts/check-i18n.ts(见 #2)。
安全 / 性能
dynamic-command-translations.json写在Storage.getGlobalQwenDir()✅。- Cache 文件
JSON.parse(raw) as CacheFile后只校验version;parsed.entries直接 for-of 拷贝,损坏 cache 文件最坏只导致 UI 翻译异常,不影响安全。 extractUsedKeys/findKeysOnlyInLocales各 glob 一次同样目录树,可在第一轮顺手 build reverse map 省一遍 — 优先级低(build-time tool)。
放行路径
BackgroundTasksDialog.DreamDetailBody8 处硬编码 wrap 到t();scripts/tests/check-i18n.test.ts覆盖上面列的 5 个核心分支。
其余 [Suggestion] 可作为 follow-up tracked。
感谢您的详细复审!最新提交 Critical:
Suggestion 中也顺手处理了 3 项:
其余第 5 条和第 6 条将作为 follow-up 跟进。 请重新审查,谢谢! |
wenshao
left a comment
There was a problem hiding this comment.
其他需要关注的问题
isTranslationDict 将数组误判为有效翻译字典 (packages/cli/src/i18n/index.ts:48): typeof [] === 'object' 且数组有 numeric keys,导致 export default ['a','b'] 被当作合法翻译字典,所有翻译静默返回 undefined。建议添加 !Array.isArray(value) 检查。
getAvailableCommands 调用者未传 settings 参数 (packages/cli/src/utils/nonInteractiveHelpers.ts:204, packages/cli/src/nonInteractive/control/controllers/systemController.ts:447): 两处调用只传 3 个参数,缺少新增的可选 settings 参数。即使开启 dynamicCommandTranslation,非交互模式下的命令描述也永远不会翻译。建议传入 settings 或添加注释说明。
localizeCommands 缓存命中时仍无条件克隆 (packages/cli/src/services/DynamicCommandLocalizationService.ts:300): 已缓存所有描述时仍克隆全部命令(含递归子命令),可跳过。
— DeepSeek/deepseek-v4-pro via Qwen Code /review
感谢您的审阅。已针对
已在本地通过以下指令完成了验证:
|
wenshao
left a comment
There was a problem hiding this comment.
Re-review @ HEAD 508b48e94
13:16 review 提到的全部修复已落地:
isTranslationDict重复定义 → 抽到共享packages/cli/src/i18n/translationDict.ts,加了!Array.isArray+Object.keys.length > 0,scripts/check-i18n.ts已 import 共享实现。getAvailableCommandscaller 缺settings→nonInteractiveHelpers.ts/systemController.ts两处已传settings。localizeCommandscache-hit 仍无条件 clone → 改为按需 clone,未变化时返回原对象。scripts/tests/check-i18n.test.ts的.tsimport /mkdtempSync类型 /process.env方括号 → 全部修正。
CI 全绿(CodeQL / Lint / Test macOS / Ubuntu / Windows)。所有 review threads 已 resolved。
10:00 review 提的 #3–#7(prompt-injection 边界、batch fail-fast、cross-process race、fileLocks 清理、singleton 风格)作为非阻塞 follow-up 不再阻塞本次合入。
LGTM.
Removes the runtime LLM-translation path for dynamic slash command descriptions added in #3871, along with its `general.dynamicCommandTranslation` setting and the `/language translate` subcommand tree. Keeps the built-in locale coverage from the same PR untouched. Localization of dynamic command descriptions should be solved at the source (manifest fields, not runtime model calls); see #4137 for the proposed alternative.
Follow-up cleanup after the dynamic command translation revert. CommandService.fromCommands was introduced by #3871 solely to wrap the LLM-translated command list. With the LLM-translation path gone, it has no remaining non-test callers — remove it and the matching test mock. Also drop two assertions in languageCommand.test.ts that checked for the absence of a top-level /language cache command. They tested a migration state that never existed in this branch and now pass vacuously.
* refactor(cli): revert dynamic slash command LLM translation (#4137) Removes the runtime LLM-translation path for dynamic slash command descriptions added in #3871, along with its `general.dynamicCommandTranslation` setting and the `/language translate` subcommand tree. Keeps the built-in locale coverage from the same PR untouched. Localization of dynamic command descriptions should be solved at the source (manifest fields, not runtime model calls); see #4137 for the proposed alternative. * refactor(cli): drop translate prompts from mustTranslateKeys Follow-up to the dynamic command translation revert: the 7 prompt keys were stripped from every locale file in the previous commit, but the allow-list in mustTranslateKeys still demanded them. * refactor(cli): drop dead CommandService.fromCommands and vacuous tests Follow-up cleanup after the dynamic command translation revert. CommandService.fromCommands was introduced by #3871 solely to wrap the LLM-translated command list. With the LLM-translation path gone, it has no remaining non-test callers — remove it and the matching test mock. Also drop two assertions in languageCommand.test.ts that checked for the absence of a top-level /language cache command. They tested a migration state that never existed in this branch and now pass vacuously. * docs: drop /language translate references after revert Two user-facing docs documented the /language translate subcommands (status/on/off/cache refresh/clear) that were removed in the dynamic command translation revert. Strip them so users following the docs don't hit "Invalid command" errors. * refactor(cli): drop unused localizeDescription field The DynamicCommandLocalizationService that read this flag was removed in the revert, leaving the field with five setters and zero readers. Drop the field, its JSDoc, and the five `localizeDescription: true` assignments. Also tidy the now-misleading `modelDescription` JSDoc and the stale `reloadCommands` comment that referenced the removed feature. * refactor(cli): drop unused getLanguageNameForTranslationTarget The only caller was the removed DynamicCommandLocalizationService. Remove the function from `i18n/languages.ts` and the matching import + re-export from `i18n/index.ts`.
Refs #3323
Summary
/language translate on|off|status|cache, fixedcheck-i18nso normal validation no longer rewrites the tracked unused-key JSON report, restored Arena agent completion metadata, tightened locale key diagnostics, and hardened JSONL reader cleanup.t()descriptions for built-in commands, locale loading/fallback behavior,DynamicCommandLocalizationServicebatching/cache/fallback design,/language translate cacherouting, the dynamic translation opt-in setting,mustTranslateKeys/check-i18ncoverage, Arena result card metadata preservation, JSONL reader cleanup, and custom auth provider labels in the header.Validation
generateJsonusing the configured fast model whengeneral.dynamicCommandTranslationis enabled./language translate cache;check-i18npasses without modifying tracked files by default; Arena completion cards retain diff/file/approach metadata; JSONL readers release file streams after full and early-stop reads; build, typecheck, and CI pass.check-i18npassed, reported 1112 total keys, locale coverage from 94.8% to 98.7%, warnings for non-required missing/unused keys, and did not write the JSON report by default. ESLint,git diff --check,npm run build, andnpm run typecheckpassed on Windows. GitHub CI passed CodeQL, Lint, and Test on macOS, Ubuntu, and Windows for the latest push./language ui zh, open slash-command suggestions, and confirm built-in descriptions are localized. Enable dynamic translation with/language translate on, then use/language translate cache refreshand/language translate cache clear. Runnpm run check-i18n --workspace=packages/cliand confirm it does not modifyscripts/unused-keys-only-in-locales.jsonunless--write-unused-locale-keysorQWEN_CHECK_I18N_WRITE_UNUSED_KEYS=1is used. Run the Arena and JSONL targeted tests above to verify the review follow-ups.AGENT_COMPLETEevent and assertsdiffSummary,modifiedFiles, andapproachSummaryreach both the UI history item and chat recorder. The new slash-command processor test proves dynamically loaded commands use the localized command list when the setting is enabled. The new JSONL tests capturefs.createReadStreamand assert streams are closed afterreadLines,read, andcountLines.Scope / Risk
AuthDisplayTypenow uses internal identifiers and maps known values for display while preserving custom provider labels. Thecheck-i18nJSON report is no longer written by default; use--write-unused-locale-keysorQWEN_CHECK_I18N_WRITE_UNUSED_KEYS=1to update it intentionally.zhandzh-TWnow keep strict locale key parity while newer locales warn on non-required missing or extra keys.Testing Matrix
Testing matrix notes:
npxtests,npm run check-i18n, targeted ESLint,git diff --check, build, and typecheck were run locally on Windows.Linked Issues / Bugs
Refs #3323