Преглед изворни кода

feat(i18n): enhance translations for user management and settings

- Added new translation keys for user management features, including user actions and key actions, across multiple languages.
- Updated existing translations in dashboard and settings JSON files for improved clarity and consistency.
- Enhanced dynamic translation usage in user-related components to provide a better user experience.
- Improved error handling and localization in forms and dialogs related to user and key management.
ding113 пре 3 месеци
родитељ
комит
111227423c
35 измењених фајлова са 2634 додато и 1078 уклоњено
  1. 0 4
      messages/en/customs.json
  2. 155 0
      messages/en/dashboard.json
  3. 256 152
      messages/en/settings.json
  4. 0 4
      messages/ja/customs.json
  5. 155 0
      messages/ja/dashboard.json
  6. 196 155
      messages/ja/settings.json
  7. 337 0
      messages/provider-i18n-patch.json
  8. 0 4
      messages/ru/customs.json
  9. 155 0
      messages/ru/dashboard.json
  10. 200 159
      messages/ru/settings.json
  11. 0 4
      messages/zh-CN/customs.json
  12. 155 0
      messages/zh-CN/dashboard.json
  13. 204 27
      messages/zh-CN/settings.json
  14. 0 4
      messages/zh-TW/customs.json
  15. 155 0
      messages/zh-TW/dashboard.json
  16. 189 148
      messages/zh-TW/settings.json
  17. 4 1
      src/app/[locale]/dashboard/_components/user/add-user-dialog.tsx
  18. 29 29
      src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  19. 8 10
      src/app/[locale]/dashboard/_components/user/forms/delete-key-confirm.tsx
  20. 21 18
      src/app/[locale]/dashboard/_components/user/forms/user-form.tsx
  21. 6 4
      src/app/[locale]/dashboard/_components/user/key-actions.tsx
  22. 11 9
      src/app/[locale]/dashboard/_components/user/key-limit-usage.tsx
  23. 57 44
      src/app/[locale]/dashboard/_components/user/key-list-header.tsx
  24. 6 4
      src/app/[locale]/dashboard/_components/user/user-actions.tsx
  25. 8 5
      src/app/[locale]/dashboard/_components/user/user-list.tsx
  26. 16 14
      src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx
  27. 39 28
      src/app/[locale]/settings/prices/_components/price-list.tsx
  28. 41 33
      src/app/[locale]/settings/prices/_components/upload-price-dialog.tsx
  29. 8 3
      src/app/[locale]/settings/providers/_components/provider-list-item.legacy.tsx
  30. 5 2
      src/app/[locale]/settings/providers/_components/provider-list.tsx
  31. 47 37
      src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx
  32. 15 4
      src/app/[locale]/settings/providers/_components/provider-type-filter.tsx
  33. 150 155
      src/app/[locale]/settings/providers/_components/scheduling-rules-dialog.tsx
  34. 0 4
      src/components/customs/overview-panel.tsx
  35. 6 13
      src/lib/provider-type-utils.tsx

+ 0 - 4
messages/en/customs.json

@@ -30,15 +30,11 @@
     "todayRequests": "Today's Requests",
     "todayCost": "Today's Cost",
     "avgResponse": "Average Response Time",
-    "requestsDescription": "Number of requests in the last 24 hours",
-    "costDescription": "Total cost in the last 24 hours",
-    "responseDescription": "Average response time (milliseconds)",
     "viewDetails": "View Details"
   },
   "activeSessions": {
     "title": "Active Sessions",
     "summary": "{count} sessions in the last {minutes} minutes",
-    "recent": "Recently Active",
     "empty": "No active sessions",
     "viewAll": "View All"
   }

+ 155 - 0
messages/en/dashboard.json

@@ -423,5 +423,160 @@
       "title": "No Keys",
       "description": "Click the \"Add Key\" button in the top right corner to add a key"
     }
+  },
+  "userList": {
+    "badge": "{count} Keys",
+    "activeKeys": "Active Keys",
+    "totalKeys": "Total Keys",
+    "emptyState": {
+      "title": "No Users",
+      "description": "Click the button below to create your first user"
+    },
+    "addUser": "Add User"
+  },
+  "keyListHeader": {
+    "todayUsage": "Today's Usage",
+    "proxyStatus": {
+      "loading": "Loading proxy status",
+      "fetchFailed": "Failed to fetch proxy status",
+      "noStatus": "No proxy status",
+      "activeRequests": "Active Requests",
+      "lastRequest": "Last Request",
+      "noRecord": "No record",
+      "timeAgo": {
+        "justNow": "Just now",
+        "secondsAgo": "{count}s ago",
+        "minutesAgo": "{count} min ago",
+        "hoursAgo": "{count}h ago",
+        "daysAgo": "{count}d ago"
+      }
+    },
+    "addKey": "Add Key",
+    "keyCreatedDialog": {
+      "title": "Key Created Successfully",
+      "description": "Your API key has been created successfully. Please copy and save it securely, as it will only be displayed once.",
+      "apiKeyLabel": "API Key",
+      "warningText": "Please copy and save before closing. You will not be able to view this key again after closing.",
+      "closeButton": "Close"
+    }
+  },
+  "keyLimitUsage": {
+    "loading": "Loading...",
+    "error": "Fetch failed",
+    "networkError": "Network error",
+    "cost5h": "5-Hour Cost",
+    "costWeekly": "Weekly Cost",
+    "costMonthly": "Monthly Cost",
+    "concurrentSessions": "Concurrent Sessions",
+    "noLimit": "No quota limits"
+  },
+  "addKeyForm": {
+    "title": "Add Key",
+    "description": "Create a new API key for the current user. The key value will be auto-generated.",
+    "submitText": "Create Key",
+    "loadingText": "Creating...",
+    "keyName": {
+      "label": "Key Name",
+      "placeholder": "Enter 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"
+    },
+    "errors": {
+      "userIdMissing": "User ID does not exist",
+      "createFailed": "Failed to create, please try again later",
+      "noKeyReturned": "Created successfully but key was not returned"
+    }
+  },
+  "userForm": {
+    "title": {
+      "add": "Add User",
+      "edit": "Edit User"
+    },
+    "description": {
+      "add": "Create a new user. The system will automatically generate a default key.",
+      "edit": "Modify user's basic information."
+    },
+    "submitText": {
+      "add": "Create User",
+      "edit": "Save Changes"
+    },
+    "loadingText": {
+      "add": "Creating...",
+      "edit": "Saving..."
+    },
+    "username": {
+      "label": "Username",
+      "placeholder": "Enter username"
+    },
+    "note": {
+      "label": "Note",
+      "placeholder": "Enter note (optional)",
+      "description": "Used to describe the user's purpose or notes"
+    },
+    "providerGroup": {
+      "label": "Provider Group",
+      "placeholder": "e.g., premium or premium,economy (optional)",
+      "description": "Specify user-specific provider groups (supports multiple, comma-separated). The system will only select from providers matching the groupTag. Leave blank = use all providers"
+    },
+    "rpm": {
+      "label": "RPM Limit",
+      "placeholder": "Requests per minute limit",
+      "description": "Default: {default}, Range: 1-10000"
+    },
+    "dailyQuota": {
+      "label": "Daily Quota",
+      "placeholder": "Daily consumption quota limit",
+      "description": "Default: ${default}, Range: $0.01-$1000"
+    }
+  },
+  "deleteKeyConfirm": {
+    "title": "Confirm Delete Key",
+    "description": "Are you sure you want to delete key \"{name}\"?\n{maskedKey}\nThis action cannot be undone. All applications using this key will lose access.",
+    "cancel": "Cancel",
+    "confirm": "Confirm Delete",
+    "confirmLoading": "Deleting...",
+    "errors": {
+      "deleteFailed": "Delete failed",
+      "retryError": "Delete failed, please try again later"
+    }
+  },
+  "keyActions": {
+    "edit": "Edit",
+    "delete": "Delete",
+    "editAriaLabel": "Edit key",
+    "deleteAriaLabel": "Delete key"
+  },
+  "userActions": {
+    "edit": "Edit user",
+    "delete": "Delete user",
+    "editAriaLabel": "Edit user",
+    "deleteAriaLabel": "Delete user"
   }
 }

+ 256 - 152
messages/en/settings.json

@@ -85,6 +85,16 @@
     "autoCleanup": "Auto Log Cleanup",
     "autoCleanupDesc": "Automatically clean up historical log data on schedule to free up database storage space.",
     "description": "Manage system basic parameters that affect site display and statistics behavior.",
+    "section": {
+      "siteParams": {
+        "title": "Site Parameters",
+        "description": "Configure site title, currency display unit, and dashboard statistics display policy."
+      },
+      "autoCleanup": {
+        "title": "Auto Log Cleanup",
+        "description": "Automatically clean up historical log data on schedule to free up database storage space."
+      }
+    },
     "form": {
       "allowGlobalView": "Allow Global Usage View",
       "allowGlobalViewDesc": "When disabled, regular users can only view their own key usage statistics in the dashboard.",
@@ -244,21 +254,21 @@
     }
   },
   "errors": {
-    "addFailed": "Failed to add provider",
-    "addSuccess": "Added successfully",
-    "deleteFailed": "Failed to delete provider",
-    "deleteSuccess": "Deleted successfully",
-    "editFailed": "Failed to update provider",
-    "editSuccess": "Updated successfully",
-    "loadFailed": "Failed to load notification settings",
+    "saveSuccess": "Save succeeded",
     "saveFailed": "Save failed",
     "saveFailed_error": "Failed to save settings",
-    "saveSuccess": "Saved successfully",
+    "addSuccess": "Add succeeded",
+    "addFailed": "Failed to add provider",
+    "editSuccess": "Update succeeded",
+    "editFailed": "Failed to update provider",
+    "deleteSuccess": "Delete succeeded",
+    "deleteFailed": "Failed to delete provider",
+    "syncSuccess": "Sync succeeded",
     "syncFailed": "Sync failed",
-    "syncSuccess": "Sync successful",
     "testFailed": "Test failed",
-    "testFailedRetry": "Test failed. Please retry.",
-    "unknownError": "An error occurred during operation"
+    "testFailedRetry": "Test failed, please retry",
+    "loadFailed": "Failed to load notification settings",
+    "unknownError": "An exception occurred during the operation"
   },
   "logs": {
     "description": "Dynamically adjust system log level to control logging verbosity in real-time.",
@@ -326,102 +336,206 @@
     "sensitiveWords": "Sensitive Words"
   },
   "notifications": {
+    "title": "Push Notifications",
+    "description": "Configure WeChat Work robot push notifications",
+    "global": {
+      "title": "Notification Master Switch",
+      "description": "Enable or disable all push notification features",
+      "enable": "Enable Push Notifications"
+    },
     "circuitBreaker": {
-      "description": "Immediately push alert when provider is completely circuit-broken",
-      "enable": "Enable Circuit Breaker Alert",
-      "test": "Test Connection",
       "title": "Circuit Breaker Alert",
+      "description": "Send alert immediately when provider is fully circuit broken",
+      "enable": "Enable Circuit Breaker Alert",
       "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
-    },
-    "costAlert": {
-      "description": "Trigger alert when user/provider spending exceeds quota threshold",
-      "enable": "Enable Cost Alert",
-      "interval": "Check Interval (minutes)",
-      "test": "Test Connection",
-      "threshold": "Alert Threshold",
-      "thresholdHelp": "Trigger alert when spending reaches {percent}% of quota",
-      "thresholdLabel": "Alert threshold: {percent}%",
-      "title": "Cost Alert",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
+      "test": "Test Connection"
     },
     "dailyLeaderboard": {
-      "description": "Send top N user spending leaderboard daily on schedule",
+      "title": "Daily User Consumption Leaderboard",
+      "description": "Send daily scheduled user consumption Top N leaderboard",
       "enable": "Enable Daily Leaderboard",
-      "test": "Test Connection",
+      "webhook": "Webhook URL",
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
       "time": "Send Time",
-      "timeError": "Time format error, should be HH:mm",
       "timePlaceholder": "09:00",
-      "title": "Daily User Spending Leaderboard",
+      "timeError": "Time format error, should be HH:mm",
       "topN": "Show Top N",
+      "test": "Test Connection"
+    },
+    "costAlert": {
+      "title": "Cost Alert",
+      "description": "Trigger alert when user/provider consumption exceeds quota threshold",
+      "enable": "Enable Cost Alert",
       "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
+      "threshold": "Alert Threshold",
+      "thresholdLabel": "Alert Threshold: {percent}%",
+      "thresholdHelp": "Alert when consumption reaches {percent}% of quota",
+      "interval": "Check Interval (minutes)",
+      "test": "Test Connection"
     },
-    "description": "Configure WeChat Work robot push notifications",
     "form": {
-      "loadError": "Failed to load notification settings",
-      "loading": "Loading...",
       "save": "Save Settings",
-      "saveError": "Failed to save settings",
-      "saveFailed": "Save failed",
       "saving": "Saving...",
+      "loading": "Loading...",
       "success": "Notification settings saved and tasks rescheduled",
-      "testError": "Connection test failed",
+      "saveFailed": "Save failed",
+      "saveError": "Failed to save settings",
+      "loadError": "Failed to load notification settings",
+      "webhookRequired": "Please fill in Webhook URL first",
+      "testSuccess": "Test message sent, please check WeChat Work",
       "testFailed": "Test failed",
-      "testFailedRetry": "Test failed. Please retry.",
-      "testNoResult": "Test succeeded but no result returned",
-      "testSuccess": "Test message sent. Check WeChat Work.",
-      "webhookRequired": "Please fill in Webhook URL first"
-    },
-    "global": {
-      "description": "Enable or disable all push notification features",
-      "enable": "Enable Push Notifications",
-      "title": "Notification Master Switch"
-    },
-    "title": "Push Notifications"
+      "testFailedRetry": "Test failed, please retry",
+      "testError": "Test connection failed",
+      "testNoResult": "Test succeeded but no result returned"
+    }
   },
   "prices": {
+    "title": "Pricing",
     "description": "Manage platform basic configuration and model pricing",
-    "dialog": {
-      "description": "Upload JSON file to update model pricing configuration",
-      "fileSizeLimit": "File size cannot exceed 10MB",
-      "fileSizeLimitSmall": "File size not exceeding 10MB",
-      "getError": "Failed to get key",
-      "readError": "Failed to get key",
-      "selectFile": "Click to select JSON file or drag and drop here",
-      "title": "Update Model Price Table",
-      "upload": "Upload",
-      "uploading": "Uploading..."
-    },
-    "noData": "System has built-in price table. Use buttons above to sync or update.",
-    "noModels": "No model prices found",
-    "search": "Search model name...",
-    "subtitle": "Model Pricing",
-    "subtitleDesc": "Manage AI model pricing configuration",
-    "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...",
+    "section": {
+      "title": "Model Pricing",
+      "description": "Manage AI model pricing configuration"
+    },
+    "searchPlaceholder": "Search model name...",
+    "sync": {
+      "button": "Sync LiteLLM Prices",
+      "syncing": "Syncing...",
+      "successWithChanges": "Price table updated successfully, {count} models updated",
+      "successNoChanges": "Price table is up to date, no updates needed",
+      "failed": "Sync failed",
+      "failedError": "Sync failed: {error}",
+      "failedNoResult": "Price table updated but no result returned",
+      "noModels": "No model prices found",
+      "partialFailure": "Partial update succeeded, but {count} models failed"
+    },
     "table": {
-      "cachePrice": "Cache Price",
+      "modelName": "Model Name",
+      "type": "Type",
+      "provider": "Provider",
       "inputPrice": "Input Price ($/M)",
-      "model": "Model",
       "outputPrice": "Output Price ($/M)",
-      "updatedAt": "Updated At"
+      "updatedAt": "Updated At",
+      "typeChat": "Chat",
+      "typeImage": "Image",
+      "typeCompletion": "Completion",
+      "typeUnknown": "Unknown",
+      "loading": "Loading...",
+      "noMatch": "No matching models found",
+      "noDataTitle": "No price data available",
+      "noDataHint": "System has built-in price table. Use buttons above to sync or update."
     },
-    "title": "Pricing",
-    "upload": "Update Model Price Table",
-    "uploadFailed": "Failed to get pricing data:",
-    "uploadSuccess": "Price table updated successfully"
+    "pagination": {
+      "showing": "Showing {from}-{to} of {total}",
+      "previous": "Previous",
+      "next": "Next",
+      "perPage": "{size} per page"
+    },
+    "stats": {
+      "totalModels": "{count} models total",
+      "searchResults": "{count} search results",
+      "lastUpdated": "Last updated: {time}"
+    },
+    "dialog": {
+      "title": "Update Model Price Table",
+      "description": "Select and upload JSON file containing model pricing data",
+      "selectFile": "Click to select JSON file or drag and drop here",
+      "fileSizeLimit": "File size cannot exceed 10MB",
+      "fileSizeLimitSmall": "File size not exceeding 10MB",
+      "invalidFileType": "Please select a JSON format file",
+      "fileTooLarge": "File size exceeds 10MB limit",
+      "upload": "Upload and Update",
+      "uploading": "Uploading...",
+      "updatePriceTable": "Update Price Table",
+      "updating": "Updating...",
+      "selectJson": "Select JSON File",
+      "updateSuccess": "Price table updated successfully, {count} models updated",
+      "updateFailed": "Update failed",
+      "systemHasBuiltIn": "System has built-in price table",
+      "manualDownload": "You can also manually download",
+      "latestPriceTable": "latest price table",
+      "andUploadViaButton": ", and upload via button above",
+      "supportedModels": "Currently supports {count} models",
+      "results": {
+        "title": "Update Results",
+        "total": "Total: {total} models",
+        "success": "Success: {success}",
+        "failed": "Failed: {failed}",
+        "skipped": "Skipped: {skipped}",
+        "details": "Details",
+        "viewDetails": "View detailed logs"
+      }
+    }
   },
   "providers": {
     "add": "Add Provider",
     "addFailed": "Failed to add provider",
     "addProvider": "Add Provider",
     "addSuccess": "Provider added successfully",
+    "types": {
+      "claude": {
+        "label": "Claude",
+        "description": "Anthropic Official API"
+      },
+      "claudeAuth": {
+        "label": "Claude Auth",
+        "description": "Claude Relay Service"
+      },
+      "codex": {
+        "label": "Codex",
+        "description": "Codex CLI API"
+      },
+      "geminiCli": {
+        "label": "Gemini CLI",
+        "description": "Gemini CLI API"
+      },
+      "openaiCompatible": {
+        "label": "OpenAI Compatible",
+        "description": "OpenAI Compatible API"
+      }
+    },
+    "list": {
+      "priority": "Priority",
+      "weight": "Weight",
+      "costMultiplier": "Cost Multiplier",
+      "todayUsageLabel": "Today's Usage",
+      "todayUsageCount": "{count} times",
+      "circuitBroken": "Circuit Broken",
+      "officialWebsite": "Official",
+      "viewFullKey": "View Complete API Key",
+      "viewFullKeyDesc": "Please keep it safe and don't share it with others",
+      "keyLoading": "Loading...",
+      "confirmDeleteTitle": "Confirm Delete Provider?",
+      "confirmDeleteMessage": "Are you sure you want to delete provider \"{name}\"? This action cannot be undone.",
+      "deleteButton": "Delete",
+      "cancelButton": "Cancel",
+      "deleteSuccess": "Deleted successfully",
+      "deleteSuccessDesc": "Provider \"{name}\" has been deleted",
+      "deleteFailed": "Delete failed",
+      "deleteError": "An error occurred during operation",
+      "unknownError": "Unknown error",
+      "getKeyFailed": "Failed to get key",
+      "keyCopied": "Key copied to clipboard",
+      "copyFailed": "Copy failed",
+      "resetCircuitSuccess": "Circuit breaker reset",
+      "resetCircuitSuccessDesc": "Provider \"{name}\" circuit breaker status cleared",
+      "resetCircuitFailed": "Failed to reset circuit breaker",
+      "toggleSuccess": "Provider {status}",
+      "toggleSuccessDesc": "Provider \"{name}\" status updated",
+      "toggleFailed": "Toggle failed",
+      "statusEnabled": "enabled",
+      "statusDisabled": "disabled"
+    },
+    "schedulingDialog": {
+      "title": "Provider Scheduling Rules",
+      "description": "Understand how the system intelligently selects upstream providers for high availability and cost optimization",
+      "triggerButton": "Scheduling Rules",
+      "step": "Step",
+      "before": "Before:",
+      "after": "After:",
+      "decision": "Decision:"
+    },
     "circuitBroken": "Circuit Broken",
     "clone": "Clone Provider",
     "cloneFailed": "Copy failed",
@@ -791,7 +905,70 @@
     "toggleSuccessDesc": "Provider \"{name}\" status updated",
     "updateFailed": "Failed to update provider",
     "viewKey": "View Complete API Key",
-    "viewKeyDesc": "Please keep it safe and don't share it with others"
+    "viewKeyDesc": "Please keep it safe and don't share it with others",
+    "types": {
+      "claude": {
+        "label": "Claude",
+        "description": "Anthropic Official API"
+      },
+      "claudeAuth": {
+        "label": "Claude Auth",
+        "description": "Claude Relay Service"
+      },
+      "codex": {
+        "label": "Codex",
+        "description": "Codex CLI API"
+      },
+      "geminiCli": {
+        "label": "Gemini CLI",
+        "description": "Gemini CLI API"
+      },
+      "openaiCompatible": {
+        "label": "OpenAI Compatible",
+        "description": "OpenAI Compatible API"
+      }
+    },
+    "list": {
+      "priority": "Priority",
+      "weight": "Weight",
+      "costMultiplier": "Cost Multiplier",
+      "todayUsageLabel": "Today's Usage",
+      "todayUsageCount": "{count} times",
+      "circuitBroken": "Circuit Broken",
+      "officialWebsite": "Official",
+      "viewFullKey": "View Complete API Key",
+      "viewFullKeyDesc": "Please keep it safe and don't share it with others",
+      "keyLoading": "Loading...",
+      "confirmDeleteTitle": "Confirm Delete Provider?",
+      "confirmDeleteMessage": "Are you sure you want to delete provider \"{name}\"? This action cannot be undone.",
+      "deleteButton": "Delete",
+      "cancelButton": "Cancel",
+      "deleteSuccess": "Deleted successfully",
+      "deleteSuccessDesc": "Provider \"{name}\" has been deleted",
+      "deleteFailed": "Delete failed",
+      "deleteError": "An error occurred during operation",
+      "unknownError": "Unknown error",
+      "getKeyFailed": "Failed to get key",
+      "keyCopied": "Key copied to clipboard",
+      "copyFailed": "Copy failed",
+      "resetCircuitSuccess": "Circuit breaker reset",
+      "resetCircuitSuccessDesc": "Provider \"{name}\" circuit breaker status cleared",
+      "resetCircuitFailed": "Failed to reset circuit breaker",
+      "toggleSuccess": "Provider {status}",
+      "toggleSuccessDesc": "Provider \"{name}\" status updated",
+      "toggleFailed": "Toggle failed",
+      "statusEnabled": "enabled",
+      "statusDisabled": "disabled"
+    },
+    "schedulingDialog": {
+      "title": "Provider Scheduling Rules",
+      "description": "Understand how the system intelligently selects upstream providers for high availability and cost optimization",
+      "triggerButton": "Scheduling Rules",
+      "step": "Step",
+      "before": "Before:",
+      "after": "After:",
+      "decision": "Decision:"
+    }
   },
   "sensitiveWords": {
     "add": "Add Sensitive Word",
@@ -847,78 +1024,5 @@
     "title": "Sensitive Words Management",
     "toggleFailed": "Toggle failed",
     "toggleFailedError": "Toggle failed:"
-  },
-  "notifications": {
-    "title": "Push Notifications",
-    "description": "Configure WeChat Work robot push notifications",
-    "global": {
-      "title": "Notification Master Switch",
-      "description": "Enable or disable all push notification features",
-      "enable": "Enable Push Notifications"
-    },
-    "circuitBreaker": {
-      "title": "Circuit Breaker Alert",
-      "description": "Send alert immediately when provider is fully circuit broken",
-      "enable": "Enable Circuit Breaker Alert",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "test": "Test Connection"
-    },
-    "dailyLeaderboard": {
-      "title": "Daily User Consumption Leaderboard",
-      "description": "Send daily scheduled user consumption Top N leaderboard",
-      "enable": "Enable Daily Leaderboard",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "time": "Send Time",
-      "timePlaceholder": "09:00",
-      "timeError": "Time format error, should be HH:mm",
-      "topN": "Show Top N",
-      "test": "Test Connection"
-    },
-    "costAlert": {
-      "title": "Cost Alert",
-      "description": "Trigger alert when user/provider consumption exceeds quota threshold",
-      "enable": "Enable Cost Alert",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "threshold": "Alert Threshold",
-      "thresholdLabel": "Alert Threshold: {percent}%",
-      "thresholdHelp": "Alert when consumption reaches {percent}% of quota",
-      "interval": "Check Interval (minutes)",
-      "test": "Test Connection"
-    },
-    "form": {
-      "save": "Save Settings",
-      "saving": "Saving...",
-      "loading": "Loading...",
-      "success": "Notification settings saved and tasks rescheduled",
-      "saveFailed": "Save failed",
-      "saveError": "Failed to save settings",
-      "loadError": "Failed to load notification settings",
-      "webhookRequired": "Please fill in Webhook URL first",
-      "testSuccess": "Test message sent, please check WeChat Work",
-      "testFailed": "Test failed",
-      "testFailedRetry": "Test failed, please retry",
-      "testError": "Test connection failed",
-      "testNoResult": "Test succeeded but no result returned"
-    }
-  },
-  "errors": {
-    "saveSuccess": "Save succeeded",
-    "saveFailed": "Save failed",
-    "saveFailed_error": "Failed to save settings",
-    "addSuccess": "Add succeeded",
-    "addFailed": "Failed to add provider",
-    "editSuccess": "Update succeeded",
-    "editFailed": "Failed to update provider",
-    "deleteSuccess": "Delete succeeded",
-    "deleteFailed": "Failed to delete provider",
-    "syncSuccess": "Sync succeeded",
-    "syncFailed": "Sync failed",
-    "testFailed": "Test failed",
-    "testFailedRetry": "Test failed, please retry",
-    "loadFailed": "Failed to load notification settings",
-    "unknownError": "An exception occurred during the operation"
   }
 }

