فهرست منبع

feat(i18n): enhance key management translations and improve user interface

- Added new translation keys for editing key forms in multiple languages, including descriptions and labels for key management features.
- Updated existing translations in quota and settings JSON files for better clarity and consistency across locales.
- Improved dynamic translation usage in key management components to enhance user experience and error handling.
- Ensured all new keys are properly integrated into the user interface for seamless localization.
ding113 3 ماه پیش
والد
کامیت
00c3bd4ec6

+ 44 - 0
messages/en/quota.json

@@ -200,6 +200,50 @@
       "success": "User quota set successfully",
       "error": "Failed to set",
       "retryError": "Failed to set, please try again later"
+    },
+    "editKeyForm": {
+      "title": "Edit Key",
+      "description": "Modify the key's name, expiration time, and rate limit configuration.",
+      "keyName": {
+        "label": "Key Name",
+        "placeholder": "Enter key name",
+        "required": "Key Name *"
+      },
+      "expiresAt": {
+        "label": "Expiration Time",
+        "placeholder": "Select expiration time",
+        "description": "Leave blank for never expires"
+      },
+      "canLoginWebUi": {
+        "label": "Allow Web UI Login",
+        "description": "When disabled, this key can only be used for API calls and cannot login to the admin panel"
+      },
+      "limit5hUsd": {
+        "label": "5-Hour Cost Limit (USD)",
+        "placeholder": "Leave blank for unlimited",
+        "description": "Maximum cost within 5 hours"
+      },
+      "limitWeeklyUsd": {
+        "label": "Weekly Cost Limit (USD)",
+        "placeholder": "Leave blank for unlimited",
+        "description": "Maximum cost per week"
+      },
+      "limitMonthlyUsd": {
+        "label": "Monthly Cost Limit (USD)",
+        "placeholder": "Leave blank for unlimited",
+        "description": "Maximum cost per month"
+      },
+      "limitConcurrentSessions": {
+        "label": "Concurrent Session Limit",
+        "placeholder": "0 means unlimited",
+        "description": "Number of simultaneous conversations"
+      },
+      "submitText": "Save Changes",
+      "loadingText": "Saving...",
+      "success": "Key updated successfully",
+      "error": "Failed to update key",
+      "retryError": "Save failed, please try again later",
+      "keyInfoMissing": "Key information does not exist"
     }
   }
 }

+ 5 - 5
messages/en/settings.json

@@ -396,24 +396,24 @@
     },
     "noData": "System has built-in price table. Use buttons above to sync or update.",
     "noModels": "No model prices found",
-    "search": "搜索模型名称...",
+    "search": "Search model name...",
     "subtitle": "Model Pricing",
     "subtitleDesc": "Manage AI model pricing configuration",
-    "sync": "同步 LiteLLM 价格",
+    "sync": "Sync LiteLLM Prices",
     "syncFailed": "Sync failed",
     "syncFailedError": "Sync failed:",
     "syncNoResult": "Price table updated but no result returned",
     "syncSuccess": "Price table updated successfully",
     "syncing": "Syncing...",
     "table": {
-      "cachePrice": "缓存价格",
+      "cachePrice": "Cache Price",
       "inputPrice": "Input Price ($/M)",
-      "model": "模型",
+      "model": "Model",
       "outputPrice": "Output Price ($/M)",
       "updatedAt": "Updated At"
     },
     "title": "Pricing",
-    "upload": "更新模型价格表",
+    "upload": "Update Model Price Table",
     "uploadFailed": "Failed to get pricing data:",
     "uploadSuccess": "Price table updated successfully"
   },

+ 1 - 1
messages/ja/internal.json

@@ -6,7 +6,7 @@
     },
     "actions": {
       "reconfigure": "パラメータを再設定",
-      "generate": "データ生成",
+      "generate": "データ生成",
       "exportScreenshot": "スクリーンショットをエクスポート",
       "exportPDF": "PDFをエクスポート"
     },

+ 101 - 57
messages/ja/quota.json

