diff --git a/src/components/JsonEditor.tsx b/src/components/JsonEditor.tsx index 65868e3..a013c06 100644 --- a/src/components/JsonEditor.tsx +++ b/src/components/JsonEditor.tsx @@ -5,6 +5,7 @@ import { oneDark } from "@codemirror/theme-one-dark"; import { EditorState } from "@codemirror/state"; import { placeholder } from "@codemirror/view"; import { linter, Diagnostic } from "@codemirror/lint"; +import { useTranslation } from "react-i18next"; interface JsonEditorProps { value: string; @@ -23,6 +24,7 @@ const JsonEditor: React.FC = ({ rows = 12, showValidation = true, }) => { + const { t } = useTranslation(); const editorRef = useRef(null); const viewRef = useRef(null); @@ -46,12 +48,12 @@ const JsonEditor: React.FC = ({ from: 0, to: doc.length, severity: "error", - message: "配置必须是JSON对象,不能是数组或其他类型", + message: t("jsonEditor.mustBeObject"), }); } } catch (e) { // 简单处理JSON解析错误 - const message = e instanceof SyntaxError ? e.message : "JSON格式错误"; + const message = e instanceof SyntaxError ? e.message : t("jsonEditor.invalidJson"); diagnostics.push({ from: 0, to: doc.length, @@ -62,7 +64,7 @@ const JsonEditor: React.FC = ({ return diagnostics; }), - [showValidation], + [showValidation, t], ); useEffect(() => { diff --git a/src/components/ProviderForm.tsx b/src/components/ProviderForm.tsx index 47fcdda..a0c4cae 100644 --- a/src/components/ProviderForm.tsx +++ b/src/components/ProviderForm.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef, useMemo } from "react"; +import { useTranslation } from "react-i18next"; import { Provider, ProviderCategory, CustomEndpoint } from "../types"; import { AppType } from "../lib/tauri-api"; import { @@ -190,6 +191,7 @@ const ProviderForm: React.FC = ({ onSubmit, onClose, }) => { + const { t } = useTranslation(); // 对于 Codex,需要分离 auth 和 config const isCodex = appType === "codex"; @@ -331,21 +333,20 @@ const ProviderForm: React.FC = ({ useState(""); const validateSettingsConfig = (value: string): string => { - return validateJsonConfig(value, "配置内容"); + const err = validateJsonConfig(value, "配置内容"); + return err ? t("providerForm.configJsonError") : ""; }; const validateCodexAuth = (value: string): string => { - if (!value.trim()) { - return ""; - } + if (!value.trim()) return ""; try { const parsed = JSON.parse(value); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { - return "auth.json 必须是 JSON 对象"; + return t("providerForm.authJsonRequired"); } return ""; } catch { - return "auth.json 格式错误,请检查JSON语法"; + return t("providerForm.authJsonError"); } }; @@ -520,7 +521,7 @@ const ProviderForm: React.FC = ({ setError(""); if (!formData.name) { - setError("请填写供应商名称"); + setError(t("providerForm.fillSupplierName")); return; } @@ -535,7 +536,7 @@ const ProviderForm: React.FC = ({ } // Codex: 仅要求 auth.json 必填;config.toml 可为空 if (!codexAuth.trim()) { - setError("请填写 auth.json 配置"); + setError(t("providerForm.fillAuthJson")); return; } @@ -552,7 +553,7 @@ const ProviderForm: React.FC = ({ ? authJson.OPENAI_API_KEY.trim() : ""; if (!key) { - setError("请填写 OPENAI_API_KEY"); + setError(t("providerForm.fillApiKey")); return; } } @@ -563,7 +564,7 @@ const ProviderForm: React.FC = ({ config: codexConfig ?? "", }; } catch (err) { - setError("auth.json 格式错误,请检查JSON语法"); + setError(t("providerForm.authJsonError")); return; } } else { @@ -572,7 +573,7 @@ const ProviderForm: React.FC = ({ ); setSettingsConfigError(currentSettingsError); if (currentSettingsError) { - setError(currentSettingsError); + setError(t("providerForm.configJsonError")); return; } @@ -586,21 +587,21 @@ const ProviderForm: React.FC = ({ "" ).trim(); if (!resolvedValue) { - setError(`请填写 ${config.label}`); + setError(t("providerForm.fillParameter", { label: config.label })); return; } } } // Claude: 原有逻辑 if (!formData.settingsConfig.trim()) { - setError("请填写配置内容"); + setError(t("providerForm.fillConfigContent")); return; } try { settingsConfig = JSON.parse(formData.settingsConfig); } catch (err) { - setError("配置JSON格式错误,请检查语法"); + setError(t("providerForm.configJsonError")); return; } } @@ -669,7 +670,7 @@ const ProviderForm: React.FC = ({ if (snippetError) { setCommonConfigError(snippetError); if (snippetError.includes("配置 JSON 解析失败")) { - setSettingsConfigError("配置JSON格式错误,请检查语法"); + setSettingsConfigError(t("providerForm.configJsonError")); } setUseCommonConfig(false); return; @@ -723,7 +724,7 @@ const ProviderForm: React.FC = ({ if (removeResult.error) { setCommonConfigError(removeResult.error); if (removeResult.error.includes("配置 JSON 解析失败")) { - setSettingsConfigError("配置JSON格式错误,请检查语法"); + setSettingsConfigError(t("providerForm.configJsonError")); } return; } @@ -736,7 +737,7 @@ const ProviderForm: React.FC = ({ if (addResult.error) { setCommonConfigError(addResult.error); if (addResult.error.includes("配置 JSON 解析失败")) { - setSettingsConfigError("配置JSON格式错误,请检查语法"); + setSettingsConfigError(t("providerForm.configJsonError")); } return; } @@ -1456,13 +1457,13 @@ const ProviderForm: React.FC = ({ onCustomClick={handleCodexCustomClick} renderCustomDescription={() => ( <> - 手动配置供应商,需要填写完整的配置信息,或者 + {t("providerForm.manualConfig")} )} @@ -1474,7 +1475,7 @@ const ProviderForm: React.FC = ({ htmlFor="name" className="block text-sm font-medium text-gray-900 dark:text-gray-100" > - 供应商名称 * + {t("providerForm.supplierNameRequired")} = ({ name="name" value={formData.name} onChange={handleChange} - placeholder="例如:Anthropic 官方" + placeholder={t("providerForm.supplierNamePlaceholder")} required autoComplete="off" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors" @@ -1494,7 +1495,7 @@ const ProviderForm: React.FC = ({ htmlFor="websiteUrl" className="block text-sm font-medium text-gray-900 dark:text-gray-100" > - 官网地址 + {t("providerForm.websiteLabel")} = ({ name="websiteUrl" value={formData.websiteUrl} onChange={handleChange} - placeholder="https://example.com(可选)" + placeholder={t("providerForm.websiteUrlPlaceholder")} autoComplete="off" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors" /> @@ -1516,10 +1517,10 @@ const ProviderForm: React.FC = ({ required={!isOfficialPreset} placeholder={ isOfficialPreset - ? "官方登录无需填写 API Key,直接保存即可" + ? t("providerForm.officialNoApiKey") : shouldShowKimiSelector - ? "填写后可获取模型列表" - : "只需要填这里,下方配置会自动填充" + ? t("providerForm.kimiApiKeyHint") + : t("providerForm.apiKeyAutoFill") } disabled={isOfficialPreset} /> @@ -1531,7 +1532,7 @@ const ProviderForm: React.FC = ({ rel="noopener noreferrer" className="text-xs text-blue-400 dark:text-blue-500 hover:text-blue-500 dark:hover:text-blue-400 transition-colors" > - 获取 API Key + {t("providerForm.getApiKey")} )} @@ -1543,7 +1544,7 @@ const ProviderForm: React.FC = ({ templateValueEntries.length > 0 && (

- 参数配置 - {selectedTemplatePreset.name.trim()} * + {t("providerForm.parameterConfig", { name: selectedTemplatePreset.name.trim() })}

{templateValueEntries.map(([key, config]) => ( @@ -1616,7 +1617,7 @@ const ProviderForm: React.FC = ({ htmlFor="baseUrl" className="block text-sm font-medium text-gray-900 dark:text-gray-100" > - 请求地址 + {t("providerForm.apiEndpoint")}
= ({ id="baseUrl" value={baseUrl} onChange={(e) => handleBaseUrlChange(e.target.value)} - placeholder="https://your-api-endpoint.com" + placeholder={t("providerForm.apiEndpointPlaceholder")} autoComplete="off" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors" />

- 💡 填写兼容 Claude API 的服务端点地址 + {t("providerForm.apiHint")}

@@ -1677,8 +1678,8 @@ const ProviderForm: React.FC = ({ onChange={handleCodexApiKeyChange} placeholder={ isCodexOfficialPreset - ? "官方无需填写 API Key,直接保存即可" - : "只需要填这里,下方 auth.json 会自动填充" + ? t("codexConfig.codexOfficialNoApiKey") + : t("codexConfig.codexApiKeyAutoFill") } disabled={isCodexOfficialPreset} required={ @@ -1695,7 +1696,7 @@ const ProviderForm: React.FC = ({ rel="noopener noreferrer" className="text-xs text-blue-400 dark:text-blue-500 hover:text-blue-500 dark:hover:text-blue-400 transition-colors" > - 获取 API Key + {t("providerForm.getApiKey")} )} @@ -1709,7 +1710,7 @@ const ProviderForm: React.FC = ({ htmlFor="codexBaseUrl" className="block text-sm font-medium text-gray-900 dark:text-gray-100" > - 请求地址 + {t("codexConfig.apiUrlLabel")} = ({ id="codexBaseUrl" value={codexBaseUrl} onChange={(e) => handleCodexBaseUrlChange(e.target.value)} - placeholder="https://your-api-endpoint.com/v1" + placeholder={t("providerForm.codexApiEndpointPlaceholder")} autoComplete="off" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors" /> @@ -1800,7 +1801,7 @@ const ProviderForm: React.FC = ({ htmlFor="anthropicModel" className="block text-sm font-medium text-gray-900 dark:text-gray-100" > - 主模型 (可选) + {t("providerForm.mainModel")} = ({ onChange={(e) => handleModelChange("ANTHROPIC_MODEL", e.target.value) } - placeholder="例如: GLM-4.5" + placeholder={t("providerForm.mainModelPlaceholder")} autoComplete="off" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors" /> @@ -1820,7 +1821,7 @@ const ProviderForm: React.FC = ({ htmlFor="anthropicSmallFastModel" className="block text-sm font-medium text-gray-900 dark:text-gray-100" > - 快速模型 (可选) + {t("providerForm.fastModel")} = ({ e.target.value ) } - placeholder="例如: GLM-4.5-Air" + placeholder={t("providerForm.fastModelPlaceholder")} autoComplete="off" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors" /> @@ -1841,7 +1842,7 @@ const ProviderForm: React.FC = ({

- 💡 留空将使用供应商的默认模型 + {t("providerForm.modelHint")}

@@ -1872,7 +1873,7 @@ const ProviderForm: React.FC = ({ onClick={onClose} className="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-white dark:hover:bg-gray-700 rounded-lg transition-colors" > - 取消 + {t("common.cancel")} diff --git a/src/components/ProviderForm/ClaudeConfigEditor.tsx b/src/components/ProviderForm/ClaudeConfigEditor.tsx index b7ecaa1..1def4e6 100644 --- a/src/components/ProviderForm/ClaudeConfigEditor.tsx +++ b/src/components/ProviderForm/ClaudeConfigEditor.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import JsonEditor from "../JsonEditor"; import { X, Save } from "lucide-react"; import { isLinux } from "../../lib/platform"; +import { useTranslation } from "react-i18next"; interface ClaudeConfigEditorProps { value: string; @@ -24,6 +25,7 @@ const ClaudeConfigEditor: React.FC = ({ commonConfigError, configError, }) => { + const { t } = useTranslation(); const [isDarkMode, setIsDarkMode] = useState(false); const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false); @@ -82,7 +84,7 @@ const ClaudeConfigEditor: React.FC = ({ htmlFor="settingsConfig" className="block text-sm font-medium text-gray-900 dark:text-gray-100" > - Claude Code 配置 (JSON) * + {t("claudeConfig.configLabel")}
@@ -100,7 +102,7 @@ const ClaudeConfigEditor: React.FC = ({ onClick={() => setIsCommonConfigModalOpen(true)} className="text-xs text-blue-500 dark:text-blue-400 hover:underline" > - 编辑通用配置 + {t("claudeConfig.editCommonConfig")}
{commonConfigError && !isCommonConfigModalOpen && ( @@ -124,7 +126,7 @@ const ClaudeConfigEditor: React.FC = ({

{configError}

)}

- 完整的 Claude Code settings.json 配置内容 + {t("claudeConfig.fullSettingsHint")}

{isCommonConfigModalOpen && (
= ({ {/* Header - 统一标题栏样式 */}

- 编辑通用配置片段 + {t("claudeConfig.editCommonConfigTitle")}

@@ -160,7 +162,7 @@ const ClaudeConfigEditor: React.FC = ({ {/* Content - 统一内容区域样式 */}

- 该片段会在勾选"写入通用配置"时合并到 settings.json 中 + {t("claudeConfig.commonConfigHint")}

= ({ onClick={closeModal} className="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-white dark:hover:bg-gray-700 rounded-lg transition-colors" > - 取消 + {t("common.cancel")}
diff --git a/src/components/ProviderForm/CodexConfigEditor.tsx b/src/components/ProviderForm/CodexConfigEditor.tsx index 907059d..dc49050 100644 --- a/src/components/ProviderForm/CodexConfigEditor.tsx +++ b/src/components/ProviderForm/CodexConfigEditor.tsx @@ -3,6 +3,7 @@ import React, { useState, useEffect, useRef } from "react"; import { X, Save } from "lucide-react"; import { isLinux } from "../../lib/platform"; +import { useTranslation } from "react-i18next"; import { generateThirdPartyAuth, @@ -74,6 +75,7 @@ const CodexConfigEditor: React.FC = ({ setIsTemplateModalOpen: externalSetTemplateModalOpen, }) => { + const { t } = useTranslation(); const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false); // 使用内部状态或外部状态 @@ -236,7 +238,7 @@ const CodexConfigEditor: React.FC = ({ htmlFor="codexAuth" className="block text-sm font-medium text-gray-900 dark:text-gray-100" > - auth.json (JSON) * + {t("codexConfig.authJson")}