fix(i18n): add Chinese i18n support to Loading.tsx

Replace hardcoded English strings "Loading" and "Loading..." with
useTranslation hook using common.loading key (already existed in
en.json and zh-CN.json).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
clousky
2026-04-15 16:52:50 +08:00
parent f07c7aea2f
commit 9a54e09d15
2 changed files with 22 additions and 334 deletions

View File

@@ -914,26 +914,6 @@
"when": "Use when long sessions must survive account rotation without losing the working context.",
"avoid": "Avoid when account switching is rare or when you do not want extra summarization requests.",
"example": "Example: Codex sessions that rotate across multiple accounts near quota exhaustion."
},
"fill-first": {
"when": "Use when you want to fully exhaust one provider's quota before moving to the next.",
"avoid": "Avoid when you need request-level load balancing between providers.",
"example": "Example: Use up all $200 of Deepgram credits before falling back to Groq."
},
"auto": {
"when": "Use when you have one preferred model and only want fallback on failure.",
"avoid": "Avoid when you need balanced load between models.",
"example": "Example: Primary coding model with cheaper backup for outages."
},
"lkgp": {
"when": "Use when you have one preferred model and only want fallback on failure.",
"avoid": "Avoid when you need balanced load between models.",
"example": "Example: Primary coding model with cheaper backup for outages."
},
"context-optimized": {
"when": "Use when you have one preferred model and only want fallback on failure.",
"avoid": "Avoid when you need balanced load between models.",
"example": "Example: Primary coding model with cheaper backup for outages."
}
},
"advancedHelp": {
@@ -1013,25 +993,6 @@
"candidatePoolHint": "Select which providers this engine should evaluate. Leave empty to use all active providers.",
"candidatePoolEmpty": "No active providers available yet.",
"candidatePoolAllProviders": "All providers",
"builderStagesDescription": "Move through the stages in order to define the combo, build the steps, choose the routing strategy and review the result.",
"builderStepsDescription": "Build each combo step in sequence: provider, model, then account. This allows repeating the same provider and model with different accounts.",
"selectProvider": "Select Provider",
"selectProviderPlaceholder": "Select provider",
"selectModel": "Select Model",
"selectModelPlaceholder": "Select provider first",
"selectAccount": "Select Account",
"autoSelectAccount": "Auto-select account at runtime",
"previewNextStep": "Choose provider and model to preview the next step.",
"addStep": "Add Step",
"comboReference": "Combo Reference",
"selectComboToReference": "Select an existing combo to reference",
"addComboReference": "Add Combo Reference",
"browseLegacyCatalog": "Browse legacy model catalog",
"addStepBeforeContinue": "Add at least one step before continuing to the next stage.",
"modePackPerformance": "Performance",
"modePackBudget": "Budget",
"modePackBalanced": "Balanced",
"modePackCustom": "Custom",
"modePackLabel": "Mode Pack",
"routerStrategyLabel": "Router Strategy",
"strategyRules": "Rules (6-Factor Scoring)",
@@ -1091,54 +1052,12 @@
"tip2": "Keep a quality fallback for hard prompts.",
"tip3": "Use for batch/background jobs where cost is the main KPI."
},
"fill-first": {
"title": "Quota drain strategy",
"description": "Exhausts one provider's quota before moving to the next in chain.",
"tip1": "Order models by free quota size — biggest first.",
"tip2": "Enable health checks to skip drained providers.",
"tip3": "Ideal for free-tier stacking (Deepgram → Groq → NIM)."
},
"p2c": {
"title": "Power-of-Two-Choices",
"description": "Picks the less-loaded of two random candidates per request — low latency at scale.",
"tip1": "Use with 4+ models for best effect.",
"tip2": "Requires latency telemetry enabled in Settings.",
"tip3": "Great replacement for round-robin in high-throughput combos."
},
"context-relay": {
"title": "Session continuity first",
"description": "Best when account rotation is expected and the next account must inherit a condensed task summary.",
"tip1": "Use with providers that rotate accounts for the same model family.",
"tip2": "Keep the handoff threshold below the hard quota cutoff to give the summary time to generate.",
"tip3": "Set a dedicated summary model only when the primary model is too expensive or unstable."
},
"strict-random": {
"title": "Shuffle deck distribution",
"description": "Each model is used exactly once per cycle before reshuffling.",
"tip1": "Use at least 2 models for meaningful distribution.",
"tip2": "Works best with equivalent-performance models.",
"tip3": "Ideal for load balancing across multiple API accounts."
},
"auto": {
"title": "Fail-safe baseline",
"description": "Use one primary model and keep fallback chain short and reliable.",
"tip1": "Put your most reliable model first.",
"tip2": "Keep 1-2 backup models with similar quality.",
"tip3": "Use safe retries to absorb transient provider failures."
},
"lkgp": {
"title": "Fail-safe baseline",
"description": "Use one primary model and keep fallback chain short and reliable.",
"tip1": "Put your most reliable model first.",
"tip2": "Keep 1-2 backup models with similar quality.",
"tip3": "Use safe retries to absorb transient provider failures."
},
"context-optimized": {
"title": "Fail-safe baseline",
"description": "Use one primary model and keep fallback chain short and reliable.",
"tip1": "Put your most reliable model first.",
"tip2": "Keep 1-2 backup models with similar quality.",
"tip3": "Use safe retries to absorb transient provider failures."
}
},
"templateFreeStack": "Free Stack ($0)",
@@ -3399,258 +3318,5 @@
"deduplicatedRequests": "Deduplicated Requests",
"savedCalls": "Saved API Calls",
"totalProcessed": "Total Requests Processed"
},
"proxyConfigModal": {
"levelGlobal": "Global",
"levelProvider": "Provider",
"levelCombo": "Combo",
"levelKey": "Key",
"levelDirect": "Direct (none)",
"titleGlobal": "Global Proxy Configuration",
"titleLevel": "{level} Proxy — {label}",
"loading": "Loading proxy configuration...",
"inheritingFrom": "Inheriting from",
"source": "Source",
"savedProxy": "Saved Proxy",
"custom": "Custom",
"selectSavedProxyPlaceholder": "Select saved proxy...",
"proxyType": "Proxy Type",
"host": "Host",
"hostPlaceholder": "1.2.3.4 or proxy.example.com",
"port": "Port",
"authOptional": "Authentication (optional)",
"username": "Username",
"usernamePlaceholder": "Username",
"password": "Password",
"passwordPlaceholder": "Password",
"connected": "Connected",
"ip": "IP:",
"connectionFailed": "Connection failed",
"testConnection": "Test Connection",
"clear": "Clear",
"cancel": "Cancel",
"save": "Save",
"errorSelectSavedProxy": "Select a saved proxy before saving.",
"errorSelectProxyFirst": "Select a saved proxy first.",
"errorProxyNotFound": "Selected proxy not found.",
"errorClearSavedProxy": "Failed to clear saved proxy",
"errorSaveProxy": "Failed to save proxy configuration",
"errorClearProxy": "Failed to clear proxy configuration",
"errorSocks5Hidden": "SOCKS5 is configured but hidden because NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=false."
},
"oauthModal": {
"title": "Connect {providerName}",
"waiting": "Waiting for Authorization",
"completeAuthInPopup": "Complete the authorization in the popup window.",
"popupClosedHint": "If the popup closes without redirecting back (e.g. Qoder), this dialog will automatically switch to manual URL input mode.",
"popupBlocked": "Popup blocked? Enter URL manually",
"deviceCodeVisitUrl": "Visit the URL below and enter the code:",
"deviceCodeVerificationUrl": "Verification URL",
"deviceCodeYourCode": "Your Code",
"deviceCodeWaiting": "Waiting for authorization...",
"googleOAuthWarning": "Remote access + Google OAuth: The default credentials only accept redirects to <code>localhost</code>. After authorizing, your browser will try to open <code>localhost</code> — copy that full URL and paste it below. For fully remote use without this manual step, <a>configure your own OAuth credentials</a>.",
"remoteAccessInfo": "Remote access: Since you're accessing OmniRoute remotely, after authorizing you'll see an error page (localhost not found). That's expected — just copy the full URL from your browser's address bar and paste it below.",
"step1OpenUrl": "Step 1: Open this URL in your browser",
"copy": "Copy",
"step2PasteCallback": "Step 2: Paste the callback URL or auth code here",
"step2Hint": "After authorization, paste the full callback URL. For Claude Code and Cline, you can also paste the Authentication Code directly, for example <code>code#state</code>.",
"connect": "Connect",
"cancel": "Cancel",
"success": "Connected Successfully!",
"successMessage": "Your {providerName} account has been connected.",
"done": "Done",
"error": "Connection Failed",
"tryAgain": "Try Again"
},
"cursorAuthModal": {
"title": "Connect Cursor IDE",
"autoDetecting": "Auto-detecting tokens...",
"readingFromCursor": "Reading from Cursor IDE or cursor-agent",
"tokensAutoDetected": "Tokens auto-detected from Cursor IDE successfully!",
"cursorNotDetected": "Cursor IDE not detected. Please paste your tokens manually.",
"accessToken": "Access Token",
"required": "*",
"accessTokenPlaceholder": "Access token will be auto-filled...",
"machineId": "Machine ID",
"optional": "(optional)",
"machineIdPlaceholder": "Machine ID will be auto-filled...",
"importing": "Importing...",
"importToken": "Import Token",
"cancel": "Cancel",
"errorAutoDetect": "Could not auto-detect tokens",
"errorAutoDetectFailed": "Failed to auto-detect tokens",
"errorEnterToken": "Please enter an access token",
"errorImportFailed": "Import failed"
},
"pricingModal": {
"title": "Pricing Configuration",
"loading": "Loading pricing data...",
"pricingRatesFormat": "Pricing Rates Format",
"ratesDescription": "All rates are in <strong>dollars per million tokens</strong> ($/1M tokens). Example: Input rate of 2.50 means $2.50 per 1,000,000 input tokens.",
"model": "Model",
"input": "Input",
"output": "Output",
"cached": "Cached",
"reasoning": "Reasoning",
"cacheCreation": "Cache Creation",
"noPricingData": "No pricing data available",
"resetToDefaults": "Reset to Defaults",
"cancel": "Cancel",
"saving": "Saving...",
"saveChanges": "Save Changes",
"resetConfirm": "Reset all pricing to defaults? This cannot be undone.",
"errorSaveFailed": "Failed to save pricing",
"errorResetFailed": "Failed to reset pricing"
},
"proxyRegistry": {
"title": "Proxy Registry",
"description": "Store reusable proxies and track assignments.",
"importLegacy": "Import Legacy",
"bulkAssign": "Bulk Assign",
"addProxy": "Add Proxy",
"loading": "Loading proxies...",
"noProxies": "No saved proxies yet.",
"tableName": "Name",
"tableEndpoint": "Endpoint",
"tableStatus": "Status",
"tableHealth": "Health (24h)",
"tableUsage": "Usage",
"tableActions": "Actions",
"test": "Test",
"edit": "Edit",
"delete": "Delete",
"modalCreateTitle": "Create Proxy",
"modalEditTitle": "Edit Proxy",
"labelName": "Name",
"labelType": "Type",
"labelHost": "Host",
"labelPort": "Port",
"labelUsername": "Username",
"labelPassword": "Password",
"labelRegion": "Region",
"labelStatus": "Status",
"labelNotes": "Notes",
"usernamePlaceholderEdit": "Leave blank to keep current username",
"passwordPlaceholderEdit": "Leave blank to keep current password",
"statusActive": "active",
"statusInactive": "inactive",
"cancel": "Cancel",
"save": "Save",
"bulkModalTitle": "Bulk Proxy Assignment",
"bulkLabelScope": "Scope",
"bulkLabelProxy": "Proxy",
"bulkClearAssignment": "(clear assignment)",
"bulkLabelScopeIds": "Scope IDs (comma or newline)",
"bulkScopeIdsPlaceholder": "provider-openai,provider-anthropic",
"bulkApply": "Apply",
"errorLoadFailed": "Failed to load proxy registry",
"errorNameHostRequired": "Name and host are required",
"errorSaveFailed": "Failed to save proxy",
"errorDeleteFailed": "Failed to delete proxy",
"errorForceDeleteConfirm": "This proxy is still assigned. Force delete and remove all assignments?",
"errorMigrateFailed": "Failed to migrate legacy proxy config",
"errorBulkFailed": "Failed to run bulk assignment",
"success": "✓",
"failure": "✗",
"failed": "failed",
"successRate": "{rate}% success",
"avgLatency": "{latency} ms avg",
"assignmentsCount": "{count} assignment(s)",
"noData": "—",
"testSuccess": "✓ {ip}",
"testLatency": "{latency}ms",
"testFailure": "✗ {error}"
},
"playground": {
"title": "Model Playground",
"description": "Test any model directly from the dashboard. Pick a provider, model, and endpoint type, then send a request to see the raw response.",
"endpoint": "Endpoint",
"provider": "Provider",
"model": "Model",
"accountKey": "Account / Key",
"autoAccounts": "Auto ({count} accounts)",
"noAccounts": "No accounts",
"send": "Send",
"cancel": "Cancel",
"audioFile": "Audio File",
"attachImages": "Attach Images (Vision)",
"multipartFormData": "multipart/form-data",
"upToImages": "up to 4 images",
"selectAudioFile": "Select an audio file to transcribe (mp3, wav, m4a, ogg, flac…)",
"clearAll": "Clear all",
"request": "Request",
"response": "Response",
"transcription": "Transcription",
"copy": "Copy",
"resetToDefault": "Reset to default",
"downloadAudio": "Download audio",
"copyText": "Copy text",
"transcriptionHint": "Transcription uses multipart/form-data. Upload the audio file above — JSON below controls extra params (model, language).",
"imagesGenerated": "{count} image{count, plural, one {} other {s}} generated",
"generatedImage": "Generated image {index}",
"save": "Save",
"endpointOptions": {
"chat": "Chat Completions",
"responses": "Responses",
"images": "Image Generation",
"embeddings": "Embeddings",
"speech": "Text to Speech",
"transcription": "Audio Transcription",
"video": "Video Generation",
"music": "Music Generation",
"rerank": "Rerank",
"search": "Web Search"
}
},
"requestLogger": {
"recording": "Recording",
"paused": "Paused",
"pipelineLogsOn": "Pipeline Logs On",
"pipelineLogsOff": "Pipeline Logs Off",
"updatingPipelineLogs": "Updating pipeline logs...",
"searchPlaceholder": "Search model, provider, account, API key, combo...",
"allProviders": "All Providers",
"allModels": "All Models",
"allAccounts": "All Accounts",
"allApiKeys": "All API Keys",
"total": "total",
"ok": "OK",
"err": "ERR",
"combo": "combo",
"keys": "keys",
"shown": "shown",
"sortNewest": "Newest",
"sortOldest": "Oldest",
"sortTokensDesc": "Tokens ↓",
"sortTokensAsc": "Tokens ↑",
"sortDurationDesc": "Duration ↓",
"sortDurationAsc": "Duration ↑",
"sortStatusDesc": "Status ↓",
"sortStatusAsc": "Status ↑",
"sortModelAsc": "Model A-Z",
"sortModelDesc": "Model Z-A",
"statusFilters": {
"all": "All",
"error": "Errors",
"success": "Success",
"combo": "Combo"
},
"columns": {
"status": "Status",
"model": "Model",
"requested": "Requested",
"provider": "Provider",
"protocol": "Req Protocol",
"account": "Account",
"apiKey": "API Key",
"combo": "Combo",
"tokens": "Tokens",
"duration": "Duration",
"time": "Time"
},
"loadingLogs": "Loading logs...",
"noLogs": "No logs recorded yet. Make some API calls to see them here.",
"noMatchingLogs": "No logs match the current filters.",
"callLogsInfo": "Call logs are also saved as JSON files to {dataDir} and rotated by {retentionDays} and {maxEntries}."
}
}

View File

@@ -1163,6 +1163,28 @@
"reorderHandle": "拖拽排序",
"failedReorder": "模型重排序失败",
"builderFlowTitle": "组合构建流程",
"builderStage": {
"basics": {
"label": "基础",
"description": "名称和起始模板"
},
"steps": {
"label": "步骤",
"description": "提供商、模型和账户选择"
},
"strategy": {
"label": "策略",
"description": "路由行为和高级设置"
},
"intelligent": {
"label": "智能路由",
"description": "自动路由候选池、预设和评分"
},
"review": {
"label": "审查",
"description": "保存前的最终验证"
}
},
"builderStageVisited": "阶段已完成",
"builderStageCurrent": "当前阶段",
"builderStagePending": "待处理",