From 5f859caeab2e64d7eeac7b3a880c9b4ab8f4ec04 Mon Sep 17 00:00:00 2001 From: thynome <125388433+thynomex@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:53:21 +0100 Subject: [PATCH 1/3] feat: add English/Chinese language toggle for the manager UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manager app (codex-plus-manager) was Chinese-only. This adds a source-text-keyed i18n layer so every user-facing string can render in English, with a topbar toggle to switch languages. - src/i18n.ts: t()/tf() helpers keyed by the original Chinese text; Chinese stays the zero-overhead default. Language is persisted to localStorage and the webview reloads on switch so module-level literals (route tables, preset labels) re-evaluate under the new lang. - src/i18n-en.ts: English dictionary (539 plain + 39 interpolated keys). - App.tsx / ProviderPresetSelector.tsx: every Chinese literal wrapped via an AST codemod (tools/i18n-codemod.mjs) — position-based edits that preserve formatting; comments and object keys left untouched. - tools/i18n-verify.mjs: scans real t()/tf() call sites and fails if the dictionary has any missing or stale key. - Added a Languages toggle button beside the theme toggle in the topbar. Verified: tsc --noEmit clean, vite build succeeds, existing unit tests pass, dictionary matches every call site. --- apps/codex-plus-manager/src/App.tsx | 1134 +++++++++-------- .../src/components/ProviderPresetSelector.tsx | 19 +- apps/codex-plus-manager/src/i18n-en.ts | 648 ++++++++++ apps/codex-plus-manager/src/i18n.ts | 71 ++ tools/i18n-codemod.mjs | 203 +++ tools/i18n-keys.json | 582 +++++++++ tools/i18n-verify.mjs | 112 ++ 7 files changed, 2198 insertions(+), 571 deletions(-) create mode 100644 apps/codex-plus-manager/src/i18n-en.ts create mode 100644 apps/codex-plus-manager/src/i18n.ts create mode 100644 tools/i18n-codemod.mjs create mode 100644 tools/i18n-keys.json create mode 100644 tools/i18n-verify.mjs diff --git a/apps/codex-plus-manager/src/App.tsx b/apps/codex-plus-manager/src/App.tsx index 5cb6b062a..8f98f8d91 100644 --- a/apps/codex-plus-manager/src/App.tsx +++ b/apps/codex-plus-manager/src/App.tsx @@ -30,6 +30,7 @@ import { ExternalLink, Hammer, KeyRound, + Languages, LayoutDashboard, MessageCircle, FileCode2, @@ -66,6 +67,7 @@ import { serializeModelWindowRows, type ModelWindowRow, } from "./model-windows"; +import { getLanguage, t, tf, toggleLanguage } from "@/i18n"; type Status = "ok" | "failed" | "not_implemented" | "not_checked" | string; @@ -544,23 +546,23 @@ type ScriptMarketResult = CommandResult<{ function providerSyncProgressMessage(result: CommandResult): string { const changed = result.changedSessionFiles ?? 0; const rows = result.sqliteRowsUpdated ?? 0; - const target = result.targetProvider || "当前 provider"; + const target = result.targetProvider || t("当前 provider"); const skipped = result.skippedLockedRolloutFiles?.length ?? 0; - const skippedText = skipped ? `,跳过 ${skipped} 个占用文件` : ""; - return `已同步到 ${target}:修复 ${changed} 个会话文件,更新 ${rows} 行索引${skippedText}。`; + const skippedText = skipped ? tf(",跳过 {0} 个占用文件", [skipped]) : ""; + return tf("已同步到 {0}:修复 {1} 个会话文件,更新 {2} 行索引{3}。", [target, changed, rows, skippedText]); } const providerSyncSourceLabels: Record = { - config: "配置", - rollout: "会话", - sqlite: "索引", - manual: "手动", + config: t("配置"), + rollout: t("会话"), + sqlite: t("索引"), + manual: t("手动"), }; function providerSyncTargetLabel(target: ProviderSyncTargetOption): string { const labels = target.sources.map((source) => providerSyncSourceLabels[source]).filter(Boolean); - const current = target.isCurrentProvider ? ["当前"] : []; - return [...labels, ...current].join(" / ") || "发现"; + const current = target.isCurrentProvider ? [t("当前")] : []; + return [...labels, ...current].join(" / ") || t("发现"); } function syncMarketInstalledState(current: ScriptMarketResult | null, userScripts: UserScriptInventory): ScriptMarketResult | null { @@ -596,17 +598,17 @@ type Route = "overview" | "relay" | "sessions" | "context" | "enhance" | "zedRem type Theme = "dark" | "light"; const routes: Array<{ id: Route; label: string; icon: LucideIcon; badge?: string }> = [ - { id: "overview", label: "概览", icon: LayoutDashboard }, - { id: "relay", label: "供应商配置", icon: KeyRound }, - { id: "sessions", label: "会话管理", icon: MessageCircle }, - { id: "context", label: "工具与插件", icon: Network }, - { id: "enhance", label: "Codex增强", icon: Hammer }, - { id: "zedRemote", label: "Zed 远程项目", icon: ExternalLink }, - { id: "userScripts", label: "脚本市场", icon: FileCode2 }, - { id: "recommendations", label: "推荐内容", icon: ExternalLink }, - { id: "maintenance", label: "安装维护", icon: Wrench }, - { id: "about", label: "关于", icon: Info }, - { id: "settings", label: "设置", icon: Settings }, + { id: "overview", label: t("概览"), icon: LayoutDashboard }, + { id: "relay", label: t("供应商配置"), icon: KeyRound }, + { id: "sessions", label: t("会话管理"), icon: MessageCircle }, + { id: "context", label: t("工具与插件"), icon: Network }, + { id: "enhance", label: t("Codex增强"), icon: Hammer }, + { id: "zedRemote", label: t("Zed 远程项目"), icon: ExternalLink }, + { id: "userScripts", label: t("脚本市场"), icon: FileCode2 }, + { id: "recommendations", label: t("推荐内容"), icon: ExternalLink }, + { id: "maintenance", label: t("安装维护"), icon: Wrench }, + { id: "about", label: t("关于"), icon: Info }, + { id: "settings", label: t("设置"), icon: Settings }, ]; const defaultSettings: BackendSettings = { @@ -650,7 +652,7 @@ const defaultSettings: BackendSettings = { relayProfiles: [ { id: "default", - name: "默认中转", + name: t("默认中转"), model: "", baseUrl: "", upstreamBaseUrl: "", @@ -721,13 +723,13 @@ export function App() { const [providerSyncProgress, setProviderSyncProgress] = useState({ active: false, percent: 0, - message: "尚未运行历史会话修复。", + message: t("尚未运行历史会话修复。"), result: null, }); const [pluginMarketplaceProgress, setPluginMarketplaceProgress] = useState({ active: false, percent: 0, - message: "尚未运行插件市场修复。", + message: t("尚未运行插件市场修复。"), }); const [pluginMarketplacePrompt, setPluginMarketplacePrompt] = useState(null); const [providerSyncTargets, setProviderSyncTargets] = useState(null); @@ -745,7 +747,7 @@ export function App() { try { return await task(); } catch (error) { - showNotice("调用失败", stringifyError(error), "failed"); + showNotice(t("调用失败"), stringifyError(error), "failed"); return null; } }; @@ -757,11 +759,11 @@ export function App() { const prev = prevLaunchStatusRef.current; const current = result.latest_launch?.status; if (prev && prev === "running" && current && (current === "stopped" || current === "failed" || current === "crashed")) { - showNotice("Codex 意外停止", `进程状态:${current}。是否要重新启动?`, "failed"); + showNotice(t("Codex 意外停止"), tf("进程状态:{0}。是否要重新启动?", [current]), "failed"); } prevLaunchStatusRef.current = current ?? null; setOverview(result); - if (!silent) showResultNotice("概览已检查", result, { silentSuccess: true }); + if (!silent) showResultNotice(t("概览已检查"), result, { silentSuccess: true }); } }; @@ -775,7 +777,7 @@ export function App() { ...current, appPath: current.appPath || result.settings.codexAppPath || "", })); - if (!silent) showResultNotice("设置已加载", result, { silentSuccess: true }); + if (!silent) showResultNotice(t("设置已加载"), result, { silentSuccess: true }); return normalized; } return null; @@ -786,7 +788,7 @@ export function App() { if (result) { setScriptMarket(result); setSettings((current) => (current ? { ...current, user_scripts: result.user_scripts } : current)); - if (!silent || !isSuccessStatus(result.status)) showResultNotice("脚本市场", result, { silentSuccess: true }); + if (!silent || !isSuccessStatus(result.status)) showResultNotice(t("脚本市场"), result, { silentSuccess: true }); } }; @@ -795,7 +797,7 @@ export function App() { if (result) { setScriptMarket(result); setSettings((current) => (current ? { ...current, user_scripts: result.user_scripts } : current)); - showResultNotice("脚本市场", result); + showResultNotice(t("脚本市场"), result); } }; @@ -804,19 +806,19 @@ export function App() { if (result) { setSettings(result); setScriptMarket((current) => syncMarketInstalledState(current, result.user_scripts)); - showResultNotice("本地脚本", result); + showResultNotice(t("本地脚本"), result); } }; const deleteUserScript = async (key: string) => { const script = settings?.user_scripts?.scripts?.find((item) => item.key === key); const name = script?.name || key; - if (!window.confirm(`删除脚本“${name}”?此操作会移除本地脚本文件。`)) return; + if (!window.confirm(tf("删除脚本“{0}”?此操作会移除本地脚本文件。", [name]))) return; const result = await run(() => call("delete_user_script", { key })); if (result) { setSettings(result); setScriptMarket((current) => syncMarketInstalledState(current, result.user_scripts)); - showResultNotice("本地脚本", result); + showResultNotice(t("本地脚本"), result); } }; @@ -824,7 +826,7 @@ export function App() { const result = await run(() => call("relay_status")); if (result) { setRelay(result); - if (!silent) showResultNotice("登录状态", result, { silentSuccess: true }); + if (!silent) showResultNotice(t("登录状态"), result, { silentSuccess: true }); } }; @@ -832,7 +834,7 @@ export function App() { const result = await run(() => call("read_relay_files")); if (result) { setRelayFiles(result); - if (!silent) showResultNotice("配置文件", result, { silentSuccess: true }); + if (!silent) showResultNotice(t("配置文件"), result, { silentSuccess: true }); } return result; }; @@ -841,7 +843,7 @@ export function App() { const result = await run(() => call("check_env_conflicts")); if (result) { setEnvConflicts(result); - if (!silent || !isSuccessStatus(result.status)) showResultNotice("环境变量检测", result, { silentSuccess: true }); + if (!silent || !isSuccessStatus(result.status)) showResultNotice(t("环境变量检测"), result, { silentSuccess: true }); } return result; }; @@ -849,7 +851,7 @@ export function App() { const removeEnvConflicts = async (names: string[]) => { const uniqueNames = Array.from(new Set(names.map((name) => name.trim()).filter(Boolean))); if (!uniqueNames.length) return; - if (!window.confirm(`删除这些环境变量?\n\n${uniqueNames.join("\n")}\n\n删除前会写入备份。`)) return; + if (!window.confirm(tf("删除这些环境变量?\n\n{0}\n\n删除前会写入备份。", [uniqueNames.join("\n")]))) return; const result = await run(() => call("remove_env_conflicts", { request: { names: uniqueNames } })); if (result) { setEnvConflicts({ @@ -857,7 +859,7 @@ export function App() { message: result.message, conflicts: result.remaining, }); - showNotice("环境变量清理", result.message, result.status); + showNotice(t("环境变量清理"), result.message, result.status); } }; @@ -865,7 +867,7 @@ export function App() { const result = await run(() => call("load_ccs_providers")); if (result) { setCcsProviders(result); - if (!silent || !isSuccessStatus(result.status)) showResultNotice("cc-switch 导入", result, { silentSuccess: true }); + if (!silent || !isSuccessStatus(result.status)) showResultNotice(t("cc-switch 导入"), result, { silentSuccess: true }); } return result; }; @@ -875,7 +877,7 @@ export function App() { if (result) { setSettings(result); setSettingsForm(normalizeSettings(result.settings)); - showResultNotice("cc-switch 导入", result); + showResultNotice(t("cc-switch 导入"), result); await refreshCcsProviders(true); } }; @@ -884,7 +886,7 @@ export function App() { const result = await run(() => call("load_pending_provider_import")); if (result) { setPendingProviderImport(result.pending); - if (!silent && !isSuccessStatus(result.status)) showResultNotice("Codex++ 导入", result, { silentSuccess: true }); + if (!silent && !isSuccessStatus(result.status)) showResultNotice(t("Codex++ 导入"), result, { silentSuccess: true }); } return result; }; @@ -895,7 +897,7 @@ export function App() { setPendingProviderImport(null); setSettings(result); setSettingsForm(normalizeSettings(result.settings)); - showResultNotice("Codex++ 导入", result); + showResultNotice(t("Codex++ 导入"), result); await refreshCcsProviders(true); } }; @@ -904,7 +906,7 @@ export function App() { const result = await run(() => call("dismiss_pending_provider_import")); if (result) { setPendingProviderImport(null); - showResultNotice("Codex++ 导入", result, { silentSuccess: true }); + showResultNotice(t("Codex++ 导入"), result, { silentSuccess: true }); } }; @@ -912,7 +914,7 @@ export function App() { const result = await run(() => call("list_local_sessions")); if (result) { setLocalSessions(result); - if (!silent || !isSuccessStatus(result.status)) showResultNotice("会话管理", result, { silentSuccess: true }); + if (!silent || !isSuccessStatus(result.status)) showResultNotice(t("会话管理"), result, { silentSuccess: true }); } return result; }; @@ -921,7 +923,7 @@ export function App() { const result = await run(() => call("list_zed_remote_projects")); if (result) { setZedRemoteProjects(result); - if (!silent || !isSuccessStatus(result.status)) showResultNotice("Zed 远程项目", result, { silentSuccess: true }); + if (!silent || !isSuccessStatus(result.status)) showResultNotice(t("Zed 远程项目"), result, { silentSuccess: true }); } return result; }; @@ -942,7 +944,7 @@ export function App() { }), ); if (result) { - showResultNotice("Zed 远程打开", result); + showResultNotice(t("Zed 远程打开"), result); await refreshZedRemoteProjects(true); } }; @@ -951,7 +953,7 @@ export function App() { const result = await run(() => call("forget_zed_remote_project", { id: project.id })); if (result) { setZedRemoteProjects(result); - showResultNotice("Zed 远程项目", result); + showResultNotice(t("Zed 远程项目"), result); } }; @@ -965,19 +967,19 @@ export function App() { setConfirmDialog({ title, message, - confirmText: "确认删除", - cancelText: "取消", + confirmText: t("确认删除"), + cancelText: t("取消"), resolve, }); }); const deleteLocalSession = async (session: LocalSession) => { const title = session.title || session.id; - const confirmed = await confirmSessionDelete("删除会话", `删除会话“${title}”?此操作会删除本地数据库记录和 rollout 文件,并创建备份。`); + const confirmed = await confirmSessionDelete(t("删除会话"), tf("删除会话“{0}”?此操作会删除本地数据库记录和 rollout 文件,并创建备份。", [title])); if (!confirmed) return; const result = await run(() => requestDeleteLocalSession(session)); if (result) { - showResultNotice("会话删除", result); + showResultNotice(t("会话删除"), result); await refreshLocalSessions(true); } }; @@ -985,17 +987,17 @@ export function App() { const deleteLocalSessions = async (sessions: LocalSession[]) => { const uniqueSessions = Array.from(new Map(sessions.map((session) => [session.id, session])).values()); if (!uniqueSessions.length) { - showNotice("批量删除会话", "请先选择要删除的会话。", "failed"); + showNotice(t("批量删除会话"), t("请先选择要删除的会话。"), "failed"); return; } const preview = uniqueSessions .slice(0, 6) .map((session) => `- ${truncateSessionDeletePreview(session.title || session.id)}`) .join("\n"); - const extraCount = uniqueSessions.length > 6 ? `\n...以及另外 ${uniqueSessions.length - 6} 个会话` : ""; + const extraCount = uniqueSessions.length > 6 ? tf("\n...以及另外 {0} 个会话", [uniqueSessions.length - 6]) : ""; const confirmed = await confirmSessionDelete( - "批量删除会话", - `删除选中的 ${uniqueSessions.length} 个会话?此操作会删除本地数据库记录和 rollout 文件,并为每个会话创建备份。\n\n${preview}${extraCount}`, + t("批量删除会话"), + tf("删除选中的 {0} 个会话?此操作会删除本地数据库记录和 rollout 文件,并为每个会话创建备份。\n\n{1}{2}", [uniqueSessions.length, preview, extraCount]), ); if (!confirmed) return; @@ -1012,12 +1014,12 @@ export function App() { if (failed.length) { showNotice( - "批量删除会话", - `已删除 ${succeeded} 个,失败 ${failed.length} 个:${failed.slice(0, 3).map(truncateSessionDeletePreview).join("、")}`, + t("批量删除会话"), + tf("已删除 {0} 个,失败 {1} 个:{2}", [succeeded, failed.length, failed.slice(0, 3).map(truncateSessionDeletePreview).join(t("、"))]), succeeded ? "ok" : "failed", ); } else { - showNotice("批量删除会话", `已删除 ${succeeded} 个会话。`, "ok"); + showNotice(t("批量删除会话"), tf("已删除 {0} 个会话。", [succeeded]), "ok"); } await refreshLocalSessions(true); }; @@ -1026,7 +1028,7 @@ export function App() { const result = await run(() => call("read_live_context_entries")); if (result) { setLiveContextEntries(result.entries); - if (!silent || !isSuccessStatus(result.status)) showResultNotice("工具与插件", result, { silentSuccess: true }); + if (!silent || !isSuccessStatus(result.status)) showResultNotice(t("工具与插件"), result, { silentSuccess: true }); } return result; }; @@ -1035,7 +1037,7 @@ export function App() { const result = await run(() => call("sync_live_context_entries", { request: { settings: next } })); if (result) { setLiveContextEntries(result.entries); - if (!silent || !isSuccessStatus(result.status)) showResultNotice("工具与插件", result, { silentSuccess: true }); + if (!silent || !isSuccessStatus(result.status)) showResultNotice(t("工具与插件"), result, { silentSuccess: true }); } return result; }; @@ -1044,7 +1046,7 @@ export function App() { const result = await run(() => call("read_latest_logs", { request: { lines: 240 } })); if (result) { setLogs(result); - if (!silent) showResultNotice("日志已刷新", result, { silentSuccess: true }); + if (!silent) showResultNotice(t("日志已刷新"), result, { silentSuccess: true }); } }; @@ -1052,7 +1054,7 @@ export function App() { const result = await run(() => call("copy_diagnostics")); if (result) { setDiagnostics(result); - if (!silent) showResultNotice("诊断已生成", result, { silentSuccess: true }); + if (!silent) showResultNotice(t("诊断已生成"), result, { silentSuccess: true }); } }; @@ -1060,7 +1062,7 @@ export function App() { const result = await run(() => call("load_watcher_state")); if (result) { setWatcher(result); - if (!silent) showResultNotice("Watcher 状态", result, { silentSuccess: true }); + if (!silent) showResultNotice(t("Watcher 状态"), result, { silentSuccess: true }); } }; @@ -1108,7 +1110,7 @@ export function App() { const launch = async () => { const result = await launchCommand("launch_codex_plus"); if (result) { - showNotice("启动任务", result.message, result.status); + showNotice(t("启动任务"), result.message, result.status); await refreshOverview(true); } }; @@ -1116,7 +1118,7 @@ export function App() { const restart = async () => { const result = await launchCommand("restart_codex_plus"); if (result) { - showNotice("重启 Codex++", result.message, result.status); + showNotice(t("重启 Codex++"), result.message, result.status); await refreshOverview(true); } }; @@ -1139,26 +1141,26 @@ export function App() { if (result) { setSettings(result); setSettingsForm(normalizeSettings(result.settings)); - showNotice("后端修复", result.message, result.status); + showNotice(t("后端修复"), result.message, result.status); } }; const repairPluginMarketplace = async () => { if (pluginMarketplaceProgress.active) return; setPluginMarketplacePrompt(null); - setPluginMarketplaceProgress({ active: true, percent: 8, message: "正在检查本地插件市场…" }); + setPluginMarketplaceProgress({ active: true, percent: 8, message: t("正在检查本地插件市场…") }); const progressTimer = window.setInterval(() => { setPluginMarketplaceProgress((current) => { if (!current.active) return current; const nextPercent = Math.min(92, current.percent + 9); const message = nextPercent < 28 - ? "正在连接 openai/plugins…" + ? t("正在连接 openai/plugins…") : nextPercent < 62 - ? "正在下载插件市场快照…" + ? t("正在下载插件市场快照…") : nextPercent < 84 - ? "正在解压并校验插件文件…" - : "正在写入 Codex 配置…"; + ? t("正在解压并校验插件文件…") + : t("正在写入 Codex 配置…"); return { ...current, percent: nextPercent, message }; }); }, 500); @@ -1170,12 +1172,12 @@ export function App() { percent: 100, message: result.message, }); - showNotice("插件市场修复", result.message, result.status); + showNotice(t("插件市场修复"), result.message, result.status); } else { setPluginMarketplaceProgress({ active: false, percent: 100, - message: "插件市场修复失败,请查看错误提示后重试。", + message: t("插件市场修复失败,请查看错误提示后重试。"), }); } } finally { @@ -1192,7 +1194,7 @@ export function App() { const installEntrypoints = async () => { const result = await run(() => call("install_entrypoints")); if (result) { - showNotice("入口安装", result.message, result.status); + showNotice(t("入口安装"), result.message, result.status); await refreshOverview(true); } }; @@ -1204,7 +1206,7 @@ export function App() { }), ); if (result) { - showNotice("入口卸载", result.message, result.status); + showNotice(t("入口卸载"), result.message, result.status); await refreshOverview(true); } }; @@ -1212,7 +1214,7 @@ export function App() { const repairShortcuts = async () => { const result = await run(() => call("repair_shortcuts")); if (result) { - showNotice("快捷方式修复", result.message, result.status); + showNotice(t("快捷方式修复"), result.message, result.status); await refreshOverview(true); } }; @@ -1221,7 +1223,7 @@ export function App() { const result = await run(() => call(command)); if (result) { setWatcher(result); - showNotice("Watcher 操作", result.message, result.status); + showNotice(t("Watcher 操作"), result.message, result.status); } }; @@ -1230,7 +1232,7 @@ export function App() { if (result) { setUpdate(result); if (!silent || result.updateAvailable) { - showNotice("GitHub Release 检查", result.message, result.status); + showNotice(t("GitHub Release 检查"), result.message, result.status); } } }; @@ -1249,7 +1251,7 @@ export function App() { const result = await run(() => call("perform_update", { release })); if (result) { setUpdate(result); - showNotice("更新安装", result.message, result.status); + showNotice(t("更新安装"), result.message, result.status); } }; @@ -1259,7 +1261,7 @@ export function App() { if (result) { setSettings(result); setSettingsForm(normalizeSettings(result.settings)); - showNotice("设置保存", result.message, result.status); + showNotice(t("设置保存"), result.message, result.status); } }; @@ -1270,7 +1272,7 @@ export function App() { if (result) { setSettings(result); setSettingsForm(normalizeSettings(result.settings)); - if (!silent || !isSuccessStatus(result.status)) showNotice("设置保存", result.message, result.status); + if (!silent || !isSuccessStatus(result.status)) showNotice(t("设置保存"), result.message, result.status); } }; @@ -1279,7 +1281,7 @@ export function App() { if (result) { setSettings(result); setSettingsForm(normalizeSettings(result.settings)); - showNotice("设置重置", result.message, result.status); + showNotice(t("设置重置"), result.message, result.status); } }; @@ -1288,7 +1290,7 @@ export function App() { if (result) { setSettings(result); setSettingsForm(normalizeSettings(result.settings)); - showNotice("图片覆盖层", result.message, result.status); + showNotice(t("图片覆盖层"), result.message, result.status); } }; @@ -1296,7 +1298,7 @@ export function App() { const result = await run(() => call("load_ads")); if (result) { setAds(result); - if (!silent) showResultNotice("推荐内容", result, { silentSuccess: true }); + if (!silent) showResultNotice(t("推荐内容"), result, { silentSuccess: true }); } }; @@ -1312,7 +1314,7 @@ export function App() { targets[0]?.id || "openai"; setSelectedProviderSyncTarget((current) => (targets.some((target) => target.id === current) ? current : preferred)); - if (!silent && !isSuccessStatus(result.status)) showNotice("Provider 同步目标", result.message, result.status); + if (!silent && !isSuccessStatus(result.status)) showNotice(t("Provider 同步目标"), result.message, result.status); } return result; }; @@ -1322,7 +1324,7 @@ export function App() { setProviderSyncProgress({ active: true, percent: 12, - message: selectedProviderSyncTarget ? `正在同步到 ${selectedProviderSyncTarget}…` : "正在扫描历史会话与索引…", + message: selectedProviderSyncTarget ? tf("正在同步到 {0}…", [selectedProviderSyncTarget]) : t("正在扫描历史会话与索引…"), result: null, }); const progressTimer = window.setInterval(() => { @@ -1331,7 +1333,7 @@ export function App() { return { ...current, percent: Math.min(88, current.percent + 8), - message: current.percent < 40 ? "正在检查会话 provider 标记…" : "正在写入修复与备份…", + message: current.percent < 40 ? t("正在检查会话 provider 标记…") : t("正在写入修复与备份…"), }; }); }, 350); @@ -1358,12 +1360,12 @@ export function App() { setSettingsForm(next); } await refreshProviderSyncTargets(true); - showNotice("历史会话修复", result.message, result.status); + showNotice(t("历史会话修复"), result.message, result.status); } else { setProviderSyncProgress({ active: false, percent: 100, - message: "历史会话修复失败,请查看错误提示后重试。", + message: t("历史会话修复失败,请查看错误提示后重试。"), result: null, }); } @@ -1378,7 +1380,7 @@ export function App() { setSettings(settingsResult); setSettingsForm(normalizeSettings(settingsResult.settings)); if (!isSuccessStatus(settingsResult.status)) { - showNotice("设置保存", settingsResult.message, settingsResult.status); + showNotice(t("设置保存"), settingsResult.message, settingsResult.status); return false; } } else { @@ -1388,7 +1390,7 @@ export function App() { if (result) { setRelay(result); await refreshRelayFiles(true); - if (!silent || !isSuccessStatus(result.status)) showNotice("官方混入 API Key", result.message, result.status); + if (!silent || !isSuccessStatus(result.status)) showNotice(t("官方混入 API Key"), result.message, result.status); } return !!result && isSuccessStatus(result.status) && result.configured; }; @@ -1400,7 +1402,7 @@ export function App() { if (result) { setSettings(result); setSettingsForm(normalizeSettings(result.settings)); - if (!silent) showNotice("Codex增强模式", result.message, result.status); + if (!silent) showNotice(t("Codex增强模式"), result.message, result.status); } return result; }; @@ -1411,7 +1413,7 @@ export function App() { setSettings(settingsResult); setSettingsForm(normalizeSettings(settingsResult.settings)); if (!isSuccessStatus(settingsResult.status)) { - showNotice("设置保存", settingsResult.message, settingsResult.status); + showNotice(t("设置保存"), settingsResult.message, settingsResult.status); return false; } } else { @@ -1421,7 +1423,7 @@ export function App() { if (result) { setRelay(result); await refreshRelayFiles(true); - if (!silent || !isSuccessStatus(result.status)) showNotice("纯 API 模式", result.message, result.status); + if (!silent || !isSuccessStatus(result.status)) showNotice(t("纯 API 模式"), result.message, result.status); } return !!result && isSuccessStatus(result.status) && result.configured; }; @@ -1431,7 +1433,7 @@ export function App() { if (result) { setRelay(result); await refreshRelayFiles(true); - if (!silent || !isSuccessStatus(result.status)) showNotice("官方登录模式", result.message, result.status); + if (!silent || !isSuccessStatus(result.status)) showNotice(t("官方登录模式"), result.message, result.status); } return !!result && isSuccessStatus(result.status) && !result.configured; }; @@ -1461,7 +1463,7 @@ export function App() { normalized = normalizeSettings(saveResult.settings); } setSettingsForm(normalized); - if (!isSuccessStatus(result.status)) showResultNotice("工具与插件", result); + if (!isSuccessStatus(result.status)) showResultNotice(t("工具与插件"), result); return normalized; }; @@ -1479,7 +1481,7 @@ export function App() { normalized = normalizeSettings(saveResult.settings); } setSettingsForm(normalized); - if (!isSuccessStatus(result.status)) showResultNotice("工具与插件", result); + if (!isSuccessStatus(result.status)) showResultNotice(t("工具与插件"), result); return normalized; }; @@ -1489,18 +1491,18 @@ export function App() { request: { configContents }, }), ); - if (result) showResultNotice("通用配置文件", result); + if (result) showResultNotice(t("通用配置文件"), result); return result && isSuccessStatus(result.status) ? result : null; }; const testRelayProfile = async (profile: RelayProfile) => { const result = await run(() => call("test_relay_profile", { profile })); - if (result) showNotice("供应商测试", result.message, result.status); + if (result) showNotice(t("供应商测试"), result.message, result.status); }; const fetchRelayProfileModels = async (profile: RelayProfile) => { const result = await run(() => call("fetch_relay_profile_models", { profile })); - if (result) showNotice("模型列表", result.message, result.status); + if (result) showNotice(t("模型列表"), result.message, result.status); return result && isSuccessStatus(result.status) ? result.models : null; }; @@ -1508,24 +1510,24 @@ export function App() { const switched = await clearRelayInjection(true); if (!switched) return; const result = await saveLaunchMode("relay", true); - if (result) showNotice("官方登录模式", "已切回官方登录;Codex增强已设为兼容增强。", result.status); + if (result) showNotice(t("官方登录模式"), t("已切回官方登录;Codex增强已设为兼容增强。"), result.status); }; const switchPureApiMode = async () => { const switched = await applyPureApiInjection(true); if (!switched) return; const result = await saveLaunchMode("patch", true); - if (result) showNotice("纯 API 模式", "已切换到纯 API;Codex增强已设为完整增强。", result.status); + if (result) showNotice(t("纯 API 模式"), t("已切换到纯 API;Codex增强已设为完整增强。"), result.status); }; const switchRelayProfile = async (next: BackendSettings, previousActiveRelayId = settingsForm.activeRelayId) => { if (relaySwitching) { - showNotice("供应商切换中", "上一次切换还没有完成,请稍后再试。", "failed"); + showNotice(t("供应商切换中"), t("上一次切换还没有完成,请稍后再试。"), "failed"); return; } let switchSettings = normalizeSettings(next); if (!switchSettings.relayProfilesEnabled) { - showNotice("供应商配置已关闭", "当前不会写入 Codex config.toml / auth.json。打开供应商配置总开关后再切换。", "failed"); + showNotice(t("供应商配置已关闭"), t("当前不会写入 Codex config.toml / auth.json。打开供应商配置总开关后再切换。"), "failed"); return; } const targetBeforeSnapshot = activeRelayProfile(switchSettings); @@ -1543,7 +1545,7 @@ export function App() { targetRelayName: selectedBeforeSave.name, error: validationError, }); - showNotice("供应商配置可能不正确", validationError, "failed"); + showNotice(t("供应商配置可能不正确"), validationError, "failed"); return; } switchSettings = await snapshotActiveRelayFilesBeforeSwitch(switchSettings, previousActiveRelayId); @@ -1591,7 +1593,7 @@ export function App() { message: result.message, activeRelayId: selectedSettings.activeRelayId, }); - showNotice("供应商切换", result.message, result.status); + showNotice(t("供应商切换"), result.message, result.status); return; } const currentSelected = activeRelayProfile(selectedSettings); @@ -1600,7 +1602,7 @@ export function App() { launchMode: selectedSettings.launchMode, status: result.status, }); - showNotice("供应商切换", relayProfileModeSwitchedText(currentSelected), result.status); + showNotice(t("供应商切换"), relayProfileModeSwitchedText(currentSelected), result.status); } finally { setRelaySwitching(false); } @@ -1620,7 +1622,7 @@ export function App() { if (!result) return next; const normalized = normalizeSettings(result.settings); if (!isSuccessStatus(result.status)) { - showNotice("供应商切换", result.message, result.status); + showNotice(t("供应商切换"), result.message, result.status); return next; } return normalized; @@ -1630,14 +1632,14 @@ export function App() { try { await navigator.clipboard.writeText(text); } catch (error) { - showNotice("复制失败", stringifyError(error), "failed"); + showNotice(t("复制失败"), stringifyError(error), "failed"); } }; const openExternalUrl = async (url: string) => { const result = await run(() => call>>("open_external_url", { url })); if (result) { - showResultNotice("打开链接", result, { silentSuccess: true }); + showResultNotice(t("打开链接"), result, { silentSuccess: true }); } }; @@ -1750,25 +1752,25 @@ export function App() { try { selected = await open( mode === "folder" - ? { directory: true, multiple: false, title: "选择 Codex 应用目录" } + ? { directory: true, multiple: false, title: t("选择 Codex 应用目录") } : { directory: false, multiple: false, - title: "选择 Codex.exe 或 Codex.app", - filters: [{ name: "Codex 应用", extensions: ["exe", "app"] }], + title: t("选择 Codex.exe 或 Codex.app"), + filters: [{ name: t("Codex 应用"), extensions: ["exe", "app"] }], }, ); } catch (error) { // Surface plugin failures (e.g. missing capability permission) so the // buttons no longer appear unresponsive — see #345. const message = error instanceof Error ? error.message : String(error); - showNotice("Codex 应用路径", `打开选择器失败:${message}`, "failed"); + showNotice(t("Codex 应用路径"), tf("打开选择器失败:{0}", [message]), "failed"); return; } if (typeof selected === "string" && selected.trim()) { const result = await saveCodexAppPath(selected.trim()); if (result) { - showNotice("Codex 应用路径", "应用路径已保存,之后启动会自动复用。", result.status); + showNotice(t("Codex 应用路径"), t("应用路径已保存,之后启动会自动复用。"), result.status); } } }, @@ -1779,7 +1781,7 @@ export function App() { setSettings(result); setSettingsForm(normalizeSettings(result.settings)); setLaunchForm((current) => ({ ...current, appPath: "" })); - showNotice("Codex 应用路径", "已清除保存路径,后续启动会回到自动探测。", result.status); + showNotice(t("Codex 应用路径"), t("已清除保存路径,后续启动会回到自动探测。"), result.status); await refreshOverview(true); } }, @@ -1789,12 +1791,12 @@ export function App() { selected = await open({ directory: false, multiple: false, - title: "选择覆盖图片", - filters: [{ name: "图片", extensions: ["png", "jpg", "jpeg", "webp", "gif", "bmp"] }], + title: t("选择覆盖图片"), + filters: [{ name: t("图片"), extensions: ["png", "jpg", "jpeg", "webp", "gif", "bmp"] }], }); } catch (error) { const message = error instanceof Error ? error.message : String(error); - showNotice("图片覆盖层", `打开选择器失败:${message}`, "failed"); + showNotice(t("图片覆盖层"), tf("打开选择器失败:{0}", [message]), "failed"); return; } if (typeof selected === "string" && selected.trim()) { @@ -1808,12 +1810,12 @@ export function App() { saveManualCodexAppPath: async () => { const appPath = launchForm.appPath.trim(); if (!appPath) { - showNotice("Codex 应用路径", "请先填写或选择应用路径。", "failed"); + showNotice(t("Codex 应用路径"), t("请先填写或选择应用路径。"), "failed"); return; } const result = await saveCodexAppPath(appPath); if (result) { - showNotice("Codex 应用路径", "应用路径已保存,之后启动会自动复用。", result.status); + showNotice(t("Codex 应用路径"), t("应用路径已保存,之后启动会自动复用。"), result.status); } }, syncProvidersNow, @@ -1861,14 +1863,14 @@ export function App() { refreshLogs, refreshDiagnostics, showMessage: async (title: string, message: string, status?: Status) => showNotice(title, message, status), - copyLogs: () => copyText(logs?.text ?? "", "日志已复制。"), - copyDiagnostics: () => copyText(diagnostics?.report ?? "", "诊断报告已复制。"), + copyLogs: () => copyText(logs?.text ?? "", t("日志已复制。")), + copyDiagnostics: () => copyText(diagnostics?.report ?? "", t("诊断报告已复制。")), goLogs: () => navigate("about"), checkHealth: async () => { await refreshOverview(true); await refreshRelay(true); await refreshWatcher(true); - showNotice("检查完成", "已刷新 Codex 应用、入口和 Watcher 状态。", "ok"); + showNotice(t("检查完成"), t("已刷新 Codex 应用、入口和 Watcher 状态。"), "ok"); }, installWatcher: () => watcherAction("install_watcher"), uninstallWatcher: () => watcherAction("uninstall_watcher"), @@ -1895,14 +1897,14 @@ export function App() { setRoute("about"); void checkUpdate(false); }} - title={`发现新版本 ${update?.latestVersion ?? ""}`} + title={tf("发现新版本 {0}", [update?.latestVersion ?? ""])} type="button" >