From b884595a237931808d4775fb709461141a26f508 Mon Sep 17 00:00:00 2001 From: Leo Date: Fri, 31 Jul 2026 12:23:36 +0800 Subject: [PATCH] fix(i18n): prevent zh-TW tool management fallbacks (#5943) Complete the Traditional Chinese strings for the About-page tool manager and align the install/update hint with the current supported tools. Add locale coverage that requires every tool-management label and preserves interpolation variables across all four translations. Constraint: The About tool manager was extended across three commits without matching zh-TW entries. Rejected: Rely on i18next fallback text | leaves the Traditional Chinese UI partially English and hides future locale drift. Confidence: high Scope-risk: narrow Directive: Update toolManagementLocales.test.ts whenever the About tool manager adds a translatable label. Tested: pnpm typecheck; pnpm format:check; 7 locale tests; 575 Vitest tests; zero missing zh-TW settings keys Not-tested: Manual visual inspection of the About page Related: e3df86587, ea604a182, 014c82d28 --- src/i18n/locales/zh-TW.json | 32 ++++++++- tests/config/toolManagementLocales.test.ts | 83 ++++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 tests/config/toolManagementLocales.test.ts diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json index a1e647c1d..c64686b44 100644 --- a/src/i18n/locales/zh-TW.json +++ b/src/i18n/locales/zh-TW.json @@ -793,9 +793,39 @@ "viewCurrentReleaseNotes": "檢視目前版本更新日誌", "officialWebsite": "官方網站", "github": "GitHub", + "manualInstallCommands": "手動安裝指令", "oneClickInstall": "一鍵安裝", - "oneClickInstallHint": "安裝 Claude Code / Codex / Gemini CLI / OpenCode", + "oneClickInstallHint": "安裝或更新 Claude Code / Codex / Gemini CLI / OpenCode / OpenClaw / Hermes", "localEnvCheck": "本地環境檢查", + "updateAllTools": "全部更新({{count}})", + "currentVersion": "目前版本", + "latestVersion": "最新版本", + "updateAvailableShort": "可更新", + "toolInstall": "安裝", + "toolUpdate": "更新", + "toolReady": "已就緒", + "toolActionDone": "{{count}} 個工具{{action}}完成", + "toolActionPartial": "{{succeeded}} 個{{action}}成功,{{failed}} 個失敗", + "toolActionFailed": "安裝/更新指令執行失敗", + "toolNotRunnable": "已安裝但無法執行(未偵測到版本)", + "toolActionVersionUnchangedTitle": "版本未變更", + "toolActionVersionUnchanged": "仍為 {{version}}(最新版本:{{latest}})。上游更新程式可能回報成功,但實際上並未套用更新。", + "toolActionInstalledNotRunnable": "已安裝,但目前環境無法執行,請檢查", + "installedNotRunnable": "已安裝 · 無法執行", + "toolCheckEnv": "請檢查執行環境", + "toolDiagnose": "診斷安裝衝突", + "toolDiagnosing": "診斷中…", + "toolConflictTitle": "偵測到多處安裝", + "toolConflictHint": "命令列實際使用標示為「預設」的項目;更新可能已寫入其他位置。", + "toolConflictDefault": "預設", + "toolConflictNotRunnable": "無法執行", + "toolDiagnoseNoConflict": "未發現安裝衝突", + "toolDiagnoseFailed": "診斷失敗", + "toolUpgradeConfirmTitle": "確認更新位置", + "toolUpgradeConfirmHint": "偵測到多處安裝。本次更新不會更新所有安裝;請以下方各工具顯示的實際更新位置為準。", + "toolUpgradeWillRun": "將執行:", + "toolUpgradeConfirmBtn": "確認更新", + "toolUpgradeUnanchoredHint": "無法判定命令列實際使用的安裝位置;將執行預設更新指令(可能安裝至 PATH 中第一個 npm)。", "envBadge": { "wsl": "WSL", "windows": "Win", diff --git a/tests/config/toolManagementLocales.test.ts b/tests/config/toolManagementLocales.test.ts new file mode 100644 index 000000000..0024b4eb3 --- /dev/null +++ b/tests/config/toolManagementLocales.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "vitest"; +import en from "@/i18n/locales/en.json"; +import ja from "@/i18n/locales/ja.json"; +import zhTW from "@/i18n/locales/zh-TW.json"; +import zh from "@/i18n/locales/zh.json"; + +const requiredKeys = [ + "manualInstallCommands", + "updateAllTools", + "currentVersion", + "latestVersion", + "updateAvailableShort", + "toolInstall", + "toolUpdate", + "toolReady", + "toolActionDone", + "toolActionPartial", + "toolActionFailed", + "toolNotRunnable", + "toolActionVersionUnchangedTitle", + "toolActionVersionUnchanged", + "toolActionInstalledNotRunnable", + "installedNotRunnable", + "toolCheckEnv", + "toolDiagnose", + "toolDiagnosing", + "toolConflictTitle", + "toolConflictHint", + "toolConflictDefault", + "toolConflictNotRunnable", + "toolDiagnoseNoConflict", + "toolDiagnoseFailed", + "toolUpgradeConfirmTitle", + "toolUpgradeConfirmHint", + "toolUpgradeWillRun", + "toolUpgradeConfirmBtn", + "toolUpgradeUnanchoredHint", +] as const; + +type SettingsTranslations = Record; + +const locales = [ + ["en", en.settings], + ["ja", ja.settings], + ["zh", zh.settings], + ["zh-TW", zhTW.settings], +] as const; + +function interpolationVariables(value: string): string[] { + return Array.from( + value.matchAll(/\{\{([^}]+)\}\}/g), + ([, name]) => name, + ).sort(); +} + +describe("About tool management locale coverage", () => { + it.each(locales)( + "defines every tool management key in %s", + (_locale, settings) => { + const missing = requiredKeys.filter((key) => { + const value = (settings as SettingsTranslations)[key]; + return typeof value !== "string" || value.trim().length === 0; + }); + + expect(missing).toEqual([]); + }, + ); + + it.each(locales.slice(1))( + "preserves interpolation variables in %s", + (_locale, settings) => { + for (const key of requiredKeys) { + const expected = en.settings[key]; + const actual = (settings as SettingsTranslations)[key]; + + expect(typeof actual).toBe("string"); + expect(interpolationVariables(actual as string)).toEqual( + interpolationVariables(expected), + ); + } + }, + ); +});