feat: integrate i18next for internationalization support
- Added i18next and react-i18next dependencies for localization. - Updated various components to utilize translation functions for user-facing text. - Enhanced user experience by providing multilingual support across the application.
This commit is contained in:
76
README_i18n.md
Normal file
76
README_i18n.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# CC Switch 国际化功能说明
|
||||
|
||||
## 已完成的工作
|
||||
|
||||
1. **安装依赖**:添加了 `react-i18next` 和 `i18next` 包
|
||||
2. **配置国际化**:在 `src/i18n/` 目录下创建了配置文件
|
||||
3. **翻译文件**:创建了英文和中文翻译文件
|
||||
4. **组件更新**:替换了主要组件中的硬编码文案
|
||||
5. **语言切换器**:添加了语言切换按钮
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── i18n/
|
||||
│ ├── index.ts # 国际化配置文件
|
||||
│ └── locales/
|
||||
│ ├── en.json # 英文翻译
|
||||
│ └── zh.json # 中文翻译
|
||||
├── components/
|
||||
│ └── LanguageSwitcher.tsx # 语言切换组件
|
||||
└── main.tsx # 导入国际化配置
|
||||
```
|
||||
|
||||
## 默认语言设置
|
||||
|
||||
- **默认语言**:英文 (en)
|
||||
- **回退语言**:英文 (en)
|
||||
|
||||
## 使用方式
|
||||
|
||||
1. 在组件中导入 `useTranslation`:
|
||||
```tsx
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function MyComponent() {
|
||||
const { t } = useTranslation();
|
||||
return <div>{t('common.save')}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
2. 切换语言:
|
||||
```tsx
|
||||
const { i18n } = useTranslation();
|
||||
i18n.changeLanguage('zh'); // 切换到中文
|
||||
```
|
||||
|
||||
## 翻译键结构
|
||||
|
||||
- `common.*` - 通用文案(保存、取消、设置等)
|
||||
- `header.*` - 头部相关文案
|
||||
- `provider.*` - 供应商相关文案
|
||||
- `notifications.*` - 通知消息
|
||||
- `settings.*` - 设置页面文案
|
||||
- `apps.*` - 应用名称
|
||||
- `console.*` - 控制台日志信息
|
||||
|
||||
## 测试功能
|
||||
|
||||
应用已添加了语言切换按钮(地球图标),点击可以在中英文之间切换,验证国际化功能是否正常工作。
|
||||
|
||||
## 已更新的组件
|
||||
|
||||
- ✅ App.tsx - 主应用组件
|
||||
- ✅ ConfirmDialog.tsx - 确认对话框
|
||||
- ✅ AddProviderModal.tsx - 添加供应商弹窗
|
||||
- ✅ EditProviderModal.tsx - 编辑供应商弹窗
|
||||
- ✅ ProviderList.tsx - 供应商列表
|
||||
- ✅ LanguageSwitcher.tsx - 语言切换器
|
||||
- 🔄 SettingsModal.tsx - 设置弹窗(部分完成)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有新的文案都应该添加到翻译文件中,而不是硬编码
|
||||
2. 翻译键名应该有意义且结构化
|
||||
3. 可以通过修改 `src/i18n/index.ts` 中的 `lng` 配置来更改默认语言
|
||||
@@ -37,10 +37,12 @@
|
||||
"@tauri-apps/plugin-process": "^2.0.0",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"i18next": "^25.5.2",
|
||||
"jsonc-parser": "^3.2.1",
|
||||
"lucide-react": "^0.542.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^16.0.0",
|
||||
"tailwindcss": "^4.1.13"
|
||||
}
|
||||
}
|
||||
|
||||
65
pnpm-lock.yaml
generated
65
pnpm-lock.yaml
generated
@@ -41,6 +41,9 @@ importers:
|
||||
codemirror:
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2
|
||||
i18next:
|
||||
specifier: ^25.5.2
|
||||
version: 25.5.2(typescript@5.9.2)
|
||||
jsonc-parser:
|
||||
specifier: ^3.2.1
|
||||
version: 3.3.1
|
||||
@@ -53,6 +56,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.3.1(react@18.3.1)
|
||||
react-i18next:
|
||||
specifier: ^16.0.0
|
||||
version: 16.0.0(i18next@25.5.2(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)
|
||||
tailwindcss:
|
||||
specifier: ^4.1.13
|
||||
version: 4.1.13
|
||||
@@ -159,6 +165,10 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/runtime@7.28.4':
|
||||
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -750,6 +760,17 @@ packages:
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
|
||||
|
||||
i18next@25.5.2:
|
||||
resolution: {integrity: sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==}
|
||||
peerDependencies:
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
jiti@2.5.1:
|
||||
resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==}
|
||||
hasBin: true
|
||||
@@ -890,6 +911,22 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
|
||||
react-i18next@16.0.0:
|
||||
resolution: {integrity: sha512-JQ+dFfLnFSKJQt7W01lJHWRC0SX7eDPobI+MSTJ3/gP39xH2g33AuTE7iddAfXYHamJdAeMGM0VFboPaD3G68Q==}
|
||||
peerDependencies:
|
||||
i18next: '>= 25.5.2'
|
||||
react: '>= 16.8.0'
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
react-refresh@0.17.0:
|
||||
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -973,6 +1010,10 @@ packages:
|
||||
terser:
|
||||
optional: true
|
||||
|
||||
void-elements@3.1.0:
|
||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
w3c-keyname@2.2.8:
|
||||
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||
|
||||
@@ -1079,6 +1120,8 @@ snapshots:
|
||||
'@babel/core': 7.28.0
|
||||
'@babel/helper-plugin-utils': 7.27.1
|
||||
|
||||
'@babel/runtime@7.28.4': {}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
@@ -1591,6 +1634,16 @@ snapshots:
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
dependencies:
|
||||
void-elements: 3.1.0
|
||||
|
||||
i18next@25.5.2(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
optionalDependencies:
|
||||
typescript: 5.9.2
|
||||
|
||||
jiti@2.5.1: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
@@ -1692,6 +1745,16 @@ snapshots:
|
||||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
|
||||
react-i18next@16.0.0(i18next@25.5.2(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
html-parse-stringify: 3.0.1
|
||||
i18next: 25.5.2(typescript@5.9.2)
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
typescript: 5.9.2
|
||||
|
||||
react-refresh@0.17.0: {}
|
||||
|
||||
react@18.3.1:
|
||||
@@ -1767,6 +1830,8 @@ snapshots:
|
||||
fsevents: 2.3.3
|
||||
lightningcss: 1.30.1
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
w3c-keyname@2.2.8: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
67
src/App.tsx
67
src/App.tsx
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Provider } from "./types";
|
||||
import { AppType } from "./lib/tauri-api";
|
||||
import ProviderList from "./components/ProviderList";
|
||||
@@ -8,6 +9,7 @@ import { ConfirmDialog } from "./components/ConfirmDialog";
|
||||
import { AppSwitcher } from "./components/AppSwitcher";
|
||||
import SettingsModal from "./components/SettingsModal";
|
||||
import { UpdateBadge } from "./components/UpdateBadge";
|
||||
import LanguageSwitcher from "./components/LanguageSwitcher";
|
||||
import { Plus, Settings, Moon, Sun } from "lucide-react";
|
||||
import { buttonStyles } from "./lib/styles";
|
||||
import { useDarkMode } from "./hooks/useDarkMode";
|
||||
@@ -17,6 +19,7 @@ import { getCodexBaseUrl } from "./utils/providerConfigUtils";
|
||||
import { useVSCodeAutoSync } from "./hooks/useVSCodeAutoSync";
|
||||
|
||||
function App() {
|
||||
const { t } = useTranslation();
|
||||
const { isDarkMode, toggleDarkMode } = useDarkMode();
|
||||
const { isAutoSyncEnabled } = useVSCodeAutoSync();
|
||||
const [activeApp, setActiveApp] = useState<AppType>("claude");
|
||||
@@ -24,7 +27,7 @@ function App() {
|
||||
const [currentProviderId, setCurrentProviderId] = useState<string>("");
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
const [editingProviderId, setEditingProviderId] = useState<string | null>(
|
||||
null,
|
||||
null
|
||||
);
|
||||
const [notification, setNotification] = useState<{
|
||||
message: string;
|
||||
@@ -44,7 +47,7 @@ function App() {
|
||||
const showNotification = (
|
||||
message: string,
|
||||
type: "success" | "error",
|
||||
duration = 3000,
|
||||
duration = 3000
|
||||
) => {
|
||||
// 清除之前的定时器
|
||||
if (timeoutRef.current) {
|
||||
@@ -88,7 +91,7 @@ function App() {
|
||||
try {
|
||||
unlisten = await window.api.onProviderSwitched(async (data) => {
|
||||
if (import.meta.env.DEV) {
|
||||
console.log("收到供应商切换事件:", data);
|
||||
console.log(t("console.providerSwitchReceived"), data);
|
||||
}
|
||||
|
||||
// 如果当前应用类型匹配,则重新加载数据
|
||||
@@ -102,7 +105,7 @@ function App() {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("设置供应商切换监听器失败:", error);
|
||||
console.error(t("console.setupListenerFailed"), error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -152,16 +155,16 @@ function App() {
|
||||
await loadProviders();
|
||||
setEditingProviderId(null);
|
||||
// 显示编辑成功提示
|
||||
showNotification("供应商配置已保存", "success", 2000);
|
||||
showNotification(t("notifications.providerSaved"), "success", 2000);
|
||||
// 更新托盘菜单
|
||||
await window.api.updateTrayMenu();
|
||||
} catch (error) {
|
||||
console.error("更新供应商失败:", error);
|
||||
console.error(t("console.updateProviderFailed"), error);
|
||||
setEditingProviderId(null);
|
||||
const errorMessage = extractErrorMessage(error);
|
||||
const message = errorMessage
|
||||
? `保存失败:${errorMessage}`
|
||||
: "保存失败,请重试";
|
||||
? t("notifications.saveFailed", { error: errorMessage })
|
||||
: t("notifications.saveFailedGeneric");
|
||||
showNotification(message, "error", errorMessage ? 6000 : 3000);
|
||||
}
|
||||
};
|
||||
@@ -170,13 +173,13 @@ function App() {
|
||||
const provider = providers[id];
|
||||
setConfirmDialog({
|
||||
isOpen: true,
|
||||
title: "删除供应商",
|
||||
message: `确定要删除供应商 "${provider?.name}" 吗?此操作无法撤销。`,
|
||||
title: t("confirm.deleteProvider"),
|
||||
message: t("confirm.deleteProviderMessage", { name: provider?.name }),
|
||||
onConfirm: async () => {
|
||||
await window.api.deleteProvider(id, activeApp);
|
||||
await loadProviders();
|
||||
setConfirmDialog(null);
|
||||
showNotification("供应商删除成功", "success");
|
||||
showNotification(t("notifications.providerDeleted"), "success");
|
||||
// 更新托盘菜单
|
||||
await window.api.updateTrayMenu();
|
||||
},
|
||||
@@ -190,9 +193,9 @@ function App() {
|
||||
if (!status.exists) {
|
||||
if (!silent) {
|
||||
showNotification(
|
||||
"未找到 VS Code 用户设置文件 (settings.json)",
|
||||
t("notifications.vscodeSettingsNotFound"),
|
||||
"error",
|
||||
3000,
|
||||
3000
|
||||
);
|
||||
}
|
||||
return;
|
||||
@@ -208,11 +211,7 @@ function App() {
|
||||
const parsed = getCodexBaseUrl(provider);
|
||||
if (!parsed) {
|
||||
if (!silent) {
|
||||
showNotification(
|
||||
"当前配置缺少 base_url,无法写入 VS Code",
|
||||
"error",
|
||||
4000,
|
||||
);
|
||||
showNotification(t("notifications.missingBaseUrl"), "error", 4000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -226,16 +225,17 @@ function App() {
|
||||
if (updatedSettings !== raw) {
|
||||
await window.api.writeVSCodeSettings(updatedSettings);
|
||||
if (!silent) {
|
||||
showNotification("已同步到 VS Code", "success", 1500);
|
||||
showNotification(t("notifications.syncedToVSCode"), "success", 1500);
|
||||
}
|
||||
}
|
||||
|
||||
// 触发providers重新加载,以更新VS Code按钮状态
|
||||
await loadProviders();
|
||||
} catch (error: any) {
|
||||
console.error("同步到VS Code失败:", error);
|
||||
console.error(t("console.syncToVSCodeFailed"), error);
|
||||
if (!silent) {
|
||||
const errorMessage = error?.message || "同步 VS Code 失败";
|
||||
const errorMessage =
|
||||
error?.message || t("notifications.syncVSCodeFailed");
|
||||
showNotification(errorMessage, "error", 5000);
|
||||
}
|
||||
}
|
||||
@@ -246,11 +246,11 @@ function App() {
|
||||
if (success) {
|
||||
setCurrentProviderId(id);
|
||||
// 显示重启提示
|
||||
const appName = activeApp === "claude" ? "Claude Code" : "Codex";
|
||||
const appName = t(`apps.${activeApp}`);
|
||||
showNotification(
|
||||
`切换成功!请重启 ${appName} 终端以生效`,
|
||||
t("notifications.switchSuccess", { appName }),
|
||||
"success",
|
||||
2000,
|
||||
2000
|
||||
);
|
||||
// 更新托盘菜单
|
||||
await window.api.updateTrayMenu();
|
||||
@@ -260,7 +260,7 @@ function App() {
|
||||
await syncCodexToVSCode(id, true); // silent模式,不显示通知
|
||||
}
|
||||
} else {
|
||||
showNotification("切换失败,请检查配置", "error");
|
||||
showNotification(t("notifications.switchFailed"), "error");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -271,13 +271,13 @@ function App() {
|
||||
|
||||
if (result.success) {
|
||||
await loadProviders();
|
||||
showNotification("已从现有配置创建默认供应商", "success", 3000);
|
||||
showNotification(t("notifications.autoImported"), "success", 3000);
|
||||
// 更新托盘菜单
|
||||
await window.api.updateTrayMenu();
|
||||
}
|
||||
// 如果导入失败(比如没有现有配置),静默处理,不显示错误
|
||||
} catch (error) {
|
||||
console.error("自动导入默认配置失败:", error);
|
||||
console.error(t("console.autoImportFailed"), error);
|
||||
// 静默处理,不影响用户体验
|
||||
}
|
||||
};
|
||||
@@ -293,22 +293,27 @@ function App() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xl font-semibold text-blue-500 dark:text-blue-400 hover:text-blue-600 dark:hover:text-blue-300 transition-colors"
|
||||
title="在 GitHub 上查看"
|
||||
title={t("header.viewOnGithub")}
|
||||
>
|
||||
CC Switch
|
||||
</a>
|
||||
<button
|
||||
onClick={toggleDarkMode}
|
||||
className={buttonStyles.icon}
|
||||
title={isDarkMode ? "切换到亮色模式" : "切换到暗色模式"}
|
||||
title={
|
||||
isDarkMode
|
||||
? t("header.toggleLightMode")
|
||||
: t("header.toggleDarkMode")
|
||||
}
|
||||
>
|
||||
{isDarkMode ? <Sun size={18} /> : <Moon size={18} />}
|
||||
</button>
|
||||
<LanguageSwitcher />
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setIsSettingsOpen(true)}
|
||||
className={buttonStyles.icon}
|
||||
title="设置"
|
||||
title={t("common.settings")}
|
||||
>
|
||||
<Settings size={18} />
|
||||
</button>
|
||||
@@ -324,7 +329,7 @@ function App() {
|
||||
className={`inline-flex items-center gap-2 ${buttonStyles.primary}`}
|
||||
>
|
||||
<Plus size={16} />
|
||||
添加供应商
|
||||
{t("header.addProvider")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Provider } from "../types";
|
||||
import { AppType } from "../lib/tauri-api";
|
||||
import ProviderForm from "./ProviderForm";
|
||||
@@ -14,11 +15,13 @@ const AddProviderModal: React.FC<AddProviderModalProps> = ({
|
||||
onAdd,
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ProviderForm
|
||||
appType={appType}
|
||||
title="添加新供应商"
|
||||
submitText="添加"
|
||||
title={t("provider.addNewProvider")}
|
||||
submitText={t("common.add")}
|
||||
showPresets={true}
|
||||
onSubmit={onAdd}
|
||||
onClose={onClose}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AlertTriangle, X } from "lucide-react";
|
||||
import { isLinux } from "../lib/platform";
|
||||
|
||||
@@ -16,11 +17,13 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
|
||||
isOpen,
|
||||
title,
|
||||
message,
|
||||
confirmText = "确定",
|
||||
cancelText = "取消",
|
||||
confirmText,
|
||||
cancelText,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
@@ -65,13 +68,13 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
|
||||
className="px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-900 hover:bg-white dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors"
|
||||
autoFocus
|
||||
>
|
||||
{cancelText}
|
||||
{cancelText || t("common.cancel")}
|
||||
</button>
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
className="px-4 py-2 text-sm font-medium bg-red-500 text-white hover:bg-red-500/90 rounded-md transition-colors"
|
||||
>
|
||||
{confirmText}
|
||||
{confirmText || t("common.confirm")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Provider } from "../types";
|
||||
import { AppType } from "../lib/tauri-api";
|
||||
import ProviderForm from "./ProviderForm";
|
||||
@@ -16,6 +17,8 @@ const EditProviderModal: React.FC<EditProviderModalProps> = ({
|
||||
onSave,
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = (data: Omit<Provider, "id">) => {
|
||||
onSave({
|
||||
...provider,
|
||||
@@ -26,8 +29,8 @@ const EditProviderModal: React.FC<EditProviderModalProps> = ({
|
||||
return (
|
||||
<ProviderForm
|
||||
appType={appType}
|
||||
title="编辑供应商"
|
||||
submitText="保存"
|
||||
title={t("common.edit")}
|
||||
submitText={t("common.save")}
|
||||
initialData={provider}
|
||||
showPresets={false}
|
||||
onSubmit={handleSubmit}
|
||||
|
||||
25
src/components/LanguageSwitcher.tsx
Normal file
25
src/components/LanguageSwitcher.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Globe } from "lucide-react";
|
||||
import { buttonStyles } from "../lib/styles";
|
||||
|
||||
const LanguageSwitcher: React.FC = () => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const toggleLanguage = () => {
|
||||
const newLang = i18n.language === "en" ? "zh" : "en";
|
||||
i18n.changeLanguage(newLang);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleLanguage}
|
||||
className={buttonStyles.icon}
|
||||
title={i18n.language === "en" ? "切换到中文" : "Switch to English"}
|
||||
>
|
||||
<Globe size={18} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSwitcher;
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Provider } from "../types";
|
||||
import { Play, Edit3, Trash2, CheckCircle2, Users } from "lucide-react";
|
||||
import { buttonStyles, cardStyles, badgeStyles, cn } from "../lib/styles";
|
||||
@@ -35,6 +36,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
appType,
|
||||
onNotify,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
// 提取API地址(兼容不同供应商配置:Claude env / Codex TOML)
|
||||
const getApiUrl = (provider: Provider): string => {
|
||||
try {
|
||||
@@ -49,9 +51,9 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
const match = cfg.config.match(/base_url\s*=\s*(['"])([^'\"]+)\1/);
|
||||
if (match && match[2]) return match[2];
|
||||
}
|
||||
return "未配置官网地址";
|
||||
return t("provider.notConfigured");
|
||||
} catch {
|
||||
return "配置错误";
|
||||
return t("provider.configError");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,7 +61,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
try {
|
||||
await window.api.openExternal(url);
|
||||
} catch (error) {
|
||||
console.error("打开链接失败:", error);
|
||||
console.error(t("console.openLinkFailed"), error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -106,11 +108,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
try {
|
||||
const status = await window.api.getVSCodeSettingsStatus();
|
||||
if (!status.exists) {
|
||||
onNotify?.(
|
||||
"未找到 VS Code 用户设置文件 (settings.json)",
|
||||
"error",
|
||||
3000
|
||||
);
|
||||
onNotify?.(t("notifications.vscodeSettingsNotFound"), "error", 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,7 +119,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
if (!isOfficial) {
|
||||
const parsed = getCodexBaseUrl(provider);
|
||||
if (!parsed) {
|
||||
onNotify?.("当前配置缺少 base_url,无法写入 VS Code", "error", 4000);
|
||||
onNotify?.(t("notifications.missingBaseUrl"), "error", 4000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -131,7 +129,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
|
||||
if (next === raw) {
|
||||
// 幂等:没有变化也提示成功
|
||||
onNotify?.("已应用到 VS Code,重启 Codex 插件以生效", "success", 3000);
|
||||
onNotify?.(t("notifications.appliedToVSCode"), "success", 3000);
|
||||
setVscodeAppliedFor(provider.id);
|
||||
// 用户手动应用时,启用自动同步
|
||||
enableAutoSync();
|
||||
@@ -139,13 +137,14 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
}
|
||||
|
||||
await window.api.writeVSCodeSettings(next);
|
||||
onNotify?.("已应用到 VS Code,重启 Codex 插件以生效", "success", 3000);
|
||||
onNotify?.(t("notifications.appliedToVSCode"), "success", 3000);
|
||||
setVscodeAppliedFor(provider.id);
|
||||
// 用户手动应用时,启用自动同步
|
||||
enableAutoSync();
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
const msg = e && e.message ? e.message : "应用到 VS Code 失败";
|
||||
const msg =
|
||||
e && e.message ? e.message : t("notifications.syncVSCodeFailed");
|
||||
onNotify?.(msg, "error", 5000);
|
||||
}
|
||||
};
|
||||
@@ -154,11 +153,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
try {
|
||||
const status = await window.api.getVSCodeSettingsStatus();
|
||||
if (!status.exists) {
|
||||
onNotify?.(
|
||||
"未找到 VS Code 用户设置文件 (settings.json)",
|
||||
"error",
|
||||
3000
|
||||
);
|
||||
onNotify?.(t("notifications.vscodeSettingsNotFound"), "error", 3000);
|
||||
return;
|
||||
}
|
||||
const raw = await window.api.readVSCodeSettings();
|
||||
@@ -167,20 +162,21 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
isOfficial: true,
|
||||
});
|
||||
if (next === raw) {
|
||||
onNotify?.("已从 VS Code 移除,重启 Codex 插件以生效", "success", 3000);
|
||||
onNotify?.(t("notifications.removedFromVSCode"), "success", 3000);
|
||||
setVscodeAppliedFor(null);
|
||||
// 用户手动移除时,禁用自动同步
|
||||
disableAutoSync();
|
||||
return;
|
||||
}
|
||||
await window.api.writeVSCodeSettings(next);
|
||||
onNotify?.("已从 VS Code 移除,重启 Codex 插件以生效", "success", 3000);
|
||||
onNotify?.(t("notifications.removedFromVSCode"), "success", 3000);
|
||||
setVscodeAppliedFor(null);
|
||||
// 用户手动移除时,禁用自动同步
|
||||
disableAutoSync();
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
const msg = e && e.message ? e.message : "移除失败";
|
||||
const msg =
|
||||
e && e.message ? e.message : t("notifications.syncVSCodeFailed");
|
||||
onNotify?.(msg, "error", 5000);
|
||||
}
|
||||
};
|
||||
@@ -214,10 +210,10 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
<Users size={24} className="text-gray-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
还没有添加任何供应商
|
||||
{t("provider.noProviders")}
|
||||
</h3>
|
||||
<p className="text-gray-500 dark:text-gray-400 text-sm">
|
||||
点击右上角的"添加供应商"按钮开始配置您的第一个API供应商
|
||||
{t("provider.noProvidersDescription")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -247,7 +243,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
)}
|
||||
>
|
||||
<CheckCircle2 size={12} />
|
||||
当前使用
|
||||
{t("provider.currentlyUsing")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -292,13 +288,13 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
)}
|
||||
title={
|
||||
vscodeAppliedFor === provider.id
|
||||
? "从 VS Code 移除我们写入的配置"
|
||||
: "将当前供应商应用到 VS Code"
|
||||
? t("provider.removeFromVSCode")
|
||||
: t("provider.applyToVSCode")
|
||||
}
|
||||
>
|
||||
{vscodeAppliedFor === provider.id
|
||||
? "从 VS Code 移除"
|
||||
: "应用到 VS Code"}
|
||||
? t("provider.removeFromVSCode")
|
||||
: t("provider.applyToVSCode")}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
@@ -312,13 +308,13 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
)}
|
||||
>
|
||||
{!isCurrent && <Play size={14} />}
|
||||
{isCurrent ? "使用中" : "启用"}
|
||||
{isCurrent ? t("provider.inUse") : t("provider.enable")}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => onEdit(provider.id)}
|
||||
className={buttonStyles.icon}
|
||||
title="编辑供应商"
|
||||
title={t("provider.editProvider")}
|
||||
>
|
||||
<Edit3 size={16} />
|
||||
</button>
|
||||
@@ -332,7 +328,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
? "text-gray-400 cursor-not-allowed"
|
||||
: "text-gray-500 hover:text-red-500 hover:bg-red-100 dark:text-gray-400 dark:hover:text-red-400 dark:hover:bg-red-500/10"
|
||||
)}
|
||||
title="删除供应商"
|
||||
title={t("provider.deleteProvider")}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
X,
|
||||
RefreshCw,
|
||||
@@ -24,6 +25,7 @@ interface SettingsModalProps {
|
||||
}
|
||||
|
||||
export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const [settings, setSettings] = useState<Settings>({
|
||||
showInTray: true,
|
||||
minimizeToTrayOnClose: true,
|
||||
@@ -54,9 +56,9 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
const appVersion = await getVersion();
|
||||
setVersion(appVersion);
|
||||
} catch (error) {
|
||||
console.error("获取版本信息失败:", error);
|
||||
console.error(t("console.getVersionFailed"), error);
|
||||
// 失败时不硬编码版本号,显示为未知
|
||||
setVersion("未知");
|
||||
setVersion(t("common.unknown"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -300,7 +302,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
{/* 标题栏 */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
||||
<h2 className="text-lg font-semibold text-blue-500 dark:text-blue-400">
|
||||
设置
|
||||
{t("settings.title")}
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
@@ -315,16 +317,16 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
{/* 窗口行为设置 */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||||
窗口行为
|
||||
{t("settings.windowBehavior")}
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-center justify-between">
|
||||
<div>
|
||||
<span className="text-sm text-gray-900 dark:text-gray-100">
|
||||
关闭时最小化到托盘
|
||||
{t("settings.minimizeToTray")}
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
勾选后点击关闭按钮会隐藏到系统托盘,取消则直接退出应用。
|
||||
{t("settings.minimizeToTrayDescription")}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
@@ -347,18 +349,18 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
{/* 配置文件位置 */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||||
配置文件位置
|
||||
{t("settings.configFileLocation")}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 px-3 py-2 bg-gray-100 dark:bg-gray-800 rounded-lg">
|
||||
<span className="text-xs font-mono text-gray-500 dark:text-gray-400">
|
||||
{configPath || "加载中..."}
|
||||
{configPath || t("common.loading")}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleOpenConfigFolder}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||
title="打开文件夹"
|
||||
title={t("settings.openFolder")}
|
||||
>
|
||||
<FolderOpen
|
||||
size={18}
|
||||
@@ -371,16 +373,15 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
{/* 配置目录覆盖 */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
配置目录覆盖(高级)
|
||||
{t("settings.configDirectoryOverride")}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-3 leading-relaxed">
|
||||
在 WSL 等环境使用 Claude Code 或 Codex 的时候,可手动指定 WSL
|
||||
里的配置目录,供应商数据与主环境保持一致。
|
||||
{t("settings.configDirectoryDescription")}
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
|
||||
Claude Code 配置目录
|
||||
{t("settings.claudeConfigDir")}
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
|
||||
29
src/i18n/index.ts
Normal file
29
src/i18n/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
import en from "./locales/en.json";
|
||||
import zh from "./locales/zh.json";
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
translation: en,
|
||||
},
|
||||
zh: {
|
||||
translation: zh,
|
||||
},
|
||||
};
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: "en", // 默认语言设置为英文
|
||||
fallbackLng: "en", // 回退语言也设置为英文
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // React 已经默认转义
|
||||
},
|
||||
|
||||
// 开发模式下显示调试信息
|
||||
debug: false,
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
109
src/i18n/locales/en.json
Normal file
109
src/i18n/locales/en.json
Normal file
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"app": {
|
||||
"title": "CC Switch",
|
||||
"description": "Claude Code & Codex Provider Switching Tool"
|
||||
},
|
||||
"common": {
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"close": "Close",
|
||||
"settings": "Settings",
|
||||
"about": "About",
|
||||
"version": "Version",
|
||||
"loading": "Loading...",
|
||||
"success": "Success",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"header": {
|
||||
"viewOnGithub": "View on GitHub",
|
||||
"toggleDarkMode": "Switch to Dark Mode",
|
||||
"toggleLightMode": "Switch to Light Mode",
|
||||
"addProvider": "Add Provider"
|
||||
},
|
||||
"provider": {
|
||||
"noProviders": "No providers added yet",
|
||||
"noProvidersDescription": "Click the \"Add Provider\" button in the top right to configure your first API provider",
|
||||
"currentlyUsing": "Currently Using",
|
||||
"enable": "Enable",
|
||||
"inUse": "In Use",
|
||||
"editProvider": "Edit Provider",
|
||||
"deleteProvider": "Delete Provider",
|
||||
"addNewProvider": "Add New Provider",
|
||||
"configError": "Configuration Error",
|
||||
"notConfigured": "Not configured for official website",
|
||||
"applyToVSCode": "Apply to VS Code",
|
||||
"removeFromVSCode": "Remove from VS Code"
|
||||
},
|
||||
"notifications": {
|
||||
"providerSaved": "Provider configuration saved",
|
||||
"providerDeleted": "Provider deleted successfully",
|
||||
"switchSuccess": "Switch successful! Please restart {{appName}} terminal to take effect",
|
||||
"switchFailed": "Switch failed, please check configuration",
|
||||
"autoImported": "Default provider created from existing configuration",
|
||||
"appliedToVSCode": "Applied to VS Code, restart Codex plugin to take effect",
|
||||
"removedFromVSCode": "Removed from VS Code, restart Codex plugin to take effect",
|
||||
"syncedToVSCode": "Synced to VS Code",
|
||||
"vscodeSettingsNotFound": "VS Code user settings file (settings.json) not found",
|
||||
"missingBaseUrl": "Current configuration missing base_url, cannot write to VS Code",
|
||||
"saveFailed": "Save failed: {{error}}",
|
||||
"saveFailedGeneric": "Save failed, please try again",
|
||||
"syncVSCodeFailed": "Sync to VS Code failed"
|
||||
},
|
||||
"confirm": {
|
||||
"deleteProvider": "Delete Provider",
|
||||
"deleteProviderMessage": "Are you sure you want to delete provider \"{{name}}\"? This action cannot be undone."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"windowBehavior": "Window Behavior",
|
||||
"minimizeToTray": "Minimize to tray on close",
|
||||
"minimizeToTrayDescription": "When checked, clicking the close button will hide to system tray, otherwise the app will exit directly.",
|
||||
"configFileLocation": "Configuration File Location",
|
||||
"openFolder": "Open Folder",
|
||||
"configDirectoryOverride": "Configuration Directory Override (Advanced)",
|
||||
"configDirectoryDescription": "When using Claude Code or Codex in environments like WSL, you can manually specify the configuration directory in WSL to keep provider data consistent with the main environment.",
|
||||
"claudeConfigDir": "Claude Code Configuration Directory",
|
||||
"codexConfigDir": "Codex Configuration Directory",
|
||||
"browsePlaceholderClaude": "e.g., /home/<your-username>/.claude",
|
||||
"browsePlaceholderCodex": "e.g., /home/<your-username>/.codex",
|
||||
"browseDirectory": "Browse Directory",
|
||||
"resetDefault": "Reset to default directory (takes effect after saving)",
|
||||
"checkForUpdates": "Check for Updates",
|
||||
"updateTo": "Update to v{{version}}",
|
||||
"updating": "Updating...",
|
||||
"checking": "Checking...",
|
||||
"upToDate": "Up to Date",
|
||||
"releaseNotes": "Release Notes",
|
||||
"viewReleaseNotes": "View release notes for this version",
|
||||
"viewCurrentReleaseNotes": "View current version release notes"
|
||||
},
|
||||
"apps": {
|
||||
"claude": "Claude Code",
|
||||
"codex": "Codex"
|
||||
},
|
||||
"console": {
|
||||
"providerSwitchReceived": "Received provider switch event:",
|
||||
"setupListenerFailed": "Failed to setup provider switch listener:",
|
||||
"updateProviderFailed": "Update provider failed:",
|
||||
"syncToVSCodeFailed": "Sync to VS Code failed:",
|
||||
"autoImportFailed": "Auto import default configuration failed:",
|
||||
"openLinkFailed": "Failed to open link:",
|
||||
"getVersionFailed": "Failed to get version info:",
|
||||
"loadSettingsFailed": "Failed to load settings:",
|
||||
"getConfigPathFailed": "Failed to get config path:",
|
||||
"getConfigDirFailed": "Failed to get config directory:",
|
||||
"detectPortableFailed": "Failed to detect portable mode:",
|
||||
"saveSettingsFailed": "Failed to save settings:",
|
||||
"updateFailed": "Update failed:",
|
||||
"checkUpdateFailed": "Check for updates failed:",
|
||||
"openConfigFolderFailed": "Failed to open config folder:",
|
||||
"selectConfigDirFailed": "Failed to select config directory:",
|
||||
"getDefaultConfigDirFailed": "Failed to get default config directory:",
|
||||
"openReleaseNotesFailed": "Failed to open release notes:"
|
||||
}
|
||||
}
|
||||
109
src/i18n/locales/zh.json
Normal file
109
src/i18n/locales/zh.json
Normal file
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"app": {
|
||||
"title": "CC Switch",
|
||||
"description": "Claude Code & Codex 供应商切换工具"
|
||||
},
|
||||
"common": {
|
||||
"add": "添加",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定",
|
||||
"close": "关闭",
|
||||
"settings": "设置",
|
||||
"about": "关于",
|
||||
"version": "版本",
|
||||
"loading": "加载中...",
|
||||
"success": "成功",
|
||||
"error": "错误",
|
||||
"unknown": "未知"
|
||||
},
|
||||
"header": {
|
||||
"viewOnGithub": "在 GitHub 上查看",
|
||||
"toggleDarkMode": "切换到暗色模式",
|
||||
"toggleLightMode": "切换到亮色模式",
|
||||
"addProvider": "添加供应商"
|
||||
},
|
||||
"provider": {
|
||||
"noProviders": "还没有添加任何供应商",
|
||||
"noProvidersDescription": "点击右上角的\"添加供应商\"按钮开始配置您的第一个API供应商",
|
||||
"currentlyUsing": "当前使用",
|
||||
"enable": "启用",
|
||||
"inUse": "使用中",
|
||||
"editProvider": "编辑供应商",
|
||||
"deleteProvider": "删除供应商",
|
||||
"addNewProvider": "添加新供应商",
|
||||
"configError": "配置错误",
|
||||
"notConfigured": "未配置官网地址",
|
||||
"applyToVSCode": "应用到 VS Code",
|
||||
"removeFromVSCode": "从 VS Code 移除"
|
||||
},
|
||||
"notifications": {
|
||||
"providerSaved": "供应商配置已保存",
|
||||
"providerDeleted": "供应商删除成功",
|
||||
"switchSuccess": "切换成功!请重启 {{appName}} 终端以生效",
|
||||
"switchFailed": "切换失败,请检查配置",
|
||||
"autoImported": "已从现有配置创建默认供应商",
|
||||
"appliedToVSCode": "已应用到 VS Code,重启 Codex 插件以生效",
|
||||
"removedFromVSCode": "已从 VS Code 移除,重启 Codex 插件以生效",
|
||||
"syncedToVSCode": "已同步到 VS Code",
|
||||
"vscodeSettingsNotFound": "未找到 VS Code 用户设置文件 (settings.json)",
|
||||
"missingBaseUrl": "当前配置缺少 base_url,无法写入 VS Code",
|
||||
"saveFailed": "保存失败:{{error}}",
|
||||
"saveFailedGeneric": "保存失败,请重试",
|
||||
"syncVSCodeFailed": "同步 VS Code 失败"
|
||||
},
|
||||
"confirm": {
|
||||
"deleteProvider": "删除供应商",
|
||||
"deleteProviderMessage": "确定要删除供应商 \"{{name}}\" 吗?此操作无法撤销。"
|
||||
},
|
||||
"settings": {
|
||||
"title": "设置",
|
||||
"windowBehavior": "窗口行为",
|
||||
"minimizeToTray": "关闭时最小化到托盘",
|
||||
"minimizeToTrayDescription": "勾选后点击关闭按钮会隐藏到系统托盘,取消则直接退出应用。",
|
||||
"configFileLocation": "配置文件位置",
|
||||
"openFolder": "打开文件夹",
|
||||
"configDirectoryOverride": "配置目录覆盖(高级)",
|
||||
"configDirectoryDescription": "在 WSL 等环境使用 Claude Code 或 Codex 的时候,可手动指定 WSL 里的配置目录,供应商数据与主环境保持一致。",
|
||||
"claudeConfigDir": "Claude Code 配置目录",
|
||||
"codexConfigDir": "Codex 配置目录",
|
||||
"browsePlaceholderClaude": "例如:/home/<你的用户名>/.claude",
|
||||
"browsePlaceholderCodex": "例如:/home/<你的用户名>/.codex",
|
||||
"browseDirectory": "浏览目录",
|
||||
"resetDefault": "恢复默认目录(需保存后生效)",
|
||||
"checkForUpdates": "检查更新",
|
||||
"updateTo": "更新到 v{{version}}",
|
||||
"updating": "更新中...",
|
||||
"checking": "检查中...",
|
||||
"upToDate": "已是最新",
|
||||
"releaseNotes": "更新日志",
|
||||
"viewReleaseNotes": "查看该版本更新日志",
|
||||
"viewCurrentReleaseNotes": "查看当前版本更新日志"
|
||||
},
|
||||
"apps": {
|
||||
"claude": "Claude Code",
|
||||
"codex": "Codex"
|
||||
},
|
||||
"console": {
|
||||
"providerSwitchReceived": "收到供应商切换事件:",
|
||||
"setupListenerFailed": "设置供应商切换监听器失败:",
|
||||
"updateProviderFailed": "更新供应商失败:",
|
||||
"syncToVSCodeFailed": "同步到VS Code失败:",
|
||||
"autoImportFailed": "自动导入默认配置失败:",
|
||||
"openLinkFailed": "打开链接失败:",
|
||||
"getVersionFailed": "获取版本信息失败:",
|
||||
"loadSettingsFailed": "加载设置失败:",
|
||||
"getConfigPathFailed": "获取配置路径失败:",
|
||||
"getConfigDirFailed": "获取配置目录失败:",
|
||||
"detectPortableFailed": "检测便携模式失败:",
|
||||
"saveSettingsFailed": "保存设置失败:",
|
||||
"updateFailed": "更新失败:",
|
||||
"checkUpdateFailed": "检查更新失败:",
|
||||
"openConfigFolderFailed": "打开配置文件夹失败:",
|
||||
"selectConfigDirFailed": "选择配置目录失败:",
|
||||
"getDefaultConfigDirFailed": "获取默认配置目录失败:",
|
||||
"openReleaseNotesFailed": "打开更新日志失败:"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import { UpdateProvider } from "./contexts/UpdateContext";
|
||||
import "./index.css";
|
||||
// 导入 Tauri API(自动绑定到 window.api)
|
||||
import "./lib/tauri-api";
|
||||
// 导入国际化配置
|
||||
import "./i18n";
|
||||
|
||||
// 根据平台添加 body class,便于平台特定样式
|
||||
try {
|
||||
@@ -23,5 +25,5 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<UpdateProvider>
|
||||
<App />
|
||||
</UpdateProvider>
|
||||
</React.StrictMode>,
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user