feat: improve i18n implementation with better translations and accessibility
- Add proper i18n keys for language switcher tooltips and aria-labels - Replace hardcoded Chinese console error messages with i18n keys - Add missing translation keys for new UI elements - Improve accessibility with proper aria-label attributes
This commit is contained in:
@@ -4,18 +4,24 @@ import { Globe } from "lucide-react";
|
|||||||
import { buttonStyles } from "../lib/styles";
|
import { buttonStyles } from "../lib/styles";
|
||||||
|
|
||||||
const LanguageSwitcher: React.FC = () => {
|
const LanguageSwitcher: React.FC = () => {
|
||||||
const { i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
const toggleLanguage = () => {
|
const toggleLanguage = () => {
|
||||||
const newLang = i18n.language === "en" ? "zh" : "en";
|
const newLang = i18n.language === "en" ? "zh" : "en";
|
||||||
i18n.changeLanguage(newLang);
|
i18n.changeLanguage(newLang);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const titleKey =
|
||||||
|
i18n.language === "en"
|
||||||
|
? "header.switchToChinese"
|
||||||
|
: "header.switchToEnglish";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={toggleLanguage}
|
onClick={toggleLanguage}
|
||||||
className={buttonStyles.icon}
|
className={buttonStyles.icon}
|
||||||
title={i18n.language === "en" ? "切换到中文" : "Switch to English"}
|
title={t(titleKey)}
|
||||||
|
aria-label={t(titleKey)}
|
||||||
>
|
>
|
||||||
<Globe size={18} />
|
<Globe size={18} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("加载设置失败:", error);
|
console.error(t("console.loadSettingsFailed"), error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
setConfigPath(path);
|
setConfigPath(path);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取配置路径失败:", error);
|
console.error(t("console.getConfigPathFailed"), error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
setResolvedClaudeDir(claudeDir || "");
|
setResolvedClaudeDir(claudeDir || "");
|
||||||
setResolvedCodexDir(codexDir || "");
|
setResolvedCodexDir(codexDir || "");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取配置目录失败:", error);
|
console.error(t("console.getConfigDirFailed"), error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
const portable = await window.api.isPortable();
|
const portable = await window.api.isPortable();
|
||||||
setIsPortable(portable);
|
setIsPortable(portable);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("检测便携模式失败:", error);
|
console.error(t("console.detectPortableFailed"), error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
setSettings(payload);
|
setSettings(payload);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("保存设置失败:", error);
|
console.error(t("console.saveSettingsFailed"), error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
await updateHandle.downloadAndInstall();
|
await updateHandle.downloadAndInstall();
|
||||||
await relaunchApp();
|
await relaunchApp();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("更新失败:", error);
|
console.error(t("console.updateFailed"), error);
|
||||||
// 更新失败时回退到打开 Releases 页面
|
// 更新失败时回退到打开 Releases 页面
|
||||||
await window.api.checkForUpdates();
|
await window.api.checkForUpdates();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -178,7 +178,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("检查更新失败:", error);
|
console.error(t("console.checkUpdateFailed"), error);
|
||||||
// 在开发模式下,模拟已是最新版本的响应
|
// 在开发模式下,模拟已是最新版本的响应
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
setShowUpToDate(true);
|
setShowUpToDate(true);
|
||||||
@@ -199,7 +199,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
try {
|
try {
|
||||||
await window.api.openAppConfigFolder();
|
await window.api.openAppConfigFolder();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("打开配置文件夹失败:", error);
|
console.error(t("console.openConfigFolderFailed"), error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
setResolvedCodexDir(sanitized);
|
setResolvedCodexDir(sanitized);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("选择配置目录失败:", error);
|
console.error(t("console.selectConfigDirFailed"), error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
const folder = app === "claude" ? ".claude" : ".codex";
|
const folder = app === "claude" ? ".claude" : ".codex";
|
||||||
return await join(home, folder);
|
return await join(home, folder);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取默认配置目录失败:", error);
|
console.error(t("console.getDefaultConfigDirFailed"), error);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -268,8 +268,9 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
const handleOpenReleaseNotes = async () => {
|
const handleOpenReleaseNotes = async () => {
|
||||||
try {
|
try {
|
||||||
const targetVersion = updateInfo?.availableVersion || version;
|
const targetVersion = updateInfo?.availableVersion || version;
|
||||||
|
const unknownLabel = t("common.unknown");
|
||||||
// 如果未知或为空,回退到 releases 首页
|
// 如果未知或为空,回退到 releases 首页
|
||||||
if (!targetVersion || targetVersion === "未知") {
|
if (!targetVersion || targetVersion === unknownLabel) {
|
||||||
await window.api.openExternal(
|
await window.api.openExternal(
|
||||||
"https://github.com/farion1231/cc-switch/releases"
|
"https://github.com/farion1231/cc-switch/releases"
|
||||||
);
|
);
|
||||||
@@ -282,7 +283,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
`https://github.com/farion1231/cc-switch/releases/tag/${tag}`
|
`https://github.com/farion1231/cc-switch/releases/tag/${tag}`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("打开更新日志失败:", error);
|
console.error(t("console.openReleaseNotesFailed"), error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -393,14 +394,14 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
claudeConfigDir: e.target.value,
|
claudeConfigDir: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
placeholder="例如:/home/<你的用户名>/.claude"
|
placeholder={t("settings.browsePlaceholderClaude")}
|
||||||
className="flex-1 px-3 py-2 text-xs font-mono bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/40"
|
className="flex-1 px-3 py-2 text-xs font-mono bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/40"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleBrowseConfigDir("claude")}
|
onClick={() => handleBrowseConfigDir("claude")}
|
||||||
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||||
title="浏览目录"
|
title={t("settings.browseDirectory")}
|
||||||
>
|
>
|
||||||
<FolderSearch size={16} />
|
<FolderSearch size={16} />
|
||||||
</button>
|
</button>
|
||||||
@@ -408,7 +409,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleResetConfigDir("claude")}
|
onClick={() => handleResetConfigDir("claude")}
|
||||||
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||||
title="恢复默认目录(需保存后生效)"
|
title={t("settings.resetDefault")}
|
||||||
>
|
>
|
||||||
<Undo2 size={16} />
|
<Undo2 size={16} />
|
||||||
</button>
|
</button>
|
||||||
@@ -417,7 +418,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
|
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
|
||||||
Codex 配置目录
|
{t("settings.codexConfigDir")}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
@@ -429,14 +430,14 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
codexConfigDir: e.target.value,
|
codexConfigDir: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
placeholder="例如:/home/<你的用户名>/.codex"
|
placeholder={t("settings.browsePlaceholderCodex")}
|
||||||
className="flex-1 px-3 py-2 text-xs font-mono bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/40"
|
className="flex-1 px-3 py-2 text-xs font-mono bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/40"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleBrowseConfigDir("codex")}
|
onClick={() => handleBrowseConfigDir("codex")}
|
||||||
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||||
title="浏览目录"
|
title={t("settings.browseDirectory")}
|
||||||
>
|
>
|
||||||
<FolderSearch size={16} />
|
<FolderSearch size={16} />
|
||||||
</button>
|
</button>
|
||||||
@@ -444,7 +445,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleResetConfigDir("codex")}
|
onClick={() => handleResetConfigDir("codex")}
|
||||||
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||||
title="恢复默认目录(需保存后生效)"
|
title={t("settings.resetDefault")}
|
||||||
>
|
>
|
||||||
<Undo2 size={16} />
|
<Undo2 size={16} />
|
||||||
</button>
|
</button>
|
||||||
@@ -456,7 +457,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
{/* 关于 */}
|
{/* 关于 */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||||||
关于
|
{t("common.about")}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
|
<div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
@@ -466,7 +467,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
CC Switch
|
CC Switch
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-gray-500 dark:text-gray-400">
|
<p className="mt-1 text-gray-500 dark:text-gray-400">
|
||||||
版本 {version}
|
{t("common.version")} {version}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -475,12 +476,14 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
onClick={handleOpenReleaseNotes}
|
onClick={handleOpenReleaseNotes}
|
||||||
className="px-2 py-1 text-xs font-medium text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 rounded-lg hover:bg-blue-500/10 transition-colors"
|
className="px-2 py-1 text-xs font-medium text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 rounded-lg hover:bg-blue-500/10 transition-colors"
|
||||||
title={
|
title={
|
||||||
hasUpdate ? "查看该版本更新日志" : "查看当前版本更新日志"
|
hasUpdate
|
||||||
|
? t("settings.viewReleaseNotes")
|
||||||
|
: t("settings.viewCurrentReleaseNotes")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className="inline-flex items-center gap-1">
|
<span className="inline-flex items-center gap-1">
|
||||||
<ExternalLink size={12} />
|
<ExternalLink size={12} />
|
||||||
更新日志
|
{t("settings.releaseNotes")}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -499,25 +502,27 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
{isDownloading ? (
|
{isDownloading ? (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Download size={12} className="animate-pulse" />
|
<Download size={12} className="animate-pulse" />
|
||||||
更新中...
|
{t("settings.updating")}
|
||||||
</span>
|
</span>
|
||||||
) : isCheckingUpdate ? (
|
) : isCheckingUpdate ? (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<RefreshCw size={12} className="animate-spin" />
|
<RefreshCw size={12} className="animate-spin" />
|
||||||
检查中...
|
{t("settings.checking")}
|
||||||
</span>
|
</span>
|
||||||
) : hasUpdate ? (
|
) : hasUpdate ? (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Download size={12} />
|
<Download size={12} />
|
||||||
更新到 v{updateInfo?.availableVersion}
|
{t("settings.updateTo", {
|
||||||
|
version: updateInfo?.availableVersion ?? "",
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
) : showUpToDate ? (
|
) : showUpToDate ? (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Check size={12} />
|
<Check size={12} />
|
||||||
已是最新
|
{t("settings.upToDate")}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
"检查更新"
|
t("settings.checkForUpdates")
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -532,14 +537,14 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
className="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
取消
|
{t("common.cancel")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={saveSettings}
|
onClick={saveSettings}
|
||||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 rounded-lg transition-colors flex items-center gap-2"
|
className="px-4 py-2 text-sm font-medium text-white bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 rounded-lg transition-colors flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Save size={16} />
|
<Save size={16} />
|
||||||
保存
|
{t("common.save")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
"viewOnGithub": "View on GitHub",
|
"viewOnGithub": "View on GitHub",
|
||||||
"toggleDarkMode": "Switch to Dark Mode",
|
"toggleDarkMode": "Switch to Dark Mode",
|
||||||
"toggleLightMode": "Switch to Light Mode",
|
"toggleLightMode": "Switch to Light Mode",
|
||||||
"addProvider": "Add Provider"
|
"addProvider": "Add Provider",
|
||||||
|
"switchToChinese": "Switch to Chinese",
|
||||||
|
"switchToEnglish": "Switch to English"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"noProviders": "No providers added yet",
|
"noProviders": "No providers added yet",
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
"viewOnGithub": "在 GitHub 上查看",
|
"viewOnGithub": "在 GitHub 上查看",
|
||||||
"toggleDarkMode": "切换到暗色模式",
|
"toggleDarkMode": "切换到暗色模式",
|
||||||
"toggleLightMode": "切换到亮色模式",
|
"toggleLightMode": "切换到亮色模式",
|
||||||
"addProvider": "添加供应商"
|
"addProvider": "添加供应商",
|
||||||
|
"switchToChinese": "切换到中文",
|
||||||
|
"switchToEnglish": "切换到英文"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"noProviders": "还没有添加任何供应商",
|
"noProviders": "还没有添加任何供应商",
|
||||||
|
|||||||
Reference in New Issue
Block a user