+ 0 - 4
messages/ja/customs.json

@@ -30,15 +30,11 @@
     "todayRequests": "本日のリクエスト",
     "todayCost": "本日のコスト",
     "avgResponse": "平均応答時間",
-    "requestsDescription": "過去24時間のリクエスト数",
-    "costDescription": "過去24時間の合計コスト",
-    "responseDescription": "平均応答時間(ミリ秒)",
     "viewDetails": "詳細を表示"
   },
   "activeSessions": {
     "title": "アクティブなセッション",
     "summary": "過去{minutes}分間の{count}個のセッション",
-    "recent": "最近アクティブ",
     "empty": "アクティブなセッションがありません",
     "viewAll": "すべて表示"
   }

+ 155 - 0
messages/ja/dashboard.json

@@ -422,5 +422,160 @@
       "title": "キーがありません",
       "description": "右上の「キーを追加」ボタンをクリックしてキーを追加できます"
     }
+  },
+  "userList": {
+    "badge": "{count} 個のキー",
+    "activeKeys": "アクティブキー",
+    "totalKeys": "総キー数",
+    "emptyState": {
+      "title": "ユーザーがいません",
+      "description": "下のボタンをクリックして最初のユーザーを作成してください"
+    },
+    "addUser": "ユーザーを追加"
+  },
+  "keyListHeader": {
+    "todayUsage": "本日の使用量",
+    "proxyStatus": {
+      "loading": "プロキシステータス読み込み中",
+      "fetchFailed": "プロキシステータスの取得に失敗しました",
+      "noStatus": "プロキシステータスなし",
+      "activeRequests": "アクティブリクエスト",
+      "lastRequest": "最新リクエスト",
+      "noRecord": "記録なし",
+      "timeAgo": {
+        "justNow": "たった今",
+        "secondsAgo": "{count}秒前",
+        "minutesAgo": "{count}分前",
+        "hoursAgo": "{count}時間前",
+        "daysAgo": "{count}日前"
+      }
+    },
+    "addKey": "キーを追加",
+    "keyCreatedDialog": {
+      "title": "キーの作成に成功しました",
+      "description": "APIキーが正常に作成されました。安全にコピーして保存してください。このキーは一度だけ表示されます。",
+      "apiKeyLabel": "API キー",
+      "warningText": "閉じる前にコピーして保存してください。閉じた後、このキーを再度表示することはできません",
+      "closeButton": "閉じる"
+    }
+  },
+  "keyLimitUsage": {
+    "loading": "読み込み中...",
+    "error": "取得に失敗しました",
+    "networkError": "ネットワークエラー",
+    "cost5h": "5時間消費",
+    "costWeekly": "週間消費",
+    "costMonthly": "月間消費",
+    "concurrentSessions": "同時セッション数",
+    "noLimit": "制限なし"
+  },
+  "addKeyForm": {
+    "title": "キーを追加",
+    "description": "現在のユーザーの新しいAPIキーを作成します。キー値は自動生成されます。",
+    "submitText": "作成を確認",
+    "loadingText": "作成中...",
+    "keyName": {
+      "label": "キー名",
+      "placeholder": "キー名を入力してください"
+    },
+    "expiresAt": {
+      "label": "有効期限",
+      "placeholder": "有効期限を選択",
+      "description": "空白の場合は無期限"
+    },
+    "canLoginWebUi": {
+      "label": "Web UI ログインを許可",
+      "description": "オフにすると、このキーはAPI呼び出しのみに使用でき、管理パネルにログインできません"
+    },
+    "limit5hUsd": {
+      "label": "5時間消費上限 (USD)",
+      "placeholder": "空白の場合は無制限",
+      "description": "5時間以内の最大消費金額"
+    },
+    "limitWeeklyUsd": {
+      "label": "週間消費上限 (USD)",
+      "placeholder": "空白の場合は無制限",
+      "description": "1週間あたりの最大消費金額"
+    },
+    "limitMonthlyUsd": {
+      "label": "月間消費上限 (USD)",
+      "placeholder": "空白の場合は無制限",
+      "description": "1ヶ月あたりの最大消費金額"
+    },
+    "limitConcurrentSessions": {
+      "label": "同時セッション上限",
+      "placeholder": "0 は無制限を意味します",
+      "description": "同時に実行される会話の数"
+    },
+    "errors": {
+      "userIdMissing": "ユーザーIDが存在しません",
+      "createFailed": "作成に失敗しました。後でもう一度お試しください",
+      "noKeyReturned": "作成に成功しましたが、キーが返されませんでした"
+    }
+  },
+  "userForm": {
+    "title": {
+      "add": "ユーザーを追加",
+      "edit": "ユーザーを編集"
+    },
+    "description": {
+      "add": "新しいユーザーを作成します。システムは自動的にデフォルトのキーを生成します。",
+      "edit": "ユーザーの基本情報を変更します。"
+    },
+    "submitText": {
+      "add": "作成を確認",
+      "edit": "変更を保存"
+    },
+    "loadingText": {
+      "add": "作成中...",
+      "edit": "保存中..."
+    },
+    "username": {
+      "label": "ユーザー名",
+      "placeholder": "ユーザー名を入力してください"
+    },
+    "note": {
+      "label": "備考",
+      "placeholder": "備考を入力してください(オプション)",
+      "description": "ユーザーの用途や備考情報を説明するために使用されます"
+    },
+    "providerGroup": {
+      "label": "プロバイダーグループ",
+      "placeholder": "例: premium または premium,economy(オプション)",
+      "description": "ユーザー専用のプロバイダーグループを指定します(複数可、カンマ区切り)。システムはgroupTagが一致するプロバイダーのみから選択します。空白=すべてのプロバイダーを使用"
+    },
+    "rpm": {
+      "label": "RPM制限",
+      "placeholder": "1分あたりのリクエスト数制限",
+      "description": "デフォルト値: {default}、範囲: 1-10000"
+    },
+    "dailyQuota": {
+      "label": "1日あたりの割当量",
+      "placeholder": "1日あたりの消費割当量制限",
+      "description": "デフォルト値: ${default}、範囲: $0.01-$1000"
+    }
+  },
+  "deleteKeyConfirm": {
+    "title": "キーの削除を確認",
+    "description": "キー「{name}」を削除してもよろしいですか?\n{maskedKey}\nこの操作は元に戻せません。削除後、このキーを使用しているすべてのアプリケーションはアクセスできなくなります。",
+    "cancel": "キャンセル",
+    "confirm": "削除を確認",
+    "confirmLoading": "削除中...",
+    "errors": {
+      "deleteFailed": "削除に失敗しました",
+      "retryError": "削除に失敗しました。後でもう一度お試しください"
+    }
+  },
+  "keyActions": {
+    "edit": "編集",
+    "delete": "削除",
+    "editAriaLabel": "キーを編集",
+    "deleteAriaLabel": "キーを削除"
+  },
+  "userActions": {
+    "edit": "ユーザーを編集",
+    "delete": "ユーザーを削除",
+    "editAriaLabel": "ユーザーを編集",
+    "deleteAriaLabel": "ユーザーを削除"
   }
 }

+ 196 - 155
messages/ja/settings.json

@@ -85,6 +85,16 @@
     "autoCleanup": "ログ自動クリーンアップ",
     "autoCleanupDesc": "スケジュールに従って履歴ログを自動的にクリーンアップし、データベース容量を解放します。",
     "description": "システムの基本パラメータを管理し、サイト表示と統計動作に影響します。",
+    "section": {
+      "siteParams": {
+        "title": "サイトパラメータ",
+        "description": "サイトタイトル、通貨表示単位、ダッシュボード統計表示ポリシーを設定します。"
+      },
+      "autoCleanup": {
+        "title": "ログ自動クリーンアップ",
+        "description": "スケジュールに従って履歴ログを自動的にクリーンアップし、データベース容量を解放します。"
+      }
+    },
     "form": {
       "allowGlobalView": "グローバル使用量表示を許可",
       "allowGlobalViewDesc": "無効にすると、一般ユーザーはダッシュボードで自分のキーの使用統計のみを表示できます。",
@@ -244,21 +254,21 @@
     }
   },
   "errors": {
-    "addFailed": "プロバイダーの追加に失敗しました",
-    "addSuccess": "正常に追加されました",
-    "deleteFailed": "プロバイダーの削除に失敗しました",
-    "deleteSuccess": "正常に削除されました",
-    "editFailed": "プロバイダーの更新に失敗しました",
-    "editSuccess": "正常に更新されました",
-    "loadFailed": "通知設定の読み込みに失敗しました",
+    "saveSuccess": "保存に成功しました",
     "saveFailed": "保存に失敗しました",
     "saveFailed_error": "設定の保存に失敗しました",
-    "saveSuccess": "正常に保存されました",
+    "addSuccess": "追加に成功しました",
+    "addFailed": "プロバイダーの追加に失敗しました",
+    "editSuccess": "更新に成功しました",
+    "editFailed": "プロバイダーの更新に失敗しました",
+    "deleteSuccess": "削除に成功しました",
+    "deleteFailed": "プロバイダーの削除に失敗しました",
+    "syncSuccess": "同期に成功しました",
     "syncFailed": "同期に失敗しました",
-    "syncSuccess": "同期成功",
     "testFailed": "テストに失敗しました",
-    "testFailedRetry": "テストに失敗しました。もう一度試してください。",
-    "unknownError": "操作中にエラーが発生しました"
+    "testFailedRetry": "テストに失敗しました。再試行してください",
+    "loadFailed": "通知設定の読み込みに失敗しました",
+    "unknownError": "操作中に例外が発生しました"
   },
   "logs": {
     "description": "システムログレベルを動的に調整してロギング詳細度をリアルタイムで制御します。",
@@ -326,96 +336,137 @@
     "sensitiveWords": "センシティブワード"
   },
   "notifications": {
+    "title": "プッシュ通知",
+    "description": "WeCom(企業微信)ロボットのプッシュ通知を設定",
+    "global": {
+      "title": "通知マスタースイッチ",
+      "description": "すべてのプッシュ通知機能を有効または無効にする",
+      "enable": "プッシュ通知を有効にする"
+    },
     "circuitBreaker": {
-      "description": "プロバイダーが完全にサーキットオープンしたときにアラートをすぐに送信します",
-      "enable": "サーキットブレーカーアラートを有効にする",
-      "test": "接続をテスト",
       "title": "サーキットブレーカーアラート",
+      "description": "プロバイダーが完全に遮断された時に即座にアラートを送信",
+      "enable": "サーキットブレーカーアラートを有効にする",
       "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
-    },
-    "costAlert": {
-      "description": "ユーザー/プロバイダー支出がクォータ閾値を超えたときにアラートをトリガーします",
-      "enable": "コストアラートを有効にする",
-      "interval": "チェック間隔(分)",
-      "test": "接続をテスト",
-      "threshold": "アラート閾値",
-      "thresholdHelp": "支出がクォータの{percent}%に達したときにアラート",
-      "thresholdLabel": "アラート閾値:{percent}%",
-      "title": "コストアラート",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
+      "test": "接続テスト"
     },
     "dailyLeaderboard": {
-      "description": "毎日スケジュール通りTop Nユーザー支出ランキングを送信します",
-      "enable": "毎日のランキングを有効にする",
-      "test": "接続をテスト",
-      "time": "送信時間",
-      "timeError": "時間形式エラー、HH:mmであるべきです",
+      "title": "日次ユーザー消費ランキング",
+      "description": "毎日定時でユーザー消費トップNランキングを送信",
+      "enable": "日次ランキングを有効にする",
+      "webhook": "Webhook URL",
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
+      "time": "送信時刻",
       "timePlaceholder": "09:00",
-      "title": "毎日のユーザー支出ランキング",
-      "topN": "Top Nを表示",
+      "timeError": "時刻形式エラー、HH:mm形式である必要があります",
+      "topN": "トップN件表示",
+      "test": "接続テスト"
+    },
+    "costAlert": {
+      "title": "コストアラート",
+      "description": "ユーザー/プロバイダーの消費がクォータしきい値を超えた時にアラートをトリガー",
+      "enable": "コストアラートを有効にする",
       "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
+      "threshold": "アラートしきい値",
+      "thresholdLabel": "アラートしきい値: {percent}%",
+      "thresholdHelp": "消費がクォータの{percent}%に達した時にアラート",
+      "interval": "チェック間隔(分)",
+      "test": "接続テスト"
     },
-    "description": "WeChat Workロボット通知の送信を設定します",
     "form": {
-      "loadError": "通知設定の読み込みに失敗しました",
-      "loading": "読み込み中...",
       "save": "設定を保存",
-      "saveError": "設定の保存に失敗しました",
-      "saveFailed": "保存に失敗しました",
       "saving": "保存中...",
-      "success": "通知設定が保存され、タスクが再スケジュールされました",
-      "testError": "接続テストに失敗しました",
+      "loading": "読み込み中...",
+      "success": "通知設定を保存し、タスクを再スケジュールしました",
+      "saveFailed": "保存に失敗しました",
+      "saveError": "設定の保存に失敗しました",
+      "loadError": "通知設定の読み込みに失敗しました",
+      "webhookRequired": "まずWebhook URLを入力してください",
+      "testSuccess": "テストメッセージを送信しました。WeComを確認してください",
       "testFailed": "テストに失敗しました",
-      "testFailedRetry": "テストに失敗しました。もう一度試してください。",
-      "testNoResult": "テストは成功しましたが結果が返されていません",
-      "testSuccess": "テストメッセージが送信されました。WeChat Workを確認してください。",
-      "webhookRequired": "まずWebhook URLを入力してください"
-    },
-    "global": {
-      "description": "すべての通知機能を有効または無効にします",
-      "enable": "通知を有効にする",
-      "title": "通知マスタースイッチ"
-    },
-    "title": "通知"
+      "testFailedRetry": "テストに失敗しました。再試行してください",
+      "testError": "接続テストに失敗しました",
+      "testNoResult": "テストは成功しましたが、結果が返されませんでした"
+    }
   },
   "prices": {
+    "title": "価格表",
     "description": "プラットフォーム基本設定とモデル価格を管理します",
-    "dialog": {
-      "description": "JSONファイルをアップロードして価格設定を更新します",
-      "fileSizeLimit": "ファイルサイズは10MBを超えることはできません",
-      "fileSizeLimitSmall": "ファイルサイズは10MB以下です",
-      "getError": "キーの取得に失敗しました",
-      "readError": "キーの取得に失敗しました",
-      "selectFile": "JSONファイルをクリックして選択、またはドラッグしてください",
-      "title": "モデル価格表を更新",
-      "upload": "アップロード",
-      "uploading": "アップロード中..."
+    "section": {
+      "title": "モデル価格",
+      "description": "AIモデルの価格設定を管理します"
+    },
+    "searchPlaceholder": "モデル名を検索...",
+    "sync": {
+      "button": "LiteLLM価格を同期",
+      "syncing": "同期中...",
+      "successWithChanges": "価格表が正常に更新されました。{count}個のモデルを更新しました",
+      "successNoChanges": "価格表は最新です。更新の必要はありません",
+      "failed": "同期に失敗しました",
+      "failedError": "同期に失敗しました: {error}",
+      "failedNoResult": "価格表は更新されましたが結果が返されていません",
+      "noModels": "モデル価格が見つかりません",
+      "partialFailure": "一部更新が成功しましたが、{count}個のモデルが失敗しました"
     },
-    "noData": "システムは組み込み価格表を持っています。上のボタンを使用して同期または更新してください。",
-    "noModels": "モデル価格が見つかりません",
-    "search": "搜索模型名称...",
-    "subtitle": "モデル価格",
-    "subtitleDesc": "AIモデルの価格設定を管理します",
-    "sync": "同步 LiteLLM 价格",
-    "syncFailed": "同期に失敗しました",
-    "syncFailedError": "同期に失敗しました:",
-    "syncNoResult": "価格表は更新されましたが結果が返されていません",
-    "syncSuccess": "価格表が正常に更新されました",
-    "syncing": "同期中...",
     "table": {
-      "cachePrice": "缓存价格",
+      "modelName": "モデル名",
+      "type": "タイプ",
+      "provider": "プロバイダー",
       "inputPrice": "入力価格 ($/M)",
-      "model": "模型",
       "outputPrice": "出力価格 ($/M)",
-      "updatedAt": "更新日時"
+      "updatedAt": "更新日時",
+      "typeChat": "チャット",
+      "typeImage": "画像生成",
+      "typeCompletion": "補完",
+      "typeUnknown": "不明",
+      "loading": "読み込み中...",
+      "noMatch": "一致するモデルが見つかりません",
+      "noDataTitle": "価格データがありません",
+      "noDataHint": "システムは組み込み価格表を持っています。上のボタンを使用して同期または更新してください。"
     },
-    "title": "価格表",
-    "upload": "更新模型价格表",
-    "uploadFailed": "価格データの取得に失敗しました:",
-    "uploadSuccess": "価格表が正常に更新されました"
+    "pagination": {
+      "showing": "{from}〜{to}件を表示(全{total}件)",
+      "previous": "前へ",
+      "next": "次へ",
+      "perPage": "1ページあたり{size}件"
+    },
+    "stats": {
+      "totalModels": "合計{count}個のモデル",
+      "searchResults": "{count}件の検索結果",
+      "lastUpdated": "最終更新: {time}"
+    },
+    "dialog": {
+      "title": "モデル価格表を更新",
+      "description": "モデル価格データを含むJSONファイルを選択してアップロード",
+      "selectFile": "JSONファイルをクリックして選択、またはドラッグしてください",
+      "fileSizeLimit": "ファイルサイズは10MBを超えることはできません",
+      "fileSizeLimitSmall": "ファイルサイズは10MB以下です",
+      "invalidFileType": "JSON形式のファイルを選択してください",
+      "fileTooLarge": "ファイルサイズが10MBを超えています",
+      "upload": "アップロードして更新",
+      "uploading": "アップロード中...",
+      "updatePriceTable": "価格表を更新",
+      "updating": "更新中...",
+      "selectJson": "JSONファイルを選択",
+      "updateSuccess": "価格表が正常に更新されました。{count}個のモデルを更新しました",
+      "updateFailed": "更新に失敗しました",
+      "systemHasBuiltIn": "システムは組み込み価格表を持っています",
+      "manualDownload": "手動でダウンロードすることもできます",
+      "latestPriceTable": "最新価格表",
+      "andUploadViaButton": "、上のボタンでアップロードしてください",
+      "supportedModels": "現在{count}個のモデルをサポート",
+      "results": {
+        "title": "更新結果",
+        "total": "合計: {total}個のモデル",
+        "success": "成功: {success}",
+        "failed": "失敗: {failed}",
+        "skipped": "スキップ: {skipped}",
+        "details": "詳細",
+        "viewDetails": "詳細ログを表示"
+      }
+    }
   },
   "providers": {
     "add": "プロバイダーを追加",
@@ -791,7 +842,70 @@
     "toggleSuccessDesc": "供应商 \"{name}\" 状态已更新",
     "updateFailed": "更新服务商失败",
     "viewKey": "查看完整 API Key",
-    "viewKeyDesc": "请妥善保管,不要泄露给他人"
+    "viewKeyDesc": "请妥善保管,不要泄露给他人",
+    "types": {
+      "claude": {
+        "label": "Claude",
+        "description": "Anthropic 公式 API"
+      },
+      "claudeAuth": {
+        "label": "Claude Auth",
+        "description": "Claude リレーサービス"
+      },
+      "codex": {
+        "label": "Codex",
+        "description": "Codex CLI API"
+      },
+      "geminiCli": {
+        "label": "Gemini CLI",
+        "description": "Gemini CLI API"
+      },
+      "openaiCompatible": {
+        "label": "OpenAI Compatible",
+        "description": "OpenAI 互換 API"
+      }
+    },
+    "list": {
+      "priority": "優先度",
+      "weight": "重み",
+      "costMultiplier": "コスト倍率",
+      "todayUsageLabel": "本日の使用量",
+      "todayUsageCount": "{count} 回",
+      "circuitBroken": "遮断中",
+      "officialWebsite": "公式",
+      "viewFullKey": "完全な API キーを表示",
+      "viewFullKeyDesc": "安全に保管し、他人と共有しないでください",
+      "keyLoading": "読み込み中...",
+      "confirmDeleteTitle": "プロバイダーの削除を確認しますか?",
+      "confirmDeleteMessage": "プロバイダー \"{name}\" を削除してもよろしいですか?この操作は元に戻せません。",
+      "deleteButton": "削除",
+      "cancelButton": "キャンセル",
+      "deleteSuccess": "削除に成功しました",
+      "deleteSuccessDesc": "プロバイダー \"{name}\" が削除されました",
+      "deleteFailed": "削除に失敗しました",
+      "deleteError": "操作中にエラーが発生しました",
+      "unknownError": "不明なエラー",
+      "getKeyFailed": "キーの取得に失敗しました",
+      "keyCopied": "キーがクリップボードにコピーされました",
+      "copyFailed": "コピーに失敗しました",
+      "resetCircuitSuccess": "サーキットブレーカーがリセットされました",
+      "resetCircuitSuccessDesc": "プロバイダー \"{name}\" のサーキットブレーカーステータスがクリアされました",
+      "resetCircuitFailed": "サーキットブレーカーのリセットに失敗しました",
+      "toggleSuccess": "プロバイダーが{status}になりました",
+      "toggleSuccessDesc": "プロバイダー \"{name}\" のステータスが更新されました",
+      "toggleFailed": "切り替えに失敗しました",
+      "statusEnabled": "有効",
+      "statusDisabled": "無効"
+    },
+    "schedulingDialog": {
+      "title": "プロバイダースケジューリングルール",
+      "description": "システムが高可用性とコスト最適化のために上流プロバイダーをインテリジェントに選択する方法を理解する",
+      "triggerButton": "スケジューリングルール",
+      "step": "ステップ",
+      "before": "前:",
+      "after": "後:",
+      "decision": "決定:"
+    }
   },
   "sensitiveWords": {
     "add": "センシティブワードを追加",
@@ -847,78 +961,5 @@
     "title": "センシティブワード管理",
     "toggleFailed": "トグルに失敗しました",
     "toggleFailedError": "トグルに失敗しました:"
-  },
-  "notifications": {
-    "title": "プッシュ通知",
-    "description": "WeCom(企業微信)ロボットのプッシュ通知を設定",
-    "global": {
-      "title": "通知マスタースイッチ",
-      "description": "すべてのプッシュ通知機能を有効または無効にする",
-      "enable": "プッシュ通知を有効にする"
-    },
-    "circuitBreaker": {
-      "title": "サーキットブレーカーアラート",
-      "description": "プロバイダーが完全に遮断された時に即座にアラートを送信",
-      "enable": "サーキットブレーカーアラートを有効にする",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "test": "接続テスト"
-    },
-    "dailyLeaderboard": {
-      "title": "日次ユーザー消費ランキング",
-      "description": "毎日定時でユーザー消費トップNランキングを送信",
-      "enable": "日次ランキングを有効にする",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "time": "送信時刻",
-      "timePlaceholder": "09:00",
-      "timeError": "時刻形式エラー、HH:mm形式である必要があります",
-      "topN": "トップN件表示",
-      "test": "接続テスト"
-    },
-    "costAlert": {
-      "title": "コストアラート",
-      "description": "ユーザー/プロバイダーの消費がクォータしきい値を超えた時にアラートをトリガー",
-      "enable": "コストアラートを有効にする",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "threshold": "アラートしきい値",
-      "thresholdLabel": "アラートしきい値: {percent}%",
-      "thresholdHelp": "消費がクォータの{percent}%に達した時にアラート",
-      "interval": "チェック間隔(分)",
-      "test": "接続テスト"
-    },
-    "form": {
-      "save": "設定を保存",
-      "saving": "保存中...",
-      "loading": "読み込み中...",
-      "success": "通知設定を保存し、タスクを再スケジュールしました",
-      "saveFailed": "保存に失敗しました",
-      "saveError": "設定の保存に失敗しました",
-      "loadError": "通知設定の読み込みに失敗しました",
-      "webhookRequired": "まずWebhook URLを入力してください",
-      "testSuccess": "テストメッセージを送信しました。WeComを確認してください",
-      "testFailed": "テストに失敗しました",
-      "testFailedRetry": "テストに失敗しました。再試行してください",
-      "testError": "接続テストに失敗しました",
-      "testNoResult": "テストは成功しましたが、結果が返されませんでした"
-    }
-  },
-  "errors": {
-    "saveSuccess": "保存に成功しました",
-    "saveFailed": "保存に失敗しました",
-    "saveFailed_error": "設定の保存に失敗しました",
-    "addSuccess": "追加に成功しました",
-    "addFailed": "プロバイダーの追加に失敗しました",
-    "editSuccess": "更新に成功しました",
-    "editFailed": "プロバイダーの更新に失敗しました",
-    "deleteSuccess": "削除に成功しました",
-    "deleteFailed": "プロバイダーの削除に失敗しました",
-    "syncSuccess": "同期に成功しました",
-    "syncFailed": "同期に失敗しました",
-    "testFailed": "テストに失敗しました",
-    "testFailedRetry": "テストに失敗しました。再試行してください",
-    "loadFailed": "通知設定の読み込みに失敗しました",
-    "unknownError": "操作中に例外が発生しました"
   }
 }

