diff --git a/src/components/providers/HealthStatusIndicator.tsx b/src/components/providers/HealthStatusIndicator.tsx index 8b87601fc..0fd319f2c 100644 --- a/src/components/providers/HealthStatusIndicator.tsx +++ b/src/components/providers/HealthStatusIndicator.tsx @@ -1,6 +1,7 @@ import React from "react"; import { cn } from "@/lib/utils"; import type { HealthStatus } from "@/lib/api/model-test"; +import { useTranslation } from "react-i18next"; interface HealthStatusIndicatorProps { status: HealthStatus; @@ -11,17 +12,20 @@ interface HealthStatusIndicatorProps { const statusConfig = { operational: { color: "bg-emerald-500", - label: "正常", + labelKey: "health.operational", + labelFallback: "正常", textColor: "text-emerald-600 dark:text-emerald-400", }, degraded: { color: "bg-yellow-500", - label: "降级", + labelKey: "health.degraded", + labelFallback: "降级", textColor: "text-yellow-600 dark:text-yellow-400", }, failed: { color: "bg-red-500", - label: "失败", + labelKey: "health.failed", + labelFallback: "失败", textColor: "text-red-600 dark:text-red-400", }, }; @@ -31,13 +35,15 @@ export const HealthStatusIndicator: React.FC = ({ responseTimeMs, className, }) => { + const { t } = useTranslation(); const config = statusConfig[status]; + const label = t(config.labelKey, { defaultValue: config.labelFallback }); return (
- {config.label} + {label} {responseTimeMs !== undefined && ` (${responseTimeMs}ms)`}
diff --git a/src/components/providers/ProviderHealthBadge.tsx b/src/components/providers/ProviderHealthBadge.tsx index 1a9f901d7..aa6b55ad6 100644 --- a/src/components/providers/ProviderHealthBadge.tsx +++ b/src/components/providers/ProviderHealthBadge.tsx @@ -1,5 +1,6 @@ import { cn } from "@/lib/utils"; import { ProviderHealthStatus } from "@/types/proxy"; +import { useTranslation } from "react-i18next"; interface ProviderHealthBadgeProps { consecutiveFailures: number; @@ -14,11 +15,14 @@ export function ProviderHealthBadge({ consecutiveFailures, className, }: ProviderHealthBadgeProps) { + const { t } = useTranslation(); + // 根据失败次数计算状态 const getStatus = () => { if (consecutiveFailures === 0) { return { - label: "正常", + labelKey: "health.operational", + labelFallback: "正常", status: ProviderHealthStatus.Healthy, color: "bg-green-500", // 使用更深/柔和的背景色,去除可能的白色内容感 @@ -27,7 +31,8 @@ export function ProviderHealthBadge({ }; } else if (consecutiveFailures < 5) { return { - label: "降级", + labelKey: "health.degraded", + labelFallback: "降级", status: ProviderHealthStatus.Degraded, color: "bg-yellow-500", bgColor: "bg-yellow-500/10", @@ -35,7 +40,8 @@ export function ProviderHealthBadge({ }; } else { return { - label: "熔断", + labelKey: "health.circuitOpen", + labelFallback: "熔断", status: ProviderHealthStatus.Failed, color: "bg-red-500", bgColor: "bg-red-500/10", @@ -45,6 +51,9 @@ export function ProviderHealthBadge({ }; const statusConfig = getStatus(); + const label = t(statusConfig.labelKey, { + defaultValue: statusConfig.labelFallback, + }); return (
- {statusConfig.label} + {label}
); } diff --git a/src/components/providers/forms/BasicFormFields.tsx b/src/components/providers/forms/BasicFormFields.tsx index 7ef94f77c..82477c2a3 100644 --- a/src/components/providers/forms/BasicFormFields.tsx +++ b/src/components/providers/forms/BasicFormFields.tsx @@ -52,7 +52,15 @@ export function BasicFormFields({ form }: BasicFormFieldsProps) {
@@ -70,16 +74,23 @@ export function ProxyPanel() { navigator.clipboard.writeText( `http://${status.address}:${status.port}`, ); - toast.success("地址已复制", { closeButton: true }); + toast.success( + t("proxy.panel.addressCopied", { + defaultValue: "地址已复制", + }), + { closeButton: true }, + ); }} > - 复制 + {t("common.copy")}
-

使用中

+

+ {t("provider.inUse")} +

{status.active_targets && status.active_targets.length > 0 ? (
{status.active_targets.map((target) => ( @@ -101,14 +112,18 @@ export function ProxyPanel() {
) : status.current_provider ? (

- 当前 Provider:{" "} + {t("proxy.panel.currentProvider", { + defaultValue: "当前 Provider:", + })}{" "} {status.current_provider}

) : (

- 当前 Provider:等待首次请求… + {t("proxy.panel.waitingFirstRequest", { + defaultValue: "当前 Provider:等待首次请求…", + })}

)}
@@ -121,7 +136,7 @@ export function ProxyPanel() {

- 故障转移队列 + {t("proxy.failoverQueue.title")}

@@ -179,23 +194,31 @@ export function ProxyPanel() {
} - label="活跃连接" + label={t("proxy.panel.stats.activeConnections", { + defaultValue: "活跃连接", + })} value={status.active_connections} /> } - label="总请求数" + label={t("proxy.panel.stats.totalRequests", { + defaultValue: "总请求数", + })} value={status.total_requests} /> } - label="成功率" + label={t("proxy.panel.stats.successRate", { + defaultValue: "成功率", + })} value={`${status.success_rate.toFixed(1)}%`} variant={status.success_rate > 90 ? "success" : "warning"} /> } - label="运行时间" + label={t("proxy.panel.stats.uptime", { + defaultValue: "运行时间", + })} value={formatUptime(status.uptime_seconds)} />
@@ -206,10 +229,12 @@ export function ProxyPanel() {

- 代理服务已停止 + {t("proxy.panel.stoppedTitle", { defaultValue: "代理服务已停止" })}

- 使用右上角开关即可启动服务 + {t("proxy.panel.stoppedDescription", { + defaultValue: "使用右上角开关即可启动服务", + })}

)} @@ -319,6 +346,7 @@ function ProviderQueueItem({ appType, isCurrent, }: ProviderQueueItemProps) { + const { t } = useTranslation(); const { data: health } = useProviderHealth(provider.id, appType); return ( @@ -344,7 +372,7 @@ function ProviderQueueItem({ {isCurrent && ( - 使用中 + {t("provider.inUse")} )} diff --git a/src/components/settings/ImportExportSection.tsx b/src/components/settings/ImportExportSection.tsx index ad70a6ac4..4dfc5d712 100644 --- a/src/components/settings/ImportExportSection.tsx +++ b/src/components/settings/ImportExportSection.tsx @@ -93,7 +93,7 @@ export function ImportExportSection({ type="button" onClick={onClear} className="absolute -top-2 -right-2 h-6 w-6 rounded-full bg-red-500 hover:bg-red-600 text-white flex items-center justify-center shadow-lg transition-colors z-10" - aria-label="Clear selection" + aria-label={t("common.clear")} > diff --git a/src/components/usage/PricingEditModal.tsx b/src/components/usage/PricingEditModal.tsx index 79958ed98..4e9a759ae 100644 --- a/src/components/usage/PricingEditModal.tsx +++ b/src/components/usage/PricingEditModal.tsx @@ -106,7 +106,9 @@ export function PricingEditModal({ onChange={(e) => setFormData({ ...formData, modelId: e.target.value }) } - placeholder="例如: claude-3-5-sonnet-20241022" + placeholder={t("usage.modelIdPlaceholder", { + defaultValue: "例如: claude-3-5-sonnet-20241022", + })} required /> @@ -122,7 +124,9 @@ export function PricingEditModal({ onChange={(e) => setFormData({ ...formData, displayName: e.target.value }) } - placeholder="例如: Claude 3.5 Sonnet" + placeholder={t("usage.displayNamePlaceholder", { + defaultValue: "例如: Claude 3.5 Sonnet", + })} required /> diff --git a/src/components/usage/RequestDetailPanel.tsx b/src/components/usage/RequestDetailPanel.tsx index aa91ee510..ea62f7780 100644 --- a/src/components/usage/RequestDetailPanel.tsx +++ b/src/components/usage/RequestDetailPanel.tsx @@ -16,8 +16,14 @@ export function RequestDetailPanel({ requestId, onClose, }: RequestDetailPanelProps) { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const { data: request, isLoading } = useRequestDetail(requestId); + const dateLocale = + i18n.language === "zh" + ? "zh-CN" + : i18n.language === "ja" + ? "ja-JP" + : "en-US"; if (isLoading) { return ( @@ -69,7 +75,9 @@ export function RequestDetailPanel({ {t("usage.time", "时间")}
- {new Date(request.createdAt * 1000).toLocaleString("zh-CN")} + {new Date(request.createdAt * 1000).toLocaleString( + dateLocale, + )}
diff --git a/src/components/usage/RequestLogTable.tsx b/src/components/usage/RequestLogTable.tsx index 7d16b6cfe..69565c657 100644 --- a/src/components/usage/RequestLogTable.tsx +++ b/src/components/usage/RequestLogTable.tsx @@ -23,7 +23,7 @@ import type { LogFilters } from "@/types/usage"; import { ChevronLeft, ChevronRight, RefreshCw, Search, X } from "lucide-react"; export function RequestLogTable() { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const queryClient = useQueryClient(); // 默认时间范围:过去24小时 @@ -62,6 +62,13 @@ export function RequestLogTable() { }); }; + const dateLocale = + i18n.language === "zh" + ? "zh-CN" + : i18n.language === "ja" + ? "ja-JP" + : "en-US"; + return (
{/* 筛选栏 */} @@ -77,10 +84,12 @@ export function RequestLogTable() { } > - + - {t("common.all", "全部端点")} + + {t("usage.allApps", { defaultValue: "全部应用" })} + Claude Codex Gemini @@ -97,7 +106,7 @@ export function RequestLogTable() { } > - + {t("common.all", "全部状态")} @@ -113,7 +122,10 @@ export function RequestLogTable() {
@@ -125,7 +137,7 @@ export function RequestLogTable() { />
@@ -140,7 +152,9 @@ export function RequestLogTable() {
- 时间范围: + + {t("usage.timeRange", { defaultValue: "时间范围" })}: + ( - {new Date(log.createdAt * 1000).toLocaleString("zh-CN")} + {new Date(log.createdAt * 1000).toLocaleString( + dateLocale, + )} {log.providerName || diff --git a/src/components/usage/UsageSummaryCards.tsx b/src/components/usage/UsageSummaryCards.tsx index 01b6dd7bd..a6d9397ff 100644 --- a/src/components/usage/UsageSummaryCards.tsx +++ b/src/components/usage/UsageSummaryCards.tsx @@ -56,15 +56,15 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) { color: "text-purple-500", bg: "bg-purple-500/10", subValue: ( -
+
- Input + {t("usage.input", { defaultValue: "Input" })} {(inputTokens / 1000).toFixed(1)}k
- Output + {t("usage.output", { defaultValue: "Output" })} {(outputTokens / 1000).toFixed(1)}k @@ -78,21 +78,21 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) { icon: Database, color: "text-orange-500", bg: "bg-orange-500/10", - subValue: ( -
-
- Write - - {(cacheWriteTokens / 1000).toFixed(1)}k - + subValue: ( +
+
+ {t("usage.cacheWrite", { defaultValue: "Write" })} + + {(cacheWriteTokens / 1000).toFixed(1)}k + +
+
+ {t("usage.cacheRead", { defaultValue: "Read" })} + + {(cacheReadTokens / 1000).toFixed(1)}k + +
-
- Read - - {(cacheReadTokens / 1000).toFixed(1)}k - -
-
), }, ]; diff --git a/src/components/usage/UsageTrendChart.tsx b/src/components/usage/UsageTrendChart.tsx index 7d82a6282..3fe75d9ff 100644 --- a/src/components/usage/UsageTrendChart.tsx +++ b/src/components/usage/UsageTrendChart.tsx @@ -17,7 +17,7 @@ interface UsageTrendChartProps { } export function UsageTrendChart({ days }: UsageTrendChartProps) { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const { data: trends, isLoading } = useUsageTrends(days); if (isLoading) { @@ -29,14 +29,20 @@ export function UsageTrendChart({ days }: UsageTrendChartProps) { } const isToday = days === 1; + const dateLocale = + i18n.language === "zh" + ? "zh-CN" + : i18n.language === "ja" + ? "ja-JP" + : "en-US"; const chartData = trends?.map((stat) => { const pointDate = new Date(stat.date); return { rawDate: stat.date, label: isToday - ? pointDate.toLocaleTimeString("zh-CN", { hour: "2-digit" }) - : pointDate.toLocaleDateString("zh-CN", { + ? pointDate.toLocaleTimeString(dateLocale, { hour: "2-digit" }) + : pointDate.toLocaleDateString(dateLocale, { month: "2-digit", day: "2-digit", }), diff --git a/src/hooks/useProxyConfig.ts b/src/hooks/useProxyConfig.ts index effb64b1e..92ba1d3c5 100644 --- a/src/hooks/useProxyConfig.ts +++ b/src/hooks/useProxyConfig.ts @@ -5,6 +5,7 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { invoke } from "@tauri-apps/api/core"; import { toast } from "sonner"; +import { useTranslation } from "react-i18next"; import type { ProxyConfig } from "@/types/proxy"; /** @@ -12,6 +13,7 @@ import type { ProxyConfig } from "@/types/proxy"; */ export function useProxyConfig() { const queryClient = useQueryClient(); + const { t } = useTranslation(); // 查询配置 const { data: config, isLoading } = useQuery({ @@ -24,12 +26,16 @@ export function useProxyConfig() { mutationFn: (newConfig: ProxyConfig) => invoke("update_proxy_config", { config: newConfig }), onSuccess: () => { - toast.success("代理配置已保存", { closeButton: true }); + toast.success(t("proxy.settings.toast.saved"), { closeButton: true }); queryClient.invalidateQueries({ queryKey: ["proxyConfig"] }); queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); }, onError: (error: Error) => { - toast.error(`保存失败: ${error.message}`); + toast.error( + t("proxy.settings.toast.saveFailed", { + error: error.message, + }), + ); }, }); diff --git a/src/hooks/useProxyStatus.ts b/src/hooks/useProxyStatus.ts index cc4c78ab8..e679513a1 100644 --- a/src/hooks/useProxyStatus.ts +++ b/src/hooks/useProxyStatus.ts @@ -50,7 +50,8 @@ export function useProxyStatus() { queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); }, onError: (error: Error) => { - const detail = extractErrorMessage(error) || "未知错误"; + const detail = + extractErrorMessage(error) || t("common.unknown", { defaultValue: "未知错误" }); toast.error( t("proxy.server.startFailed", { defaultValue: `启动代理服务失败: ${detail}`, @@ -75,7 +76,8 @@ export function useProxyStatus() { queryClient.invalidateQueries({ queryKey: ["providerHealth"] }); }, onError: (error: Error) => { - const detail = extractErrorMessage(error) || "未知错误"; + const detail = + extractErrorMessage(error) || t("common.unknown", { defaultValue: "未知错误" }); toast.error( t("proxy.stopWithRestoreFailed", { defaultValue: `停止失败: ${detail}`, @@ -111,7 +113,8 @@ export function useProxyStatus() { queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] }); }, onError: (error: Error) => { - const detail = extractErrorMessage(error) || "未知错误"; + const detail = + extractErrorMessage(error) || t("common.unknown", { defaultValue: "未知错误" }); toast.error( t("proxy.takeover.failed", { defaultValue: `操作失败: ${detail}`, @@ -133,8 +136,14 @@ export function useProxyStatus() { queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); }, onError: (error: Error) => { - const detail = extractErrorMessage(error) || "未知错误"; - toast.error(`切换失败: ${detail}`); + const detail = + extractErrorMessage(error) || t("common.unknown", { defaultValue: "未知错误" }); + toast.error( + t("proxy.switchFailed", { + error: detail, + defaultValue: `切换失败: ${detail}`, + }), + ); }, }); diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 63fea6c41..322d42282 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -378,7 +378,19 @@ "daysAgo": "{{count}} day ago", "multiplePlans": "{{count}} plans", "expand": "Expand", - "collapse": "Collapse" + "collapse": "Collapse", + "modelIdPlaceholder": "e.g., claude-3-5-sonnet-20241022", + "displayNamePlaceholder": "e.g., Claude 3.5 Sonnet", + "appType": "App Type", + "allApps": "All Apps", + "statusCode": "Status Code", + "searchProviderPlaceholder": "Search provider...", + "searchModelPlaceholder": "Search model...", + "timeRange": "Time Range", + "input": "Input", + "output": "Output", + "cacheWrite": "Write", + "cacheRead": "Read" }, "usageScript": { "title": "Configure Usage Query", @@ -448,7 +460,9 @@ "tip3": "• Entire config must be wrapped in () to form object literal expression" }, "errors": { - "usage_query_failed": "Usage query failed" + "usage_query_failed": "Usage query failed", + "configLoadFailedTitle": "Configuration Load Failed", + "configLoadFailedMessage": "Unable to read configuration file:\n{{path}}\n\nError details:\n{{detail}}\n\nPlease check if the JSON is valid, or restore from a backup file (e.g., config.json.bak) in the same directory.\n\nThe app will exit so you can fix this." }, "presetSelector": { "title": "Select Configuration Type", @@ -847,7 +861,9 @@ "label": "Icon", "colorLabel": "Icon Color", "selectIcon": "Select Icon", - "preview": "Preview" + "preview": "Preview", + "clickToChange": "Click to change icon", + "clickToSelect": "Click to select icon" }, "migration": { "success": "Configuration migrated successfully" @@ -855,7 +871,36 @@ "agents": { "title": "Agents" }, + "health": { + "operational": "Operational", + "degraded": "Degraded", + "failed": "Failed", + "circuitOpen": "Circuit Open", + "consecutiveFailures": "{{count}} consecutive failures" + }, "proxy": { + "panel": { + "serviceAddress": "Service Address", + "addressCopied": "Address copied", + "currentProvider": "Current Provider:", + "waitingFirstRequest": "Current Provider: Waiting for first request...", + "stoppedTitle": "Proxy Service Stopped", + "stoppedDescription": "Use the toggle in the top right to start the service", + "openSettings": "Configure Proxy Service", + "stats": { + "activeConnections": "Active Connections", + "totalRequests": "Total Requests", + "successRate": "Success Rate", + "uptime": "Uptime" + } + }, + "settings": { + "toast": { + "saved": "Proxy configuration saved", + "saveFailed": "Save failed: {{error}}" + } + }, + "switchFailed": "Switch failed: {{error}}", "failoverQueue": { "title": "Failover Queue", "description": "Manage failover order for each app's providers", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 1b3f178e1..6a3f9ba9d 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -378,7 +378,19 @@ "daysAgo": "{{count}} 日前", "multiplePlans": "{{count}} プラン", "expand": "展開", - "collapse": "折りたたむ" + "collapse": "折りたたむ", + "modelIdPlaceholder": "例: claude-3-5-sonnet-20241022", + "displayNamePlaceholder": "例: Claude 3.5 Sonnet", + "appType": "アプリ種別", + "allApps": "すべてのアプリ", + "statusCode": "ステータスコード", + "searchProviderPlaceholder": "プロバイダーを検索...", + "searchModelPlaceholder": "モデルを検索...", + "timeRange": "期間", + "input": "Input", + "output": "Output", + "cacheWrite": "Write", + "cacheRead": "Read" }, "usageScript": { "title": "利用状況を設定", @@ -448,7 +460,9 @@ "tip3": "• 全体を () で囲み、オブジェクトリテラル式にしてください" }, "errors": { - "usage_query_failed": "利用状況の取得に失敗しました" + "usage_query_failed": "利用状況の取得に失敗しました", + "configLoadFailedTitle": "設定の読み込みに失敗しました", + "configLoadFailedMessage": "設定ファイルを読み込めません:\n{{path}}\n\nエラー詳細:\n{{detail}}\n\nJSON が正しいか確認するか、同じディレクトリのバックアップファイル(config.json.bak など)から復元してください。\n\nアプリを終了して修正してください。" }, "presetSelector": { "title": "設定タイプを選択", @@ -847,12 +861,88 @@ "label": "アイコン", "colorLabel": "アイコンカラー", "selectIcon": "アイコンを選択", - "preview": "プレビュー" + "preview": "プレビュー", + "clickToChange": "クリックでアイコンを変更", + "clickToSelect": "クリックでアイコンを選択" }, "migration": { "success": "設定の移行が完了しました" }, "agents": { "title": "エージェント" + }, + "health": { + "operational": "正常", + "degraded": "低下", + "failed": "失敗", + "circuitOpen": "サーキットオープン", + "consecutiveFailures": "{{count}} 回連続失敗" + }, + "proxy": { + "panel": { + "serviceAddress": "サービスアドレス", + "addressCopied": "アドレスをコピーしました", + "currentProvider": "現在のプロバイダー:", + "waitingFirstRequest": "現在のプロバイダー: 最初のリクエスト待ち...", + "stoppedTitle": "プロキシサービス停止中", + "stoppedDescription": "右上のトグルでサービスを開始できます", + "openSettings": "プロキシサービスを設定", + "stats": { + "activeConnections": "アクティブ接続", + "totalRequests": "総リクエスト数", + "successRate": "成功率", + "uptime": "稼働時間" + } + }, + "settings": { + "toast": { + "saved": "プロキシ設定を保存しました", + "saveFailed": "保存に失敗しました: {{error}}" + } + }, + "switchFailed": "切り替えに失敗しました: {{error}}", + "failoverQueue": { + "title": "フェイルオーバーキュー", + "description": "各アプリのプロバイダーのフェイルオーバー順序を管理します", + "info": "現在アクティブなプロバイダーが常に優先されます。リクエストが失敗すると、システムはキュー順に他のプロバイダーを試行します。", + "selectProvider": "キューに追加するプロバイダーを選択", + "noAvailableProviders": "追加できるプロバイダーがありません", + "empty": "フェイルオーバーキューが空です。自動フェイルオーバーを有効にするにはプロバイダーを追加してください。", + "dragHint": "ドラッグでフェイルオーバー順序を調整します。番号が小さいほど優先度が高くなります。", + "toggleEnabled": "有効/無効", + "addSuccess": "フェイルオーバーキューに追加しました", + "addFailed": "追加に失敗しました", + "removeSuccess": "フェイルオーバーキューから削除しました", + "removeFailed": "削除に失敗しました", + "reorderSuccess": "キュー順序を更新しました", + "reorderFailed": "順序の更新に失敗しました", + "toggleFailed": "状態の更新に失敗しました" + }, + "autoFailover": { + "info": "フェイルオーバーキューに複数のプロバイダーが設定されている場合、リクエストが失敗すると優先度順に試行します。プロバイダーが連続失敗のしきい値に達すると、サーキットブレーカーが開き、一時的にスキップされます。", + "configSaved": "自動フェイルオーバー設定を保存しました", + "configSaveFailed": "保存に失敗しました", + "retrySettings": "リトライとタイムアウト設定", + "failureThreshold": "失敗しきい値", + "failureThresholdHint": "この回数連続で失敗するとサーキットブレーカーが開きます(推奨: 3-10)", + "timeout": "回復待ち時間(秒)", + "timeoutHint": "サーキットが開いた後、回復を試みるまでの待ち時間(推奨: 30-120)", + "circuitBreakerSettings": "サーキットブレーカー詳細設定", + "successThreshold": "回復成功しきい値", + "successThresholdHint": "半開状態でこの回数成功するとサーキットブレーカーが閉じます", + "errorRate": "エラー率しきい値 (%)", + "errorRateHint": "この値を超えるとサーキットブレーカーが開きます", + "minRequests": "最小リクエスト数", + "minRequestsHint": "エラー率を計算する前の最小リクエスト数", + "explanationTitle": "仕組み", + "failureThresholdLabel": "失敗しきい値", + "failureThresholdExplain": "この回数連続で失敗すると、サーキットブレーカーが開き、プロバイダーは一時的に利用不可になります", + "timeoutLabel": "回復待ち時間", + "timeoutExplain": "サーキットが開いた後、半開状態を試みるまでの待ち時間", + "successThresholdLabel": "回復成功しきい値", + "successThresholdExplain": "半開状態でこの回数成功するとサーキットブレーカーが閉じ、プロバイダーが再び利用可能になります", + "errorRateLabel": "エラー率しきい値", + "errorRateExplain": "失敗しきい値に達していなくても、エラー率がこの値を超えるとサーキットブレーカーが開きます" + } } } diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 5f2e8a0de..9126ce13c 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -378,7 +378,19 @@ "daysAgo": "{{count}} 天前", "multiplePlans": "{{count}} 个套餐", "expand": "展开", - "collapse": "收起" + "collapse": "收起", + "modelIdPlaceholder": "例如: claude-3-5-sonnet-20241022", + "displayNamePlaceholder": "例如: Claude 3.5 Sonnet", + "appType": "应用类型", + "allApps": "全部应用", + "statusCode": "状态码", + "searchProviderPlaceholder": "搜索供应商...", + "searchModelPlaceholder": "搜索模型...", + "timeRange": "时间范围", + "input": "Input", + "output": "Output", + "cacheWrite": "Write", + "cacheRead": "Read" }, "usageScript": { "title": "配置用量查询", @@ -448,7 +460,9 @@ "tip3": "• 整个配置必须用 () 包裹,形成对象字面量表达式" }, "errors": { - "usage_query_failed": "用量查询失败" + "usage_query_failed": "用量查询失败", + "configLoadFailedTitle": "配置加载失败", + "configLoadFailedMessage": "无法读取配置文件:\n{{path}}\n\n错误详情:\n{{detail}}\n\n请手动检查 JSON 是否有效,或从同目录的备份文件(如 config.json.bak)恢复。\n\n应用将退出以便您进行修复。" }, "presetSelector": { "title": "选择配置类型", @@ -847,7 +861,9 @@ "label": "图标", "colorLabel": "图标颜色", "selectIcon": "选择图标", - "preview": "预览" + "preview": "预览", + "clickToChange": "点击更换图标", + "clickToSelect": "点击选择图标" }, "migration": { "success": "配置迁移成功" @@ -855,7 +871,36 @@ "agents": { "title": "智能体" }, + "health": { + "operational": "正常", + "degraded": "降级", + "failed": "失败", + "circuitOpen": "熔断", + "consecutiveFailures": "连续失败 {{count}} 次" + }, "proxy": { + "panel": { + "serviceAddress": "服务地址", + "addressCopied": "地址已复制", + "currentProvider": "当前 Provider:", + "waitingFirstRequest": "当前 Provider:等待首次请求…", + "stoppedTitle": "代理服务已停止", + "stoppedDescription": "使用右上角开关即可启动服务", + "openSettings": "配置代理服务", + "stats": { + "activeConnections": "活跃连接", + "totalRequests": "总请求数", + "successRate": "成功率", + "uptime": "运行时间" + } + }, + "settings": { + "toast": { + "saved": "代理配置已保存", + "saveFailed": "保存失败: {{error}}" + } + }, + "switchFailed": "切换失败: {{error}}", "failoverQueue": { "title": "故障转移队列", "description": "管理各应用的供应商故障转移顺序", diff --git a/src/main.tsx b/src/main.tsx index 6223e9143..d3fb63e2e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,7 +4,7 @@ import App from "./App"; import { UpdateProvider } from "./contexts/UpdateContext"; import "./index.css"; // 导入国际化配置 -import "./i18n"; +import i18n from "./i18n"; import { QueryClientProvider } from "@tanstack/react-query"; import { ThemeProvider } from "@/components/theme-provider"; import { queryClient } from "@/lib/query"; @@ -43,8 +43,18 @@ async function handleConfigLoadError( const detail = payload?.error ?? "Unknown error"; await message( - `无法读取配置文件:\n${path}\n\n错误详情:\n${detail}\n\n请手动检查 JSON 是否有效,或从同目录的备份文件(如 config.json.bak)恢复。\n\n应用将退出以便您进行修复。`, - { title: "配置加载失败", kind: "error" }, + i18n.t("errors.configLoadFailedMessage", { + path, + detail, + defaultValue: + "无法读取配置文件:\n{{path}}\n\n错误详情:\n{{detail}}\n\n请手动检查 JSON 是否有效,或从同目录的备份文件(如 config.json.bak)恢复。\n\n应用将退出以便您进行修复。", + }), + { + title: i18n.t("errors.configLoadFailedTitle", { + defaultValue: "配置加载失败", + }), + kind: "error", + }, ); await exit(1);