From 02a65f90c4e71a72f694937f1b97632c5d635123 Mon Sep 17 00:00:00 2001 From: MikeWang0316tw Date: Fri, 15 May 2026 15:26:12 +0800 Subject: [PATCH] fix(i18n): Correct zh-TW translations to match Traditional Chinese conventions (#4129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(i18n): Correct zh-TW translations to match Traditional Chinese conventions Fix ~131 lines of Traditional Chinese (zh-TW) translations that used Simplified Chinese character forms instead of standard Traditional Chinese usage. Changes: - 文件 → 檔案 (47 occurrences) - 爲 → 為 (45 occurrences) - 啓 → 啟 (44 occurrences) - 曆史 → 歷史 (6 occurrences) - 鏈接 → 連結 (4 occurrences) - 菜單 → 選單 (3 occurrences) * fix(i18n): Replace 服務器 with 伺服器 (15 occurrences) Align with Traditional Chinese convention where 伺服器 is the standard term for 'server' in computing contexts. * fix(i18n): Update zh-TW.js header comment to prevent accidental overwrite Clarify that the file is the authoritative source and should not be overwritten with auto-generated output, to prevent future maintainers from regenerating with raw OpenCC and losing manual corrections. * fix(i18n): Add zh-TW regression check and maintenance docs Addresses reviewer feedback on PR #4129 (points 2 and 3): - scripts/check-i18n.ts: Iterate over parsed zh-TW translation values (not raw file content) and report the offending key. Replace the earlier substring list with ZH_TW_FORBIDDEN_PATTERNS, which targets the three real regression categories: variant Traditional characters produced by OpenCC s2t (爲, 啓), Mainland-Chinese vocabulary (服務器, 菜單, 鏈接), and pure Simplified characters. Excludes 禁用 / 配置 / 文件 / 打開 to avoid false positives on Taiwan-valid usage. - scripts/tests/check-i18n.test.ts: Cover the new check, including negative cases for Taiwan-valid vocabulary. - docs/users/features/language.md: Document zh-TW maintenance — the vocabulary table, why raw OpenCC s2t output is not acceptable, and where the CI-enforced list lives. Co-Authored-By: Claude Opus 4.7 * fix(i18n): Address review feedback on zh-TW check (#4129) - check-i18n.ts: Sort ZH_TW_FORBIDDEN_PATTERNS longest-first and break on first match so e.g. `历史` reports the specific bigram instead of also firing the bare `历` rule (no duplicate CI errors). - check-i18n.ts: Add ZH_TW_ALLOWED_EXCEPTIONS escape hatch so a future legitimate translation (e.g. 區塊鏈 in a UI string) can opt out by key without weakening the global pattern list. - docs/users/features/language.md: Add a "CI enforced?" column so contributors can tell which rows block CI vs. which are review-only style guidance. Replace bare `曆` in the table with the `曆史` bigram and note that `曆` is correct in calendar terms (日曆, 農曆, 西曆) — prevents a future maintainer from globally replacing 曆→歷. - Tests: Cover the dedup behavior on overlapping patterns. Co-Authored-By: Claude Opus 4.7 * docs(i18n): Note word-boundary limitation of zh-TW substring check Document the known limitation that `includes()`-based pattern matching does not respect Chinese word boundaries — a bigram like `鏈接` will false-positive on `區塊鏈接口` (區塊鏈 + 接口). Direct contributors to `ZH_TW_ALLOWED_EXCEPTIONS` when this happens instead of weakening the pattern list. Co-Authored-By: Claude Opus 4.7 --------- Co-authored-by: Claude Opus 4.7 --- docs/users/features/language.md | 23 ++ packages/cli/src/i18n/locales/zh-TW.js | 296 +++++++++++++------------ scripts/check-i18n.ts | 119 ++++++++++ scripts/tests/check-i18n.test.ts | 79 +++++++ 4 files changed, 370 insertions(+), 147 deletions(-) diff --git a/docs/users/features/language.md b/docs/users/features/language.md index 97cefa91b..159f06a95 100644 --- a/docs/users/features/language.md +++ b/docs/users/features/language.md @@ -145,6 +145,29 @@ User directory takes precedence over built-in translations. > Contributions are welcome! If you’d like to improve built-in translations or add new languages. > For a concrete example, see [PR #1238: feat(i18n): add Russian language support](https://github.com/QwenLM/qwen-code/pull/1238). +### Maintaining `zh-TW` (Traditional Chinese for Taiwan) + +`zh-TW` is **not** an automatic OpenCC s2t conversion of `zh.js` — it is a hand-maintained Taiwan-vocabulary translation. When adding or updating keys, please follow the conventions below. + +The "CI enforced?" column indicates whether `npm run check-i18n` will fail the build on a violation. Rows marked **No** are style guidance enforced by review only — typically because the offending form has a legitimate non-UI meaning (`文件` can mean "document", `打開` is colloquially fine in Taiwan). + +| Avoid | Use instead | CI enforced? | Reason | +| --------------------- | --------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 文件 (file) | 檔案 | No | Taiwan term for filesystem files (but `文件` can legitimately mean "document") | +| 服務器 / 服务器 | 伺服器 | Yes | Taiwan term for "server" | +| 菜單 / 菜单 | 選單 | Yes | Taiwan term for "menu" | +| 鏈接 / 链接 | 連結 | Yes | Taiwan term for "link" (bare `鏈` is fine — e.g. 區塊鏈) | +| 打開 | 開啟 | No | Taiwan-preferred verb for "open" (UI); `打開` is colloquially common | +| 爲 / 啓 / 曆史 / 鏈接 | 為 / 啟 / 歷史 / 連結 | Yes | Variant Traditional forms from raw OpenCC s2t. Note: `曆` is context-dependent and correct in calendar terms (日曆, 農曆, 西曆); CI only flags the bigram `曆史`, not bare `曆`. | + +If you are not a Traditional Chinese speaker and need to bootstrap a value, **do not paste raw OpenCC `s2t` output**: the default s2t profile emits variant Traditional characters (e.g. 爲, 啓) that Taiwan does not use, and never rewrites Mainland-Chinese vocabulary (服務器, 菜單). Prefer `s2twp.json` (Simplified → Taiwan with phrase mapping) as a starting point and then ask a Taiwan-Chinese speaker to review. + +The `check-i18n` script (run in CI via `npm run check-i18n`) will fail the build if any of the CI-enforced substrings above end up in a `zh-TW` value. See `scripts/check-i18n.ts → ZH_TW_FORBIDDEN_PATTERNS` for the full list. If a translation legitimately needs to contain a CI-forbidden substring, add its key to `ZH_TW_ALLOWED_EXCEPTIONS` in the same file with a brief justification. + +> [!note] +> +> The check uses plain substring matching, which does not understand Chinese word boundaries. A bigram pattern can therefore false-positive across compound-word boundaries — for example, `區塊鏈接口` (= `區塊鏈` + `接口`) contains the substring `鏈接` even though neither word is incorrect. If you hit a surprising CI failure of this kind, add the translation key to `ZH_TW_ALLOWED_EXCEPTIONS` rather than removing the pattern. + ### Language Pack Format ```javascript diff --git a/packages/cli/src/i18n/locales/zh-TW.js b/packages/cli/src/i18n/locales/zh-TW.js index d28b53665..e768db515 100644 --- a/packages/cli/src/i18n/locales/zh-TW.js +++ b/packages/cli/src/i18n/locales/zh-TW.js @@ -5,7 +5,9 @@ */ // Traditional Chinese (zh-TW) translations for Qwen Code CLI -// Auto-generated: structure from en.js, values from zh-TW (manual) or opencc(zh.js s2t) +// Bootstrapped from en.js structure with opencc(zh.js s2t), +// then extensively hand-corrected for Taiwan vocabulary conventions. +// This file is the authoritative source — do not overwrite with auto-generated output. export default { '↑ to manage attachments': '↑ 管理附件', '← → select, Delete to remove, ↓ to exit': '← → 選擇,Delete 刪除,↓ 退出', @@ -13,7 +15,7 @@ export default { 'Basics:': '基礎功能:', 'Add context': '添加上下文', 'Use {{symbol}} to specify files for context (e.g., {{example}}) to target specific files or folders.': - '使用 {{symbol}} 指定文件作爲上下文(例如,{{example}}),用於定位特定文件或文件夾', + '使用 {{symbol}} 指定檔案作為上下文(例如,{{example}}),用於定位特定檔案或檔案夾', '@': '@', '@src/myFile.ts': '@src/myFile.ts', 'Shell mode': 'Shell 模式', @@ -30,18 +32,18 @@ export default { 'Commands:': '命令:', 'shell command': 'shell 命令', 'Model Context Protocol command (from external servers)': - 'Model Context Protocol 命令(來自外部服務器)', + 'Model Context Protocol 命令(來自外部伺服器)', 'Keyboard Shortcuts:': '鍵盤快捷鍵:', 'Toggle this help display': '切換此幫助顯示', 'Toggle shell mode': '切換命令行模式', - 'Open command menu': '打開命令菜單', - 'Add file context': '添加文件上下文', + 'Open command menu': '打開命令選單', + 'Add file context': '添加檔案上下文', 'Accept suggestion / Autocomplete': '接受建議 / 自動補全', 'Reverse search history': '反向搜索歷史', 'Press ? again to close': '再次按 ? 關閉', 'for shell mode': '命令行模式', - 'for commands': '命令菜單', - 'for file paths': '文件路徑', + 'for commands': '命令選單', + 'for file paths': '檔案路徑', 'to clear input': '清空輸入', 'to cycle approvals': '切換審批模式', 'to quit': '退出', @@ -63,7 +65,7 @@ export default { 'Initializing...': '正在初始化...', 'Connecting to MCP servers... ({{connected}}/{{total}})': '正在連接到 MCP servers... ({{connected}}/{{total}})', - 'Type your message or @path/to/file': '輸入您的消息或 @ 文件路徑', + 'Type your message or @path/to/file': '輸入您的消息或 @ 檔案路徑', '? for shortcuts': '按 ? 查看快捷鍵', "Press 'i' for INSERT mode and 'Esc' for NORMAL mode.": "按 'i' 進入插入模式,按 'Esc' 進入普通模式", @@ -91,7 +93,7 @@ export default { 'Memory Usage': '內存使用', 'IDE Client': 'IDE 客戶端', 'Analyzes the project and creates a tailored QWEN.md file.': - '分析項目並創建定製的 QWEN.md 文件', + '分析項目並創建定製的 QWEN.md 檔案', 'List available Qwen Code tools. Usage: /tools [desc]': '列出可用的 Qwen Code 工具。用法:/tools [desc]', 'List available skills.': '列出可用技能。', @@ -101,7 +103,7 @@ export default { '查看或更改工具使用的審批模式', 'Invalid approval mode "{{arg}}". Valid modes: {{modes}}': '無效的審批模式 "{{arg}}"。有效模式:{{modes}}', - 'Approval mode set to "{{mode}}"': '審批模式已設置爲 "{{mode}}"', + 'Approval mode set to "{{mode}}"': '審批模式已設置為 "{{mode}}"', 'View or change the language setting': '查看或更改語言設置', 'List background tasks (text dump — interactive dialog opens via the footer pill)': '列出背景任務(文字列表;互動式對話框可透過頁腳中的「背景任務」入口開啟)', @@ -192,7 +194,7 @@ export default { 'Delete Agent': '刪除智能體', Back: '返回', 'No agent selected': '未選擇智能體', - 'File Path: ': '文件路徑: ', + 'File Path: ': '檔案路徑: ', 'Tools: ': '工具: ', 'Color: ': '顏色: ', 'Description:': '描述:', @@ -226,7 +228,7 @@ export default { 'Generate with Qwen Code (Recommended)': '使用 Qwen Code 生成(推薦)', 'Manual Creation': '手動創建', 'Describe what this subagent should do and when it should be used. (Be comprehensive for best results)': - '描述此子智能體應該做什麼以及何時使用它。(爲了獲得最佳效果,請全面描述)', + '描述此子智能體應該做什麼以及何時使用它。(為了獲得最佳效果,請全面描述)', 'e.g., Expert code reviewer that reviews code based on best practices...': '例如:專業的代碼審查員,根據最佳實踐審查代碼...', 'Generating subagent configuration...': '正在生成子智能體配置...', @@ -257,20 +259,20 @@ export default { 'go back': '返回', '↑↓ to navigate, ': '↑↓ 導航,', 'Enter a clear, unique name for this subagent.': - '爲此子智能體輸入一個清晰、唯一的名稱。', + '為此子智能體輸入一個清晰、唯一的名稱。', 'e.g., Code Reviewer': '例如:代碼審查員', - 'Name cannot be empty.': '名稱不能爲空。', + 'Name cannot be empty.': '名稱不能為空。', "Write the system prompt that defines this subagent's behavior. Be comprehensive for best results.": - '編寫定義此子智能體行爲的系統提示。爲了獲得最佳效果,請全面描述。', + '編寫定義此子智能體行為的系統提示。為了獲得最佳效果,請全面描述。', 'e.g., You are an expert code reviewer...': '例如:您是一位專業的代碼審查員...', - 'System prompt cannot be empty.': '系統提示不能爲空。', + 'System prompt cannot be empty.': '系統提示不能為空。', 'Describe when and how this subagent should be used.': '描述何時以及如何使用此子智能體。', 'e.g., Reviews code for best practices and potential bugs.': '例如:審查代碼以查找最佳實踐和潛在錯誤。', - 'Description cannot be empty.': '描述不能爲空。', - 'Failed to launch editor: {{error}}': '啓動編輯器失敗: {{error}}', + 'Description cannot be empty.': '描述不能為空。', + 'Failed to launch editor: {{error}}': '啟動編輯器失敗: {{error}}', 'Failed to save and edit subagent: {{error}}': '保存並編輯子智能體失敗: {{error}}', 'Manage Extensions': '管理擴展', @@ -278,7 +280,7 @@ export default { 'View Extension': '查看擴展', 'Update Extension': '更新擴展', 'Disable Extension': '禁用擴展', - 'Enable Extension': '啓用擴展', + 'Enable Extension': '啟用擴展', 'Uninstall Extension': '卸載擴展', 'Select Scope': '選擇作用域', 'User Scope': '用戶作用域', @@ -297,9 +299,9 @@ export default { 'Name:': '名稱:', 'MCP Servers:': 'MCP Servers:', 'Settings:': '設置:', - active: '已啓用', + active: '已啟用', disabled: '已禁用', - enabled: '已啓用', + enabled: '已啟用', 'View Details': '查看詳情', 'Update failed:': '更新失敗:', 'Updating {{name}}...': '正在更新 {{name}}...', @@ -307,7 +309,7 @@ export default { 'User (global)': '用戶(全局)', 'Workspace (project-specific)': '工作區(項目特定)', 'Disable "{{name}}" - Select Scope': '禁用 "{{name}}" - 選擇作用域', - 'Enable "{{name}}" - Select Scope': '啓用 "{{name}}" - 選擇作用域', + 'Enable "{{name}}" - Select Scope': '啟用 "{{name}}" - 選擇作用域', 'No extension selected': '未選擇擴展', '{{count}} extensions installed': '已安裝 {{count}} 個擴展', "Use '/extensions install' to install your first extension.": @@ -320,11 +322,11 @@ export default { 'View and edit Qwen Code settings': '查看和編輯 Qwen Code 設置', Settings: '設置', 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.': - '要查看更改,必須重啓 Qwen Code。按 r 退出並立即應用更改。', + '要查看更改,必須重啟 Qwen Code。按 r 退出並立即應用更改。', 'Vim Mode': 'Vim 模式', 'Attribution: commit': '署名:提交', 'Terminal Bell Notification': '終端響鈴通知', - 'Enable Usage Statistics': '啓用使用統計', + 'Enable Usage Statistics': '啟用使用統計', Theme: '主題', 'Preferred Editor': '首選編輯器', 'Auto-connect to IDE': '自動連接到 IDE', @@ -339,7 +341,7 @@ export default { 'Show Citations': '顯示引用', 'Custom Witty Phrases': '自定義詼諧短語', 'Show Welcome Back Dialog': '顯示歡迎回來對話框', - 'Enable User Feedback': '啓用用戶反饋', + 'Enable User Feedback': '啟用用戶反饋', 'How is Qwen doing this session? (optional)': 'Qwen 這次表現如何?(可選)', Bad: '不滿意', Fine: '還行', @@ -349,15 +351,15 @@ export default { 'Max Session Turns': '最大會話輪次', 'Skip Next Speaker Check': '跳過下一個說話者檢查', 'Skip Loop Detection': '跳過循環檢測', - 'Skip Startup Context': '跳過啓動上下文', - 'Enable OpenAI Logging': '啓用 OpenAI 日誌', + 'Skip Startup Context': '跳過啟動上下文', + 'Enable OpenAI Logging': '啟用 OpenAI 日誌', 'OpenAI Logging Directory': 'OpenAI 日誌目錄', Timeout: '超時', 'Max Retries': '最大重試次數', 'Load Memory From Include Directories': '從包含目錄加載內存', 'Respect .gitignore': '遵守 .gitignore', 'Respect .qwenignore': '遵守 .qwenignore', - 'Enable Recursive File Search': '啓用遞歸文件搜索', + 'Enable Recursive File Search': '啟用遞歸檔案搜索', 'Interactive Shell (PTY)': '交互式 Shell (PTY)', 'Show Color': '顯示顏色', 'Auto Accept': '自動接受', @@ -365,7 +367,7 @@ export default { 'Use Builtin Ripgrep': '使用內置 Ripgrep', 'Tool Output Truncation Threshold': '工具輸出截斷閾值', 'Tool Output Truncation Lines': '工具輸出截斷行數', - 'Folder Trust': '文件夾信任', + 'Folder Trust': '檔案夾信任', 'Tool Schema Compliance': 'Tool Schema 兼容性', 'Auto (detect from system)': '自動(從系統檢測)', 'Auto (detect terminal theme)': '自動(檢測終端主題)', @@ -395,7 +397,7 @@ export default { 'Manage extensions': '管理擴展', 'Manage installed extensions': '管理已安裝的擴展', 'Disable an extension': '禁用擴展', - 'Enable an extension': '啓用擴展', + 'Enable an extension': '啟用擴展', 'Install an extension from a git repo or local path': '從 Git 倉庫或本地路徑安裝擴展', 'Uninstall an extension': '卸載擴展', @@ -412,7 +414,7 @@ export default { 'Do you want to continue?': '是否繼續?', 'Installing extension "{{name}}".': '正在安裝擴展 "{{name}}"。', '**Extensions may introduce unexpected behavior. Ensure you have investigated the extension source and trust the author.**': - '**擴展可能會引入意外行爲。請確保您已調查過擴展源並信任作者。**', + '**擴展可能會引入意外行為。請確保您已調查過擴展源並信任作者。**', 'This extension will run the following MCP servers:': '此擴展將運行以下 MCP servers:', local: '本地', @@ -430,12 +432,12 @@ export default { '--ref and --auto-update are not applicable for marketplace extensions.': '--ref 和 --auto-update 不適用於市場擴展。', 'Extension "{{name}}" installed successfully and enabled.': - '擴展 "{{name}}" 安裝成功並已啓用。', + '擴展 "{{name}}" 安裝成功並已啟用。', 'The github URL, local path, or marketplace source (marketplace-url:plugin-name) of the extension to install.': '要安裝的擴展的 GitHub URL、本地路徑或市場源(marketplace-url:plugin-name)。', 'The git ref to install from.': '要安裝的 Git 引用。', - 'Enable auto-update for this extension.': '爲此擴展啓用自動更新。', - 'Enable pre-release versions for this extension.': '爲此擴展啓用預發佈版本。', + 'Enable auto-update for this extension.': '為此擴展啟用自動更新。', + 'Enable pre-release versions for this extension.': '為此擴展啟用預發佈版本。', 'Acknowledge the security risks of installing an extension and skip the confirmation prompt.': '確認安裝擴展的安全風險並跳過確認提示。', 'The source argument must be provided.': '必須提供來源參數。', @@ -445,15 +447,15 @@ export default { 'The name or source path of the extension to uninstall.': '要卸載的擴展的名稱或源路徑。', 'Please include the name of the extension to uninstall as a positional argument.': - '請將要卸載的擴展名稱作爲位置參數。', - 'Enables an extension.': '啓用擴展。', - 'The name of the extension to enable.': '要啓用的擴展名稱。', + '請將要卸載的擴展名稱作為位置參數。', + 'Enables an extension.': '啟用擴展。', + 'The name of the extension to enable.': '要啟用的擴展名稱。', 'The scope to enable the extenison in. If not set, will be enabled in all scopes.': - '啓用擴展的作用域。如果未設置,將在所有作用域中啓用。', + '啟用擴展的作用域。如果未設置,將在所有作用域中啟用。', 'Extension "{{name}}" successfully enabled for scope "{{scope}}".': - '擴展 "{{name}}" 已在作用域 "{{scope}}" 中啓用。', + '擴展 "{{name}}" 已在作用域 "{{scope}}" 中啟用。', 'Extension "{{name}}" successfully enabled in all scopes.': - '擴展 "{{name}}" 已在所有作用域中啓用。', + '擴展 "{{name}}" 已在所有作用域中啟用。', 'Invalid scope: {{scope}}. Please use one of {{scopes}}.': '無效的作用域:{{scope}}。請使用 {{scopes}} 之一。', 'Disables an extension.': '禁用擴展。', @@ -479,19 +481,19 @@ export default { 'Type:': '類型:', 'Ref:': '引用:', 'Release tag:': '發佈標籤:', - 'Enabled (User):': '已啓用(用戶):', - 'Enabled (Workspace):': '已啓用(工作區):', - 'Context files:': '上下文文件:', + 'Enabled (User):': '已啟用(用戶):', + 'Enabled (Workspace):': '已啟用(工作區):', + 'Context files:': '上下文檔案:', 'Skills:': '技能:', 'Agents:': '智能體:', 'MCP servers:': 'MCP servers:', - 'Link extension failed to install.': '鏈接擴展安裝失敗。', + 'Link extension failed to install.': '連結擴展安裝失敗。', 'Extension "{{name}}" linked successfully and enabled.': - '擴展 "{{name}}" 鏈接成功並已啓用。', + '擴展 "{{name}}" 連結成功並已啟用。', 'Links an extension from a local path. Updates made to the local path will always be reflected.': - '從本地路徑鏈接擴展。對本地路徑的更新將始終反映。', - 'The name of the extension to link.': '要鏈接的擴展名稱。', - 'Set a specific setting for an extension.': '爲擴展設置特定配置。', + '從本地路徑連結擴展。對本地路徑的更新將始終反映。', + 'The name of the extension to link.': '要連結的擴展名稱。', + 'Set a specific setting for an extension.': '為擴展設置特定配置。', 'Name of the extension to configure.': '要配置的擴展名稱。', 'The setting to configure (name or env var).': '要配置的設置(名稱或環境變量)。', @@ -522,7 +524,7 @@ export default { 'check status of IDE integration': '檢查 IDE 集成狀態', 'install required IDE companion for {{ideName}}': '安裝 {{ideName}} 所需的 IDE 配套工具', - 'enable IDE integration': '啓用 IDE 集成', + 'enable IDE integration': '啟用 IDE 集成', 'disable IDE integration': '禁用 IDE 集成', 'IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.': '您當前環境不支持 IDE 集成。要使用此功能,請在以下支持的 IDE 之一中運行 Qwen Code:VS Code 或 VS Code 分支版本。', @@ -530,30 +532,30 @@ export default { 'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf, Trae)': '配置終端按鍵綁定以支持多行輸入(VS Code、Cursor、Windsurf、Trae)', 'Please restart your terminal for the changes to take effect.': - '請重啓終端以使更改生效。', + '請重啟終端以使更改生效。', 'Failed to configure terminal: {{error}}': '配置終端失敗:{{error}}', 'Could not determine {{terminalName}} config path on Windows: APPDATA environment variable is not set.': '無法確定 {{terminalName}} 在 Windows 上的配置路徑:未設置 APPDATA 環境變量。', '{{terminalName}} keybindings.json exists but is not a valid JSON array. Please fix the file manually or delete it to allow automatic configuration.': - '{{terminalName}} keybindings.json 存在但不是有效的 JSON 數組。請手動修復文件或刪除它以允許自動配置。', - 'File: {{file}}': '文件:{{file}}', + '{{terminalName}} keybindings.json 存在但不是有效的 JSON 數組。請手動修復檔案或刪除它以允許自動配置。', + 'File: {{file}}': '檔案:{{file}}', 'Failed to parse {{terminalName}} keybindings.json. The file contains invalid JSON. Please fix the file manually or delete it to allow automatic configuration.': - '解析 {{terminalName}} keybindings.json 失敗。文件包含無效的 JSON。請手動修復文件或刪除它以允許自動配置。', + '解析 {{terminalName}} keybindings.json 失敗。檔案包含無效的 JSON。請手動修復檔案或刪除它以允許自動配置。', 'Error: {{error}}': '錯誤:{{error}}', 'Shift+Enter binding already exists': 'Shift+Enter 綁定已存在', 'Ctrl+Enter binding already exists': 'Ctrl+Enter 綁定已存在', 'Existing keybindings detected. Will not modify to avoid conflicts.': - '檢測到現有按鍵綁定。爲避免衝突,不會修改。', + '檢測到現有按鍵綁定。為避免衝突,不會修改。', 'Please check and modify manually if needed: {{file}}': '如有需要,請手動檢查並修改:{{file}}', 'Added Shift+Enter and Ctrl+Enter keybindings to {{terminalName}}.': - '已爲 {{terminalName}} 添加 Shift+Enter 和 Ctrl+Enter 按鍵綁定。', + '已為 {{terminalName}} 添加 Shift+Enter 和 Ctrl+Enter 按鍵綁定。', 'Modified: {{file}}': '已修改:{{file}}', '{{terminalName}} keybindings already configured.': '{{terminalName}} 按鍵綁定已配置。', 'Failed to configure {{terminalName}}.': '配置 {{terminalName}} 失敗。', 'Your terminal is already configured for an optimal experience with multiline input (Shift+Enter and Ctrl+Enter).': - '您的終端已配置爲支持多行輸入(Shift+Enter 和 Ctrl+Enter)的最佳體驗。', + '您的終端已配置為支持多行輸入(Shift+Enter 和 Ctrl+Enter)的最佳體驗。', 'Manage Qwen Code hooks': '管理 Qwen Code Hook', 'List all configured hooks': '列出所有已配置的 Hook', Hooks: 'Hook', @@ -568,7 +570,7 @@ export default { '{{count}} hook configured': '{{count}} 個 Hook 已配置', '{{count}} hooks configured': '{{count}} 個 Hook 已配置', 'This menu is read-only. To add or modify hooks, edit settings.json directly or ask Qwen Code.': - '此菜單爲只讀。要添加或修改 Hook,請直接編輯 settings.json 或詢問 Qwen Code。', + '此選單為只讀。要添加或修改 Hook,請直接編輯 settings.json 或詢問 Qwen Code。', 'Enter to select · Esc to cancel': 'Enter 選擇 · Esc 取消', 'Exit codes:': '退出碼:', 'Configured hooks:': '已配置的 Hook:', @@ -594,7 +596,7 @@ export default { 'Tool operations will proceed without hook validation': '工具操作將在沒有 Hook 驗證的情況下繼續', 'To re-enable hooks, remove "disableAllHooks" from settings.json or ask Qwen Code.': - '要重新啓用 Hook,請從 settings.json 中刪除 "disableAllHooks" 或詢問 Qwen Code。', + '要重新啟用 Hook,請從 settings.json 中刪除 "disableAllHooks" 或詢問 Qwen Code。', Project: '項目', User: '用戶', Skill: '技能', @@ -613,33 +615,33 @@ export default { 'When a new session is started': '新會話開始時', 'Right before Qwen Code concludes its response': 'Qwen Code 結束響應之前', 'When a subagent (Agent tool call) is started': - '子智能體(Agent 工具調用)啓動時', + '子智能體(Agent 工具調用)啟動時', 'Right before a subagent concludes its response': '子智能體結束響應之前', 'Before conversation compaction': '對話壓縮前', 'When a session is ending': '會話結束時', 'When a permission dialog is displayed': '顯示權限對話框時', 'Input to command is JSON of tool call arguments.': - '命令輸入爲工具調用參數的 JSON。', + '命令輸入為工具調用參數的 JSON。', 'Input to command is JSON with fields "inputs" (tool call arguments) and "response" (tool call response).': - '命令輸入爲包含 "inputs"(工具調用參數)和 "response"(工具調用響應)字段的 JSON。', + '命令輸入為包含 "inputs"(工具調用參數)和 "response"(工具調用響應)字段的 JSON。', 'Input to command is JSON with tool_name, tool_input, tool_use_id, error, error_type, is_interrupt, and is_timeout.': - '命令輸入爲包含 tool_name、tool_input、tool_use_id、error、error_type、is_interrupt 和 is_timeout 的 JSON。', + '命令輸入為包含 tool_name、tool_input、tool_use_id、error、error_type、is_interrupt 和 is_timeout 的 JSON。', 'Input to command is JSON with notification message and type.': - '命令輸入爲包含通知消息和類型的 JSON。', + '命令輸入為包含通知消息和類型的 JSON。', 'Input to command is JSON with original user prompt text.': - '命令輸入爲包含原始用戶提示文本的 JSON。', + '命令輸入為包含原始用戶提示文本的 JSON。', 'Input to command is JSON with session start source.': - '命令輸入爲包含會話啓動來源的 JSON。', + '命令輸入為包含會話啟動來源的 JSON。', 'Input to command is JSON with session end reason.': - '命令輸入爲包含會話結束原因的 JSON。', + '命令輸入為包含會話結束原因的 JSON。', 'Input to command is JSON with agent_id and agent_type.': - '命令輸入爲包含 agent_id 和 agent_type 的 JSON。', + '命令輸入為包含 agent_id 和 agent_type 的 JSON。', 'Input to command is JSON with agent_id, agent_type, and agent_transcript_path.': - '命令輸入爲包含 agent_id、agent_type 和 agent_transcript_path 的 JSON。', + '命令輸入為包含 agent_id、agent_type 和 agent_transcript_path 的 JSON。', 'Input to command is JSON with compaction details.': - '命令輸入爲包含壓縮詳情的 JSON。', + '命令輸入為包含壓縮詳情的 JSON。', 'Input to command is JSON with tool_name, tool_input, and tool_use_id. Output JSON with hookSpecificOutput containing decision to allow or deny.': - '命令輸入爲包含 tool_name、tool_input 和 tool_use_id 的 JSON。輸出包含 hookSpecificOutput 的 JSON,其中包含允許或拒絕的決定。', + '命令輸入為包含 tool_name、tool_input 和 tool_use_id 的 JSON。輸出包含 hookSpecificOutput 的 JSON,其中包含允許或拒絕的決定。', 'stdout/stderr not shown': 'stdout/stderr 不顯示', 'show stderr to model and continue conversation': '向模型顯示 stderr 並繼續對話', @@ -658,21 +660,21 @@ export default { 'show stderr to subagent and continue having it run': '向子智能體顯示 stderr 並繼續運行', 'stdout appended as custom compact instructions': - 'stdout 作爲自定義壓縮指令追加', + 'stdout 作為自定義壓縮指令追加', 'block compaction': '阻止壓縮', 'show stderr to user only but continue with compaction': '僅向用戶顯示 stderr 但繼續壓縮', 'use hook decision if provided': '如果提供則使用 Hook 決定', 'Config not loaded.': '配置未加載。', 'Hooks are not enabled. Enable hooks in settings to use this feature.': - 'Hook 未啓用。請在設置中啓用 Hook 以使用此功能。', + 'Hook 未啟用。請在設置中啟用 Hook 以使用此功能。', 'Export current session message history to a file': - '將當前會話的消息記錄導出到文件', - 'Export session to HTML format': '將會話導出爲 HTML 文件', - 'Export session to JSON format': '將會話導出爲 JSON 文件', + '將當前會話的消息記錄導出到檔案', + 'Export session to HTML format': '將會話導出為 HTML 檔案', + 'Export session to JSON format': '將會話導出為 JSON 檔案', 'Export session to JSONL format (one message per line)': - '將會話導出爲 JSONL 文件(每行一條消息)', - 'Export session to markdown format': '將會話導出爲 Markdown 文件', + '將會話導出為 JSONL 檔案(每行一條消息)', + 'Export session to markdown format': '將會話導出為 Markdown 檔案', 'generate personalized programming insights from your chat history': '根據你的聊天記錄生成個性化編程洞察', 'Resume a previous session': '恢復先前會話', @@ -681,7 +683,7 @@ export default { '回應或工具呼叫正在進行時無法分支。請等待其完成或處理待確認的工具呼叫。', 'No conversation to branch.': '沒有可分支的對話。', 'Restore a tool call. This will reset the conversation and file history to the state it was in when the tool call was suggested': - '恢復某次工具調用。這將把對話與文件歷史重置到提出該工具調用建議時的狀態', + '恢復某次工具調用。這將把對話與檔案歷史重置到提出該工具調用建議時的狀態', 'Could not detect terminal type. Supported terminals: VS Code, Cursor, Windsurf, and Trae.': '無法檢測終端類型。支持的終端:VS Code、Cursor、Windsurf 和 Trae。', 'Terminal "{{terminal}}" is not supported yet.': @@ -699,31 +701,31 @@ export default { 'Example: /language output 中文': '示例:/language output 中文', 'Example: /language output English': '示例:/language output English', 'Example: /language output 日本語': '示例:/language output 日本語', - 'UI language changed to {{lang}}': 'UI 語言已更改爲 {{lang}}', - 'LLM output language set to {{lang}}': 'LLM 輸出語言已設置爲 {{lang}}', + 'UI language changed to {{lang}}': 'UI 語言已更改為 {{lang}}', + 'LLM output language set to {{lang}}': 'LLM 輸出語言已設置為 {{lang}}', 'Please restart the application for the changes to take effect.': - '請重啓應用程序以使更改生效。', + '請重啟應用程序以使更改生效。', 'Failed to generate LLM output language rule file: {{error}}': - '生成 LLM 輸出語言規則文件失敗:{{error}}', + '生成 LLM 輸出語言規則檔案失敗:{{error}}', 'Invalid command. Available subcommands:': '無效的命令。可用的子命令:', 'Available subcommands:': '可用的子命令:', 'To request additional UI language packs, please open an issue on GitHub.': '如需請求其他 UI 語言包,請在 GitHub 上提交 issue', 'Available options:': '可用選項:', - 'Set UI language to {{name}}': '將 UI 語言設置爲 {{name}}', + 'Set UI language to {{name}}': '將 UI 語言設置為 {{name}}', 'Tool Approval Mode': '工具審批模式', '{{mode}} mode': '{{mode}} 模式', 'Analyze only, do not modify files or execute commands': - '僅分析,不修改文件或執行命令', + '僅分析,不修改檔案或執行命令', 'Require approval for file edits or shell commands': - '需要批准文件編輯或 shell 命令', - 'Automatically approve file edits': '自動批准文件編輯', + '需要批准檔案編輯或 shell 命令', + 'Automatically approve file edits': '自動批准檔案編輯', 'Automatically approve all tools': '自動批准所有工具', 'Workspace approval mode exists and takes priority. User-level change will have no effect.': '工作區審批模式已存在並具有優先級。用戶級別的更改將無效。', 'Apply To': '應用於', 'Workspace Settings': '工作區設置', - 'Open auto-memory folder': '打開自動記憶文件夾', + 'Open auto-memory folder': '打開自動記憶檔案夾', 'Auto-memory: {{status}}': '自動記憶:{{status}}', 'Auto-dream: {{status}} · {{lastDream}} · /dream to run': '自動整理:{{status}} · {{lastDream}} · /dream 立即運行', @@ -735,7 +737,7 @@ export default { 'Usage: /forget ': '用法:/forget <要刪除的記憶文本>', 'No managed auto-memory entries matched: {{query}}': '沒有匹配的託管自動記憶條目:{{query}}', - 'Consolidate managed auto-memory topic files.': '整理託管自動記憶主題文件', + 'Consolidate managed auto-memory topic files.': '整理託管自動記憶主題檔案', 'Open MCP management dialog': '打開 MCP 管理對話框', 'Could not retrieve tool registry.': '無法檢索工具註冊表', "Successfully authenticated and refreshed tools for '{{name}}'.": @@ -745,10 +747,10 @@ export default { "Discovered {{count}} tool(s) from '{{name}}'.": "從 '{{name}}' 發現了 {{count}} 個工具。", 'Authentication complete. Returning to server details...': - '認證完成,正在返回服務器詳情...', + '認證完成,正在返回伺服器詳情...', 'Authentication successful.': '認證成功。', 'Manage MCP servers': '管理 MCP servers', - 'Server Detail': '服務器詳情', + 'Server Detail': '伺服器詳情', Tools: '工具', 'Tool Detail': '工具詳情', 'Loading...': '加載中...', @@ -762,21 +764,21 @@ export default { '↑↓ 導航 · Enter 確認 · Esc 返回', 'User Settings (global)': '用戶設置(全局)', 'Workspace Settings (project-specific)': '工作區設置(項目級)', - 'Disable server:': '禁用服務器:', + 'Disable server:': '禁用伺服器:', 'Select where to add the server to the exclude list:': - '選擇將服務器添加到排除列表的位置:', + '選擇將伺服器添加到排除列表的位置:', 'Press Enter to confirm, Esc to cancel': '按 Enter 確認,Esc 取消', 'View tools': '查看工具', Reconnect: '重新連接', - Enable: '啓用', + Enable: '啟用', Disable: '禁用', Authenticate: '認證', 'Re-authenticate': '重新認證', 'Clear Authentication': '清空認證', - 'Server:': '服務器:', + 'Server:': '伺服器:', 'Command:': '命令:', 'Working Directory:': '工作目錄:', - 'No server selected': '未選擇服務器', + 'No server selected': '未選擇伺服器', prompts: '提示詞', 'Error:': '錯誤:', tool: '工具', @@ -787,8 +789,8 @@ export default { 'User MCPs': '用戶 MCP', 'Project MCPs': '項目 MCP', 'Extension MCPs': '擴展 MCP', - server: '個服務器', - servers: '個服務器', + server: '個伺服器', + servers: '個伺服器', 'Add MCP servers to your settings to get started.': '請在設置中添加 MCP servers 以開始使用。', 'Run qwen --debug to see error logs': '運行 qwen --debug 查看錯誤日誌', @@ -798,10 +800,10 @@ export default { 'Press c to copy the authorization URL to your clipboard.': '按 c 複製授權 URL 到剪貼板。', 'Copy request sent to your terminal. If paste is empty, copy the URL above manually.': - '已向終端發送複製請求;若粘貼爲空,請手動複製上方 URL。', + '已向終端發送複製請求;若粘貼為空,請手動複製上方 URL。', 'Cannot write to terminal — copy the URL above manually.': '無法寫入終端,請手動複製上方 URL。', - 'No tools available for this server.': '此服務器沒有可用工具。', + 'No tools available for this server.': '此伺服器沒有可用工具。', destructive: '破壞性', 'read-only': '只讀', 'open-world': '開放世界', @@ -811,7 +813,7 @@ export default { required: '必填', Parameters: '參數', 'No tool selected': '未選擇工具', - Server: '服務器', + Server: '伺服器', '{{count}} invalid tools': '{{count}} 個無效工具', invalid: '無效', 'invalid: {{reason}}': '無效:{{reason}}', @@ -856,24 +858,24 @@ export default { 'Starting a new session and clearing.': '正在開始新會話並清屏。', 'Already compressing, wait for previous request to complete': '正在壓縮中,請等待上一個請求完成', - 'Failed to compress chat history.': '壓縮聊天曆史失敗', - 'Failed to compress chat history: {{error}}': '壓縮聊天曆史失敗:{{error}}', - 'Compressing chat history': '正在壓縮聊天曆史', + 'Failed to compress chat history.': '壓縮聊天歷史失敗', + 'Failed to compress chat history: {{error}}': '壓縮聊天歷史失敗:{{error}}', + 'Compressing chat history': '正在壓縮聊天歷史', 'Chat history compressed from {{originalTokens}} to {{newTokens}} tokens.': - '聊天曆史已從 {{originalTokens}} 個 token 壓縮到 {{newTokens}} 個 token。', + '聊天歷史已從 {{originalTokens}} 個 token 壓縮到 {{newTokens}} 個 token。', 'Compression was not beneficial for this history size.': '對於此歷史記錄大小,壓縮沒有益處。', 'Chat history compression did not reduce size. This may indicate issues with the compression prompt.': - '聊天曆史壓縮未能減小大小。這可能表明壓縮提示存在問題。', + '聊天歷史壓縮未能減小大小。這可能表明壓縮提示存在問題。', 'Could not compress chat history due to a token counting error.': - '由於 token 計數錯誤,無法壓縮聊天曆史。', + '由於 token 計數錯誤,無法壓縮聊天歷史。', 'Configuration is not available.': '配置不可用。', 'Please provide at least one path to add.': '請提供至少一個要添加的路徑。', 'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.': - '/directory add 命令在限制性沙箱配置文件中不受支持。請改爲在啓動會話時使用 --include-directories。', + '/directory add 命令在限制性沙箱配置檔案中不受支持。請改為在啟動會話時使用 --include-directories。', "Error adding '{{path}}': {{error}}": "添加 '{{path}}' 時出錯:{{error}}", 'Successfully added QWEN.md files from the following directories if there are:\n- {{directories}}': - '如果存在,已成功從以下目錄添加 QWEN.md 文件:\n- {{directories}}', + '如果存在,已成功從以下目錄添加 QWEN.md 檔案:\n- {{directories}}', 'Error refreshing memory: {{error}}': '刷新內存時出錯:{{error}}', 'Successfully added directories:\n- {{directories}}': '成功添加目錄:\n- {{directories}}', @@ -960,7 +962,7 @@ export default { 'Browser-based authentication with third-party providers (e.g. OpenRouter, ModelScope)': '基於瀏覽器的第三方提供商認證(例如 OpenRouter、ModelScope)', 'Authentication is enforced to be {{enforcedType}}, but you are currently using {{currentType}}.': - '認證方式被強制設置爲 {{enforcedType}},但您當前使用的是 {{currentType}}', + '認證方式被強制設置為 {{enforcedType}},但您當前使用的是 {{currentType}}', 'Qwen OAuth Authentication': 'Qwen OAuth 認證', 'Please visit this URL to authorize:': '請訪問此 URL 進行授權:', 'Waiting for authorization': '等待授權中', @@ -977,9 +979,9 @@ export default { 'Missing API key for OpenAI-compatible auth. Set settings.security.auth.apiKey, or set the {{envKeyHint}} environment variable.': '缺少 OpenAI 兼容認證的 API Key。請設置 settings.security.auth.apiKey 或設置 {{envKeyHint}} 環境變量。', '{{envKeyHint}} environment variable not found. Please set it in your .env file or environment variables.': - '未找到 {{envKeyHint}} 環境變量。請在 .env 文件或系統環境變量中進行設置。', + '未找到 {{envKeyHint}} 環境變量。請在 .env 檔案或系統環境變量中進行設置。', '{{envKeyHint}} environment variable not found (or set settings.security.auth.apiKey). Please set it in your .env file or environment variables.': - '未找到 {{envKeyHint}} 環境變量(或設置 settings.security.auth.apiKey)。請在 .env 文件或系統環境變量中進行設置。', + '未找到 {{envKeyHint}} 環境變量(或設置 settings.security.auth.apiKey)。請在 .env 檔案或系統環境變量中進行設置。', 'Missing API key for OpenAI-compatible auth. Set the {{envKeyHint}} environment variable.': '缺少 OpenAI 兼容認證的 API Key。請設置 {{envKeyHint}} 環境變量。', 'Anthropic provider missing required baseUrl in modelProviders[].baseUrl.': @@ -991,7 +993,7 @@ export default { 'Authenticated successfully with {{authType}} credentials.': '使用 {{authType}} 憑據成功認證。', 'Invalid QWEN_DEFAULT_AUTH_TYPE value: "{{value}}". Valid values are: {{validValues}}': - '無效的 QWEN_DEFAULT_AUTH_TYPE 值:"{{value}}"。有效值爲:{{validValues}}', + '無效的 QWEN_DEFAULT_AUTH_TYPE 值:"{{value}}"。有效值為:{{validValues}}', 'Select Model': '選擇模型', 'API Key': 'API Key', '(default)': '(默認)', @@ -1007,7 +1009,7 @@ export default { 'not set': '未設置', none: '無', unknown: '未知', - 'Manage folder trust settings': '管理文件夾信任設置', + 'Manage folder trust settings': '管理檔案夾信任設置', 'Manage permission rules': '管理 permission rules', Allow: '允許', Ask: '詢問', @@ -1052,9 +1054,9 @@ export default { 'Add directory…': '添加目錄…', 'Add directory to workspace': '添加工作區目錄', 'Qwen Code can read files in the workspace, and make edits when auto-accept edits is on.': - 'Qwen Code 可以讀取工作區中的文件,並在自動接受編輯模式開啓時進行編輯。', + 'Qwen Code 可以讀取工作區中的檔案,並在自動接受編輯模式開啟時進行編輯。', 'Qwen Code will be able to read files in this directory and make edits when auto-accept edits is on.': - 'Qwen Code 將能夠讀取此目錄中的文件,並在自動接受編輯模式開啓時進行編輯。', + 'Qwen Code 將能夠讀取此目錄中的檔案,並在自動接受編輯模式開啟時進行編輯。', 'Enter the path to the directory:': '輸入目錄路徑:', 'Enter directory path…': '輸入目錄路徑…', 'Tab to complete · Enter to add · Esc to cancel': @@ -1069,11 +1071,11 @@ export default { 'This directory is already in the workspace.': '此目錄已在工作區中。', 'Already covered by existing directory: {{dir}}': '已被現有目錄覆蓋:{{dir}}', 'Using:': '已加載: ', - '{{count}} open file': '{{count}} 個打開的文件', - '{{count}} open files': '{{count}} 個打開的文件', + '{{count}} open file': '{{count}} 個打開的檔案', + '{{count}} open files': '{{count}} 個打開的檔案', '(ctrl+g to view)': '(按 ctrl+g 查看)', - '{{count}} {{name}} file': '{{count}} 個 {{name}} 文件', - '{{count}} {{name}} files': '{{count}} 個 {{name}} 文件', + '{{count}} {{name}} file': '{{count}} 個 {{name}} 檔案', + '{{count}} {{name}} files': '{{count}} 個 {{name}} 檔案', '{{count}} MCP server': '{{count}} 個 MCP server', '{{count}} MCP servers': '{{count}} 個 MCP servers', '{{count}} Blocked': '{{count}} 個已阻止', @@ -1085,13 +1087,13 @@ export default { 'Press ↑ to edit queued messages': '按 ↑ 編輯排隊消息', 'No MCP servers configured.': '未配置 MCP servers', '⏳ MCP servers are starting up ({{count}} initializing)...': - '⏳ MCP servers 正在啓動({{count}} 個正在初始化)...', + '⏳ MCP servers 正在啟動({{count}} 個正在初始化)...', 'Note: First startup may take longer. Tool availability will update automatically.': - '注意:首次啓動可能需要更長時間。工具可用性將自動更新', + '注意:首次啟動可能需要更長時間。工具可用性將自動更新', 'Configured MCP servers:': '已配置的 MCP servers:', Ready: '就緒', 'Starting... (first startup may take longer)': - '正在啓動...(首次啓動可能需要更長時間)', + '正在啟動...(首次啟動可能需要更長時間)', Disconnected: '已斷開連接', '{{count}} tool': '{{count}} 個工具', '{{count}} tools': '{{count}} 個工具', @@ -1109,20 +1111,20 @@ export default { Blocked: '已阻止', '💡 Tips:': '💡 提示:', Use: '使用', - 'to show server and tool descriptions': '顯示服務器和工具描述', + 'to show server and tool descriptions': '顯示伺服器和工具描述', 'to show tool parameter schemas': '顯示 tool parameter schemas', 'to hide descriptions': '隱藏描述', 'to authenticate with OAuth-enabled servers': - '使用支持 OAuth 的服務器進行認證', + '使用支持 OAuth 的伺服器進行認證', Press: '按', 'to toggle tool descriptions on/off': '切換工具描述開關', "Starting OAuth authentication for MCP server '{{name}}'...": - "正在爲 MCP server '{{name}}' 啓動 OAuth 認證...", + "正在為 MCP server '{{name}}' 啟動 OAuth 認證...", 'Tips:': '提示:', 'Use /compress when the conversation gets long to summarize history and free up context.': '對話變長時用 /compress,總結歷史並釋放上下文。', 'Start a fresh idea with /clear or /new; the previous session stays available in history.': - '用 /clear 或 /new 開啓新思路;之前的會話會保留在歷史記錄中。', + '用 /clear 或 /new 開啟新思路;之前的會話會保留在歷史記錄中。', 'Use /bug to submit issues to the maintainers when something goes off.': '遇到問題時,用 /bug 將問題提交給維護者。', 'Switch auth type quickly with /auth.': '用 /auth 快速切換認證方式。', @@ -1141,11 +1143,11 @@ export default { 'Press Ctrl+O to toggle compact mode — hide tool output and thinking for a cleaner view.': '按 Ctrl+O 切換緊湊模式 ── 隱藏工具輸出和思考過程,界面更簡潔。', 'Add a QWEN.md file to give Qwen Code persistent project context.': - '添加 QWEN.md 文件,爲 Qwen Code 提供持久的項目上下文。', + '添加 QWEN.md 檔案,為 Qwen Code 提供持久的項目上下文。', 'Use /btw to ask a quick side question without disrupting the conversation.': '用 /btw 快速問一個小問題,不會打斷當前對話。', 'Context is almost full! Run /compress now or start /new to continue.': - '上下文即將用滿!請立即執行 /compress 或使用 /new 開啓新會話。', + '上下文即將用滿!請立即執行 /compress 或使用 /new 開啟新會話。', 'Context is getting full. Use /compress to free up space.': '上下文空間不足,用 /compress 釋放空間。', 'Long conversation? /compress summarizes history to free context.': @@ -1204,21 +1206,21 @@ export default { 'Session start time is unavailable, cannot calculate stats.': '會話開始時間不可用,無法計算統計信息', 'Command Format Migration': '命令格式遷移', - 'Found {{count}} TOML command file:': '發現 {{count}} 個 TOML 命令文件:', - 'Found {{count}} TOML command files:': '發現 {{count}} 個 TOML 命令文件:', + 'Found {{count}} TOML command file:': '發現 {{count}} 個 TOML 命令檔案:', + 'Found {{count}} TOML command files:': '發現 {{count}} 個 TOML 命令檔案:', 'Current tasks': '目前任務', '... and {{count}} more': '... 以及其他 {{count}} 個', 'The TOML format is deprecated. Would you like to migrate them to Markdown format?': 'TOML 格式已棄用。是否將它們遷移到 Markdown 格式?', '(Backups will be created and original files will be preserved)': - '(將創建備份,原始文件將保留)', + '(將創建備份,原始檔案將保留)', 'Waiting for user confirmation...': '等待用戶確認...', WITTY_LOADING_PHRASES: [ '正在努力搬磚,請稍候...', '老闆在身後,快加載啊!', '頭髮掉光前,一定能加載完...', - '服務器正在深呼吸,準備放大招...', - '正在向服務器投餵咖啡...', + '伺服器正在深呼吸,準備放大招...', + '正在向伺服器投餵咖啡...', '正在賦能全鏈路,尋找關鍵抓手...', '正在降本增效,優化加載路徑...', '正在打破部門壁壘,沉澱方法論...', @@ -1226,7 +1228,7 @@ export default { '正在對齊顆粒度,打磨底層邏輯...', '大力出奇跡,正在強行加載...', '只要我不寫代碼,代碼就沒有 Bug...', - '正在把 Bug 轉化爲 Feature...', + '正在把 Bug 轉化為 Feature...', '只要我不尷尬,Bug 就追不上我...', '正在試圖理解去年的自己寫了什麼...', '正在猿力覺醒中,請耐心等待...', @@ -1243,23 +1245,23 @@ export default { 'Enter sensitive value...': '請輸入敏感值...', 'Press Enter to submit, Escape to cancel': '按 Enter 提交,Escape 取消', 'Markdown file already exists: {{filename}}': - 'Markdown 文件已存在:{{filename}}', + 'Markdown 檔案已存在:{{filename}}', 'TOML Command Format Deprecation Notice': 'TOML 命令格式棄用通知', 'Found {{count}} command file(s) in TOML format:': - '發現 {{count}} 個 TOML 格式的命令文件:', + '發現 {{count}} 個 TOML 格式的命令檔案:', 'The TOML format for commands is being deprecated in favor of Markdown format.': '命令的 TOML 格式正在被棄用,推薦使用 Markdown 格式。', 'Markdown format is more readable and easier to edit.': 'Markdown 格式更易讀、更易編輯。', 'You can migrate these files automatically using:': - '您可以使用以下命令自動遷移這些文件:', - 'Or manually convert each file:': '或手動轉換每個文件:', + '您可以使用以下命令自動遷移這些檔案:', + 'Or manually convert each file:': '或手動轉換每個檔案:', 'TOML: prompt = "..." / description = "..."': 'TOML:prompt = "..." / description = "..."', 'Markdown: YAML frontmatter + content': 'Markdown:YAML frontmatter + 內容', 'The migration tool will:': '遷移工具將:', - 'Convert TOML files to Markdown': '將 TOML 文件轉換爲 Markdown', - 'Create backups of original files': '創建原始文件的備份', + 'Convert TOML files to Markdown': '將 TOML 檔案轉換為 Markdown', + 'Create backups of original files': '創建原始檔案的備份', 'Preserve all command functionality': '保留所有命令功能', 'TOML format will continue to work for now, but migration is recommended.': 'TOML 格式目前仍可使用,但建議遷移。', @@ -1277,7 +1279,7 @@ export default { 'Press Ctrl+Y to retry': '按 Ctrl+Y 重試。', 'No failed request to retry.': '沒有可重試的失敗請求。', 'to retry last request': '重試上一次請求', - 'API key cannot be empty.': 'API Key 不能爲空。', + 'API key cannot be empty.': 'API Key 不能為空。', 'Invalid API key. Coding Plan API keys start with "sk-sp-". Please check.': '無效的 API Key,Coding Plan API Key 均以 "sk-sp-" 開頭,請檢查', 'You can get your Coding Plan API key here': @@ -1285,7 +1287,7 @@ export default { 'You can get your Token Plan API key here': '您可以在這裏獲取 Token Plan API Key', 'API key is stored in settings.env. You can migrate it to a .env file for better security.': - 'API Key 已存儲在 settings.env 中。您可以將其遷移到 .env 文件以獲得更好的安全性。', + 'API Key 已存儲在 settings.env 中。您可以將其遷移到 .env 檔案以獲得更好的安全性。', 'New model configurations are available for Alibaba Cloud Coding Plan. Update now?': '阿里雲百鍊 Coding Plan 有新模型配置可用。是否立即更新?', 'Coding Plan configuration updated successfully. New models are now available.': @@ -1325,7 +1327,7 @@ export default { 'System prompt': '系統提示', 'Built-in tools': '內置工具', 'MCP tools': 'MCP tools', - 'Memory files': '記憶文件', + 'Memory files': '記憶檔案', Skills: '技能', Messages: '消息', 'Run /context detail for per-item breakdown.': @@ -1357,7 +1359,7 @@ export default { 'API key for Coding Plan': 'Coding Plan 的 API Key', 'Show current authentication status': '顯示當前認證狀態', 'Authentication completed successfully.': '認證完成。', - 'Starting Qwen OAuth authentication...': '正在啓動 Qwen OAuth 認證...', + 'Starting Qwen OAuth authentication...': '正在啟動 Qwen OAuth 認證...', 'Successfully authenticated with Qwen OAuth.': '已成功通過 Qwen OAuth 認證。', 'Failed to authenticate with Qwen OAuth: {{error}}': 'Qwen OAuth 認證失敗:{{error}}', @@ -1419,7 +1421,7 @@ export default { 'Exited plan mode. Previous approval mode restored.': '已退出計劃模式,已恢復之前的審批模式。', 'Enabled plan mode. The agent will analyze and plan without executing tools.': - '啓用計劃模式。智能體將只分析和規劃,而不執行工具。', + '啟用計劃模式。智能體將只分析和規劃,而不執行工具。', 'Already in plan mode. Use "/plan exit" to exit plan mode.': '已處於計劃模式。使用 "/plan exit" 退出計劃模式。', 'Not in plan mode. Use "/plan" to enter plan mode first.': @@ -1487,7 +1489,7 @@ export default { 'Manage AI translation for dynamic slash command descriptions': '管理動態斜線命令描述的 AI 翻譯', 'Enable AI translation for dynamic slash command descriptions': - '啓用動態斜線命令描述的 AI 翻譯', + '啟用動態斜線命令描述的 AI 翻譯', 'Disable AI translation for dynamic slash command descriptions': '禁用動態斜線命令描述的 AI 翻譯', 'Show AI translation status for dynamic slash command descriptions': @@ -1495,7 +1497,7 @@ export default { 'AI translation for dynamic slash command descriptions is {{status}}.': '動態斜線命令描述的 AI 翻譯處於{{status}}狀態。', 'AI translation for dynamic slash command descriptions is now enabled.': - '動態斜線命令描述的 AI 翻譯現已啓用。', + '動態斜線命令描述的 AI 翻譯現已啟用。', 'AI translation for dynamic slash command descriptions is now disabled.': '動態斜線命令描述的 AI 翻譯現已禁用。', 'Manage extension settings': '管理擴展設置', diff --git a/scripts/check-i18n.ts b/scripts/check-i18n.ts index 282d21e0f..eb9109e6e 100644 --- a/scripts/check-i18n.ts +++ b/scripts/check-i18n.ts @@ -71,6 +71,112 @@ export function shouldWriteUnusedKeysJson(): boolean { ); } +/** + * Substrings that should not appear in zh-TW (Taiwan Traditional Chinese) values. + * + * Three categories of regressions we want to catch automatically: + * 1. Variant Traditional characters that OpenCC s2t produces by default but + * Taiwan does not use as primary forms (e.g. 爲, 啓). + * 2. Mainland-Chinese vocabulary whose characters are valid Traditional but + * the word itself is not used in Taiwan (e.g. 服務器, 菜單, 鏈接). + * 3. Pure Simplified Chinese characters that would only appear if OpenCC + * was not run at all (e.g. 为, 启, 链). + * + * Deliberately excluded to avoid false positives: + * - 禁用 / 配置 / 設置 — standard in Taiwan. + * - 文件 — contextual (can legitimately mean "document"). + * - 打開 — colloquially common in Taiwan even if 開啟 is preferred for UI. + * - Bare 鏈 — valid in 區塊鏈 etc.; only the bigram 鏈接 is flagged. + * + * Known limitation: matching is plain substring (`includes()`) and does not + * respect Chinese word boundaries. Bigram patterns can therefore false-positive + * across compound-word boundaries — e.g. `區塊鏈接口` (= `區塊鏈` + `接口`) + * contains the substring `鏈接` even though neither word is wrong. When this + * happens, add the affected translation key to ZH_TW_ALLOWED_EXCEPTIONS below + * with a brief justification, rather than weakening the pattern list. + */ +const ZH_TW_FORBIDDEN_PATTERNS_RAW: ReadonlyArray<{ + pattern: string; + preferred: string; +}> = [ + // Variant Traditional characters from OpenCC s2t output + { pattern: '爲', preferred: '為' }, + { pattern: '啓', preferred: '啟' }, + // Mainland-Chinese vocabulary (valid Traditional chars, wrong word for Taiwan) + { pattern: '曆史', preferred: '歷史' }, + { pattern: '鏈接', preferred: '連結' }, + { pattern: '菜單', preferred: '選單' }, + { pattern: '服務器', preferred: '伺服器' }, + // Same Mainland vocabulary written in Simplified form + { pattern: '菜单', preferred: '選單' }, + { pattern: '服务器', preferred: '伺服器' }, + { pattern: '链接', preferred: '連結' }, + { pattern: '历史', preferred: '歷史' }, + // Pure Simplified characters (no ambiguity with valid Traditional usage) + { pattern: '为', preferred: '為' }, + { pattern: '启', preferred: '啟' }, + { pattern: '历', preferred: '歷' }, + { pattern: '链', preferred: '鏈/連' }, + { pattern: '选', preferred: '選' }, + { pattern: '删', preferred: '刪' }, + { pattern: '扩', preferred: '擴' }, + { pattern: '设', preferred: '設' }, + { pattern: '详', preferred: '詳' }, + { pattern: '认', preferred: '認' }, +]; + +// Sorted longest-first so that more specific patterns (e.g. `历史`) are matched +// before their constituent characters (`历`), avoiding duplicate findings on +// the same translation value. +const ZH_TW_FORBIDDEN_PATTERNS = [...ZH_TW_FORBIDDEN_PATTERNS_RAW].sort( + (a, b) => b.pattern.length - a.pattern.length, +); + +/** + * Translation keys whose zh-TW value is allowed to contain an otherwise + * forbidden substring. Use this as an escape hatch when a legitimate + * translation needs a normally-banned character or word — add the key here + * with a comment explaining why, instead of weakening the global pattern list. + * + * Example: + * 'Open block explorer for {{address}}': '...區塊鏈瀏覽器...', // 區塊鏈 = blockchain + */ +const ZH_TW_ALLOWED_EXCEPTIONS: ReadonlySet = new Set([ + // (empty — no legitimate exceptions today) +]); + +/** + * Walk every translation value and report any value containing a forbidden + * substring. Iterating over the parsed dict (rather than the raw file) + * lets us report the offending key, and avoids matching characters inside + * file-level comments or JS syntax. + * + * Only the longest matching pattern per value is reported, to keep CI output + * focused on the most actionable fix. + */ +export function findForbiddenZhTwPatterns( + translations: TranslationDict, +): Array<{ key: string; pattern: string; preferred: string }> { + const findings: Array<{ key: string; pattern: string; preferred: string }> = + []; + + for (const [key, value] of Object.entries(translations)) { + if (ZH_TW_ALLOWED_EXCEPTIONS.has(key)) continue; + const candidates = Array.isArray(value) ? value : [value]; + for (const candidate of candidates) { + if (typeof candidate !== 'string') continue; + for (const { pattern, preferred } of ZH_TW_FORBIDDEN_PATTERNS) { + if (candidate.includes(pattern)) { + findings.push({ key, pattern, preferred }); + break; + } + } + } + } + + return findings; +} + async function loadTranslationsFile( filePath: string, ): Promise { @@ -433,6 +539,19 @@ export async function checkI18n( } } + // Check zh-TW.js for Taiwan-vocabulary regressions (raw OpenCC output, + // Mainland-Chinese vocabulary, or Simplified characters slipping in). + const zhTWTranslations = localeTranslations.get('zh-TW'); + if (zhTWTranslations) { + for (const { key, pattern, preferred } of findForbiddenZhTwPatterns( + zhTWTranslations, + )) { + errors.push( + `Non-Taiwan vocabulary in zh-TW.js at "${key}": "${pattern}" should be "${preferred}"`, + ); + } + } + const usedKeys = await extractUsedKeys(sourceDir); const unusedKeys = findUnusedKeys(enKeys, usedKeys); const unusedKeysOnlyInLocales = diff --git a/scripts/tests/check-i18n.test.ts b/scripts/tests/check-i18n.test.ts index 2f711b874..4069f265e 100644 --- a/scripts/tests/check-i18n.test.ts +++ b/scripts/tests/check-i18n.test.ts @@ -17,6 +17,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { LanguageDefinition } from '../../packages/cli/src/i18n/languages.js'; import { checkI18n, + findForbiddenZhTwPatterns, printCheckI18nResult, shouldWriteUnusedKeysJson, type CheckI18nOptions, @@ -341,6 +342,84 @@ describe('checkI18n', () => { expect(result.stats.unusedKeys).toEqual([]); }); + it('flags Mainland-Chinese vocabulary and variant Traditional chars in zh-TW values', async () => { + const { localesDir, sourceDir } = makeFixture(); + writeLocale(localesDir, 'en', { + Open: 'Open', + Server: 'Server', + Menu: 'Menu', + Disable: 'Disable', + Config: 'Config', + }); + writeLocale(localesDir, 'zh', { + Open: '打开', + Server: '服务器', + Menu: '菜单', + Disable: '禁用', + Config: '配置', + }); + writeLocale(localesDir, 'zh-TW', { + // Regressions we expect the check to catch + Open: '啓動', // variant Traditional 啓 (OpenCC s2t artifact) + Server: '服務器', // Mainland vocabulary + Menu: '菜單', // Mainland vocabulary + // Taiwan-standard vocabulary — must NOT be flagged + Disable: '禁用', + Config: '配置', + }); + writeSource( + sourceDir, + "t('Open');\nt('Server');\nt('Menu');\nt('Disable');\nt('Config');\n", + ); + + const result = await checkI18n({ + localesDir, + sourceDir, + supportedLanguages: languages('en', 'zh', 'zh-TW'), + mustTranslateKeys: [], + }); + + expect(result.errors).toContain( + 'Non-Taiwan vocabulary in zh-TW.js at "Open": "啓" should be "啟"', + ); + expect(result.errors).toContain( + 'Non-Taiwan vocabulary in zh-TW.js at "Server": "服務器" should be "伺服器"', + ); + expect(result.errors).toContain( + 'Non-Taiwan vocabulary in zh-TW.js at "Menu": "菜單" should be "選單"', + ); + expect(result.errors).not.toContainEqual( + expect.stringContaining('at "Disable"'), + ); + expect(result.errors).not.toContainEqual( + expect.stringContaining('at "Config"'), + ); + }); + + it('returns no findings for clean Taiwan Traditional translations', () => { + const findings = findForbiddenZhTwPatterns({ + Open: '開啟', + Server: '伺服器', + Menu: '選單', + Disable: '禁用', + Config: '配置', + Link: '連結', + History: '歷史', + }); + expect(findings).toEqual([]); + }); + + it('reports only the most specific pattern per value (no duplicate findings)', () => { + // `历史` (Simplified) overlaps with the single-char pattern `历`. + // We expect exactly one finding for the longer/more specific pattern. + const findings = findForbiddenZhTwPatterns({ + History: '历史', + }); + expect(findings).toEqual([ + { key: 'History', pattern: '历史', preferred: '歷史' }, + ]); + }); + it('detects the unused-keys JSON flag from argv or env', () => { const originalArgv = process.argv; const originalEnv = process.env['QWEN_CHECK_I18N_WRITE_UNUSED_KEYS'];