+ 337 - 0
messages/provider-i18n-patch.json

@@ -0,0 +1,337 @@
+{
+  "zh-CN": {
+    "providers": {
+      "types": {
+        "claude": {
+          "label": "Claude",
+          "description": "Anthropic 官方 API"
+        },
+        "claudeAuth": {
+          "label": "Claude Auth",
+          "description": "Claude 中转服务"
+        },
+        "codex": {
+          "label": "Codex",
+          "description": "Codex CLI API"
+        },
+        "geminiCli": {
+          "label": "Gemini CLI",
+          "description": "Gemini CLI API"
+        },
+        "openaiCompatible": {
+          "label": "OpenAI Compatible",
+          "description": "OpenAI 兼容 API"
+        }
+      },
+      "list": {
+        "priority": "优先级",
+        "weight": "权重",
+        "costMultiplier": "成本倍数",
+        "todayUsageLabel": "今日用量",
+        "todayUsageCount": "{count} 次",
+        "circuitBroken": "熔断中",
+        "officialWebsite": "官网",
+        "viewFullKey": "查看完整 API Key",
+        "viewFullKeyDesc": "请妥善保管,不要泄露给他人",
+        "keyLoading": "加载中...",
+        "confirmDeleteTitle": "确认删除供应商?",
+        "confirmDeleteMessage": "确定要删除供应商 \"{name}\" 吗?此操作无法撤销。",
+        "deleteButton": "删除",
+        "cancelButton": "取消",
+        "deleteSuccess": "删除成功",
+        "deleteSuccessDesc": "供应商 \"{name}\" 已删除",
+        "deleteFailed": "删除失败",
+        "deleteError": "操作过程中出现异常",
+        "unknownError": "未知错误",
+        "getKeyFailed": "获取密钥失败",
+        "keyCopied": "密钥已复制到剪贴板",
+        "copyFailed": "复制失败",
+        "resetCircuitSuccess": "熔断器已重置",
+        "resetCircuitSuccessDesc": "供应商 \"{name}\" 的熔断状态已解除",
+        "resetCircuitFailed": "重置熔断器失败",
+        "toggleSuccess": "供应商已{status}",
+        "toggleSuccessDesc": "供应商 \"{name}\" 状态已更新",
+        "toggleFailed": "状态切换失败",
+        "statusEnabled": "启用",
+        "statusDisabled": "禁用"
+      },
+      "schedulingDialog": {
+        "title": "供应商调度规则说明",
+        "description": "了解系统如何智能选择上游供应商,确保高可用性和成本优化",
+        "triggerButton": "调度规则说明",
+        "step": "步骤",
+        "before": "过滤前:",
+        "after": "过滤后:",
+        "decision": "决策:"
+      }
+    }
+  },
+  "en": {
+    "providers": {
+      "types": {
+        "claude": {
+          "label": "Claude",
+          "description": "Anthropic Official API"
+        },
+        "claudeAuth": {
+          "label": "Claude Auth",
+          "description": "Claude Relay Service"
+        },
+        "codex": {
+          "label": "Codex",
+          "description": "Codex CLI API"
+        },
+        "geminiCli": {
+          "label": "Gemini CLI",
+          "description": "Gemini CLI API"
+        },
+        "openaiCompatible": {
+          "label": "OpenAI Compatible",
+          "description": "OpenAI Compatible API"
+        }
+      },
+      "list": {
+        "priority": "Priority",
+        "weight": "Weight",
+        "costMultiplier": "Cost Multiplier",
+        "todayUsageLabel": "Today's Usage",
+        "todayUsageCount": "{count} times",
+        "circuitBroken": "Circuit Broken",
+        "officialWebsite": "Official",
+        "viewFullKey": "View Complete API Key",
+        "viewFullKeyDesc": "Please keep it safe and don't share it with others",
+        "keyLoading": "Loading...",
+        "confirmDeleteTitle": "Confirm Delete Provider?",
+        "confirmDeleteMessage": "Are you sure you want to delete provider \"{name}\"? This action cannot be undone.",
+        "deleteButton": "Delete",
+        "cancelButton": "Cancel",
+        "deleteSuccess": "Deleted successfully",
+        "deleteSuccessDesc": "Provider \"{name}\" has been deleted",
+        "deleteFailed": "Delete failed",
+        "deleteError": "An error occurred during operation",
+        "unknownError": "Unknown error",
+        "getKeyFailed": "Failed to get key",
+        "keyCopied": "Key copied to clipboard",
+        "copyFailed": "Copy failed",
+        "resetCircuitSuccess": "Circuit breaker reset",
+        "resetCircuitSuccessDesc": "Provider \"{name}\" circuit breaker status cleared",
+        "resetCircuitFailed": "Failed to reset circuit breaker",
+        "toggleSuccess": "Provider {status}",
+        "toggleSuccessDesc": "Provider \"{name}\" status updated",
+        "toggleFailed": "Toggle failed",
+        "statusEnabled": "enabled",
+        "statusDisabled": "disabled"
+      },
+      "schedulingDialog": {
+        "title": "Provider Scheduling Rules",
+        "description": "Understand how the system intelligently selects upstream providers for high availability and cost optimization",
+        "triggerButton": "Scheduling Rules",
+        "step": "Step",
+        "before": "Before:",
+        "after": "After:",
+        "decision": "Decision:"
+      }
+    }
+  },
+  "zh-TW": {
+    "providers": {
+      "types": {
+        "claude": {
+          "label": "Claude",
+          "description": "Anthropic 官方 API"
+        },
+        "claudeAuth": {
+          "label": "Claude Auth",
+          "description": "Claude 中繼服務"
+        },
+        "codex": {
+          "label": "Codex",
+          "description": "Codex CLI API"
+        },
+        "geminiCli": {
+          "label": "Gemini CLI",
+          "description": "Gemini CLI API"
+        },
+        "openaiCompatible": {
+          "label": "OpenAI Compatible",
+          "description": "OpenAI 相容 API"
+        }
+      },
+      "list": {
+        "priority": "優先級",
+        "weight": "權重",
+        "costMultiplier": "成本倍數",
+        "todayUsageLabel": "今日用量",
+        "todayUsageCount": "{count} 次",
+        "circuitBroken": "熔斷中",
+        "officialWebsite": "官網",
+        "viewFullKey": "查看完整 API Key",
+        "viewFullKeyDesc": "請妥善保管,不要洩露給他人",
+        "keyLoading": "載入中...",
+        "confirmDeleteTitle": "確認刪除供應商?",
+        "confirmDeleteMessage": "確定要刪除供應商 \"{name}\" 嗎?此操作無法撤銷。",
+        "deleteButton": "刪除",
+        "cancelButton": "取消",
+        "deleteSuccess": "刪除成功",
+        "deleteSuccessDesc": "供應商 \"{name}\" 已刪除",
+        "deleteFailed": "刪除失敗",
+        "deleteError": "操作過程中出現異常",
+        "unknownError": "未知錯誤",
+        "getKeyFailed": "獲取密鑰失敗",
+        "keyCopied": "密鑰已複製到剪貼板",
+        "copyFailed": "複製失敗",
+        "resetCircuitSuccess": "熔斷器已重置",
+        "resetCircuitSuccessDesc": "供應商 \"{name}\" 的熔斷狀態已解除",
+        "resetCircuitFailed": "重置熔斷器失敗",
+        "toggleSuccess": "供應商已{status}",
+        "toggleSuccessDesc": "供應商 \"{name}\" 狀態已更新",
+        "toggleFailed": "狀態切換失敗",
+        "statusEnabled": "啟用",
+        "statusDisabled": "禁用"
+      },
+      "schedulingDialog": {
+        "title": "供應商調度規則說明",
+        "description": "了解系統如何智慧選擇上游供應商,確保高可用性和成本優化",
+        "triggerButton": "調度規則說明",
+        "step": "步驟",
+        "before": "過濾前:",
+        "after": "過濾後:",
+        "decision": "決策:"
+      }
+    }
+  },
+  "ru": {
+    "providers": {
+      "types": {
+        "claude": {
+          "label": "Claude",
+          "description": "Официальный API Anthropic"
+        },
+        "claudeAuth": {
+          "label": "Claude Auth",
+          "description": "Служба ретрансляции Claude"
+        },
+        "codex": {
+          "label": "Codex",
+          "description": "Codex CLI API"
+        },
+        "geminiCli": {
+          "label": "Gemini CLI",
+          "description": "Gemini CLI API"
+        },
+        "openaiCompatible": {
+          "label": "OpenAI Compatible",
+          "description": "Совместимый с OpenAI API"
+        }
+      },
+      "list": {
+        "priority": "Приоритет",
+        "weight": "Вес",
+        "costMultiplier": "Множитель стоимости",
+        "todayUsageLabel": "Использование сегодня",
+        "todayUsageCount": "{count} раз(а)",
+        "circuitBroken": "Разорвано",
+        "officialWebsite": "Официальный",
+        "viewFullKey": "Просмотр полного API-ключа",
+        "viewFullKeyDesc": "Пожалуйста, храните его в безопасности и не делитесь с другими",
+        "keyLoading": "Загрузка...",
+        "confirmDeleteTitle": "Подтвердить удаление провайдера?",
+        "confirmDeleteMessage": "Вы уверены, что хотите удалить провайдера \"{name}\"? Это действие нельзя отменить.",
+        "deleteButton": "Удалить",
+        "cancelButton": "Отмена",
+        "deleteSuccess": "Успешно удалено",
+        "deleteSuccessDesc": "Провайдер \"{name}\" был удален",
+        "deleteFailed": "Не удалось удалить",
+        "deleteError": "Произошла ошибка во время операции",
+        "unknownError": "Неизвестная ошибка",
+        "getKeyFailed": "Не удалось получить ключ",
+        "keyCopied": "Ключ скопирован в буфер обмена",
+        "copyFailed": "Не удалось скопировать",
+        "resetCircuitSuccess": "Автоматический выключатель сброшен",
+        "resetCircuitSuccessDesc": "Статус автоматического выключателя провайдера \"{name}\" очищен",
+        "resetCircuitFailed": "Не удалось сбросить автоматический выключатель",
+        "toggleSuccess": "Провайдер {status}",
+        "toggleSuccessDesc": "Статус провайдера \"{name}\" обновлен",
+        "toggleFailed": "Не удалось переключить",
+        "statusEnabled": "включен",
+        "statusDisabled": "отключен"
+      },
+      "schedulingDialog": {
+        "title": "Правила планирования провайдеров",
+        "description": "Узнайте, как система интеллектуально выбирает вышестоящих провайдеров для высокой доступности и оптимизации затрат",
+        "triggerButton": "Правила планирования",
+        "step": "Шаг",
+        "before": "До:",
+        "after": "После:",
+        "decision": "Решение:"
+      }
+    }
+  },
+  "ja": {
+    "providers": {
+      "types": {
+        "claude": {
+          "label": "Claude",
+          "description": "Anthropic 公式 API"
+        },
+        "claudeAuth": {
+          "label": "Claude Auth",
+          "description": "Claude リレーサービス"
+        },
+        "codex": {
+          "label": "Codex",
+          "description": "Codex CLI API"
+        },
+        "geminiCli": {
+          "label": "Gemini CLI",
+          "description": "Gemini CLI API"
+        },
+        "openaiCompatible": {
+          "label": "OpenAI Compatible",
+          "description": "OpenAI 互換 API"
+        }
+      },
+      "list": {
+        "priority": "優先度",
+        "weight": "重み",
+        "costMultiplier": "コスト倍率",
+        "todayUsageLabel": "本日の使用量",
+        "todayUsageCount": "{count} 回",
+        "circuitBroken": "遮断中",
+        "officialWebsite": "公式",
+        "viewFullKey": "完全な API キーを表示",
+        "viewFullKeyDesc": "安全に保管し、他人と共有しないでください",
+        "keyLoading": "読み込み中...",
+        "confirmDeleteTitle": "プロバイダーの削除を確認しますか?",
+        "confirmDeleteMessage": "プロバイダー \"{name}\" を削除してもよろしいですか?この操作は元に戻せません。",
+        "deleteButton": "削除",
+        "cancelButton": "キャンセル",
+        "deleteSuccess": "削除に成功しました",
+        "deleteSuccessDesc": "プロバイダー \"{name}\" が削除されました",
+        "deleteFailed": "削除に失敗しました",
+        "deleteError": "操作中にエラーが発生しました",
+        "unknownError": "不明なエラー",
+        "getKeyFailed": "キーの取得に失敗しました",
+        "keyCopied": "キーがクリップボードにコピーされました",
+        "copyFailed": "コピーに失敗しました",
+        "resetCircuitSuccess": "サーキットブレーカーがリセットされました",
+        "resetCircuitSuccessDesc": "プロバイダー \"{name}\" のサーキットブレーカーステータスがクリアされました",
+        "resetCircuitFailed": "サーキットブレーカーのリセットに失敗しました",
+        "toggleSuccess": "プロバイダーが{status}になりました",
+        "toggleSuccessDesc": "プロバイダー \"{name}\" のステータスが更新されました",
+        "toggleFailed": "切り替えに失敗しました",
+        "statusEnabled": "有効",
+        "statusDisabled": "無効"
+      },
+      "schedulingDialog": {
+        "title": "プロバイダースケジューリングルール",
+        "description": "システムが高可用性とコスト最適化のために上流プロバイダーをインテリジェントに選択する方法を理解する",
+        "triggerButton": "スケジューリングルール",
+        "step": "ステップ",
+        "before": "前:",
+        "after": "後:",
+        "decision": "決定:"
+      }
+    }
+  }
+}

+ 0 - 4
messages/ru/customs.json

@@ -30,15 +30,11 @@
     "todayRequests": "Запросы сегодня",
     "todayCost": "Стоимость сегодня",
     "avgResponse": "Среднее время ответа",
-    "requestsDescription": "Количество запросов за последние 24 часа",
-    "costDescription": "Общая стоимость за последние 24 часа",
-    "responseDescription": "Среднее время ответа (миллисекунды)",
     "viewDetails": "Просмотр деталей"
   },
   "activeSessions": {
     "title": "Активные сеансы",
     "summary": "{count} сеансов в последние {minutes} минут",
-    "recent": "Недавно активные",
     "empty": "Нет активных сеансов",
     "viewAll": "Просмотреть все"
   }

+ 155 - 0
messages/ru/dashboard.json

@@ -422,5 +422,160 @@
       "title": "Нет ключей",
       "description": "Нажмите кнопку \"Добавить ключ\" в правом верхнем углу, чтобы добавить ключ"
     }
+  },
+  "userList": {
+    "badge": "{count} ключей",
+    "activeKeys": "Активные ключи",
+    "totalKeys": "Всего ключей",
+    "emptyState": {
+      "title": "Нет пользователей",
+      "description": "Нажмите кнопку ниже, чтобы создать первого пользователя"
+    },
+    "addUser": "Добавить пользователя"
+  },
+  "keyListHeader": {
+    "todayUsage": "Использование сегодня",
+    "proxyStatus": {
+      "loading": "Загрузка статуса прокси",
+      "fetchFailed": "Не удалось получить статус прокси",
+      "noStatus": "Нет статуса прокси",
+      "activeRequests": "Активные запросы",
+      "lastRequest": "Последний запрос",
+      "noRecord": "Нет записи",
+      "timeAgo": {
+        "justNow": "Только что",
+        "secondsAgo": "{count}с назад",
+        "minutesAgo": "{count} мин назад",
+        "hoursAgo": "{count}ч назад",
+        "daysAgo": "{count}д назад"
+      }
+    },
+    "addKey": "Добавить ключ",
+    "keyCreatedDialog": {
+      "title": "Ключ успешно создан",
+      "description": "Ваш API-ключ был успешно создан. Пожалуйста, скопируйте и сохраните его в безопасном месте, так как он будет показан только один раз.",
+      "apiKeyLabel": "API ключ",
+      "warningText": "Пожалуйста, скопируйте и сохраните перед закрытием. После закрытия вы не сможете снова просмотреть этот ключ",
+      "closeButton": "Закрыть"
+    }
+  },
+  "keyLimitUsage": {
+    "loading": "Загрузка...",
+    "error": "Ошибка загрузки",
+    "networkError": "Сетевая ошибка",
+    "cost5h": "Расход за 5 часов",
+    "costWeekly": "Недельный расход",
+    "costMonthly": "Месячный расход",
+    "concurrentSessions": "Параллельные сеансы",
+    "noLimit": "Без ограничений"
+  },
+  "addKeyForm": {
+    "title": "Добавить ключ",
+    "description": "Создать новый API-ключ для текущего пользователя. Значение ключа будет сгенерировано автоматически.",
+    "submitText": "Создать ключ",
+    "loadingText": "Создание...",
+    "keyName": {
+      "label": "Имя ключа",
+      "placeholder": "Введите имя ключа"
+    },
+    "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": "Количество одновременных разговоров"
+    },
+    "errors": {
+      "userIdMissing": "ID пользователя не существует",
+      "createFailed": "Не удалось создать, попробуйте позже",
+      "noKeyReturned": "Создан успешно, но ключ не был возвращен"
+    }
+  },
+  "userForm": {
+    "title": {
+      "add": "Добавить пользователя",
+      "edit": "Редактировать пользователя"
+    },
+    "description": {
+      "add": "Создать нового пользователя. Система автоматически сгенерирует ключ по умолчанию.",
+      "edit": "Изменить основную информацию пользователя."
+    },
+    "submitText": {
+      "add": "Создать пользователя",
+      "edit": "Сохранить изменения"
+    },
+    "loadingText": {
+      "add": "Создание...",
+      "edit": "Сохранение..."
+    },
+    "username": {
+      "label": "Имя пользователя",
+      "placeholder": "Введите имя пользователя"
+    },
+    "note": {
+      "label": "Примечание",
+      "placeholder": "Введите примечание (необязательно)",
+      "description": "Используется для описания назначения пользователя или примечаний"
+    },
+    "providerGroup": {
+      "label": "Группа поставщиков",
+      "placeholder": "например, premium или premium,economy (необязательно)",
+      "description": "Укажите группы поставщиков для конкретного пользователя (поддерживается несколько, разделенных запятыми). Система будет выбирать только из поставщиков, соответствующих groupTag. Пусто = использовать всех поставщиков"
+    },
+    "rpm": {
+      "label": "Лимит RPM",
+      "placeholder": "Лимит запросов в минуту",
+      "description": "По умолчанию: {default}, диапазон: 1-10000"
+    },
+    "dailyQuota": {
+      "label": "Дневная квота",
+      "placeholder": "Лимит дневного расхода",
+      "description": "По умолчанию: ${default}, диапазон: $0.01-$1000"
+    }
+  },
+  "deleteKeyConfirm": {
+    "title": "Подтвердить удаление ключа",
+    "description": "Вы уверены, что хотите удалить ключ \"{name}\"?\n{maskedKey}\nЭто действие нельзя отменить. Все приложения, использующие этот ключ, потеряют доступ.",
+    "cancel": "Отмена",
+    "confirm": "Подтвердить удаление",
+    "confirmLoading": "Удаление...",
+    "errors": {
+      "deleteFailed": "Не удалось удалить",
+      "retryError": "Не удалось удалить, попробуйте позже"
+    }
+  },
+  "keyActions": {
+    "edit": "Редактировать",
+    "delete": "Удалить",
+    "editAriaLabel": "Редактировать ключ",
+    "deleteAriaLabel": "Удалить ключ"
+  },
+  "userActions": {
+    "edit": "Редактировать пользователя",
+    "delete": "Удалить пользователя",
+    "editAriaLabel": "Редактировать пользователя",
+    "deleteAriaLabel": "Удалить пользователя"
   }
 }

+ 200 - 159
messages/ru/settings.json

@@ -85,6 +85,16 @@
     "autoCleanup": "Автоматическая очистка логов",
     "autoCleanupDesc": "Автоматически очищать исторические логи по расписанию для освобождения места в БД.",
     "description": "Управление основными параметрами системы, влияющими на отображение и поведение статистики.",