@@ -1,50 +1,50 @@
 {
   "header": {
-    "title": "用户配额",
+    "title": "ユーザークォータ",
     "role": {
-      "admin": "管理",
-      "user": "用户"
+      "admin": "管理",
+      "user": "ユーザー"
     },
-    "keysCountSuffix": "个密钥",
-    "rpm": "请求限流",
-    "todayCost": "今日消费",
-    "exceededNotice": "已超过限额,请联系管理员处理"
+    "keysCountSuffix": "個のキー",
+    "rpm": "リクエストレート制限",
+    "todayCost": "本日の使用量",
+    "exceededNotice": "クォータを超過しました。管理者にお問い合わせください"
   },
   "countdown": {
-    "reset": "重置倒计时"
+    "reset": "リセットまでのカウントダウン"
   },
   "windowType": {
     "5h": {
-      "label": "5小时滚动",
-      "description": "统计过去5小时内的消费,每小时滚动更新"
+      "label": "5時間ローリング",
+      "description": "過去5時間の使用量を集計、毎時ローリング更新"
     },
     "weekly": {
-      "label": "每周重置",
-      "description": "自然周制(周一00:00重置),整周统计"
+      "label": "毎週リセット",
+      "description": "暦週制(月曜日00:00にリセット)、週単位で集計"
     },
     "monthly": {
-      "label": "每月重置",
-      "description": "自然月制(每月1日00:00重置),整月统计"
+      "label": "毎月リセット",
+      "description": "暦月制(毎月1日00:00にリセット)、月単位で集計"
     },
     "daily": {
-      "label": "每日重置",
-      "description": "自然日制(每日00:00重置),按日统计"
+      "label": "毎日リセット",
+      "description": "暦日制(毎日00:00にリセット)、日単位で集計"
     }
   },
   "toolbar": {
-    "searchPlaceholder": "搜索用户名或密钥",
-    "filter": "筛选",
-    "sort": "排序",
-    "refresh": "新",
-    "autoRefresh": "自动刷新",
+    "searchPlaceholder": "ユーザー名またはキーを検索",
+    "filter": "フィルター",
+    "sort": "並び替え",
+    "refresh": "新",
+    "autoRefresh": "自動更新",
     "filterOptions": {
-      "all": "全部",
-      "warning": "警",
-      "exceeded": "已超限"
+      "all": "すべて",
+      "warning": "警",
+      "exceeded": "超過"
     },
     "sortOptions": {
-      "name": "按名称",
-      "usage": "使用率"
+      "name": "名前順",
+      "usage": "使用率"
     },
     "interval": {
       "10s": "10秒",
@@ -63,7 +63,7 @@
   },
   "users": {
     "title": "ユーザークォータ統計",
-    "totalCount": "合計 {count} のユーザー",
+    "totalCount": "合計 {count} のユーザー",
     "noNote": "備考なし",
     "rpm": {
       "label": "RPM クォータ",
@@ -119,10 +119,10 @@
   },
   "keys": {
     "title": "キークォータ統計",
-    "totalCount": "合計 {users} のユーザー、{keys} 個のキー",
+    "totalCount": "合計 {users} のユーザー、{keys} 個のキー",
     "searchPlaceholder": "ユーザーまたはキーを検索...",
     "filterLabel": "フィルター条件",
-    "filterCount": "{users} のユーザー、{keys} 個のキーを表示",
+    "filterCount": "{users} のユーザー、{keys} 個のキーを表示",
     "filter": {
       "all": "すべてのキー",
       "keyQuota": "キークォータのみ",
@@ -152,54 +152,98 @@
     "noMatches": "一致するユーザーまたはキーがありません",
     "editDialog": {
       "title": "キークォータ設定",
-      "description": "密钥: {keyName} ({userName})",
+      "description": "キー: {keyName} ({userName})",
       "cost5h": {
-        "label": "5小时限额(USD)",
-        "placeholder": "不限制",
-        "current": "当前已用: {currency}{current} / {currency}{limit}"
+        "label": "5時間クォータ (USD)",
+        "placeholder": "無制限",
+        "current": "現在使用: {currency}{current} / {currency}{limit}"
       },
       "costWeekly": {
-        "label": "周限额(USD)",
-        "placeholder": "不限制",
-        "current": "当前已用: {currency}{current} / {currency}{limit}"
+        "label": "週次クォータ (USD)",
+        "placeholder": "無制限",
+        "current": "現在使用: {currency}{current} / {currency}{limit}"
       },
       "costMonthly": {
-        "label": "月限额(USD)",
-        "placeholder": "不限制",
-        "current": "当前已用: {currency}{current} / {currency}{limit}"
+        "label": "月次クォータ (USD)",
+        "placeholder": "無制限",
+        "current": "現在使用: {currency}{current} / {currency}{limit}"
       },
       "concurrentSessions": {
-        "label": "并发 Session 限额",
-        "placeholder": "0 = 不限制",
-        "current": "当前并发: {current} / {limit}"
+        "label": "同時セッションクォータ",
+        "placeholder": "0 = 無制限",
+        "current": "現在の同時接続: {current} / {limit}"
       },
-      "clearAll": "清除所有限额",
+      "clearAll": "すべてのクォータをクリア",
       "save": "保存",
-      "setQuota": "设置限额",
+      "setQuota": "クォータを設定",
       "success": "クォータが正常に設定されました",
-      "clearSuccess": "限额已清除",
-      "error": "设置失败",
-      "clearError": "清除失败",
-      "retryError": "设置失败,请稍后重试"
+      "clearSuccess": "クォータがクリアされました",
+      "error": "設定に失敗しました",
+      "clearError": "クリアに失敗しました",
+      "retryError": "設定に失敗しました。後でもう一度お試しください"
     },
     "editUserDialog": {
       "title": "ユーザークォータ設定",
-      "description": "用户: {userName}",
+      "description": "ユーザー: {userName}",
       "rpm": {
-        "label": "每分钟请求数 (RPM)",
+        "label": "1分あたりのリクエスト数 (RPM)",
         "placeholder": "60",
-        "current": "当前: {current} / {limit} 请求/分钟"
+        "current": "現在: {current} / {limit} リクエスト/分"
       },
       "dailyQuota": {
-        "label": "每日消费限额(USD)",
+        "label": "日次コストクォータ (USD)",
         "placeholder": "100",
-        "current": "今日用: {currency}{current} / {currency}{limit}"
+        "current": "今日の使用: {currency}{current} / {currency}{limit}"
       },
       "save": "保存",
-      "editQuota": "编辑限额",
-      "success": "用户限额设置成功",
-      "error": "设置失败",
-      "retryError": "设置失败,请稍后重试"
+      "editQuota": "クォータを編集",
+      "success": "ユーザークォータが正常に設定されました",
+      "error": "設定に失敗しました",
+      "retryError": "設定に失敗しました。後でもう一度お試しください"
+    },
+    "editKeyForm": {
+      "title": "キーを編集",
+      "description": "キーの名前、有効期限、流量制限の設定を変更します。",
+      "keyName": {
+        "label": "キー名",
+        "placeholder": "キー名を入力してください",
+        "required": "キー名 *"
+      },
+      "expiresAt": {
+        "label": "有効期限",
+        "placeholder": "有効期限を選択",
+        "description": "空欄の場合は無期限"
+      },
+      "canLoginWebUi": {
+        "label": "Web UI へのログインを許可",
+        "description": "無効にすると、このキーは API 呼び出しにのみ使用でき、管理画面にはログインできません"
+      },
+      "limit5hUsd": {
+        "label": "5時間消費上限 (USD)",
+        "placeholder": "空欄の場合は無制限",
+        "description": "5時間以内の最大消費金額"
+      },
+      "limitWeeklyUsd": {
+        "label": "週間消費上限 (USD)",
+        "placeholder": "空欄の場合は無制限",
+        "description": "毎週の最大消費金額"
+      },
+      "limitMonthlyUsd": {
+        "label": "月間消費上限 (USD)",
+        "placeholder": "空欄の場合は無制限",
+        "description": "毎月の最大消費金額"
+      },
+      "limitConcurrentSessions": {
+        "label": "同時セッション上限",
+        "placeholder": "0 = 無制限",
+        "description": "同時実行可能な会話数"
+      },
+      "submitText": "変更を保存",
+      "loadingText": "保存中...",
+      "success": "キーが正常に更新されました",
+      "error": "キーの更新に失敗しました",
+      "retryError": "保存に失敗しました。後でもう一度お試しください",
+      "keyInfoMissing": "キー情報が存在しません"
     }
   }
 }

+ 38 - 38
messages/ja/settings.json

@@ -2,44 +2,44 @@
   "clientVersions": {
     "description": "クライアントバージョン要件を管理し、ユーザーが最新の安定版を使用していることを確認します。VSCodeとCLIは個別に管理されます。",
     "empty": {
-      "description": "过去 7 天内没有活跃用户使用可识别的客户端",
-      "title": "暂无客户端数据"
+      "description": "過去7日間に認識可能なクライアントを使用したアクティブユーザーがいません",
+      "title": "クライアントデータなし"
     },
     "features": {
-      "activeWindow": "活跃窗口:",
-      "activeWindowDesc": "仅统计过去 7 天内有请求的用户",
-      "autoDetect": "系统会自动检测每种客户端的最新稳定版本(GA 版本)",
-      "blockOldVersion": "使用旧版本的用户将收到 HTTP 400 错误,无法继续使用服务",
-      "errorMessage": "错误提示中包含当前版本和需要升级的版本号",
-      "gaRule": "判定规则:",
-      "gaRuleDesc": "当某个版本被 1 个以上用户使用时,视为 GA 版本",
-      "recommendation": "推荐做法:",
-      "recommendationDesc": "先观察下方的版本分布,确认新版本稳定后再启用。",
-      "title": "功能说明",
-      "whatHappens": "启用后会发生什么:"
+      "activeWindow": "アクティブウィンドウ:",
+      "activeWindowDesc": "過去7日間にリクエストがあったユーザーのみを集計",
+      "autoDetect": "システムは各クライアントの最新安定版(GAバージョン)を自動検出します",
+      "blockOldVersion": "旧バージョンを使用するユーザーはHTTP 400エラーを受信し、サービスを継続使用できません",
+      "errorMessage": "エラーメッセージには現在のバージョンとアップグレード必要バージョン番号が含まれます",
+      "gaRule": "判定ルール:",
+      "gaRuleDesc": "1人以上のユーザーが使用しているバージョンをGAバージョンとみなします",
+      "recommendation": "推奨方法:",
+      "recommendationDesc": "まず下記のバージョン分布を確認し、新バージョンが安定していることを確認してから有効にしてください。",
+      "title": "機能説明",
+      "whatHappens": "有効化後の動作:"
     },
     "section": {
       "distribution": {
-        "description": "显示过去 7 天内活跃用户的客户端版本信息。每种客户端类型独立统计 GA 版本。",
-        "title": "客户端版本分布"
+        "description": "過去7日間のアクティブユーザーのクライアントバージョン情報を表示します。各クライアントタイプごとにGAバージョンを独立して集計します。",
+        "title": "クライアントバージョン分布"
       },
       "settings": {
-        "description": "启用后,系统将自动检测客户端版本并拦截旧版本用户的请求。",
-        "title": "升级提醒设置"
+        "description": "有効にすると、システムはクライアントバージョンを自動的に検出し、旧バージョンユーザーのリクエストをブロックします。",
+        "title": "アップグレードリマインダー設定"
       }
     },
     "table": {
-      "currentGA": "当前 GA 版本:",
-      "internalType": "内部类型:",
-      "lastActive": "最后活跃时间",
+      "currentGA": "現在のGAバージョン:",
+      "internalType": "内部タイプ:",
+      "lastActive": "最終アクティブ時間",
       "latest": "最新",
-      "needsUpgrade": "需升级",
-      "noUsers": "暂无用户数据",
-      "status": "状态",
-      "unknown": "未知",
-      "user": "用户",
-      "usersCount": "{count} 位用户",
-      "version": "当前版本"
+      "needsUpgrade": "アップグレード必要",
+      "noUsers": "ユーザーデータなし",
+      "status": "ステータス",
+      "unknown": "不明",
+      "user": "ユーザー",
+      "usersCount": "{count}名のユーザー",
+      "version": "現在のバージョン"
     },
     "title": "クライアント更新リマインダー",
     "toggle": {
@@ -420,26 +420,26 @@
   "providers": {
     "add": "プロバイダーを追加",
     "addFailed": "プロバイダーの追加に失敗しました",
-    "addProvider": "新增服务商",
+    "addProvider": "プロバイダーを追加",
     "addSuccess": "プロバイダーが正常に追加されました",
-    "circuitBroken": "熔断中",
+    "circuitBroken": "サーキットブレーカー作動中",
     "clone": "プロバイダーを複製",
     "cloneFailed": "コピーに失敗しました",
     "confirmDelete": "このプロバイダーを削除してもよろしいですか?",
-    "confirmDeleteDesc": "确定要删除供应商 \"{name}\" 吗?此操作无法撤销。",
-    "confirmDeleteProvider": "确认删除供应商?",
-    "confirmDeleteProviderDesc": "确定要删除服务商\"{name}\"吗?此操作不可恢复。",
-    "createProvider": "新增服务商",
+    "confirmDeleteDesc": "プロバイダー「{name}」を削除してもよろしいですか?この操作は元に戻せません。",
+    "confirmDeleteProvider": "プロバイダーの削除を確認しますか?",
+    "confirmDeleteProviderDesc": "サービスプロバイダー「{name}」を削除してもよろしいですか?この操作は復元できません。",
+    "createProvider": "サービスプロバイダーを追加",
     "delete": "プロバイダーを削除",
     "deleteFailed": "プロバイダーの削除に失敗しました",
-    "deleteSuccess": "除成功",
+    "deleteSuccess": "除成功",
     "description": "APIサービスプロバイダーを設定し、可用性ステータスを維持します。",
-    "disabledStatus": "禁用",
-    "displayCount": "显示 {filtered} / {total} 个供应商",
+    "disabledStatus": "無効",
+    "displayCount": "{filtered} / {total}個のプロバイダーを表示",
     "edit": "プロバイダーを編集",
     "editFailed": "プロバイダーの更新に失敗しました",
-    "editProvider": "编辑服务商",
-    "enabledStatus": "启用",
+    "editProvider": "サービスプロバイダーを編集",
+    "enabledStatus": "有効",
     "form": {
       "addRedirect": "リダイレクトを追加",
       "allowAllModels": "✓ すべてのモデルを許可(推奨)",

+ 126 - 82
messages/ru/quota.json

@@ -1,55 +1,55 @@
 {
   "header": {
-    "title": "用户配额",
+    "title": "Квоты пользователей",
     "role": {
-      "admin": "管理员",
-      "user": "用户"
+      "admin": "Администратор",
+      "user": "Пользователь"
     },
-    "keysCountSuffix": "个密钥",
-    "rpm": "请求限流",
-    "todayCost": "今日消费",
-    "exceededNotice": "已超过限额,请联系管理员处理"
+    "keysCountSuffix": " ключей",
+    "rpm": "Ограничение запросов",
+    "todayCost": "Расходы за сегодня",
+    "exceededNotice": "Квота превышена, обратитесь к администратору"
   },
   "countdown": {
-    "reset": "重置倒计时"
+    "reset": "Обратный отсчет до сброса"
   },
   "windowType": {
     "5h": {
-      "label": "5小时滚动",
-      "description": "统计过去5小时内的消费,每小时滚动更新"
+      "label": "5-часовое скользящее окно",
+      "description": "Учет расходов за последние 5 часов, обновляется ежечасно"
     },
     "weekly": {
-      "label": "每周重置",
-      "description": "自然周制(周一00:00重置),整周统计"
+      "label": "Еженедельный сброс",
+      "description": "Календарная неделя (сброс в понедельник 00:00), статистика за неделю"
     },
     "monthly": {
-      "label": "每月重置",
-      "description": "自然月制(每月1日00:00重置),整月统计"
+      "label": "Ежемесячный сброс",
+      "description": "Календарный месяц (сброс 1-го числа в 00:00), статистика за месяц"
     },
     "daily": {
-      "label": "每日重置",
-      "description": "自然日制(每日00:00重置),按日统计"
+      "label": "Ежедневный сброс",
+      "description": "Календарный день (сброс ежедневно в 00:00), статистика за день"
     }
   },
   "toolbar": {
-    "searchPlaceholder": "搜索用户名或密钥",
-    "filter": "筛选",
-    "sort": "排序",
-    "refresh": "刷新",
-    "autoRefresh": "自动刷新",
+    "searchPlaceholder": "Поиск по имени пользователя или ключу",
+    "filter": "Фильтр",
+    "sort": "Сортировка",
+    "refresh": "Обновить",
+    "autoRefresh": "Автообновление",
     "filterOptions": {
-      "all": "全部",
-      "warning": "预警",
-      "exceeded": "已超限"
+      "all": "Все",
+      "warning": "Предупреждение",
+      "exceeded": "Превышено"
     },
     "sortOptions": {
-      "name": "按名称",
-      "usage": "按使用率"
+      "name": "По имени",
+      "usage": "По использованию"
     },
     "interval": {
-      "10s": "10",
-      "30s": "30",
-      "60s": "60"
+      "10s": "10 сек",
+      "30s": "30 сек",
+      "60s": "60 сек"
     }
   },
   "layout": {
@@ -64,35 +64,35 @@
   "users": {
     "title": "Статистика квот пользователей",
     "totalCount": "Всего пользователей: {count}",
-    "noNote": "Нет заметок",
+    "noNote": "Без заметок",
     "rpm": {
-      "label": "Квота RPM",
+      "label": "RPM квота",
       "description": "Запросов в минуту"
     },
     "dailyCost": {
-      "label": "Дневные расходы",
+      "label": "Ежедневные расходы",
       "resetAt": "Сброс в"
     },
-    "noQuotaData": "Невозможно получить информацию о квоте",
-    "noMatches": "Совпадающие пользователи не найдены",
-    "noData": "Нет данных пользователей",
+    "noQuotaData": "Не удалось получить информацию о квоте",
+    "noMatches": "Пользователи не найдены",
+    "noData": "Нет данных о пользователях",
     "sort": {
       "name": "По имени",
       "usage": "По использованию"
     },
     "filter": {
       "all": "Все",
-      "warning": "Близко к лимиту (>60%)",
+      "warning": "Приближение к лимиту (>60%)",
       "exceeded": "Превышено (≥100%)"
     }
   },
   "providers": {
     "title": "Статистика квот провайдеров",
     "totalCount": "Всего провайдеров: {count}",
-    "filterCount": "Показано {filtered} / {total} провайдеров",
+    "filterCount": "Показано {filtered} из {total} провайдеров",
     "status": {
-      "enabled": "Включено",
-      "disabled": "Отключено"
+      "enabled": "Включен",
+      "disabled": "Отключен"
     },
     "card": {
       "priority": "Приоритет",
@@ -102,26 +102,26 @@
       "label": "Расходы за 5 часов"
     },
     "costWeekly": {
-      "label": "Недельные расходы",
+      "label": "Еженедельные расходы",
       "resetAt": "Сброс в"
     },
     "costMonthly": {
-      "label": "Месячные расходы",
+      "label": "Ежемесячные расходы",
       "resetAt": "Сброс в"
     },
     "concurrentSessions": {
-      "label": "Одновременные сессии"
+      "label": "Параллельные сессии"
     },
     "noQuotaSet": "Квота не установлена",
-    "noQuotaData": "Невозможно получить информацию о квоте",
-    "noMatches": "Совпадающие провайдеры не найдены",
-    "unlimitedSection": "Провайдеры без квоты ({count})"
+    "noQuotaData": "Не удалось получить информацию о квоте",
+    "noMatches": "Провайдеры не найдены",
+    "unlimitedSection": "Провайдеры без квот ({count})"
   },
   "keys": {
     "title": "Статистика квот ключей",
     "totalCount": "Всего {users} пользователей, {keys} ключей",
     "searchPlaceholder": "Поиск пользователя или ключа...",
-    "filterLabel": "Условия фильтра",
+    "filterLabel": "Фильтры",
     "filterCount": "Показано {users} пользователей, {keys} ключей",
     "filter": {
       "all": "Все ключи",
@@ -131,12 +131,12 @@
       "exceeded": "Превышено (≥100%)"
     },
     "table": {
-      "keyName": "Имя ключа",
+      "keyName": "Название ключа",
       "quotaType": "Тип квоты",
-      "cost5h": "Квота за 5 часов",
-      "costWeekly": "Недельная квота",
-      "costMonthly": "Месячная квота",
-      "concurrentSessions": "Лимит одновременных",
+      "cost5h": "5-часовая квота",
+      "costWeekly": "Еженедельная квота",
+      "costMonthly": "Ежемесячная квота",
+      "concurrentSessions": "Лимит параллельных",
       "status": "Статус",
       "actions": "Действия"
     },
@@ -145,61 +145,105 @@
       "inherited": "Унаследовано от пользователя"
     },
     "status": {
-      "disabled": "Отключено",
-      "restricted": "Ограничено",
+      "disabled": "Отключен",
+      "restricted": "Ограничен",
       "normal": "Нормально"
     },
-    "noMatches": "Совпадающие пользователи или ключи не найдены",
+    "noMatches": "Пользователи или ключи не найдены",
     "editDialog": {
       "title": "Установить квоту ключа",
-      "description": "密钥: {keyName} ({userName})",
+      "description": "Ключ: {keyName} ({userName})",
       "cost5h": {
-        "label": "5小时限额(USD)",
-        "placeholder": "不限制",
-        "current": "当前已用: {currency}{current} / {currency}{limit}"
+        "label": "5-часовая квота (USD)",
+        "placeholder": "Неограниченно",
+        "current": "Использовано: {currency}{current} из {currency}{limit}"
       },
       "costWeekly": {
-        "label": "周限额(USD)",
-        "placeholder": "不限制",
-        "current": "当前已用: {currency}{current} / {currency}{limit}"
+        "label": "Еженедельная квота (USD)",
+        "placeholder": "Неограниченно",
+        "current": "Использовано: {currency}{current} из {currency}{limit}"
       },
       "costMonthly": {
-        "label": "月限额(USD)",
-        "placeholder": "不限制",
-        "current": "当前已用: {currency}{current} / {currency}{limit}"
+        "label": "Ежемесячная квота (USD)",
+        "placeholder": "Неограниченно",
+        "current": "Использовано: {currency}{current} из {currency}{limit}"
       },
       "concurrentSessions": {
-        "label": "并发 Session 限额",
-        "placeholder": "0 = 不限制",
-        "current": "当前并发: {current} / {limit}"
+        "label": "Квота параллельных сессий",
+        "placeholder": "0 = без ограничений",
+        "current": "Текущие параллельные: {current} из {limit}"
       },
-      "clearAll": "清除所有限额",
+      "clearAll": "Очистить все квоты",
       "save": "Сохранить",
-      "setQuota": "设置限额",
+      "setQuota": "Установить квоту",
       "success": "Квота успешно установлена",
-      "clearSuccess": "限额已清除",
-      "error": "设置失败",
-      "clearError": "清除失败",
-      "retryError": "设置失败,请稍后重试"
+      "clearSuccess": "Квота очищена",
+      "error": "Не удалось установить",
+      "clearError": "Не удалось очистить",
+      "retryError": "Не удалось установить, попробуйте позже"
     },
     "editUserDialog": {
       "title": "Установить квоту пользователя",
-      "description": "用户: {userName}",
+      "description": "Пользователь: {userName}",
       "rpm": {
-        "label": "每分钟请求数 (RPM)",
+        "label": "Запросов в минуту (RPM)",
         "placeholder": "60",
-        "current": "当前: {current} / {limit} 请求/分钟"
+        "current": "Текущее: {current} из {limit} запросов/минуту"
       },
       "dailyQuota": {
-        "label": "每日消费限额(USD)",
+        "label": "Ежедневная квота расходов (USD)",
         "placeholder": "100",
-        "current": "今日已用: {currency}{current} / {currency}{limit}"
+        "current": "Использовано сегодня: {currency}{current} из {currency}{limit}"
       },
       "save": "Сохранить",
-      "editQuota": "编辑限额",
-      "success": "用户限额设置成功",
-      "error": "设置失败",
-      "retryError": "设置失败,请稍后重试"
+      "editQuota": "Редактировать квоту",
+      "success": "Квота пользователя успешно установлена",
+      "error": "Не удалось установить",
+      "retryError": "Не удалось установить, попробуйте позже"
+    },
+    "editKeyForm": {
+      "title": "Редактировать ключ",
+      "description": "Измените название ключа, срок действия и настройки ограничения трафика.",
+      "keyName": {
+        "label": "Название ключа",
+        "placeholder": "Введите название ключа",
+        "required": "Название ключа *"
+      },
+      "expiresAt": {
+        "label": "Срок действия",
+        "placeholder": "Выберите срок действия",
+        "description": "Оставьте пустым для бессрочного действия"
+      },
+      "canLoginWebUi": {
+        "label": "Разрешить вход в веб-интерфейс",
+        "description": "При отключении этот ключ можно использовать только для вызовов API, вход в панель управления невозможен"
+      },
+      "limit5hUsd": {
+        "label": "Лимит расходов за 5 часов (USD)",
+        "placeholder": "Оставьте пустым для отсутствия ограничений",
+        "description": "Максимальная сумма расходов за 5 часов"
+      },
+      "limitWeeklyUsd": {
+        "label": "Еженедельный лимит расходов (USD)",
+        "placeholder": "Оставьте пустым для отсутствия ограничений",
+        "description": "Максимальная сумма расходов в неделю"
+      },
+      "limitMonthlyUsd": {
+        "label": "Ежемесячный лимит расходов (USD)",
+        "placeholder": "Оставьте пустым для отсутствия ограничений",
+        "description": "Максимальная сумма расходов в месяц"
+      },
+      "limitConcurrentSessions": {
+        "label": "Лимит параллельных сессий",
+        "placeholder": "0 = без ограничений",
+        "description": "Количество одновременных диалогов"
+      },
+      "submitText": "Сохранить изменения",
+      "loadingText": "Сохранение...",
+      "success": "Ключ успешно обновлен",
+      "error": "Не удалось обновить ключ",
+      "retryError": "Не удалось сохранить, попробуйте позже",
+      "keyInfoMissing": "Информация о ключе отсутствует"
     }
   }
 }

+ 136 - 136
messages/ru/settings.json

@@ -2,44 +2,44 @@
   "clientVersions": {
     "description": "Управление требованиями версии клиента для обеспечения использования последней стабильной версии. VSCode и CLI управляются отдельно.",
     "empty": {
-      "description": "过去 7 天内没有活跃用户使用可识别的客户端",
-      "title": "暂无客户端数据"
+      "description": "За последние 7 дней не было активных пользователей с распознаваемыми клиентами",
+      "title": "Нет данных о клиентах"
     },
     "features": {
-      "activeWindow": "活跃窗口:",
-      "activeWindowDesc": "仅统计过去 7 天内有请求的用户",
-      "autoDetect": "系统会自动检测每种客户端的最新稳定版本(GA 版本)",
-      "blockOldVersion": "使用旧版本的用户将收到 HTTP 400 错误,无法继续使用服务",
-      "errorMessage": "错误提示中包含当前版本和需要升级的版本号",
-      "gaRule": "判定规则:",
-      "gaRuleDesc": "当某个版本被 1 个以上用户使用时,视为 GA 版本",
-      "recommendation": "推荐做法:",
-      "recommendationDesc": "先观察下方的版本分布,确认新版本稳定后再启用。",
-      "title": "功能说明",
-      "whatHappens": "启用后会发生什么:"
+      "activeWindow": "Активное окно:",
+      "activeWindowDesc": "Учитываются только пользователи с запросами за последние 7 дней",
+      "autoDetect": "Система автоматически определяет последнюю стабильную версию (GA версию) каждого типа клиента",
+      "blockOldVersion": "Пользователи старых версий будут получать HTTP 400 ошибку и не смогут продолжить использование сервиса",
+      "errorMessage": "Сообщение об ошибке будет содержать текущую версию и требуемую версию для обновления",
+      "gaRule": "Правила определения:",
+      "gaRuleDesc": "Версия считается GA версией, когда её используют более 1 пользователя",
+      "recommendation": "Рекомендуемый подход:",
+      "recommendationDesc": "Сначала изучите распределение версий ниже, убедитесь в стабильности новой версии перед включением.",
+      "title": "Описание функции",
+      "whatHappens": "Что произойдет после включения:"
     },
     "section": {
       "distribution": {
-        "description": "显示过去 7 天内活跃用户的客户端版本信息。每种客户端类型独立统计 GA 版本。",
-        "title": "客户端版本分布"
+        "description": "Показывает информацию о версиях клиентов активных пользователей за последние 7 дней. GA версия рассчитывается независимо для каждого типа клиента.",
+        "title": "Распределение версий клиентов"
       },
       "settings": {
-        "description": "启用后,系统将自动检测客户端版本并拦截旧版本用户的请求。",
-        "title": "升级提醒设置"
+        "description": "После включения система будет автоматически проверять версию клиента и блокировать запросы от пользователей со старыми версиями.",
+        "title": "Настройки напоминания об обновлении"
       }
     },
     "table": {
-      "currentGA": "当前 GA 版本:",
-      "internalType": "内部类型:",
-      "lastActive": "最后活跃时间",
-      "latest": "最新",
-      "needsUpgrade": "需升级",
-      "noUsers": "暂无用户数据",
-      "status": "状态",
-      "unknown": "未知",
-      "user": "用户",
-      "usersCount": "{count} 位用户",
-      "version": "当前版本"
+      "currentGA": "Текущая GA версия:",
+      "internalType": "Внутренний тип:",
+      "lastActive": "Последняя активность",
+      "latest": "Последняя",
+      "needsUpgrade": "Требуется обновление",
+      "noUsers": "Нет данных о пользователях",
+      "status": "Статус",
+      "unknown": "Неизвестно",
+      "user": "Пользователь",
+      "usersCount": "{count} пользователей",
+      "version": "Текущая версия"
     },
     "title": "Напоминание об обновлении клиента",
     "toggle": {
@@ -396,183 +396,183 @@
     },
     "noData": "Система имеет встроенный прайс-лист. Используйте кнопки выше для синхронизации.",
     "noModels": "Цены моделей не найдены",
-    "search": "搜索模型名称...",
+    "search": "Поиск по названию модели...",
     "subtitle": "Цены моделей",
     "subtitleDesc": "Управление ценами AI моделей",
-    "sync": "同步 LiteLLM 价格",
+    "sync": "Синхронизировать цены LiteLLM",
     "syncFailed": "Ошибка синхронизации",
     "syncFailedError": "Ошибка синхронизации:",
     "syncNoResult": "Прайс-лист обновлен но результат не возвращен",
     "syncSuccess": "Прайс-лист обновлен успешно",
     "syncing": "Синхронизация...",
     "table": {
-      "cachePrice": "缓存价格",
+      "cachePrice": "Цена кэша",
       "inputPrice": "Цена ввода ($/M)",
-      "model": "模型",
+      "model": "Модель",
       "outputPrice": "Цена вывода ($/M)",
       "updatedAt": "Обновлено"
     },
     "title": "Прайс-лист",
-    "upload": "更新模型价格表",
+    "upload": "Обновить прайс-лист моделей",
     "uploadFailed": "Ошибка получения данных о ценах:",
     "uploadSuccess": "Прайс-лист обновлен успешно"
   },
   "providers": {
     "add": "Добавить поставщика",
     "addFailed": "Ошибка добавления поставщика",
-    "addProvider": "新增服务商",
+    "addProvider": "Добавить провайдера",
     "addSuccess": "Поставщик добавлен успешно",
-    "circuitBroken": "熔断中",
+    "circuitBroken": "Цепь разомкнута",
     "clone": "Дублировать поставщика",
     "cloneFailed": "Ошибка копирования",
     "confirmDelete": "Вы уверены, что хотите удалить этого поставщика?",
-    "confirmDeleteDesc": "确定要删除供应商 \"{name}\" 吗?此操作无法撤销。",
-    "confirmDeleteProvider": "确认删除供应商?",
-    "confirmDeleteProviderDesc": "确定要删除服务商\"{name}\"吗?此操作不可恢复。",
-    "createProvider": "新增服务商",
+    "confirmDeleteDesc": "Вы уверены, что хотите удалить провайдера \"{name}\"? Это действие не может быть отменено.",
+    "confirmDeleteProvider": "Подтвердить удаление провайдера?",
+    "confirmDeleteProviderDesc": "Вы уверены, что хотите удалить провайдера \"{name}\"? Это действие не может быть восстановлено.",
+    "createProvider": "Добавить провайдера",
     "delete": "Удалить поставщика",
     "deleteFailed": "Ошибка удаления поставщика",
-    "deleteSuccess": "删除成功",
+    "deleteSuccess": "Успешно удалено",
     "description": "Настройка поставщиков API и контроль статуса доступности.",
-    "disabledStatus": "禁用",
-    "displayCount": "显示 {filtered} / {total} 个供应商",
+    "disabledStatus": "Отключено",
+    "displayCount": "Показано {filtered} / {total} провайдеров",
     "edit": "Редактировать поставщика",
     "editFailed": "Ошибка обновления поставщика",
-    "editProvider": "编辑服务商",
-    "enabledStatus": "启用",
+    "editProvider": "Редактировать провайдера",
+    "enabledStatus": "Включено",
     "form": {
       "addRedirect": "Добавить переправку",
       "allowAllModels": "✓ Разрешить все модели (рекомендуется)",
-      "apiAddress": "API 地址",
-      "apiAddressPlaceholder": "例如: https://open.bigmodel.cn/api/anthropic",
-      "apiAddressRequired": "API 地址 *",
+      "apiAddress": "Адрес API",
+      "apiAddressPlaceholder": "Например: https://open.bigmodel.cn/api/anthropic",
+      "apiAddressRequired": "Адрес API *",
       "apiKey": "API ключ",
-      "apiKeyCurrent": "当前密钥:",
-      "apiKeyLeaveEmpty": "(留空不更改)",
-      "apiKeyLeaveEmptyDesc": "留空则不更改密钥",
+      "apiKeyCurrent": "Текущий ключ:",
+      "apiKeyLeaveEmpty": "(оставьте пустым, чтобы не изменять)",
+      "apiKeyLeaveEmptyDesc": "Оставьте пустым, чтобы не изменять ключ",
       "apiKeyOptional": "Оставьте пустым, чтобы оставить текущий ключ",
       "apiKeyPlaceholder": "Введите API ключ",
-      "apiKeyRequired": "API 密钥 *",
+      "apiKeyRequired": "API ключ *",
       "baseUrl": "Базовый URL",
       "baseUrlPlaceholder": "например: https://open.bigmodel.cn/api/anthropic",
       "baseUrlRequired": "Пожалуйста, сначала заполните URL поставщика",
-      "circuitBreakerConfig": "熔断器配置",
-      "circuitBreakerConfigSummary": "{failureThreshold} 次失败 / {openDuration} 分钟熔断 / {successThreshold} 次成功恢复",
-      "circuitBreakerDesc": "供应商连续失败时自动熔断,避免影响整体服务质量",
+      "circuitBreakerConfig": "Конфигурация автоматического выключателя",
+      "circuitBreakerConfigSummary": "{failureThreshold} сбоев / {openDuration} мин. размыкания / {successThreshold} успехов для восстановления",
+      "circuitBreakerDesc": "Автоматическое размыкание при последовательных сбоях провайдера для предотвращения влияния на общее качество сервиса",
       "clearSearch": "Очистить поиск",
       "codexInstructions": "Политика инструкций Codex",
       "codexInstructionsAuto": "Автоматически (рекомендуется)",
       "codexInstructionsDesc": "(определяет политику планирования)",
       "codexInstructionsForce": "Принудительно официальные",
       "codexInstructionsKeep": "Сохранить оригинал",
-      "codexStrategyAutoDesc": "透传客户端 instructions,400 错误时自动重试官方 prompt",
-      "codexStrategyAutoLabel": "自动 (推荐)",
-      "codexStrategyConfig": "Codex Instructions 策略",
-      "codexStrategyConfigAuto": "自动 (推荐)",
-      "codexStrategyConfigForce": "强制官方",
-      "codexStrategyConfigKeep": "透传原样",
-      "codexStrategyDesc": "控制如何处理 Codex 请求的 instructions 字段,影响与上游中转站的兼容性",
-      "codexStrategyForceDesc": "始终使用官方 Codex CLI instructions(约 4000+ 字)",
-      "codexStrategyForceLabel": "强制官方",
-      "codexStrategyHint": "提示: 部分严格的 Codex 中转站(如 88code、foxcode)需要官方 instructions,选择\"自动\"或\"强制官方\"策略",
-      "codexStrategyKeepDesc": "始终透传客户端 instructions,不自动重试(适用于宽松中转站)",
-      "codexStrategyKeepLabel": "透传原样",
-      "codexStrategySelect": "策略选择",
-      "collapseAll": "折叠全部高级配置",
-      "confirmAdd": "确认添加",
-      "confirmAddPending": "添加中...",
-      "confirmUpdate": "确认更新",
-      "confirmUpdatePending": "更新中...",
+      "codexStrategyAutoDesc": "Передавать instructions клиента, автоматически повторять с официальным prompt при ошибке 400",
+      "codexStrategyAutoLabel": "Автоматически (рекомендуется)",
+      "codexStrategyConfig": "Стратегия Codex Instructions",
+      "codexStrategyConfigAuto": "Автоматически (рекомендуется)",
+      "codexStrategyConfigForce": "Принудительно официальные",
+      "codexStrategyConfigKeep": "Передавать как есть",
+      "codexStrategyDesc": "Управляет обработкой поля instructions в запросах Codex, влияет на совместимость с вышестоящими узлами",
+      "codexStrategyForceDesc": "Всегда использовать официальные Codex CLI instructions (около 4000+ символов)",
+      "codexStrategyForceLabel": "Принудительно официальные",
+      "codexStrategyHint": "Подсказка: некоторые строгие узлы Codex (например, 88code, foxcode) требуют официальные instructions, выберите стратегию \"Автоматически\" или \"Принудительно официальные\"",
+      "codexStrategyKeepDesc": "Всегда передавать instructions клиента без автоматического повтора (подходит для гибких узлов)",
+      "codexStrategyKeepLabel": "Передавать как есть",
+      "codexStrategySelect": "Выбор стратегии",
+      "collapseAll": "Свернуть все расширенные настройки",
+      "confirmAdd": "Подтвердить добавление",
+      "confirmAddPending": "Добавление...",
+      "confirmUpdate": "Подтвердить обновление",
+      "confirmUpdatePending": "Обновление...",
       "costMultiplier": "Множитель стоимости",
       "costMultiplierDesc": "например: A (стоимость 1.0x), C (стоимость 0.8x)",
-      "costMultiplierLabel": "成本倍率",
+      "costMultiplierLabel": "Коэффициент стоимости",
       "costMultiplierPlaceholder": "1.0",
-      "deleteButton": "删除",
+      "deleteButton": "Удалить",
       "enabled": "Включено",
-      "expandAll": "展开全部高级配置",
-      "failureThreshold": "失败阈值(次)",
-      "failureThresholdDesc": "连续失败多少次后触发熔断",
+      "expandAll": "Развернуть все расширенные настройки",
+      "failureThreshold": "Порог сбоев (раз)",
+      "failureThresholdDesc": "Сколько последовательных сбоев до размыкания",
       "failureThresholdPlaceholder": "5",
-      "filterAllProviders": "全部供应商",
+      "filterAllProviders": "Все поставщики",
       "filterByType": "Фильтр по типу",
-      "filterProvider": "筛选供应商类型",
+      "filterProvider": "Фильтр типа поставщика",
       "group": "Группа",
       "groupPlaceholder": "например: premium, economy",
-      "joinClaudePool": "加入 Claude 调度池",
-      "joinClaudePoolDesc": "启用后,此供应商将与 Claude 类型供应商一起参与负载均衡调度",
-      "joinClaudePoolHelp": "仅当模型重定向配置中存在映射到 claude-* 模型时可用。启用后,当用户请求 claude-* 模型时,此供应商也会参与调度选择。",
+      "joinClaudePool": "Присоединиться к пулу планирования Claude",
+      "joinClaudePoolDesc": "При включении этот поставщик будет участвовать в балансировке нагрузки вместе с поставщиками типа Claude",
+      "joinClaudePoolHelp": "Доступно только при наличии перенаправлений на модели claude-* в конфигурации. При включении этот поставщик также будет участвовать в выборе при запросах моделей claude-*.",
       "leaveEmpty": "Оставьте пустым для неограниченного доступа",
       "limit0Means": "0 означает без ограничений",
-      "limit5hLabel": "5小时消费上限 (USD)",
+      "limit5hLabel": "Лимит расходов за 5 часов (USD)",
       "limitAmount5h": "Лимит расходов за 5 часов (USD)",
       "limitAmount5hDesc": "например: Поставщик B имеет лимит $10, уже потрачено $9.8",
       "limitAmountMonthly": "Месячный лимит расходов (USD)",
       "limitAmountWeekly": "Недельный лимит расходов (USD)",
       "limitConcurrent": "Лимит параллельных сеансов",
       "limitConcurrentDesc": "например: Поставщик C имеет лимит 2, в данный момент 2 активных сеанса",
-      "limitConcurrentLabel": "并发 Session 上限",
-      "limitMonthlyLabel": "月消费上限 (USD)",
-      "limitPlaceholder0": "0 表示无限制",
-      "limitPlaceholderUnlimited": "留空表示无限制",
-      "limitWeeklyLabel": "周消费上限 (USD)",
+      "limitConcurrentLabel": "Лимит одновременных сеансов",
+      "limitMonthlyLabel": "Месячный лимит расходов (USD)",
+      "limitPlaceholder0": "0 означает без ограничений",
+      "limitPlaceholderUnlimited": "Оставьте пустым для неограниченного доступа",
+      "limitWeeklyLabel": "Недельный лимит расходов (USD)",
       "modelRedirects": "Перенаправление моделей",
-      "modelRedirectsAddNew": "添加新规则",
-      "modelRedirectsCurrentRules": "当前规则 ({count})",
+      "modelRedirectsAddNew": "Добавить новое правило",
+      "modelRedirectsCurrentRules": "Текущие правила ({count})",
       "modelRedirectsDesc": "Переправить запросы Claude к другим поддерживаемым моделям",
-      "modelRedirectsEmpty": "暂无重定向规则。添加规则后,系统将自动重写请求中的模型名称。",
-      "modelRedirectsExists": "模型 \"{model}\" 已存在重定向规则",
-      "modelRedirectsLabel": "模型重定向配置",
-      "modelRedirectsOptional": "(可选)",
-      "modelRedirectsSourceModel": "用户请求的模型",
-      "modelRedirectsSourcePlaceholder": "例如: claude-sonnet-4-5-20250929",
-      "modelRedirectsSourceRequired": "源模型名称不能为空",
-      "modelRedirectsTargetModel": "实际转发的模型",
-      "modelRedirectsTargetPlaceholder": "例如: glm-4.6",
-      "modelRedirectsTargetRequired": "目标模型名称不能为空",
-      "modelWhitelist": "模型白名单",
-      "modelWhitelistAllowAll": "允许所有 {type} 模型",
-      "modelWhitelistAllowAllClause": "允许所有 Claude 模型",
-      "modelWhitelistAllowAllOpenAI": "允许所有 OpenAI 模型",
-      "modelWhitelistClear": "清空",
-      "modelWhitelistDesc": "限制此供应商可以处理的模型。默认情况下,供应商可以处理该类型下的所有模型。",
-      "modelWhitelistLabel": "允许的模型",
-      "modelWhitelistLoading": "加载中...",
-      "modelWhitelistManualAdd": "手动添加模型",
-      "modelWhitelistManualDesc": "支持添加任意模型名称(不限于价格表中的模型)",
-      "modelWhitelistManualPlaceholder": "输入模型名称(如 gpt-5-turbo)",
-      "modelWhitelistNotFound": "未找到模型",
-      "modelWhitelistSearchPlaceholder": "搜索模型名称...",
-      "modelWhitelistSelectAll": "全选 ({count})",
-      "modelWhitelistSelected": "已选择 {count} 个模型",
-      "modelWhitelistSelectedOnly": "仅允许选中的 {count} 个模型。其他模型的请求不会调度到此供应商。",
+      "modelRedirectsEmpty": "Нет правил перенаправления. После добавления правил система автоматически перезапишет имена моделей в запросах.",
+      "modelRedirectsExists": "Правило перенаправления для модели \"{model}\" уже существует",
+      "modelRedirectsLabel": "Конфигурация перенаправления моделей",
+      "modelRedirectsOptional": "(необязательно)",
+      "modelRedirectsSourceModel": "Модель запроса пользователя",
+      "modelRedirectsSourcePlaceholder": "например: claude-sonnet-4-5-20250929",
+      "modelRedirectsSourceRequired": "Имя исходной модели не может быть пустым",
+      "modelRedirectsTargetModel": "Фактически перенаправляемая модель",
+      "modelRedirectsTargetPlaceholder": "например: glm-4.6",
+      "modelRedirectsTargetRequired": "Имя целевой модели не может быть пустым",
+      "modelWhitelist": "Белый список моделей",
+      "modelWhitelistAllowAll": "Разрешить все модели {type}",
+      "modelWhitelistAllowAllClause": "Разрешить все модели Claude",
+      "modelWhitelistAllowAllOpenAI": "Разрешить все модели OpenAI",
+      "modelWhitelistClear": "Очистить",
+      "modelWhitelistDesc": "Ограничить модели, которые может обрабатывать этот поставщик. По умолчанию поставщик может обрабатывать все модели этого типа.",
+      "modelWhitelistLabel": "Разрешенные модели",
+      "modelWhitelistLoading": "Загрузка...",
+      "modelWhitelistManualAdd": "Добавить модель вручную",
+      "modelWhitelistManualDesc": "Поддерживает добавление любого имени модели (не ограничено прайс-листом)",
+      "modelWhitelistManualPlaceholder": "Введите имя модели (например, gpt-5-turbo)",
+      "modelWhitelistNotFound": "Модели не найдены",
+      "modelWhitelistSearchPlaceholder": "Поиск по имени модели...",
+      "modelWhitelistSelectAll": "Выбрать все ({count})",
+      "modelWhitelistSelected": "Выбрано {count} моделей",
+      "modelWhitelistSelectedOnly": "Разрешены только выбранные {count} моделей. Запросы других моделей не будут направлены к этому поставщику.",
       "name": "Имя",
       "namePlaceholder": "Введите имя поставщика",
-      "openDuration": "熔断时长(分钟)",
-      "openDurationDesc": "熔断后多久自动进入半开状态",
+      "openDuration": "Длительность размыкания (минуты)",
+      "openDurationDesc": "Время автоматического перехода в полуоткрытое состояние после размыкания",
       "openDurationPlaceholder": "30",
       "priority": "Приоритет",
       "priorityDesc": "В пределах одного приоритета сортировка по множителю стоимости от низкого к высокому",
-      "priorityLabel": "优先级",
+      "priorityLabel": "Приоритет",
       "priorityPlaceholder": "0",
-      "providerGroupDesc": "供应商分组标签。只有用户的 providerGroup 与此值匹配时,该用户才能使用此供应商。示例:设置为 \"premium\" 表示只供 providerGroup=\"premium\" 的用户使用",
-      "providerGroupLabel": "供应商分组",
-      "providerGroupPlaceholder": "例如: premium, economy",
-      "providerName": "服务商名称",
-      "providerNamePlaceholder": "例如: 智谱",
-      "providerNameRequired": "服务商名称 *",
+      "providerGroupDesc": "Метка группы поставщика. Пользователь может использовать этого поставщика только если его providerGroup совпадает с этим значением. Пример: установка \"premium\" означает использование только пользователями с providerGroup=\"premium\"",
+      "providerGroupLabel": "Группа поставщика",
+      "providerGroupPlaceholder": "например: premium, economy",
+      "providerName": "Имя поставщика",
+      "providerNamePlaceholder": "например: Zhipu",
+      "providerNameRequired": "Имя поставщика *",
       "providerType": "Тип поставщика",
-      "providerTypeDesc": "选择供应商的 API 格式类型。",
-      "providerTypeDisabledNote": "注:Gemini CLI 和 OpenAI Compatible 类型功能正在开发中,暂不可用",
+      "providerTypeDesc": "Выберите тип формата API поставщика.",
+      "providerTypeDisabledNote": "Примечание: функции типов Gemini CLI и OpenAI Compatible находятся в разработке и временно недоступны",
       "proxy": "Прокси",
-      "proxyAddressFormats": "支持格式:",
-      "proxyAddressLabel": "代理地址",
-      "proxyAddressOptional": "(可选)",
-      "proxyAddressPlaceholder": "例如: http://proxy.example.com:8080 或 socks5://127.0.0.1:1080",
-      "proxyConfig": "代理配置",
-      "proxyConfigDesc": "配置代理服务器以改善供应商连接性(支持 HTTP、HTTPS、SOCKS4、SOCKS5)",
-      "proxyConfigNone": "未配置",
-      "proxyConfigSummary": "已配置代理",
-      "proxyConfigSummaryFallback": " (启用降级)",
+      "proxyAddressFormats": "Поддерживаемые форматы:",
+      "proxyAddressLabel": "Адрес прокси",
+      "proxyAddressOptional": "(необязательно)",
+      "proxyAddressPlaceholder": "например: http://proxy.example.com:8080 или socks5://127.0.0.1:1080",
+      "proxyConfig": "Конфигурация прокси",
+      "proxyConfigDesc": "Настройка прокси-сервера для улучшения подключения к поставщику (поддерживает HTTP, HTTPS, SOCKS4, SOCKS5)",
+      "proxyConfigNone": "Не настроен",
+      "proxyConfigSummary": "Прокси настроен",
+      "proxyConfigSummaryFallback": " (откат включен)",
       "proxyConfigured": "Прокси настроен",
       "proxyFallback": "Откат при ошибке прокси",
       "proxyFallbackDesc": "Перейти на прямое соединение при ошибке прокси",

+ 1 - 1
messages/zh-CN/internal.json

@@ -77,7 +77,7 @@
       "success": "成功"
     },
     "errors": {
-      "failed": "Failed to generate logs"
+      "failed": "生成日志失败"
     }
   }
 }

+ 44 - 0
messages/zh-CN/quota.json

@@ -200,6 +200,50 @@
       "success": "用户限额设置成功",
       "error": "设置失败",
       "retryError": "设置失败,请稍后重试"
+    },
+    "editKeyForm": {
+      "title": "编辑密钥",
+      "description": "修改密钥的名称、过期时间和限流配置。",
+      "keyName": {
+        "label": "密钥名称",
+        "placeholder": "请输入密钥名称",
+        "required": "密钥名称 *"
+      },
+      "expiresAt": {
+        "label": "过期时间",
+        "placeholder": "选择过期时间",
+        "description": "留空表示永不过期"
+      },
+      "canLoginWebUi": {
+        "label": "允许登录 Web UI",
+        "description": "关闭后,此 Key 仅可用于 API 调用,无法登录管理后台"
+      },
+      "limit5hUsd": {
+        "label": "5小时消费上限 (USD)",
+        "placeholder": "留空表示无限制",
+        "description": "5小时内最大消费金额"
+      },
+      "limitWeeklyUsd": {
+        "label": "周消费上限 (USD)",
+        "placeholder": "留空表示无限制",
+        "description": "每周最大消费金额"
+      },
+      "limitMonthlyUsd": {
+        "label": "月消费上限 (USD)",
+        "placeholder": "留空表示无限制",
+        "description": "每月最大消费金额"
+      },
+      "limitConcurrentSessions": {
+        "label": "并发 Session 上限",
+        "placeholder": "0 表示无限制",
+        "description": "同时运行的对话数量"
+      },
+      "submitText": "保存修改",
+      "loadingText": "保存中...",
+      "success": "密钥更新成功",
+      "error": "密钥更新失败",
+      "retryError": "保存失败,请稍后重试",
+      "keyInfoMissing": "密钥信息不存在"
     }
   }
 }

+ 11 - 11
messages/zh-TW/dashboard.json

@@ -1,10 +1,10 @@
 {
   "title": {
     "costRanking": "消耗排行榜",
-    "costRankingDescription": "查看用消耗排名,資料每 5 分鐘更新一次",
+    "costRankingDescription": "查看用消耗排名,資料每 5 分鐘更新一次",
     "usageLogs": "使用記錄",
-    "clients": "用端",
-    "userAndKeyManagement": "用和密鑰管理",
+    "clients": "用端",
+    "userAndKeyManagement": "用和密鑰管理",
     "requestMessages": "請求 Messages",
     "activeSessions": "活躍 Session(最近 5 分鐘)",
     "sessionMonitoring": "Session 監控",
@@ -12,7 +12,7 @@
     "inactiveSessions": "非活躍 Session(超過 5 分鐘,僅供查看)",
     "quotasManagement": "額度管理",
     "quotasManagementDescription": "查看和管理所有層級的額度使用情況",
-    "usersQuotas": "用額度統計",
+    "usersQuotas": "用額度統計",
     "keysQuotas": "密鑰額度統計",
     "providersQuotas": "供應商額度統計",
     "usageLogsDescription": "查看 API 調用日誌和使用統計",
@@ -23,16 +23,16 @@
     "dashboard": "查看系統統計資料和監控資訊",
     "quotas": "查看和管理所有層級的額度使用情況",
     "clientInfo": "User-Agent 請求頭",
-    "requestMessagesDescription": "用端傳送的訊息內容",
+    "requestMessagesDescription": "用端傳送的訊息內容",
     "responseBodyDescription": "伺服器傳回的完整回應(5分鐘 TTL)"
   },
   "overview": {
     "title": "概覽",
     "totalRequests": "總請求數",
     "totalCost": "總成本",
-    "totalUsers": "總用數",
+    "totalUsers": "總用數",
     "totalProviders": "總供應商數",
-    "activeUsers": "活躍用",
+    "activeUsers": "活躍用",
     "activeSessions": "活躍工作階段",
     "errorRate": "錯誤率",
     "avgResponseTime": "平均回應時間"
@@ -175,7 +175,7 @@
   },
   "leaderboard": {
     "title": "成本排行榜",
-    "description": "查看用和密鑰的成本統計排名",
+    "description": "查看用和密鑰的成本統計排名",
     "tabs": {
       "users": "使用者排名",
       "keys": "密鑰排名",
@@ -306,7 +306,7 @@
     "description": "管理使用者、金鑰和供應商的使用額度",
     "tabs": {
       "overview": "概覽",
-      "users": "用額度",
+      "users": "用額度",
       "keys": "密鑰額度",
       "providers": "供應商額度"
     },
@@ -326,8 +326,8 @@
       "exceeded": "已超額 (≥100%)"
     },
     "users": {
-      "title": "用額度統計",
-      "totalUsers": "共 {count} 個用"
+      "title": "用額度統計",
+      "totalUsers": "共 {count} 個用"
     },
     "keys": {
       "title": "密鑰額度統計",

+ 67 - 23
messages/zh-TW/quota.json

@@ -152,54 +152,98 @@
     "noMatches": "沒有匹配的用戶或密鑰",
     "editDialog": {
       "title": "設置密鑰限額",
-      "description": "密: {keyName} ({userName})",
+      "description": "密: {keyName} ({userName})",
       "cost5h": {
-        "label": "5小时限额(USD)",
+        "label": "5小時限額 (USD)",
         "placeholder": "不限制",
-        "current": "前已用: {currency}{current} / {currency}{limit}"
+        "current": "前已用: {currency}{current} / {currency}{limit}"
       },
       "costWeekly": {
-        "label": "周限(USD)",
+        "label": "周限(USD)",
         "placeholder": "不限制",
-        "current": "前已用: {currency}{current} / {currency}{limit}"
+        "current": "前已用: {currency}{current} / {currency}{limit}"
       },
       "costMonthly": {
-        "label": "月限(USD)",
+        "label": "月限(USD)",
         "placeholder": "不限制",
-        "current": "前已用: {currency}{current} / {currency}{limit}"
+        "current": "前已用: {currency}{current} / {currency}{limit}"
       },
       "concurrentSessions": {
-        "label": "并发 Session 限额",
+        "label": "並發 Session 限額",
         "placeholder": "0 = 不限制",
-        "current": "当前并发: {current} / {limit}"
+        "current": "當前並發: {current} / {limit}"
       },
-      "clearAll": "清除所有限",
+      "clearAll": "清除所有限",
       "save": "保存",
-      "setQuota": "设置限额",
+      "setQuota": "設置限額",
       "success": "限額設置成功",
-      "clearSuccess": "限已清除",
-      "error": "设置失败",
-      "clearError": "清除失",
-      "retryError": "设置失败,请稍后重试"
+      "clearSuccess": "限已清除",
+      "error": "設置失敗",
+      "clearError": "清除失",
+      "retryError": "設置失敗,請稍後重試"
     },
     "editUserDialog": {
       "title": "設置用戶限額",
-      "description": "用: {userName}",
+      "description": "用: {userName}",
       "rpm": {
-        "label": "每分钟请求数 (RPM)",
+        "label": "每分鐘請求數 (RPM)",
         "placeholder": "60",
-        "current": "当前: {current} / {limit} 请求/分钟"
+        "current": "當前: {current} / {limit} 請求/分鐘"
       },
       "dailyQuota": {
-        "label": "每日消费限额(USD)",
+        "label": "每日消費限額 (USD)",
         "placeholder": "100",
         "current": "今日已用: {currency}{current} / {currency}{limit}"
       },
       "save": "保存",
-      "editQuota": "编辑限额",
-      "success": "用户限额设置成功",
-      "error": "设置失败",
-      "retryError": "设置失败,请稍后重试"
+      "editQuota": "編輯限額",
+      "success": "用戶限額設置成功",
+      "error": "設置失敗",
+      "retryError": "設置失敗,請稍後重試"
+    },
+    "editKeyForm": {
+      "title": "編輯金鑰",
+      "description": "修改金鑰的名稱、過期時間和流量限制設定。",
+      "keyName": {
+        "label": "金鑰名稱",
+        "placeholder": "請輸入金鑰名稱",
+        "required": "金鑰名稱 *"
+      },
+      "expiresAt": {
+        "label": "過期時間",
+        "placeholder": "選擇過期時間",
+        "description": "留空表示永不過期"
+      },
+      "canLoginWebUi": {
+        "label": "允許登入 Web UI",
+        "description": "關閉後,此金鑰僅可用於 API 呼叫,無法登入管理後台"
+      },
+      "limit5hUsd": {
+        "label": "5小時消費上限 (USD)",
+        "placeholder": "留空表示無限制",
+        "description": "5小時內最大消費金額"
+      },
+      "limitWeeklyUsd": {
+        "label": "週消費上限 (USD)",
+        "placeholder": "留空表示無限制",
+        "description": "每週最大消費金額"
+      },
+      "limitMonthlyUsd": {
+        "label": "月消費上限 (USD)",
+        "placeholder": "留空表示無限制",
+        "description": "每月最大消費金額"
+      },
+      "limitConcurrentSessions": {
+        "label": "並發 Session 上限",
+        "placeholder": "0 表示無限制",
+        "description": "同時運行的對話數量"
+      },
+      "submitText": "儲存修改",
+      "loadingText": "儲存中...",
+      "success": "金鑰更新成功",
+      "error": "金鑰更新失敗",
+      "retryError": "儲存失敗,請稍後重試",
+      "keyInfoMissing": "金鑰資訊不存在"
     }
   }
 }

+ 29 - 26
src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx

@@ -9,6 +9,7 @@ import { Switch } from "@/components/ui/switch";
 import { useZodForm } from "@/lib/hooks/use-zod-form";
 import { KeyFormSchema } from "@/lib/validation/schemas";
 import { toast } from "sonner";
+import { useTranslations } from "next-intl";
 
 interface EditKeyFormProps {
   keyData?: {
@@ -27,6 +28,7 @@ interface EditKeyFormProps {
 export function EditKeyForm({ keyData, onSuccess }: EditKeyFormProps) {
   const [isPending, startTransition] = useTransition();
   const router = useRouter();
+  const t = useTranslations("quota.keys.editKeyForm");
 
   const formatExpiresAt = (expiresAt: string) => {
     if (!expiresAt || expiresAt === "永不过期") return "";
@@ -50,7 +52,7 @@ export function EditKeyForm({ keyData, onSuccess }: EditKeyFormProps) {
     },
     onSubmit: async (data) => {
       if (!keyData) {
-        throw new Error("密钥信息不存在");
+        throw new Error(t("keyInfoMissing"));
       }
 
       startTransition(async () => {
@@ -65,14 +67,15 @@ export function EditKeyForm({ keyData, onSuccess }: EditKeyFormProps) {
             limitConcurrentSessions: data.limitConcurrentSessions,
           });
           if (!res.ok) {
-            toast.error(res.error || "保存失败");
+            toast.error(res.error || t("error"));
             return;
           }
+          toast.success(t("success"));
           onSuccess?.();
           router.refresh();
         } catch (err) {
           console.error("编辑Key失败:", err);
-          toast.error("保存失败,请稍后重试");
+          toast.error(t("retryError"));
         }
       });
     },
@@ -81,10 +84,10 @@ export function EditKeyForm({ keyData, onSuccess }: EditKeyFormProps) {
   return (
     <DialogFormLayout
       config={{
-        title: "编辑 Key",
-        description: "修改密钥的名称、过期时间和限流配置。",
-        submitText: "保存修改",
-        loadingText: "保存中...",
+        title: t("title"),
+        description: t("description"),
+        submitText: t("submitText"),
+        loadingText: t("loadingText"),
       }}
       onSubmit={form.handleSubmit}
       isSubmitting={isPending}
@@ -92,28 +95,28 @@ export function EditKeyForm({ keyData, onSuccess }: EditKeyFormProps) {
       error={form.errors._form}
     >
       <TextField
-        label="Key名称"
+        label={t("keyName.label")}
         required
         maxLength={64}
         autoFocus
-        placeholder="请输入Key名称"
+        placeholder={t("keyName.placeholder")}
         {...form.getFieldProps("name")}
       />
 
       <DateField
-        label="过期时间"
-        placeholder="选择过期时间"
-        description="留空表示永不过期"
+        label={t("expiresAt.label")}
+        placeholder={t("expiresAt.placeholder")}
+        description={t("expiresAt.description")}
         {...form.getFieldProps("expiresAt")}
       />
 
       <div className="flex items-start justify-between gap-4 rounded-lg border border-dashed border-border px-4 py-3">
         <div>
           <Label htmlFor="can-login-web-ui" className="text-sm font-medium">
-            允许登录 Web UI
+            {t("canLoginWebUi.label")}
           </Label>
           <p className="text-xs text-muted-foreground mt-1">
-            关闭后,此 Key 仅可用于 API 调用,无法登录管理后台
+            {t("canLoginWebUi.description")}
           </p>
         </div>
         <Switch
@@ -124,36 +127,36 @@ export function EditKeyForm({ keyData, onSuccess }: EditKeyFormProps) {
       </div>
 
       <NumberField
-        label="5小时消费上限 (USD)"
-        placeholder="留空表示无限制"
-        description="5小时内最大消费金额"
+        label={t("limit5hUsd.label")}
+        placeholder={t("limit5hUsd.placeholder")}
+        description={t("limit5hUsd.description")}
         min={0}
         step={0.01}
         {...form.getFieldProps("limit5hUsd")}
       />
 
       <NumberField
-        label="周消费上限 (USD)"
-        placeholder="留空表示无限制"
-        description="每周最大消费金额"
+        label={t("limitWeeklyUsd.label")}
+        placeholder={t("limitWeeklyUsd.placeholder")}
+        description={t("limitWeeklyUsd.description")}
         min={0}
         step={0.01}
         {...form.getFieldProps("limitWeeklyUsd")}
       />
 
       <NumberField
-        label="月消费上限 (USD)"
-        placeholder="留空表示无限制"
-        description="每月最大消费金额"
+        label={t("limitMonthlyUsd.label")}
+        placeholder={t("limitMonthlyUsd.placeholder")}
+        description={t("limitMonthlyUsd.description")}
         min={0}
         step={0.01}
         {...form.getFieldProps("limitMonthlyUsd")}
       />
 
       <NumberField
-        label="并发 Session 上限"
-        placeholder="0 表示无限制"
-        description="同时运行的对话数量"
+        label={t("limitConcurrentSessions.label")}
+        placeholder={t("limitConcurrentSessions.placeholder")}
+        description={t("limitConcurrentSessions.description")}
         min={0}
         step={1}
         {...form.getFieldProps("limitConcurrentSessions")}

+ 40 - 30
src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx

@@ -18,6 +18,7 @@ import { Settings, Loader2 } from "lucide-react";
 import { editKey } from "@/actions/keys";
 import { toast } from "sonner";
 import { type CurrencyCode, CURRENCY_CONFIG } from "@/lib/utils/currency";
+import { useTranslations } from "next-intl";
 
 interface KeyQuota {
   cost5h: { current: number; limit: number | null };
@@ -46,6 +47,7 @@ export function EditKeyQuotaDialog({
   const router = useRouter();
   const [isPending, startTransition] = useTransition();
   const [open, setOpen] = useState(false);
+  const t = useTranslations("quota.keys.editDialog");
 
   const currencySymbol = CURRENCY_CONFIG[currencyCode].symbol;
 
@@ -76,14 +78,14 @@ export function EditKeyQuotaDialog({
         });
 
         if (result.ok) {
-          toast.success("限额设置成功");
+          toast.success(t("success"));
           setOpen(false);
           router.refresh();
         } else {
-          toast.error(result.error || "设置失败");
+          toast.error(result.error || t("error"));
         }
       } catch (error) {
-        toast.error("设置失败,请稍后重试");
+        toast.error(t("retryError"));
         console.error(error);
       }
     });
@@ -101,14 +103,14 @@ export function EditKeyQuotaDialog({
         });
 
         if (result.ok) {
-          toast.success("限额已清除");
+          toast.success(t("clearSuccess"));
           setOpen(false);
           router.refresh();
         } else {
-          toast.error(result.error || "清除失败");
+          toast.error(result.error || t("clearError"));
         }
       } catch (error) {
-        toast.error("清除失败,请稍后重试");
+        toast.error(t("retryError"));
         console.error(error);
       }
     });
@@ -120,98 +122,106 @@ export function EditKeyQuotaDialog({
         {trigger || (
           <Button variant="outline" size="sm">
             <Settings className="h-4 w-4" />
-            <span className="ml-2">设置限额</span>
+            <span className="ml-2">{t("setQuota")}</span>
           </Button>
         )}
       </DialogTrigger>
       <DialogContent className="sm:max-w-[500px]">
         <form onSubmit={handleSubmit}>
           <DialogHeader>
-            <DialogTitle>设置密钥限额</DialogTitle>
+            <DialogTitle>{t("title")}</DialogTitle>
             <DialogDescription>
-              密钥: {keyName} ({userName})
+              {t("description", { keyName, userName })}
             </DialogDescription>
           </DialogHeader>
 
           <div className="grid gap-4 py-4">
             {/* 5小时限额 */}
             <div className="grid gap-2">
-              <Label htmlFor="limit5h">5小时限额(USD)</Label>
+              <Label htmlFor="limit5h">{t("cost5h.label")}</Label>
               <Input
                 id="limit5h"
                 type="number"
                 step="0.01"
                 min="0"
-                placeholder="不限制"
+                placeholder={t("cost5h.placeholder")}
                 value={limit5h}
                 onChange={(e) => setLimit5h(e.target.value)}
               />
               {currentQuota?.cost5h.limit && (
                 <p className="text-xs text-muted-foreground">
-                  当前已用: {currencySymbol}
-                  {currentQuota.cost5h.current.toFixed(4)} / {currencySymbol}
-                  {currentQuota.cost5h.limit.toFixed(2)}
+                  {t("cost5h.current", {
+                    currency: currencySymbol,
+                    current: currentQuota.cost5h.current.toFixed(4),
+                    limit: currentQuota.cost5h.limit.toFixed(2),
+                  })}
                 </p>
               )}
             </div>
 
             {/* 周限额 */}
             <div className="grid gap-2">
-              <Label htmlFor="limitWeekly">周限额(USD)</Label>
+              <Label htmlFor="limitWeekly">{t("costWeekly.label")}</Label>
               <Input
                 id="limitWeekly"
                 type="number"
                 step="0.01"
                 min="0"
-                placeholder="不限制"
+                placeholder={t("costWeekly.placeholder")}
                 value={limitWeekly}
                 onChange={(e) => setLimitWeekly(e.target.value)}
               />
               {currentQuota?.costWeekly.limit && (
                 <p className="text-xs text-muted-foreground">
-                  当前已用: {currencySymbol}
-                  {currentQuota.costWeekly.current.toFixed(4)} / {currencySymbol}
-                  {currentQuota.costWeekly.limit.toFixed(2)}
+                  {t("costWeekly.current", {
+                    currency: currencySymbol,
+                    current: currentQuota.costWeekly.current.toFixed(4),
+                    limit: currentQuota.costWeekly.limit.toFixed(2),
+                  })}
                 </p>
               )}
             </div>
 
             {/* 月限额 */}
             <div className="grid gap-2">
-              <Label htmlFor="limitMonthly">月限额(USD)</Label>
+              <Label htmlFor="limitMonthly">{t("costMonthly.label")}</Label>
               <Input
                 id="limitMonthly"
                 type="number"
                 step="0.01"
                 min="0"
-                placeholder="不限制"
+                placeholder={t("costMonthly.placeholder")}
                 value={limitMonthly}
                 onChange={(e) => setLimitMonthly(e.target.value)}
               />
               {currentQuota?.costMonthly.limit && (
                 <p className="text-xs text-muted-foreground">
-                  当前已用: {currencySymbol}
-                  {currentQuota.costMonthly.current.toFixed(4)} / {currencySymbol}
-                  {currentQuota.costMonthly.limit.toFixed(2)}
+                  {t("costMonthly.current", {
+                    currency: currencySymbol,
+                    current: currentQuota.costMonthly.current.toFixed(4),
+                    limit: currentQuota.costMonthly.limit.toFixed(2),
+                  })}
                 </p>
               )}
             </div>
 
             {/* 并发限额 */}
             <div className="grid gap-2">
-              <Label htmlFor="limitConcurrent">并发 Session 限额</Label>
+              <Label htmlFor="limitConcurrent">{t("concurrentSessions.label")}</Label>
               <Input
                 id="limitConcurrent"
                 type="number"
                 min="0"
-                placeholder="0 = 不限制"
+                placeholder={t("concurrentSessions.placeholder")}
                 value={limitConcurrent}
                 onChange={(e) => setLimitConcurrent(e.target.value)}
               />
               {currentQuota && currentQuota.concurrentSessions.limit > 0 && (
                 <p className="text-xs text-muted-foreground">
-                  当前并发: {currentQuota.concurrentSessions.current} /{" "}
-                  {currentQuota.concurrentSessions.limit}
+                  {t("concurrentSessions.current", {
+                    current: currentQuota.concurrentSessions.current,
+                    limit: currentQuota.concurrentSessions.limit,
+                  })}
                 </p>
               )}
             </div>
@@ -228,12 +238,12 @@ export function EditKeyQuotaDialog({
                 onClick={handleClearQuota}
                 disabled={isPending}
               >
-                清除所有限额
+                {t("clearAll")}
               </Button>
             )}
             <Button type="submit" disabled={isPending}>
               {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
-              保存
+              {t("save")}
             </Button>
           </DialogFooter>
         </form>

+ 22 - 15
src/app/[locale]/dashboard/quotas/keys/_components/edit-user-quota-dialog.tsx

@@ -18,6 +18,7 @@ import { Settings, Loader2 } from "lucide-react";
 import { editUser } from "@/actions/users";
 import { toast } from "sonner";
 import { type CurrencyCode, CURRENCY_CONFIG } from "@/lib/utils/currency";
+import { useTranslations } from "next-intl";
 
 interface UserQuota {
   rpm: { current: number; limit: number; window: "per_minute" };
@@ -42,6 +43,7 @@ export function EditUserQuotaDialog({
   const router = useRouter();
   const [isPending, startTransition] = useTransition();
   const [open, setOpen] = useState(false);
+  const t = useTranslations("quota.keys.editUserDialog");
 
   const currencySymbol = CURRENCY_CONFIG[currencyCode].symbol;
 
@@ -62,14 +64,14 @@ export function EditUserQuotaDialog({
         });
 
         if (result.ok) {
-          toast.success("用户限额设置成功");
+          toast.success(t("success"));
           setOpen(false);
           router.refresh();
         } else {
-          toast.error(result.error || "设置失败");
+          toast.error(result.error || t("error"));
         }
       } catch (error) {
-        toast.error("设置失败,请稍后重试");
+        toast.error(t("retryError"));
         console.error(error);
       }
     });
@@ -81,55 +83,60 @@ export function EditUserQuotaDialog({
         {trigger || (
           <Button variant="outline" size="sm">
             <Settings className="h-4 w-4" />
-            <span className="ml-2">编辑限额</span>
+            <span className="ml-2">{t("editQuota")}</span>
           </Button>
         )}
       </DialogTrigger>
       <DialogContent className="sm:max-w-[500px]">
         <form onSubmit={handleSubmit}>
           <DialogHeader>
-            <DialogTitle>设置用户限额</DialogTitle>
-            <DialogDescription>用户: {userName}</DialogDescription>
+            <DialogTitle>{t("title")}</DialogTitle>
+            <DialogDescription>{t("description", { userName })}</DialogDescription>
           </DialogHeader>
 
           <div className="grid gap-4 py-4">
             {/* RPM 限制 */}
             <div className="grid gap-2">
-              <Label htmlFor="rpmLimit">每分钟请求数 (RPM)</Label>
+              <Label htmlFor="rpmLimit">{t("rpm.label")}</Label>
               <Input
                 id="rpmLimit"
                 type="number"
                 min="1"
-                placeholder="60"
+                placeholder={t("rpm.placeholder")}
                 value={rpmLimit}
                 onChange={(e) => setRpmLimit(e.target.value)}
                 required
               />
               {currentQuota && (
                 <p className="text-xs text-muted-foreground">
-                  当前: {currentQuota.rpm.current} / {currentQuota.rpm.limit} 请求/分钟
+                  {t("rpm.current", {
+                    current: currentQuota.rpm.current,
+                    limit: currentQuota.rpm.limit,
+                  })}
                 </p>
               )}
             </div>
 
             {/* 每日消费限额 */}
             <div className="grid gap-2">
-              <Label htmlFor="dailyQuota">每日消费限额(USD)</Label>
+              <Label htmlFor="dailyQuota">{t("dailyQuota.label")}</Label>
               <Input
                 id="dailyQuota"
                 type="number"
                 step="0.01"
                 min="0"
-                placeholder="100"
+                placeholder={t("dailyQuota.placeholder")}
                 value={dailyQuota}
                 onChange={(e) => setDailyQuota(e.target.value)}
                 required
               />
               {currentQuota && (
                 <p className="text-xs text-muted-foreground">
-                  今日已用: {currencySymbol}
-                  {currentQuota.dailyCost.current.toFixed(4)} / {currencySymbol}
-                  {currentQuota.dailyCost.limit.toFixed(2)}
+                  {t("dailyQuota.current", {
+                    currency: currencySymbol,
+                    current: currentQuota.dailyCost.current.toFixed(4),
+                    limit: currentQuota.dailyCost.limit.toFixed(2),
+                  })}
                 </p>
               )}
             </div>
@@ -138,7 +145,7 @@ export function EditUserQuotaDialog({
           <DialogFooter>
             <Button type="submit" disabled={isPending}>
               {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
-              保存
+              {t("save")}
             </Button>
           </DialogFooter>
         </form>

+ 10 - 8
src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-manager.tsx

@@ -13,6 +13,7 @@ import {
 import { Search } from "lucide-react";
 import type { CurrencyCode } from "@/lib/utils/currency";
 import { hasKeyQuotaSet, isWarning, isExceeded } from "@/lib/utils/quota-helpers";
+import { useTranslations } from "next-intl";
 
 interface KeyQuota {
   cost5h: { current: number; limit: number | null };
@@ -48,6 +49,7 @@ interface KeysQuotaManagerProps {
 }
 
 export function KeysQuotaManager({ users, currencyCode = "USD" }: KeysQuotaManagerProps) {
+  const t = useTranslations("quota.keys");
   const [searchQuery, setSearchQuery] = useState("");
   const [filter, setFilter] = useState<
     "all" | "key-quota" | "user-quota-only" | "warning" | "exceeded"
@@ -107,7 +109,7 @@ export function KeysQuotaManager({ users, currencyCode = "USD" }: KeysQuotaManag
         <div className="relative flex-1 max-w-sm">
           <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
           <Input
-            placeholder="搜索用户或密钥..."
+            placeholder={t("searchPlaceholder")}
             value={searchQuery}
             onChange={(e) => setSearchQuery(e.target.value)}
             className="pl-9"
@@ -117,20 +119,20 @@ export function KeysQuotaManager({ users, currencyCode = "USD" }: KeysQuotaManag
         {/* 筛选器 */}
         <Select value={filter} onValueChange={(value: typeof filter) => setFilter(value)}>
           <SelectTrigger className="w-[180px]">
-            <SelectValue placeholder="筛选条件" />
+            <SelectValue placeholder={t("filterLabel")} />
           </SelectTrigger>
           <SelectContent>
-            <SelectItem value="all">全部密钥</SelectItem>
-            <SelectItem value="key-quota">仅密钥限额</SelectItem>
-            <SelectItem value="user-quota-only">仅用户限额</SelectItem>
-            <SelectItem value="warning">预警(≥60%)</SelectItem>
-            <SelectItem value="exceeded">超限(≥100%)</SelectItem>
+            <SelectItem value="all">{t("filter.all")}</SelectItem>
+            <SelectItem value="key-quota">{t("filter.keyQuota")}</SelectItem>
+            <SelectItem value="user-quota-only">{t("filter.userQuotaOnly")}</SelectItem>
+            <SelectItem value="warning">{t("filter.warning")}</SelectItem>
+            <SelectItem value="exceeded">{t("filter.exceeded")}</SelectItem>
           </SelectContent>
         </Select>
 
         {/* 统计信息 */}
         <div className="text-sm text-muted-foreground ml-auto">
-          显示 {totalFilteredUsers} 个用户,{totalFilteredKeys} 个密钥
+          {t("filterCount", { users: totalFilteredUsers, keys: totalFilteredKeys })}
         </div>
       </div>
 

+ 43 - 40
src/app/[locale]/dashboard/sessions/[sessionId]/messages/page.tsx

@@ -12,11 +12,12 @@ import { Section } from "@/components/section";
 import { Badge } from "@/components/ui/badge";
 import { useQuery } from "@tanstack/react-query";
 import { formatCurrency, type CurrencyCode } from "@/lib/utils/currency";
+import { useTranslations } from "next-intl";
 
 async function fetchSystemSettings(): Promise<{ currencyDisplay: CurrencyCode }> {
   const response = await fetch("/api/system-settings");
   if (!response.ok) {
-    throw new Error("获取系统设置失败");
+    throw new Error("Failed to fetch system settings");
   }
   return response.json();
 }
@@ -26,6 +27,8 @@ async function fetchSystemSettings(): Promise<{ currencyDisplay: CurrencyCode }>
  * 双栏布局:左侧完整内容 + 右侧信息卡片
  */
 export default function SessionMessagesPage() {
+  const t = useTranslations("dashboard.sessions");
+  const tDesc = useTranslations("dashboard.description");
   const params = useParams();
   const router = useRouter();
   const sessionId = params.sessionId as string;
@@ -60,17 +63,17 @@ export default function SessionMessagesPage() {
           setResponse(result.data.response);
           setSessionStats(result.data.sessionStats);
         } else {
-          setError(result.error || "获取失败");
+          setError(result.error || t("status.fetchFailed"));
         }
       } catch (err) {
-        setError(err instanceof Error ? err.message : "未知错误");
+        setError(err instanceof Error ? err.message : t("status.unknownError"));
       } finally {
         setIsLoading(false);
       }
     };
 
     void fetchDetails();
-  }, [sessionId]);
+  }, [sessionId, t]);
 
   const handleCopyMessages = async () => {
     if (!messages) return;
@@ -80,7 +83,7 @@ export default function SessionMessagesPage() {
       setCopiedMessages(true);
       setTimeout(() => setCopiedMessages(false), 2000);
     } catch (err) {
-      console.error("复制失败:", err);
+      console.error(t("errors.copyFailed"), err);
     }
   };
 
@@ -92,7 +95,7 @@ export default function SessionMessagesPage() {
       setCopiedResponse(true);
       setTimeout(() => setCopiedResponse(false), 2000);
     } catch (err) {
-      console.error("复制失败:", err);
+      console.error(t("errors.copyFailed"), err);
     }
   };
 
@@ -135,10 +138,10 @@ export default function SessionMessagesPage() {
         <div className="flex items-center gap-4">
           <Button variant="outline" size="sm" onClick={() => router.back()}>
             <ArrowLeft className="h-4 w-4 mr-2" />
-            返回
+            {t("actions.back")}
           </Button>
           <div>
-            <h1 className="text-2xl font-bold">Session Messages</h1>
+            <h1 className="text-2xl font-bold">{t("details.title")}</h1>
             <p className="text-sm text-muted-foreground font-mono mt-1">{sessionId}</p>
           </div>
         </div>
@@ -155,18 +158,18 @@ export default function SessionMessagesPage() {
               {copiedMessages ? (
                 <>
                   <Check className="h-4 w-4 mr-2" />
-                  已复制
+                  {t("actions.copied")}
                 </>
               ) : (
                 <>
                   <Copy className="h-4 w-4 mr-2" />
-                  复制 Messages
+                  {t("actions.copyMessages")}
                 </>
               )}
             </Button>
             <Button variant="outline" size="sm" onClick={handleDownload}>
               <Download className="h-4 w-4 mr-2" />
-              下载 Messages
+              {t("actions.downloadMessages")}
             </Button>
           </div>
         )}
@@ -174,7 +177,7 @@ export default function SessionMessagesPage() {
 
       {/* 内容区域 */}
       {isLoading ? (
-        <div className="text-center py-16 text-muted-foreground">加载中...</div>
+        <div className="text-center py-16 text-muted-foreground">{t("status.loading")}</div>
       ) : error ? (
         <div className="text-center py-16">
           <div className="text-destructive text-lg mb-2">{error}</div>
@@ -185,7 +188,7 @@ export default function SessionMessagesPage() {
           <div className="lg:col-span-2 space-y-6">
             {/* User-Agent 信息 */}
             {sessionStats?.userAgent && (
-              <Section title="客户端信息" description="User-Agent 请求头">
+              <Section title={t("details.clientInfo")} description={tDesc("clientInfo")}>
                 <>
                   <div className="rounded-md border bg-muted/50 p-4">
                     <div className="flex items-start gap-3">
@@ -199,7 +202,7 @@ export default function SessionMessagesPage() {
 
             {/* Messages 数据 */}
             {messages !== null && (
-              <Section title="请求 Messages" description="客户端发送的消息内容">
+              <Section title={t("details.requestMessages")} description={t("details.requestMessagesDescription")}>
                 <>
                   <div className="rounded-md border bg-muted/50 p-6">
                     <pre className="text-xs overflow-x-auto whitespace-pre-wrap break-words font-mono">
@@ -213,8 +216,8 @@ export default function SessionMessagesPage() {
             {/* Response Body */}
             {response !== null && (
               <Section
-                title="响应体内容"
-                description="服务器返回的完整响应(5分钟 TTL)"
+                title={t("details.responseBody")}
+                description={t("details.responseBodyDescription")}
                 actions={
                   <Button
                     variant="ghost"
@@ -225,12 +228,12 @@ export default function SessionMessagesPage() {
                     {copiedResponse ? (
                       <>
                         <Check className="h-4 w-4 mr-2" />
-                        已复制
+                        {t("actions.copied")}
                       </>
                     ) : (
                       <>
                         <Copy className="h-4 w-4 mr-2" />
-                        复制响应体
+                        {t("actions.copyResponse")}
                       </>
                     )}
                   </Button>
@@ -249,9 +252,9 @@ export default function SessionMessagesPage() {
             {/* 无数据提示 */}
             {!sessionStats?.userAgent && !messages && !response && (
               <div className="text-center py-16">
-                <div className="text-muted-foreground text-lg mb-2">暂无详细数据</div>
+                <div className="text-muted-foreground text-lg mb-2">{t("details.noDetailedData")}</div>
                 <p className="text-sm text-muted-foreground">
-                  提示:请设置环境变量 STORE_SESSION_MESSAGES=true 以启用 messages 和 response 存储
+                  {t("details.storageTip")}
                 </p>
               </div>
             )}
@@ -263,13 +266,13 @@ export default function SessionMessagesPage() {
               {/* Session 概览卡片 */}
               <Card>
                 <CardHeader>
-                  <CardTitle className="text-base">Session 概览</CardTitle>
-                  <CardDescription>聚合统计信息</CardDescription>
+                  <CardTitle className="text-base">{t("details.overview")}</CardTitle>
+                  <CardDescription>{t("details.overviewDescription")}</CardDescription>
                 </CardHeader>
                 <CardContent className="space-y-3">
                   {/* 请求数量 */}
                   <div className="flex items-center justify-between">
-                    <span className="text-sm text-muted-foreground">总请求数</span>
+                    <span className="text-sm text-muted-foreground">{t("details.totalRequests")}</span>
                     <Badge variant="secondary" className="font-mono font-semibold">
                       <Hash className="h-3 w-3 mr-1" />
                       {sessionStats.requestCount}
@@ -282,13 +285,13 @@ export default function SessionMessagesPage() {
                       <div className="border-t my-3" />
                       <div className="flex flex-col gap-2">
                         <div className="flex items-center justify-between">
-                          <span className="text-sm text-muted-foreground">首次请求</span>
+                          <span className="text-sm text-muted-foreground">{t("details.firstRequest")}</span>
                           <code className="text-xs font-mono">
                             {new Date(sessionStats.firstRequestAt).toLocaleString("zh-CN")}
                           </code>
                         </div>
                         <div className="flex items-center justify-between">
-                          <span className="text-sm text-muted-foreground">最后请求</span>
+                          <span className="text-sm text-muted-foreground">{t("details.lastRequest")}</span>
                           <code className="text-xs font-mono">
                             {new Date(sessionStats.lastRequestAt).toLocaleString("zh-CN")}
                           </code>
@@ -302,7 +305,7 @@ export default function SessionMessagesPage() {
                     <>
                       <div className="border-t my-3" />
                       <div className="flex items-center justify-between">
-                        <span className="text-sm text-muted-foreground">总耗时</span>
+                        <span className="text-sm text-muted-foreground">{t("details.totalDuration")}</span>
                         <code className="text-sm font-mono font-semibold">
                           {sessionStats.totalDurationMs < 1000
                             ? `${sessionStats.totalDurationMs}ms`
@@ -317,14 +320,14 @@ export default function SessionMessagesPage() {
               {/* 供应商和模型卡片 */}
               <Card>
                 <CardHeader>
-                  <CardTitle className="text-base">供应商与模型</CardTitle>
-                  <CardDescription>使用的提供商和模型</CardDescription>
+                  <CardTitle className="text-base">{t("details.providersAndModels")}</CardTitle>
+                  <CardDescription>{t("details.providersAndModelsDescription")}</CardDescription>
                 </CardHeader>
                 <CardContent className="space-y-3">
                   {/* 供应商列表 */}
                   {sessionStats.providers.length > 0 && (
                     <div className="flex flex-col gap-2">
-                      <span className="text-sm text-muted-foreground">供应商</span>
+                      <span className="text-sm text-muted-foreground">{t("details.providers")}</span>
                       <div className="flex flex-wrap gap-2">
                         {sessionStats.providers.map((provider: { id: number; name: string }) => (
                           <Badge key={provider.id} variant="outline" className="text-xs">
@@ -340,7 +343,7 @@ export default function SessionMessagesPage() {
                     <>
                       <div className="border-t my-3" />
                       <div className="flex flex-col gap-2">
-                        <span className="text-sm text-muted-foreground">模型</span>
+                        <span className="text-sm text-muted-foreground">{t("details.models")}</span>
                         <div className="flex flex-wrap gap-2">
                           {sessionStats.models.map((model: string, idx: number) => (
                             <Badge key={idx} variant="secondary" className="text-xs font-mono">
@@ -357,13 +360,13 @@ export default function SessionMessagesPage() {
               {/* Token 使用卡片 */}
               <Card>
                 <CardHeader>
-                  <CardTitle className="text-base">Token 使用(总量)</CardTitle>
-                  <CardDescription>所有请求的累计统计</CardDescription>
+                  <CardTitle className="text-base">{t("details.tokenUsage")}</CardTitle>
+                  <CardDescription>{t("details.tokenUsageDescription")}</CardDescription>
                 </CardHeader>
                 <CardContent className="space-y-3">
                   {sessionStats.totalInputTokens > 0 && (
                     <div className="flex items-center justify-between">
-                      <span className="text-sm text-muted-foreground">总输入</span>
+                      <span className="text-sm text-muted-foreground">{t("details.totalInput")}</span>
                       <code className="text-sm font-mono">
                         {sessionStats.totalInputTokens.toLocaleString()}
                       </code>
@@ -372,7 +375,7 @@ export default function SessionMessagesPage() {
 
                   {sessionStats.totalOutputTokens > 0 && (
                     <div className="flex items-center justify-between">
-                      <span className="text-sm text-muted-foreground">总输出</span>
+                      <span className="text-sm text-muted-foreground">{t("details.totalOutput")}</span>
                       <code className="text-sm font-mono">
                         {sessionStats.totalOutputTokens.toLocaleString()}
                       </code>
@@ -381,7 +384,7 @@ export default function SessionMessagesPage() {
 
                   {sessionStats.totalCacheCreationTokens > 0 && (
                     <div className="flex items-center justify-between">
-                      <span className="text-sm text-muted-foreground">缓存创建</span>
+                      <span className="text-sm text-muted-foreground">{t("details.cacheCreation")}</span>
                       <code className="text-sm font-mono">
                         {sessionStats.totalCacheCreationTokens.toLocaleString()}
                       </code>
@@ -390,7 +393,7 @@ export default function SessionMessagesPage() {
 
                   {sessionStats.totalCacheReadTokens > 0 && (
                     <div className="flex items-center justify-between">
-                      <span className="text-sm text-muted-foreground">缓存读取</span>
+                      <span className="text-sm text-muted-foreground">{t("details.cacheRead")}</span>
                       <code className="text-sm font-mono">
                         {sessionStats.totalCacheReadTokens.toLocaleString()}
                       </code>
@@ -401,7 +404,7 @@ export default function SessionMessagesPage() {
                     <>
                       <div className="border-t my-3" />
                       <div className="flex items-center justify-between">
-                        <span className="text-sm font-semibold">总计</span>
+                        <span className="text-sm font-semibold">{t("details.total")}</span>
                         <code className="text-sm font-mono font-semibold">
                           {totalTokens.toLocaleString()}
                         </code>
@@ -415,12 +418,12 @@ export default function SessionMessagesPage() {
               {sessionStats.totalCostUsd && parseFloat(sessionStats.totalCostUsd) > 0 && (
                 <Card>
                   <CardHeader>
-                    <CardTitle className="text-base">成本信息(总计)</CardTitle>
-                    <CardDescription>所有请求的累计费用</CardDescription>
+                    <CardTitle className="text-base">{t("details.costInfo")}</CardTitle>
+                    <CardDescription>{t("details.costInfoDescription")}</CardDescription>
                   </CardHeader>
                   <CardContent className="space-y-3">
                     <div className="flex items-center justify-between">
-                      <span className="text-sm text-muted-foreground">总费用</span>
+                      <span className="text-sm text-muted-foreground">{t("details.totalFee")}</span>
                       <code className="text-lg font-mono font-semibold text-green-600">
                         {formatCurrency(sessionStats.totalCostUsd, currencyCode, 6)}
                       </code>

+ 19 - 16
src/app/[locale]/dashboard/sessions/_components/active-sessions-table.tsx

@@ -16,6 +16,7 @@ import { cn } from "@/lib/utils";
 import type { ActiveSessionInfo } from "@/types/session";
 import type { CurrencyCode } from "@/lib/utils/currency";
 import { formatCurrency } from "@/lib/utils/currency";
+import { useTranslations } from "next-intl";
 
 interface ActiveSessionsTableProps {
   sessions: ActiveSessionInfo[];
@@ -44,6 +45,8 @@ export function ActiveSessionsTable({
   inactive = false,
   currencyCode = "USD",
 }: ActiveSessionsTableProps) {
+  const t = useTranslations("dashboard.sessions");
+
   // 按开始时间降序排序(最新的在前)
   const sortedSessions = [...sessions].sort((a, b) => b.startTime - a.startTime);
 
@@ -51,10 +54,10 @@ export function ActiveSessionsTable({
     <div className="space-y-4">
       <div className="flex items-center justify-between">
         <div className="text-sm text-muted-foreground">
-          共 {sessions.length} 个{inactive ? "非活跃" : "活跃"} Session
-          {inactive && <span className="ml-2 text-xs">(不计入并发数)</span>}
+          {t("table.count", { count: sessions.length, type: t(inactive ? "table.inactive" : "table.active") })}
+          {inactive && <span className="ml-2 text-xs">{t("table.notCountedInConcurrency")}</span>}
         </div>
-        {isLoading && <div className="text-sm text-muted-foreground animate-pulse">刷新中...</div>}
+        {isLoading && <div className="text-sm text-muted-foreground animate-pulse">{t("table.refreshing")}</div>}
       </div>
 
       <div
@@ -66,24 +69,24 @@ export function ActiveSessionsTable({
         <Table>
           <TableHeader>
             <TableRow>
-              <TableHead>Session ID</TableHead>
-              <TableHead>用户</TableHead>
-              <TableHead>密钥</TableHead>
-              <TableHead>供应商</TableHead>
-              <TableHead>模型</TableHead>
-              <TableHead className="text-center">请求数</TableHead>
-              <TableHead className="text-right">总输入</TableHead>
-              <TableHead className="text-right">总输出</TableHead>
-              <TableHead className="text-right">总成本</TableHead>
-              <TableHead className="text-right">总耗时</TableHead>
-              <TableHead className="text-center">操作</TableHead>
+              <TableHead>{t("columns.sessionId")}</TableHead>
+              <TableHead>{t("columns.user")}</TableHead>
+              <TableHead>{t("columns.key")}</TableHead>
+              <TableHead>{t("columns.provider")}</TableHead>
+              <TableHead>{t("columns.model")}</TableHead>
+              <TableHead className="text-center">{t("columns.requestCount")}</TableHead>
+              <TableHead className="text-right">{t("columns.totalInput")}</TableHead>
+              <TableHead className="text-right">{t("columns.totalOutput")}</TableHead>
+              <TableHead className="text-right">{t("columns.totalCost")}</TableHead>
+              <TableHead className="text-right">{t("columns.totalDuration")}</TableHead>
+              <TableHead className="text-center">{t("columns.actions")}</TableHead>
             </TableRow>
           </TableHeader>
           <TableBody>
             {sortedSessions.length === 0 ? (
               <TableRow>
                 <TableCell colSpan={11} className="text-center text-muted-foreground">
-                  暂无活跃 Session
+                  {t("table.noActiveSessions")}
                 </TableCell>
               </TableRow>
             ) : (
@@ -127,7 +130,7 @@ export function ActiveSessionsTable({
                     <Link href={`/dashboard/sessions/${session.sessionId}/messages`}>
                       <Button variant="ghost" size="sm">
                         <Eye className="h-4 w-4 mr-1" />
-                        查看
+                        {t("actions.view")}
                       </Button>
                     </Link>
                   </TableCell>

+ 10 - 8
src/app/[locale]/dashboard/sessions/_components/session-messages-dialog.tsx

@@ -13,12 +13,14 @@ import {
 import { Eye } from "lucide-react";
 import { getSessionMessages } from "@/actions/active-sessions";
 import { useState } from "react";
+import { useTranslations } from "next-intl";
 
 interface SessionMessagesDialogProps {
   sessionId: string;
 }
 
 export function SessionMessagesDialog({ sessionId }: SessionMessagesDialogProps) {
+  const t = useTranslations("dashboard.sessions");
   const [isOpen, setIsOpen] = useState(false);
   const [messages, setMessages] = useState<unknown | null>(null);
   const [isLoading, setIsLoading] = useState(false);
@@ -34,10 +36,10 @@ export function SessionMessagesDialog({ sessionId }: SessionMessagesDialogProps)
       if (result.ok) {
         setMessages(result.data);
       } else {
-        setError(result.error || "获取失败");
+        setError(result.error || t("status.fetchFailed"));
       }
     } catch (err) {
-      setError(err instanceof Error ? err.message : "未知错误");
+      setError(err instanceof Error ? err.message : t("status.unknownError"));
     } finally {
       setIsLoading(false);
     }
@@ -63,24 +65,24 @@ export function SessionMessagesDialog({ sessionId }: SessionMessagesDialogProps)
       <DialogTrigger asChild>
         <Button variant="ghost" size="sm">
           <Eye className="h-4 w-4 mr-1" />
-          查看
+          {t("actions.view")}
         </Button>
       </DialogTrigger>
       <DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
         <DialogHeader>
-          <DialogTitle>Session Messages</DialogTitle>
+          <DialogTitle>{t("details.title")}</DialogTitle>
           <DialogDescription className="font-mono text-xs">{sessionId}</DialogDescription>
         </DialogHeader>
 
         <div className="space-y-4">
           {isLoading ? (
-            <div className="text-center py-8 text-muted-foreground">加载中...</div>
+            <div className="text-center py-8 text-muted-foreground">{t("status.loading")}</div>
           ) : error ? (
             <div className="text-center py-8 text-destructive">
               {error}
-              {error.includes("未存储") && (
+              {error.includes(t("status.storageNotEnabled")) && (
                 <p className="text-sm text-muted-foreground mt-2">
-                  提示:请设置环境变量 STORE_SESSION_MESSAGES=true 以启用 messages 存储
+                  {t("status.storageNotEnabledHint")}
                 </p>
               )}
             </div>
@@ -89,7 +91,7 @@ export function SessionMessagesDialog({ sessionId }: SessionMessagesDialogProps)
               <pre className="text-xs overflow-x-auto">{JSON.stringify(messages, null, 2)}</pre>
             </div>
           ) : (
-            <div className="text-center py-8 text-muted-foreground">暂无数据</div>
+            <div className="text-center py-8 text-muted-foreground">{t("details.noData")}</div>
           )}
         </div>
       </DialogContent>