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:
Jason
2025-09-28 20:35:14 +08:00
parent aa05a8475f
commit ea81c3d839
4 changed files with 49 additions and 34 deletions

View File

@@ -4,18 +4,24 @@ import { Globe } from "lucide-react";
import { buttonStyles } from "../lib/styles";
const LanguageSwitcher: React.FC = () => {
const { i18n } = useTranslation();
const { t, i18n } = useTranslation();
const toggleLanguage = () => {
const newLang = i18n.language === "en" ? "zh" : "en";
i18n.changeLanguage(newLang);
};
const titleKey =
i18n.language === "en"
? "header.switchToChinese"
: "header.switchToEnglish";
return (
<button
onClick={toggleLanguage}
className={buttonStyles.icon}
title={i18n.language === "en" ? "切换到中文" : "Switch to English"}
title={t(titleKey)}
aria-label={t(titleKey)}
>
<Globe size={18} />
</button>

View File

@@ -86,7 +86,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
: undefined,
});
} catch (error) {
console.error("加载设置失败:", error);
console.error(t("console.loadSettingsFailed"), error);
}
};
@@ -97,7 +97,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
setConfigPath(path);
}
} catch (error) {
console.error("获取配置路径失败:", error);
console.error(t("console.getConfigPathFailed"), error);
}
};
@@ -110,7 +110,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
setResolvedClaudeDir(claudeDir || "");
setResolvedCodexDir(codexDir || "");
} 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();
setIsPortable(portable);
} catch (error) {
console.error("检测便携模式失败:", error);
console.error(t("console.detectPortableFailed"), error);
}
};
@@ -140,7 +140,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
setSettings(payload);
onClose();
} 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 relaunchApp();
} catch (error) {
console.error("更新失败:", error);
console.error(t("console.updateFailed"), error);
// 更新失败时回退到打开 Releases 页面
await window.api.checkForUpdates();
} finally {
@@ -178,7 +178,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
}, 3000);
}
} catch (error) {
console.error("检查更新失败:", error);
console.error(t("console.checkUpdateFailed"), error);
// 在开发模式下,模拟已是最新版本的响应
if (import.meta.env.DEV) {
setShowUpToDate(true);
@@ -199,7 +199,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
try {
await window.api.openAppConfigFolder();
} catch (error) {
console.error("打开配置文件夹失败:", error);
console.error(t("console.openConfigFolderFailed"), error);
}
};
@@ -230,7 +230,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
setResolvedCodexDir(sanitized);
}
} 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";
return await join(home, folder);
} catch (error) {
console.error("获取默认配置目录失败:", error);
console.error(t("console.getDefaultConfigDirFailed"), error);
return "";
}
};
@@ -268,8 +268,9 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
const handleOpenReleaseNotes = async () => {
try {
const targetVersion = updateInfo?.availableVersion || version;
const unknownLabel = t("common.unknown");
// 如果未知或为空,回退到 releases 首页
if (!targetVersion || targetVersion === "未知") {
if (!targetVersion || targetVersion === unknownLabel) {
await window.api.openExternal(
"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}`
);
} 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,
})
}
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"
/>
<button
type="button"
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"
title="浏览目录"
title={t("settings.browseDirectory")}
>
<FolderSearch size={16} />
</button>
@@ -408,7 +409,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
type="button"
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"
title="恢复默认目录(需保存后生效)"
title={t("settings.resetDefault")}
>
<Undo2 size={16} />
</button>
@@ -417,7 +418,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
<div>
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
Codex
{t("settings.codexConfigDir")}
</label>
<div className="flex gap-2">
<input
@@ -429,14 +430,14 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
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"
/>
<button
type="button"
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"
title="浏览目录"
title={t("settings.browseDirectory")}
>
<FolderSearch size={16} />
</button>
@@ -444,7 +445,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
type="button"
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"
title="恢复默认目录(需保存后生效)"
title={t("settings.resetDefault")}
>
<Undo2 size={16} />
</button>
@@ -456,7 +457,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
{/* 关于 */}
<div>
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
{t("common.about")}
</h3>
<div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
<div className="flex items-start justify-between">
@@ -466,7 +467,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
CC Switch
</p>
<p className="mt-1 text-gray-500 dark:text-gray-400">
{version}
{t("common.version")} {version}
</p>
</div>
</div>
@@ -475,12 +476,14 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
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"
title={
hasUpdate ? "查看该版本更新日志" : "查看当前版本更新日志"
hasUpdate
? t("settings.viewReleaseNotes")
: t("settings.viewCurrentReleaseNotes")
}
>
<span className="inline-flex items-center gap-1">
<ExternalLink size={12} />
{t("settings.releaseNotes")}
</span>
</button>
<button
@@ -499,25 +502,27 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
{isDownloading ? (
<span className="flex items-center gap-1">
<Download size={12} className="animate-pulse" />
...
{t("settings.updating")}
</span>
) : isCheckingUpdate ? (
<span className="flex items-center gap-1">
<RefreshCw size={12} className="animate-spin" />
...
{t("settings.checking")}
</span>
) : hasUpdate ? (
<span className="flex items-center gap-1">
<Download size={12} />
v{updateInfo?.availableVersion}
{t("settings.updateTo", {
version: updateInfo?.availableVersion ?? "",
})}
</span>
) : showUpToDate ? (
<span className="flex items-center gap-1">
<Check size={12} />
{t("settings.upToDate")}
</span>
) : (
"检查更新"
t("settings.checkForUpdates")
)}
</button>
</div>
@@ -532,14 +537,14 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
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"
>
{t("common.cancel")}
</button>
<button
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"
>
<Save size={16} />
{t("common.save")}
</button>
</div>
</div>

View File

@@ -23,7 +23,9 @@
"viewOnGithub": "View on GitHub",
"toggleDarkMode": "Switch to Dark Mode",
"toggleLightMode": "Switch to Light Mode",
"addProvider": "Add Provider"
"addProvider": "Add Provider",
"switchToChinese": "Switch to Chinese",
"switchToEnglish": "Switch to English"
},
"provider": {
"noProviders": "No providers added yet",

View File

@@ -23,7 +23,9 @@
"viewOnGithub": "在 GitHub 上查看",
"toggleDarkMode": "切换到暗色模式",
"toggleLightMode": "切换到亮色模式",
"addProvider": "添加供应商"
"addProvider": "添加供应商",
"switchToChinese": "切换到中文",
"switchToEnglish": "切换到英文"
},
"provider": {
"noProviders": "还没有添加任何供应商",