+    "section": {
+      "siteParams": {
+        "title": "Параметры сайта",
+        "description": "Настройка заголовка сайта, валюты отображения и политики отображения статистики на панели."
+      },
+      "autoCleanup": {
+        "title": "Автоматическая очистка логов",
+        "description": "Автоматически очищать исторические логи по расписанию для освобождения места в БД."
+      }
+    },
     "form": {
       "allowGlobalView": "Разрешить просмотр глобального использования",
       "allowGlobalViewDesc": "При отключении обычные пользователи могут видеть только статистику использования своих ключей на панели.",
@@ -244,21 +254,21 @@
     }
   },
   "errors": {
-    "addFailed": "Ошибка добавления поставщика",
-    "addSuccess": "Добавлено успешно",
-    "deleteFailed": "Ошибка удаления поставщика",
-    "deleteSuccess": "Удалено успешно",
-    "editFailed": "Ошибка обновления поставщика",
-    "editSuccess": "Обновлено успешно",
-    "loadFailed": "Ошибка загрузки параметров уведомлений",
+    "saveSuccess": "Сохранение успешно",
     "saveFailed": "Ошибка сохранения",
-    "saveFailed_error": "Ошибка сохранения параметров",
-    "saveSuccess": "Сохранено успешно",
-    "syncFailed": "Ошибка синхронизации",
+    "saveFailed_error": "Не удалось сохранить настройки",
+    "addSuccess": "Добавление успешно",
+    "addFailed": "Не удалось добавить провайдера",
+    "editSuccess": "Обновление успешно",
+    "editFailed": "Не удалось обновить провайдера",
+    "deleteSuccess": "Удаление успешно",
+    "deleteFailed": "Не удалось удалить провайдера",
     "syncSuccess": "Синхронизация успешна",
-    "testFailed": "Ошибка тестирования",
-    "testFailedRetry": "Ошибка тестирования. Пожалуйста, повторите.",
-    "unknownError": "Ошибка при выполнении операции"
+    "syncFailed": "Ошибка синхронизации",
+    "testFailed": "Тест не пройден",
+    "testFailedRetry": "Тест не пройден, попробуйте снова",
+    "loadFailed": "Не удалось загрузить настройки уведомлений",
+    "unknownError": "Во время операции произошло исключение"
   },
   "logs": {
     "description": "Динамическая регулировка уровня логирования для контроля подробности.",
@@ -326,96 +336,137 @@
     "sensitiveWords": "Чувствительные слова"
   },
   "notifications": {
-    "circuitBreaker": {
-      "description": "Немедленно отправить оповещение когда поставщик полностью замкнут",
-      "enable": "Включить оповещение автоматического выключателя",
-      "test": "Проверить соединение",
-      "title": "Оповещение автоматического выключателя",
-      "webhook": "URL Webhook",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
+    "title": "Push-уведомления",
+    "description": "Настройка push-уведомлений робота WeChat Work",
+    "global": {
+      "title": "Главный переключатель уведомлений",
+      "description": "Включить или отключить все функции push-уведомлений",
+      "enable": "Включить push-уведомления"
     },
-    "costAlert": {
-      "description": "Отправить оповещение когда расходы превышают порог квоты",
-      "enable": "Включить оповещение о стоимости",
-      "interval": "Интервал проверки (минуты)",
-      "test": "Проверить соединение",
-      "threshold": "Порог оповещения",
-      "thresholdHelp": "Оповестить когда расходы достигают {percent}% квоты",
-      "thresholdLabel": "Порог оповещения: {percent}%",
-      "title": "Оповещение о стоимости",
-      "webhook": "URL Webhook",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
+    "circuitBreaker": {
+      "title": "Оповещение о размыкателе цепи",
+      "description": "Отправить оповещение немедленно при полном размыкании провайдера",
+      "enable": "Включить оповещение о размыкателе цепи",
+      "webhook": "Webhook URL",
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
+      "test": "Тест подключения"
     },
     "dailyLeaderboard": {
-      "description": "Отправлять ежедневно рейтинг Top N пользователей по расходам",
+      "title": "Ежедневный рейтинг потребления пользователей",
+      "description": "Ежедневная отправка рейтинга топ N пользователей по потреблению",
       "enable": "Включить ежедневный рейтинг",
-      "test": "Проверить соединение",
+      "webhook": "Webhook URL",
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
       "time": "Время отправки",
-      "timeError": "Ошибка формата времени, должно быть HH:mm",
       "timePlaceholder": "09:00",
-      "title": "Ежедневный рейтинг расходов",
-      "topN": "Показать Top N",
-      "webhook": "URL Webhook",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
+      "timeError": "Ошибка формата времени, должен быть HH:mm",
+      "topN": "Показать топ N",
+      "test": "Тест подключения"
+    },
+    "costAlert": {
+      "title": "Оповещение о расходах",
+      "description": "Триггер оповещения при превышении порога квоты потребления пользователя/провайдера",
+      "enable": "Включить оповещение о расходах",
+      "webhook": "Webhook URL",
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
+      "threshold": "Порог оповещения",
+      "thresholdLabel": "Порог оповещения: {percent}%",
+      "thresholdHelp": "Оповещение при достижении {percent}% квоты",
+      "interval": "Интервал проверки (минуты)",
+      "test": "Тест подключения"
     },
-    "description": "Настройка отправки уведомлений через робота WeChat Work",
     "form": {
-      "loadError": "Ошибка загрузки параметров уведомлений",
+      "save": "Сохранить настройки",
+      "saving": "Сохранение...",
       "loading": "Загрузка...",
-      "save": "Сохранить параметры",
-      "saveError": "Ошибка сохранения параметров",
+      "success": "Настройки уведомлений сохранены и задачи перепланированы",
       "saveFailed": "Ошибка сохранения",
-      "saving": "Сохранение...",
-      "success": "Параметры уведомлений сохранены и перепланы переданы",
-      "testError": "Ошибка проверки соединения",
-      "testFailed": "Ошибка тестирования",
-      "testFailedRetry": "Ошибка тестирования. Пожалуйста, повторите.",
-      "testNoResult": "Тестирование успешно но результат не возвращен",
-      "testSuccess": "Тестовое сообщение отправлено. Проверьте WeChat Work.",
-      "webhookRequired": "Пожалуйста, сначала заполните URL Webhook"
-    },
-    "global": {
-      "description": "Включить или отключить все функции отправки уведомлений",
-      "enable": "Включить отправку уведомлений",
-      "title": "Главный переключатель уведомлений"
-    },
-    "title": "Отправка уведомлений"
+      "saveError": "Не удалось сохранить настройки",
+      "loadError": "Не удалось загрузить настройки уведомлений",
+      "webhookRequired": "Сначала заполните Webhook URL",
+      "testSuccess": "Тестовое сообщение отправлено, проверьте WeChat Work",
+      "testFailed": "Тест не пройден",
+      "testFailedRetry": "Тест не пройден, попробуйте снова",
+      "testError": "Ошибка тестирования подключения",
+      "testNoResult": "Тест пройден, но результат не возвращен"
+    }
   },
   "prices": {
+    "title": "Прайс-лист",
     "description": "Управление конфигурацией платформы и ценами моделей",
-    "dialog": {
-      "description": "Загрузить JSON файл для обновления прайс-листа",
-      "fileSizeLimit": "Размер файла не может превышать 10MB",
-      "fileSizeLimitSmall": "Размер файла не превышает 10MB",
-      "getError": "Ошибка получения ключа",
-      "readError": "Ошибка получения ключа",
-      "selectFile": "Нажмите для выбора JSON или перетащите сюда",
-      "title": "Обновить прайс-лист",
-      "upload": "Загрузить",
-      "uploading": "Загрузка..."
+    "section": {
+      "title": "Цены моделей",
+      "description": "Управление ценами AI моделей"
+    },
+    "searchPlaceholder": "Поиск по названию модели...",
+    "sync": {
+      "button": "Синхронизировать цены LiteLLM",
+      "syncing": "Синхронизация...",
+      "successWithChanges": "Прайс-лист успешно обновлён, {count} моделей обновлено",
+      "successNoChanges": "Прайс-лист актуален, обновление не требуется",
+      "failed": "Ошибка синхронизации",
+      "failedError": "Ошибка синхронизации: {error}",
+      "failedNoResult": "Прайс-лист обновлен но результат не возвращен",
+      "noModels": "Цены моделей не найдены",
+      "partialFailure": "Частичное обновление выполнено, но {count} моделей не удалось обновить"
     },
-    "noData": "Система имеет встроенный прайс-лист. Используйте кнопки выше для синхронизации.",
-    "noModels": "Цены моделей не найдены",
-    "search": "Поиск по названию модели...",
-    "subtitle": "Цены моделей",
-    "subtitleDesc": "Управление ценами AI моделей",
-    "sync": "Синхронизировать цены LiteLLM",
-    "syncFailed": "Ошибка синхронизации",
-    "syncFailedError": "Ошибка синхронизации:",
-    "syncNoResult": "Прайс-лист обновлен но результат не возвращен",
-    "syncSuccess": "Прайс-лист обновлен успешно",
-    "syncing": "Синхронизация...",
     "table": {
-      "cachePrice": "Цена кэша",
+      "modelName": "Название модели",
+      "type": "Тип",
+      "provider": "Поставщик",
       "inputPrice": "Цена ввода ($/M)",
-      "model": "Модель",
       "outputPrice": "Цена вывода ($/M)",
-      "updatedAt": "Обновлено"
+      "updatedAt": "Обновлено",
+      "typeChat": "Чат",
+      "typeImage": "Генерация изображений",
+      "typeCompletion": "Дополнение",
+      "typeUnknown": "Неизвестно",
+      "loading": "Загрузка...",
+      "noMatch": "Соответствующие модели не найдены",
+      "noDataTitle": "Данные о ценах отсутствуют",
+      "noDataHint": "Система имеет встроенный прайс-лист. Используйте кнопки выше для синхронизации."
     },
-    "title": "Прайс-лист",
-    "upload": "Обновить прайс-лист моделей",
-    "uploadFailed": "Ошибка получения данных о ценах:",
-    "uploadSuccess": "Прайс-лист обновлен успешно"
+    "pagination": {
+      "showing": "Показано {from}-{to} из {total}",
+      "previous": "Назад",
+      "next": "Вперёд",
+      "perPage": "{size} на странице"
+    },
+    "stats": {
+      "totalModels": "Всего моделей: {count}",
+      "searchResults": "Результатов поиска: {count}",
+      "lastUpdated": "Последнее обновление: {time}"
+    },
+    "dialog": {
+      "title": "Обновить прайс-лист",
+      "description": "Выберите и загрузите JSON файл с данными о ценах моделей",
+      "selectFile": "Нажмите для выбора JSON или перетащите сюда",
+      "fileSizeLimit": "Размер файла не может превышать 10MB",
+      "fileSizeLimitSmall": "Размер файла не превышает 10MB",
+      "invalidFileType": "Пожалуйста, выберите файл в формате JSON",
+      "fileTooLarge": "Размер файла превышает лимит 10MB",
+      "upload": "Загрузить и обновить",
+      "uploading": "Загрузка...",
+      "updatePriceTable": "Обновить прайс-лист",
+      "updating": "Обновление...",
+      "selectJson": "Выбрать JSON файл",
+      "updateSuccess": "Прайс-лист успешно обновлён, {count} моделей обновлено",
+      "updateFailed": "Ошибка обновления",
+      "systemHasBuiltIn": "Система имеет встроенный прайс-лист",
+      "manualDownload": "Вы также можете скачать вручную",
+      "latestPriceTable": "последний прайс-лист",
+      "andUploadViaButton": ", и загрузить через кнопку выше",
+      "supportedModels": "Поддерживается {count} моделей",
+      "results": {
+        "title": "Результаты обновления",
+        "total": "Всего: {total} моделей",
+        "success": "Успешно: {success}",
+        "failed": "Ошибок: {failed}",
+        "skipped": "Пропущено: {skipped}",
+        "details": "Подробности",
+        "viewDetails": "Просмотреть подробный журнал"
+      }
+    }
   },
   "providers": {
     "add": "Добавить поставщика",
@@ -791,7 +842,70 @@
     "toggleSuccessDesc": "Статус поставщика \"{name}\" обновлен",
     "updateFailed": "Не удалось обновить поставщика",
     "viewKey": "Просмотреть полный API ключ",
-    "viewKeyDesc": "Пожалуйста, храните бережно и не раскрывайте другим"
+    "viewKeyDesc": "Пожалуйста, храните бережно и не раскрывайте другим",
+    "types": {
+      "claude": {
+        "label": "Claude",
+        "description": "Официальный API Anthropic"
+      },
+      "claudeAuth": {
+        "label": "Claude Auth",
+        "description": "Служба ретрансляции Claude"
+      },
+      "codex": {
+        "label": "Codex",
+        "description": "Codex CLI API"
+      },
+      "geminiCli": {
+        "label": "Gemini CLI",
+        "description": "Gemini CLI API"
+      },
+      "openaiCompatible": {
+        "label": "OpenAI Compatible",
+        "description": "Совместимый с OpenAI API"
+      }
+    },
+    "list": {
+      "priority": "Приоритет",
+      "weight": "Вес",
+      "costMultiplier": "Множитель стоимости",
+      "todayUsageLabel": "Использование сегодня",
+      "todayUsageCount": "{count} раз(а)",
+      "circuitBroken": "Разорвано",
+      "officialWebsite": "Официальный",
+      "viewFullKey": "Просмотр полного API-ключа",
+      "viewFullKeyDesc": "Пожалуйста, храните его в безопасности и не делитесь с другими",
+      "keyLoading": "Загрузка...",
+      "confirmDeleteTitle": "Подтвердить удаление провайдера?",
+      "confirmDeleteMessage": "Вы уверены, что хотите удалить провайдера \"{name}\"? Это действие нельзя отменить.",
+      "deleteButton": "Удалить",
+      "cancelButton": "Отмена",
+      "deleteSuccess": "Успешно удалено",
+      "deleteSuccessDesc": "Провайдер \"{name}\" был удален",
+      "deleteFailed": "Не удалось удалить",
+      "deleteError": "Произошла ошибка во время операции",
+      "unknownError": "Неизвестная ошибка",
+      "getKeyFailed": "Не удалось получить ключ",
+      "keyCopied": "Ключ скопирован в буфер обмена",
+      "copyFailed": "Не удалось скопировать",
+      "resetCircuitSuccess": "Автоматический выключатель сброшен",
+      "resetCircuitSuccessDesc": "Статус автоматического выключателя провайдера \"{name}\" очищен",
+      "resetCircuitFailed": "Не удалось сбросить автоматический выключатель",
+      "toggleSuccess": "Провайдер {status}",
+      "toggleSuccessDesc": "Статус провайдера \"{name}\" обновлен",
+      "toggleFailed": "Не удалось переключить",
+      "statusEnabled": "включен",
+      "statusDisabled": "отключен"
+    },
+    "schedulingDialog": {
+      "title": "Правила планирования провайдеров",
+      "description": "Узнайте, как система интеллектуально выбирает вышестоящих провайдеров для высокой доступности и оптимизации затрат",
+      "triggerButton": "Правила планирования",
+      "step": "Шаг",
+      "before": "До:",
+      "after": "После:",
+      "decision": "Решение:"
+    }
   },
   "sensitiveWords": {
     "add": "Добавить чувствительное слово",
@@ -847,78 +961,5 @@
     "title": "Управление чувствительными словами",
     "toggleFailed": "Ошибка переключения",
     "toggleFailedError": "Ошибка переключения:"
-  },
-  "notifications": {
-    "title": "Push-уведомления",
-    "description": "Настройка push-уведомлений робота WeChat Work",
-    "global": {
-      "title": "Главный переключатель уведомлений",
-      "description": "Включить или отключить все функции push-уведомлений",
-      "enable": "Включить push-уведомления"
-    },
-    "circuitBreaker": {
-      "title": "Оповещение о размыкателе цепи",
-      "description": "Отправить оповещение немедленно при полном размыкании провайдера",
-      "enable": "Включить оповещение о размыкателе цепи",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "test": "Тест подключения"
-    },
-    "dailyLeaderboard": {
-      "title": "Ежедневный рейтинг потребления пользователей",
-      "description": "Ежедневная отправка рейтинга топ N пользователей по потреблению",
-      "enable": "Включить ежедневный рейтинг",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "time": "Время отправки",
-      "timePlaceholder": "09:00",
-      "timeError": "Ошибка формата времени, должен быть HH:mm",
-      "topN": "Показать топ N",
-      "test": "Тест подключения"
-    },
-    "costAlert": {
-      "title": "Оповещение о расходах",
-      "description": "Триггер оповещения при превышении порога квоты потребления пользователя/провайдера",
-      "enable": "Включить оповещение о расходах",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "threshold": "Порог оповещения",
-      "thresholdLabel": "Порог оповещения: {percent}%",
-      "thresholdHelp": "Оповещение при достижении {percent}% квоты",
-      "interval": "Интервал проверки (минуты)",
-      "test": "Тест подключения"
-    },
-    "form": {
-      "save": "Сохранить настройки",
-      "saving": "Сохранение...",
-      "loading": "Загрузка...",
-      "success": "Настройки уведомлений сохранены и задачи перепланированы",
-      "saveFailed": "Ошибка сохранения",
-      "saveError": "Не удалось сохранить настройки",
-      "loadError": "Не удалось загрузить настройки уведомлений",
-      "webhookRequired": "Сначала заполните Webhook URL",
-      "testSuccess": "Тестовое сообщение отправлено, проверьте WeChat Work",
-      "testFailed": "Тест не пройден",
-      "testFailedRetry": "Тест не пройден, попробуйте снова",
-      "testError": "Ошибка тестирования подключения",
-      "testNoResult": "Тест пройден, но результат не возвращен"
-    }
-  },
-  "errors": {
-    "saveSuccess": "Сохранение успешно",
-    "saveFailed": "Ошибка сохранения",
-    "saveFailed_error": "Не удалось сохранить настройки",
-    "addSuccess": "Добавление успешно",
-    "addFailed": "Не удалось добавить провайдера",
-    "editSuccess": "Обновление успешно",
-    "editFailed": "Не удалось обновить провайдера",
-    "deleteSuccess": "Удаление успешно",
-    "deleteFailed": "Не удалось удалить провайдера",
-    "syncSuccess": "Синхронизация успешна",
-    "syncFailed": "Ошибка синхронизации",
-    "testFailed": "Тест не пройден",
-    "testFailedRetry": "Тест не пройден, попробуйте снова",
-    "loadFailed": "Не удалось загрузить настройки уведомлений",
-    "unknownError": "Во время операции произошло исключение"
   }
 }

+ 0 - 4
messages/zh-CN/customs.json

@@ -30,15 +30,11 @@
     "todayRequests": "今日请求",
     "todayCost": "今日消费",
     "avgResponse": "平均响应时间",
-    "requestsDescription": "过去24小时内的请求数",
-    "costDescription": "过去24小时内的消费成本",
-    "responseDescription": "平均响应时间(毫秒)",
     "viewDetails": "查看详情"
   },
   "activeSessions": {
     "title": "活跃 Session",
     "summary": "{count} 个 Session,{minutes} 分钟内",
-    "recent": "最近活跃",
     "empty": "暂无活跃 Session",
     "viewAll": "查看全部"
   }

+ 155 - 0
messages/zh-CN/dashboard.json

@@ -423,5 +423,160 @@
       "title": "暂无 Key",
       "description": "可点击右上角 \"新增 Key\" 按钮添加密钥"
     }
+  },
+  "userList": {
+    "badge": "{count} 个 Key",
+    "activeKeys": "活跃密钥",
+    "totalKeys": "总密钥",
+    "emptyState": {
+      "title": "暂无用户",
+      "description": "点击下方按钮创建第一个用户"
+    },
+    "addUser": "新增用户"
+  },
+  "keyListHeader": {
+    "todayUsage": "今日用量",
+    "proxyStatus": {
+      "loading": "代理状态加载中",
+      "fetchFailed": "代理状态获取失败",
+      "noStatus": "暂无代理状态",
+      "activeRequests": "活跃请求",
+      "lastRequest": "最近请求",
+      "noRecord": "暂无记录",
+      "timeAgo": {
+        "justNow": "刚刚",
+        "secondsAgo": "{count}s前",
+        "minutesAgo": "{count}分钟前",
+        "hoursAgo": "{count}小时前",
+        "daysAgo": "{count}天前"
+      }
+    },
+    "addKey": "新增 Key",
+    "keyCreatedDialog": {
+      "title": "Key 创建成功",
+      "description": "你的 API Key 已成功创建。请务必复制并妥善保存,此密钥仅显示一次。",
+      "apiKeyLabel": "API Key",
+      "warningText": "请在关闭前复制并保存,关闭后将无法再次查看此密钥",
+      "closeButton": "关闭"
+    }
+  },
+  "keyLimitUsage": {
+    "loading": "加载中...",
+    "error": "获取失败",
+    "networkError": "网络错误",
+    "cost5h": "5小时消费",
+    "costWeekly": "周消费",
+    "costMonthly": "月消费",
+    "concurrentSessions": "并发 Session",
+    "noLimit": "无限额限制"
+  },
+  "addKeyForm": {
+    "title": "新增 Key",
+    "description": "为当前用户创建新的API密钥,Key值将自动生成。",
+    "submitText": "确认创建",
+    "loadingText": "创建中...",
+    "keyName": {
+      "label": "Key名称",
+      "placeholder": "请输入Key名称"
+    },
+    "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": "同时运行的对话数量"
+    },
+    "errors": {
+      "userIdMissing": "用户ID不存在",
+      "createFailed": "创建失败,请稍后重试",
+      "noKeyReturned": "创建成功但未返回密钥"
+    }
+  },
+  "userForm": {
+    "title": {
+      "add": "新增用户",
+      "edit": "编辑用户"
+    },
+    "description": {
+      "add": "创建新用户,系统将自动为其生成默认密钥。",
+      "edit": "修改用户的基本信息。"
+    },
+    "submitText": {
+      "add": "确认创建",
+      "edit": "保存修改"
+    },
+    "loadingText": {
+      "add": "创建中...",
+      "edit": "保存中..."
+    },
+    "username": {
+      "label": "用户名",
+      "placeholder": "请输入用户名"
+    },
+    "note": {
+      "label": "备注",
+      "placeholder": "请输入备注(可选)",
+      "description": "用于描述用户的用途或备注信息"
+    },
+    "providerGroup": {
+      "label": "供应商分组",
+      "placeholder": "例如: premium 或 premium,economy(可选)",
+      "description": "指定用户专属的供应商分组(支持多个,逗号分隔)。系统将只从 groupTag 匹配的供应商中选择。留空=使用所有供应商"
+    },
+    "rpm": {
+      "label": "RPM限制",
+      "placeholder": "每分钟请求数限制",
+      "description": "默认值: {default},范围: 1-10000"
+    },
+    "dailyQuota": {
+      "label": "每日额度",
+      "placeholder": "每日消费额度限制",
+      "description": "默认值: ${default},范围: $0.01-$1000"
+    }
+  },
+  "deleteKeyConfirm": {
+    "title": "确认删除密钥",
+    "description": "您确定要删除密钥 \"{name}\" 吗?\n{maskedKey}\n此操作无法撤销,删除后所有使用此密钥的应用将无法访问。",
+    "cancel": "取消",
+    "confirm": "确认删除",
+    "confirmLoading": "删除中...",
+    "errors": {
+      "deleteFailed": "删除失败",
+      "retryError": "删除失败,请稍后重试"
+    }
+  },
+  "keyActions": {
+    "edit": "编辑",
+    "delete": "删除",
+    "editAriaLabel": "编辑密钥",
+    "deleteAriaLabel": "删除密钥"
+  },
+  "userActions": {
+    "edit": "编辑用户",
+    "delete": "删除用户",
+    "editAriaLabel": "编辑用户",
+    "deleteAriaLabel": "删除用户"
   }
 }

+ 204 - 27
messages/zh-CN/settings.json

@@ -48,6 +48,16 @@
     "siteSettingsDesc": "配置站点标题、货币显示单位与仪表盘统计展示策略。",
     "autoCleanup": "自动日志清理",
     "autoCleanupDesc": "定时自动清理历史日志数据,释放数据库存储空间。",
+    "section": {
+      "siteParams": {
+        "title": "站点参数",
+        "description": "配置站点标题、货币显示单位与仪表盘统计展示策略。"
+      },
+      "autoCleanup": {
+        "title": "自动日志清理",
+        "description": "定时自动清理历史日志数据,释放数据库存储空间。"
+      }
+    },
     "form": {
       "siteTitle": "站点标题",
       "siteTitlePlaceholder": "例如:Claude Code Hub",
@@ -103,6 +113,69 @@
     "subtitle": "服务商管理",
     "subtitleDesc": "配置上游服务商的金额限流和并发限制,留空表示无限制。",
     "add": "添加供应商",
+    "types": {
+      "claude": {
+        "label": "Claude",
+        "description": "Anthropic 官方 API"
+      },
+      "claudeAuth": {
+        "label": "Claude Auth",
+        "description": "Claude 中转服务"
+      },
+      "codex": {
+        "label": "Codex",
+        "description": "Codex CLI API"
+      },
+      "geminiCli": {
+        "label": "Gemini CLI",
+        "description": "Gemini CLI API"
+      },
+      "openaiCompatible": {
+        "label": "OpenAI Compatible",
+        "description": "OpenAI 兼容 API"
+      }
+    },
+    "list": {
+      "priority": "优先级",
+      "weight": "权重",
+      "costMultiplier": "成本倍数",
+      "todayUsageLabel": "今日用量",
+      "todayUsageCount": "{count} 次",
+      "circuitBroken": "熔断中",
+      "officialWebsite": "官网",
+      "viewFullKey": "查看完整 API Key",
+      "viewFullKeyDesc": "请妥善保管,不要泄露给他人",
+      "keyLoading": "加载中...",
+      "confirmDeleteTitle": "确认删除供应商?",
+      "confirmDeleteMessage": "确定要删除供应商 \"{name}\" 吗?此操作无法撤销。",
+      "deleteButton": "删除",
+      "cancelButton": "取消",
+      "deleteSuccess": "删除成功",
+      "deleteSuccessDesc": "供应商 \"{name}\" 已删除",
+      "deleteFailed": "删除失败",
+      "deleteError": "操作过程中出现异常",
+      "unknownError": "未知错误",
+      "getKeyFailed": "获取密钥失败",
+      "keyCopied": "密钥已复制到剪贴板",
+      "copyFailed": "复制失败",
+      "resetCircuitSuccess": "熔断器已重置",
+      "resetCircuitSuccessDesc": "供应商 \"{name}\" 的熔断状态已解除",
+      "resetCircuitFailed": "重置熔断器失败",
+      "toggleSuccess": "供应商已{status}",
+      "toggleSuccessDesc": "供应商 \"{name}\" 状态已更新",
+      "toggleFailed": "状态切换失败",
+      "statusEnabled": "启用",
+      "statusDisabled": "禁用"
+    },
+    "schedulingDialog": {
+      "title": "供应商调度规则说明",
+      "description": "了解系统如何智能选择上游供应商,确保高可用性和成本优化",
+      "triggerButton": "调度规则说明",
+      "step": "步骤",
+      "before": "过滤前:",
+      "after": "过滤后:",
+      "decision": "决策:"
+    },
     "addSuccess": "添加服务商成功",
     "addFailed": "添加服务商失败",
     "edit": "编辑服务商",
@@ -471,42 +544,146 @@
     "keyLoading": "加载中...",
     "resetCircuit": "熔断器已重置",
     "resetCircuitDesc": "供应商 \"{name}\" 的熔断状态已解除",
-    "resetCircuitFailed": "重置熔断器失败"
+    "resetCircuitFailed": "重置熔断器失败",
+    "types": {
+      "claude": {
+        "label": "Claude",
+        "description": "Anthropic 官方 API"
+      },
+      "claudeAuth": {
+        "label": "Claude Auth",
+        "description": "Claude 中转服务"
+      },
+      "codex": {
+        "label": "Codex",
+        "description": "Codex CLI API"
+      },
+      "geminiCli": {
+        "label": "Gemini CLI",
+        "description": "Gemini CLI API"
+      },
+      "openaiCompatible": {
+        "label": "OpenAI Compatible",
+        "description": "OpenAI 兼容 API"
+      }
+    },
+    "list": {
+      "priority": "优先级",
+      "weight": "权重",
+      "costMultiplier": "成本倍数",
+      "todayUsageLabel": "今日用量",
+      "todayUsageCount": "{count} 次",
+      "circuitBroken": "熔断中",
+      "officialWebsite": "官网",
+      "viewFullKey": "查看完整 API Key",
+      "viewFullKeyDesc": "请妥善保管,不要泄露给他人",
+      "keyLoading": "加载中...",
+      "confirmDeleteTitle": "确认删除供应商?",
+      "confirmDeleteMessage": "确定要删除供应商 \"{name}\" 吗?此操作无法撤销。",
+      "deleteButton": "删除",
+      "cancelButton": "取消",
+      "deleteSuccess": "删除成功",
+      "deleteSuccessDesc": "供应商 \"{name}\" 已删除",
+      "deleteFailed": "删除失败",
+      "deleteError": "操作过程中出现异常",
+      "unknownError": "未知错误",
+      "getKeyFailed": "获取密钥失败",
+      "keyCopied": "密钥已复制到剪贴板",
+      "copyFailed": "复制失败",
+      "resetCircuitSuccess": "熔断器已重置",
+      "resetCircuitSuccessDesc": "供应商 \"{name}\" 的熔断状态已解除",
+      "resetCircuitFailed": "重置熔断器失败",
+      "toggleSuccess": "供应商已{status}",
+      "toggleSuccessDesc": "供应商 \"{name}\" 状态已更新",
+      "toggleFailed": "状态切换失败",
+      "statusEnabled": "启用",
+      "statusDisabled": "禁用"
+    },
+    "schedulingDialog": {
+      "title": "供应商调度规则说明",
+      "description": "了解系统如何智能选择上游供应商,确保高可用性和成本优化",
+      "triggerButton": "调度规则说明",
+      "step": "步骤",
+      "before": "过滤前:",
+      "after": "过滤后:",
+      "decision": "决策:"
+    }
   },
   "prices": {
     "title": "价格表",
     "description": "管理平台基础配置与模型价格",
-    "subtitle": "模型价格",
-    "subtitleDesc": "管理 AI 模型的价格配置",
-    "search": "搜索模型名称...",
-    "sync": "同步 LiteLLM 价格",
-    "syncing": "同步中...",
-    "syncSuccess": "价格表更新成功",
-    "syncFailed": "同步失败",
-    "syncFailedError": "同步失败:",
-    "syncNoResult": "价格表更新成功但未返回处理结果",
-    "upload": "更新模型价格表",
-    "uploadSuccess": "价格表更新成功",
-    "uploadFailed": "获取价格数据失败:",
-    "noData": "系统已内置价格表,请通过上方按钮同步或更新",
-    "noModels": "未找到支持的模型价格",
+    "section": {
+      "title": "模型价格",
+      "description": "管理 AI 模型的价格配置"
+    },
+    "searchPlaceholder": "搜索模型名称...",
+    "sync": {
+      "button": "同步 LiteLLM 价格",
+      "syncing": "同步中...",
+      "successWithChanges": "价格表更新成功,共更新 {count} 个模型",
+      "successNoChanges": "价格表已是最新,无需更新",
+      "failed": "同步失败",
+      "failedError": "同步失败: {error}",
+      "failedNoResult": "价格表更新成功但未返回处理结果",
+      "noModels": "未找到支持的模型价格",
+      "partialFailure": "部分更新成功,但有 {count} 个模型失败"
+    },
     "table": {
-      "model": "模型",
-      "inputPrice": "输入价格",
-      "outputPrice": "输出价格",
-      "cachePrice": "缓存价格",
-      "updatedAt": "更新时间"
+      "modelName": "模型名称",
+      "type": "类型",
+      "provider": "提供商",
+      "inputPrice": "输入价格 ($/M)",
+      "outputPrice": "输出价格 ($/M)",
+      "updatedAt": "更新时间",
+      "typeChat": "对话",
+      "typeImage": "图像生成",
+      "typeCompletion": "补全",
+      "typeUnknown": "未知",
+      "loading": "加载中...",
+      "noMatch": "未找到匹配的模型",
+      "noDataTitle": "暂无价格数据",
+      "noDataHint": "系统已内置价格表,请通过上方按钮同步或更新"
+    },
+    "pagination": {
+      "showing": "显示 {from}-{to} 条,共 {total} 条",
+      "previous": "上一页",
+      "next": "下一页",
+      "perPage": "每页 {size} 条"
+    },
+    "stats": {
+      "totalModels": "共 {count} 个模型",
+      "searchResults": "搜索到 {count} 个结果",
+      "lastUpdated": "最后更新: {time}"
     },
     "dialog": {
       "title": "更新模型价格表",
-      "description": "上传 JSON 文件以更新模型价格配置",
-      "selectFile": "点击选择JSON文件或拖拽到此处",
-      "fileSizeLimit": "文件大小不能超过10MB",
-      "fileSizeLimitSmall": "文件大小不超过10MB",
-      "upload": "上传",
+      "description": "选择包含模型价格数据的 JSON 文件并上传",
+      "selectFile": "点击选择 JSON 文件或拖拽到此处",
+      "fileSizeLimit": "文件大小不能超过 10MB",
+      "fileSizeLimitSmall": "文件大小不超过 10MB",
+      "invalidFileType": "请选择 JSON 格式的文件",
+      "fileTooLarge": "文件大小超过 10MB 限制",
+      "upload": "上传并更新",
       "uploading": "上传中...",
-      "readError": "获取密钥失败",
-      "getError": "获取密钥失败"
+      "updatePriceTable": "更新价格表",
+      "updating": "更新中...",
+      "selectJson": "选择 JSON 文件",
+      "updateSuccess": "价格表更新成功,共更新 {count} 个模型",
+      "updateFailed": "更新失败",
+      "systemHasBuiltIn": "系统已内置价格表",
+      "manualDownload": "你也可以手动下载",
+      "latestPriceTable": "最新价格表",
+      "andUploadViaButton": ",并通过上方按钮上传",
+      "supportedModels": "当前支持 {count} 个模型",
+      "results": {
+        "title": "更新结果",
+        "total": "总计: {total} 个模型",
+        "success": "成功: {success}",
+        "failed": "失败: {failed}",
+        "skipped": "跳过: {skipped}",
+        "details": "详细信息",
+        "viewDetails": "查看详细日志"
+      }
     }
   },
   "sensitiveWords": {

+ 0 - 4
messages/zh-TW/customs.json

@@ -30,15 +30,11 @@
     "todayRequests": "今日請求",
     "todayCost": "今日消費",
     "avgResponse": "平均回應時間",
-    "requestsDescription": "過去24小時內的請求數",
-    "costDescription": "過去24小時內的消費成本",
-    "responseDescription": "平均回應時間(毫秒)",
     "viewDetails": "查看詳情"
   },
   "activeSessions": {
     "title": "活躍 Session",
     "summary": "{count} 個 Session,{minutes} 分鐘內",
-    "recent": "最近活躍",
     "empty": "暫無活躍 Session",
     "viewAll": "查看全部"
   }

