Просмотр исходного кода

feat(i18n): add multilingual support for provider components

- Add i18n translations for proxy test button, model selector, and model redirect editor
- Support 5 languages: zh-CN, en, ja, ru, zh-TW
- Replace hardcoded Chinese strings with useTranslations hooks
- Add 41 translation keys across 3 component modules (proxyTest, modelSelect, modelRedirect)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
ding113 3 месяцев назад
Родитель
Сommit
cdd5a6fa27

+ 49 - 0
messages/en/settings.json

@@ -575,6 +575,55 @@
     "editProvider": "Edit Provider",
     "enabledStatus": "enabled",
     "form": {
+      "proxyTest": {
+        "fillUrlFirst": "Please fill in provider URL first",
+        "testFailed": "Test failed",
+        "testFailedRetry": "Test failed, please retry",
+        "noResult": "Test succeeded but no result returned",
+        "connectionSuccess": "Connection successful",
+        "connectionFailed": "Connection failed",
+        "viaProxy": "(via proxy)",
+        "viaDirect": "(direct)",
+        "responseTime": "Response time:",
+        "statusCode": "Status code:",
+        "connectionMethod": "Connection method:",
+        "proxy": "Proxy",
+        "direct": "Direct",
+        "errorType": "Error type:",
+        "testing": "Testing...",
+        "testConnection": "Test Connection",
+        "timeoutError": "Connection timeout (5s). Please check:\n1. Is proxy server accessible\n2. Are proxy address and port correct\n3. Are proxy credentials correct",
+        "proxyError": "Proxy error:",
+        "networkError": "Network error:"
+      },
+      "modelSelect": {
+        "allowAllModels": "Allow all {type} models",
+        "selectedCount": "Selected {count} models",
+        "searchPlaceholder": "Search model name...",
+        "loading": "Loading...",
+        "notFound": "Model not found",
+        "selectAll": "Select All ({count})",
+        "clear": "Clear",
+        "manualAdd": "Manually Add Model",
+        "manualPlaceholder": "Enter model name (e.g. gpt-5-turbo)",
+        "manualDesc": "Support adding any model name (not limited to price table)",
+        "claude": "Claude",
+        "openai": "OpenAI"
+      },
+      "modelRedirect": {
+        "currentRules": "Current Rules ({count})",
+        "addNewRule": "Add New Rule",
+        "sourceModel": "User Requested Model",
+        "targetModel": "Actual Forwarded Model",
+        "sourcePlaceholder": "e.g. claude-sonnet-4-5-20250929",
+        "targetPlaceholder": "e.g. glm-4.6",
+        "add": "Add",
+        "sourceEmpty": "Source model name cannot be empty",
+        "targetEmpty": "Target model name cannot be empty",
+        "alreadyExists": "Model \"{model}\" already has a redirect rule",
+        "description": "Redirect Claude Code client requested models (e.g. claude-sonnet-4.5) to upstream provider supported models (e.g. glm-4.6, gemini-pro). For cost optimization or third-party AI integration.",
+        "emptyState": "No redirect rules yet. After adding rules, the system will automatically rewrite model names in requests."
+      },
       "addRedirect": "Add Redirect",
       "allowAllModels": "✓ Allow All Models (Recommended)",
       "apiAddress": "API Address",

+ 49 - 0
messages/ja/settings.json

@@ -512,6 +512,55 @@
     "editProvider": "サービスプロバイダーを編集",
     "enabledStatus": "有効",
     "form": {
+      "proxyTest": {
+        "fillUrlFirst": "まずプロバイダーURLを入力してください",
+        "testFailed": "テスト失敗",
+        "testFailedRetry": "テストに失敗しました。再試行してください",
+        "noResult": "テスト成功ですが結果が返されませんでした",
+        "connectionSuccess": "接続成功",
+        "connectionFailed": "接続失敗",
+        "viaProxy": "(プロキシ経由)",
+        "viaDirect": "(直接接続)",
+        "responseTime": "応答時間:",
+        "statusCode": "ステータスコード:",
+        "connectionMethod": "接続方式:",
+        "proxy": "プロキシ",
+        "direct": "直接接続",
+        "errorType": "エラータイプ:",
+        "testing": "テスト中...",
+        "testConnection": "接続テスト",
+        "timeoutError": "接続タイムアウト(5秒)。以下を確認してください:\n1. プロキシサーバーにアクセスできるか\n2. プロキシアドレスとポートが正しいか\n3. プロキシ認証情報が正しいか",
+        "proxyError": "プロキシエラー:",
+        "networkError": "ネットワークエラー:"
+      },
+      "modelSelect": {
+        "allowAllModels": "すべての {type} モデルを許可",
+        "selectedCount": "{count} 個のモデルを選択済み",
+        "searchPlaceholder": "モデル名を検索...",
+        "loading": "読み込み中...",
+        "notFound": "モデルが見つかりません",
+        "selectAll": "すべて選択 ({count})",
+        "clear": "クリア",
+        "manualAdd": "手動でモデルを追加",
+        "manualPlaceholder": "モデル名を入力(例:gpt-5-turbo)",
+        "manualDesc": "任意のモデル名を追加できます(価格表のモデルに限定されません)",
+        "claude": "Claude",
+        "openai": "OpenAI"
+      },
+      "modelRedirect": {
+        "currentRules": "現在のルール ({count})",
+        "addNewRule": "新規ルールを追加",
+        "sourceModel": "ユーザーがリクエストするモデル",
+        "targetModel": "実際に転送されるモデル",
+        "sourcePlaceholder": "例:claude-sonnet-4-5-20250929",
+        "targetPlaceholder": "例:glm-4.6",
+        "add": "追加",
+        "sourceEmpty": "ソースモデル名を入力してください",
+        "targetEmpty": "ターゲットモデル名を入力してください",
+        "alreadyExists": "モデル \"{model}\" のリダイレクトルールは既に存在します",
+        "description": "Claude Code クライアントがリクエストするモデル(例:claude-sonnet-4.5)を、上流プロバイダーが実際にサポートするモデル(例:glm-4.6、gemini-pro)にリダイレクトします。コスト最適化やサードパーティAIサービスへの接続に使用します。",
+        "emptyState": "リダイレクトルールがありません。ルールを追加すると、システムは自動的にリクエスト内のモデル名を書き換えます。"
+      },
       "addRedirect": "リダイレクトを追加",
       "allowAllModels": "✓ すべてのモデルを許可(推奨)",
       "apiAddress": "API 地址",

+ 49 - 0
messages/ru/settings.json

@@ -512,6 +512,55 @@
     "editProvider": "Редактировать провайдера",
     "enabledStatus": "Включено",
     "form": {
+      "proxyTest": {
+        "fillUrlFirst": "Пожалуйста, сначала заполните URL провайдера",
+        "testFailed": "Тест не пройден",
+        "testFailedRetry": "Тест не пройден, попробуйте снова",
+        "noResult": "Тест успешен, но результат не возвращен",
+        "connectionSuccess": "Соединение успешно",
+        "connectionFailed": "Соединение не удалось",
+        "viaProxy": "(через прокси)",
+        "viaDirect": "(прямое)",
+        "responseTime": "Время отклика:",
+        "statusCode": "Код состояния:",
+        "connectionMethod": "Способ соединения:",
+        "proxy": "Прокси",
+        "direct": "Прямое",
+        "errorType": "Тип ошибки:",
+        "testing": "Тестирование...",
+        "testConnection": "Проверить соединение",
+        "timeoutError": "Тайм-аут соединения (5 секунд). Проверьте:\n1. Доступен ли прокси-сервер\n2. Правильность адреса и порта прокси\n3. Правильность данных аутентификации прокси",
+        "proxyError": "Ошибка прокси:",
+        "networkError": "Сетевая ошибка:"
+      },
+      "modelSelect": {
+        "allowAllModels": "Разрешить все модели {type}",
+        "selectedCount": "Выбрано моделей: {count}",
+        "searchPlaceholder": "Поиск по названию модели...",
+        "loading": "Загрузка...",
+        "notFound": "Модели не найдены",
+        "selectAll": "Выбрать все ({count})",
+        "clear": "Очистить",
+        "manualAdd": "Добавить модель вручную",
+        "manualPlaceholder": "Введите название модели (например, gpt-5-turbo)",
+        "manualDesc": "Поддержка добавления любого названия модели (не ограничено прайс-листом)",
+        "claude": "Claude",
+        "openai": "OpenAI"
+      },
+      "modelRedirect": {
+        "currentRules": "Текущие правила ({count})",
+        "addNewRule": "Добавить новое правило",
+        "sourceModel": "Запрашиваемая пользователем модель",
+        "targetModel": "Фактически перенаправляемая модель",
+        "sourcePlaceholder": "Например: claude-sonnet-4-5-20250929",
+        "targetPlaceholder": "Например: glm-4.6",
+        "add": "Добавить",
+        "sourceEmpty": "Название исходной модели не может быть пустым",
+        "targetEmpty": "Название целевой модели не может быть пустым",
+        "alreadyExists": "Правило перенаправления для модели \"{model}\" уже существует",
+        "description": "Перенаправлять запросы моделей от клиента Claude Code (например, claude-sonnet-4.5) к моделям, поддерживаемым вышестоящим провайдером (например, glm-4.6, gemini-pro). Используется для оптимизации затрат или подключения сторонних AI-сервисов.",
+        "emptyState": "Пока нет правил перенаправления. После добавления правил система автоматически переписывает названия моделей в запросах."
+      },
       "addRedirect": "Добавить переправку",
       "allowAllModels": "✓ Разрешить все модели (рекомендуется)",
       "apiAddress": "Адрес API",

+ 49 - 0
messages/zh-CN/settings.json

@@ -187,6 +187,55 @@
     "confirmDelete": "确定要删除此供应商吗?",
     "notFound": "未找到匹配的供应商",
     "form": {
+      "proxyTest": {
+        "fillUrlFirst": "请先填写供应商 URL",
+        "testFailed": "测试失败",
+        "testFailedRetry": "测试失败,请重试",
+        "noResult": "测试成功但未返回结果",
+        "connectionSuccess": "连接成功",
+        "connectionFailed": "连接失败",
+        "viaProxy": "(通过代理)",
+        "viaDirect": "(直连)",
+        "responseTime": "响应时间:",
+        "statusCode": "状态码:",
+        "connectionMethod": "连接方式:",
+        "proxy": "代理",
+        "direct": "直连",
+        "errorType": "错误类型:",
+        "testing": "测试中...",
+        "testConnection": "测试连接",
+        "timeoutError": "连接超时(5秒)。请检查:\n1. 代理服务器是否可访问\n2. 代理地址和端口是否正确\n3. 代理认证信息是否正确",
+        "proxyError": "代理错误:",
+        "networkError": "网络错误:"
+      },
+      "modelSelect": {
+        "allowAllModels": "允许所有 {type} 模型",
+        "selectedCount": "已选择 {count} 个模型",
+        "searchPlaceholder": "搜索模型名称...",
+        "loading": "加载中...",
+        "notFound": "未找到模型",
+        "selectAll": "全选 ({count})",
+        "clear": "清空",
+        "manualAdd": "手动添加模型",
+        "manualPlaceholder": "输入模型名称(如 gpt-5-turbo)",
+        "manualDesc": "支持添加任意模型名称(不限于价格表中的模型)",
+        "claude": "Claude",
+        "openai": "OpenAI"
+      },
+      "modelRedirect": {
+        "currentRules": "当前规则 ({count})",
+        "addNewRule": "添加新规则",
+        "sourceModel": "用户请求的模型",
+        "targetModel": "实际转发的模型",
+        "sourcePlaceholder": "例如: claude-sonnet-4-5-20250929",
+        "targetPlaceholder": "例如: glm-4.6",
+        "add": "添加",
+        "sourceEmpty": "源模型名称不能为空",
+        "targetEmpty": "目标模型名称不能为空",
+        "alreadyExists": "模型 \"{model}\" 已存在重定向规则",
+        "description": "将 Claude Code 客户端请求的模型(如 claude-sonnet-4.5)重定向到上游供应商实际支持的模型(如 glm-4.6、gemini-pro)。用于成本优化或接入第三方 AI 服务。",
+        "emptyState": "暂无重定向规则。添加规则后,系统将自动重写请求中的模型名称。"
+      },
       "name": {
         "label": "服务商名称 *",
         "placeholder": "例如: 智谱"

+ 49 - 0
messages/zh-TW/settings.json

@@ -550,6 +550,55 @@
       "codexStrategyKeepLabel": "透传原样",
       "codexStrategySelect": "策略选择",
       "collapseAll": "折叠全部高级配置",
+      "proxyTest": {
+        "fillUrlFirst": "請先填寫供應商 URL",
+        "testFailed": "測試失敗",
+        "testFailedRetry": "測試失敗,請重試",
+        "noResult": "測試成功但未返回結果",
+        "connectionSuccess": "連線成功",
+        "connectionFailed": "連線失敗",
+        "viaProxy": "(透過代理)",
+        "viaDirect": "(直連)",
+        "responseTime": "回應時間:",
+        "statusCode": "狀態碼:",
+        "connectionMethod": "連線方式:",
+        "proxy": "代理",
+        "direct": "直連",
+        "errorType": "錯誤類型:",
+        "testing": "測試中...",
+        "testConnection": "測試連線",
+        "timeoutError": "連線逾時(5秒)。請檢查:\n1. 代理伺服器是否可存取\n2. 代理位址和連接埠是否正確\n3. 代理認證資訊是否正確",
+        "proxyError": "代理錯誤:",
+        "networkError": "網路錯誤:"
+      },
+      "modelSelect": {
+        "allowAllModels": "允許所有 {type} 模型",
+        "selectedCount": "已選擇 {count} 個模型",
+        "searchPlaceholder": "搜尋模型名稱...",
+        "loading": "載入中...",
+        "notFound": "未找到模型",
+        "selectAll": "全選 ({count})",
+        "clear": "清空",
+        "manualAdd": "手動新增模型",
+        "manualPlaceholder": "輸入模型名稱(例如 gpt-5-turbo)",
+        "manualDesc": "支援新增任意模型名稱(不限於價格表中的模型)",
+        "claude": "Claude",
+        "openai": "OpenAI"
+      },
+      "modelRedirect": {
+        "currentRules": "目前規則 ({count})",
+        "addNewRule": "新增規則",
+        "sourceModel": "使用者請求的模型",
+        "targetModel": "實際轉發的模型",
+        "sourcePlaceholder": "例如:claude-sonnet-4-5-20250929",
+        "targetPlaceholder": "例如:glm-4.6",
+        "add": "新增",
+        "sourceEmpty": "來源模型名稱不能為空",
+        "targetEmpty": "目標模型名稱不能為空",
+        "alreadyExists": "模型「{model}」已存在重新導向規則",
+        "description": "將 Claude Code 客戶端請求的模型(如 claude-sonnet-4.5)重新導向到上游供應商實際支援的模型(如 glm-4.6、gemini-pro)。用於成本最佳化或接入第三方 AI 服務。",
+        "emptyState": "目前無重新導向規則。新增規則後,系統將自動重寫請求中的模型名稱。"
+      },
       "confirmAdd": "确认添加",
       "confirmAddPending": "添加中...",
       "confirmUpdate": "确认更新",

+ 21 - 19
src/app/[locale]/settings/providers/_components/forms/proxy-test-button.tsx

@@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button";
 import { Loader2, CheckCircle2, XCircle, Activity } from "lucide-react";
 import { testProviderProxy } from "@/actions/providers";
 import { toast } from "sonner";
+import { useTranslations } from "next-intl";
 
 interface ProxyTestButtonProps {
   providerUrl: string;
@@ -24,6 +25,7 @@ export function ProxyTestButton({
   proxyFallbackToDirect = false,
   disabled = false,
 }: ProxyTestButtonProps) {
+  const t = useTranslations("settings.providers.form.proxyTest");
   const [isTesting, setIsTesting] = useState(false);
   const [testResult, setTestResult] = useState<{
     success: boolean;
@@ -41,7 +43,7 @@ export function ProxyTestButton({
   const handleTest = async () => {
     // 验证必填字段
     if (!providerUrl.trim()) {
-      toast.error("请先填写供应商 URL");
+      toast.error(t("fillUrlFirst"));
       return;
     }
 
@@ -56,12 +58,12 @@ export function ProxyTestButton({
       });
 
       if (!response.ok) {
-        toast.error(response.error || "测试失败");
+        toast.error(response.error || t("testFailed"));
         return;
       }
 
       if (!response.data) {
-        toast.error("测试成功但未返回结果");
+        toast.error(t("noResult"));
         return;
       }
 
@@ -70,29 +72,29 @@ export function ProxyTestButton({
       // 显示测试结果
       if (response.data.success) {
         const details = response.data.details;
-        const proxyUsed = details?.usedProxy ? "(通过代理)" : "(直连)";
+        const proxyUsed = details?.usedProxy ? t("viaProxy") : t("viaDirect");
         const responseTime = details?.responseTime ? `${details.responseTime}ms` : "N/A";
 
-        toast.success(`连接成功 ${proxyUsed}`, {
-          description: `响应时间: ${responseTime}${details?.statusCode ? ` | 状态码: ${details.statusCode}` : ""}`,
+        toast.success(`${t("connectionSuccess")} ${proxyUsed}`, {
+          description: `${t("responseTime")} ${responseTime}${details?.statusCode ? ` | ${t("statusCode")} ${details.statusCode}` : ""}`,
         });
       } else {
         const errorType = response.data.details?.errorType;
         const errorMessage = response.data.details?.error || response.data.message;
 
-        toast.error("连接失败", {
+        toast.error(t("connectionFailed"), {
           description:
             errorType === "Timeout"
-              ? "连接超时(5秒)。请检查:\n1. 代理服务器是否可访问\n2. 代理地址和端口是否正确\n3. 代理认证信息是否正确"
+              ? t("timeoutError")
               : errorType === "ProxyError"
-                ? `代理错误: ${errorMessage}`
-                : `网络错误: ${errorMessage}`,
+                ? `${t("proxyError")} ${errorMessage}`
+                : `${t("networkError")} ${errorMessage}`,
           duration: 5000, // 延长显示时间,让用户看清楚诊断提示
         });
       }
     } catch (error) {
       console.error("测试代理连接失败:", error);
-      toast.error("测试失败,请重试");
+      toast.error(t("testFailedRetry"));
     } finally {
       setIsTesting(false);
     }
@@ -104,7 +106,7 @@ export function ProxyTestButton({
       return (
         <>
           <Loader2 className="h-4 w-4 mr-2 animate-spin" />
-          测试中...
+          {t("testing")}
         </>
       );
     }
@@ -114,14 +116,14 @@ export function ProxyTestButton({
         return (
           <>
             <CheckCircle2 className="h-4 w-4 mr-2 text-green-600" />
-            连接成功
+            {t("connectionSuccess")}
           </>
         );
       } else {
         return (
           <>
             <XCircle className="h-4 w-4 mr-2 text-red-600" />
-            连接失败
+            {t("connectionFailed")}
           </>
         );
       }
@@ -130,7 +132,7 @@ export function ProxyTestButton({
     return (
       <>
         <Activity className="h-4 w-4 mr-2" />
-        测试连接
+        {t("testConnection")}
       </>
     );
   };
@@ -159,17 +161,17 @@ export function ProxyTestButton({
           <div className="font-medium mb-1">{testResult.message}</div>
           {testResult.details && (
             <div className="space-y-0.5 text-xs opacity-80">
-              {testResult.details.statusCode && <div>状态码: {testResult.details.statusCode}</div>}
+              {testResult.details.statusCode && <div>{t("statusCode")} {testResult.details.statusCode}</div>}
               {testResult.details.responseTime !== undefined && (
-                <div>响应时间: {testResult.details.responseTime}ms</div>
+                <div>{t("responseTime")} {testResult.details.responseTime}ms</div>
               )}
               {testResult.details.usedProxy !== undefined && (
                 <div>
-                  连接方式: {testResult.details.usedProxy ? "代理" : "直连"}
+                  {t("connectionMethod")} {testResult.details.usedProxy ? t("proxy") : t("direct")}
                   {testResult.details.proxyUrl && ` (${testResult.details.proxyUrl})`}
                 </div>
               )}
-              {testResult.details.errorType && <div>错误类型: {testResult.details.errorType}</div>}
+              {testResult.details.errorType && <div>{t("errorType")} {testResult.details.errorType}</div>}
             </div>
           )}
         </div>

+ 13 - 9
src/app/[locale]/settings/providers/_components/model-multi-select.tsx

@@ -16,6 +16,7 @@ import { Checkbox } from "@/components/ui/checkbox";
 import { Input } from "@/components/ui/input";
 import { Label } from "@/components/ui/label";
 import { getAvailableModelsByProviderType } from "@/actions/model-prices";
+import { useTranslations } from "next-intl";
 
 interface ModelMultiSelectProps {
   providerType: "claude" | "codex" | "gemini-cli" | "openai-compatible";
@@ -30,6 +31,7 @@ export function ModelMultiSelect({
   onChange,
   disabled = false,
 }: ModelMultiSelectProps) {
+  const t = useTranslations("settings.providers.form.modelSelect");
   const [open, setOpen] = useState(false);
   const [availableModels, setAvailableModels] = useState<string[]>([]);
   const [loading, setLoading] = useState(true);
@@ -86,11 +88,13 @@ export function ModelMultiSelect({
         >
           {selectedModels.length === 0 ? (
             <span className="text-muted-foreground">
-              允许所有 {providerType === "claude" ? "Claude" : "OpenAI"} 模型
+              {t("allowAllModels", {
+                type: providerType === "claude" ? t("claude") : t("openai")
+              })}
             </span>
           ) : (
             <div className="flex gap-2 items-center">
-              <span className="truncate">已选择 {selectedModels.length} 个模型</span>
+              <span className="truncate">{t("selectedCount", { count: selectedModels.length })}</span>
               <Badge variant="secondary" className="ml-auto">
                 {selectedModels.length}
               </Badge>
@@ -110,9 +114,9 @@ export function ModelMultiSelect({
         onTouchMove={(e) => e.stopPropagation()}
       >
         <Command shouldFilter={true}>
-          <CommandInput placeholder="搜索模型名称..." />
+          <CommandInput placeholder={t("searchPlaceholder")} />
           <CommandList className="max-h-[250px] overflow-y-auto">
-            <CommandEmpty>{loading ? "加载中..." : "未找到模型"}</CommandEmpty>
+            <CommandEmpty>{loading ? t("loading") : t("notFound")}</CommandEmpty>
 
             {!loading && (
               <>
@@ -126,7 +130,7 @@ export function ModelMultiSelect({
                       className="flex-1"
                       type="button"
                     >
-                      全选 ({availableModels.length})
+                      {t("selectAll", { count: availableModels.length })}
                     </Button>
                     <Button
                       size="sm"
@@ -136,7 +140,7 @@ export function ModelMultiSelect({
                       className="flex-1"
                       type="button"
                     >
-                      清空
+                      {t("clear")}
                     </Button>
                   </div>
                 </CommandGroup>
@@ -167,10 +171,10 @@ export function ModelMultiSelect({
 
         {/* 新增:手动输入区域 */}
         <div className="border-t p-3 space-y-2">
-          <Label className="text-xs font-medium">手动添加模型</Label>
+          <Label className="text-xs font-medium">{t("manualAdd")}</Label>
           <div className="flex gap-2">
             <Input
-              placeholder="输入模型名称(如 gpt-5-turbo)"
+              placeholder={t("manualPlaceholder")}
               value={customModel}
               onChange={(e) => setCustomModel(e.target.value)}
               onKeyDown={(e) => {
@@ -192,7 +196,7 @@ export function ModelMultiSelect({
             </Button>
           </div>
           <p className="text-xs text-muted-foreground">
-            支持添加任意模型名称(不限于价格表中的模型)
+            {t("manualDesc")}
           </p>
         </div>
       </PopoverContent>

+ 14 - 14
src/app/[locale]/settings/providers/_components/model-redirect-editor.tsx

@@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button";
 import { Input } from "@/components/ui/input";
 import { Label } from "@/components/ui/label";
 import { Badge } from "@/components/ui/badge";
+import { useTranslations } from "next-intl";
 
 interface ModelRedirectEditorProps {
   value: Record<string, string>;
@@ -17,6 +18,7 @@ export function ModelRedirectEditor({
   onChange,
   disabled = false,
 }: ModelRedirectEditorProps) {
+  const t = useTranslations("settings.providers.form.modelRedirect");
   const [newSource, setNewSource] = useState("");
   const [newTarget, setNewTarget] = useState("");
   const [error, setError] = useState<string | null>(null);
@@ -29,17 +31,17 @@ export function ModelRedirectEditor({
 
     // 验证输入
     if (!newSource.trim()) {
-      setError("源模型名称不能为空");
+      setError(t("sourceEmpty"));
       return;
     }
     if (!newTarget.trim()) {
-      setError("目标模型名称不能为空");
+      setError(t("targetEmpty"));
       return;
     }
 
     // 检查是否已存在
     if (value[newSource.trim()]) {
-      setError(`模型 "${newSource.trim()}" 已存在重定向规则`);
+      setError(t("alreadyExists", { model: newSource.trim() }));
       return;
     }
 
@@ -73,7 +75,7 @@ export function ModelRedirectEditor({
       {redirects.length > 0 && (
         <div className="space-y-2">
           <div className="text-xs font-medium text-muted-foreground">
-            当前规则 ({redirects.length})
+            {t("currentRules", { count: redirects.length })}
           </div>
           <div className="space-y-1">
             {redirects.map(([source, target]) => (
@@ -107,18 +109,18 @@ export function ModelRedirectEditor({
 
       {/* 添加新规则表单 */}
       <div className="space-y-2">
-        <div className="text-xs font-medium text-muted-foreground">添加新规则</div>
+        <div className="text-xs font-medium text-muted-foreground">{t("addNewRule")}</div>
         <div className="grid grid-cols-[1fr_auto_1fr_auto] gap-2 items-end">
           <div className="space-y-1">
             <Label htmlFor="new-source" className="text-xs">
-              用户请求的模型
+              {t("sourceModel")}
             </Label>
             <Input
               id="new-source"
               value={newSource}
               onChange={(e) => setNewSource(e.target.value)}
               onKeyDown={handleKeyDown}
-              placeholder="例如: claude-sonnet-4-5-20250929"
+              placeholder={t("sourcePlaceholder")}
               disabled={disabled}
               className="font-mono text-sm"
             />
@@ -128,14 +130,14 @@ export function ModelRedirectEditor({
 
           <div className="space-y-1">
             <Label htmlFor="new-target" className="text-xs">
-              实际转发的模型
+              {t("targetModel")}
             </Label>
             <Input
               id="new-target"
               value={newTarget}
               onChange={(e) => setNewTarget(e.target.value)}
               onKeyDown={handleKeyDown}
-              placeholder="例如: glm-4.6"
+              placeholder={t("targetPlaceholder")}
               disabled={disabled}
               className="font-mono text-sm"
             />
@@ -149,7 +151,7 @@ export function ModelRedirectEditor({
             className="mb-0"
           >
             <Plus className="h-4 w-4 mr-1" />
-            添加
+            {t("add")}
           </Button>
         </div>
 
@@ -163,16 +165,14 @@ export function ModelRedirectEditor({
 
         {/* 帮助文本 */}
         <p className="text-xs text-muted-foreground">
-          将 Claude Code 客户端请求的模型(如
-          claude-sonnet-4.5)重定向到上游供应商实际支持的模型(如
-          glm-4.6、gemini-pro)。用于成本优化或接入第三方 AI 服务。
+          {t("description")}
         </p>
       </div>
 
       {/* 空状态提示 */}
       {redirects.length === 0 && (
         <div className="text-center py-6 text-sm text-muted-foreground border border-dashed rounded-md">
-          暂无重定向规则。添加规则后,系统将自动重写请求中的模型名称。
+          {t("emptyState")}
         </div>
       )}
     </div>