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
This commit is contained in:
Leo
2026-07-31 12:23:36 +08:00
committed by GitHub
parent 4317bd9981
commit b884595a23
2 changed files with 114 additions and 1 deletions
+31 -1
View File
@@ -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",
@@ -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<string, unknown>;
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),
);
}
},
);
});