+ 155 - 0
messages/zh-TW/dashboard.json

@@ -422,5 +422,160 @@
       "title": "暫無金鑰",
       "description": "可點選右上角「新增金鑰」按鈕新增金鑰"
     }
+  },
+  "userList": {
+    "badge": "{count} 個金鑰",
+    "activeKeys": "活躍金鑰",
+    "totalKeys": "總金鑰數",
+    "emptyState": {
+      "title": "暫無使用者",
+      "description": "點選下方按鈕建立第一個使用者"
+    },
+    "addUser": "新增使用者"
+  },
+  "keyListHeader": {
+    "todayUsage": "今日用量",
+    "proxyStatus": {
+      "loading": "代理狀態載入中",
+      "fetchFailed": "代理狀態取得失敗",
+      "noStatus": "暫無代理狀態",
+      "activeRequests": "活躍請求",
+      "lastRequest": "最近請求",
+      "noRecord": "暫無記錄",
+      "timeAgo": {
+        "justNow": "剛剛",
+        "secondsAgo": "{count}秒前",
+        "minutesAgo": "{count}分鐘前",
+        "hoursAgo": "{count}小時前",
+        "daysAgo": "{count}天前"
+      }
+    },
+    "addKey": "新增金鑰",
+    "keyCreatedDialog": {
+      "title": "金鑰建立成功",
+      "description": "您的 API 金鑰已成功建立。請務必複製並妥善保存,此金鑰僅顯示一次。",
+      "apiKeyLabel": "API 金鑰",
+      "warningText": "請在關閉前複製並儲存,關閉後將無法再次檢視此金鑰",
+      "closeButton": "關閉"
+    }
+  },
+  "keyLimitUsage": {
+    "loading": "載入中...",
+    "error": "取得失敗",
+    "networkError": "網路錯誤",
+    "cost5h": "5小時消費",
+    "costWeekly": "週消費",
+    "costMonthly": "月消費",
+    "concurrentSessions": "並發 Session",
+    "noLimit": "無額度限制"
+  },
+  "addKeyForm": {
+    "title": "新增金鑰",
+    "description": "為目前使用者建立新的 API 金鑰,金鑰值將自動產生。",
+    "submitText": "確認建立",
+    "loadingText": "建立中...",
+    "keyName": {
+      "label": "金鑰名稱",
+      "placeholder": "請輸入金鑰名稱"
+    },
+    "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": "同時執行的對話數量"
+    },
+    "errors": {
+      "userIdMissing": "使用者 ID 不存在",
+      "createFailed": "建立失敗,請稍後重試",
+      "noKeyReturned": "建立成功但未傳回金鑰"
+    }
+  },
+  "userForm": {
+    "title": {
+      "add": "新增使用者",
+      "edit": "編輯使用者"
+    },
+    "description": {
+      "add": "建立新使用者,系統將自動為其產生預設金鑰。",
+      "edit": "修改使用者的基本資訊。"
+    },
+    "submitText": {
+      "add": "確認建立",
+      "edit": "儲存修改"
+    },
+    "loadingText": {
+      "add": "建立中...",
+      "edit": "儲存中..."
+    },
+    "username": {
+      "label": "使用者名稱",
+      "placeholder": "請輸入使用者名稱"
+    },
+    "note": {
+      "label": "備註",
+      "placeholder": "請輸入備註(可選)",
+      "description": "用於描述使用者的用途或備註資訊"
+    },
+    "providerGroup": {
+      "label": "供應商分組",
+      "placeholder": "例如:premium 或 premium,economy(可選)",
+      "description": "指定使用者專屬的供應商分組(支援多個,逗號分隔)。系統將只從 groupTag 符合的供應商中選擇。留空=使用所有供應商"
+    },
+    "rpm": {
+      "label": "RPM 限制",
+      "placeholder": "每分鐘請求數限制",
+      "description": "預設值:{default},範圍:1-10000"
+    },
+    "dailyQuota": {
+      "label": "每日額度",
+      "placeholder": "每日消費額度限制",
+      "description": "預設值:${default},範圍:$0.01-$1000"
+    }
+  },
+  "deleteKeyConfirm": {
+    "title": "確認刪除金鑰",
+    "description": "您確定要刪除金鑰「{name}」嗎?\n{maskedKey}\n此操作無法復原,刪除後所有使用此金鑰的應用程式將無法存取。",
+    "cancel": "取消",
+    "confirm": "確認刪除",
+    "confirmLoading": "刪除中...",
+    "errors": {
+      "deleteFailed": "刪除失敗",
+      "retryError": "刪除失敗,請稍後重試"
+    }
+  },
+  "keyActions": {
+    "edit": "編輯",
+    "delete": "刪除",
+    "editAriaLabel": "編輯金鑰",
+    "deleteAriaLabel": "刪除金鑰"
+  },
+  "userActions": {
+    "edit": "編輯使用者",
+    "delete": "刪除使用者",
+    "editAriaLabel": "編輯使用者",
+    "deleteAriaLabel": "刪除使用者"
   }
 }

+ 189 - 148
messages/zh-TW/settings.json

@@ -85,6 +85,16 @@
     "autoCleanup": "自動日誌清理",
     "autoCleanupDesc": "定時自動清理歷史日誌資料,釋放資料庫儲存空間。",
     "description": "管理系統的基礎參數,影響站台顯示和統計行為。",
+    "section": {
+      "siteParams": {
+        "title": "站台參數",
+        "description": "設定站台標題、貨幣顯示單位與儀表板統計展示策略。"
+      },
+      "autoCleanup": {
+        "title": "自動日誌清理",
+        "description": "定時自動清理歷史日誌資料,釋放資料庫儲存空間。"
+      }
+    },
     "form": {
       "allowGlobalView": "允許查看全站使用量",
       "allowGlobalViewDesc": "關閉後,普通使用者在儀表板僅能查看自己金鑰的使用統計。",
@@ -244,20 +254,20 @@
     }
   },
   "errors": {
-    "addFailed": "新增服務商失敗",
+    "saveSuccess": "儲存成功",
+    "saveFailed": "儲存失敗",
+    "saveFailed_error": "儲存設定失敗",
     "addSuccess": "新增成功",
-    "deleteFailed": "刪除供應商失敗",
-    "deleteSuccess": "刪除成功",
-    "editFailed": "更新服務商失敗",
+    "addFailed": "新增服務商失敗",
     "editSuccess": "更新成功",
-    "loadFailed": "載入通知設定失敗",
-    "saveFailed": "保存失敗",
-    "saveFailed_error": "保存設定失敗",
-    "saveSuccess": "保存成功",
-    "syncFailed": "同步失敗",
+    "editFailed": "更新服務商失敗",
+    "deleteSuccess": "刪除成功",
+    "deleteFailed": "刪除供應商失敗",
     "syncSuccess": "同步成功",
+    "syncFailed": "同步失敗",
     "testFailed": "測試失敗",
     "testFailedRetry": "測試失敗,請重試",
+    "loadFailed": "載入通知設定失敗",
     "unknownError": "操作過程中出現異常"
   },
   "logs": {
@@ -326,96 +336,137 @@
     "sensitiveWords": "敏感詞"
   },
   "notifications": {
+    "title": "訊息推送",
+    "description": "設定企業微信機器人訊息推送",
+    "global": {
+      "title": "通知總開關",
+      "description": "啟用或停用所有訊息推送功能",
+      "enable": "啟用訊息推送"
+    },
     "circuitBreaker": {
+      "title": "熔斷器告警",
       "description": "供應商完全熔斷時立即推送告警訊息",
       "enable": "啟用熔斷器告警",
-      "test": "測試連線",
-      "title": "熔斷器告警",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
-    },
-    "costAlert": {
-      "description": "檢測使用者/供應商消費超過配額閾值時觸發告警",
-      "enable": "啟用成本預警",
-      "interval": "檢查間隔(分鐘)",
-      "test": "測試連線",
-      "threshold": "預警閾值",
-      "thresholdHelp": "當消費達到配額的 {percent}% 時觸發告警",
-      "thresholdLabel": "預警閾值: {percent}%",
-      "title": "成本預警",
       "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
+      "test": "測試連線"
     },
     "dailyLeaderboard": {
+      "title": "每日使用者消費排行榜",
       "description": "每天定時發送使用者消費 Top N 排行榜",
       "enable": "啟用每日排行榜",
-      "test": "測試連線",
+      "webhook": "Webhook URL",
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
       "time": "發送時間",
-      "timeError": "時間格式錯誤,應為 HH:mm",
       "timePlaceholder": "09:00",
-      "title": "每日使用者消費排行榜",
+      "timeError": "時間格式錯誤,應為 HH:mm",
       "topN": "顯示前 N 名",
+      "test": "測試連線"
+    },
+    "costAlert": {
+      "title": "成本預警",
+      "description": "偵測使用者/供應商消費超過配額閾值時觸發告警",
+      "enable": "啟用成本預警",
       "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
+      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
+      "threshold": "預警閾值",
+      "thresholdLabel": "預警閾值: {percent}%",
+      "thresholdHelp": "當消費達到配額的 {percent}% 時觸發告警",
+      "interval": "檢查間隔(分鐘)",
+      "test": "測試連線"
     },
-    "description": "設定企業微信機器人訊息推送",
     "form": {
-      "loadError": "載入通知設定失敗",
+      "save": "儲存設定",
+      "saving": "儲存中...",
       "loading": "載入中...",
-      "save": "保存設定",
-      "saveError": "保存設定失敗",
-      "saveFailed": "保存失敗",
-      "saving": "保存中...",
-      "success": "通知設定已保存並重新調度工作",
-      "testError": "連線測試失敗",
+      "success": "通知設定已儲存並重新排程任務",
+      "saveFailed": "儲存失敗",
+      "saveError": "儲存設定失敗",
+      "loadError": "載入通知設定失敗",
+      "webhookRequired": "請先填寫 Webhook URL",
+      "testSuccess": "測試訊息已發送,請檢查企業微信",
       "testFailed": "測試失敗",
       "testFailedRetry": "測試失敗,請重試",
-      "testNoResult": "測試成功但未返回結果",
-      "testSuccess": "測試訊息已發送,請檢查企業微信",
-      "webhookRequired": "請先填寫 Webhook URL"
-    },
-    "global": {
-      "description": "啟用或停用所有訊息推送功能",
-      "enable": "啟用訊息推送",
-      "title": "通知總開關"
-    },
-    "title": "訊息推送"
+      "testError": "測試連線失敗",
+      "testNoResult": "測試成功但未返回結果"
+    }
   },
   "prices": {
+    "title": "價格表",
     "description": "管理平台基礎設定與模型價格",
-    "dialog": {
-      "description": "上傳 JSON 檔案以更新模型價格設定",
-      "fileSizeLimit": "檔案大小不能超過10MB",
-      "fileSizeLimitSmall": "檔案大小不超過10MB",
-      "getError": "取得金鑰失敗",
-      "readError": "取得金鑰失敗",
-      "selectFile": "點擊選擇JSON檔案或拖曳到此處",
-      "title": "更新模型價格表",
-      "upload": "上傳",
-      "uploading": "上傳中..."
+    "section": {
+      "title": "模型價格",
+      "description": "管理 AI 模型的價格設定"
+    },
+    "searchPlaceholder": "搜尋模型名稱...",
+    "sync": {
+      "button": "同步 LiteLLM 價格",
+      "syncing": "同步中...",
+      "successWithChanges": "價格表更新成功,共更新 {count} 個模型",
+      "successNoChanges": "價格表已是最新,無需更新",
+      "failed": "同步失敗",
+      "failedError": "同步失敗: {error}",
+      "failedNoResult": "價格表更新成功但未返回處理結果",
+      "noModels": "未找到支援的模型價格",
+      "partialFailure": "部分更新成功,但有 {count} 個模型失敗"
     },
-    "noData": "系統已內置價格表,請透過上方按鈕同步或更新",
-    "noModels": "未找到支援的模型價格",
-    "search": "搜索模型名称...",
-    "subtitle": "模型價格",
-    "subtitleDesc": "管理 AI 模型的價格設定",
-    "sync": "同步 LiteLLM 价格",
-    "syncFailed": "同步失敗",
-    "syncFailedError": "同步失敗:",
-    "syncNoResult": "價格表更新成功但未返回處理結果",
-    "syncSuccess": "價格表更新成功",
-    "syncing": "同步中...",
     "table": {
-      "cachePrice": "缓存价格",
+      "modelName": "模型名稱",
+      "type": "類型",
+      "provider": "提供商",
       "inputPrice": "輸入價格 ($/M)",
-      "model": "模型",
       "outputPrice": "輸出價格 ($/M)",
-      "updatedAt": "更新時間"
+      "updatedAt": "更新時間",
+      "typeChat": "對話",
+      "typeImage": "圖像生成",
+      "typeCompletion": "補全",
+      "typeUnknown": "未知",
+      "loading": "載入中...",
+      "noMatch": "未找到符合的模型",
+      "noDataTitle": "暫無價格資料",
+      "noDataHint": "系統已內置價格表,請透過上方按鈕同步或更新"
     },
-    "title": "價格表",
-    "upload": "更新模型价格表",
-    "uploadFailed": "取得價格資料失敗:",
-    "uploadSuccess": "價格表更新成功"
+    "pagination": {
+      "showing": "顯示 {from}-{to} 條,共 {total} 條",
+      "previous": "上一頁",
+      "next": "下一頁",
+      "perPage": "每頁 {size} 條"
+    },
+    "stats": {
+      "totalModels": "共 {count} 個模型",
+      "searchResults": "搜尋到 {count} 個結果",
+      "lastUpdated": "最後更新: {time}"
+    },
+    "dialog": {
+      "title": "更新模型價格表",
+      "description": "選擇包含模型價格資料的 JSON 檔案並上傳",
+      "selectFile": "點擊選擇 JSON 檔案或拖曳到此處",
+      "fileSizeLimit": "檔案大小不能超過 10MB",
+      "fileSizeLimitSmall": "檔案大小不超過 10MB",
+      "invalidFileType": "請選擇 JSON 格式的檔案",
+      "fileTooLarge": "檔案大小超過 10MB 限制",
+      "upload": "上傳並更新",
+      "uploading": "上傳中...",
+      "updatePriceTable": "更新價格表",
+      "updating": "更新中...",
+      "selectJson": "選擇 JSON 檔案",
+      "updateSuccess": "價格表更新成功,共更新 {count} 個模型",
+      "updateFailed": "更新失敗",
+      "systemHasBuiltIn": "系統已內置價格表",
+      "manualDownload": "你也可以手動下載",
+      "latestPriceTable": "最新價格表",
+      "andUploadViaButton": ",並透過上方按鈕上傳",
+      "supportedModels": "目前支援 {count} 個模型",
+      "results": {
+        "title": "更新結果",
+        "total": "總計: {total} 個模型",
+        "success": "成功: {success}",
+        "failed": "失敗: {failed}",
+        "skipped": "跳過: {skipped}",
+        "details": "詳細資訊",
+        "viewDetails": "檢視詳細記錄"
+      }
+    }
   },
   "providers": {
     "add": "新增供應商",
@@ -791,7 +842,70 @@
     "toggleSuccessDesc": "供应商 \"{name}\" 状态已更新",
     "updateFailed": "更新服务商失败",
     "viewKey": "查看完整 API Key",
-    "viewKeyDesc": "请妥善保管,不要泄露给他人"
+    "viewKeyDesc": "请妥善保管,不要泄露给他人",
+    "types": {
+      "claude": {
+        "label": "Claude",
+        "description": "Anthropic 官方 API"
+      },
+      "claudeAuth": {
+        "label": "Claude Auth",
+        "description": "Claude 中繼服務"
+      },
+      "codex": {
+        "label": "Codex",
+        "description": "Codex CLI API"
+      },
+      "geminiCli": {
+        "label": "Gemini CLI",
+        "description": "Gemini CLI API"
+      },
+      "openaiCompatible": {
+        "label": "OpenAI Compatible",
+        "description": "OpenAI 相容 API"
+      }
+    },
+    "list": {
+      "priority": "優先級",
+      "weight": "權重",
+      "costMultiplier": "成本倍數",
+      "todayUsageLabel": "今日用量",
+      "todayUsageCount": "{count} 次",
+      "circuitBroken": "熔斷中",
+      "officialWebsite": "官網",
+      "viewFullKey": "查看完整 API Key",
+      "viewFullKeyDesc": "請妥善保管,不要洩露給他人",
+      "keyLoading": "載入中...",
+      "confirmDeleteTitle": "確認刪除供應商?",
+      "confirmDeleteMessage": "確定要刪除供應商 \"{name}\" 嗎?此操作無法撤銷。",
+      "deleteButton": "刪除",
+      "cancelButton": "取消",
+      "deleteSuccess": "刪除成功",
+      "deleteSuccessDesc": "供應商 \"{name}\" 已刪除",
+      "deleteFailed": "刪除失敗",
+      "deleteError": "操作過程中出現異常",
+      "unknownError": "未知錯誤",
+      "getKeyFailed": "獲取密鑰失敗",
+      "keyCopied": "密鑰已複製到剪貼板",
+      "copyFailed": "複製失敗",
+      "resetCircuitSuccess": "熔斷器已重置",
+      "resetCircuitSuccessDesc": "供應商 \"{name}\" 的熔斷狀態已解除",
+      "resetCircuitFailed": "重置熔斷器失敗",
+      "toggleSuccess": "供應商已{status}",
+      "toggleSuccessDesc": "供應商 \"{name}\" 狀態已更新",
+      "toggleFailed": "狀態切換失敗",
+      "statusEnabled": "啟用",
+      "statusDisabled": "禁用"
+    },
+    "schedulingDialog": {
+      "title": "供應商調度規則說明",
+      "description": "了解系統如何智慧選擇上游供應商,確保高可用性和成本優化",
+      "triggerButton": "調度規則說明",
+      "step": "步驟",
+      "before": "過濾前:",
+      "after": "過濾後:",
+      "decision": "決策:"
+    }
   },
   "sensitiveWords": {
     "add": "新增敏感詞",
@@ -847,78 +961,5 @@
     "title": "敏感詞管理",
     "toggleFailed": "狀態切換失敗",
     "toggleFailedError": "狀態切換失敗:"
-  },
-  "notifications": {
-    "title": "訊息推送",
-    "description": "設定企業微信機器人訊息推送",
-    "global": {
-      "title": "通知總開關",
-      "description": "啟用或停用所有訊息推送功能",
-      "enable": "啟用訊息推送"
-    },
-    "circuitBreaker": {
-      "title": "熔斷器告警",
-      "description": "供應商完全熔斷時立即推送告警訊息",
-      "enable": "啟用熔斷器告警",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "test": "測試連線"
-    },
-    "dailyLeaderboard": {
-      "title": "每日使用者消費排行榜",
-      "description": "每天定時發送使用者消費 Top N 排行榜",
-      "enable": "啟用每日排行榜",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "time": "發送時間",
-      "timePlaceholder": "09:00",
-      "timeError": "時間格式錯誤,應為 HH:mm",
-      "topN": "顯示前 N 名",
-      "test": "測試連線"
-    },
-    "costAlert": {
-      "title": "成本預警",
-      "description": "偵測使用者/供應商消費超過配額閾值時觸發告警",
-      "enable": "啟用成本預警",
-      "webhook": "Webhook URL",
-      "webhookPlaceholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
-      "threshold": "預警閾值",
-      "thresholdLabel": "預警閾值: {percent}%",
-      "thresholdHelp": "當消費達到配額的 {percent}% 時觸發告警",
-      "interval": "檢查間隔(分鐘)",
-      "test": "測試連線"
-    },
-    "form": {
-      "save": "儲存設定",
-      "saving": "儲存中...",
-      "loading": "載入中...",
-      "success": "通知設定已儲存並重新排程任務",
-      "saveFailed": "儲存失敗",
-      "saveError": "儲存設定失敗",
-      "loadError": "載入通知設定失敗",
-      "webhookRequired": "請先填寫 Webhook URL",
-      "testSuccess": "測試訊息已發送,請檢查企業微信",
-      "testFailed": "測試失敗",
-      "testFailedRetry": "測試失敗,請重試",
-      "testError": "測試連線失敗",
-      "testNoResult": "測試成功但未返回結果"
-    }
-  },
-  "errors": {
-    "saveSuccess": "儲存成功",
-    "saveFailed": "儲存失敗",
-    "saveFailed_error": "儲存設定失敗",
-    "addSuccess": "新增成功",
-    "addFailed": "新增服務商失敗",
-    "editSuccess": "更新成功",
-    "editFailed": "更新服務商失敗",
-    "deleteSuccess": "刪除成功",
-    "deleteFailed": "刪除供應商失敗",
-    "syncSuccess": "同步成功",
-    "syncFailed": "同步失敗",
-    "testFailed": "測試失敗",
-    "testFailedRetry": "測試失敗,請重試",
-    "loadFailed": "載入通知設定失敗",
-    "unknownError": "操作過程中出現異常"
   }
 }

+ 4 - 1
src/app/[locale]/dashboard/_components/user/add-user-dialog.tsx

@@ -5,6 +5,7 @@ import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
 import { ListPlus } from "lucide-react";
 import { UserForm } from "./forms/user-form";
 import { FormErrorBoundary } from "@/components/form-error-boundary";
+import { useTranslations } from "next-intl";
 
 type ButtonProps = ComponentProps<typeof Button>;
 
@@ -20,11 +21,13 @@ export function AddUserDialog({
   className,
 }: AddUserDialogProps) {
   const [open, setOpen] = useState(false);
+  const t = useTranslations("dashboard.userList");
+
   return (
     <Dialog open={open} onOpenChange={setOpen}>
       <DialogTrigger asChild>
         <Button variant={variant} size={size} className={className}>
-          <ListPlus className="h-4 w-4" /> 新增用户
+          <ListPlus className="h-4 w-4" /> {t("addUser")}
         </Button>
       </DialogTrigger>
       <DialogContent>

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

@@ -2,6 +2,7 @@
 import { useTransition } from "react";
 import { useRouter } from "next/navigation";
 import { toast } from "sonner";
+import { useTranslations } from "next-intl";
 import { addKey } from "@/actions/keys";
 import { DialogFormLayout } from "@/components/form/form-layout";
 import { TextField, DateField, NumberField } from "@/components/form/form-field";
@@ -18,6 +19,7 @@ interface AddKeyFormProps {
 export function AddKeyForm({ userId, onSuccess }: AddKeyFormProps) {
   const [isPending, startTransition] = useTransition();
   const router = useRouter();
+  const t = useTranslations("dashboard.addKeyForm");
 
   const form = useZodForm({
     schema: KeyFormSchema,
@@ -32,7 +34,7 @@ export function AddKeyForm({ userId, onSuccess }: AddKeyFormProps) {
     },
     onSubmit: async (data) => {
       if (!userId) {
-        throw new Error("用户ID不存在");
+        throw new Error(t("errors.userIdMissing"));
       }
 
       try {
@@ -48,13 +50,13 @@ export function AddKeyForm({ userId, onSuccess }: AddKeyFormProps) {
         });
 
         if (!result.ok) {
-          toast.error(result.error || "创建失败,请稍后重试");
+          toast.error(result.error || t("errors.createFailed"));
           return;
         }
 
         const payload = result.data;
         if (!payload) {
-          toast.error("创建成功但未返回密钥");
+          toast.error(t("errors.noKeyReturned"));
           return;
         }
 
@@ -65,7 +67,7 @@ export function AddKeyForm({ userId, onSuccess }: AddKeyFormProps) {
       } catch (err) {
         console.error("添加Key失败:", err);
         // 使用toast显示具体的错误信息
-        const errorMessage = err instanceof Error ? err.message : "创建失败,请稍后重试";
+        const errorMessage = err instanceof Error ? err.message : t("errors.createFailed");
         toast.error(errorMessage);
       }
     },
@@ -74,10 +76,10 @@ export function AddKeyForm({ userId, onSuccess }: AddKeyFormProps) {
   return (
     <DialogFormLayout
       config={{
-        title: "新增 Key",
-        description: "为当前用户创建新的API密钥,Key值将自动生成。",
-        submitText: "确认创建",
-        loadingText: "创建中...",
+        title: t("title"),
+        description: t("description"),
+        submitText: t("submitText"),
+        loadingText: t("loadingText"),
       }}
       onSubmit={form.handleSubmit}
       isSubmitting={isPending}
@@ -85,29 +87,27 @@ export function AddKeyForm({ userId, onSuccess }: AddKeyFormProps) {
       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 调用,无法登录管理后台
-          </p>
+          <p className="text-xs text-muted-foreground mt-1">{t("canLoginWebUi.description")}</p>
         </div>
         <Switch
           id="can-login-web-ui"
@@ -117,36 +117,36 @@ export function AddKeyForm({ userId, onSuccess }: AddKeyFormProps) {
       </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")}

+ 8 - 10
src/app/[locale]/dashboard/_components/user/forms/delete-key-confirm.tsx

@@ -9,6 +9,7 @@ import {
 } from "@/components/ui/dialog";
 import { Button } from "@/components/ui/button";
 import { useRouter } from "next/navigation";
+import { useTranslations } from "next-intl";
 import { removeKey } from "@/actions/keys";
 import { toast } from "sonner";
 
@@ -26,6 +27,7 @@ export function DeleteKeyConfirm({
 }: DeleteKeyConfirmProps & { onSuccess?: () => void }) {
   const router = useRouter();
   const [isPending, startTransition] = useTransition();
+  const t = useTranslations("dashboard.deleteKeyConfirm");
 
   const handleConfirm = () => {
     if (!keyData) return;
@@ -33,14 +35,14 @@ export function DeleteKeyConfirm({
       try {
         const res = await removeKey(keyData.id);
         if (!res.ok) {
-          toast.error(res.error || "删除失败");
+          toast.error(res.error || t("errors.deleteFailed"));
           return;
         }
         onSuccess?.();
         router.refresh();
       } catch (error) {
         console.error("删除Key失败:", error);
-        toast.error("删除失败,请稍后重试");
+        toast.error(t("errors.retryError"));
       }
     });
   };
@@ -48,24 +50,20 @@ export function DeleteKeyConfirm({
   return (
     <>
       <DialogHeader>
-        <DialogTitle>确认删除密钥</DialogTitle>
+        <DialogTitle>{t("title")}</DialogTitle>
         <DialogDescription>
-          您确定要删除密钥 &ldquo;<strong>{keyData?.name}</strong>&rdquo; 吗?
-          <br />
-          <code className="bg-muted px-2 py-1 rounded text-xs">{keyData?.maskedKey}</code>
-          <br />
-          此操作无法撤销,删除后所有使用此密钥的应用将无法访问。
+          {t("description", { name: keyData?.name, maskedKey: keyData?.maskedKey })}
         </DialogDescription>
       </DialogHeader>
 
       <DialogFooter>
         <DialogClose asChild>
           <Button type="button" variant="outline" disabled={isPending}>
-            取消
+            {t("cancel")}
           </Button>
         </DialogClose>
         <Button variant="destructive" onClick={handleConfirm} disabled={isPending}>
-          {isPending ? "删除中..." : "确认删除"}
+          {isPending ? t("confirmLoading") : t("confirm")}
         </Button>
       </DialogFooter>
     </>

+ 21 - 18
src/app/[locale]/dashboard/_components/user/forms/user-form.tsx

@@ -90,13 +90,16 @@ export function UserForm({ user, onSuccess }: UserFormProps) {
     },
   });
 
+  // Use dashboard translations for form
+  const tForm = useTranslations("dashboard.userForm");
+
   return (
     <DialogFormLayout
       config={{
-        title: isEdit ? "编辑用户" : "新增用户",
-        description: isEdit ? "修改用户的基本信息。" : "创建新用户,系统将自动为其生成默认密钥。",
-        submitText: isEdit ? "保存修改" : "确认创建",
-        loadingText: isEdit ? "保存中..." : "创建中...",
+        title: tForm(isEdit ? "title.edit" : "title.add"),
+        description: tForm(isEdit ? "description.edit" : "description.add"),
+        submitText: tForm(isEdit ? "submitText.edit" : "submitText.add"),
+        loadingText: tForm(isEdit ? "loadingText.edit" : "loadingText.add"),
       }}
       onSubmit={form.handleSubmit}
       isSubmitting={isPending}
@@ -104,50 +107,50 @@ export function UserForm({ user, onSuccess }: UserFormProps) {
       error={form.errors._form}
     >
       <TextField
-        label="用户名"
+        label={tForm("username.label")}
         required
         maxLength={64}
         autoFocus
-        placeholder="请输入用户名"
+        placeholder={tForm("username.placeholder")}
         {...form.getFieldProps("name")}
       />
 
       <TextField
-        label="备注"
+        label={tForm("note.label")}
         maxLength={200}
-        placeholder="请输入备注(可选)"
-        description="用于描述用户的用途或备注信息"
+        placeholder={tForm("note.placeholder")}
+        description={tForm("note.description")}
         {...form.getFieldProps("note")}
       />
 
       <TextField
-        label="供应商分组"
+        label={tForm("providerGroup.label")}
         maxLength={50}
-        placeholder="例如: premium 或 premium,economy(可选)"
-        description="指定用户专属的供应商分组(支持多个,逗号分隔)。系统将只从 groupTag 匹配的供应商中选择。留空=使用所有供应商"
+        placeholder={tForm("providerGroup.placeholder")}
+        description={tForm("providerGroup.description")}
         {...form.getFieldProps("providerGroup")}
       />
 
       <TextField
-        label="RPM限制"
+        label={tForm("rpm.label")}
         type="number"
         required
         min={1}
         max={10000}
-        placeholder="每分钟请求数限制"
-        description={`默认值: ${USER_DEFAULTS.RPM},范围: 1-10000`}
+        placeholder={tForm("rpm.placeholder")}
+        description={tForm("rpm.description", { default: USER_DEFAULTS.RPM })}
         {...form.getFieldProps("rpm")}
       />
 
       <TextField
-        label="每日额度"
+        label={tForm("dailyQuota.label")}
         type="number"
         required
         min={0.01}
         max={1000}
         step={0.01}
-        placeholder="每日消费额度限制"
-        description={`默认值: $${USER_DEFAULTS.DAILY_QUOTA},范围: $0.01-$1000`}
+        placeholder={tForm("dailyQuota.placeholder")}
+        description={tForm("dailyQuota.description", { default: USER_DEFAULTS.DAILY_QUOTA })}
         {...form.getFieldProps("dailyQuota")}
       />
     </DialogFormLayout>

+ 6 - 4
src/app/[locale]/dashboard/_components/user/key-actions.tsx

@@ -1,6 +1,7 @@
 "use client";
 import { useState } from "react";
 import { SquarePen, Trash2 } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
 import { EditKeyForm } from "./forms/edit-key-form";
 import { DeleteKeyConfirm } from "./forms/delete-key-confirm";
@@ -18,6 +19,7 @@ interface KeyActionsProps {
 export function KeyActions({ keyData, currentUser, keyOwnerUserId, canDelete }: KeyActionsProps) {
   const [openEdit, setOpenEdit] = useState(false);
   const [openDelete, setOpenDelete] = useState(false);
+  const t = useTranslations("dashboard.keyActions");
 
   // 权限检查:只有管理员或Key的拥有者才能编辑/删除
   const canManageKey =
@@ -35,9 +37,9 @@ export function KeyActions({ keyData, currentUser, keyOwnerUserId, canDelete }:
         <DialogTrigger asChild>
           <button
             type="button"
-            aria-label="编辑密钥"
+            aria-label={t("editAriaLabel")}
             className="inline-flex items-center justify-center p-1.5 text-muted-foreground hover:text-foreground transition-colors"
-            title="编辑"
+            title={t("edit")}
           >
             <SquarePen className="h-4 w-4" />
           </button>
@@ -55,9 +57,9 @@ export function KeyActions({ keyData, currentUser, keyOwnerUserId, canDelete }:
           <DialogTrigger asChild>
             <button
               type="button"
-              aria-label="删除密钥"
+              aria-label={t("deleteAriaLabel")}
               className="inline-flex items-center justify-center p-1.5 text-muted-foreground hover:text-red-600"
-              title="删除"
+              title={t("delete")}
             >
               <Trash2 className="h-4 w-4" />
             </button>

+ 11 - 9
src/app/[locale]/dashboard/_components/user/key-limit-usage.tsx

@@ -1,6 +1,7 @@
 "use client";
 
 import { useEffect, useState } from "react";
+import { useTranslations } from "next-intl";
 import { getKeyLimitUsage } from "@/actions/keys";
 import { formatCurrency, type CurrencyCode } from "@/lib/utils/currency";
 import { Loader2, AlertCircle } from "lucide-react";
@@ -23,6 +24,7 @@ export function KeyLimitUsage({ keyId, currencyCode = "USD" }: KeyLimitUsageProp
   const [data, setData] = useState<LimitUsageData | null>(null);
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState<string | null>(null);
+  const t = useTranslations("dashboard.keyLimitUsage");
 
   useEffect(() => {
     async function fetchData() {
@@ -35,23 +37,23 @@ export function KeyLimitUsage({ keyId, currencyCode = "USD" }: KeyLimitUsageProp
           setData(result.data);
         } else {
           // result.ok === false 时,result 是 { ok: false; error: string }
-          setError(result.error || "获取失败");
+          setError(result.error || t("error"));
         }
       } catch {
-        setError("网络错误");
+        setError(t("networkError"));
       } finally {
         setLoading(false);
       }
     }
 
     void fetchData();
-  }, [keyId]);
+  }, [keyId, t]);
 
   if (loading) {
     return (
       <div className="flex items-center gap-2 text-sm text-muted-foreground">
         <Loader2 className="h-3 w-3 animate-spin" />
-        <span>加载中...</span>
+        <span>{t("loading")}</span>
       </div>
     );
   }
@@ -69,25 +71,25 @@ export function KeyLimitUsage({ keyId, currencyCode = "USD" }: KeyLimitUsageProp
 
   const items = [
     {
-      label: "5小时消费",
+      label: t("cost5h"),
       current: data.cost5h.current,
       limit: data.cost5h.limit,
       isCost: true,
     },
     {
-      label: "周消费",
+      label: t("costWeekly"),
       current: data.costWeekly.current,
       limit: data.costWeekly.limit,
       isCost: true,
     },
     {
-      label: "月消费",
+      label: t("costMonthly"),
       current: data.costMonthly.current,
       limit: data.costMonthly.limit,
       isCost: true,
     },
     {
-      label: "并发 Session",
+      label: t("concurrentSessions"),
       current: data.concurrentSessions.current,
       limit: data.concurrentSessions.limit || null,
       isCost: false,
@@ -95,7 +97,7 @@ export function KeyLimitUsage({ keyId, currencyCode = "USD" }: KeyLimitUsageProp
   ].filter((item) => item.limit !== null && item.limit > 0); // 只显示有限额的项目
 
   if (items.length === 0) {
-    return <div className="text-xs text-muted-foreground">无限额限制</div>;
+    return <div className="text-xs text-muted-foreground">{t("noLimit")}</div>;
   }
 
   return (

+ 57 - 44
src/app/[locale]/dashboard/_components/user/key-list-header.tsx

@@ -11,6 +11,7 @@ import {
   DialogTrigger,
 } from "@/components/ui/dialog";
 import { ListPlus, Copy, CheckCircle } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { AddKeyForm } from "./forms/add-key-form";
 import { UserActions } from "./user-actions";
 import type { UserDisplay } from "@/types/user";
@@ -29,41 +30,43 @@ async function fetchProxyStatus(): Promise<ProxyStatusResponse> {
     if (result.data) {
       return result.data;
     }
-    throw new Error("获取代理状态失败");
+    throw new Error("Failed to fetch proxy status");
   }
-  throw new Error(result.error || "获取代理状态失败");
+  throw new Error(result.error || "Failed to fetch proxy status");
 }
 
-function formatRelativeTime(timestamp: number): string {
-  const diff = Date.now() - timestamp;
-  if (diff <= 0) {
-    return "刚刚";
-  }
+function createFormatRelativeTime(t: (key: string, params?: Record<string, number>) => string) {
+  return (timestamp: number): string => {
+    const diff = Date.now() - timestamp;
+    if (diff <= 0) {
+      return t("proxyStatus.timeAgo.justNow");
+    }
 
-  const seconds = Math.floor(diff / 1000);
-  if (seconds < 5) {
-    return "刚刚";
-  }
-  if (seconds < 60) {
-    return `${seconds}s前`;
-  }
+    const seconds = Math.floor(diff / 1000);
+    if (seconds < 5) {
+      return t("proxyStatus.timeAgo.justNow");
+    }
+    if (seconds < 60) {
+      return t("proxyStatus.timeAgo.secondsAgo", { count: seconds });
+    }
 
-  const minutes = Math.floor(seconds / 60);
-  if (minutes < 60) {
-    return `${minutes}分钟前`;
-  }
+    const minutes = Math.floor(seconds / 60);
+    if (minutes < 60) {
+      return t("proxyStatus.timeAgo.minutesAgo", { count: minutes });
+    }
 
-  const hours = Math.floor(minutes / 60);
-  if (hours < 24) {
-    return `${hours}小时前`;
-  }
+    const hours = Math.floor(minutes / 60);
+    if (hours < 24) {
+      return t("proxyStatus.timeAgo.hoursAgo", { count: hours });
+    }
 
-  const days = Math.floor(hours / 24);
-  if (days < 7) {
-    return `${days}天前`;
-  }
+    const days = Math.floor(hours / 24);
+    if (days < 7) {
+      return t("proxyStatus.timeAgo.daysAgo", { count: days });
+    }
 
-  return new Date(timestamp).toLocaleDateString("zh-CN");
+    return new Date(timestamp).toLocaleDateString();
+  };
 }
 
 function StatusSpinner() {
@@ -89,10 +92,13 @@ export function KeyListHeader({
   const [openAdd, setOpenAdd] = useState(false);
   const [keyResult, setKeyResult] = useState<{ generatedKey: string; name: string } | null>(null);
   const [copied, setCopied] = useState(false);
+  const t = useTranslations("dashboard.keyListHeader");
 
   const totalTodayUsage =
     activeUser?.keys.reduce((sum, key) => sum + (key.todayUsage ?? 0), 0) ?? 0;
 
+  const formatRelativeTime = useMemo(() => createFormatRelativeTime(t), [t]);
+
   const proxyStatusEnabled = Boolean(activeUser);
   const {
     data: proxyStatus,
@@ -120,18 +126,18 @@ export function KeyListHeader({
     if (proxyStatusLoading) {
       return (
         <div className="flex items-center gap-1 text-xs text-muted-foreground">
-          <span>代理状态加载中</span>
+          <span>{t("proxyStatus.loading")}</span>
           <StatusSpinner />
         </div>
       );
     }
 
     if (proxyStatusError) {
-      return <div className="text-xs text-destructive">代理状态获取失败</div>;
+      return <div className="text-xs text-destructive">{t("proxyStatus.fetchFailed")}</div>;
     }
 
     if (!activeUserStatus) {
-      return <div className="text-xs text-muted-foreground">暂无代理状态</div>;
+      return <div className="text-xs text-muted-foreground">{t("proxyStatus.noStatus")}</div>;
     }
 
     const activeProviders = Array.from(
@@ -141,18 +147,18 @@ export function KeyListHeader({
     return (
       <div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">
         <div className="flex items-center gap-1">
-          <span>活跃请求</span>
+          <span>{t("proxyStatus.activeRequests")}</span>
           <span className="font-medium text-foreground">{activeUserStatus.activeCount}</span>
           {activeProviders.length > 0 && (
             <span className="text-muted-foreground">({activeProviders.join("、")})</span>
           )}
         </div>
         <div className="flex items-center gap-1">
-          <span>最近请求</span>
+          <span>{t("proxyStatus.lastRequest")}</span>
           <span className="text-foreground">
             {activeUserStatus.lastRequest
               ? `${activeUserStatus.lastRequest.providerName} / ${activeUserStatus.lastRequest.model}`
-              : "暂无记录"}
+              : t("proxyStatus.noRecord")}
           </span>
           {activeUserStatus.lastRequest && (
             <span className="text-muted-foreground">
@@ -162,7 +168,14 @@ export function KeyListHeader({
         </div>
       </div>
     );
-  }, [proxyStatusEnabled, proxyStatusLoading, proxyStatusError, activeUserStatus]);
+  }, [
+    proxyStatusEnabled,
+    proxyStatusLoading,
+    proxyStatusError,
+    activeUserStatus,
+    t,
+    formatRelativeTime,
+  ]);
 
   const handleKeyCreated = (result: { generatedKey: string; name: string }) => {
     setOpenAdd(false); // 关闭表单dialog
@@ -200,8 +213,8 @@ export function KeyListHeader({
           <div className="mt-1">
             <div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground">
               <div>
-                今日用量 {activeUser ? formatCurrency(totalTodayUsage, currencyCode) : "-"} /{" "}
-                {activeUser ? formatCurrency(activeUser.dailyQuota, currencyCode) : "-"}
+                {t("todayUsage")} {activeUser ? formatCurrency(totalTodayUsage, currencyCode) : "-"}{" "}
+                / {activeUser ? formatCurrency(activeUser.dailyQuota, currencyCode) : "-"}
               </div>
               {proxyStatusContent}
             </div>
@@ -216,7 +229,7 @@ export function KeyListHeader({
                 className="hover:bg-primary hover:text-primary-foreground cursor-pointer transition-colors"
                 disabled={!activeUser}
               >
-                <ListPlus className="h-3.5 w-3.5" /> 新增 Key
+                <ListPlus className="h-3.5 w-3.5" /> {t("addKey")}
               </Button>
             </DialogTrigger>
             <DialogContent>
@@ -234,17 +247,17 @@ export function KeyListHeader({
           <DialogHeader>
             <DialogTitle className="flex items-center gap-2">
               <CheckCircle className="h-5 w-5 text-green-600" />
-              Key 创建成功
+              {t("keyCreatedDialog.title")}
             </DialogTitle>
-            <DialogDescription>
-              你的 API Key 已成功创建。请务必复制并妥善保存,此密钥仅显示一次。
-            </DialogDescription>
+            <DialogDescription>{t("keyCreatedDialog.description")}</DialogDescription>
           </DialogHeader>
 
           {keyResult && (
             <div className="space-y-4">
               <div>
-                <label className="text-sm font-medium mb-2 block">API Key</label>
+                <label className="text-sm font-medium mb-2 block">
+                  {t("keyCreatedDialog.apiKeyLabel")}
+                </label>
                 <div className="relative">
                   <div className="p-3 bg-muted/50 rounded-md font-mono text-sm break-all border-2 border-dashed border-orange-300 pr-12">
                     {keyResult.generatedKey}
@@ -263,7 +276,7 @@ export function KeyListHeader({
                   </Button>
                 </div>
                 <p className="text-xs text-muted-foreground mt-2">
-                  请在关闭前复制并保存,关闭后将无法再次查看此密钥
+                  {t("keyCreatedDialog.warningText")}
                 </p>
               </div>
             </div>
@@ -271,7 +284,7 @@ export function KeyListHeader({
 
           <DialogFooter>
             <Button onClick={handleCloseSuccess} variant="secondary">
-              关闭
+              {t("keyCreatedDialog.closeButton")}
             </Button>
           </DialogFooter>
         </DialogContent>

+ 6 - 4
src/app/[locale]/dashboard/_components/user/user-actions.tsx

@@ -1,6 +1,7 @@
 "use client";
 import { useState } from "react";
 import { SquarePen, Trash } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
 import { UserForm } from "./forms/user-form";
 import { DeleteUserConfirm } from "./forms/delete-user-confirm";
@@ -15,6 +16,7 @@ interface UserActionsProps {
 export function UserActions({ user, currentUser }: UserActionsProps) {
   const [openEdit, setOpenEdit] = useState(false);
   const [openDelete, setOpenDelete] = useState(false);
+  const t = useTranslations("dashboard.userActions");
 
   // 权限检查:只有管理员才能编辑用户信息
   const canEditUser = currentUser?.role === "admin";
@@ -31,9 +33,9 @@ export function UserActions({ user, currentUser }: UserActionsProps) {
         <DialogTrigger asChild>
           <button
             type="button"
-            aria-label="编辑用户"
+            aria-label={t("editAriaLabel")}
             className="inline-flex items-center justify-center p-1 text-muted-foreground hover:text-foreground transition-colors"
-            title="编辑用户"
+            title={t("edit")}
           >
             <SquarePen className="h-3.5 w-3.5" />
           </button>
@@ -50,9 +52,9 @@ export function UserActions({ user, currentUser }: UserActionsProps) {
         <DialogTrigger asChild>
           <button
             type="button"
-            aria-label="删除用户"
+            aria-label={t("deleteAriaLabel")}
             className="inline-flex items-center justify-center p-1 text-muted-foreground hover:text-red-600 transition-colors"
-            title="删除用户"
+            title={t("delete")}
           >
             <Trash className="h-3.5 w-3.5" />
           </button>

+ 8 - 5
src/app/[locale]/dashboard/_components/user/user-list.tsx

@@ -3,6 +3,7 @@ import type { UserDisplay } from "@/types/user";
 import type { User } from "@/types/user";
 import { ListContainer, ListItem, ListItemData } from "@/components/ui/list";
 import { AddUserDialog } from "./add-user-dialog";
+import { useTranslations } from "next-intl";
 
 interface UserListProps {
   users: UserDisplay[];
@@ -12,22 +13,24 @@ interface UserListProps {
 }
 
 export function UserList({ users, activeUserId, onUserSelect, currentUser }: UserListProps) {
+  const t = useTranslations("dashboard.userList");
+
   // 转换数据格式
   const listItems: ListItemData[] = users.map((user) => ({
     id: user.id,
     title: user.name,
     subtitle: user.note,
     badge: {
-      text: `${user.keys.length} 个 Key`,
+      text: t("badge", { count: user.keys.length }),
       variant: "outline" as const,
     },
     metadata: [
       {
-        label: "活跃密钥",
+        label: t("activeKeys"),
         value: user.keys.filter((k) => k.status === "enabled").length.toString(),
       },
       {
-        label: "总密钥",
+        label: t("totalKeys"),
         value: user.keys.length.toString(),
       },
     ],
@@ -37,8 +40,8 @@ export function UserList({ users, activeUserId, onUserSelect, currentUser }: Use
     <div className="space-y-3">
       <ListContainer
         emptyState={{
-          title: "暂无用户",
-          description: "点击下方按钮创建第一个用户",
+          title: t("emptyState.title"),
+          description: t("emptyState.description"),
         }}
       >
         <div className="space-y-2">

+ 16 - 14
src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx

@@ -22,6 +22,7 @@ import { QuotaWindowType } from "@/components/quota/quota-window-type";
 import { QuotaCountdownCompact } from "@/components/quota/quota-countdown";
 import { hasKeyQuotaSet, isUserExceeded, getUsageRate } from "@/lib/utils/quota-helpers";
 import { EditKeyQuotaDialog } from "./edit-key-quota-dialog";
+import { useTranslations } from "next-intl";
 
 interface KeyQuota {
   cost5h: { current: number; limit: number | null; resetAt?: Date };
@@ -57,6 +58,7 @@ interface KeysQuotaClientProps {
 }
 
 export function KeysQuotaClient({ users, currencyCode = "USD" }: KeysQuotaClientProps) {
+  const t = useTranslations("quota.keys");
   // 默认展开所有用户组
   const [openUsers, setOpenUsers] = useState<Set<number>>(new Set(users.map((user) => user.id)));
 
@@ -76,7 +78,7 @@ export function KeysQuotaClient({ users, currencyCode = "USD" }: KeysQuotaClient
     return (
       <Card>
         <CardContent className="flex items-center justify-center py-10">
-          <p className="text-muted-foreground">没有匹配的用户或密钥</p>
+          <p className="text-muted-foreground">{t("noMatches")}</p>
         </CardContent>
       </Card>
     );
@@ -115,14 +117,14 @@ export function KeysQuotaClient({ users, currencyCode = "USD" }: KeysQuotaClient
                 <Table>
                   <TableHeader>
                     <TableRow>
-                      <TableHead className="w-[200px]">密钥名称</TableHead>
-                      <TableHead className="w-[120px]">限额类型</TableHead>
-                      <TableHead className="w-[150px]">5小时限额</TableHead>
-                      <TableHead className="w-[150px]">周限额</TableHead>
-                      <TableHead className="w-[150px]">月限额</TableHead>
-                      <TableHead className="w-[120px]">并发限制</TableHead>
-                      <TableHead className="w-[100px]">状态</TableHead>
-                      <TableHead className="w-[100px] text-right">操作</TableHead>
+                      <TableHead className="w-[200px]">{t("table.keyName")}</TableHead>
+                      <TableHead className="w-[120px]">{t("table.quotaType")}</TableHead>
+                      <TableHead className="w-[150px]">{t("table.cost5h")}</TableHead>
+                      <TableHead className="w-[150px]">{t("table.costWeekly")}</TableHead>
+                      <TableHead className="w-[150px]">{t("table.costMonthly")}</TableHead>
+                      <TableHead className="w-[120px]">{t("table.concurrentSessions")}</TableHead>
+                      <TableHead className="w-[100px]">{t("table.status")}</TableHead>
+                      <TableHead className="w-[100px] text-right">{t("table.actions")}</TableHead>
                     </TableRow>
                   </TableHeader>
                   <TableBody>
@@ -138,9 +140,9 @@ export function KeysQuotaClient({ users, currencyCode = "USD" }: KeysQuotaClient
                           {/* 限额类型 */}
                           <TableCell>
                             {hasKeyQuota ? (
-                              <Badge variant="default">独立限额</Badge>
+                              <Badge variant="default">{t("quotaType.independent")}</Badge>
                             ) : (
-                              <Badge variant="outline">继承用户</Badge>
+                              <Badge variant="outline">{t("quotaType.inherited")}</Badge>
                             )}
                           </TableCell>
 
@@ -283,10 +285,10 @@ export function KeysQuotaClient({ users, currencyCode = "USD" }: KeysQuotaClient
                               className="text-xs"
                             >
                               {!key.isEnabled
-                                ? "已禁用"
+                                ? t("status.disabled")
                                 : userExceeded && !hasKeyQuota
-                                  ? "受限"
-                                  : "正常"}
+                                  ? t("status.restricted")
+                                  : t("status.normal")}
                             </Badge>
                           </TableCell>
 

+ 39 - 28
src/app/[locale]/settings/prices/_components/price-list.tsx

@@ -2,6 +2,7 @@
 
 import { useState, useEffect } from "react";
 import { Search, Package, DollarSign, ChevronLeft, ChevronRight } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { Input } from "@/components/ui/input";
 import { Button } from "@/components/ui/button";
 import {
@@ -39,6 +40,7 @@ export function PriceList({
   initialPage,
   initialPageSize,
 }: PriceListProps) {
+  const t = useTranslations("settings.prices");
   const [searchTerm, setSearchTerm] = useState("");
   const [prices, setPrices] = useState<ModelPrice[]>(initialPrices);
   const [total, setTotal] = useState(initialTotal);
@@ -169,13 +171,13 @@ export function PriceList({
   const getModeLabel = (mode?: string) => {
     switch (mode) {
       case "chat":
-        return <Badge variant="default">对话</Badge>;
+        return <Badge variant="default">{t("table.typeChat")}</Badge>;
       case "image_generation":
-        return <Badge variant="secondary">图像生成</Badge>;
+        return <Badge variant="secondary">{t("table.typeImage")}</Badge>;
       case "completion":
-        return <Badge variant="outline">补全</Badge>;
+        return <Badge variant="outline">{t("table.typeCompletion")}</Badge>;
       default:
-        return <Badge variant="outline">未知</Badge>;
+        return <Badge variant="outline">{t("table.typeUnknown")}</Badge>;
     }
   };
 
@@ -186,14 +188,16 @@ export function PriceList({
         <div className="relative flex-1">
           <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
           <Input
-            placeholder="搜索模型名称..."
+            placeholder={t("searchPlaceholder")}
             value={searchTerm}
             onChange={(e) => handleSearchChange(e.target.value)}
             className="pl-9"
           />
         </div>
         <div className="flex items-center gap-2">
-          <span className="text-sm text-muted-foreground">每页显示:</span>
+          <span className="text-sm text-muted-foreground">
+            {t("pagination.perPage", { size: "" }).replace(/\d+/, "")}
+          </span>
           <Select
             value={pageSize.toString()}
             onValueChange={(value) => handlePageSizeChange(parseInt(value, 10))}
@@ -216,12 +220,12 @@ export function PriceList({
         <Table className="table-fixed">
           <TableHeader>
             <TableRow>
-              <TableHead className="w-48 whitespace-normal">模型名称</TableHead>
-              <TableHead className="w-24">类型</TableHead>
-              <TableHead className="w-32 whitespace-normal">提供商</TableHead>
-              <TableHead className="w-32 text-right">输入价格 ($/M)</TableHead>
-              <TableHead className="w-32 text-right">输出价格 ($/M)</TableHead>
-              <TableHead className="w-32">更新时间</TableHead>
+              <TableHead className="w-48 whitespace-normal">{t("table.modelName")}</TableHead>
+              <TableHead className="w-24">{t("table.type")}</TableHead>
+              <TableHead className="w-32 whitespace-normal">{t("table.provider")}</TableHead>
+              <TableHead className="w-32 text-right">{t("table.inputPrice")}</TableHead>
+              <TableHead className="w-32 text-right">{t("table.outputPrice")}</TableHead>
+              <TableHead className="w-32">{t("table.updatedAt")}</TableHead>
             </TableRow>
           </TableHeader>
           <TableBody>
@@ -230,7 +234,7 @@ export function PriceList({
                 <TableCell colSpan={6} className="text-center py-8">
                   <div className="flex items-center justify-center gap-2 text-muted-foreground">
                     <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-current"></div>
-                    <span>加载中...</span>
+                    <span>{t("table.loading")}</span>
                   </div>
                 </TableCell>
               </TableRow>
@@ -276,13 +280,13 @@ export function PriceList({
                     {searchTerm ? (
                       <>
                         <Search className="h-8 w-8 opacity-50" />
-                        <p>未找到匹配的模型</p>
+                        <p>{t("table.noMatch")}</p>
                       </>
                     ) : (
                       <>
                         <Package className="h-8 w-8 opacity-50" />
-                        <p>暂无价格数据</p>
-                        <p className="text-sm">系统已内置价格表,请通过上方按钮同步或更新</p>
+                        <p>{t("table.noDataTitle")}</p>
+                        <p className="text-sm">{t("table.noDataHint")}</p>
                       </>
                     )}
                   </div>
@@ -297,8 +301,11 @@ export function PriceList({
       {totalPages > 1 && (
         <div className="flex items-center justify-between">
           <div className="text-sm text-muted-foreground">
-            显示第 {(page - 1) * pageSize + 1} - {Math.min(page * pageSize, total)} 条,共 {total}{" "}
-            条记录
+            {t("pagination.showing", {
+              from: (page - 1) * pageSize + 1,
+              to: Math.min(page * pageSize, total),
+              total: total,
+            })}
           </div>
           <div className="flex items-center gap-2">
             <Button
@@ -308,7 +315,7 @@ export function PriceList({
               disabled={page <= 1 || isLoading}
             >
               <ChevronLeft className="h-4 w-4" />
-              上一页
+              {t("pagination.previous")}
             </Button>
 
             <div className="flex items-center gap-1">
@@ -346,7 +353,7 @@ export function PriceList({
               onClick={() => handlePageChange(page + 1)}
               disabled={page >= totalPages || isLoading}
             >
-              下一页
+              {t("pagination.next")}
               <ChevronRight className="h-4 w-4" />
             </Button>
           </div>
@@ -357,18 +364,22 @@ export function PriceList({
       <div className="flex items-center justify-between text-sm text-muted-foreground">
         <div className="flex items-center gap-1">
           <DollarSign className="h-4 w-4" />
-          <span>共 {total} 个模型价格</span>
+          <span>{t("stats.totalModels", { count: total })}</span>
           {searchTerm && (
-            <span className="text-muted-foreground">(搜索结果:{filteredPrices.length} 个)</span>
+            <span className="text-muted-foreground">
+              ({t("stats.searchResults", { count: filteredPrices.length })})
+            </span>
           )}
         </div>
         <div>
-          最后更新:
-          {prices.length > 0
-            ? new Date(
-                Math.max(...prices.map((p) => new Date(p.createdAt).getTime()))
-              ).toLocaleDateString("zh-CN")
-            : "-"}
+          {t("stats.lastUpdated", {
+            time:
+              prices.length > 0
+                ? new Date(
+                    Math.max(...prices.map((p) => new Date(p.createdAt).getTime()))
+                  ).toLocaleDateString()
+                : "-",
+          })}
         </div>
       </div>
     </div>

+ 41 - 33
src/app/[locale]/settings/prices/_components/upload-price-dialog.tsx

@@ -3,6 +3,7 @@
 import { useEffect, useState } from "react";
 import { useRouter } from "next/navigation";
 import { createPortal } from "react-dom";
+import { useTranslations } from "next-intl";
 import { Upload, FileJson, CheckCircle, XCircle, AlertCircle, Loader2 } from "lucide-react";
 import { Button } from "@/components/ui/button";
 import {
@@ -27,6 +28,7 @@ interface UploadPriceDialogProps {
 }
 
 function PageLoadingOverlay({ active }: PageLoadingOverlayProps) {
+  const t = useTranslations("settings.prices");
   const [mounted, setMounted] = useState(false);
 
   useEffect(() => {
@@ -41,7 +43,7 @@ function PageLoadingOverlay({ active }: PageLoadingOverlayProps) {
     <div className="fixed inset-0 z-[9999] flex items-center justify-center bg-background/80 backdrop-blur-sm">
       <div className="flex items-center gap-3 rounded-lg bg-card/90 px-5 py-4 shadow-lg ring-1 ring-border/40">
         <Loader2 className="h-5 w-5 animate-spin text-primary" />
-        <span className="text-sm text-muted-foreground">正在更新模型价格...</span>
+        <span className="text-sm text-muted-foreground">{t("dialog.updating")}</span>
       </div>
     </div>,
     document.body
@@ -55,6 +57,7 @@ export function UploadPriceDialog({
   defaultOpen = false,
   isRequired = false,
 }: UploadPriceDialogProps) {
+  const t = useTranslations("settings.prices");
   const router = useRouter();
   const [open, setOpen] = useState(defaultOpen);
   const [uploading, setUploading] = useState(false);
@@ -81,13 +84,13 @@ export function UploadPriceDialog({
 
     // 验证文件类型
     if (!file.name.endsWith(".json")) {
-      toast.error("请选择JSON文件");
+      toast.error(t("dialog.invalidFileType"));
       return;
     }
 
     // 验证文件大小(限制10MB)
     if (file.size > 10 * 1024 * 1024) {
-      toast.error("文件大小不能超过10MB");
+      toast.error(t("dialog.fileTooLarge"));
       return;
     }
 
@@ -107,15 +110,16 @@ export function UploadPriceDialog({
       }
 
       if (!response.data) {
-        toast.error("价格表更新成功但未返回处理结果");
+        toast.error(t("dialog.updateFailed"));
         return;
       }
 
       setResult(response.data);
-      toast.success("价格表更新成功");
+      const totalUpdates = response.data.added.length + response.data.updated.length;
+      toast.success(t("dialog.updateSuccess", { count: totalUpdates }));
     } catch (error) {
       console.error("更新失败:", error);
-      toast.error("更新失败,请重试");
+      toast.error(t("dialog.updateFailed"));
     } finally {
       setUploading(false);
       // 清除文件输入
@@ -148,7 +152,7 @@ export function UploadPriceDialog({
         <DialogTrigger asChild>
           <Button variant="outline" size="sm" disabled={uploading}>
             <Upload className="h-4 w-4 mr-2" />
-            更新价格表
+            {t("dialog.updatePriceTable")}
           </Button>
         </DialogTrigger>
         <DialogContent
@@ -165,12 +169,8 @@ export function UploadPriceDialog({
           }}
         >
           <DialogHeader>
-            <DialogTitle>{isRequired ? "更新模型价格表" : "更新模型价格表"}</DialogTitle>
-            <DialogDescription>
-              {isRequired
-                ? "选择包含模型价格数据的 JSON 文件以更新价格表"
-                : "选择包含模型价格数据的 JSON 文件以更新价格表"}
-            </DialogDescription>
+            <DialogTitle>{t("dialog.title")}</DialogTitle>
+            <DialogDescription>{t("dialog.description")}</DialogDescription>
           </DialogHeader>
 
           {!result ? (
@@ -179,12 +179,14 @@ export function UploadPriceDialog({
                 <div className="flex flex-col items-center space-y-3">
                   <FileJson className="h-10 w-10 text-muted-foreground/50" />
                   <div className="text-center">
-                    <p className="text-sm text-muted-foreground">点击选择JSON文件或拖拽到此处</p>
-                    <p className="text-xs text-muted-foreground mt-1">文件大小不超过10MB</p>
+                    <p className="text-sm text-muted-foreground">{t("dialog.selectFile")}</p>
+                    <p className="text-xs text-muted-foreground mt-1">
+                      {t("dialog.fileSizeLimitSmall")}
+                    </p>
                   </div>
                   <label htmlFor="price-file-input">
                     <Button variant="secondary" size="sm" disabled={uploading} asChild>
-                      <span>{uploading ? "更新中..." : "选择文件"}</span>
+                      <span>{uploading ? t("dialog.updating") : t("dialog.selectJson")}</span>
                     </Button>
                   </label>
                   <input
@@ -199,29 +201,27 @@ export function UploadPriceDialog({
               </div>
 
               <div className="text-xs text-muted-foreground space-y-1">
+                <p>• {t("dialog.systemHasBuiltIn")}</p>
                 <p>
-                  • 系统已内置 LiteLLM 价格表,如需更新可使用左侧&quot;同步 LiteLLM 价格&quot;按钮
-                </p>
-                <p>
-                  • 也可以手动下载{" "}
+                  • {t("dialog.manualDownload")}{" "}
                   <a
                     className="text-blue-500 underline"
                     href="https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
                     target="_blank"
                     rel="noopener noreferrer"
                   >
-                    最新价格表
+                    {t("dialog.latestPriceTable")}
                   </a>{" "}
-                  并通过此按钮更新
+                  {t("dialog.andUploadViaButton")}
                 </p>
-                <p>• 支持 Claude 和 OpenAI 模型(claude-, gpt-, o1-, o3- 前缀)</p>
+                <p>• {t("dialog.supportedModels", { count: "Claude + OpenAI" })}</p>
               </div>
             </div>
           ) : (
             <div className="space-y-4">
               <div className="text-sm space-y-2">
                 <div className="flex items-center justify-between p-2 bg-muted/50 rounded">
-                  <span>处理总数</span>
+                  <span>{t("dialog.results.total", { total: result.total })}</span>
                   <span className="font-mono">{result.total}</span>
                 </div>
 
@@ -229,11 +229,13 @@ export function UploadPriceDialog({
                   <div className="p-2 bg-green-50 dark:bg-green-950/20 rounded">
                     <div className="flex items-center gap-2 mb-1">
                       <CheckCircle className="h-4 w-4 text-green-600" />
-                      <span className="font-medium">新增模型 ({result.added.length})</span>
+                      <span className="font-medium">
+                        {t("dialog.results.success", { success: result.added.length })}
+                      </span>
                     </div>
                     <div className="text-xs text-muted-foreground ml-6">
                       {result.added.slice(0, 3).join(", ")}
-                      {result.added.length > 3 && ` 等${result.added.length}个`}
+                      {result.added.length > 3 && ` (+${result.added.length - 3})`}
                     </div>
                   </div>
                 )}
@@ -242,11 +244,13 @@ export function UploadPriceDialog({
                   <div className="p-2 bg-blue-50 dark:bg-blue-950/20 rounded">
                     <div className="flex items-center gap-2 mb-1">
                       <AlertCircle className="h-4 w-4 text-blue-600" />
-                      <span className="font-medium">更新模型 ({result.updated.length})</span>
+                      <span className="font-medium">
+                        {t("dialog.results.success", { success: result.updated.length })}
+                      </span>
                     </div>
                     <div className="text-xs text-muted-foreground ml-6">
                       {result.updated.slice(0, 3).join(", ")}
-                      {result.updated.length > 3 && ` 等${result.updated.length}个`}
+                      {result.updated.length > 3 && ` (+${result.updated.length - 3})`}
                     </div>
                   </div>
                 )}
@@ -254,7 +258,9 @@ export function UploadPriceDialog({
                 {result.unchanged.length > 0 && (
                   <div className="p-2 bg-gray-50 dark:bg-gray-950/20 rounded">
                     <div className="flex items-center gap-2">
-                      <span className="font-medium">未变化 ({result.unchanged.length})</span>
+                      <span className="font-medium">
+                        {t("dialog.results.skipped", { skipped: result.unchanged.length })}
+                      </span>
                     </div>
                   </div>
                 )}
@@ -263,11 +269,13 @@ export function UploadPriceDialog({
                   <div className="p-2 bg-red-50 dark:bg-red-950/20 rounded">
                     <div className="flex items-center gap-2 mb-1">
                       <XCircle className="h-4 w-4 text-red-600" />
-                      <span className="font-medium">处理失败 ({result.failed.length})</span>
+                      <span className="font-medium">
+                        {t("dialog.results.failed", { failed: result.failed.length })}
+                      </span>
                     </div>
                     <div className="text-xs text-muted-foreground ml-6">
                       {result.failed.slice(0, 3).join(", ")}
-                      {result.failed.length > 3 && ` 等${result.failed.length}个`}
+                      {result.failed.length > 3 && ` (+${result.failed.length - 3})`}
                     </div>
                   </div>
                 )}
@@ -275,8 +283,8 @@ export function UploadPriceDialog({
 
               <Button onClick={handleClose} className="w-full">
                 {isRequired && result && (result.added.length > 0 || result.updated.length > 0)
-                  ? "进入控制面板"
-                  : "完成"}
+                  ? t("common.confirm")
+                  : t("common.completed")}
               </Button>
             </div>
           )}

+ 8 - 3
src/app/[locale]/settings/providers/_components/provider-list-item.legacy.tsx

@@ -15,7 +15,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
 import { Edit, Globe, Key, RotateCcw, Copy, CheckCircle } from "lucide-react";
 import type { ProviderDisplay } from "@/types/provider";
 import type { User } from "@/types/user";
-import { getProviderTypeConfig } from "@/lib/provider-type-utils";
+import { getProviderTypeConfig, getProviderTypeTranslationKey } from "@/lib/provider-type-utils";
+import { useTranslations } from "next-intl";
 import { ProviderForm } from "./forms/provider-form";
 import { Switch } from "@/components/ui/switch";
 import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
@@ -67,6 +68,7 @@ export function ProviderListItem({
   const [copied, setCopied] = useState(false);
   const [resetPending, startResetTransition] = useTransition();
   const canEdit = currentUser?.role === "admin";
+  const t = useTranslations("providers.types");
 
   const {
     enabled,
@@ -105,6 +107,9 @@ export function ProviderListItem({
   // 获取供应商类型配置
   const typeConfig = getProviderTypeConfig(item.providerType);
   const TypeIcon = typeConfig.icon;
+  const typeKey = getProviderTypeTranslationKey(item.providerType);
+  const typeLabel = t(`${typeKey}.label`);
+  const typeDescription = t(`${typeKey}.description`);
 
   // 处理手动解除熔断
   const handleResetCircuit = () => {
@@ -179,7 +184,7 @@ export function ProviderListItem({
             {/* 供应商类型图标 */}
             <span
               className={`inline-flex h-5 w-5 items-center justify-center rounded-md ${typeConfig.bgColor}`}
-              title={typeConfig.description}
+              title={typeDescription}
             >
               <TypeIcon className={`h-3 w-3 ${typeConfig.iconColor}`} />
             </span>
@@ -188,7 +193,7 @@ export function ProviderListItem({
             </h3>
             {/* 供应商类型标签 */}
             <Badge variant="outline" className="text-[10px] h-4 px-1.5 font-normal">
-              {typeConfig.label}
+              {typeLabel}
             </Badge>
 
             {/* 熔断器状态徽章 */}

+ 5 - 2
src/app/[locale]/settings/providers/_components/provider-list.tsx

@@ -1,5 +1,6 @@
 "use client";
 import { Globe } from "lucide-react";
+import { useTranslations } from "next-intl";
 import type { ProviderDisplay } from "@/types/provider";
 import type { User } from "@/types/user";
 import { ProviderRichListItem } from "./provider-rich-list-item";
@@ -29,14 +30,16 @@ export function ProviderList({
   currencyCode = "USD",
   enableMultiProviderTypes,
 }: ProviderListProps) {
+  const t = useTranslations("settings.providers");
+
   if (providers.length === 0) {
     return (
       <div className="flex flex-col items-center justify-center py-12 px-4">
         <div className="w-12 h-12 rounded-full bg-muted/50 flex items-center justify-center mb-3">
           <Globe className="h-6 w-6 text-muted-foreground" />
         </div>
-        <h3 className="font-medium text-foreground mb-1">暂无服务商配置</h3>
-        <p className="text-sm text-muted-foreground text-center">添加你的第一个 API 服务商</p>
+        <h3 className="font-medium text-foreground mb-1">{t("noProviders")}</h3>
+        <p className="text-sm text-muted-foreground text-center">{t("noProvidersDesc")}</p>
       </div>
     );
   }

+ 47 - 37
src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx

@@ -16,7 +16,7 @@ import {
 } from "lucide-react";
 import type { ProviderDisplay } from "@/types/provider";
 import type { User } from "@/types/user";
-import { getProviderTypeConfig } from "@/lib/provider-type-utils";
+import { getProviderTypeConfig, getProviderTypeTranslationKey } from "@/lib/provider-type-utils";
 import {
   Dialog,
   DialogContent,
@@ -24,6 +24,7 @@ import {
   DialogTitle,
   DialogDescription,
 } from "@/components/ui/dialog";
+import { useTranslations } from "next-intl";
 import { ProviderForm } from "./forms/provider-form";
 import { FormErrorBoundary } from "@/components/form-error-boundary";
 import { getUnmaskedProviderKey, resetProviderCircuit, removeProvider } from "@/actions/providers";
@@ -81,10 +82,16 @@ export function ProviderRichListItem({
   const [togglePending, startToggleTransition] = useTransition();
 
   const canEdit = currentUser?.role === "admin";
+  const tTypes = useTranslations("providers.types");
+  const tList = useTranslations("settings.providers.list");
+  const tCommon = useTranslations("settings.common");
 
   // 获取供应商类型配置
   const typeConfig = getProviderTypeConfig(provider.providerType);
   const TypeIcon = typeConfig.icon;
+  const typeKey = getProviderTypeTranslationKey(provider.providerType);
+  const typeLabel = tTypes(`${typeKey}.label`);
+  const typeDescription = tTypes(`${typeKey}.description`);
 
   // 处理编辑
   const handleEdit = () => {
@@ -113,19 +120,19 @@ export function ProviderRichListItem({
         try {
           const res = await removeProvider(provider.id);
           if (res.ok) {
-            toast.success("删除成功", {
-              description: `供应商 "${provider.name}" 已删除`,
+            toast.success(tList("deleteSuccess"), {
+              description: tList("deleteSuccessDesc", { name: provider.name }),
             });
             router.refresh();
           } else {
-            toast.error("删除失败", {
-              description: res.error || "未知错误",
+            toast.error(tList("deleteFailed"), {
+              description: res.error || tList("unknownError"),
             });
           }
         } catch (error) {
           console.error("删除供应商失败:", error);
-          toast.error("删除失败", {
-            description: "操作过程中出现异常",
+          toast.error(tList("deleteFailed"), {
+            description: tList("deleteError"),
           });
         }
       });
@@ -139,8 +146,8 @@ export function ProviderRichListItem({
     if (result.ok) {
       setUnmaskedKey(result.data.key);
     } else {
-      toast.error("获取密钥失败", {
-        description: result.error || "未知错误",
+      toast.error(tList("getKeyFailed"), {
+        description: result.error || tList("unknownError"),
       });
       setShowKeyDialog(false);
     }
@@ -152,11 +159,11 @@ export function ProviderRichListItem({
       try {
         await navigator.clipboard.writeText(unmaskedKey);
         setCopied(true);
-        toast.success("密钥已复制到剪贴板");
+        toast.success(tList("keyCopied"));
         setTimeout(() => setCopied(false), 3000);
       } catch (error) {
         console.error("复制失败:", error);
-        toast.error("复制失败");
+        toast.error(tList("copyFailed"));
       }
     }
   };
@@ -174,19 +181,19 @@ export function ProviderRichListItem({
       try {
         const res = await resetProviderCircuit(provider.id);
         if (res.ok) {
-          toast.success("熔断器已重置", {
-            description: `供应商 "${provider.name}" 的熔断状态已解除`,
+          toast.success(tList("resetCircuitSuccess"), {
+            description: tList("resetCircuitSuccessDesc", { name: provider.name }),
           });
           router.refresh();
         } else {
-          toast.error("重置熔断器失败", {
-            description: res.error || "未知错误",
+          toast.error(tList("resetCircuitFailed"), {
+            description: res.error || tList("unknownError"),
           });
         }
       } catch (error) {
         console.error("重置熔断器失败:", error);
-        toast.error("重置熔断器失败", {
-          description: "操作过程中出现异常",
+        toast.error(tList("resetCircuitFailed"), {
+          description: tList("deleteError"),
         });
       }
     });
@@ -200,19 +207,20 @@ export function ProviderRichListItem({
           is_enabled: !provider.isEnabled,
         });
         if (res.ok) {
-          toast.success(`供应商已${!provider.isEnabled ? "启用" : "禁用"}`, {
-            description: `供应商 "${provider.name}" 状态已更新`,
+          const status = !provider.isEnabled ? tList("statusEnabled") : tList("statusDisabled");
+          toast.success(tList("toggleSuccess", { status }), {
+            description: tList("toggleSuccessDesc", { name: provider.name }),
           });
           router.refresh();
         } else {
-          toast.error("状态切换失败", {
-            description: res.error || "未知错误",
+          toast.error(tList("toggleFailed"), {
+            description: res.error || tList("unknownError"),
           });
         }
       } catch (error) {
         console.error("状态切换失败:", error);
-        toast.error("状态切换失败", {
-          description: "操作过程中出现异常",
+        toast.error(tList("toggleFailed"), {
+          description: tList("deleteError"),
         });
       }
     });
@@ -268,7 +276,7 @@ export function ProviderRichListItem({
             {healthStatus && healthStatus.circuitState === "open" && (
               <Badge variant="destructive" className="flex items-center gap-1 flex-shrink-0">
                 <AlertTriangle className="h-3 w-3" />
-                熔断中
+                {tList("circuitBroken")}
               </Badge>
             )}
           </div>
@@ -287,7 +295,7 @@ export function ProviderRichListItem({
                 onClick={(e) => e.stopPropagation()}
               >
                 <Globe className="h-3 w-3" />
-                官网
+                {tList("officialWebsite")}
               </a>
             )}
 
@@ -310,23 +318,25 @@ export function ProviderRichListItem({
         {/* 右侧:指标(仅桌面端) */}
         <div className="hidden md:grid grid-cols-3 gap-4 text-center flex-shrink-0">
           <div>
-            <div className="text-xs text-muted-foreground">优先级</div>
+            <div className="text-xs text-muted-foreground">{tList("priority")}</div>
             <div className="font-medium">{provider.priority}</div>
           </div>
           <div>
-            <div className="text-xs text-muted-foreground">权重</div>
+            <div className="text-xs text-muted-foreground">{tList("weight")}</div>
             <div className="font-medium">{provider.weight}</div>
           </div>
           <div>
-            <div className="text-xs text-muted-foreground">成本倍数</div>
+            <div className="text-xs text-muted-foreground">{tList("costMultiplier")}</div>
             <div className="font-medium">{provider.costMultiplier}x</div>
           </div>
         </div>
 
         {/* 今日用量(仅大屏) */}
         <div className="hidden lg:block text-center flex-shrink-0 min-w-[100px]">
-          <div className="text-xs text-muted-foreground">今日用量</div>
-          <div className="font-medium">{provider.todayCallCount || 0} 次</div>
+          <div className="text-xs text-muted-foreground">{tList("todayUsageLabel")}</div>
+          <div className="font-medium">
+            {tList("todayUsageCount", { count: provider.todayCallCount || 0 })}
+          </div>
           <div className="text-xs font-mono text-muted-foreground mt-0.5">
             {formatCurrency(parseFloat(provider.todayTotalCostUsd || "0"), currencyCode)}
           </div>
@@ -404,13 +414,13 @@ export function ProviderRichListItem({
               </AlertDialogTrigger>
               <AlertDialogContent>
                 <AlertDialogHeader>
-                  <AlertDialogTitle>确认删除供应商?</AlertDialogTitle>
+                  <AlertDialogTitle>{tList("confirmDeleteTitle")}</AlertDialogTitle>
                   <AlertDialogDescription>
-                    确定要删除供应商 &quot;{provider.name}&quot; 吗?此操作无法撤销。
+                    {tList("confirmDeleteMessage", { name: provider.name })}
                   </AlertDialogDescription>
                 </AlertDialogHeader>
                 <div className="flex justify-end gap-2">
-                  <AlertDialogCancel>取消</AlertDialogCancel>
+                  <AlertDialogCancel>{tList("cancelButton")}</AlertDialogCancel>
                   <AlertDialogAction
                     onClick={(e) => {
                       e.stopPropagation();
@@ -419,7 +429,7 @@ export function ProviderRichListItem({
                     className="bg-red-600 hover:bg-red-700"
                     disabled={deletePending}
                   >
-                    删除
+                    {tList("deleteButton")}
                   </AlertDialogAction>
                 </div>
               </AlertDialogContent>
@@ -466,13 +476,13 @@ export function ProviderRichListItem({
       <Dialog open={showKeyDialog} onOpenChange={handleCloseDialog}>
         <DialogContent className="max-w-lg">
           <DialogHeader>
-            <DialogTitle>查看完整 API Key</DialogTitle>
-            <DialogDescription>请妥善保管,不要泄露给他人</DialogDescription>
+            <DialogTitle>{tList("viewFullKey")}</DialogTitle>
+            <DialogDescription>{tList("viewFullKeyDesc")}</DialogDescription>
           </DialogHeader>
           <div className="space-y-4">
             <div className="flex items-center gap-2">
               <code className="flex-1 font-mono bg-muted px-3 py-2 rounded text-sm break-all">
-                {unmaskedKey || "加载中..."}
+                {unmaskedKey || tList("keyLoading")}
               </code>
               <Button onClick={handleCopy} disabled={!unmaskedKey} size="icon" variant="outline">
                 {copied ? (

+ 15 - 4
src/app/[locale]/settings/providers/_components/provider-type-filter.tsx

@@ -8,7 +8,12 @@ import {
 } from "@/components/ui/select";
 import { Filter } from "lucide-react";
 import type { ProviderType } from "@/types/provider";
-import { PROVIDER_TYPE_CONFIG, getAllProviderTypes } from "@/lib/provider-type-utils";
+import {
+  PROVIDER_TYPE_CONFIG,
+  getAllProviderTypes,
+  getProviderTypeTranslationKey,
+} from "@/lib/provider-type-utils";
+import { useTranslations } from "next-intl";
 
 interface ProviderTypeFilterProps {
   value: ProviderType | "all";
@@ -16,23 +21,29 @@ interface ProviderTypeFilterProps {
 }
 
 export function ProviderTypeFilter({ value, onChange }: ProviderTypeFilterProps) {
+  const tTypes = useTranslations("providers.types");
+  const tForm = useTranslations("settings.providers.form");
+
   return (
     <div className="flex items-center gap-2">
       <Filter className="h-4 w-4 text-muted-foreground" />
       <Select value={value} onValueChange={onChange}>
         <SelectTrigger className="w-[200px]">
-          <SelectValue placeholder="筛选供应商类型" />
+          <SelectValue placeholder={tForm("filterByType")} />
         </SelectTrigger>
         <SelectContent>
-          <SelectItem value="all">全部供应商</SelectItem>
+          <SelectItem value="all">{tForm("filterAllProviders")}</SelectItem>
           {getAllProviderTypes().map((type) => {
             const config = PROVIDER_TYPE_CONFIG[type];
             const Icon = config.icon;
+            const typeKey = getProviderTypeTranslationKey(type);
+            const label = tTypes(`${typeKey}.label`);
+
             return (
               <SelectItem key={type} value={type}>
                 <div className="flex items-center gap-2">
                   <Icon className={`h-3.5 w-3.5 ${config.iconColor}`} />
-                  <span>{config.label}</span>
+                  <span>{label}</span>
                 </div>
               </SelectItem>
             );

+ 150 - 155
src/app/[locale]/settings/providers/_components/scheduling-rules-dialog.tsx

@@ -14,6 +14,7 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
 import { Badge } from "@/components/ui/badge";
 import { Info, ChevronDown, ChevronRight, Lightbulb } from "lucide-react";
 import { useState } from "react";
+import { useTranslations } from "next-intl";
 
 interface ScenarioStep {
   step: string;
@@ -25,134 +26,142 @@ interface ScenarioStep {
   };
 }
 
-const scenarios: Array<{
-  title: string;
-  emoji: string;
-  description: string;
-  steps: ScenarioStep[];
-}> = [
-  {
-    title: "优先级分层选择",
-    emoji: "🎯",
-    description: "系统首先按优先级过滤,只从最高优先级的供应商中选择",
-    steps: [
-      {
-        step: "初始状态",
-        description: "有 4 个已启用的供应商,优先级各不相同",
-        example: {
-          before: "供应商 A (优先级 0), B (优先级 1), C (优先级 0), D (优先级 2)",
-          after: "筛选出最高优先级(0)的供应商:A, C",
-          decision: "只从 A 和 C 中选择,B 和 D 被过滤",
+// Helper function to build scenarios from translations
+function useScenarios() {
+  const t = useTranslations("settings.providers.guide");
+
+  return [
+    {
+      title: t("scenario1Title"),
+      emoji: "🎯",
+      description: t("scenario1Desc"),
+      steps: [
+        {
+          step: t("scenario1Step1"),
+          description: t("scenario1Step1Desc"),
+          example: {
+            before: t("scenario1Step1Before"),
+            after: t("scenario1Step1After"),
+            decision: t("scenario1Step1Decision"),
+          },
         },
-      },
-      {
-        step: "成本排序",
-        description: "在同优先级内,按成本倍率从低到高排序",
-        example: {
-          before: "A (成本 1.0x), C (成本 0.8x)",
-          after: "排序后:C (0.8x), A (1.0x)",
-          decision: "成本更低的 C 有更高的被选中概率",
+        {
+          step: t("scenario1Step2"),
+          description: t("scenario1Step2Desc"),
+          example: {
+            before: t("scenario1Step2Before"),
+            after: t("scenario1Step2After"),
+            decision: t("scenario1Step2Decision"),
+          },
         },
-      },
-      {
-        step: "加权随机",
-        description: "使用权重进行随机选择,权重越高被选中概率越大",
-        example: {
-          before: "C (权重 3), A (权重 1)",
-          after: "C 被选中概率 75%, A 被选中概率 25%",
-          decision: "最终随机选择了 C",
+        {
+          step: t("scenario1Step3"),
+          description: t("scenario1Step3Desc"),
+          example: {
+            before: t("scenario1Step3Before"),
+            after: t("scenario1Step3After"),
+            decision: t("scenario1Step3Decision"),
+          },
         },
-      },
-    ],
-  },
-  {
-    title: "用户分组过滤",
-    emoji: "👥",
-    description: "如果用户指定了供应商组,系统会优先从该组中选择",
-    steps: [
-      {
-        step: "检查用户分组",
-        description: "用户配置了 providerGroup = 'premium'",
-        example: {
-          before: "所有供应商:A (default), B (premium), C (premium), D (economy)",
-          after: "过滤出 'premium' 组:B, C",
-          decision: "只从 B 和 C 中选择",
+      ],
+    },
+    {
+      title: t("scenario2Title"),
+      emoji: "👥",
+      description: t("scenario2Desc"),
+      steps: [
+        {
+          step: t("scenario2Step1"),
+          description: t("scenario2Step1Desc"),
+          example: {
+            before: t("scenario2Step1Before"),
+            after: t("scenario2Step1After"),
+            decision: t("scenario2Step1Decision"),
+          },
         },
-      },
-      {
-        step: "分组降级",
-        description: "如果用户组内没有可用供应商,降级到所有供应商",
-        example: {
-          before: "用户组 'vip' 内的供应商全部禁用或超限",
-          after: "降级到所有启用的供应商:A, B, C, D",
-          decision: "记录警告并从全局供应商池中选择",
+        {
+          step: t("scenario2Step2"),
+          description: t("scenario2Step2Desc"),
+          example: {
+            before: t("scenario2Step2Before"),
+            after: t("scenario2Step2After"),
+            decision: t("scenario2Step2Decision"),
+          },
         },
-      },
-    ],
-  },
-  {
-    title: "健康度过滤(熔断器 + 限流)",
-    emoji: "🛡️",
-    description: "系统自动过滤掉熔断或超限的供应商",
-    steps: [
-      {
-        step: "熔断器检查",
-        description: "连续失败 5 次后熔断器打开,60 秒内不可用",
-        example: {
-          before: "供应商 A 连续失败 5 次,熔断器状态:open",
-          after: "A 被过滤,剩余:B, C, D",
-          decision: "A 在 60 秒后自动恢复到半开状态",
+      ],
+    },
+    {
+      title: t("scenario3Title"),
+      emoji: "🛡️",
+      description: t("scenario3Desc"),
+      steps: [
+        {
+          step: t("scenario3Step1"),
+          description: t("scenario3Step1Desc"),
+          example: {
+            before: t("scenario3Step1Before"),
+            after: t("scenario3Step1After"),
+            decision: t("scenario3Step1Decision"),
+          },
         },
-      },
-      {
-        step: "金额限流",
-        description: "检查 5 小时、7 天、30 天的消费额度是否超限",
-        example: {
-          before: "供应商 B 的 5 小时限额 $10,已消耗 $9.8",
-          after: "B 被过滤(接近限额),剩余:C, D",
-          decision: "5 小时窗口滑动后自动恢复",
+        {
+          step: t("scenario3Step2"),
+          description: t("scenario3Step2Desc"),
+          example: {
+            before: t("scenario3Step2Before"),
+            after: t("scenario3Step2After"),
+            decision: t("scenario3Step2Decision"),
+          },
         },
-      },
-      {
-        step: "并发 Session 限制",
-        description: "检查当前活跃 Session 数是否超过配置的并发限制",
-        example: {
-          before: "供应商 C 并发限制 2,当前活跃 Session 数:2",
-          after: "C 被过滤(已满),剩余:D",
-          decision: "Session 过期(5 分钟)后自动释放",
+        {
+          step: t("scenario3Step3"),
+          description: t("scenario3Step3Desc"),
+          example: {
+            before: t("scenario3Step3Before"),
+            after: t("scenario3Step3After"),
+            decision: t("scenario3Step3Decision"),
+          },
         },
-      },
-    ],
-  },
-  {
-    title: "会话复用机制",
-    emoji: "🔄",
-    description: "连续对话优先使用同一供应商,利用 Claude 的上下文缓存",
-    steps: [
-      {
-        step: "检查历史请求",
-        description: "查询该 API Key 最近 10 秒内使用的供应商",
-        example: {
-          before: "最近一次请求使用了供应商 B",
-          after: "检查 B 是否启用且健康",
-          decision: "B 可用,直接复用,跳过随机选择",
+      ],
+    },
+    {
+      title: t("scenario4Title"),
+      emoji: "🔄",
+      description: t("scenario4Desc"),
+      steps: [
+        {
+          step: t("scenario4Step1"),
+          description: t("scenario4Step1Desc"),
+          example: {
+            before: t("scenario4Step1Before"),
+            after: t("scenario4Step1After"),
+            decision: t("scenario4Step1Decision"),
+          },
         },
-      },
-      {
-        step: "复用失效",
-        description: "如果上次使用的供应商不可用,则重新选择",
-        example: {
-          before: "上次使用的供应商 B 已被禁用或熔断",
-          after: "进入正常选择流程",
-          decision: "从其他可用供应商中选择",
+        {
+          step: t("scenario4Step2"),
+          description: t("scenario4Step2Desc"),
+          example: {
+            before: t("scenario4Step2Before"),
+            after: t("scenario4Step2After"),
+            decision: t("scenario4Step2Decision"),
+          },
         },
-      },
-    ],
-  },
-];
+      ],
+    },
+  ];
+}
 
-function ScenarioCard({ title, emoji, description, steps }: (typeof scenarios)[0]) {
+interface ScenarioCardProps {
+  title: string;
+  emoji: string;
+  description: string;
+  steps: ScenarioStep[];
+}
+
+function ScenarioCard({ title, emoji, description, steps }: ScenarioCardProps) {
   const [isOpen, setIsOpen] = useState(false);
+  const t = useTranslations("settings.providers.guide");
 
   return (
     <Collapsible open={isOpen} onOpenChange={setIsOpen} className="border rounded-lg">
@@ -178,22 +187,22 @@ function ScenarioCard({ title, emoji, description, steps }: (typeof scenarios)[0
             <div key={index} className="border-l-2 border-primary/30 pl-4 space-y-2">
               <div className="flex items-baseline gap-2">
                 <Badge variant="outline" className="shrink-0">
-                  步骤 {index + 1}
+                  {t("step")} {index + 1}
                 </Badge>
                 <span className="font-medium text-sm">{step.step}</span>
               </div>
               <p className="text-sm text-muted-foreground">{step.description}</p>
               <div className="bg-muted/50 rounded-md p-3 space-y-1.5 text-xs">
                 <div>
-                  <span className="font-medium">过滤前:</span>
+                  <span className="font-medium">{t("before")}</span>
                   <span className="text-muted-foreground"> {step.example.before}</span>
                 </div>
                 <div>
-                  <span className="font-medium">过滤后:</span>
+                  <span className="font-medium">{t("after")}</span>
                   <span className="text-muted-foreground"> {step.example.after}</span>
                 </div>
                 <div className="pt-1 border-t border-border/50">
-                  <span className="font-medium text-primary">决策:</span>
+                  <span className="font-medium text-primary">{t("decision")}</span>
                   <span className="text-foreground"> {step.example.decision}</span>
                 </div>
               </div>
@@ -206,47 +215,43 @@ function ScenarioCard({ title, emoji, description, steps }: (typeof scenarios)[0
 }
 
 export function SchedulingRulesDialog() {
+  const t = useTranslations("settings.providers.schedulingDialog");
+  const tGuide = useTranslations("settings.providers.guide");
+  const scenarios = useScenarios();
+
   return (
     <Dialog>
       <DialogTrigger asChild>
         <Button variant="outline" size="sm" className="gap-2">
           <Info className="h-4 w-4" />
-          调度规则说明
+          {t("triggerButton")}
         </Button>
       </DialogTrigger>
       <DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
         <DialogHeader>
           <DialogTitle className="flex items-center gap-2 text-xl">
             <Lightbulb className="h-5 w-5 text-primary" />
-            供应商调度规则说明
+            {t("title")}
           </DialogTitle>
-          <DialogDescription>
-            了解系统如何智能选择上游供应商,确保高可用性和成本优化
-          </DialogDescription>
+          <DialogDescription>{t("description")}</DialogDescription>
         </DialogHeader>
 
         <div className="space-y-4 mt-4">
           <Alert>
             <Info className="h-4 w-4" />
-            <AlertTitle>核心原则</AlertTitle>
+            <AlertTitle>{tGuide("title")}</AlertTitle>
             <AlertDescription className="space-y-1 text-sm">
-              <p>
-                1️⃣ <strong>优先级优先</strong>:只从最高优先级(数值最小)的供应商中选择
-              </p>
-              <p>
-                2️⃣ <strong>成本优化</strong>:同优先级内,成本倍率低的供应商有更高概率
-              </p>
-              <p>
-                3️⃣ <strong>健康过滤</strong>:自动跳过熔断或超限的供应商
-              </p>
-              <p>
-                4️⃣ <strong>会话复用</strong>:连续对话复用同一供应商,节省上下文成本
-              </p>
+              <p>{tGuide("priorityFirst")}</p>
+              <p>{tGuide("costOptimize")}</p>
+              <p>{tGuide("healthFilter")}</p>
+              <p>{tGuide("sessionReuse")}</p>
             </AlertDescription>
           </Alert>
 
           <div className="space-y-3">
-            <h3 className="font-semibold text-sm text-muted-foreground">交互式场景演示</h3>
+            <h3 className="font-semibold text-sm text-muted-foreground">
+              {tGuide("scenariosTitle")}
+            </h3>
             {scenarios.map((scenario, index) => (
               <ScenarioCard key={index} {...scenario} />
             ))}
@@ -254,23 +259,13 @@ export function SchedulingRulesDialog() {
 
           <Alert variant="default" className="bg-primary/5 border-primary/20">
             <Lightbulb className="h-4 w-4 text-primary" />
-            <AlertTitle className="text-primary">最佳实践建议</AlertTitle>
+            <AlertTitle className="text-primary">{tGuide("bestPracticesTitle")}</AlertTitle>
             <AlertDescription className="space-y-1 text-sm text-foreground">
-              <p>
-                • <strong>优先级设置</strong>:核心供应商设为 0,备用供应商设为 1-3
-              </p>
-              <p>
-                • <strong>权重配置</strong>:根据供应商容量设置权重(容量大 = 权重高)
-              </p>
-              <p>
-                • <strong>成本倍率</strong>:官方倍率为 1.0,自建服务可设置为 0.8-1.2
-              </p>
-              <p>
-                • <strong>限额设置</strong>:根据预算设置 5 小时、7 天、30 天限额
-              </p>
-              <p>
-                • <strong>并发控制</strong>:根据供应商 API 限制设置 Session 并发数
-              </p>
+              <p>{tGuide("bestPracticesPriority")}</p>
+              <p>{tGuide("bestPracticesWeight")}</p>
+              <p>{tGuide("bestPracticesCost")}</p>
+              <p>{tGuide("bestPracticesLimit")}</p>
+              <p>{tGuide("bestPracticesConcurrent")}</p>
             </AlertDescription>
           </Alert>
         </div>

+ 0 - 4
src/components/customs/overview-panel.tsx

@@ -199,25 +199,21 @@ export function OverviewPanel({ currencyCode = "USD", isAdmin = false }: Overvie
           <MetricCard
             title={tc("metrics.concurrent")}
             value={metrics.concurrentSessions}
-            description={tc("activeSessions.recent")}
             icon={Activity}
           />
           <MetricCard
             title={tc("metrics.todayRequests")}
             value={metrics.todayRequests}
-            description={tc("metrics.requestsDescription")}
             icon={TrendingUp}
           />
           <MetricCard
             title={tc("metrics.todayCost")}
             value={formatCurrency(metrics.todayCost, currencyCode)}
-            description={tc("metrics.costDescription")}
             icon={DollarSign}
           />
           <MetricCard
             title={tc("metrics.avgResponse")}
             value={metrics.avgResponseTime}
-            description={tc("metrics.responseDescription")}
             icon={Clock}
             formatter={formatResponseTime}
           />

+ 6 - 13
src/lib/provider-type-utils.tsx

@@ -10,51 +10,39 @@ const AnthropicOrangeAvatar: React.FC<{ className?: string }> = ({ className })
   return <Anthropic.Avatar size={size} background="#ffffff" shape="circle" className={className} />;
 };
 
-// 供应商类型配置
+// 供应商类型 UI 配置(仅包含图标和颜色,不包含翻译文本)
 export const PROVIDER_TYPE_CONFIG: Record<
   ProviderType,
   {
-    label: string;
     icon: React.ComponentType<{ className?: string }>;
     iconColor: string;
     bgColor: string;
-    description: string;
   }
 > = {
   claude: {
-    label: "Claude",
     icon: Claude.Color,
     iconColor: "text-orange-600",
     bgColor: "bg-orange-500/15",
-    description: "Anthropic 官方 API",
   },
   "claude-auth": {
-    label: "Claude Auth",
     icon: AnthropicOrangeAvatar, // Anthropic Avatar 橙色圆形图标
     iconColor: "text-purple-600",
     bgColor: "bg-purple-500/15",
-    description: "Claude 中转服务",
   },
   codex: {
-    label: "Codex",
     icon: OpenAI, // OpenAI 无文字版本(默认 Mono)
     iconColor: "text-blue-600",
     bgColor: "bg-blue-500/15",
-    description: "Codex CLI API",
   },
   "gemini-cli": {
-    label: "Gemini CLI",
     icon: Gemini.Color,
     iconColor: "text-emerald-600",
     bgColor: "bg-emerald-500/15",
-    description: "Gemini CLI API",
   },
   "openai-compatible": {
-    label: "OpenAI Compatible",
     icon: OpenAI, // OpenAI 无文字版本(默认 Mono)
     iconColor: "text-cyan-600",
     bgColor: "bg-cyan-500/15",
-    description: "OpenAI 兼容 API",
   },
 };
 
@@ -67,3 +55,8 @@ export function getProviderTypeConfig(type: ProviderType) {
 export function getAllProviderTypes(): ProviderType[] {
   return Object.keys(PROVIDER_TYPE_CONFIG) as ProviderType[];
 }
+
+// 将供应商类型转换为翻译键(claude-auth -> claudeAuth, gemini-cli -> geminiCli)
+export function getProviderTypeTranslationKey(type: ProviderType): string {
+  return type.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
+}