diff --git a/scripts/i18n-gate.mjs b/scripts/i18n-gate.mjs new file mode 100755 index 000000000000..cba81b0e9b99 --- /dev/null +++ b/scripts/i18n-gate.mjs @@ -0,0 +1,66 @@ +#!/usr/bin/env node +// scripts/i18n-gate.mjs — Fail CI if any i18n locale still contains __MISSING__ markers. +// Usage: node scripts/i18n-gate.mjs src/i18n/messages/ [--strict] +// --strict also fails if any locale is missing keys present in en.json. +import fs from "node:fs"; +import path from "node:path"; + +const dir = process.argv[2] || "src/i18n/messages"; +const strict = process.argv.includes("--strict"); + +function getAllKeys(obj, prefix = "") { + const out = {}; + for (const [k, v] of Object.entries(obj)) { + const p = prefix ? `${prefix}.${k}` : k; + if (v && typeof v === "object" && !Array.isArray(v)) Object.assign(out, getAllKeys(v, p)); + else out[p] = v; + } + return out; +} + +const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json")); +let bad = 0; +let report = []; + +// 1. Detect __MISSING__: markers +for (const f of files) { + const json = JSON.parse(fs.readFileSync(path.join(dir, f), "utf8")); + const flat = getAllKeys(json); + const missing = Object.entries(flat) + .filter(([, v]) => typeof v === "string" && v.startsWith("__MISSING__:")) + .map(([k]) => k); + if (missing.length) { + bad += missing.length; + report.push( + `${f}: ${missing.length} __MISSING__: markers (e.g. ${missing.slice(0, 3).join(", ")})` + ); + } +} + +// 2. (--strict) Detect keys in en.json missing from other locales +if (strict && files.includes("en.json")) { + const en = getAllKeys(JSON.parse(fs.readFileSync(path.join(dir, "en.json"), "utf8"))); + const enKeys = new Set(Object.keys(en)); + for (const f of files) { + if (f === "en.json") continue; + const json = JSON.parse(fs.readFileSync(path.join(dir, f), "utf8")); + const flat = getAllKeys(json); + const missingKeys = Object.keys(flat).filter((k) => !enKeys.has(k)); + if (missingKeys.length) { + bad += missingKeys.length; + report.push( + `${f}: ${missingKeys.length} keys not in en.json (e.g. ${missingKeys.slice(0, 3).join(", ")})` + ); + } + } +} + +if (bad) { + console.error(`i18n-gate FAIL: ${bad} issue(s) across ${files.length} locales`); + report.forEach((r) => console.error(" " + r)); + process.exit(1); +} else { + console.log( + `i18n-gate OK: ${files.length} locales, no __MISSING__: markers${strict ? " (strict mode)" : ""}` + ); +} diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index f3cab6d48cbe..ad17de4484ce 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -180,7 +180,7 @@ "selectWorkingModels": "Add working", "unselectWorkingModels": "Unselect working", "noWorkingModelsToSelect": "No working models left to add", - "providerTestHint": "Tip: check a provider → Test → Add working. Click again to unselect.", + "providerTestHint": "Tip: check a provider \u2192 Test \u2192 Add working. Click again to unselect.", "selectAllProviders": "Check all providers", "clearProviderSelection": "Uncheck providers", "providersSelectedCount": "{count} providers checked", @@ -640,7 +640,7 @@ "compatibleProviders": "Compatible Providers", "freeTierProviders": "Free Tier Providers", "freeTierLabel": "Free tier available", - "freeTierProvidersDesc": "Providers with free tiers — some require an API key signup, others need no credentials at all.", + "freeTierProvidersDesc": "Providers with free tiers \u2014 some require an API key signup, others need no credentials at all.", "clearCache": "Clear Cache", "reqs": "Reqs", "addAnthropicCompatible": "Add Anthropic Compatible", @@ -766,7 +766,7 @@ "batchDetailCreated": "Created", "providerTopologyEmpty": "No providers connected yet", "badgeToastUnlocked": "Badge Unlocked!", - "batchListSearchPlaceholder": "Search by ID, endpoint, model…", + "batchListSearchPlaceholder": "Search by ID, endpoint, model\u2026", "batchListDeleteAllCompletedTitle": "Delete all completed batches", "batchListBatchesTable": "Batches", "changelogViewerLoading": "Loading changelog from GitHub...", @@ -780,7 +780,7 @@ "batchFileDetailClose": "Close", "batchFileDetailFailedToLoad": "Failed to load file contents", "batchFileDetailLoadError": "Error loading file contents", - "batchFilesListSearchPlaceholder": "Search by ID or filename…", + "batchFilesListSearchPlaceholder": "Search by ID or filename\u2026", "batchFilesListFilesTable": "Files", "batchFilesCount": "{count, plural, one {# file} other {# files}}", "batchFilesAllPurposes": "All purposes", @@ -796,7 +796,7 @@ "fine-tune": "Fine-tuning", "assistants": "Assistants" }, - "batchPageLoadingMore": "Loading more…", + "batchPageLoadingMore": "Loading more\u2026", "recommended": "Recommended", "understand": "I understand", "batchConceptTitle": "Batch processing", @@ -806,17 +806,17 @@ "batchConceptAsync24h": "Async with a 24h completion window", "batchConceptUseCases": "Best for bulk classification, evaluations, and embeddings", "batchHeaderSubtitle": "Run many requests as one job", - "batchStep1": "1 · Upload JSONL", + "batchStep1": "1 \u00b7 Upload JSONL", "batchStep1Desc": "Add requests", - "batchStep2": "2 · Create batch", + "batchStep2": "2 \u00b7 Create batch", "batchStep2Desc": "Run job", - "batchStep3": "3 · Get results", + "batchStep3": "3 \u00b7 Get results", "batchStep3Desc": "Download output", "filesConceptTitle": "Batch files", "filesConceptSubtitle": "JSONL files used by batches: input requests, results, and errors.", - "filesConceptInput": "Input — your JSONL with one request per line", - "filesConceptOutput": "Output — results from completed requests", - "filesConceptError": "Errors — requests that failed", + "filesConceptInput": "Input \u2014 your JSONL with one request per line", + "filesConceptOutput": "Output \u2014 results from completed requests", + "filesConceptError": "Errors \u2014 requests that failed", "filesConceptRetention": "Retention: 30 days by default (29 days on Anthropic)", "wizardTitle": "New batch", "wizardClose": "Close", @@ -824,7 +824,7 @@ "wizardBack": "Back", "wizardCancel": "Cancel", "wizardCreate": "Create batch", - "wizardCreating": "Creating…", + "wizardCreating": "Creating\u2026", "wizardStep1Destination": "Destination", "wizardStep2Input": "Input", "wizardStep3Validate": "Validate", @@ -835,10 +835,10 @@ "wizardInputKindJsonl": "JSONL", "wizardInputKindCsv": "CSV (we'll convert)", "wizardDropOrPick": "Drop a file or click to pick", - "wizardCsvMappingTitle": "Map CSV columns → request fields", + "wizardCsvMappingTitle": "Map CSV columns \u2192 request fields", "wizardCsvMappingAddField": "Add field", "wizardCsvNoColumns": "No columns detected in CSV header.", - "wizardCsvIgnoreColumn": "— ignore —", + "wizardCsvIgnoreColumn": "\u2014 ignore \u2014", "wizardCsvCustomIdMapped": "custom_id mapped", "wizardCsvContentMapped": "Content field mapped (messages, input, or prompt)", "wizardCsvApplyMapping": "Apply mapping", @@ -846,20 +846,20 @@ "wizardCsvRowsSkipped": "{count} rows skipped", "wizardCsvRowError": "Row {row}: {reason}", "wizardValidationOk": "All lines valid", - "wizardValidating": "Validating…", - "wizardValidationParseFailed": "Validation failed — could not parse content.", - "wizardValidationSummary": "{lines} lines · {ids} unique custom_ids", + "wizardValidating": "Validating\u2026", + "wizardValidationParseFailed": "Validation failed \u2014 could not parse content.", + "wizardValidationSummary": "{lines} lines \u00b7 {ids} unique custom_ids", "wizardValidationErrorCount": "{count, plural, one {# error} other {# errors}} found", "wizardValidationDuplicateIds": "Duplicate custom_ids detected:", "wizardValidationFirstErrors": "Errors (first {count}):", "wizardValidationLine": "Line {line}", "wizardValidationErrors": "Validation errors", "wizardValidationPreview": "Preview (first 5 requests)", - "wizardValidationSamplingNote": "File is large — validated by sampling (first 1000 + last 100 lines). Full validation runs server-side.", + "wizardValidationSamplingNote": "File is large \u2014 validated by sampling (first 1000 + last 100 lines). Full validation runs server-side.", "wizardCostSync": "Sync cost", "wizardCostBatch": "Batch cost (-50%)", "wizardCostSavings": "Savings", - "wizardCostEstimatedNotice": "Estimated cost — real billing may vary.", + "wizardCostEstimatedNotice": "Estimated cost \u2014 real billing may vary.", "wizardErrorUpload": "Failed to upload file. Try again.", "wizardErrorCreate": "Failed to create batch. Try again.", "wizardEmptyProviders": "Connect a provider with batch support (OpenAI, Anthropic, or Gemini) to create a batch.", @@ -891,11 +891,11 @@ "filesListSizeColumn": "Size", "batchListProgressPartial": "(partial)", "batchListProviderColumn": "Provider", - "batchListProviderUnknown": "—", + "batchListProviderUnknown": "\u2014", "batchListProviderOther": "Other", "batchListTitle": "Batches", "batchListCount": "{count} batches", - "batchListRemovingCompleted": "Removing…", + "batchListRemovingCompleted": "Removing\u2026", "batchListRemoveCompleted": "Remove completed", "batchListTableStatus": "Status", "batchListTableId": "ID", @@ -904,9 +904,9 @@ "batchListTableProgress": "Progress", "batchListTableCreated": "Created", "batchListTableExpires": "Expires", - "batchListLoading": "Loading…", + "batchListLoading": "Loading\u2026", "batchListEmpty": "No batches found", - "batchListValidating": "Validating…", + "batchListValidating": "Validating\u2026", "batchStatusAll": "All statuses", "batchStatusInProgress": "In progress", "batchStatusValidating": "Validating", @@ -921,44 +921,44 @@ "batchStatusFinalizingWithFailures": "finalizing (with failures)", "batchStatusCancelledWithFailures": "cancelled with failures", "batchStatusExpiredWithFailures": "expired (partial)", - "wizardCostEstimating": "Estimating cost…", + "wizardCostEstimating": "Estimating cost\u2026", "wizardCostRequests": "Requests", "wizardCostInputTok": "input tok", "wizardCostOutputTok": "output tok", "wizardCostWindow": "Window", - "wizardDestinationSelectProvider": "Select a provider…", - "wizardDestinationSelectModel": "Select a model…", + "wizardDestinationSelectProvider": "Select a provider\u2026", + "wizardDestinationSelectModel": "Select a model\u2026", "wizardDestinationConnectProvider": "Connect a provider", - "batchListBatchCreated": "Batch {id} created — refreshing list…", + "batchListBatchCreated": "Batch {id} created \u2014 refreshing list\u2026", "batchListBatchCreatedDismiss": "Dismiss", - "batchListRefreshing": "Refreshing…", + "batchListRefreshing": "Refreshing\u2026", "batchListRefresh": "Refresh", "uploadFileModalRemove": "Remove", - "uploadFileModalUploading": "Uploading…", - "wizardInputReading": "Reading file…", + "uploadFileModalUploading": "Uploading\u2026", + "wizardInputReading": "Reading file\u2026", "wizardInputReady": "Ready", - "wizardInputLargeFileLabel": "Large file — sampling validation", - "wizardInputCsvJsonlReady": "JSONL generated — ready to validate.", - "wizardInputLargeFileWarning": "Large file detected — validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.", + "wizardInputLargeFileLabel": "Large file \u2014 sampling validation", + "wizardInputCsvJsonlReady": "JSONL generated \u2014 ready to validate.", + "wizardInputLargeFileWarning": "Large file detected \u2014 validation runs by sampling (first 5 MB + last 100 KB). Full validation happens server-side.", "wizardCostWindow24h": "24h completion window", "wizardValidationFieldsOk": "required fields valid", "filesListDelete": "Delete", "filesListDownload": "Download", "batchDetailActionCancel": "Cancel batch", "batchDetailActionRetry": "Retry failed requests", - "batchDetailCancelling": "Cancelling…", - "batchDetailRetrying": "Preparing retry…", + "batchDetailCancelling": "Cancelling\u2026", + "batchDetailRetrying": "Preparing retry\u2026", "batchDetailCancelConfirm": "Cancel this batch? In-progress requests will stop.", "batchActionCancelError": "Failed to cancel batch. Try again.", "batchActionRetryError": "Failed to retry failed requests. Try again.", "batchConceptRetentionNote": "Results and error files are retained for 30 days (Anthropic: 29 days)", "batchFilesSizeColumn": "Size", "toggleNoAuthProvider": "Toggle no-auth provider", - "loadingModels": "Loading models…", + "loadingModels": "Loading models\u2026", "selectAModel": "Select a model", "degraded": "Degraded", "zeroConfigBannerTitle": "Running in zero-config mode", - "zeroConfigBannerBody": "OmniRoute auto-generated secure encryption keys on first launch. They are persisted to {dataDir}. No action is required — your data is encrypted and safe. To use custom keys, add JWT_SECRET and STORAGE_ENCRYPTION_KEY to that file.", + "zeroConfigBannerBody": "OmniRoute auto-generated secure encryption keys on first launch. They are persisted to {dataDir}. No action is required \u2014 your data is encrypted and safe. To use custom keys, add JWT_SECRET and STORAGE_ENCRYPTION_KEY to that file.", "batchRelativeTimeIn": "in {value}", "batchRelativeTimeAgo": "{value} ago", "batchDetailsTitle": "Batch Details", @@ -1291,7 +1291,7 @@ "resilienceConnections": "Connection Resilience", "resilienceConnectionsSubtitle": "Cooldown, breaker, lockout state", "settingsModalityBridge": "Modality Bridge", - "settingsModalityBridgeSubtitle": "Image/audio → text fallback for text-only models", + "settingsModalityBridgeSubtitle": "Image/audio \u2192 text fallback for text-only models", "commandPalette": { "title": "Command palette", "searchPlaceholder": "Search pages, settings, tools...", @@ -1470,7 +1470,7 @@ "failedFetch": "Failed to fetch compliance audit log.", "viewDetails": "View details", "closeDetails": "Close details", - "notAvailable": "—", + "notAvailable": "\u2014", "system": "system", "previous": "Previous", "next": "Next", @@ -1560,7 +1560,7 @@ "header": { "logout": "Logout", "quickNavigation": "Quick nav", - "quickNavigationTitle": "Quick navigation (⌘K / Ctrl+K)", + "quickNavigationTitle": "Quick navigation (\u2318K / Ctrl+K)", "openQuickNavigation": "Open quick navigation", "switchToLightMode": "Switch to light mode", "switchToDarkMode": "Switch to dark mode", @@ -1594,7 +1594,7 @@ "costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers", "cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.", "limitsDescription": "Configure rate limits and quotas per API key and provider", - "runtimeDescription": "Live runtime observability — circuit breakers, cooldowns, model lockouts, sessions and quota alerts", + "runtimeDescription": "Live runtime observability \u2014 circuit breakers, cooldowns, model lockouts, sessions and quota alerts", "apiManagerDescription": "Manage API keys and access control for your OmniRoute instance", "batchDescription": "Process large volumes of requests asynchronously with batched API calls", "contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.", @@ -1606,7 +1606,7 @@ "memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing", "skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution", "agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration", - "translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini", + "translatorDescription": "Translate and test prompts across API formats: OpenAI \u2194 Claude \u2194 Gemini", "playgroundDescription": "Test prompts interactively with live provider responses and format inspection", "searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking", "logsDescription": "Real-time request logs, error traces, and streaming event inspector", @@ -1672,7 +1672,7 @@ "disabled": "Disabled", "connected": "connected", "disconnected": "disconnected", - "lastSync": "Remote settings sync {status} — Last sync: {time}", + "lastSync": "Remote settings sync {status} \u2014 Last sync: {time}", "statusLabel": "Remote settings sync status: {status}" }, "breadcrumbs": { @@ -1795,7 +1795,7 @@ "documentation": "Documentation", "healthMonitor": "Health Monitor", "reportIssue": "Report issue", - "activeError": "{active} active · {errors} error", + "activeError": "{active} active \u00b7 {errors} error", "oauthLabel": "OAuth", "apiKeyLabel": "API Key", "requestsShort": "{count} reqs", @@ -1838,7 +1838,7 @@ "noResponseStream": "No response stream received.", "updateComplete": "Update complete!", "updateFailed": "Update failed.", - "updateNetworkError": "Network error — connection lost during update.", + "updateNetworkError": "Network error \u2014 connection lost during update.", "stepInstallPackage": "Install Package", "stepRebuildNativeModules": "Rebuild Native Modules", "stepRestartService": "Restart Service", @@ -1886,7 +1886,7 @@ "diversityScoreTitle": "Provider Diversity", "diversityScoreDesc": "Provider concentration snapshot for the recent traffic window.", "diversityShannonEntropy": "Shannon entropy", - "diversityWindow": "Window: {count} reqs · Last {mins} mins", + "diversityWindow": "Window: {count} reqs \u00b7 Last {mins} mins", "diversityHealthy": "Healthy Distribution", "diversityRiskHigh": "High Vendor Lock-in Risk", "diversityRiskModerate": "Moderate Distribution", @@ -1909,16 +1909,16 @@ "chartModelUsageOverTime": "Model Usage Over Time", "chartNoData": "No data", "chartWeekly": "Weekly", - "activitySummary": "{active} active days · {tokens} tokens · {days} days", + "activitySummary": "{active} active days \u00b7 {tokens} tokens \u00b7 {days} days", "activityCellTitle": "{date}: {tokens} tokens", "activityLess": "Less", "activityMore": "More", "chartApiKeyBreakdown": "API Key Breakdown", "chartApiKey": "API Key", "mostActiveDay": "Most Active Day", - "datedTokenCount": "{date} · {tokens} tokens", + "datedTokenCount": "{date} \u00b7 {tokens} tokens", "noDataLast7Days": "No data in the last 7 days", - "requestTokenSummary": "{requests} requests · {tokens} tokens", + "requestTokenSummary": "{requests} requests \u00b7 {tokens} tokens", "chartByAccount": "By Account", "chartByApiKey": "By API Key", "unknownApiKey": "Unknown API key", @@ -1929,7 +1929,7 @@ "chartDate": "Date", "chartRequestsByProviderDate": "Requests by Provider & Date", "filterAllKeys": "All Keys", - "filterSearchKeys": "Search keys…", + "filterSearchKeys": "Search keys\u2026", "filterNoKeysMatch": "No keys match", "filterOneKey": "1 key", "filterMultipleKeys": "{count} keys", @@ -1988,7 +1988,7 @@ "comboHealth": "Combo Health", "comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics", "compressionAnalyticsTitle": "Compression Analytics", - "compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats.", + "compressionAnalyticsDescription": "Compression analytics \u2014 token savings, mode breakdown, and provider stats.", "autoRoutingTotalAutoRequests": "Total Auto Requests", "autoRoutingAvgSelectionScore": "Avg Selection Score", "autoRoutingExplorationRate": "Exploration Rate", @@ -2015,7 +2015,7 @@ "comboHealthQuotaRisk": "{level} quota risk", "comboHealthConfidence": "{level} confidence", "comboHealthProjectedCost": "Projected cost", - "comboHealthCostHistory": "history {total} · {daily}/day", + "comboHealthCostHistory": "history {total} \u00b7 {daily}/day", "comboHealthProjectedRequests": "Projected requests", "comboHealthRequestsInRange": "{count} in selected range", "comboHealthWorstProjectedQuota": "Worst projected quota", @@ -2070,7 +2070,7 @@ "stable": "Stable" }, "comboHealthUsageSkewDescription": "Model request share and token share within this combo.", - "comboHealthShareSummary": "Request share {requests} · Token share {tokens}", + "comboHealthShareSummary": "Request share {requests} \u00b7 Token share {tokens}", "comboHealthPerformance": "Performance", "comboHealthPerformanceDescription": "Reliability and throughput for routed combo traffic.", "comboHealthExecutionTargetsDescription": "Step-level runtime metrics and quota visibility for structured combo targets.", @@ -2098,14 +2098,14 @@ "compressionAnalyticsTotalTokens": "Total tokens", "compressionAnalyticsCacheTokens": "Cache tokens", "compressionAnalyticsNoDataYet": "No compression data yet", - "compressionAnalyticsLoading": "Loading compression analytics…", + "compressionAnalyticsLoading": "Loading compression analytics\u2026", "compressionAnalyticsNoDataDescription": "Compression requests will appear here after the first request through /v1/chat/completions with compression enabled.", "rangeLast24h": "Last 24h", "rangeLast7d": "Last 7d", "rangeLast30d": "Last 30d", "rangeAllTime": "All time", - "compressionAnalyticsModeStats": "{count} requests · {tokens} tokens saved", - "compressionAnalyticsSkipped": " · {count} skipped (no-op)", + "compressionAnalyticsModeStats": "{count} requests \u00b7 {tokens} tokens saved", + "compressionAnalyticsSkipped": " \u00b7 {count} skipped (no-op)", "compressionAnalyticsRealTokens": "{count} real tokens", "compressionAnalyticsValidationRestores": "validation restores", "compressionAnalyticsRealUsageReceipts": "Real Usage Receipts", @@ -2137,8 +2137,8 @@ }, "providerUtilizationGlobalView": "Global View", "providerUtilizationAccountSplit": "Account Split", - "providerUtilizationLoading": "Loading utilization data…", - "retrying": "Retrying…", + "providerUtilizationLoading": "Loading utilization data\u2026", + "retrying": "Retrying\u2026", "retry": "Retry", "providerUtilizationNoDataDescription": "Provider quota snapshots will appear here after utilization data is collected.", "providerUtilizationStepConnect": "Connect providers through OAuth or API keys in Providers", @@ -2193,7 +2193,7 @@ "routeStep": "Step", "tokens": "Tokens", "notAvailable": "n/a", - "routeTokenCounts": "{input} in · {output} out", + "routeTokenCounts": "{input} in \u00b7 {output} out", "routeEvidence": "Evidence", "routeFactors": "Routing factors", "routeFactorsSubtitle": "Weighted signals used for this explanation", @@ -2202,7 +2202,7 @@ "routeRecommendations": "Recommendations", "routeLimitations": "Limitations", "routeNoKnownLimitations": "No known limitations for this explanation.", - "searchAnalyticsLoading": "Loading search analytics…", + "searchAnalyticsLoading": "Loading search analytics\u2026", "searchAnalyticsNoData": "No search data available yet.", "searchAnalyticsNoDataDescription": "Search requests will appear here after the first search via /v1/search.", "searchAnalyticsToday": "{count} today", @@ -2213,7 +2213,7 @@ "searchAnalyticsProviderBreakdown": "Provider Breakdown", "searchAnalyticsQueries": "queries", "searchAnalyticsEmptyDescription": "Use POST /v1/search to start routing web searches.", - "searchAnalyticsFreeTier": "Free tier available: Serper (2,500/mo), Brave (2,000/mo), Exa (1,000/mo), Tavily (1,000/mo) — total 6,500+ free searches/month with automatic failover.", + "searchAnalyticsFreeTier": "Free tier available: Serper (2,500/mo), Brave (2,000/mo), Exa (1,000/mo), Tavily (1,000/mo) \u2014 total 6,500+ free searches/month with automatic failover.", "autoRoutingNoDataAvailable": "No auto-routing analytics available. Make requests using the auto/ prefix to see metrics.", "defaultVariantLabel": "Default", "freeTierAvailable": "Free tier available" @@ -2262,7 +2262,7 @@ "deleteConfirm": "Delete this API key?", "usageTips": "Usage Tips", "tipAuth": "Use API keys in the Authorization header as Bearer YOUR_KEY", - "tipSecure": "Keys are only shown once during creation — store them securely", + "tipSecure": "Keys are only shown once during creation \u2014 store them securely", "tipSeparate": "Create separate keys for different clients or environments", "tipRestrict": "Restrict keys to specific models for better security and cost control", "keyName": "Key Name", @@ -2280,7 +2280,7 @@ "localUsageCommandBadge": "Usage cmd", "keyCreated": "API Key Created", "keyCreatedSuccess": "Key created successfully!", - "keyCreatedNote": "Copy and store this key now — it won't be shown again.", + "keyCreatedNote": "Copy and store this key now \u2014 it won't be shown again.", "done": "Done", "savePermissions": "Save Permissions", "endpointRestrictions": "Allowed Endpoints", @@ -2335,7 +2335,7 @@ "failedUpdatePermissionsRetry": "Failed to update permissions. Please try again.", "unknownProvider": "unknown", "copyMaskedKey": "Copy masked key", - "keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key", + "keyOnlyAvailableAtCreation": "Full key available only at creation time \u2014 copy it when you first create the key", "modelsCount": "{count, plural, one {# model} other {# models}}", "devicesCount": "{count, plural, one {# device} other {# devices}}", "devicesTooltip": "{count, plural, one {# distinct IP/User-Agent device seen with this key (last 30 min)} other {# distinct IP/User-Agent devices seen with this key (last 30 min)}}", @@ -2351,7 +2351,7 @@ "models": "{count} models", "permissionsTitle": "Permissions: {name}", "allowAllDesc": "This key can access all available models.", - "restrictLoading": "Loading model catalog…", + "restrictLoading": "Loading model catalog\u2026", "restrictCatalogUnavailable": "Model catalog unavailable; this key has {selectedCount} selected model restrictions.", "restrictDesc": "This key can access {selectedCount} of {totalModels} models.", "selectedCount": "{count} selected", @@ -2418,7 +2418,7 @@ "refreshAuditLogAria": "Refresh audit log", "tableAria": "Audit log entries", "failedFetchAuditLog": "Failed to fetch audit log", - "notAvailable": "—", + "notAvailable": "\u2014", "description": "Administrative actions and security events", "showing": "Showing {count} entries (offset {offset})", "previous": "Previous" @@ -2453,7 +2453,7 @@ "addConnection": "Add Connection", "backToProviders": "Back to Providers", "connections": "{count} Connections", - "noConnections": "No connections yet — add one from the provider page.", + "noConnections": "No connections yet \u2014 add one from the provider page.", "loading": "Loading...", "suggestedModels": "Suggested models from provider", "imageGeneration": "Image Generation", @@ -2475,7 +2475,7 @@ "enterTextToSynthesize": "Please enter text to synthesize.", "selectAudioToTranscribe": "Please select an audio file to transcribe.", "noSpeechDetected": "No speech detected in the audio file. If you uploaded music or a silent file, try an audio file with spoken words. Provider: \"{provider}\".", - "emptyTranscription": "Transcription returned empty text. The audio may contain no recognizable speech, or the \"{provider}\" API key may be invalid. Check Dashboard → Logs → Proxy for details.", + "emptyTranscription": "Transcription returned empty text. The audio may contain no recognizable speech, or the \"{provider}\" API key may be invalid. Check Dashboard \u2192 Logs \u2192 Proxy for details.", "topazRequiresImage": "Topaz requires an input image.", "enterPrompt": "Please enter a prompt.", "enhanceThisImage": "Enhance this image", @@ -2508,8 +2508,8 @@ "wordTimestamps": "Word-level timestamps ({count} words)", "providerCount": "{count} providers", "imageToTextBridgeAvailable": "Modality Bridge can describe images for text-only models now. The inline playground will be available when /api/v1/images/understanding is implemented.", - "imageToTextBridgeCta": "Configure the Image→Text bridge in Modality Bridge settings", - "sttBridgeCta": "Configure the Speech→Text bridge in Modality Bridge settings", + "imageToTextBridgeCta": "Configure the Image\u2192Text bridge in Modality Bridge settings", + "sttBridgeCta": "Configure the Speech\u2192Text bridge in Modality Bridge settings", "existingConnection": "Existing connection", "openrouterSttDescription": "OpenRouter speech-to-text models use your configured OpenRouter connection. No separate credential is required." }, @@ -2586,16 +2586,16 @@ "statusMissing": "No credential", "statusRateLimited": "Rate limited", "configureProvider": "Configure in Providers", - "loadingProviders": "Loading providers…", + "loadingProviders": "Loading providers\u2026", "failedToLoadProviders": "Failed to load providers", "costPerQuery": "Cost/query", "freeQuota": "Free quota/mo", "scrapeUrl": "URL to scrape", "scrapeUrlPlaceholder": "https://example.com", "scrapeUrlRequired": "URL is required", - "scrapeUrlInvalid": "Invalid URL — it must start with http:// or https://", + "scrapeUrlInvalid": "Invalid URL \u2014 it must start with http:// or https://", "scrapeExtract": "Extract", - "scrapeExtracting": "Extracting…", + "scrapeExtracting": "Extracting\u2026", "scrapeFullPage": "Full page", "scrapeFormat": "Format", "formatMarkdown": "Markdown", @@ -2610,7 +2610,7 @@ "scrapeEmptyState": "Enter a URL to extract its content", "scrapeProvidersAvailable": "Available providers: Firecrawl, Jina Reader, Tavily, TinyFish.", "compareRun": "Compare", - "compareRunning": "Comparing…", + "compareRunning": "Comparing\u2026", "autoProvider": "Auto (cheapest)", "configuredStatus": "Configured", "rateLimitedStatus": "Rate limited", @@ -2631,7 +2631,7 @@ "contentTruncated": "Content truncated to 256 KB (original size: {size})", "viewFullRaw": "View full raw content", "rawScrapedContent": "Raw scraped content", - "rawContent": "Raw content — {size}", + "rawContent": "Raw content \u2014 {size}", "closeRawModal": "Close raw content modal", "httpError": "Error {status}", "failed": "Failed", @@ -2644,11 +2644,11 @@ "musicSample": "Upbeat jazz piano with light percussion", "noAudioUrl": "No audio URL in response: {response}", "documentUrl": "Document URL", - "fileTooLarge25Mb": "File too large — max 25 MB", + "fileTooLarge25Mb": "File too large \u2014 max 25 MB", "selectAudioFirst": "Please select an audio file first.", "speechToText": "Speech to Text", - "chooseFile": "Choose file…", - "audioFormats25Mb": "mp3, wav, m4a, ogg, flac — max 25 MB", + "chooseFile": "Choose file\u2026", + "audioFormats25Mb": "mp3, wav, m4a, ogg, flac \u2014 max 25 MB", "textToSpeech": "Text to Speech", "ttsSample": "Hello, this is a text-to-speech test.", "video": "Video", @@ -2663,7 +2663,7 @@ "selectAll": "Select all", "clear": "Clear", "maxCompareProviders": "A maximum of {count} providers can be compared at once.", - "compareResults": "Results — “{query}”", + "compareResults": "Results \u2014 \u201c{query}\u201d", "resultCount": "{count} results", "noResults": "No results", "sharedResultTitle": "In common with another provider", @@ -2675,8 +2675,8 @@ "cliTools": { "title": "CLI Tools", "classifierCompatTitle": "Auto-permission classifier compatibility", - "classifierCompatDescription": "Short-circuit Claude Code’s --permission-mode auto security classifier with a synthetic allow response so fallback routes do not fail closed. Off by default.", - "classifierCompatCycle": "Cycle: off → auto → always", + "classifierCompatDescription": "Short-circuit Claude Code\u2019s --permission-mode auto security classifier with a synthetic allow response so fallback routes do not fail closed. Off by default.", + "classifierCompatCycle": "Cycle: off \u2192 auto \u2192 always", "classifierCompatLoadFailed": "Failed to load setting", "classifierCompatMode": { "off": "Off", @@ -2693,12 +2693,12 @@ "ccOnboardingWindowNote": "Claude Code assumes a 200K context window for any model id it does not recognize. For a model with a different real window, add CLAUDE_CODE_AUTO_COMPACT_WINDOW just under it so auto-compaction does not fire too early.", "failedSave": "Failed to save", "profileSyncTitle": "CLI profile auto-sync", - "profileSyncDescription": "After provider models are synchronized, automatically regenerate CLI tool profiles from the live catalog. Off by default — only profile files are written; the active/default configuration is never changed.", + "profileSyncDescription": "After provider models are synchronized, automatically regenerate CLI tool profiles from the live catalog. Off by default \u2014 only profile files are written; the active/default configuration is never changed.", "profileSyncLoadFailed": "Failed to load settings", "codexProfiles": "Codex profiles", "codexProfilesDescription": "Regenerate ~/.codex/*.config.toml after model discovery.", "claudeProfiles": "Claude Code profiles", - "claudeProfilesDescription": "Regenerate each ~/.claude/profiles/…/settings.json after model discovery.", + "claudeProfilesDescription": "Regenerate each ~/.claude/profiles/\u2026/settings.json after model discovery.", "noActiveProviders": "No active providers", "noActiveProvidersDesc": "Please add and connect providers first to configure CLI tools.", "mapModels": "Map Models", @@ -2901,7 +2901,7 @@ "cursor": "Cursor AI Code Editor", "continue": "Continue AI Assistant", "opencode": "OpenCode AI coding agent (Terminal)", - "kiro": "Amazon Kiro — AI-powered IDE", + "kiro": "Amazon Kiro \u2014 AI-powered IDE", "windsurf": "Windsurf AI Code Editor", "copilot": "GitHub Copilot AI Assistant", "amp": "Sourcegraph Amp coding assistant CLI", @@ -2922,7 +2922,7 @@ "interpreter": "Open Interpreter autonomous coding agent CLI", "omp": "Oh My Pi terminal coding agent", "letta": "Letta CLI agent with persistent memory and tool use", - "prime-agent": "Prime Agent — self-improving RLM coding harness with OpenAI-compatible provider support", + "prime-agent": "Prime Agent \u2014 self-improving RLM coding harness with OpenAI-compatible provider support", "warp": "Warp AI terminal with custom provider support", "agent-deck": "Agent Deck multi-agent orchestrator" }, @@ -3007,7 +3007,7 @@ "steps": { "1": { "title": "Open Kiro Settings", - "desc": "Go to Settings → AI Provider" + "desc": "Go to Settings \u2192 AI Provider" }, "2": { "title": "Base URL", @@ -3128,12 +3128,12 @@ "hermesRemovePendingRole": "Remove this role from pending changes", "hermesApply": "Apply to Hermes Agent", "hermesRolesWillUpdate": "{count, plural, one {# role will be updated} other {# roles will be updated}}", - "hermesPreviewPath": "Preview — will write to ~/.hermes/config.yaml", + "hermesPreviewPath": "Preview \u2014 will write to ~/.hermes/config.yaml", "hermesSaveDescription": "Saves the selected model for each role into", "copilotConfigGenerator": "GitHub Copilot Config Generator", "copilotGeneratorDescriptionPrefix": "Generates the", "copilotGeneratorDescriptionSuffix": "block for VS Code GitHub Copilot using the Azure vendor pattern. Select the models you want, then copy the JSON into your config file.", - "copilotCompatibilityWarning": "This configuration uses the Azure vendor workaround for custom model lists. Tested with VS Code ≥ 1.109 and GitHub Copilot Chat ≥ v0.37. Future extension updates may change this behavior.", + "copilotCompatibilityWarning": "This configuration uses the Azure vendor workaround for custom model lists. Tested with VS Code \u2265 1.109 and GitHub Copilot Chat \u2265 v0.37. Future extension updates may change this behavior.", "copilotApiKey": "API Key", "copilotSelectModels": "Select models ({selected}/{total})", "selectAll": "Select all", @@ -3170,7 +3170,7 @@ "cliproxyapiUnhealthy": "Unhealthy", "cliproxyapiUnknown": "Unknown", "cliproxyapiUpdate": "Update", - "cliproxyapiUpdateAvailable": "Update available: v{current} → v{latest}", + "cliproxyapiUpdateAvailable": "Update available: v{current} \u2192 v{latest}", "cliproxyapiVersion": "Version" }, "combos": { @@ -3178,7 +3178,7 @@ "description": "Create model combos with weighted routing and fallback support", "autoCatalogTitle": "Auto-routing catalog", "autoCatalogTemplateCount": "{count} templates", - "autoCatalogDescription": "Built-in auto/* combos resolved dynamically from connected providers. Use any of these IDs as the model field — no setup needed.", + "autoCatalogDescription": "Built-in auto/* combos resolved dynamically from connected providers. Use any of these IDs as the model field \u2014 no setup needed.", "autoCatalogExpand": "Expand auto-routing catalog", "autoCatalogCollapse": "Collapse auto-routing catalog", "createCombo": "Create Combo", @@ -3213,7 +3213,7 @@ "errorDeleting": "Error deleting combo", "testFailed": "Test request failed", "failedToggle": "Failed to toggle combo", - "testResults": "Test Results — {name}", + "testResults": "Test Results \u2014 {name}", "resolvedBy": "Resolved by:", "more": "+{count} more", "reqs": "reqs", @@ -3240,7 +3240,7 @@ "resetAware": "Reset-Aware RR", "resetAwareDesc": "Balances remaining quota against 5h and weekly resets, then round-robins similar scores", "strictRandom": "Strict Random", - "strictRandomDesc": "Shuffle deck — uses each model once before reshuffling", + "strictRandomDesc": "Shuffle deck \u2014 uses each model once before reshuffling", "fusion": "Fusion", "fusionDesc": "Fans the prompt to every panel model in parallel, then a judge model synthesizes one final answer", "fusionJudgeModel": "Judge model", @@ -3279,9 +3279,9 @@ "liveInactiveCount": "Inactive ({count})", "liveNoRun": "No combo run available.", "liveDataHint": "Live data arrives through the WebSocket combo channel.", - "liveDisconnected": "Live disabled — WebSocket disconnected. Showing last known state.", + "liveDisconnected": "Live disabled \u2014 WebSocket disconnected. Showing last known state.", "liveSelectCombo": "Select combo", - "liveSelectComboPlaceholder": "— select combo —", + "liveSelectComboPlaceholder": "\u2014 select combo \u2014", "liveTargetCount": "{count, plural, one {# target} other {# targets}}", "liveSingle": "Single", "liveFleet": "Fleet", @@ -3355,13 +3355,13 @@ "example": "Prefer a 60% weekly account resetting tomorrow over an 80% account resetting later." }, "strict-random": { - "when": "Use when you want perfectly even spread — each model used once before repeating.", + "when": "Use when you want perfectly even spread \u2014 each model used once before repeating.", "avoid": "Avoid when models have different quality or latency and order matters.", "example": "Example: Multiple accounts of the same model to distribute usage evenly." }, "p2c": { "when": "Use when you want low-latency selection using Power-of-Two-Choices algorithm.", - "avoid": "Avoid for small combos with 2 or fewer models — no benefit over round-robin.", + "avoid": "Avoid for small combos with 2 or fewer models \u2014 no benefit over round-robin.", "example": "Example: High-throughput inference across 4+ equivalent model endpoints." }, "context-relay": { @@ -3459,7 +3459,7 @@ "routingInputsHint": "Mode pack and weighting stay here; breaker runtime state stays on the Health page.", "emailVisibilityHint": "Account emails here follow the global privacy toggle.", "emailVisibilityTooltip": "Use the eye icon to reveal or hide account emails globally across combos, providers and quota screens.", - "builderModeStep": "Step by step (Provider → Model)", + "builderModeStep": "Step by step (Provider \u2192 Model)", "builderModeGlobal": "Global model search (Custom combo)", "builderGlobalSearchPlaceholder": "Search models across all providers (e.g. opus, sonnet, deepseek, kimi, qwen)...", "builderGlobalAddAll": "Add all", @@ -3504,7 +3504,7 @@ "advancedWeightsTitle": "Advanced: Scoring Weights", "nestedComboFlatten": "Flatten nested combos", "nestedComboExecute": "Execute nested combos as targets", - "effectiveRoutingShare": "Effective routing share (weight ÷ total)", + "effectiveRoutingShare": "Effective routing share (weight \u00f7 total)", "weightQuota": "Quota", "weightHealth": "Health", "weightCostInv": "Cost", @@ -3636,7 +3636,7 @@ "usageGuideInvokeTitle": "How to call this combo", "usageGuideInvokeDesc": "Send the combo's exact name as the model, e.g. model: \"my-combo\" (or combo/my-combo).", "usageGuideInvokeAutoNote": "auto and auto/* are a separate zero-config router that does not use your combos (unless a combo is literally named auto).", - "usageGuideInvokeOpenrouterNote": "openrouter/auto is a real paid OpenRouter product (Auto Best Available), not an OmniRoute alias — exclude it via Settings → Routing → Hide paid models.", + "usageGuideInvokeOpenrouterNote": "openrouter/auto is a real paid OpenRouter product (Auto Best Available), not an OmniRoute alias \u2014 exclude it via Settings \u2192 Routing \u2192 Hide paid models.", "emailVisibilityStateOn": "On", "emailVisibilityStateOff": "Off", "reorderHandle": "Drag to reorder", @@ -3667,7 +3667,7 @@ "builderStageVisited": "Stage completed", "builderStageCurrent": "Current stage", "builderStagePending": "Pending", - "builderStageLocked": "Locked — complete previous stage first", + "builderStageLocked": "Locked \u2014 complete previous stage first", "builderTitle": "Build a Combo", "builderBrowseCatalog": "Browse catalog", "builderProvider": "Provider", @@ -3744,7 +3744,7 @@ "kimiPresetCta": "Add preset", "intelligentComboCount": "{count, plural, one {# intelligent combo} other {# intelligent combos}}", "providersInScope": "{count, plural, one {# provider in scope} other {# providers in scope}}", - "savingModePack": "Saving {pack}…", + "savingModePack": "Saving {pack}\u2026", "errorTitle": "Failed to load combos", "errorDescription": "We could not load combo data right now. Check your connection and try again.", "errorId": "Error ID: {id}", @@ -3840,7 +3840,7 @@ "dimension": "Dimension", "share": "Share", "legacyOrFree": "Legacy / Free", - "filterRows": "Filter rows…", + "filterRows": "Filter rows\u2026", "filterCostExplorerRows": "Filter Cost Explorer rows", "noMatchingCostRows": "No matching cost rows", "noMatchingCostRowsDescription": "Adjust the search text, grouping, or selected time window.", @@ -3850,7 +3850,7 @@ "endpoint": { "title": "API Endpoint", "subtitle": "Use the OpenAI-compatible endpoint with most SDKs and tools.", - "testEndpoint": "Test endpoint →", + "testEndpoint": "Test endpoint \u2192", "advancedProtocols": "Advanced protocols", "available": "Available Endpoints", "cloudProxy": "Cloud Proxy", @@ -3894,7 +3894,7 @@ "webSearch": "Web Search", "webSearchDesc": "Unified web search across multiple providers with automatic failover and caching", "searchProvider": "Search Provider", - "searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected.", + "searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed \u2014 search providers are ready to use once an API key is connected.", "enableCloudTitle": "Enable Cloud Proxy", "whatYouGet": "What you will get", "cloudBenefitAccess": "Access your API from anywhere in the world", @@ -3916,8 +3916,8 @@ "syncing": "Syncing...", "disabling": "Disabling...", "cloudConnectedVerified": "Cloud Proxy connected and verified!", - "connectedVerificationPending": "Connected — verification pending", - "connectedVerificationPendingWithError": "Connected — verification pending: {error}", + "connectedVerificationPending": "Connected \u2014 verification pending", + "connectedVerificationPendingWithError": "Connected \u2014 verification pending: {error}", "cloudDisabledSuccess": "Cloud disabled successfully", "syncedSuccess": "Synced successfully", "failedDisable": "Failed to disable cloud", @@ -3946,7 +3946,7 @@ "cloudflaredUnsupportedNote": "This platform is not supported for managed installs. Install cloudflared manually or point CLOUDFLARED_BIN to an existing binary.", "cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.", "cloudflaredLastError": "Last error: {error}", - "providerModelsTitle": "{provider} — Models", + "providerModelsTitle": "{provider} \u2014 Models", "noModelsForProvider": "No models available for this provider.", "chat": "Chat", "embedding": "Embedding", @@ -3980,7 +3980,7 @@ "a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.", "a2aQuickStartStep3": "Track and control tasks using `tasks/get` and `tasks/cancel`.", "completionsLegacy": "Completions (Legacy)", - "completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format", + "completionsLegacyDesc": "Legacy OpenAI text completions \u2014 accepts both prompt string and messages array format", "messagesApi": "Messages", "messagesApiDesc": "Native Anthropic Messages API format for Claude-compatible providers", "imageEdits": "Image Edits", @@ -4136,13 +4136,13 @@ "obsidianTokenConfigured": "Token configured. Obsidian tools are available via MCP.", "obsidianDisconnect": "Disconnect", "obsidianVaultSync": "Vault Sync (WebDAV)", - "obsidianVaultSyncDescription": "Sync your vault to Obsidian mobile using WebDAV over Tailscale. Obsidian mobile has built-in WebDAV support — no plugins needed.", + "obsidianVaultSyncDescription": "Sync your vault to Obsidian mobile using WebDAV over Tailscale. Obsidian mobile has built-in WebDAV support \u2014 no plugins needed.", "obsidianVaultDirectoryPath": "Vault Directory Path", "obsidianEnable": "Enable", "obsidianWebdavEnabled": "WebDAV sync enabled", "obsidianDisable": "Disable", "obsidianConfigureMobile": "Configure Obsidian Mobile", - "obsidianMobileInstructions": "In Obsidian mobile: Settings → Sync → WebDAV → enter the following:", + "obsidianMobileInstructions": "In Obsidian mobile: Settings \u2192 Sync \u2192 WebDAV \u2192 enter the following:", "obsidianWebdavUrl": "WebDAV URL", "obsidianUsername": "Username", "obsidianPassword": "Password", @@ -4192,14 +4192,14 @@ "disableLabel": "Disable {label}", "enableLabel": "Enable {label}", "transportMode": "Transport Mode", - "transportStdioDesc": "Local — IDE spawns process via omniroute --mcp", - "transportSseDesc": "Remote — Server-Sent Events over HTTP", - "transportStreamableHttpDesc": "Remote — Modern bidirectional HTTP", + "transportStdioDesc": "Local \u2014 IDE spawns process via omniroute --mcp", + "transportSseDesc": "Remote \u2014 Server-Sent Events over HTTP", + "transportStreamableHttpDesc": "Remote \u2014 Modern bidirectional HTTP", "copy": "Copy", "mcpDashboardCopyUrl": "Copy URL", "mcpDisabledTitle": "MCP is disabled", "mcpDisabledDesc": "Enable MCP above to configure transport mode and view server telemetry.", - "mcpIntro": "Model Context Protocol — {tools} tools across {scopes} scopes, {transports} transports (stdio / SSE / Streamable HTTP).", + "mcpIntro": "Model Context Protocol \u2014 {tools} tools across {scopes} scopes, {transports} transports (stdio / SSE / Streamable HTTP).", "mcpStep1": "Run via {code}", "mcpStep2": "Configure your MCP client to connect over stdio transport.", "mcpStep3": "Invoke tools like {code1} and {code2}.", @@ -4329,7 +4329,7 @@ "enableLabel": "Enable {label}", "a2aDisabledTitle": "A2A is disabled", "a2aDisabledDesc": "Enable A2A above to view task telemetry, agent details, and validation tools.", - "a2aIntro": "Agent2Agent JSON-RPC 2.0 endpoint — send tasks, stream responses, cancel in-flight jobs.", + "a2aIntro": "Agent2Agent JSON-RPC 2.0 endpoint \u2014 send tasks, stream responses, cancel in-flight jobs.", "a2aStep1": "Discover the agent card at {code}.", "a2aStep2": "Send JSON-RPC to {code1} using {code2} or {code3}.", "a2aStep3": "Track and cancel tasks with {code1} and {code2}." @@ -4341,7 +4341,7 @@ "allTypes": "All Types", "cancel": "Cancel", "checkHealth": "Check health", - "checkingHealth": "Checking…", + "checkingHealth": "Checking\u2026", "compactOld": "Compact old", "concept": { "title": "Conversational Memory", @@ -4430,7 +4430,7 @@ "pipelineOk": "Pipeline OK ({latencyMs}ms)", "playground": { "infoTitle": "Memory Playground", - "infoDesc": "Simulate what would be retrieved for a given query. No memories are modified — read-only preview.", + "infoDesc": "Simulate what would be retrieved for a given query. No memories are modified \u2014 read-only preview.", "queryLabel": "Test query", "queryPlaceholder": "Type a question or test phrase...", "strategyLabel": "Strategy", @@ -4458,9 +4458,9 @@ "description": "Optional Qdrant integration for scalable semantic search", "enableLabel": "Enable Qdrant", "enableDesc": "When enabled, Qdrant is used as the primary vector store", - "banner": "Tier 2 vector store — an external, scalable alternative to the built-in sqlite-vec (Tier 1). Enable it only if you have a very large memory set or want shared memory across instances; most users are fine on sqlite-vec. When enabled it becomes the primary store and automatically falls back to sqlite-vec if unreachable.", - "hostHelp": "Local Docker: http://localhost:6333 · Qdrant Cloud: your cluster URL", - "collectionHelp": "Any name — OmniRoute creates it on first use", + "banner": "Tier 2 vector store \u2014 an external, scalable alternative to the built-in sqlite-vec (Tier 1). Enable it only if you have a very large memory set or want shared memory across instances; most users are fine on sqlite-vec. When enabled it becomes the primary store and automatically falls back to sqlite-vec if unreachable.", + "hostHelp": "Local Docker: http://localhost:6333 \u00b7 Qdrant Cloud: your cluster URL", + "collectionHelp": "Any name \u2014 OmniRoute creates it on first use", "embeddingModelHelp": "Sets the vector dimension automatically on first use. Existing memories are not back-filled, and changing the model after data exists needs a fresh collection.", "testConnection": "Test connection", "testing": "Testing...", @@ -4599,7 +4599,7 @@ "previous": "Previous", "next": "Next", "activeProvider": "Active provider:", - "changeInSettings": "Change this in Settings → Memory & Skills.", + "changeInSettings": "Change this in Settings \u2192 Memory & Skills.", "installs": "installs", "marketplaceSkillsMpHint": "Configure your SkillsMP API key in Settings to browse the marketplace.", "marketplaceSkillsShHint": "Search the skills.sh open directory to discover and install agent skills.", @@ -4645,7 +4645,7 @@ "latencyP95": "p95", "latencyP99": "p99", "millisecondsShort": "{value}ms", - "notAvailable": "—", + "notAvailable": "\u2014", "totalRequests": "Total requests", "noDataYet": "No data yet", "promptCache": "Prompt Cache", @@ -4722,7 +4722,7 @@ "gracefulDegradationStatus": "Graceful Degradation Status", "additionalModels": "+{count} more models", "providerHealthMatrixTitle": "Provider Health Matrix", - "providerHealthMatrixDescription": "Provider × account × model states from breakers, cooldowns, lockouts and logs.", + "providerHealthMatrixDescription": "Provider \u00d7 account \u00d7 model states from breakers, cooldowns, lockouts and logs.", "healthMatrixRange": "Health matrix range", "providerFilter": "Provider filter", "onlyIssues": "Only issues", @@ -4733,17 +4733,17 @@ "loadingProviderHealthMatrix": "Loading provider health matrix...", "failedProviderHealthMatrix": "Failed to load Provider Health Matrix: {error}", "noProvidersMatchedFilters": "No providers matched the current filters.", - "modelPillSummary": "{requests} req · {successRate} success · {latency} avg", - "modelLockoutSummary": "{reason} · {duration} left", + "modelPillSummary": "{requests} req \u00b7 {successRate} success \u00b7 {latency} avg", + "modelLockoutSummary": "{reason} \u00b7 {duration} left", "locked": "locked", "inferred": "inferred", "inactive": "Inactive", "noConnectionId": "no connection id", - "accountModelSummary": "{connectionId} · {count} models", + "accountModelSummary": "{connectionId} \u00b7 {count} models", "cooldown": "cooldown", "durationRemaining": "{duration} remaining", "noSyncedModelsOrTraffic": "No synced models or recent traffic yet.", - "providerRowSummary": "{active}/{total} active accounts · {requests} req · {successRate} success · {latency} avg", + "providerRowSummary": "{active}/{total} active accounts \u00b7 {requests} req \u00b7 {successRate} success \u00b7 {latency} avg", "cooldownCount": "{count} cooldown", "lockoutCount": "{count} lockouts", "issueCount": "{count} issues", @@ -4850,7 +4850,7 @@ "target": "Target", "details": "Details", "ipAddress": "IP Address", - "notAvailable": "—", + "notAvailable": "\u2014", "noEntries": "No audit log entries found", "previous": "Previous", "next": "Next", @@ -4893,7 +4893,7 @@ "clientPayload": "Client Request Payload", "upstreamPayload": "Upstream Provider Payload", "upstreamNotSentYet": "Not sent to upstream yet", - "runningRequestDetailMeta": "Account: {account} — Elapsed: {elapsed}", + "runningRequestDetailMeta": "Account: {account} \u2014 Elapsed: {elapsed}", "export": "Export", "exporting": "Exporting...", "exportFailed": "Export failed", @@ -4913,7 +4913,7 @@ "copyFailed": "Failed to copy log entry", "copyLogEntry": "Copy log entry", "filterByLevel": "Filter by log level", - "searchPlaceholder": "Search logs…", + "searchPlaceholder": "Search logs\u2026", "searchAria": "Search log entries", "disableAutoScroll": "Disable auto-scroll", "enableAutoScroll": "Enable auto-scroll", @@ -4949,12 +4949,12 @@ "confirmPasswordPlaceholder": "Confirm password", "passwordsMismatch": "Passwords do not match", "setupComplete": "Setup Complete!", - "goToDashboard": "Go to Dashboard →", + "goToDashboard": "Go to Dashboard \u2192", "welcomeDesc": "OmniRoute is your local AI API proxy. It routes requests to multiple AI providers with load balancing, failover, and usage tracking.", "multiProvider": "Multi-Provider", "usageTracking": "Usage Tracking", "securityDesc": "Set a password to protect your dashboard, or skip for now.", - "securityDescSkipWarning": "⚠️ Without a password, you won't be able to add providers during setup. You can add them later from the dashboard after setting a password.", + "securityDescSkipWarning": "\u26a0\ufe0f Without a password, you won't be able to add providers during setup. You can add them later from the dashboard after setting a password.", "providerDesc": "Connect your first AI provider. You can add more later.", "providerRequiresPassword": "You need to set a password first to add providers. Please go back to the security step and set a password, or add providers later from the dashboard.", "apiKeyRequired": "API Key (required)", @@ -4977,7 +4977,7 @@ "apiKeyHelp": "An API key is a password for AI services. Get one from your provider's website (e.g., platform.openai.com, console.anthropic.com).", "tier": { "subtitle": "OmniRoute organises providers into three tiers so routing prefers the most reliable, lowest-cost path first.", - "flowCaption": "Requests flow through your subscription quotas first, then pay-per-token cheap providers, then free-tier providers — automatic, zero-config.", + "flowCaption": "Requests flow through your subscription quotas first, then pay-per-token cheap providers, then free-tier providers \u2014 automatic, zero-config.", "afterSetup": "after setup.", "tier1": { "label": "Premium clients", @@ -5040,7 +5040,7 @@ "importErrorInvalidPriority": "invalid priority (must be 1-100)", "importErrorMalformedRow": "malformed row", "importErrorNotArray": "file must contain a JSON array", - "importFromFileImporting": "Importing…", + "importFromFileImporting": "Importing\u2026", "importFromFileImport": "Import {count} providers", "importFromFileResult": "Imported {success} providers ({failed} failed)", "adaptaTutorial": { @@ -5054,7 +5054,7 @@ "step2Title": "Open DevTools", "step2DescPrefix": "Press", "step2DescSuffix": "to open Developer Tools.", - "step3Title": "Go to Application → Cookies", + "step3Title": "Go to Application \u2192 Cookies", "step3DescPrefix": "In the", "step3DescMiddle": "expand", "step3DescSuffix": "and click", @@ -5146,8 +5146,8 @@ "riskNotice": { "title": "Before continuing", "detailsTitle": "Usage caveats", - "tooltip": "Provider with usage caveats — click for details", - "oauth": "This provider uses your official product session/OAuth, which is not authorized for proxy/router use. We don't recommend intensive autonomous agent usage (OpenCloud-style, long multi-step flows, large batches) — the upstream may react by restricting or banning the account. Use at your own risk.", + "tooltip": "Provider with usage caveats \u2014 click for details", + "oauth": "This provider uses your official product session/OAuth, which is not authorized for proxy/router use. We don't recommend intensive autonomous agent usage (OpenCloud-style, long multi-step flows, large batches) \u2014 the upstream may react by restricting or banning the account. Use at your own risk.", "webCookie": "This provider authenticates through your web session cookies. The upstream service may invalidate the session at any time, requiring you to log in again. Not recommended for long unattended operations. Use at your own risk.", "deprecated": "This provider was discontinued by its upstream and may stop working without notice. Existing connections may continue working until the upstream fully removes access. Consider alternatives.", "dontShowAgain": "Don't show this again for this provider", @@ -5155,7 +5155,7 @@ "cancel": "Cancel" }, "warningNotice": { - "tooltip": "{count} connection(s) flagged — up to {maxFailures} recent failures{lastFailureSuffix}. Click to view connection health.", + "tooltip": "{count} connection(s) flagged \u2014 up to {maxFailures} recent failures{lastFailureSuffix}. Click to view connection health.", "lastFailureSuffix": " (last failure {time})", "ariaLabel": "View connection health details, {count} warning(s)" }, @@ -5203,7 +5203,7 @@ "clearing": "Clearing...", "until": "Until {time}", "providerTestFailed": "Provider test failed", - "providerTestTimeout": "Provider test timed out — too many connections to test at once", + "providerTestTimeout": "Provider test timed out \u2014 too many connections to test at once", "modeTest": "{mode} Test", "passedCount": "{count} passed", "failedCount": "{count} failed", @@ -5255,7 +5255,7 @@ "filterError": "Error", "filterBanned": "Banned", "filterCreditsExhausted": "Credits Exhausted", - "accountSearchPlaceholder": "Search accounts…", + "accountSearchPlaceholder": "Search accounts\u2026", "noFilteredConnections": "No connections match the current filter.", "failedSetAlias": "Failed to set alias", "setAliasSuccess": "Alias {alias} set", @@ -5277,7 +5277,7 @@ "importFromModels": "Import from /models", "modelsImported": "{count} models imported", "allModelsAlreadyImported": "All models already imported", - "noNewModelsToImport": "No new models to import — all models are already in the registry or custom models list", + "noNewModelsToImport": "No new models to import \u2014 all models are already in the registry or custom models list", "skippingExistingModels": "Skipping {count} existing models", "autoSync": "Auto-Sync", "autoSyncShort": "Sync", @@ -5293,7 +5293,7 @@ "resetToUpstreamDefaultsSuccess": "Restored upstream model defaults", "resetToUpstreamDefaultsFailed": "Failed to restore upstream model defaults", "autoSyncTooltip": "Automatically refresh model list every 24h (configurable via MODEL_SYNC_INTERVAL_HOURS)", - "autoSyncEnabled": "Auto-sync enabled — models will refresh periodically", + "autoSyncEnabled": "Auto-sync enabled \u2014 models will refresh periodically", "autoSyncDisabled": "Auto-sync disabled", "autoSyncToggleFailed": "Failed to toggle auto-sync", "autoSyncPartialFailure": "Auto-sync updated for some connections, but not all", @@ -5306,13 +5306,13 @@ "connectionCount": "{count} connection(s)", "fetchingModels": "Fetching available models...", "failedFetchModels": "Failed to fetch models", - "noFreeModelsFound": "No free models found for this provider — nothing imported.", + "noFreeModelsFound": "No free models found for this provider \u2014 nothing imported.", "fetchModelsSuccess": "Found {count} new models", "fetchModelsFailed": "Could not auto-fetch models (you can retry from settings)", "noModelsFound": "No models found", "importFailed": "Import failed", "noNewModelsAdded": "No new models were added.", - "adding": "Adding…", + "adding": "Adding\u2026", "close": "Close", "importingModelsTitle": "Importing Models", "copyModel": "Copy model", @@ -5363,7 +5363,7 @@ "importingModelById": "Importing {modelId}...", "importSuccessCount": "Successfully imported {count, plural, one {# model} other {# models}}!", "noNewModelsAddedExisting": "No new models were added (all already exist).", - "importDoneCount": "✓ Done! {count, plural, one {# model imported.} other {# models imported.}}", + "importDoneCount": "\u2713 Done! {count, plural, one {# model imported.} other {# models imported.}}", "unexpectedErrorOccurred": "An unexpected error occurred", "getApiKey": "Get API key", "getApiKeyDescription": "Register or sign up for an API key", @@ -5424,7 +5424,7 @@ "proxyOn": "Proxy On", "proxyOff": "Direct (bypass proxy)", "proxyEnabledTitle": "Proxy is enabled for this connection", - "proxyDisabledTitle": "Direct connection — bypasses every proxy (including the global proxy) for this connection", + "proxyDisabledTitle": "Direct connection \u2014 bypasses every proxy (including the global proxy) for this connection", "perKeyProxyOn": "Per-key", "perKeyProxyOff": "Connection", "perKeyProxyEnabledTitle": "Per-key proxy assignment enabled for this provider", @@ -5481,7 +5481,7 @@ "paramFiltersAllowedHint": "These params are re-added after denylist stripping (only if the client sent them).", "paramFiltersAutoLearnLabel": "Auto-learn from 400 errors", "paramFiltersAutoLearnHint": "When enabled, if the upstream returns a 400 with \"Unsupported parameter: X\", the param is automatically added to the block list and the request retried.", - "paramFiltersSaving": "Saving…", + "paramFiltersSaving": "Saving\u2026", "paramFiltersSaveChanges": "Save Changes", "paramFiltersResetToDefault": "Reset to Default", "paramFiltersLoadError": "Failed to load param filter config: {error}", @@ -5490,15 +5490,15 @@ "paramFiltersResetSuccess": "Param filter config reset to defaults", "paramFiltersResetError": "Failed to reset param filter config: {error}", "interceptionSectionTitle": "Web Tool Interception", - "interceptionSectionHint": "Route this provider's native web_search / web_fetch tool calls through OmniRoute's own search and fetch endpoints instead of letting the provider run them natively. Off by default — existing behavior is unchanged.", + "interceptionSectionHint": "Route this provider's native web_search / web_fetch tool calls through OmniRoute's own search and fetch endpoints instead of letting the provider run them natively. Off by default \u2014 existing behavior is unchanged.", "interceptSearchLabel": "Intercept web_search", "interceptSearchHint": "Rewrite native web_search tool calls to OmniRoute's /v1/search.", "interceptFetchLabel": "Intercept web_fetch", "interceptFetchHint": "Rewrite native web_fetch tool calls to OmniRoute's /v1/web/fetch.", "interceptionLoadError": "Failed to load interception settings: {error}", "interceptionSaveError": "Failed to save interception settings: {error}", - "ccAliasSectionTitle": "Expose in Claude Code (claude/…)", - "ccAliasSectionHint": "Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.", + "ccAliasSectionTitle": "Expose in Claude Code (claude/\u2026)", + "ccAliasSectionHint": "Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default \u2014 enabling this doubles catalog entries for all clients.", "ccAliasProviderLevelLabel": "Provider default", "ccAliasModelOverridesLabel": "Per-model overrides", "ccAliasModelOverrideAriaLabel": "Override for {modelId}", @@ -5510,7 +5510,7 @@ "ccAliasLoadError": "Failed to load discovery-alias settings: {error}", "ccAliasSaveError": "Failed to save discovery-alias setting: {error}", "compatUpstreamHeadersLabel": "Extra upstream headers", - "compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.", + "compatUpstreamHeadersHint": "High-privilege setting \u2014 same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) \u2014 the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.", "compatUpstreamHeaderName": "Header name", "compatUpstreamHeaderValue": "Value", "compatUpstreamAddRow": "Add header", @@ -5603,7 +5603,7 @@ "claudeImportSuccess": "Claude connection imported successfully", "claudeImportInvalidJson": "Could not parse the file as JSON", "claudeImportInvalidShape": "The file is not a valid .credentials.json", - "claudeImportDuplicate": "Account already exists — enable \"Replace existing\" to overwrite", + "claudeImportDuplicate": "Account already exists \u2014 enable \"Replace existing\" to overwrite", "claudeImportIdentityUnverified": "Bootstrap could not verify the account. Enable \"Replace existing\" or provide an email.", "claudeImportFailed": "Failed to import Claude auth", "claudeImportBulkModeUpload": "Upload files", @@ -5615,7 +5615,7 @@ "claudeImportBulkSubmit": "Import all", "claudeImportBulkSuccess": "Imported {count} Claude connections", "claudeImportBulkFailed": "Some entries failed to import", - "claudeImportBulkZipExtracting": "Extracting ZIP…", + "claudeImportBulkZipExtracting": "Extracting ZIP\u2026", "claudeImportBulkZipError": "Failed to extract ZIP", "geminiAuthAppliedLocal": "Gemini auth applied locally", "geminiAuthApplyFailed": "Failed to apply Gemini auth locally", @@ -5635,7 +5635,7 @@ "geminiImportSuccess": "Gemini connection imported successfully", "geminiImportInvalidJson": "Could not parse the file as JSON", "geminiImportInvalidShape": "The file is not a valid oauth_creds.json", - "geminiImportDuplicate": "Account already exists — enable \"Replace existing\" to overwrite", + "geminiImportDuplicate": "Account already exists \u2014 enable \"Replace existing\" to overwrite", "geminiImportIdentityUnverified": "Could not verify identity from id_token. Enable \"Replace existing\" or provide an email.", "geminiImportFailed": "Failed to import Gemini auth", "geminiImportBulkModeUpload": "Upload files", @@ -5647,7 +5647,7 @@ "geminiImportBulkSubmit": "Import all", "geminiImportBulkSuccess": "Imported {count} Gemini connections", "geminiImportBulkFailed": "Some entries failed to import", - "geminiImportBulkZipExtracting": "Extracting ZIP…", + "geminiImportBulkZipExtracting": "Extracting ZIP\u2026", "geminiImportBulkZipError": "Failed to extract ZIP", "codexAuthAppliedLocal": "Codex auth.json applied locally", "codexAuthApplyFailed": "Failed to apply Codex auth.json locally", @@ -5683,7 +5683,7 @@ "codexImportSuccess": "Codex connection imported successfully", "codexImportInvalidJson": "Could not parse the file as JSON", "codexImportInvalidShape": "The file is not a valid Codex auth.json", - "codexImportDuplicate": "Account already exists — enable \"Replace existing\" to overwrite", + "codexImportDuplicate": "Account already exists \u2014 enable \"Replace existing\" to overwrite", "codexImportFailed": "Failed to import Codex auth", "codexImportDetectedEmail": "Detected: {email}", "codexImportNoEmailDetected": "No email detected in file", @@ -5697,7 +5697,7 @@ "codexImportBulkLimit": "Max 50 files per import", "codexImportBulkSuccess": "{count} imported", "codexImportBulkFailed": "{count} failed", - "codexImportBulkZipExtracting": "Extracting ZIP…", + "codexImportBulkZipExtracting": "Extracting ZIP\u2026", "codexImportBulkZipError": "Failed to extract ZIP", "advancedSettings": "Advanced Settings", "chatPathLabel": "Chat Endpoint Path", @@ -5714,7 +5714,7 @@ "showEmails": "Show all emails", "hideEmails": "Hide all emails", "a": "A", - "accountConcurrencyCapHint": "Max concurrent requests this connection/account can handle. Subscription accounts usually allow only a few (1–3) — going over triggers 429s and cooldowns. Leave empty for the default.", + "accountConcurrencyCapHint": "Max concurrent requests this connection/account can handle. Subscription accounts usually allow only a few (1\u20133) \u2014 going over triggers 429s and cooldowns. Leave empty for the default.", "accountConcurrencyCapLabel": "Account concurrency cap", "accountIdHint": "Required by some providers (e.g. Cloudflare Workers AI) alongside the API token.", "accountIdLabel": "Account ID", @@ -5737,11 +5737,11 @@ "audioShortLabel": "Audio Short Label", "azureOpenAiBaseUrlHint": "Azure Open Ai Base Url Hint", "bailianBaseUrlHint": "Bailian Base Url Hint", - "claudeWebCookieHint": "Open claude.ai → DevTools → Application → Cookies → claude.ai, copy the 'sessionKey' value. Also need cf_clearance, __cf_bm, _cfuvid for Cloudflare.", + "claudeWebCookieHint": "Open claude.ai \u2192 DevTools \u2192 Application \u2192 Cookies \u2192 claude.ai, copy the 'sessionKey' value. Also need cf_clearance, __cf_bm, _cfuvid for Cloudflare.", "claudeWebCookiePlaceholder": "sessionKey=sk-ant-...", "blackboxWebCookieHint": "Blackbox Web Cookie Hint", "blackboxWebCookiePlaceholder": "Blackbox Web Cookie Placeholder", - "t3ChatWebCookieHint": "Open t3.chat → DevTools → Application → Local Storage → https://t3.chat, copy 'convex-session-id'. Then open DevTools → Network, copy the full Cookie header from any chat request. Paste both values in the fields below.", + "t3ChatWebCookieHint": "Open t3.chat \u2192 DevTools \u2192 Application \u2192 Local Storage \u2192 https://t3.chat, copy 'convex-session-id'. Then open DevTools \u2192 Network, copy the full Cookie header from any chat request. Paste both values in the fields below.", "t3ChatWebCookiePlaceholder": "convex-session-id=abc123...", "grokWebCookieHint": "Grok Web Cookie Hint", "blockClaudeExtraUsageDescription": "When enabled, OmniRoute marks this Claude connection unavailable as soon as the usage API reports queued extra usage, so fallback switches to another connection instead of continuing on pay-as-you-go extra billing.", @@ -5806,7 +5806,7 @@ "expirationBannerExpiredDesc": "Expiration Banner Expired Desc", "expirationBannerExpiringSoon": "Expiration Banner Expiring Soon", "expirationBannerExpiringSoonDesc": "Expiration Banner Expiring Soon Desc", - "extraApiKeyMasked": "Key {index}: {prefix}••••{suffix}", + "extraApiKeyMasked": "Key {index}: {prefix}\u2022\u2022\u2022\u2022{suffix}", "extraApiKeysHint": "Extra Api Keys Hint", "extraApiKeysLabel": "Extra Api Keys Label", "apiKeyHealthLabel": "API Key Health", @@ -5862,13 +5862,13 @@ "perplexityWebCookieHint": "Perplexity Web Cookie Hint", "perplexityWebCookiePlaceholder": "Perplexity Web Cookie Placeholder", "personalAccessTokenLabel": "Personal Access Token", - "qoderPatHint": "Paste your Qoder Personal Access Token (PAT). Generate one from Qoder → Settings → Personal Access Tokens.", + "qoderPatHint": "Paste your Qoder Personal Access Token (PAT). Generate one from Qoder \u2192 Settings \u2192 Personal Access Tokens.", "qoderPatPlaceholder": "Your Qoder personal access token", "rateLimitOverridesSection": "Rate Limit Overrides", "rateLimitOverridesMaxConcurrentHint": "Max concurrent requests override for this connection. Overrides the account-level cap.", "rateLimitOverridesMaxConcurrentLabel": "Max Concurrent (Rate Limit)", - "rateLimitOverridesMaxWaitMsHint": "Maximum time (ms) a request may wait for a rate-limit slot before failing. Overrides the global Resilience setting for this connection only.", - "rateLimitOverridesMaxWaitMsLabel": "Max Queue Wait (ms)", + "rateLimitOverridesMaxWaitMsHint": "Maximum time (ms) a request may wait for a rate-limit slot before failing", + "rateLimitOverridesMaxWaitMsLabel": "Max wait (ms)", "rateLimitOverridesMinTimeHint": "Minimum time (ms) between requests. Overrides the default rate limiter delay.", "rateLimitOverridesMinTimeLabel": "Min Interval (ms)", "rateLimitOverridesRpmHint": "Max requests per minute for this connection. Overrides the provider default.", @@ -5891,7 +5891,7 @@ "searchProvider": "Search Provider", "searchProviderDesc": "Search Provider Desc", "searchProviders": "Search Providers", - "searchByModel": "Search by model…", + "searchByModel": "Search by model\u2026", "searchProvidersHeading": "Search Providers Heading", "searxngBaseUrlHint": "Searxng Base Url Hint", "searxngInfo": "Searxng Info", @@ -5908,7 +5908,7 @@ "tagGroupPlaceholder": "Tag Group Placeholder", "testModel": "Test Model", "testingModel": "Testing Model", - "modelTestQuotaTooltip": "Quota exhausted — resets tomorrow or needs a top-up", + "modelTestQuotaTooltip": "Quota exhausted \u2014 resets tomorrow or needs a top-up", "toggleOffShort": "Off", "toggleOnShort": "On", "tokenExpiredBadge": "Token Expired Badge", @@ -5918,10 +5918,10 @@ "totalKeysRotating": "{count, plural, one {1 key rotating} other {# keys rotating}}", "unhideModel": "Unhide Model", "upstreamProxyProviders": "Upstream Proxy Providers", - "validationModelIdHint": "Model used to verify the API key. Leave blank to use the provider’s first available model.", + "validationModelIdHint": "Model used to verify the API key. Leave blank to use the provider\u2019s first available model.", "validationModelIdLabel": "Validation Model", "validationModelIdPlaceholder": "e.g. meta-llama/llama-3.1-8b-instruct", - "vertexServiceAccountPlaceholder": "Paste your Service Account JSON ({\"type\":\"service_account\",\"project_id\":\"…\",\"client_email\":\"…\",\"private_key\":\"…\"}) or an OAuth access_token", + "vertexServiceAccountPlaceholder": "Paste your Service Account JSON ({\"type\":\"service_account\",\"project_id\":\"\u2026\",\"client_email\":\"\u2026\",\"private_key\":\"\u2026\"}) or an OAuth access_token", "webCookieProviders": "Web Cookie Providers", "weeklyShort": "Weekly Short", "xiaomiMimoBaseUrlHint": "Xiaomi Mimo Base Url Hint", @@ -5940,16 +5940,16 @@ "zedImportNetworkError": "Zed Import Network Error", "zedImportNone": "Zed Import None", "zedImportSuccess": "Imported {credentials} credential(s) from Zed for {providers} provider(s)", - "zedImporting": "Importing…", + "zedImporting": "Importing\u2026", "zedNoCredentials": "No Zed credentials found in the keychain", "zedUnsupportedCredentials": "Found {count} keychain credential(s), but none matched supported providers", "zedManualTitle": "Manual Token Import", "zedManualDescription": "Use this when OmniRoute runs in Docker or the keychain is unavailable. Paste the API key that Zed stored under ~/.config/zed/settings.json, or copy it from the Zed AI settings panel.", - "zedPasteApiKey": "Paste API key…", - "zedSaving": "Saving…", + "zedPasteApiKey": "Paste API key\u2026", + "zedSaving": "Saving\u2026", "zedImportAction": "Import", "cursorAgentNudgeTitle": "Enable automatic Cursor session renewal", - "cursorAgentNudgeBody": "Install cursor-agent for automatic session renewal — without it, Cursor connections need periodic manual reconnection roughly every 24 hours.", + "cursorAgentNudgeBody": "Install cursor-agent for automatic session renewal \u2014 without it, Cursor connections need periodic manual reconnection roughly every 24 hours.", "cursorAgentNudgeDismiss": "Dismiss", "zedManualImportFailed": "Manual import failed", "zedManualImportSuccess": "Imported {provider} token from Zed", @@ -5962,14 +5962,14 @@ "grokImportFailed": "Failed to import Grok Build auth", "grokImportSuccess": "Grok Build connection imported successfully", "grokValidToken": "Valid Grok Build token detected", - "grokRefreshIncluded": "Refresh token included — automatic token renewal is enabled", - "grokRefreshMissing": "No refresh token found — re-import after the token expires", + "grokRefreshIncluded": "Refresh token included \u2014 automatic token renewal is enabled", + "grokRefreshMissing": "No refresh token found \u2014 re-import after the token expires", "grokConnectionName": "Connection name (optional)", - "grokSaving": "Saving…", + "grokSaving": "Saving\u2026", "grokSaveConnection": "Save Connection", "freeTierProviders": "Free Tier Providers", "freeTierLabel": "Free tier available", - "freeTierProvidersDesc": "Providers with free tiers — some require an API key signup, others need no credentials at all.", + "freeTierProvidersDesc": "Providers with free tiers \u2014 some require an API key signup, others need no credentials at all.", "providerSummaryAll": "Total", "ideProviders": "IDE Providers", "ideProvidersDesc": "Editors with built-in AI subscription. Use the provider page to import credentials directly from the IDE keychain.", @@ -5983,24 +5983,24 @@ "providerDetailPathAutoDetectedAllOs": "Path is auto-detected per OS (Linux/Mac/Windows).", "providerDetailMyClaudeAccountPlaceholder": "My Claude account", "providerDetailPathAutoDetected": "Path is auto-detected per OS (Linux/Mac).", - "compatBlockedParamsPlaceholder": "thinking, … (comma-separated)", - "compatAllowedParamsPlaceholder": "reasoning, … (comma-separated)", - "compatSaving": "saving…", - "paramFiltersBlockedPlaceholder": "thinking, reasoning_budget, … (comma-separated)", - "paramFiltersAllowedPlaceholder": "reasoning, … (comma-separated)", + "compatBlockedParamsPlaceholder": "thinking, \u2026 (comma-separated)", + "compatAllowedParamsPlaceholder": "reasoning, \u2026 (comma-separated)", + "compatSaving": "saving\u2026", + "paramFiltersBlockedPlaceholder": "thinking, reasoning_budget, \u2026 (comma-separated)", + "paramFiltersAllowedPlaceholder": "reasoning, \u2026 (comma-separated)", "customGatewayNamePlaceholder": "My Gateway", "inherit": "Inherit", "claudeImportEntryName": "entry {number}", "claudeImportParseError": "parse error", "claudeImportNoValidEntries": "No valid entries to import", "webFetch": "Web Fetch", - "webFetchTooltip": "Providers that extract content from web URLs (HTML → Markdown, scrape, screenshot)", + "webFetchTooltip": "Providers that extract content from web URLs (HTML \u2192 Markdown, scrape, screenshot)", "webFetchProvidersHeading": "Web Fetch Providers", "compatibleProvidersDesc": "OpenAI-compatible and Anthropic-compatible endpoints you host or configure. Point any OpenAI SDK to your own URL and route requests here.", - "oauthProvidersDesc": "Providers authenticated via OAuth — sign in once and OmniRoute handles token rotation automatically.", + "oauthProvidersDesc": "Providers authenticated via OAuth \u2014 sign in once and OmniRoute handles token rotation automatically.", "webCookieProvidersDesc": "These providers use browser web sessions, cookies, or web tokens instead of API keys. Open a provider to add the required session credential.", "apiKeyProvidersDesc": "Standard API key providers. Add your key once and OmniRoute routes, retries, and rate-limits on your behalf.", - "noAuthProvidersDesc": "Open endpoints that require no credentials — ready to use immediately without any sign-up.", + "noAuthProvidersDesc": "Open endpoints that require no credentials \u2014 ready to use immediately without any sign-up.", "upstreamProxyProvidersDesc": "Route outbound traffic through an upstream proxy before it reaches the provider. Useful for corporate networks or traffic inspection.", "webFetchProvidersDesc": "Providers that fetch and extract content from web URLs. Use them to ground prompts with live web data.", "aggregatorsGatewaysDesc": "Multi-provider aggregators and AI gateways that expose a single unified API across dozens of underlying models.", @@ -6010,7 +6010,7 @@ "searchProvidersDesc": "Web and document search providers. Attach them to LLM calls for Retrieval-Augmented Generation.", "audioProvidersDesc": "Text-to-speech and speech-to-text providers for voice I/O and audio transcription pipelines.", "embeddingRerankProvidersDesc": "Vector embedding and reranking providers for semantic search, RAG pipelines, and similarity scoring.", - "imageProvidersDesc": "Image generation and vision providers — create images from text or analyse existing ones.", + "imageProvidersDesc": "Image generation and vision providers \u2014 create images from text or analyse existing ones.", "videoProvidersDesc": "Video generation providers. Create short video clips from text prompts or images.", "onboardingWizard": "Provider Onboarding Wizard", "onboardingWizardShort": "Onboarding Wizard", @@ -6030,7 +6030,7 @@ "onboardingChooseProviderDescription": "Select a provider, then the wizard will guide you through credentials and testing.", "onboardingChangeType": "Change type", "onboardingChangeProvider": "Change provider", - "onboardingSearchProviders": "Search providers…", + "onboardingSearchProviders": "Search providers\u2026", "onboardingApiKeyOptional": "API key optional", "onboardingProviderConnected": "Provider connected", "onboardingProviderSavedWithWarnings": "Provider saved with warnings", @@ -6041,15 +6041,15 @@ "onboardingOpenProviderDetails": "Open provider details", "onboardingTryInPlayground": "Try in playground", "onboardingDefaultConnectionName": "{provider} Primary", - "onboardingTestingConnection": "Testing provider connection…", - "onboardingValidatingCredentials": "Validating credentials…", - "onboardingSavingConnection": "Saving provider connection…", + "onboardingTestingConnection": "Testing provider connection\u2026", + "onboardingValidatingCredentials": "Validating credentials\u2026", + "onboardingSavingConnection": "Saving provider connection\u2026", "onboardingProviderFailed": "Provider onboarding failed", - "onboardingCreatingCompatibleProvider": "Creating compatible provider…", - "onboardingSavingCompatibleConnection": "Saving compatible provider connection…", + "onboardingCreatingCompatibleProvider": "Creating compatible provider\u2026", + "onboardingSavingCompatibleConnection": "Saving compatible provider connection\u2026", "onboardingCustomProviderFallbackName": "Custom provider", "onboardingCustomProviderFailed": "Custom provider onboarding failed", - "onboardingLoadingOAuthConnection": "Loading OAuth connection…", + "onboardingLoadingOAuthConnection": "Loading OAuth connection\u2026", "onboardingOAuthNoConnectionFound": "OAuth finished, but no provider connection was found.", "onboardingOAuthFailed": "OAuth onboarding failed", "onboardingAddProvider": "Add {provider}", @@ -6061,7 +6061,7 @@ "onboardingSearchCx": "Search CX / Engine id", "onboardingProviderSpecificIdPlaceholder": "Optional provider-specific id", "onboardingCustomUserAgent": "Custom User-Agent", - "onboardingWorking": "Working…", + "onboardingWorking": "Working\u2026", "onboardingValidateSaveTest": "Validate, save and test", "onboardingBack": "Back", "onboardingCreateCustomCompatibleProvider": "Create custom compatible provider", @@ -6080,10 +6080,10 @@ "onboardingStartOAuthFlow": "Start OAuth flow", "onboardingProviderDescriptions": { "360ai": "Get API key at ai.360.cn", - "agentrouter": "Get $200 free credits at https://agentrouter.org/register — no credit card required.", + "agentrouter": "Get $200 free credits at https://agentrouter.org/register \u2014 no credit card required.", "unorouter": "Create an API key at https://unorouter.ai, then paste it here as a Bearer token.", "agnes": "Get API key at agnes-ai.com", - "aimlapi": "Free tier paused (2026) — AI/ML API is now pay-as-you-go only (min $20 top-up); no recurring free credits.", + "aimlapi": "Free tier paused (2026) \u2014 AI/ML API is now pay-as-you-go only (min $20 top-up); no recurring free credits.", "ai21": "$10 trial credits on signup (valid 3 months), no credit card required", "alibaba": "Connect Alibaba with an API key.", "alibaba-cn": "Connect Alibaba (China) with an API key.", @@ -6091,7 +6091,7 @@ "bedrock": "Native Bedrock integration: model discovery uses Bedrock foundation models and inference profiles, while chat uses the regional Bedrock Runtime Converse/ConverseStream APIs.", "anthropic": "Connect Anthropic with an API key.", "ant-ling": "Register and create an API key at the Ant Ling API console (https://chat.ant-ling.com/open), then paste it here. OmniRoute routes chat traffic to https://api.ant-ling.com/v1/chat/completions; the provider is OpenAI-compatible and also exposes an Anthropic-compatible surface.", - "api-airforce": "Get your API key from https://panel.api.airforce — OpenAI-compatible endpoint at https://api.airforce/v1", + "api-airforce": "Get your API key from https://panel.api.airforce \u2014 OpenAI-compatible endpoint at https://api.airforce/v1", "arcee-ai": "Get API key at arcee.ai", "azure-ai": "Foundry uses the OpenAI v1 surface with deployment names as models. OmniRoute normalizes root resource URLs to the v1 chat and /models endpoints.", "azure-openai": "Use your Azure OpenAI API key. Base URL should be your resource endpoint, for example https://my-resource.openai.azure.com.", @@ -6100,18 +6100,18 @@ "baidu": "Get API key at console.bce.baidu.com", "qianfan": "Use a Qianfan API key from Baidu AI Cloud. The default endpoint is OpenAI-compatible v2.", "baseten": "$30 free trial credits for GPU inference", - "bazaarlink": "Create a free API key at https://bazaarlink.ai — model 'auto:free' routes to zero-cost inference. All models use the provider/model-name format, e.g. xiaomi/mimo-v2.5-pro.", + "bazaarlink": "Create a free API key at https://bazaarlink.ai \u2014 model 'auto:free' routes to zero-cost inference. All models use the provider/model-name format, e.g. xiaomi/mimo-v2.5-pro.", "black-forest-labs": "Connect Black Forest Labs with an API key.", "blackbox": "Free tier: unlimited basic chat plus Minimax-M2.5, no credit card required", - "bluesminds": "Get your API key at https://www.bluesminds.com — OpenAI-compatible endpoint at https://api.bluesminds.com/v1 with free daily credits. VIP models (Claude Opus 4.5, Gemini 2.5 Pro) consume pi credits.", + "bluesminds": "Get your API key at https://www.bluesminds.com \u2014 OpenAI-compatible endpoint at https://api.bluesminds.com/v1 with free daily credits. VIP models (Claude Opus 4.5, Gemini 2.5 Pro) consume pi credits.", "byteplus": "Connect BytePlus ModelArk with an API key.", "bytez": "$1 free credits, refreshes every 4 weeks", - "cerebras": "Free Trial: 1M tokens/day, 30K TPM, 5 RPM — no credit card.", + "cerebras": "Free Trial: 1M tokens/day, 30K TPM, 5 RPM \u2014 no credit card.", "charm-hyper": "Create an API key at https://hyper.charm.land, then paste it here as a Bearer token.", "chutes": "Bearer API key for the Chutes OpenAI-compatible gateway.", "clarifai": "Clarifai exposes OpenAI-compatible chat, responses and /models on /v2/ext/openai/v1. Public/community models typically require a PAT; app-scoped keys only work for resources inside that app.", "cloudflare-ai": "Requires API Token AND Account ID (found at dash.cloudflare.com)", - "clova-studio": "CLOVA Studio (HyperCLOVA X) is OpenAI-compatible on /v1/openai. OmniRoute probes /v1/openai/models and routes chat traffic to /v1/openai/chat/completions. Uses the current clovastudio.stream.ntruss.com host — the legacy clovastudio.apigw.ntruss.com endpoint is being deprecated.", + "clova-studio": "CLOVA Studio (HyperCLOVA X) is OpenAI-compatible on /v1/openai. OmniRoute probes /v1/openai/models and routes chat traffic to /v1/openai/chat/completions. Uses the current clovastudio.stream.ntruss.com host \u2014 the legacy clovastudio.apigw.ntruss.com endpoint is being deprecated.", "codestral": "Connect Codestral with an API key.", "cohere": "Free Trial: 1,000 API calls/month for testing, no credit card required", "command-code": "Create or copy an API key from Command Code, then paste it here as a Bearer token.", @@ -6129,14 +6129,14 @@ "empower": "Empower exposes OpenAI-compatible chat on https://app.empower.dev/api/v1 with tool-calling support on empower-functions.", "factory": "Get your Factory API key at https://app.factory.ai/settings/api-keys, then paste it as a Bearer token. OpenAI-compatible endpoint at https://api.factory.ai/v1.", "fal-ai": "Connect Fal.ai with an API key.", - "featherless-ai": "Free tier available — no credit card required", + "featherless-ai": "Free tier available \u2014 no credit card required", "fenayai": "Bearer API key for the FenayAI OpenAI-compatible gateway.", "firecrawl": "Connect Firecrawl with an API key.", "fireworks": "$1 free starter credits on signup for API testing", "freeaiapikey": "Discounted API proxy for 40+ models including GPT-5, Claude Opus 4.6, Claude Sonnet 4.6, Qwen 3.5. Get your API key at https://freeaiapikey.com/dashboard. Base URL: https://freeaiapikey.com/v1.", - "freemodel-dev": "Get $300 free API credits at https://freemodel.dev — no payment info required. OpenAI-compatible endpoint. GPT-5.4 and GPT-5.5 models available.", - "friendliai": "Free tier for serverless inference — no credit card required", - "gemini": "Free forever: 1,500 req/day for Gemini 2.5 Flash — no credit card, get key at aistudio.google.com", + "freemodel-dev": "Get $300 free API credits at https://freemodel.dev \u2014 no payment info required. OpenAI-compatible endpoint. GPT-5.4 and GPT-5.5 models available.", + "friendliai": "Free tier for serverless inference \u2014 no credit card required", + "gemini": "Free forever: 1,500 req/day for Gemini 2.5 Flash \u2014 no credit card, get key at aistudio.google.com", "gigachat": "Connect GigaChat (Sber) with an API key.", "gitlab": "GitLab personal access token for the public Code Suggestions API. Configure a self-hosted base URL when not using gitlab.com.", "gitlawb-gmi": "Get your API key from Gitlawb Opengateway dashboard.", @@ -6145,16 +6145,16 @@ "glm-cn": "Connect GLM Coding (China) with an API key.", "glmt": "Preset GLM profile with higher token budget, thinking enabled, and longer timeout.", "getgoapi": "Connect GoAPI with an API key.", - "groq": "Free tier: 30 RPM / 14.4K RPD — no credit card", + "groq": "Free tier: 30 RPM / 14.4K RPD \u2014 no credit card", "haiper": "Get API key at haiper.ai/haiper-api", "heroku": "Connect Heroku AI with an API key.", "hcnsec": "Get API key at api.hcnsec.cn", - "huggingface": "Free Inference API for thousands of models (Whisper, VITS, SDXL…)", + "huggingface": "Free Inference API for thousands of models (Whisper, VITS, SDXL\u2026)", "hyperbolic": "$1-5 trial credits on signup for serverless inference", "watsonx": "The watsonx model gateway exposes OpenAI-compatible /chat/completions and /models under /ml/gateway/v1.", "ideogram": "Get API key at ideogram.ai/docs/api", "iflytek": "Get API key at console.xfyun.cn", - "inception": "Inception Labs is OpenAI-compatible at https://api.inceptionlabs.ai/v1. mercury-2 is the first diffusion LLM (dLLM) in the catalog — 5-10x faster generation than comparable autoregressive models, with tool calling, json_mode, and structured outputs.", + "inception": "Inception Labs is OpenAI-compatible at https://api.inceptionlabs.ai/v1. mercury-2 is the first diffusion LLM (dLLM) in the catalog \u2014 5-10x faster generation than comparable autoregressive models, with tool calling, json_mode, and structured outputs.", "inference-net": "$25 free credits on signup plus research grants available", "internlm": "Free monthly quota ~1M input / 3M output tokens (~10 RPM)", "jina-ai": "Bearer API key for api.jina.ai (embeddings, rerank, classify, segment, search). Not the Reader / r.jina.ai card. Dashboard keys take precedence over JINA_AI_API_KEY.", @@ -6170,14 +6170,14 @@ "liquid": "Get API key at liquid.ai", "llamagate": "Connect LlamaGate with an API key.", "llm7": "Works without API key (use 'unused' as key). Get free token at token.llm7.io for higher limits.", - "longcat": "Free: one-time 10M-token grant after account signup + KYC verification (LongCat-2.0). One-time only — not a recurring daily/monthly allowance.", + "longcat": "Free: one-time 10M-token grant after account signup + KYC verification (LongCat-2.0). One-time only \u2014 not a recurring daily/monthly allowance.", "maritalk": "Connect Maritalk with an API key.", "meta-llama": "Connect Meta Llama API with an API key.", "minimax-cn": "Connect Minimax (China) with an API key.", "minimax": "Connect Minimax Coding with an API key.", "mistral": "Free Experiment tier: rate-limited access to all models, no credit card required", "modal": "Modal commonly serves user-hosted OpenAI-compatible apps on /v1. OmniRoute will probe /v1/models and route chat traffic to /v1/chat/completions.", - "modelscope": "Free tier via ModelScope API-Inference — Alibaba account required.", + "modelscope": "Free tier via ModelScope API-Inference \u2014 Alibaba account required.", "monsterapi": "Get API key at monsterapi.ai", "moonshot": "Connect Kimi with an API key.", "morph": "Free tier: 250K credits/month, $0", @@ -6192,7 +6192,7 @@ "nvidia": "Free dev access: ~40 RPM, 70+ models (Kimi K2.5, GLM 4.7, DeepSeek V3.2...)", "oci": "OCI exposes OpenAI-compatible chat and responses endpoints. Project ID is optional in OmniRoute but may be required for Responses and agentic workflows.", "ollama-cloud": "Connect Ollama Cloud with an API key.", - "openadapter": "OpenAdapter exposes an OpenAI-compatible chat completions endpoint at https://api.openadapter.in/v1/chat/completions, aggregating 70+ open-source models (DeepSeek, Qwen, Kimi, MiniMax, GLM, Llama, Mistral, …). OmniRoute uses the OpenAI protocol.", + "openadapter": "OpenAdapter exposes an OpenAI-compatible chat completions endpoint at https://api.openadapter.in/v1/chat/completions, aggregating 70+ open-source models (DeepSeek, Qwen, Kimi, MiniMax, GLM, Llama, Mistral, \u2026). OmniRoute uses the OpenAI protocol.", "openai": "Connect OpenAI with an API key.", "opencode-go": "Connect OpenCode Go with an API key.", "opencode-zen": "Connect OpenCode Zen with an API key.", @@ -6202,11 +6202,11 @@ "ovhcloud": "Connect OVHcloud AI with an API key.", "perplexity": "Connect Perplexity with an API key.", "piapi": "Connect PiAPI with an API key.", - "pioneer": "$75 free usage credits — no credit card required", + "pioneer": "$75 free usage credits \u2014 no credit card required", "plamo": "PLaMo is OpenAI-compatible at https://api.platform.preferredai.jp/v1. Built by Preferred Networks and optimized for Japanese. Docs are primarily in Japanese.", "poe": "Poe exposes OpenAI-compatible chat and responses on https://api.poe.com/v1, with authenticated balance checks on /usage/current_balance.", "pollinations": "Free keyless tier: openai, openai-fast, openai-large, qwen-coder, mistral, deepseek, grok, gemini-flash-lite-3.1, perplexity-fast, perplexity-reasoning. Premium models (claude, gemini, midijourney) require a Pollinations API key from enter.pollinations.ai.", - "publicai": "Requires an API key — one-time signup credit, then paid", + "publicai": "Requires an API key \u2014 one-time signup credit, then paid", "qiniu": "Create a Qiniu AI inference API key at https://portal.qiniu.com/ai-inference/api-key, then paste it here as a Bearer token. OpenAI-compatible endpoint at https://api.qnaigc.com/v1, proxying DeepSeek, Claude, Kimi and more behind one key.", "recraft": "Connect Recraft with an API key.", "reka": "Reka Chat is OpenAI-compatible on /v1. OmniRoute probes /v1/models and routes chat traffic to /v1/chat/completions.", @@ -6215,7 +6215,7 @@ "sambanova": "$5 free credits on signup (30-day validity), no credit card required", "sap": "Model discovery uses /v2/lm/scenarios/foundation-models/models on AI_API_URL. Chat requests use deploymentUrl/chat/completions and require AI-Resource-Group.", "sarvam": "Sarvam AI is OpenAI-compatible on /v1. OmniRoute probes /v1/models and routes chat traffic to /v1/chat/completions. Models are tuned for Indic languages.", - "scaleway": "1M free tokens for new accounts — EU/GDPR compliant (Paris), Qwen3 235B & Llama 70B", + "scaleway": "1M free tokens for new accounts \u2014 EU/GDPR compliant (Paris), Qwen3 235B & Llama 70B", "sensenova": "Get API key at platform.sensenova.cn", "siliconflow": "$1 free credits plus permanently free models after identity verification", "snowflake": "Connect Snowflake Cortex with an API key.", @@ -6274,7 +6274,7 @@ "gitlab-duo": "OAuth application with ai_features + read_user scopes. Configure GITLAB_DUO_OAUTH_CLIENT_ID and optionally GITLAB_DUO_OAUTH_CLIENT_SECRET on this OmniRoute instance.", "kilocode": "Connect Kilo Code with the existing OAuth flow.", "kimi-coding": "Connect Kimi Coding with the existing OAuth flow.", - "kiro": "Free tier: 50 credits/month (~25K–100K tokens). ⚠️ Kiro ToS prohibits third-party proxy/harness use.", + "kiro": "Free tier: 50 credits/month (~25K\u2013100K tokens). \u26a0\ufe0f Kiro ToS prohibits third-party proxy/harness use.", "codex": "Connect OpenAI Codex with the existing OAuth flow.", "qwen": "Connect Qwen Code with the existing OAuth flow.", "github-models": "Create a GitHub PAT with 'models: read' scope at github.com/settings/tokens" @@ -6298,18 +6298,18 @@ "webTokenRequiredCredential": "Required token: {credential}", "webSessionGuideStep1": "Sign in to {provider} in your browser.", "webSessionGuideStep2": "Open the browser developer tools and inspect a request made by the web app.", - "webSessionGuideStep2Fast": "Fast path: install the Cookie Editor extension (chromewebstore.google.com → Cookie Editor), open it on the {provider} tab, find {credential} (select all numbered chunks if split), and click Export → Copy with the export format set to “Cookie header”.", + "webSessionGuideStep2Fast": "Fast path: install the Cookie Editor extension (chromewebstore.google.com \u2192 Cookie Editor), open it on the {provider} tab, find {credential} (select all numbered chunks if split), and click Export \u2192 Copy with the export format set to \u201cCookie header\u201d.", "webSessionGuideStep3": "Copy the required credential from the provider's own domain. For cookies, copy only the Cookie header value and omit Cookie:.", - "webSessionGuideStep3Manual": "Manual path: open the browser developer tools (F12 → Network), refresh the page, open an authenticated request, and copy the Cookie header value from Request Headers — omit the Cookie: prefix.", + "webSessionGuideStep3Manual": "Manual path: open the browser developer tools (F12 \u2192 Network), refresh the page, open an authenticated request, and copy the Cookie header value from Request Headers \u2014 omit the Cookie: prefix.", "webSessionGuideStep4": "Paste it here and check the connection. If it stops working, sign in again and replace it with a fresh value.", "harImportButtonLabel": "Import .har file", - "harImportButtonBusy": "Importing…", + "harImportButtonBusy": "Importing\u2026", "harImportButtonHint": "Export from DevTools Network tab after sending at least one chat message.", - "harImportStatusValid": "Imported — valid for ~{minutes}m.", - "harImportStatusExpiringSoon": "Imported — valid for only ~{minutes}m more.", - "harImportStatusExpired": "Imported, but this token already expired ({minutes}m ago) — export a fresh HAR.", + "harImportStatusValid": "Imported \u2014 valid for ~{minutes}m.", + "harImportStatusExpiringSoon": "Imported \u2014 valid for only ~{minutes}m more.", + "harImportStatusExpired": "Imported, but this token already expired ({minutes}m ago) \u2014 export a fresh HAR.", "harImportStatusUnknownExpiry": "Imported. Couldn't read its expiry.", - "harImportErrorNotJson": "That file isn't valid JSON — is it really a .har export?", + "harImportErrorNotJson": "That file isn't valid JSON \u2014 is it really a .har export?", "harImportErrorNoEntries": "This HAR has no network entries recorded.", "harImportErrorNoChathubUrl": "No Copilot chat connection found in this HAR. Send at least one chat message in m365.cloud.microsoft before exporting.", "harImportErrorUnparsableUrl": "Found the chat connection, but couldn't read its URL.", @@ -6345,7 +6345,7 @@ "kimiOfficialSupporterTooltip": "Kimi (Moonshot AI) is an official OmniRoute launch partner", "cheaperInferenceSupporterBadge": "Open Source Friend", "cheaperInferenceSupporterTooltip": "Cheaper Inference backs OmniRoute as an Open Source Friend", - "kimiPartnerLinkNote": "Partner link — supports OmniRoute at no extra cost to you", + "kimiPartnerLinkNote": "Partner link \u2014 supports OmniRoute at no extra cost to you", "codexQuotaPools": "Codex quota pools", "codexPoolAvailable": "Available", "codexPoolPartiallyLimited": "Partially limited", @@ -6356,9 +6356,9 @@ "codexPoolUsed": "used", "codexPoolUntil": "Until {value}", "anonymousFallbackTitle": "Anonymous fallback", - "anonymousFallbackDesc": "When all configured connections are exhausted (quota, credits, or expiry), temporarily use this provider's keyless tier. Turn off to skip this provider instead of sending anonymous requests — recommended when the keyless tier rejects them (401).", + "anonymousFallbackDesc": "When all configured connections are exhausted (quota, credits, or expiry), temporarily use this provider's keyless tier. Turn off to skip this provider instead of sending anonymous requests \u2014 recommended when the keyless tier rejects them (401).", "anonymousFallbackEnabled": "Anonymous fallback enabled for {provider}", - "anonymousFallbackDisabled": "Anonymous fallback disabled for {provider} — exhausted connections will skip this provider", + "anonymousFallbackDisabled": "Anonymous fallback disabled for {provider} \u2014 exhausted connections will skip this provider", "anonymousFallbackUpdateFailed": "Failed to update anonymous fallback setting", "batchDeleteFailed": "Batch delete failed", "batchDeleteNetworkError": "Network error during batch delete", @@ -6383,19 +6383,19 @@ "codexTierFastLabel": "Fast", "codexTierFlexLabel": "Flex", "commandCodeApplyFailed": "Failed to apply Command Code auth", - "commandCodeApplyingApproval": "Browser approved, applying…", - "commandCodeApplyingKey": "Applying browser-approved key…", - "commandCodeApprovalInstructions": "Open the auth URL, approve access, then paste the returned key/JSON/URL below…", + "commandCodeApplyingApproval": "Browser approved, applying\u2026", + "commandCodeApplyingKey": "Applying browser-approved key\u2026", + "commandCodeApprovalInstructions": "Open the auth URL, approve access, then paste the returned key/JSON/URL below\u2026", "commandCodeAuthExpired": "Command Code auth expired", "commandCodeConnected": "Command Code connected", "commandCodeConnectionAdded": "Command Code connection added", "commandCodeLinkExpired": "Command Code link expired", - "commandCodeOpeningStudio": "Opening Command Code Studio…", + "commandCodeOpeningStudio": "Opening Command Code Studio\u2026", "commandCodePopupBlocked": "Popup blocked. Please allow popups and try Command Code Connect again.", "commandCodeStartFailed": "Failed to start Command Code auth", "connectionDeleted": "Connection deleted", "connectionFallback": "connection", - "coolingConnectionsDescription": "These connections returned a 429 (rate-limit) on their last request. OmniRoute will skip them until the timer expires — no manual disable required.", + "coolingConnectionsDescription": "These connections returned a 429 (rate-limit) on their last request. OmniRoute will skip them until the timer expires \u2014 no manual disable required.", "coolingConnectionsTitle": "Currently cooling ({count})", "failedDeleteAlias": "Failed to delete alias", "failedDeleteConnection": "Failed to delete connection", @@ -6415,7 +6415,7 @@ "networkErrorDeletingAlias": "Network error deleting alias", "networkErrorSettingAlias": "Network error setting alias", "noProvidersMatch": "No providers match your search.", - "noSavedProxies": "No saved proxies found. Add proxies in Settings → Proxy first.", + "noSavedProxies": "No saved proxies found. Add proxies in Settings \u2192 Proxy first.", "pageLoadErrorDescription": "We could not load provider data right now. Check your connection and try again.", "pageLoadErrorId": "Error ID: {id}", "pageLoadErrorRetry": "Try Again", @@ -6456,20 +6456,20 @@ "routingOpObfuscateWordsLabel": "Obfuscate words (ZWJ)", "routingOpDropParagraphContainsDesc": "Removes any paragraph (text block split on blank lines) inside the system prompt whose text contains ANY of the listed substrings. Use to strip third-party client fingerprints like 'github.com/anomalyco/opencode' or 'docs.openwebui.com' that Anthropic's classifier flags.", "routingOpDropParagraphStartsWithDesc": "Removes any paragraph that STARTS WITH one of the listed prefixes. Use for identity lines like 'You are OpenCode' or 'You are Open WebUI' that announce the calling client.", - "routingOpReplaceTextDesc": "Replaces a literal substring with another literal substring. Use for known trigger phrases — e.g. rewrite 'Here is some useful information about the environment you are running in:' to 'Environment context you are running in:' (an empirically-validated trigger phrase).", + "routingOpReplaceTextDesc": "Replaces a literal substring with another literal substring. Use for known trigger phrases \u2014 e.g. rewrite 'Here is some useful information about the environment you are running in:' to 'Environment context you are running in:' (an empirically-validated trigger phrase).", "routingOpReplaceRegexDesc": "Replaces text matching a regular expression. Use when you need patterns (character classes, optional whitespace, anchors) instead of literal substrings. A malformed pattern is caught at runtime when the op runs.", - "routingOpDropBlockContainsDesc": "Removes ENTIRE system blocks (not just paragraphs) whose text contains any of the listed substrings. Use when a whole block is fingerprint-bearing and you want it gone — e.g. an injected MCP-server description.", + "routingOpDropBlockContainsDesc": "Removes ENTIRE system blocks (not just paragraphs) whose text contains any of the listed substrings. Use when a whole block is fingerprint-bearing and you want it gone \u2014 e.g. an injected MCP-server description.", "routingOpPrependSystemBlockDesc": "Inserts a new text block at the FRONT of the system array. Use to add the SDK identity 'You are a Claude agent, built on Anthropic's Claude Agent SDK.' that Anthropic's classifier expects.", "routingOpAppendSystemBlockDesc": "Inserts a new text block at the END of the system array. Use for cosmetic additions that don't need to be at position [0].", "routingOpInjectBillingHeaderDesc": "Prepends the special 'x-anthropic-billing-header: cc_version=...; cc_entrypoint=...; cch=...;' text block that Anthropic's classifier validates. Required for CC bridge relay endpoints; for the native claude provider OmniRoute already injects its own billing line so this op is usually redundant there.", - "routingOpObfuscateWordsDesc": "Inserts a Zero-Width-Joiner character after the first letter of each listed word, so 'opencode' becomes 'o‍pencode'. Reads identical to humans but bypasses classifier word matches. Targets system blocks, user/assistant messages, and tool descriptions.", + "routingOpObfuscateWordsDesc": "Inserts a Zero-Width-Joiner character after the first letter of each listed word, so 'opencode' becomes 'o\u200dpencode'. Reads identical to humans but bypasses classifier word matches. Targets system blocks, user/assistant messages, and tool descriptions.", "routingNeedlesHint": "List of substrings. A paragraph matches if it contains ANY one of them. Add one per line via 'Add entry'.", "routingPrefixesHint": "List of strings. A paragraph matches if it starts with any one of them (leading whitespace is trimmed before matching).", "routingCaseSensitiveHint": "When ON, 'OpenCode' and 'opencode' are different strings. When OFF (default), the comparison ignores case.", - "routingMatchLiteralHint": "Exact literal substring to find. No regex syntax — special chars like . * ? are treated as themselves.", + "routingMatchLiteralHint": "Exact literal substring to find. No regex syntax \u2014 special chars like . * ? are treated as themselves.", "routingReplacementTextHint": "Replacement string. Leave blank to delete the match. The output preserves surrounding text.", "routingAllOccurrencesHint": "When ON (default), every instance is replaced. When OFF, only the first match is replaced.", - "routingPatternHint": "JavaScript regex source. Don't wrap in slashes — just 'foo(.*)bar'. Server rejects patterns that fail to compile.", + "routingPatternHint": "JavaScript regex source. Don't wrap in slashes \u2014 just 'foo(.*)bar'. Server rejects patterns that fail to compile.", "routingRegexFlagsHint": "JavaScript regex flags (g = all matches, i = case-insensitive, s = dot matches newline, m = multiline). Default 'g'.", "routingBlockTextHint": "Full text of the new system block. Use a literal string; the system block stores text only.", "routingIdempotencyKeyHint": "Optional. If set, the op skips when a block whose text starts with this key is already present. Prevents double-prepend on retries.", @@ -6481,8 +6481,8 @@ "routingObfuscateTargetsLabel": "Targets", "routingSummarizeDropParagraphContains": "drop paragraphs containing: {items}", "routingSummarizeDropParagraphStartsWith": "drop paragraphs starting with: {items}", - "routingSummarizeReplaceText": "replace \"{match}\" → \"{replacement}\"", - "routingSummarizeReplaceRegex": "regex /{pattern}/{flags} → \"{replacement}\"", + "routingSummarizeReplaceText": "replace \"{match}\" \u2192 \"{replacement}\"", + "routingSummarizeReplaceRegex": "regex /{pattern}/{flags} \u2192 \"{replacement}\"", "routingSummarizeDropBlockContains": "drop blocks containing: {items}", "routingSummarizePrependSystemBlock": "prepend block: \"{text}\"", "routingSummarizeAppendSystemBlock": "append block: \"{text}\"", @@ -6503,7 +6503,7 @@ "routingOpSummaryCount": "{count, plural, =0 {no ops} one {# op} other {# ops}}", "routingOpEnabled": "enabled", "routingOpDisabled": "disabled", - "routingOpStatusSeparator": "·", + "routingOpStatusSeparator": "\u00b7", "resilienceSettingsIntro": "Automatic retry, cooldown, and fallback when providers fail.", "aiSettingsIntro": "AI-specific settings for thinking budget, model behavior, and compression.", "systemPrompt": "System Prompt", @@ -6552,7 +6552,7 @@ "memoryDesc": "Persistent cross-session conversational memory", "memoryEnabled": "Enable Memory", "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", - "memoryTokenCostWarning": "Heads up: with memory enabled, OmniRoute injects up to {tokens} tokens of retrieved context into every chat request — this increases token usage and cost. To skip injection for a specific request, send the \"x-omniroute-no-memory: true\" header.", + "memoryTokenCostWarning": "Heads up: with memory enabled, OmniRoute injects up to {tokens} tokens of retrieved context into every chat request \u2014 this increases token usage and cost. To skip injection for a specific request, send the \"x-omniroute-no-memory: true\" header.", "maxTokens": "Max Tokens", "retentionDays": "Retention", "recent": "Recent", @@ -6588,7 +6588,7 @@ "modelsDevInfo": "How It Works", "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", - "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "modelsDevInfoOrder": "User Override \u2192 models.dev \u2192 LiteLLM \u2192 Hardcoded Default", "systemTheme": "System Theme", "debugToggle": "Enable Debug Mode", "logToolSourcesToggle": "Log Tool Sources", @@ -6635,7 +6635,7 @@ "strategy": "Strategy", "preserveClientCache": "Preserve Client Cache", "enabled": "Enabled", - "loading": "Loading…", + "loading": "Loading\u2026", "saving": "Saving...", "save": "Save", "circuitBreaker": "Circuit Breaker", @@ -6650,7 +6650,7 @@ "autoDisableThreshold": "Ban Threshold", "autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.", "autoDisableBannedScope": "Apply auto-disable to", - "autoDisableBannedScopeDesc": "Login seats (paid subscriptions and free accounts) can be locked by the upstream if OmniRoute keeps retrying after a ban. Paid prepaid API keys do not have that risk — leave them in the pool so fill-first can use the next key.", + "autoDisableBannedScopeDesc": "Login seats (paid subscriptions and free accounts) can be locked by the upstream if OmniRoute keeps retrying after a ban. Paid prepaid API keys do not have that risk \u2014 leave them in the pool so fill-first can use the next key.", "autoDisableBannedScopeAll": "All connections", "autoDisableBannedScopeAllDesc": "Deactivate every connection that returns a permanent ban signal, including prepaid API keys.", "autoDisableBannedScopeSubscription": "Login / subscription accounts only", @@ -6692,7 +6692,7 @@ "savedSuccessfully": "Settings saved successfully", "ai": "AI", "advanced": "Advanced", - "localMode": "Local Mode — All data stored on your machine", + "localMode": "Local Mode \u2014 All data stored on your machine", "settingsSectionsAria": "Settings sections", "switchThemes": "Switch between light and dark themes", "themeSelectionAria": "Theme selection", @@ -6756,11 +6756,11 @@ "startOnLogin": "Start on Login", "startOnLoginDesc": "Automatically launch OmniRoute on system startup and run silently in the background tray.", "flushCache": "Flush Cache", - "flushing": "Flushing…", + "flushing": "Flushing\u2026", "size": "Size", "hits": "Hits", "evictions": "Evictions", - "loadingCacheStats": "Loading cache stats…", + "loadingCacheStats": "Loading cache stats\u2026", "globalProxy": "Global Proxy", "globalProxyDesc": "Configure a global outbound proxy for all API calls. Individual providers, combos, and keys can override this.", "noGlobalProxy": "No global proxy configured", @@ -6795,13 +6795,13 @@ "localCoreEndpointPlaceholder": "socks5://127.0.0.1:1080", "localCoreEndpointDesc": "Only accepts 127.0.0.1 / localhost (SS/VMess/Trojan/VLESS require a local sing-box/clash core).", "routeByProvider": "Route by Provider (multi-select)", - "loadingProviders": "Loading Provider list…", + "loadingProviders": "Loading Provider list\u2026", "autoRefreshInterval": "Auto Refresh Interval (minutes)", "enableAfterCreate": "Enable on creation (sync and take effect immediately)", - "saving": "Saving…", + "saving": "Saving\u2026", "saveChanges": "Save Changes", "createSubscription": "Create Subscription", - "loading": "Loading…", + "loading": "Loading\u2026", "noSubscriptions": "No subscriptions yet. Click \"Add Subscription\" to get started.", "statusOk": "OK", "statusError": "Error", @@ -6865,26 +6865,26 @@ "healthcheckLatency": "Latency", "proxyDocumentationScopeTitle": "Proxy scope resolution", "proxyDocumentationScopeDescBefore": "OmniRoute resolves the outbound proxy in priority order:", - "proxyDocumentationScopeOrder": "combo → account → provider → global", + "proxyDocumentationScopeOrder": "combo \u2192 account \u2192 provider \u2192 global", "proxyDocumentationScopeDescAfter": ". The most specific scope wins.", "proxyDocumentationAddTitle": "Adding a custom proxy", "proxyDocumentationAddDescBefore": "Go to the", - "proxyDocumentationAddDescMiddle": "tab → click", + "proxyDocumentationAddDescMiddle": "tab \u2192 click", "proxyDocumentationAddCta": "+ Add proxy", "proxyDocumentationAddDescAfter": ". Fill in type (http/https/socks5), host, and port. Optionally assign it to a scope.", "proxyDocumentationBulkTitle": "Bulk import format", "proxyDocumentationBulkDesc": "Pipe-delimited fields:", "proxyDocumentationSocks5DescBefore": "SOCKS5 proxies are disabled by default. Set", - "proxyDocumentationFreePoolDesc": "The Free Pool tab aggregates proxies from 1proxy, Proxifly, and IPLocate. Use ⊕ to test and promote a proxy to your registry. Only proxies that pass the connectivity test are added.", - "proxyDocumentationVercelRelayDescBefore": "The Vercel Relay is an outbound edge relay — not an inbound tunnel. Deploying one sends LLM API calls through Vercel's dynamic IPs, bypassing datacenter geo-blocks and rate limits. The relay is protected by a generated secret header", + "proxyDocumentationFreePoolDesc": "The Free Pool tab aggregates proxies from 1proxy, Proxifly, and IPLocate. Use \u2295 to test and promote a proxy to your registry. Only proxies that pass the connectivity test are added.", + "proxyDocumentationVercelRelayDescBefore": "The Vercel Relay is an outbound edge relay \u2014 not an inbound tunnel. Deploying one sends LLM API calls through Vercel's dynamic IPs, bypassing datacenter geo-blocks and rate limits. The relay is protected by a generated secret header", "proxyDocumentationVercelRelayDescAfter": "Your Vercel token is used only during deploy and is never stored.", "vercelRelayTokenRequired": "Token is required", "vercelRelayDeployFailed": "Deploy failed", - "vercelRelayTokenHint": "Token is used only during deploy — never stored.", + "vercelRelayTokenHint": "Token is used only during deploy \u2014 never stored.", "denoRelayTokenRequired": "Deno Deploy token is required", "denoRelayOrgDomainRequired": "Organization domain is required", "denoRelayDeployFailed": "Deno Deploy failed", - "denoRelayTokenHint": "Organization token (prefix ddo_) from console.deno.com → Organization → Settings → Organization Tokens. Used once for deploy and never stored.", + "denoRelayTokenHint": "Organization token (prefix ddo_) from console.deno.com \u2192 Organization \u2192 Settings \u2192 Organization Tokens. Used once for deploy and never stored.", "denoRelayOrgDomainHint": "Your Deno Deploy organization's default domain (e.g. acme.deno.net). The relay will be reachable at https://<app-name>.<org-slug>.deno.net.", "proxyFreePoolFilterProtocol": "Filter by protocol", "proxyFreePoolProtocol": "Protocol", @@ -6911,7 +6911,7 @@ "proxyFreePoolBulkResult": "{succeeded} added, {failed} failed", "proxyFreePoolPageSummary": "Page {page} of {totalPages} ({total} total proxies)", "proxyFreePoolTotalSummary": "{total} total proxies", - "proxyFreePoolSearchPlaceholder": "Search host…", + "proxyFreePoolSearchPlaceholder": "Search host\u2026", "proxyFreePoolSearchLabel": "Search proxies by host", "proxyFreePoolSortLabel": "Sort proxies", "proxyFreePoolSortQuality": "Quality", @@ -6934,9 +6934,9 @@ "chars": "{count} chars", "thinkingBudgetTitle": "Thinking Budget", "thinkingBudgetDesc": "Controls whether OmniRoute rewrites client thinking/reasoning parameters before requests go upstream. This is separate from compression, prompt cache, and API-key token limits.", - "thinkingBudgetIndependenceHint": "Compression, routing, and token-limit features keep working in every mode. Auto does not mean “auto-show thinking” — it strips client reasoning fields.", + "thinkingBudgetIndependenceHint": "Compression, routing, and token-limit features keep working in every mode. Auto does not mean \u201cauto-show thinking\u201d \u2014 it strips client reasoning fields.", "passthrough": "Passthrough", - "passthroughDesc": "No changes — client controls thinking budget", + "passthroughDesc": "No changes \u2014 client controls thinking budget", "thinkingModePassthrough": "Passthrough", "thinkingModePassthroughDesc": "Leave client reasoning unchanged (effort, summary, thinking blocks). Required for Codex/Desktop visible thinking. Default.", "thinkingModeAuto": "Auto (strip)", @@ -6997,7 +6997,7 @@ "systemTransforms": "System-block Transform Pipeline", "systemTransformsDesc": "Per-provider ordered pipeline of transforms applied to the request body before forwarding. Supports any provider ID.", "systemTransformsAddProvider": "Add provider", - "systemTransformsAddProviderPlaceholder": "Select a provider…", + "systemTransformsAddProviderPlaceholder": "Select a provider\u2026", "systemTransformsAddProviderAllConfigured": "All providers already configured", "systemTransformsRemoveProvider": "Remove provider", "systemTransformsNoProviders": "No providers configured. Add a provider to get started.", @@ -7023,7 +7023,7 @@ "resetAware": "Reset-Aware RR", "resetAwareDesc": "Prefer accounts with healthy remaining quota and nearer resets", "strictRandom": "Strict Random", - "strictRandomDesc": "Shuffle deck — uses each account once before reshuffling", + "strictRandomDesc": "Shuffle deck \u2014 uses each account once before reshuffling", "stickyLimit": "Sticky Limit", "stickyLimitDesc": "Calls per account before switching when global account fallback uses Round Robin", "routingStrategyTitle": "Routing Strategy", @@ -7193,7 +7193,7 @@ "noCircuitBreakers": "No circuit breakers active yet. They are created automatically when requests flow through the combo pipeline.", "failures": "{count} failure(s)", "policiesLocked": "Policies & Locked Identifiers", - "allOperational": "All systems operational — no lockouts or tripped breakers", + "allOperational": "All systems operational \u2014 no lockouts or tripped breakers", "loadingPolicies": "Loading policies...", "lockedIdentifiers": "Locked Identifiers", "unlockedIdentifier": "Unlocked: {identifier}", @@ -7252,7 +7252,7 @@ "errorDuringRestore": "An error occurred during restore", "errorDuringImport": "An error occurred during import", "modelPricing": "Model Pricing", - "modelPricingDesc": "Configure cost rates per model • All rates in $/1M tokens", + "modelPricingDesc": "Configure cost rates per model \u2022 All rates in $/1M tokens", "pricingCoverage": "Coverage", "pricingAuth": "Auth", "pricingSort": "Sort", @@ -7261,7 +7261,7 @@ "pricingMostModels": "Most models", "pricingHighestCoverage": "Highest coverage", "pricingLowestCoverage": "Lowest coverage", - "pricingNameAscending": "Name (A–Z)", + "pricingNameAscending": "Name (A\u2013Z)", "pricingCoverageGaps": "Coverage gaps", "pricingClearFilters": "Clear filters", "pricingShowingProviders": "Showing {visible} of {total}", @@ -7305,7 +7305,7 @@ "pricingDescCached": "Cached: reused input (~50% of input rate)", "pricingDescReasoning": "Reasoning: thinking tokens (falls back to Output)", "pricingDescCacheWrite": "Cache Write: creating cache entries (falls back to Input)", - "pricingDescFormula": "Cost = (input × input_rate) + (output × output_rate) + (cached × cached_rate) per million tokens.", + "pricingDescFormula": "Cost = (input \u00d7 input_rate) + (output \u00d7 output_rate) + (cached \u00d7 cached_rate) per million tokens.", "pricingSettingsTitle": "Pricing Settings", "totalModels": "Total Models", "active": "Active", @@ -7330,9 +7330,9 @@ "echoRequestedModelTitle": "Echo requested model name in responses", "echoRequestedModelDesc": "When enabled, the response `model` field echoes the alias or combo name the client requested instead of the upstream model name. Fixes strict clients (e.g. Claude Desktop) that reject a response whose model does not match the request.", "webSearchRouteTitle": "Web search routing", - "webSearchRouteDesc": "When a request includes a native web_search tool, route the whole request to this model instead of the default — useful for providers that don't implement Anthropic's web_search server tool. Leave blank to disable.", - "webSearchRoutePlaceholder": "Search or select a model…", - "paidModelPatternWarning": "This pattern only matches paid models — enable paid models or adjust the pattern.", + "webSearchRouteDesc": "When a request includes a native web_search tool, route the whole request to this model instead of the default \u2014 useful for providers that don't implement Anthropic's web_search server tool. Leave blank to disable.", + "webSearchRoutePlaceholder": "Search or select a model\u2026", + "paidModelPatternWarning": "This pattern only matches paid models \u2014 enable paid models or adjust the pattern.", "clearLkgpCache": "Clear LKGP Cache", "lkgpCacheCleared": "LKGP cache cleared successfully", "lkgpCacheClearFailed": "Failed to clear LKGP cache", @@ -7471,12 +7471,12 @@ "compressionLiveZoneTitle": "Cache-aligned Live Zone", "compressionLiveZoneDesc": "Keep the compressed conversation prefix stable and process only newly appended items.", "compressionExclusionsTitle": "Compression Exclusions", - "compressionExclusionsDesc": "Model ids or provider/model patterns that must never be compressed. `*` is the only wildcard (e.g. `openai/*`, `*embedding*`). A matching request passes through byte-identical — no compression engine runs.", + "compressionExclusionsDesc": "Model ids or provider/model patterns that must never be compressed. `*` is the only wildcard (e.g. `openai/*`, `*embedding*`). A matching request passes through byte-identical \u2014 no compression engine runs.", "compressionExclusionsPlaceholder": "One pattern per line, e.g.\nopenai/text-embedding-3-large\nanthropic/*", "compressionExclusionsSave": "Save", "compressionExclusionsSaved": "Saved", "compressionExclusionsCount": "{count, plural, one {# exclusion} other {# exclusions}} configured", - "compressionExclusionsEmpty": "No exclusions configured — every model/endpoint is eligible for compression (default behavior).", + "compressionExclusionsEmpty": "No exclusions configured \u2014 every model/endpoint is eligible for compression (default behavior).", "compressionCavemanConfig": "Caveman Engine Configuration", "compressionCavemanConfigDesc": "Fine-tune the rule-based compression engine", "compressionCavemanPanelHint": "Its on/off and level are set in the panel:", @@ -7617,7 +7617,7 @@ "routingAntigravitySignatureBypassStrictLabel": "Bypass Strict", "routingAntigravitySignatureBypassStrictDesc": "Require full protobuf validation before accepting a client-provided signature.", "routingHeaderFingerprintTitle": "Header fingerprint (per provider)", - "routingServerRejectedSave": "⚠ Server rejected save:", + "routingServerRejectedSave": "\u26a0 Server rejected save:", "routingAddTransformOp": "Add a transform op", "routingClientCacheControlTitle": "Client Cache Control", "routingClientCacheControlDesc": "Configure whether OmniRoute preserves client-provided cache_control markers", @@ -7739,8 +7739,8 @@ "storageLastVacuum": "Last Vacuum", "storageLastOptimization": "Last Optimization", "storageIntegrityCheck": "Integrity Check", - "storageIntegrityOk": "✓ OK", - "storageIntegrityError": "✗ Error", + "storageIntegrityOk": "\u2713 OK", + "storageIntegrityError": "\u2717 Error", "storageUsageTokenBuffer": "Usage Token Buffer", "storageUsageTokenBufferDesc": "Extra tokens added to reported usage to account for system prompt overhead.", "storageUsageTokenBufferHint": "Set to 0 to report raw provider token counts. Default: 2000.", @@ -7770,10 +7770,10 @@ "compressionDerivedRuns": "runs: {pipeline}", "compressionDerivedMode": "mode: {mode}", "compressionAdaptiveOff": "Adaptive context budget: off (legacy auto-trigger)", - "compressionAdaptiveTarget": "Adaptive ({mode}, policy: {policy}) — target ≈ {target, number} tokens (for a {contextLimit, number}-token window)", + "compressionAdaptiveTarget": "Adaptive ({mode}, policy: {policy}) \u2014 target \u2248 {target, number} tokens (for a {contextLimit, number}-token window)", "compressionOutputStylesDescription": "Inject response-shaping instructions without rewriting provider output. Combine freely.", "mcpAccessibilityDescription": "Scopes MCP tool outputs (separate store).", - "compressionStylesTileSummary": "{tokens, number} tokens saved · {runs, plural, one {# run styled} other {# runs styled}}", + "compressionStylesTileSummary": "{tokens, number} tokens saved \u00b7 {runs, plural, one {# run styled} other {# runs styled}}", "compressionStylesTileEmpty": "No styled runs yet.", "compressionLevel": { "minimal": "Minimal", @@ -7851,7 +7851,7 @@ "description": "Action-first output: next action leads, steps numbered, one concrete next step, no preamble." }, "terse-cjk": { - "label": "Terse CJK (文言)", + "label": "Terse CJK (\u6587\u8a00)", "description": "Classical-Chinese ultra-terse style (available only for Chinese)." } }, @@ -7910,7 +7910,7 @@ "modelCooldownsAllReactivated": "All models in cooldown have been reactivated.", "modelCooldownsClearFailed": "Failed to clear cooldowns", "modelCooldownsReactivateAll": "Reactivate all", - "modelCooldownsReasonRemaining": "reason: {reason} • remaining: {remaining}", + "modelCooldownsReasonRemaining": "reason: {reason} \u2022 remaining: {remaining}", "modelCooldownsReactivate": "Reactivate", "responsesStateTitle": "Responses State", "responsesStateDesc": "Control how OmniRoute forwards previous_response_id.", @@ -7926,7 +7926,7 @@ "codexFastTierSaveError": "Failed to update Codex Fast Tier setting", "codexAutoPingTitle": "Codex Quota Auto-Ping", "codexAutoPingDesc": "Opt-in per connection: sends a tiny request right after a Codex session window resets so it isn't cold when you need it.", - "codexAutoPingWarning": "Consumes a small amount of real Codex quota on every ping. Off by default — enable only for connections you actively rely on.", + "codexAutoPingWarning": "Consumes a small amount of real Codex quota on every ping. Off by default \u2014 enable only for connections you actively rely on.", "codexAutoPingSaveError": "Failed to update Codex Auto-Ping setting", "codexAutoPingToggleAria": "Toggle Codex quota auto-ping for {connection}", "codexFastTierTierLabel": "Service tier", @@ -7938,7 +7938,7 @@ "codexFastTierModelCheckbox": "Enable Fast Tier for {model}", "claudeFastModeTitle": "Claude Fast Mode", "claudeFastModeDesc": "Opt selected Claude requests into Anthropic Fast Mode (speed:\"fast\").", - "claudeFastModeHint": "Anthropic does not officially support Fast Mode for SDK-style clients. When enabled, OmniRoute forwards an X-CPA-Force-Fast-Mode header so a paired CLIProxyAPI build can opt-in spoof the entrypoint. Only the listed Opus models are gated by Anthropic's client-side check. Subscription tier, Max plan, and Fast Mode credit balance are still enforced server-side — Anthropic may return out_of_credits even when the toggle is on.", + "claudeFastModeHint": "Anthropic does not officially support Fast Mode for SDK-style clients. When enabled, OmniRoute forwards an X-CPA-Force-Fast-Mode header so a paired CLIProxyAPI build can opt-in spoof the entrypoint. Only the listed Opus models are gated by Anthropic's client-side check. Subscription tier, Max plan, and Fast Mode credit balance are still enforced server-side \u2014 Anthropic may return out_of_credits even when the toggle is on.", "claudeFastModeModelsLabel": "Applied to models ({count})", "claudeFastModeModelCheckbox": "Enable Fast Mode for {model}", "claudeFastModeSaveError": "Failed to update Claude Fast Mode setting", @@ -7946,12 +7946,12 @@ "cors": { "wildcard": { "title": "CORS is open to every origin (CORS_ALLOW_ALL=true)", - "desc": "Any website can call this server's API from a visitor's browser. Use only on trusted networks — set explicit origins in ALLOWED_ORIGINS and disable CORS_ALLOW_ALL in production." + "desc": "Any website can call this server's API from a visitor's browser. Use only on trusted networks \u2014 set explicit origins in ALLOWED_ORIGINS and disable CORS_ALLOW_ALL in production." } }, "title": "Authorization Inventory", "description": "5-tier route classification with live bypass policy. Read shows the full taxonomy; mutations re-prompt for the management password.", - "loading": "Loading inventory…", + "loading": "Loading inventory\u2026", "loadError": "Failed to load authz inventory", "tier": { "LOCAL_ONLY": "Local only", @@ -8055,7 +8055,7 @@ "resilienceProviderCooldownEnabledDesc": "When enabled, failed providers are tracked globally and skipped for a cooldown period.", "resilienceProviderCooldownMin": "Minimum cooldown", "resilienceProviderCooldownMax": "Maximum cooldown", - "forcedFingerprintTitle": "Always enabled for {provider} — required for OAuth account safety; cannot be turned off.", + "forcedFingerprintTitle": "Always enabled for {provider} \u2014 required for OAuth account safety; cannot be turned off.", "forcedFingerprintBadge": "Required", "sessionAffinityTitle": "Session affinity", "sessionAffinityDesc": "Keeps one conversation on the same account for this many seconds, for any provider. 0 disables it.", @@ -8089,7 +8089,7 @@ "cloudflareRelaySuccess": "Cloudflare Relay deployed successfully", "cloudflareRelayButton": "Deploy Cloudflare Relay", "cloudflareRelayModalTitle": "Deploy Cloudflare Worker Relay", - "cloudflareRelayWarning": "Deploys a Cloudflare Worker that proxies LLM requests through Cloudflare's edge network — masking the host IP behind dynamic Cloudflare addresses. The Worker enforces a one-time auth secret so the public workers.dev URL cannot be reused as an open relay.", + "cloudflareRelayWarning": "Deploys a Cloudflare Worker that proxies LLM requests through Cloudflare's edge network \u2014 masking the host IP behind dynamic Cloudflare addresses. The Worker enforces a one-time auth secret so the public workers.dev URL cannot be reused as an open relay.", "cloudflareRelayTokenHowto": "Create the API token under My Profile -> API Tokens -> Create Token -> Custom Token -> Account / Workers Scripts / Edit.", "cloudflareRelayAccountIdLabel": "Cloudflare Account ID", "cloudflareRelayAccountIdHint": "Found on the right side of the Cloudflare dashboard overview page.", @@ -8113,7 +8113,7 @@ "removeErrorCode": "Remove {code}", "addErrorCode": "Add error code...", "suggestions": "Suggestions:", - "modelLockoutMaxCooldownHint": "≥ Base Cooldown — 3,600,000ms", + "modelLockoutMaxCooldownHint": "\u2265 Base Cooldown \u2014 3,600,000ms", "modelLockoutEnabled": "Enable Model Lockout", "modelLockoutEnabledDescription": "When enabled, models that fail with configured error codes are temporarily locked to prevent retry loops.", "modelLockoutErrorCodes": "Error Codes", @@ -8163,7 +8163,7 @@ "modalityBridgeTimeoutMs": "Timeout (ms)", "modalityBridgeMaxImages": "Max images per request", "visionMaxCharsLabel": "Max description characters", - "visionMaxCharsHint": "Cap the vision model's description length. 0 = unlimited — raise for detail-heavy OCR tasks.", + "visionMaxCharsHint": "Cap the vision model's description length. 0 = unlimited \u2014 raise for detail-heavy OCR tasks.", "modalityBridgeCacheEnabled": "Cache descriptions", "modalityBridgeCacheEnabledDesc": "Reuse descriptions for identical images (SHA-256 keyed, in-memory).", "modalityBridgeCacheTtlMinutes": "Cache TTL (minutes)", @@ -8174,12 +8174,12 @@ "modalityBridgeStatsLastUsed": "last used", "modalityBridgeStatsNever": "never", "modalityBridgeTestButton": "Test with sample image", - "modalityBridgeTestRunning": "Testing…", - "modalityBridgeTestOk": "Bridge OK — {count} image(s) described by {model}", + "modalityBridgeTestRunning": "Testing\u2026", + "modalityBridgeTestOk": "Bridge OK \u2014 {count} image(s) described by {model}", "modalityBridgeTestReroute": "Bridge rerouted the request to {model}", "modalityBridgeTestNoop": "Bridge did not activate (model may support vision natively or bridge is disabled)", "modalityBridgeTestError": "Test failed: {message}", - "modalityBridgeAudioComingSoon": "The Audio bridge (speech → text via /v1/audio/transcriptions) ships in the next release. Its settings keys are already reserved.", + "modalityBridgeAudioComingSoon": "The Audio bridge (speech \u2192 text via /v1/audio/transcriptions) ships in the next release. Its settings keys are already reserved.", "modalityBridgeMovedTitle": "Vision Bridge moved", "modalityBridgeMovedBody": "Vision Bridge settings now live in the dedicated Modality Bridge page.", "modalityBridgeMovedCta": "Open Modality Bridge settings", @@ -8191,8 +8191,8 @@ "modalityBridgeAudioModelAuto": "Auto (first connected STT provider)", "modalityBridgeAudioMaxClips": "Max audio clips per request", "modalityBridgeAudioTestButton": "Test with sample audio", - "modalityBridgeAudioTestRunning": "Testing audio…", - "modalityBridgeAudioTestOk": "Audio Bridge OK — {count} clip(s) transcribed by {model}", + "modalityBridgeAudioTestRunning": "Testing audio\u2026", + "modalityBridgeAudioTestOk": "Audio Bridge OK \u2014 {count} clip(s) transcribed by {model}", "modalityBridgeAudioTestNoop": "Audio Bridge did not activate (the target may support audio, no STT provider is connected, or the bridge is disabled)", "modalityBridgeAudioTestError": "Audio test failed: {message}", "modalityBridgeVideoTitle": "Video Bridge", @@ -8330,7 +8330,7 @@ "result": "Result", "previewEmpty": "Run a sample to preview RTK output.", "detected": "Detected", - "masterSwitchOffAlert": "Token Saver master switch is OFF — these settings will not affect requests until you turn it on from Compression Settings.", + "masterSwitchOffAlert": "Token Saver master switch is OFF \u2014 these settings will not affect requests until you turn it on from Compression Settings.", "tokensFiltered": "Tokens filtered", "filtersActive": "Filters active", "requests": "Requests", @@ -8342,13 +8342,13 @@ "tooltipMaxChars": "Maximum characters per output block.", "tooltipMaxLines": "Maximum lines to keep from command output. Excess is truncated.", "learnDiscoverTitle": "Learn & Discover filters", - "learnDiscoverDesc": "Mine your captured command output (Raw Output retention must be on) to suggest new RTK filters. Suggestions only — you review and save them.", + "learnDiscoverDesc": "Mine your captured command output (Raw Output retention must be on) to suggest new RTK filters. Suggestions only \u2014 you review and save them.", "discoverHeading": "Discover repeated noise", "discoverButton": "Scan samples", - "discoverScanning": "Scanning…", + "discoverScanning": "Scanning\u2026", "discoverEmpty": "No repeated noise found. Enable Raw Output retention and run a few commands first.", "discoverSamples": "{count} sample(s) scanned", - "discoverHits": "{hits}× across samples", + "discoverHits": "{hits}\u00d7 across samples", "learnHeading": "Learn a filter for a command", "learnCommandPlaceholder": "e.g. npm install", "learnButton": "Suggest filter", @@ -8360,9 +8360,9 @@ "tomlChooseFile": "Choose a TOML file", "tomlImportPlaceholder": "Paste RTK filters.toml content here...", "tomlValidate": "Validate", - "tomlValidating": "Validating…", + "tomlValidating": "Validating\u2026", "tomlInstall": "Install globally", - "tomlInstalling": "Installing…", + "tomlInstalling": "Installing\u2026", "tomlConfirmOverwrite": "Replace the existing global file and create a backup", "tomlValidationPassed": "Validation passed", "tomlValidationFailed": "Validation completed with failing inline tests", @@ -8376,7 +8376,7 @@ "tomlFileReadError": "Could not read the selected TOML file." }, "compressionEngineConfig": { - "loading": "Loading…", + "loading": "Loading\u2026", "loadFailed": "Failed to load engine information.", "engineNotFound": "Engine \"{engine}\" not found.", "saveFailed": "Failed to save configuration.", @@ -8387,11 +8387,11 @@ "configuration": "Configuration", "noAdditionalConfiguration": "No additional configuration.", "save": "Save", - "saving": "Saving…", + "saving": "Saving\u2026", "globalSettingsOnly": "This layer is configured by the global settings; there is no per-engine override to save here yet.", "preview": "Preview", "previewInput": "Preview input", - "processing": "Processing…", + "processing": "Processing\u2026", "previewSample": "The quick brown fox jumps over the lazy dog. This is a sample message used to preview compression. It contains enough text to show meaningful token savings.", "originalTokens": "Original tokens", "compressedTokens": "Compressed tokens", @@ -8514,7 +8514,7 @@ }, "options": { "model": { - "tinybert": "TinyBERT (57 MB, fast — default)", + "tinybert": "TinyBERT (57 MB, fast \u2014 default)", "bert-base": "BERT-base (710 MB, higher accuracy)" }, "intensity": { @@ -8554,28 +8554,28 @@ "save": "Save", "cancel": "Cancel", "enabled": "Enabled", - "loading": "Loading…", + "loading": "Loading\u2026", "hubTitle": "Compression Hub", "hubDescription": "Pick which compression profile runs globally.", "hideExplanation": "Hide explanation", "howItWorks": "How it works", "saveSettingsFailed": "Failed to save settings.", "explanationIntro": "Compression reduces tokens and cost by rewriting history before it is sent to the provider while preserving meaning.", - "explanationActiveProfile": "Active profile: chooses which compression profile runs globally — the panel-derived Default or one of your saved named combos.", + "explanationActiveProfile": "Active profile: chooses which compression profile runs globally \u2014 the panel-derived Default or one of your saved named combos.", "explanationDefault": "Default (from panel): derived from the master switch and per-engine toggles you configure in Compression Settings.", "explanationNamedCombos": "Named combos: saved pipelines you build in the named-combo editor. Selecting one makes it the active profile for every request.", "explanationPreview": "Preview: shows which engines the active profile runs, in order.", "activeProfile": "Active profile", - "activeProfileDescription": "Pick which compression profile runs globally — the panel-derived Default or a saved named combo.", + "activeProfileDescription": "Pick which compression profile runs globally \u2014 the panel-derived Default or a saved named combo.", "defaultFromPanel": "Default (from panel)", "runs": "Runs:", - "defaultConfiguredPrefix": "Default — configured in", + "defaultConfiguredPrefix": "Default \u2014 configured in", "compressionSettings": "Compression Settings", "providerDelegated": "Provider-delegated compression", "contextEditingClaude": "Context Editing (Claude)", "contextEditingDescription": "Lets the provider clear old tool-use blocks server-side, without rewriting the message.", "contextEditingAria": "Context Editing", - "contextEditingNote": "Currently available for Claude (Anthropic) only. It is a delegated mode: the provider clears old tool-use blocks server-side — we do not rewrite the message. It does not affect other providers.", + "contextEditingNote": "Currently available for Claude (Anthropic) only. It is a delegated mode: the provider clears old tool-use blocks server-side \u2014 we do not rewrite the message. It does not affect other providers.", "namedCombos": "Named combos", "namedCombosDescription": "Save different pipelines and assign them to specific routing combos.", "comboNamePlaceholder": "Combo name", @@ -8583,7 +8583,7 @@ "nameRequired": "Enter a combo name before saving.", "pipelineRequired": "Add at least one pipeline step before saving.", "saveComboFailed": "Failed to save combo (HTTP {status}).", - "deleteNamedConfirm": "Delete combo “{name}”?", + "deleteNamedConfirm": "Delete combo \u201c{name}\u201d?", "intensityLite": "Lite", "intensityFull": "Full", "intensityUltra": "Ultra", @@ -8608,15 +8608,15 @@ "stepProgress": "step {current}/{total}", "inlineView": "Inline", "splitComingSoon": "Split view (coming soon)", - "inputPlaceholder": "Paste a prompt, tool output, or context…", + "inputPlaceholder": "Paste a prompt, tool output, or context\u2026", "activeCombined": "Active in the combined flow", "requiresOnnx": "requires an ONNX model", "verifyFidelity": "Verify fidelity (reject any layer that corrupts content)", - "fuzzyDedup": "Fuzzy deduplication (near-duplicate → CCR)", + "fuzzyDedup": "Fuzzy deduplication (near-duplicate \u2192 CCR)", "protectSensitive": "Protect sensitive content (risk gate)", "quantumLock": "QuantumLock (stabilize the cache prefix)", "saliencyHeatmap": "Saliency heatmap", - "running": "Running…", + "running": "Running\u2026", "run": "Run", "laneRejected": "rejected: {reason}", "error": "error", @@ -8634,7 +8634,7 @@ "judgeModelPlaceholder": "e.g. claude-haiku", "maxCostUsd": "USD cap", "enterJudgeModel": "Enter a judge model", - "verifying": "Verifying…", + "verifying": "Verifying\u2026", "verifyAll": "Verify all", "spent": "spent ${spent} / ${cap}", "capReached": "cap reached", @@ -8646,7 +8646,7 @@ "fidelity": "Fidelity", "playTab": "Play", "compareTab": "Compare", - "encoderComparison": "Encoder A/B — {count} array(s)", + "encoderComparison": "Encoder A/B \u2014 {count} array(s)", "encoderWinner": "winner: {winner}", "encoder": "encoder", "bytes": "bytes", @@ -8659,15 +8659,15 @@ "economics": { "fewerTokens": "fewer tokens on the converted block", "savings": "end-to-end savings (measured)", - "imageTokens": "image tokens for a 1568×728 page (about 28k characters)", + "imageTokens": "image tokens for a 1568\u00d7728 page (about 28k characters)", "accuracy": "scoped Fable 5 reading receipt" }, - "beforeAfterTitle": "Before → after", - "blockSavings": "−{percent}% tokens on this block", - "realRender": "A real render of {characters, number} characters of dense tool documentation — not a mockup.", - "textTokens": "Text · ≈ {tokens, number} tokens", - "renderedTokens": "Rendered page · ≈ {tokens, number} tokens", - "renderedImageAlt": "Rendered dense page, {width}×{height}px", + "beforeAfterTitle": "Before \u2192 after", + "blockSavings": "\u2212{percent}% tokens on this block", + "realRender": "A real render of {characters, number} characters of dense tool documentation \u2014 not a mockup.", + "textTokens": "Text \u00b7 \u2248 {tokens, number} tokens", + "renderedTokens": "Rendered page \u00b7 \u2248 {tokens, number} tokens", + "renderedImageAlt": "Rendered dense page, {width}\u00d7{height}px", "gatesTitle": "When it fires", "gatesDescription": "Fail-closed: every gate must pass, or the request passes through untouched (each skip is recorded as skip:<reason>).", "gates": { @@ -8679,7 +8679,7 @@ "transport": { "label": "Transport", "pass": "direct provider", - "why": "Aggregators may resample images and destroy legibility — only a direct provider route is authoritative." + "why": "Aggregators may resample images and destroy legibility \u2014 only a direct provider route is authoritative." }, "format": { "label": "Format", @@ -8722,7 +8722,7 @@ }, "embeddedServices": { "title": "Embedded Services", - "description": "Optional helpers that run on your machine. Most users can start without them — install one only when an integration needs it.", + "description": "Optional helpers that run on your machine. Most users can start without them \u2014 install one only when an integration needs it.", "stateRunning": "Running", "stateStopped": "Stopped", "stateStarting": "Starting", @@ -8735,28 +8735,28 @@ "externalTitle": "External 9Router detected", "externalDescription": "OmniRoute found 9Router at {host}:{port}. Its lifecycle, logs, updates, and service key remain managed by the external installation.", "start": "Start", - "starting": "Starting…", + "starting": "Starting\u2026", "stop": "Stop", - "stopping": "Stopping…", + "stopping": "Stopping\u2026", "restart": "Restart", - "restarting": "Restarting…", + "restarting": "Restarting\u2026", "update": "Update", - "updating": "Updating…", + "updating": "Updating\u2026", "install": "Install", - "installing": "Installing…", + "installing": "Installing\u2026", "autoStart": "Auto-start", "autoStartDescription": "Launch {name} automatically when OmniRoute starts", "apiKey": "API Key", "apiKeyDescription": "Key used by OmniRoute to authenticate with {name}", - "keyRotated": "Key rotated — {name} restarted to apply the new key", + "keyRotated": "Key rotated \u2014 {name} restarted to apply the new key", "keyRotateFailed": "Failed to rotate key", "keyRevealFailed": "Failed to reveal key", "keyRevealEmpty": "Reveal failed: key not returned", "reveal": "Reveal", - "revealing": "Revealing…", + "revealing": "Revealing\u2026", "hide": "Hide", "rotateKey": "Rotate key", - "rotating": "Rotating…", + "rotating": "Rotating\u2026", "keyAutoHide": "Key will be hidden automatically in 30 seconds.", "revealTitle": "Reveal API Key", "revealConfirm": "Revealing the API key will be logged in the audit trail. Continue?", @@ -8764,32 +8764,32 @@ "install9Router": "Install 9Router", "install9RouterDescription": "Downloads and installs 9Router via npm. Requires approximately 500 MB of disk space.", "version": "Version", - "versionHint": "Enter “latest” or a specific version (for example, 1.2.3).", + "versionHint": "Enter \u201clatest\u201d or a specific version (for example, 1.2.3).", "servicePort": "Port", "servicePortHint": "OmniRoute and 9Router must use different ports. The default 9Router port is 20130.", "installationFailed": "Installation failed (HTTP {status})", - "installationSucceeded": "9Router installed successfully. Starting up…", - "networkInstallFailed": "Network error — could not reach the install endpoint", + "installationSucceeded": "9Router installed successfully. Starting up\u2026", + "networkInstallFailed": "Network error \u2014 could not reach the install endpoint", "availableModels": "Available Models", - "modelsLoading": "Loading…", + "modelsLoading": "Loading\u2026", "modelsDiscovered": "{count, plural, one {# model discovered} other {# models discovered}}", "modelsLoadFailed": "Failed to load models", - "refreshing": "Refreshing…", + "refreshing": "Refreshing\u2026", "refreshNow": "Refresh now", - "noModels": "No models found. Select “Refresh now” to sync from the running service.", + "noModels": "No models found. Select \u201cRefresh now\u201d to sync from the running service.", "unavailable": "Unavailable", "pageOf": "Page {page} of {total}", "previous": "Previous", "next": "Next", "providerExposure": "Provider Exposure", "providerExposureDescription": "Expose 9Router models as a routing target under the 9router/ prefix.", - "providerExposureLabel": "Expose as 9router/…", + "providerExposureLabel": "Expose as 9router/\u2026", "providerExposureHint": "When enabled, discovered models appear in provider selectors across OmniRoute.", "providerExposureUpdateFailed": "Failed to update (HTTP {status})", - "providerExposureNetworkFailed": "Network error — could not update the provider exposure setting", + "providerExposureNetworkFailed": "Network error \u2014 could not update the provider exposure setting", "webUi": "9Router Web UI", "openNewTab": "Open in new tab", - "filterLogs": "Filter logs…", + "filterLogs": "Filter logs\u2026", "resume": "Resume", "pause": "Pause", "clear": "Clear", @@ -8801,7 +8801,7 @@ "enableFallback": "Enable fallback", "cliproxyUrl": "CLIProxyAPI URL", "fallbackCodes": "Fallback status codes (comma-separated)", - "invalidUrl": "Invalid URL — must start with http:// or https://", + "invalidUrl": "Invalid URL \u2014 must start with http:// or https://", "saved": "Saved", "saveFailed": "Failed to save setting", "modelMapping": "Model Mapping", @@ -8811,7 +8811,7 @@ "mappingSaveFailed": "Failed to save mapping", "mappingInvalidJson": "Invalid JSON.", "mappingMustBeObject": "The mapping must be a JSON object, not an array or primitive value.", - "mappingValueMustBeString": "The value for key “{key}” must be a string.", + "mappingValueMustBeString": "The value for key \u201c{key}\u201d must be a string.", "save": "Save" }, "contextCaveman": { @@ -8839,7 +8839,7 @@ "inputCompressionDesc": "Rewrite chat history with shorter wording. Reduces input tokens by ~50%.", "analyticsTitle": "Compression Analytics", "noAnalytics": "No compression analytics yet.", - "masterDisabledWarning": "The Token Saver master switch is OFF — these settings will not affect requests until you turn it on from Compression Settings or change it here.", + "masterDisabledWarning": "The Token Saver master switch is OFF \u2014 these settings will not affect requests until you turn it on from Compression Settings or change it here.", "outputMode": "Output Mode", "outputModeDesc": "Control how compressed output appears", "outputModeTitle": "Output Mode", @@ -8873,7 +8873,7 @@ "chatTester": "Chat Tester", "testBench": "Test Bench", "liveMonitor": "Live Monitor", - "modeDescriptionPlayground": "Paste any API request body and see how OmniRoute translates it between provider formats (OpenAI ↔ Claude ↔ Gemini ↔ Responses API)", + "modeDescriptionPlayground": "Paste any API request body and see how OmniRoute translates it between provider formats (OpenAI \u2194 Claude \u2194 Gemini \u2194 Responses API)", "modeDescriptionChatTester": "Send real chat requests through OmniRoute and inspect the full round-trip: input, translated request, provider response, and translated output.", "modeDescriptionTestBench": "Run predefined scenarios and compare compatibility across providers and models.", "modeDescriptionLiveMonitor": "Watch translation events in real time as requests flow through OmniRoute.", @@ -8891,8 +8891,8 @@ "errors": "Errors", "avgLatency": "Avg Latency", "millisecondsShort": "{value}ms", - "notAvailableSymbol": "—", - "liveAutoRefreshing": "Live — Auto-refreshing", + "notAvailableSymbol": "\u2014", + "liveAutoRefreshing": "Live \u2014 Auto-refreshing", "paused": "Paused", "eventsAppearHint": "Translation events appear here as requests flow through OmniRoute. Use any of these methods to generate events:", "chatTesterTab": "Chat Tester tab", @@ -8903,10 +8903,10 @@ "ok": "OK", "errorShort": "ERR", "formatConverter": "Format Converter", - "formatConverterDescription": "Paste or type a JSON request body. The translator will auto-detect the source format and convert it to the target format. Use this to debug how OmniRoute translates requests between formats (OpenAI ↔ Claude ↔ Gemini ↔ Responses API).", - "translationPathHubSpoke": "{source} → OpenAI (intermediate) → {target}", - "translationPathDirect": "{source} → {target} (direct translator)", - "translationPathPassthrough": "Same format — no translation needed", + "formatConverterDescription": "Paste or type a JSON request body. The translator will auto-detect the source format and convert it to the target format. Use this to debug how OmniRoute translates requests between formats (OpenAI \u2194 Claude \u2194 Gemini \u2194 Responses API).", + "translationPathHubSpoke": "{source} \u2192 OpenAI (intermediate) \u2192 {target}", + "translationPathDirect": "{source} \u2192 {target} (direct translator)", + "translationPathPassthrough": "Same format \u2014 no translation needed", "openaiIntermediatePanel": "OpenAI Intermediate", "autoFeaturesTitle": "What OmniRoute does automatically", "autoFeaturesCount": "8 features", @@ -8915,7 +8915,7 @@ "featureSchemaCoercion": "Schema Coercion", "featureSchemaCoercionDesc": "Fixes broken tool schemas: adds missing additionalProperties, sanitizes long descriptions, normalizes nested objects.", "featureRoleNormalization": "Role Normalization", - "featureRoleNormalizationDesc": "Maps developer→system for non-OpenAI targets. Maps system→user for models that don't support system role.", + "featureRoleNormalizationDesc": "Maps developer\u2192system for non-OpenAI targets. Maps system\u2192user for models that don't support system role.", "featureToolCallIds": "Tool Call ID Normalization", "featureToolCallIdsDesc": "Generates unique tool_call IDs when missing. Normalizes to 9-char format for providers like Mistral.", "featureMissingToolResponse": "Tool Response Injection", @@ -8923,9 +8923,9 @@ "featureThinkingBudget": "Thinking Budget", "featureThinkingBudgetDesc": "Proxy-level control of thinking/reasoning request fields: passthrough (client controls), auto (strip all thinking params), custom (fixed budget), or adaptive (scale by complexity). Independent of compression and routing. Does not decrypt encrypted reasoning content.", "featureDirectPaths": "Direct Translation Paths", - "featureDirectPathsDesc": "Some format pairs (Claude→Gemini) have direct translators that bypass the OpenAI hub, producing more accurate output.", + "featureDirectPathsDesc": "Some format pairs (Claude\u2192Gemini) have direct translators that bypass the OpenAI hub, producing more accurate output.", "featureImageMapping": "Image Size Mapping", - "featureImageMappingDesc": "Translates image dimension conventions between API formats (e.g., OpenAI detail levels → Gemini dimensions).", + "featureImageMappingDesc": "Translates image dimension conventions between API formats (e.g., OpenAI detail levels \u2192 Gemini dimensions).", "input": "Input", "output": "Output", "auto": "Auto", @@ -8934,7 +8934,7 @@ "clear": "Clear", "inputPlaceholder": "Paste a request body here or select a template below...", "exampleTemplates": "Example Templates", - "exampleTemplatesHint": "— Click to load", + "exampleTemplatesHint": "\u2014 Click to load", "templateLoadHint": "Template loads the request in {format} format. Change Source Format to load in a different format.", "compatibilityTester": "Compatibility Tester", "compatibilityReport": "Compatibility Report", @@ -8946,7 +8946,7 @@ "running": "Running...", "passed": "passed", "failed": "failed", - "passedIconLabel": "✅ Passed", + "passedIconLabel": "\u2705 Passed", "chunks": "chunks", "scenarioSimpleChat": "Simple Chat", "scenarioToolCalling": "Tool Calling", @@ -8980,7 +8980,7 @@ "userGreeting": "Hello! How are you today?" }, "toolCalling": { - "userWeather": "What's the weather in São Paulo?", + "userWeather": "What's the weather in S\u00e3o Paulo?", "toolDescription": "Get current weather for a location", "cityNameDescription": "City name" }, @@ -9017,7 +9017,7 @@ "translationFailed": "Translation failed: {error}", "pipelineDebugger": "Pipeline Debugger", "translationPipeline": "Translation Pipeline", - "loading": "Loading…", + "loading": "Loading\u2026", "compressionOriginal": "Original", "compressionCompressed": "Compressed", "compressionSaved": "Saved", @@ -9026,9 +9026,9 @@ "copyIntermediateJson": "Copy intermediate JSON", "copyOutputJson": "Copy output JSON", "pipelineVisualization": "Pipeline visualization", - "pipelineVisualizationHint": "Send a message to see how your request flows through detection → translation → provider call.", + "pipelineVisualizationHint": "Send a message to see how your request flows through detection \u2192 translation \u2192 provider call.", "chatTesterDescription": "Send messages as a specific client format and inspect each step of the translation pipeline.", - "chatTesterFlow": "Client Request → Format Detection → OpenAI Intermediate → Provider Format → Response", + "chatTesterFlow": "Client Request \u2192 Format Detection \u2192 OpenAI Intermediate \u2192 Provider Format \u2192 Response", "clickStepToInspect": "Click any step to inspect the data at that stage.", "clientFormat": "Client Format", "provider": "Provider", @@ -9059,8 +9059,8 @@ "liveMonitorMemoryNote": "Events are stored in memory and lost on restart.", "liveMonitorMemoryCapNote": "Max 200 events retained.", "eventSourcesLabel": "Event sources:", - "eventSourceTranslatorPage": "• Translator page (Chat Tester, Test Bench)", - "eventSourceMainPipeline": "• Main request pipeline (CLI/IDE/API traffic)", + "eventSourceTranslatorPage": "\u2022 Translator page (Chat Tester, Test Bench)", + "eventSourceMainPipeline": "\u2022 Main request pipeline (CLI/IDE/API traffic)", "liveMonitorDescriptionPrefix": "Shows translation events as API calls flow through OmniRoute. Events come from the in-memory buffer (resets on restart). Use", "liveMonitorDescriptionSuffix": ", or external API calls to generate events.", "streamTransformer": "Stream Transformer", @@ -9091,7 +9091,7 @@ "scenarioSchemaCoercion": "Schema coercion (structured output)", "techniques": "Techniques:", "friendlyTitle": "Translator", - "friendlySubtitle": "Use your existing app with any provider — without rewriting code.", + "friendlySubtitle": "Use your existing app with any provider \u2014 without rewriting code.", "conceptHeadline": "Your app speaks one API's \"language\". The Translator converts it to use another provider.", "conceptDiagramAppLabel": "Your app", "conceptDiagramSourceLabel": "Source format", @@ -9120,27 +9120,27 @@ "simpleInputPanelTitle": "Input", "simpleInputPanelHint": "Free-text message or ready-made example", "simpleResultPanelTitle": "Translation + Response", - "narratedDetected": "✓ Detected: {format}", + "narratedDetected": "\u2713 Detected: {format}", "narratedTranslating": "Translating to {target}...", "narratedSending": "Sending to {target}...", - "narratedSuccess": "→ translated to {target} · response in {latency}ms", + "narratedSuccess": "\u2192 translated to {target} \u00b7 response in {latency}ms", "narratedError": "Failed: {reason}", "narratedSeeTranslatedJson": "see translated JSON", "narratedSeePipeline": "see pipeline", "advancedSectionTitle": "Advanced", - "advancedSectionSubtitle": "Raw JSON, pipeline and technical tools. Everything here is the same as the old tabs — just reorganized.", + "advancedSectionSubtitle": "Raw JSON, pipeline and technical tools. Everything here is the same as the old tabs \u2014 just reorganized.", "advancedRawJsonTitle": "Raw JSON (auto-detect + Monaco)", "advancedRawJsonSubtitle": "Paste a JSON request; the format is detected automatically.", "advancedPipelineTitle": "OpenAI intermediate pipeline", "advancedPipelineSubtitle": "Visualize each translation step (hub-and-spoke).", - "advancedStreamTransformTitle": "Stream Transformer (Chat → Responses SSE)", + "advancedStreamTransformTitle": "Stream Transformer (Chat \u2192 Responses SSE)", "advancedStreamTransformSubtitle": "Converts Chat Completions SSE into Responses API.", "advancedTestBenchTitle": "Test Bench (8 scenarios)", "advancedTestBenchSubtitle": "Runs all scenarios and reports pass/fail + compatibility %.", "advancedCompressionTitle": "Compression Preview", "advancedCompressionSubtitle": "Estimate token savings across different compression modes.", "monitorOriginHint": "Events generated by Translate or the main pipeline appear here in real time.", - "monitorEmptyCta": "Go to the Translate tab and send a request — it will appear here.", + "monitorEmptyCta": "Go to the Translate tab and send a request \u2014 it will appear here.", "monitorOpenTranslateButton": "Go to Translate", "pipelineStepClientRequest": "Client Request", "pipelineStepClientRequestDesc": "Request received in client format", @@ -9162,7 +9162,7 @@ "compressionEmptyHint": "Fill in the input field on the Translate tab (Simple Controls or Raw JSON) to enable the preview.", "compressionModeLabel": "Compression mode", "compressionPreviewButton": "Preview Compression", - "compressionPreviewing": "Previewing…", + "compressionPreviewing": "Previewing\u2026", "compressionPreviewFailed": "Compression preview failed", "tokens": "tokens", "pauseAutoRefresh": "Pause auto-refresh", @@ -9213,8 +9213,8 @@ "monthlyLimitPlaceholder": "e.g. 50.00", "warningThresholdPlaceholder": "80", "saveLimits": "Save Limits", - "budgetOk": "Budget OK — {remaining} remaining", - "budgetExceeded": "Budget exceeded — requests may be blocked", + "budgetOk": "Budget OK \u2014 {remaining} remaining", + "budgetExceeded": "Budget exceeded \u2014 requests may be blocked", "totalRequests": "Total requests", "noDataYet": "No data yet", "latency": "Latency", @@ -9230,7 +9230,7 @@ "hitsMisses": "Hits / Misses", "circuitBreakers": "Circuit Breakers", "lockedIPs": "Locked IPs", - "lockoutsAutoRefreshHint": "Per-model rate limit locks • Auto-refresh 10s", + "lockoutsAutoRefreshHint": "Per-model rate limit locks \u2022 Auto-refresh 10s", "lockedCount": "{count, plural, one {# locked} other {# locked}}", "timeLeft": "{time} left", "howItWorks": "How It Works", @@ -9281,13 +9281,13 @@ "runAllRunning": "Running all...", "runAllProgress": "Running {current}/{total}: {name}", "runAllFailedSuites": "{count, plural, one {# suite failed} other {# suites failed}}", - "runAllCompleted": "Ran {suites} suites — {passed} passed, {failed} failed", + "runAllCompleted": "Ran {suites} suites \u2014 {passed} passed, {failed} failed", "runAllCompletedWithFailures": "Ran {completed} suites; {failedSuites} failed to complete", "runningProgress": "Running {current}/{total}...", "passRate": "pass rate", - "summaryBreakdown": "{passed} passed · {failed} failed · {total} total", - "passedIconLabel": "✅ Passed", - "failedIconLabel": "❌ Failed", + "summaryBreakdown": "{passed} passed \u00b7 {failed} failed \u00b7 {total} total", + "passedIconLabel": "\u2705 Passed", + "failedIconLabel": "\u274c Failed", "resultPassed": "Passed", "resultFailed": "Failed", "expandResult": "Expand result details", @@ -9301,12 +9301,12 @@ "noTestCasesDefined": "No test cases defined", "runEvalHint": "Click \"Run Eval\" to execute all cases against your LLM endpoint. Each test sends a real request through OmniRoute.", "notifyNoTestCases": "No test cases defined for this suite", - "notifyAllCasesPassed": "All {total} cases passed ✅", + "notifyAllCasesPassed": "All {total} cases passed \u2705", "notifySomeCasesFailed": "{passed}/{total} passed, {failed} failed", "notifyEvalRunFailed": "Eval run failed", "notifyEvalTitle": "Eval: {name}", "modelEvals": "Model Evaluations", - "evalsHeroDescription": "Test and validate your LLM endpoints by running predefined evaluation suites. Each suite contains test cases that send real prompts through OmniRoute and compare responses against expected criteria — helping you detect regressions, compare models, and ensure response quality across providers.", + "evalsHeroDescription": "Test and validate your LLM endpoints by running predefined evaluation suites. Each suite contains test cases that send real prompts through OmniRoute and compare responses against expected criteria \u2014 helping you detect regressions, compare models, and ensure response quality across providers.", "qualityValidation": "Quality Validation", "modelComparison": "Model Comparison", "regressionDetection": "Regression Detection", @@ -9316,7 +9316,7 @@ "activeSessions": "Active Sessions", "noSessions": "No active sessions", "sessionsHint": "Sessions appear as requests flow through the proxy", - "sessionsTrackedHint": "Tracked via request fingerprinting • Auto-refresh 5s", + "sessionsTrackedHint": "Tracked via request fingerprinting \u2022 Auto-refresh 5s", "session": "Session", "age": "Age", "requests": "Requests", @@ -9452,7 +9452,7 @@ "suiteBuilderDeleteFailed": "Failed to delete suite", "suiteBuilderCaseSystemPromptPlaceholder": "Optional system instructions...", "scorecardCases": "Cases", - "runCompletedWithScore": "Evaluation completed — {score}% pass rate", + "runCompletedWithScore": "Evaluation completed \u2014 {score}% pass rate", "suiteBuilderCustomBadge": "Custom", "targetSuiteDefaults": "Suite defaults", "suiteBuilderDeleteConfirm": "Are you sure you want to delete this suite? This action cannot be undone.", @@ -9517,8 +9517,8 @@ "weeklyLimitSummary": "Weekly budget limit summary", "suiteBuilderDescriptionLabel": "Description", "targetComparisonHint": "Side-by-side comparison of evaluation results between targets.", - "compareCompletedWithScore": "Comparison completed — {score}% pass rate", - "staleQuotaTooltip": "Last refresh failed — showing cached data", + "compareCompletedWithScore": "Comparison completed \u2014 {score}% pass rate", + "staleQuotaTooltip": "Last refresh failed \u2014 showing cached data", "quotaThresholdLabel": "Min left", "quotaCutoffsColumnHelp": "Stop requests when remaining quota falls to this percentage or below.", "quotaCutoffsButtonDefault": "Default", @@ -9543,14 +9543,14 @@ "budgetKpiActiveKeys": "Active keys", "budgetPageTitle": "Budget", "budgetPageDescription": "Set daily, weekly, and monthly spending limits for each API key.", - "budgetTemplateStorageHint": "{count, plural, one {# template} other {# templates}} · edit via localStorage:", - "budgetAboveLimitShort": "above limit ⚠", + "budgetTemplateStorageHint": "{count, plural, one {# template} other {# templates}} \u00b7 edit via localStorage:", + "budgetAboveLimitShort": "above limit \u26a0", "budgetOnTrackShort": "on track", - "budgetAtOrAboveWarning": "≥ warning threshold", + "budgetAtOrAboveWarning": "\u2265 warning threshold", "budgetTemplates": "Templates", "budgetSelectKeysFirst": "Select keys first to apply", "budgetApplyToSelected": "Apply to {count, plural, one {# selected key} other {# selected keys}}", - "budgetSelectedTemplateHint": "{count, plural, one {# selected} other {# selected}} · click a template to apply", + "budgetSelectedTemplateHint": "{count, plural, one {# selected} other {# selected}} \u00b7 click a template to apply", "budgetTemplateMonthlyAmount": "${amount}/month", "budgetTemplateDailyAmount": "${amount}/day", "budgetNoKeysSelected": "No keys selected", @@ -9574,22 +9574,22 @@ "budgetColumnMonth": "Month", "budgetColumnStatus": "Status", "budgetSearchKeysPlaceholder": "Search keys...", - "budgetSortPctUsed": "Sort: % Used ↓", - "budgetSortTodayDollar": "Sort: Today $ ↓", - "budgetSortMonthDollar": "Sort: Month $ ↓", - "budgetSortNameAZ": "Sort: Name (A–Z)", + "budgetSortPctUsed": "Sort: % Used \u2193", + "budgetSortTodayDollar": "Sort: Today $ \u2193", + "budgetSortMonthDollar": "Sort: Month $ \u2193", + "budgetSortNameAZ": "Sort: Name (A\u2013Z)", "budgetColDailyLim": "Daily lim", "budgetColMonthlyLim": "Monthly lim", "budgetColUsedPct": "Used %", "percentUsed": "{pct}% used", - "budgetLoading": "Loading…", + "budgetLoading": "Loading\u2026", "budgetNoKeysMatch": "No keys match filters", "budgetLinearExtrapolation": "linear extrapolation", "budgetThisMonthSoFar": "This month so far", "budgetProjectedEndOfMonth": "Projected end of month", "budgetByProvider": "by provider", "budgetProjection": "Projection", - "budgetAboveMonthlyLimit": "⚠ above {limit}/month", + "budgetAboveMonthlyLimit": "\u26a0 above {limit}/month", "budgetCostBreakdown30d": "Cost breakdown (30 days)", "budgetLimits": "Limits", "budgetResetDaily": "Daily", @@ -9603,7 +9603,7 @@ "budgetMonthlyDollar": "Monthly $", "budgetWarnAtPct": "Warn at %", "quotaAlerts": "Quota alerts", - "quotaTableRefreshing": "⟳ Refreshing...", + "quotaTableRefreshing": "\u27f3 Refreshing...", "noSpendLast30Days": "No spend in last 30 days", "updatedShort": "Updated", "lastRefreshed": "Last refreshed", @@ -9788,7 +9788,7 @@ "quitDesktopApp": "Quit the OmniRoute desktop app completely", "navigateToAppDataDirectory": "Navigate to the app data directory:", "checkSystemAppDataFolder": "(Check your system app data folder)", - "relaunchDesktopFreshSetup": "Relaunch the OmniRoute desktop app — it will start fresh setup", + "relaunchDesktopFreshSetup": "Relaunch the OmniRoute desktop app \u2014 it will start fresh setup", "alternativeSetPasswordTitle": "Alternative: Set New Password", "alternativeSetPasswordDescription": "Set a new initial password via the server environment file:", "openServerEnvFilePrefix": "Open", @@ -9802,19 +9802,19 @@ "unexpectedAuthenticationError": "Unexpected error during authentication. Please try again.", "connectOpenAiCodexTitle": "Connect OpenAI Codex", "codexConnectDescription": "Authorize a ChatGPT account to finish setting up this connection.", - "validatingCodexLink": "Validating link…", + "validatingCodexLink": "Validating link\u2026", "codexGenerateCodeDescription": "Click below to generate a one-time code, then sign in to OpenAI.", "startCodexFlow": "Start", - "requestingOpenAiCode": "Requesting code from OpenAI…", + "requestingOpenAiCode": "Requesting code from OpenAI\u2026", "codexVerificationInstructions": "1. Open the OpenAI verification page and 2. enter this code. This page updates automatically once you authorize.", "yourCode": "Your code", "openVerificationPage": "Open verification page", "copyUrlShort": "Copy URL", - "savingConnection": "Saving connection…", + "savingConnection": "Saving connection\u2026", "codexConnected": "Connected!", "codexConnectionRegistered": "The Codex account was registered. You can close this tab.", "tryAgain": "Try again", - "traeAuthorizationSuccess": "Trae authorization ✓", + "traeAuthorizationSuccess": "Trae authorization \u2713", "traeAuthorizationFailed": "Trae authorization failed", "closeAuthorizationWindow": "You can close this window.", "returnToDashboard": "Return to the dashboard.", @@ -9824,7 +9824,7 @@ "navigateToAppData": "Navigate to the app data directory", "checkAppDataFolder": "Check your system app data folder", "deleteSettingsJson": "Delete", - "relaunchAppFresh": "Relaunch the OmniRoute desktop app — it will start fresh setup", + "relaunchAppFresh": "Relaunch the OmniRoute desktop app \u2014 it will start fresh setup", "alternativeSetPassword": "Alternative: Set New Password", "setNewPasswordViaEnv": "Set a new initial password via the server environment file:", "openServerEnv": "Open", @@ -9884,12 +9884,12 @@ "routeRequestsStepDescription": "Point your CLI tools to {endpoint}", "terminal": "terminal", "copy": "Copy", - "copied": "✓ Copied", + "copied": "\u2713 Copied", "startingOmniRoute": "Starting OmniRoute...", "serverRunningOnLabel": "Server running on", "dashboardLabel": "Dashboard", - "readyToRoute": "Ready to route! ✓", - "configureProvidersNote": "📝 Configure providers in dashboard or use environment variables", + "readyToRoute": "Ready to route! \u2713", + "configureProvidersNote": "\ud83d\udcdd Configure providers in dashboard or use environment variables", "dataLocation": "Data Location:", "dataLocationMacLinux": "macOS/Linux:", "dataLocationWindows": "Windows:", @@ -9902,7 +9902,7 @@ "legal": "Legal", "mitLicense": "MIT License", "footerTagline": "The unified endpoint for AI generation. Connect, route, and manage your AI providers with ease.", - "copyright": "© {year} OmniRoute. All rights reserved.", + "copyright": "\u00a9 {year} OmniRoute. All rights reserved.", "flowToolClaudeCode": "Claude Code", "flowToolOpenAICodex": "OpenAI Codex", "flowToolCline": "Cline", @@ -10134,17 +10134,17 @@ "featureAcpText": "Agent Communication Protocol (ACP) registry for managing agent-to-agent workflows and tool orchestration.", "protocolAcpTitle": "ACP (Agent Communication)", "protocolAcpDesc": "Register and manage agents via the ACP registry for inter-agent communication and tool sharing.", - "protocolAcpStep1": "Navigate to Dashboard → Agents to view registered ACP agents.", + "protocolAcpStep1": "Navigate to Dashboard \u2192 Agents to view registered ACP agents.", "protocolAcpStep2": "Register new agents with capabilities and endpoint configuration.", "protocolAcpStep3": "Use CLI Tools to configure agent communication channels.", - "metadataTitleTemplate": "%s — OmniRoute Docs", + "metadataTitleTemplate": "%s \u2014 OmniRoute Docs", "metadataDefaultTitle": "OmniRoute Documentation", - "metadataDescription": "Comprehensive documentation for OmniRoute AI gateway — setup, API, compression, deployment, and more.", + "metadataDescription": "Comprehensive documentation for OmniRoute AI gateway \u2014 setup, API, compression, deployment, and more.", "layoutNavTitle": "OmniRoute Docs", "layoutHomeLink": "Docs Home", - "layoutDashboardLink": "← Back to Dashboard", + "layoutDashboardLink": "\u2190 Back to Dashboard", "homeTitle": "OmniRoute Documentation", - "homeMetadataDescription": "Everything you need to route, compress, and scale your AI — setup guides, API reference, compression, deployment, and more.", + "homeMetadataDescription": "Everything you need to route, compress, and scale your AI \u2014 setup guides, API reference, compression, deployment, and more.", "homeTwitterDescription": "Comprehensive docs for OmniRoute AI gateway", "homeDescription": "Everything you need to route, compress, and scale your AI", "homeSearchHint": "Press Ctrl K to search the docs", @@ -10155,14 +10155,14 @@ "featuredProvidersTitle": "Providers Guide", "featuredProvidersDescription": "Connect AI providers in minutes", "nonTechUsersTitle": "For Non-Tech Users", - "nonTechUsersSubtitle": "Get started quickly — no technical background needed", + "nonTechUsersSubtitle": "Get started quickly \u2014 no technical background needed", "techUsersTitle": "For Tech Users", "techUsersSubtitle": "Deep dive into architecture, APIs, and internals", - "apiExplorerMetadataTitle": "API Explorer — OmniRoute Docs", - "apiExplorerMetadataDescription": "Interactive API explorer — try OmniRoute endpoints live with real-time responses", + "apiExplorerMetadataTitle": "API Explorer \u2014 OmniRoute Docs", + "apiExplorerMetadataDescription": "Interactive API explorer \u2014 try OmniRoute endpoints live with real-time responses", "apiExplorerTitle": "API Explorer", "apiExplorerDescription": "Try OmniRoute endpoints live. Select an endpoint, configure your request, and see the response in real time.", - "apiExplorerSseStarted": "SSE stream started — check the terminal/devtools for real-time output.", + "apiExplorerSseStarted": "SSE stream started \u2014 check the terminal/devtools for real-time output.", "apiExplorerError": "Error: {message}", "apiExplorerAuth": "auth", "apiExplorerResponseLabel": "Response", @@ -10170,7 +10170,7 @@ "apiExplorerChooseApi": "Choose an API from the sidebar to see details and try it live", "feedbackThanks": "Thanks for your feedback!", "feedbackQuestion": "Was this page helpful?", - "pageMetadataTitle": "{title} — OmniRoute Docs", + "pageMetadataTitle": "{title} \u2014 OmniRoute Docs", "pageMetadataDescription": "OmniRoute documentation: {title}", "breadcrumb": "Breadcrumb", "apiReferenceTable": { @@ -10261,7 +10261,7 @@ "scanning": "Scanning system for CLI agents...", "opencodeIntegration": "OpenCode Integration", "opencodeDetected": "opencode {version} detected", - "opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.", + "opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models \u2014 drop it in your project root and run {command}.", "downloadConfig": "Download {file}", "downloaded": "Downloaded!", "setupGuideTitle": "Setup guide", @@ -10273,19 +10273,19 @@ "setupGuideCommandMissingTitle": "Fix 'command not found'", "setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh.", "cliToolsRedirectTitle": "Looking to configure your IDE?", - "cliToolsRedirectDesc": "If you want to point Claude Code, Cursor, Codex, or any IDE at OmniRoute as a proxy, go to CLI Tools instead. This page is for the reverse — using local CLI binaries as execution targets that OmniRoute can spawn and route requests through.", + "cliToolsRedirectDesc": "If you want to point Claude Code, Cursor, Codex, or any IDE at OmniRoute as a proxy, go to CLI Tools instead. This page is for the reverse \u2014 using local CLI binaries as execution targets that OmniRoute can spawn and route requests through.", "spawnArgsPlaceholder": "e.g., --quiet, --json, --no-auto-commits", "binaryNamePlaceholder": "e.g., my-agent or /usr/local/bin/llm-cli", "versionCommandPlaceholder": "e.g., my-agent --version", "architectureTitle": "How Agent Targets Work", - "flowLocalBinary": "3 · CLI process with its own auth", - "flowOmniRoute": "1 · Request arrives at OmniRoute", + "flowLocalBinary": "3 \u00b7 CLI process with its own auth", + "flowOmniRoute": "1 \u00b7 Request arrives at OmniRoute", "agentNamePlaceholder": "e.g., My Custom Agent", "architectureDescription": "When a request arrives, OmniRoute can spawn a local CLI binary (e.g., claude, codex, goose) and pipe the request through it. The CLI processes the request using its own credentials and returns the result back through OmniRoute.", - "flowExecute": "4 · Response returned to client", - "flowSpawn": "2 · OmniRoute spawns local binary", - "cliToolsRedirectCta": "Go to CLI Tools →", - "comparisonTitle": "CLI Tools vs Agent Targets — What's the Difference?", + "flowExecute": "4 \u00b7 Response returned to client", + "flowSpawn": "2 \u00b7 OmniRoute spawns local binary", + "cliToolsRedirectCta": "Go to CLI Tools \u2192", + "comparisonTitle": "CLI Tools vs Agent Targets \u2014 What's the Difference?", "comparisonCliToolsLabel": "CLI Tools page", "comparisonCliToolsTitle": "Your IDE sends requests through OmniRoute", "comparisonCliToolsDesc": "Configure Claude Code, Codex, Cursor, and other IDEs to use OmniRoute as their API base URL. OmniRoute acts as a proxy, routing requests to your configured providers.", @@ -10315,12 +10315,12 @@ "browseAllSkillsOnGithub": "Browse all skills on GitHub", "apiSkills": "API Skills", "cliSkills": "CLI Skills", - "apiSkillsSubtitle": "{count} skills — control OmniRoute via REST / HTTP", - "cliSkillsSubtitle": "{count} skills — control OmniRoute via the omniroute terminal binary", + "apiSkillsSubtitle": "{count} skills \u2014 control OmniRoute via REST / HTTP", + "cliSkillsSubtitle": "{count} skills \u2014 control OmniRoute via the omniroute terminal binary", "howToUseStep1": "Click {copyUrl} on the skill you want your agent to know about.", - "howToUseStep2": "In your AI agent (Claude, Cursor, Cline…), say:", + "howToUseStep2": "In your AI agent (Claude, Cursor, Cline\u2026), say:", "howToUseStep2Code": "Use the skill at [pasted-url]", - "howToUseStep3": "The agent fetches the SKILL.md and learns OmniRoute's API or CLI — no manual docs needed." + "howToUseStep3": "The agent fetches the SKILL.md and learns OmniRoute's API or CLI \u2014 no manual docs needed." }, "cloudAgents": { "title": "Cloud Agents", @@ -10329,7 +10329,7 @@ "aboutTitle": "About Cloud Agents", "aboutDescription": "Cloud agents are remote AI coding assistants that can execute tasks autonomously. They work differently from local CLI agents - you interact with them through OmniRoute's API.", "howItWorksTitle": "How it works:", - "howItWorksDesc": "Create a task → Agent analyzes and proposes a plan → You approve → Agent executes → Results returned", + "howItWorksDesc": "Create a task \u2192 Agent analyzes and proposes a plan \u2192 You approve \u2192 Agent executes \u2192 Results returned", "newTaskTitle": "Create New Task", "newTaskDescription": "Start a new task with a cloud agent", "selectAgent": "Select Agent", @@ -10585,7 +10585,7 @@ "levelKey": "Key", "levelDirect": "Direct (no proxy)", "titleGlobal": "Global Proxy Configuration", - "titleLevel": "{level} Proxy — {label}", + "titleLevel": "{level} Proxy \u2014 {label}", "loading": "Loading proxy configuration...", "inheritingFrom": "Inheriting from", "source": "Source", @@ -10627,22 +10627,22 @@ "deviceCodeYourCode": "Your code", "deviceCodeWaiting": "Waiting for authorization...", "googleLoopbackTitle": "Google sign-in can't complete from this address", - "googleLoopbackWhatHappens": "Google only releases the authorization code once {redirectUri} is reachable from the browser that approves the sign-in. Here that address points at this computer, not at the OmniRoute server — so the consent screen hangs instead of redirecting, and there is no callback URL to copy.", - "googleLoopbackRecommended": "Recommended — run this on your own computer, then paste the result below:", - "googleLoopbackHelperNote": "It opens the Google consent locally (where 127.0.0.1 works) and prints a one-line omniroute-cred-v1.… blob. Paste that blob into the Step 2 field below — it accepts a credential blob as well as a callback URL.", + "googleLoopbackWhatHappens": "Google only releases the authorization code once {redirectUri} is reachable from the browser that approves the sign-in. Here that address points at this computer, not at the OmniRoute server \u2014 so the consent screen hangs instead of redirecting, and there is no callback URL to copy.", + "googleLoopbackRecommended": "Recommended \u2014 run this on your own computer, then paste the result below:", + "googleLoopbackHelperNote": "It opens the Google consent locally (where 127.0.0.1 works) and prints a one-line omniroute-cred-v1.\u2026 blob. Paste that blob into the Step 2 field below \u2014 it accepts a credential blob as well as a callback URL.", "googleLoopbackTunnelLabel": "Or forward the dashboard port over SSH and reload OmniRoute through the tunnel:", "googleLoopbackTunnelNote": "Replace {userPlaceholder} with your SSH username, keep the terminal open, then open {localUrl} and connect again from there.", "googleLoopbackHeadlessAlt": "For fully headless use with no local callback at all, configure your own Google OAuth credentials plus a public base URL.", - "remoteAccessInfo": "Remote access: Since you're accessing OmniRoute remotely, after authorization you'll see an error page (localhost not found). This is normal — just copy the full URL from your browser address bar and paste it below.", + "remoteAccessInfo": "Remote access: Since you're accessing OmniRoute remotely, after authorization you'll see an error page (localhost not found). This is normal \u2014 just copy the full URL from your browser address bar and paste it below.", "loopbackMismatchTitle": "Sign-in can't complete from this address", "loopbackMismatchWhatHappened": "What's happening", - "loopbackMismatchExplanation": "After you approve the login, {providerName} always sends the browser back to {redirectUri}. That address points at the computer running this browser, not at the OmniRoute server — so the authorization code never reaches OmniRoute and the provider fails the sign-in without showing an error.", + "loopbackMismatchExplanation": "After you approve the login, {providerName} always sends the browser back to {redirectUri}. That address points at the computer running this browser, not at the OmniRoute server \u2014 so the authorization code never reaches OmniRoute and the provider fails the sign-in without showing an error.", "loopbackMismatchHowToFix": "How to fix it", "loopbackMismatchStep1": "On this computer, open a terminal and start an SSH tunnel to the OmniRoute server:", - "loopbackMismatchStep1Note": "Replace {userPlaceholder} with your SSH username. Keep this terminal open until the connection shows as active — both ports are needed: one serves the dashboard, the other receives the callback.", + "loopbackMismatchStep1Note": "Replace {userPlaceholder} with your SSH username. Keep this terminal open until the connection shows as active \u2014 both ports are needed: one serves the dashboard, the other receives the callback.", "loopbackMismatchStep2": "In this browser, reopen OmniRoute through the tunnel:", "loopbackMismatchStep3": "Then connect {providerName} again from the new tab. The callback now reaches the server and the login completes normally.", - "loopbackMismatchAlternative": "No SSH access? If this provider offers a token import tab, connect with a token instead — that path doesn't use a loopback callback.", + "loopbackMismatchAlternative": "No SSH access? If this provider offers a token import tab, connect with a token instead \u2014 that path doesn't use a loopback callback.", "step1OpenUrl": "Step 1: Open this URL in your browser", "copy": "Copy", "step2PasteCallback": "Step 2: Paste callback URL or authorization code here", @@ -10671,7 +10671,7 @@ "tabBrowserLogin": "Browser Login", "tabImportAuthJson": "Import auth.json", "tabPasteApiKey": "Paste API Key", - "windsurfPasteDescription": "In the Windsurf / VS Code IDE, run the \"Windsurf: Provide Auth Token\" command from the command palette (or click the Jupyter \"Get Windsurf Authentication Token\" button), then copy the shown token and paste it below. Opening windsurf.com/show-auth-token directly only shows a \"Redirecting\" page — the IDE must initiate the flow.", + "windsurfPasteDescription": "In the Windsurf / VS Code IDE, run the \"Windsurf: Provide Auth Token\" command from the command palette (or click the Jupyter \"Get Windsurf Authentication Token\" button), then copy the shown token and paste it below. Opening windsurf.com/show-auth-token directly only shows a \"Redirecting\" page \u2014 the IDE must initiate the flow.", "grokAuthJsonDescription": "Paste the FULL contents of ~/.grok/auth.json (not just the JWT \"key\" field). A bare JWT has no refresh_token, so the connection dies after expiry (#7610). Prefer the dedicated Import auth.json modal when available.", "devinDesktopPasteDescription": "Paste an existing Devin API key from an authenticated Devin session. Key export availability and steps vary by Devin version and account.", "devinPasteDescription": "Provide your WINDSURF_API_KEY (obtained via `devin auth login`, or via the Windsurf IDE \"Windsurf: Provide Auth Token\" command).", @@ -10679,7 +10679,7 @@ "grokAuthJsonLabel": "Grok Build auth.json", "apiTokenPlaceholder": "ws-...", "apiKeyTokenLabel": "API Key / Token", - "saving": "Saving…", + "saving": "Saving\u2026", "saveConnection": "Save Connection", "gheDescription": "Enter the base URL of your GitHub Enterprise instance (e.g. https://ghe.yourcompany.com).", "gheUrlPlaceholder": "https://ghe.yourcompany.com", @@ -10694,10 +10694,10 @@ "title": "Connect Cursor", "tabLogin": "Login with Cursor", "tabImport": "Import token", - "loginDescription": "Opens Cursor's browser login. Works in Docker — approve in your host browser, then return here.", + "loginDescription": "Opens Cursor's browser login. Works in Docker \u2014 approve in your host browser, then return here.", "loginWithCursor": "Login with Cursor", - "startingLogin": "Starting…", - "waitingApproval": "Waiting for Cursor login approval…", + "startingLogin": "Starting\u2026", + "waitingApproval": "Waiting for Cursor login approval\u2026", "openUrlHint": "If the browser did not open:", "openLoginLink": "Open login page", "cancelLogin": "Cancel login", @@ -10708,7 +10708,7 @@ "dockerImportHint": "Running in Docker? Prefer Login with Cursor. IDE auto-import and cursor-agent are usually unavailable inside the container. See docs/providers/CURSOR-DOCKER.md.", "accessToken": "Access Token", "refreshToken": "Refresh Token", - "refreshTokenPlaceholder": "Optional — enables automatic token refresh", + "refreshTokenPlaceholder": "Optional \u2014 enables automatic token refresh", "required": "*", "accessTokenPlaceholder": "Access token will auto-populate...", "machineId": "Machine ID", @@ -10723,7 +10723,7 @@ "errorImportFailed": "Import failed", "errorLoginStart": "Failed to start Cursor login", "errorLoginPoll": "Cursor login failed", - "errorLoginTimeout": "Cursor login timed out — try again" + "errorLoginTimeout": "Cursor login timed out \u2014 try again" }, "pricingModal": { "title": "Pricing Configuration", @@ -10811,26 +10811,26 @@ "errorForceDeleteConfirm": "This proxy is still assigned. Force delete and remove all assignments?", "errorMigrateFailed": "Failed to migrate legacy proxy config", "errorBulkFailed": "Failed to execute bulk assignment", - "success": "✓", - "failure": "✗", + "success": "\u2713", + "failure": "\u2717", "failed": "Failed", "successRate": "{rate}% success", "avgLatency": "{latency}ms average", "assignmentsCount": "{count} assignments", - "noData": "—", - "testSuccess": "✓ {ip}", + "noData": "\u2014", + "testSuccess": "\u2713 {ip}", "testLatency": "{latency}ms", - "testFailure": "✗ {error}", + "testFailure": "\u2717 {error}", "repair": "Repair", "relayAuthMissing": "auth missing", "relayRepairTooltip": "Recover relay auth in place. If the token is unrecoverable (e.g. after a STORAGE_ENCRYPTION_KEY rotation), redeploy the relay instead.", - "relayRepairRedeployRequired": "Relay auth is unrecoverable — redeploy the relay to write a fresh token.", + "relayRepairRedeployRequired": "Relay auth is unrecoverable \u2014 redeploy the relay to write a fresh token.", "relayRepairFailed": "Relay repair failed", "relayRepairError": "repair failed", "relayProbeSummary": "Relay probes: {alive}/{tested} alive", "bulkImport": "Bulk Import", "bulkImportTitle": "Bulk Import Proxies", - "bulkImportDescription": "Paste proxy profiles one per line. Supported formats: pipe-delimited (NAME|HOST|PORT|…), shorthand (ip:port, ip:port:user:pass, user:pass@ip:port, user:pass:ip:port, protocol://ip:port, protocol://user:pass@ip:port), or protocol header mode (bare protocol on first line applies to subsequent shorthand lines). Existing proxies (same host + port) will be updated.", + "bulkImportDescription": "Paste proxy profiles one per line. Supported formats: pipe-delimited (NAME|HOST|PORT|\u2026), shorthand (ip:port, ip:port:user:pass, user:pass@ip:port, user:pass:ip:port, protocol://ip:port, protocol://user:pass@ip:port), or protocol header mode (bare protocol on first line applies to subsequent shorthand lines). Existing proxies (same host + port) will be updated.", "bulkImportParse": "Parse", "bulkImportImport": "Import {count} Proxies", "bulkImportImporting": "Importing...", @@ -10849,7 +10849,7 @@ "batchSelectedCount": "{count} selected", "batchDeleteSelected": "Delete {count} selected", "batchActivateSelected": "Enable {count} selected", - "testPassed": "✓ OK", + "testPassed": "\u2713 OK", "close": "Close", "managePool": "Manage Pool", "poolTitle": "Proxy Pool & Rotation", @@ -10873,7 +10873,7 @@ "poolAddLabel": "Add a proxy", "poolAddMember": "Add", "poolAddFailed": "Failed to add the proxy to the pool", - "poolSelectProxy": "Select a proxy…", + "poolSelectProxy": "Select a proxy\u2026", "poolSaveFailed": "Failed to save pool assignment" }, "playground": { @@ -10891,7 +10891,7 @@ "attachImages": "Attach Images (Vision)", "multipartFormData": "multipart/form-data", "upToImages": "Up to 4 images", - "selectAudioFile": "Select audio file for transcription (mp3, wav, m4a, ogg, flac…)", + "selectAudioFile": "Select audio file for transcription (mp3, wav, m4a, ogg, flac\u2026)", "clearAll": "Clear All", "request": "Request", "response": "Response", @@ -10900,7 +10900,7 @@ "resetToDefault": "Reset to default", "downloadAudio": "Download audio", "copyText": "Copy text", - "transcriptionHint": "Transcription uses multipart/form-data. Upload the audio file above — the JSON below controls extra parameters (model, language).", + "transcriptionHint": "Transcription uses multipart/form-data. Upload the audio file above \u2014 the JSON below controls extra parameters (model, language).", "imagesGenerated": "Generated {count} images", "generatedImage": "Generated image {index}", "save": "Save", @@ -10949,11 +10949,11 @@ "savePreset": "Save preset", "noPresets": "No presets", "presetNamePlaceholder": "Preset name", - "savingPreset": "Saving…", + "savingPreset": "Saving\u2026", "nameRequired": "Name is required", "failedToSavePreset": "Failed to save preset", "improvePrompt": "Improve prompt", - "improvingPrompt": "Improving…", + "improvingPrompt": "Improving\u2026", "improvePromptTitle": "Improve your prompt with AI", "setModelFirst": "Set a model first", "setModelInConfigFirst": "Set a model in the Config pane first.", @@ -10969,19 +10969,19 @@ "noStateToExport": "No playground state available to export.", "closeExportModal": "Close export modal", "close": "Close", - "exportRealKeyWarning": "Security warning: export blocked — a real API key was detected in the output. Please reset your API key and try again.", + "exportRealKeyWarning": "Security warning: export blocked \u2014 a real API key was detected in the output. Please reset your API key and try again.", "placeholderHintPrefix": "Replace", "placeholderHintSuffix": "with your actual API key, or set it as an environment variable.", "copyLangCode": "Copy {language} code", - "loadingPresets": "Loading…", - "loadPresetPlaceholder": "Load preset…", + "loadingPresets": "Loading\u2026", + "loadPresetPlaceholder": "Load preset\u2026", "copyCode": "Copy", "copiedCode": "Copied!", "addModel": "+ Add model", "runAll": "Run all", "cancelAll": "Cancel all", "maxColumnsReached": "Maximum {max} columns", - "modelPlaceholderCompare": "Model (Cmd+K)…", + "modelPlaceholderCompare": "Model (Cmd+K)\u2026", "noModel": "No model", "statusLabel": "Status: {status}", "status": { @@ -10996,7 +10996,7 @@ "readyToRun": "Ready to run.", "errorLabel": "Error", "unknownError": "Unknown error occurred.", - "waitingForResponse": "Waiting for response…", + "waitingForResponse": "Waiting for response\u2026", "ttft": "TTFT", "tps": "TPS", "metricsDisclaimer": "client-side estimate", @@ -11005,7 +11005,7 @@ "costEstimated": "(estimated)", "ttftTitle": "Time to first token (client-side estimate)", "tpsTitle": "Tokens per second (client-side estimate)", - "tokenCountsTitle": "Prompt tokens ↑ / Completion tokens ↓", + "tokenCountsTitle": "Prompt tokens \u2191 / Completion tokens \u2193", "estimatedCostTitle": "Estimated cost (not guaranteed accurate)", "toolsLabel": "Tools", "toolsCount": "Tools ({count})", @@ -11038,17 +11038,17 @@ "networkError": "Network error", "regenerateLastResponse": "Regenerate last response", "regenerate": "Regenerate", - "startConversation": "Start a conversation — type a message below", + "startConversation": "Start a conversation \u2014 type a message below", "role": { "system": "system", "user": "user", "assistant": "assistant" }, - "generating": "Generating…", + "generating": "Generating\u2026", "typeMessageWithShortcut": "Type a message... (Enter to send, Shift+Enter for newline)", "stop": "Stop", "noResponseBody": "No response body", - "comparePromptPlaceholder": "Enter your prompt here…", + "comparePromptPlaceholder": "Enter your prompt here\u2026", "userPrompt": "User prompt", "cancelAllStreams": "Cancel all streams", "runAllColumns": "Run all columns", @@ -11058,9 +11058,9 @@ "columnCount": "{count}/{max} columns", "addModelToCompare": "Add a model column to compare", "modelsSimultaneously": "Up to {max} models simultaneously", - "running": "Running…", + "running": "Running\u2026", "runLabel": "Run", - "enterToolResult": "Enter tool result…", + "enterToolResult": "Enter tool result\u2026", "build": { "step1Label": "What to test?", "step2Label": "Configure", @@ -11071,15 +11071,15 @@ "step2Subtitle": "Set up the tools or JSON schema that will be used in the request.", "step3Title": "Run", "modeToolsTitle": "Tools", - "modeToolsDesc": "Test function calling — define tools and see how the model invokes them.", + "modeToolsDesc": "Test function calling \u2014 define tools and see how the model invokes them.", "modeJsonTitle": "JSON", - "modeJsonDesc": "Test structured output — constrain the response to a JSON schema.", + "modeJsonDesc": "Test structured output \u2014 constrain the response to a JSON schema.", "modeBothTitle": "Tools + JSON", "modeBothDesc": "Combine function calling and structured output in a single request.", "backButton": "Back", "nextButton": "Next", "runButton": "Run", - "promptPlaceholder": "Enter your message… (Enter to send, Shift+Enter for newline)" + "promptPlaceholder": "Enter your message\u2026 (Enter to send, Shift+Enter for newline)" } }, "miniPlayground": { @@ -11120,7 +11120,7 @@ "selectAudioFirst": "Select an audio file first.", "speechToText": "Speech to Text", "chooseFile": "Choose file", - "audioFormats25Mb": "MP3, WAV, M4A, OGG or FLAC · Maximum 25 MB", + "audioFormats25Mb": "MP3, WAV, M4A, OGG or FLAC \u00b7 Maximum 25 MB", "musicSample": "A calm ambient track with warm piano and soft strings", "noAudioUrl": "The response did not include an audio URL: {response}", "music": "Music", @@ -11142,11 +11142,11 @@ "noKeysFound": "No API keys found. Add one in the Keys section.", "exampleLabel": "Example", "latency": "{ms}ms", - "statsLine": "{ms}ms · {tokensIn} in / {tokensOut} out tokens", + "statsLine": "{ms}ms \u00b7 {tokensIn} in / {tokensOut} out tokens", "defaultKey": "(default)", "clear": "Clear", "emptyConversation": "Send a message to start the conversation", - "sendHint": "Shift+Enter for newline · Enter to send", + "sendHint": "Shift+Enter for newline \u00b7 Enter to send", "you": "You", "assistant": "Assistant", "errorLabel": "Error", @@ -11174,12 +11174,12 @@ "shown": "Shown", "sortNewest": "Newest", "sortOldest": "Oldest", - "sortTokensDesc": "Tokens ↓", - "sortTokensAsc": "Tokens ↑", - "sortDurationDesc": "Duration ↓", - "sortDurationAsc": "Duration ↑", - "sortStatusDesc": "Status ↓", - "sortStatusAsc": "Status ↑", + "sortTokensDesc": "Tokens \u2193", + "sortTokensAsc": "Tokens \u2191", + "sortDurationDesc": "Duration \u2193", + "sortDurationAsc": "Duration \u2191", + "sortStatusDesc": "Status \u2193", + "sortStatusAsc": "Status \u2191", "sortModelAsc": "Model A-Z", "sortModelDesc": "Model Z-A", "sortLogs": "Sort logs", @@ -11285,7 +11285,7 @@ "totalIn": "Total In: {value}", "cacheRead": "Cache Read: {value}", "cacheWrite": "Cache Write: {value}", - "compressed": "Compressed: {from} → {to} ({percent}% saved)", + "compressed": "Compressed: {from} \u2192 {to} ({percent}% saved)", "output": "Output", "totalOut": "Total Out: {value}", "reasoning": "Reasoning: {value}", @@ -11293,7 +11293,7 @@ "requestProtocol": "Req Protocol", "cacheSource": "Cache Source", "modelPinning": "Model Pinning", - "activeModelPinning": "Active — model selected via session pinning", + "activeModelPinning": "Active \u2014 model selected via session pinning", "rotated": "Rotated: {initial} -> {final}", "apiKey": "API Key", "noApiKey": "No API key", @@ -11345,8 +11345,8 @@ "direct": "direct", "newest": "Newest", "oldest": "Oldest", - "latencyDesc": "Latency ↓", - "latencyAsc": "Latency ↑", + "latencyDesc": "Latency \u2193", + "latencyAsc": "Latency \u2191", "refresh": "Refresh", "columns": "Columns", "loadingProxyLogs": "Loading proxy logs...", @@ -11377,7 +11377,7 @@ }, "runtime": { "title": "Runtime", - "description": "Realtime observability — 3-layer resilience + sessions + quota alerts", + "description": "Realtime observability \u2014 3-layer resilience + sessions + quota alerts", "pause": "Pause", "resume": "Resume", "refreshNow": "Refresh now", @@ -11453,13 +11453,13 @@ "kpiProvidersWithQuota": "Providers w/ quota", "emptyTitle": "No pools configured", "emptyDescription": "Create a pool to assign API keys to share a provider's quota window with % allocations.", - "loading": "Loading…", + "loading": "Loading\u2026", "removePool": "Remove pool", "removeConfirm": "Remove this pool?", "pool": "Pool", "used": "used", "allocationsCount": "Allocations ({count})", - "allocatedFree": "{allocated}% allocated · {free}% free", + "allocatedFree": "{allocated}% allocated \u00b7 {free}% free", "noAllocations": "No keys allocated yet", "capLabel": "cap {value}", "notTrackedYet": "(not tracked yet)", @@ -11476,10 +11476,10 @@ "editAllocations": "Edit allocations", "newPoolTitle": "New quota pool", "providerConnection": "Provider connection (account)", - "selectConnection": "Select a connection…", + "selectConnection": "Select a connection\u2026", "noEligibleConnections": "No connections with quota data. Refresh from /dashboard/quota first.", "quotaWindow": "Quota window", - "selectWindow": "Select a window…", + "selectWindow": "Select a window\u2026", "alreadyUsedSuffix": "(already used)", "windowReset": "Reset", "duplicatePoolError": "A pool for this connection + window already exists", @@ -11488,11 +11488,11 @@ "editTitle": "Edit allocations", "noKeysAdded": "No keys allocated. Add one below.", "totalLabel": "Total: {percent}%", - "totalExceeded": "⚠ exceeds 100%", - "addKey": "+ Add key…", + "totalExceeded": "\u26a0 exceeds 100%", + "addKey": "+ Add key\u2026", "equalSplit": "Equal split", "save": "Save allocations", - "betaPreviewLabel": "Beta — UI preview.", + "betaPreviewLabel": "Beta \u2014 UI preview.", "betaConfigSavedPrefix": "Configuration is saved in", "betaConfigSavedSuffix": "(not yet persisted on the server). Per-request cap enforcement is not yet wired into the proxy pipeline. This screen lets you design and visualize the quota split; real enforcement will land in a future iteration with DB persistence and upstream call interception.", "policyLabel": "Policy:", @@ -11505,11 +11505,11 @@ "conceptFairShare": "Fair-share: each key receives quota proportional to its configured weight", "conceptBorrowing": "Borrowing: keys can consume free balance from others without breaching the cap", "conceptGlobalCap": "Hard global cap: the provider's absolute limit is never exceeded", - "conceptWindows": "Windows: 5h, hourly, daily, weekly, monthly — each tracked independently", + "conceptWindows": "Windows: 5h, hourly, daily, weekly, monthly \u2014 each tracked independently", "conceptKeyHowTitle": "Enabling a key for quota", - "conceptKeyHowDesc": "Create the key normally in API Keys — it appears in the wizard's key step automatically. Tick Exclusive there to make it quota-only. No separate enable step.", + "conceptKeyHowDesc": "Create the key normally in API Keys \u2014 it appears in the wizard's key step automatically. Tick Exclusive there to make it quota-only. No separate enable step.", "conceptExclusiveTitle": "What \"Exclusive\" does", - "conceptExclusiveDesc": "An exclusive key only sees/uses the pool's quotaShared-* models — every other model is hidden from its /v1/models and blocked.", + "conceptExclusiveDesc": "An exclusive key only sees/uses the pool's quotaShared-* models \u2014 every other model is hidden from its /v1/models and blocked.", "burnRateTitle": "Burn rate", "burnRateExhaustsIn": "Exhausts in", "dimensionResetIn": "Resets in", @@ -11539,7 +11539,7 @@ "wizardNext": "Next", "wizardBack": "Back", "wizardCreatePool": "Create pool", - "wizardDimensionsEditedNotice": "Dimensions edited — will be saved as a manual override when the pool is created.", + "wizardDimensionsEditedNotice": "Dimensions edited \u2014 will be saved as a manual override when the pool is created.", "wizardExclusiveLabel": "Exclusive quota", "wizardExclusiveHint": "When enabled, these API keys will only be allowed to use this pool's virtual models (allowedQuotas reconciliation is applied on save).", "wizardPreviewLabel": "Virtual model names preview", @@ -11549,7 +11549,7 @@ "wizardSingleProviderNote": "A pool uses a single provider", "wizardPreviewMoreModels": "+{count} more", "accountQuotaTitle": "Account quota", - "accountQuotaNone": "—", + "accountQuotaNone": "\u2014", "logTitle": "Usage log", "logEmpty": "No usage yet", "groupLabel": "Group", @@ -11561,20 +11561,20 @@ "wizardGroupLabel": "Group", "allGroups": "All groups", "endpointsTitle": "Available endpoints", - "endpointsHint": "Call these virtual models with any allocated key — routing + quota are handled per group.", + "endpointsHint": "Call these virtual models with any allocated key \u2014 routing + quota are handled per group.", "previewForKey": "Preview for key", "previewKeyNone": "(all endpoints)", "endpointsBaseUrl": "Base URL", "endpointsCollapse": "Collapse", "endpointsExpand": "Expand", "endpointsAnthropicNote": "Anthropic-native", - "endpointsResponsesNote": "OpenAI Responses — codex/github", - "endpointsWsNote": "WebSocket — codex only", + "endpointsResponsesNote": "OpenAI Responses \u2014 codex/github", + "endpointsWsNote": "WebSocket \u2014 codex only", "betaText": "Quota Share is functional but bugs are expected. Found one? Please report it.", "betaReportLink": "Report an issue", "deleteGroup": "Delete group", "deleteGroupConfirm": "Delete this group? Its pools must be reassigned or removed first.", - "deleteGroupHasPools": "This group still has pools — reassign or delete them first.", + "deleteGroupHasPools": "This group still has pools \u2014 reassign or delete them first.", "ungroupedTitle": "Ungrouped", "ungroupedHint": "These pools are not assigned to a known group. Edit a pool to move it into a real group.", "removeFailed": "Could not remove this pool.", @@ -11629,11 +11629,11 @@ }, "quotaPlans": { "title": "Plans & Quotas", - "description": "Configure quota plans per provider — dimensions (%, requests, tokens, $) and time windows", + "description": "Configure quota plans per provider \u2014 dimensions (%, requests, tokens, $) and time windows", "providerLabel": "Provider / Connection", "detectedPlanLabel": "Detected plan", "manualPlanLabel": "Manual override", - "unconfiguredLabel": "Not configured — manual required", + "unconfiguredLabel": "Not configured \u2014 manual required", "dimensionLabel": "Dimensions", "addDimension": "Add dimension", "removeDimension": "Remove", @@ -11677,7 +11677,7 @@ "refresh": "Refresh", "refreshAria": "Refresh activity feed", "loading": "Loading", - "loadingActivity": "Loading activity…", + "loadingActivity": "Loading activity\u2026", "fetchFailed": "Failed to fetch activity", "relative": { "justNow": "just now", @@ -11740,7 +11740,7 @@ }, "agentBridge": { "title": "AgentBridge", - "subtitle": "Use IDE agents with OmniRoute models — no config required", + "subtitle": "Use IDE agents with OmniRoute models \u2014 no config required", "riskBannerTitle": "Use at your own risk", "riskBannerBody": "AgentBridge intercepts HTTPS traffic from IDE agents. By activating it you accept responsibility for compliance with the terms of service of each agent. Never use on devices or networks where TLS inspection is prohibited.", "riskBannerDismiss": "Dismiss", @@ -11760,18 +11760,18 @@ "restartServer": "Restart", "trustCert": "Trust Cert", "downloadCert": "Download Cert", - "certManualTitle": "Certificate couldn't be installed automatically (e.g. inside a container). The bridge can still run — trust the CA manually:", + "certManualTitle": "Certificate couldn't be installed automatically (e.g. inside a container). The bridge can still run \u2014 trust the CA manually:", "regenerateCert": "Regenerate Cert", - "starting": "Starting…", - "stopping": "Stopping…", - "restarting": "Restarting…", - "trusting": "Trusting…", - "regenerating": "Regenerating…", + "starting": "Starting\u2026", + "stopping": "Stopping\u2026", + "restarting": "Restarting\u2026", + "trusting": "Trusting\u2026", + "regenerating": "Regenerating\u2026", "upstreamCaLabel": "Upstream CA Certificate (corporate)", "upstreamCaPlaceholder": "/etc/ssl/certs/corp-ca.pem", "upstreamCaTest": "Test TLS", "upstreamCaTestOk": "TLS test passed", - "upstreamCaTestError": "TLS test failed — check the path and CA file", + "upstreamCaTestError": "TLS test failed \u2014 check the path and CA file", "bypassSectionTitle": "Bypass List", "bypassSectionDesc": "Hosts matching these patterns are tunneled directly (no TLS decryption). Defaults include banks.gov, and corporate SSO.", "bypassDefaultsLabel": "Default bypass patterns (read-only)", @@ -11782,7 +11782,7 @@ "filterActive": "Active", "filterSetupRequired": "Setup required", "filterInvestigating": "Investigating", - "searchAgents": "Search agents…", + "searchAgents": "Search agents\u2026", "noAgentsMatch": "No agents match the current filter", "agentHosts": "Intercepted hosts", "certTrusted": "Certificate trusted", @@ -11794,12 +11794,12 @@ "noMappings": "No model mappings configured. Run setup wizard to auto-detect models.", "noMappingsDesc": "No model mappings configured yet. Add mappings to route agent requests through OmniRoute.", "addMapping": "Add mapping", - "selectModel": "Select…", + "selectModel": "Select\u2026", "saveMappings": "Save mappings", "setupWizard": "Setup wizard", "startDns": "Start DNS", "stopDns": "Stop DNS", - "toggling": "Toggling…", + "toggling": "Toggling\u2026", "viewTraffic": "View traffic", "emptyNoProvidersTitle": "No providers configured yet", "emptyNoProvidersBody": "To use AgentBridge, first connect at least one provider. It will be the destination where IDE requests are routed.", @@ -11818,13 +11818,13 @@ "wizardNotRunning": "not running", "wizardCertCheck": "Certificate", "wizardTrusted": "trusted", - "wizardNotTrusted": "not yet trusted — use Trust Cert button", + "wizardNotTrusted": "not yet trusted \u2014 use Trust Cert button", "wizardTutorialTitle": "Setup instructions:", "wizardEnableDns": "Add /etc/hosts entries", "wizardDnsAlreadyEnabled": "DNS already enabled for this agent", - "enablingDns": "Enabling…", + "enablingDns": "Enabling\u2026", "modelSelectorTitle": "Select target model", - "modelSelectorSearch": "Search models…", + "modelSelectorSearch": "Search models\u2026", "noModelsFound": "No models found", "quickLinks": "Quick links", "quickLinkProviders": "Configure providers", @@ -11834,31 +11834,31 @@ "maintenanceSubtitle": "Self-test the capture pipeline, undo leftover system state, and move your setup between machines.", "orphanedStateWarning": "A previous session left system state behind (DNS spoof, CA, or system proxy). Run Repair to clean it up.", "diagnose": "Diagnose", - "diagnosing": "Diagnosing…", + "diagnosing": "Diagnosing\u2026", "diagnoseHealthy": "Capture pipeline is healthy.", "diagnoseUnhealthy": "Capture pipeline has problems:", "repair": "Repair", - "repairing": "Repairing…", + "repairing": "Repairing\u2026", "repairDone": "Repaired: {items}", - "repairNothing": "Nothing to repair — system state is clean.", + "repairNothing": "Nothing to repair \u2014 system state is clean.", "removeCa": "Remove CA", "removeCaConfirm": "Remove CA?", "removeCaDone": "MITM root CA removed from the OS trust store.", - "removing": "Removing…", + "removing": "Removing\u2026", "confirm": "Confirm", "exportConfig": "Export config", - "exporting": "Exporting…", + "exporting": "Exporting\u2026", "importConfig": "Import config", - "importing": "Importing…", + "importing": "Importing\u2026", "importInvalidJson": "The selected file is not valid JSON.", - "importDone": "Imported {bypass} bypass · {hosts} hosts · {agents} agents", + "importDone": "Imported {bypass} bypass \u00b7 {hosts} hosts \u00b7 {agents} agents", "save": "Save", - "saving": "Saving…", + "saving": "Saving\u2026", "cancel": "Cancel", "back": "Back", "next": "Next", "done": "Done", - "loading": "Loading…", + "loading": "Loading\u2026", "riskNoticeTitle": "Risk acknowledgment", "riskNoticeBody": "AgentBridge will intercept HTTPS traffic from this agent by redirecting its API hosts via DNS. Only enable if you accept responsibility for compliance with the agent's terms of service and any applicable network policies.", "pageMoved": { @@ -11915,7 +11915,7 @@ "maxPerMinute": "Max Requests/Minute", "maxPerDay": "Max Requests/Day", "createButton": "Create Token", - "createdTitle": "Token Created — Copy it now!", + "createdTitle": "Token Created \u2014 Copy it now!", "tokenFor": "Token for {name}:", "shownOnce": "This token will not be shown again. Store it securely.", "dismiss": "Dismiss", @@ -11946,7 +11946,7 @@ "profileLlmOnly": "LLM only", "profileCustom": "Custom", "profileAll": "All", - "filterHost": "Filter host…", + "filterHost": "Filter host\u2026", "filterAgent": "Agent", "filterStatus": "Status", "pauseBtn": "Pause", @@ -11972,7 +11972,7 @@ "rawEvents": "Raw events", "mergedView": "Merged view", "noBody": "No body.", - "streaming": "streaming…", + "streaming": "streaming\u2026", "manageHosts": "Manage hosts", "copySnippet": "Copy proxy snippet", "addHost": "Add", @@ -11981,7 +11981,7 @@ "noSessionsYet": "No sessions yet", "allTraffic": "All traffic (no session)", "sessionsDropdown": "Sessions", - "annotationPlaceholder": "Add a note…", + "annotationPlaceholder": "Add a note\u2026", "contextFingerprint": "Context fingerprint", "llmProvider": "Detected provider", "llmApiKind": "API kind", @@ -11990,12 +11990,12 @@ "llmStream": "Stream", "llmMappedTo": "Mapped to", "llmCostEstimate": "Cost estimate", - "systemProxyExitWarning": "System-wide proxy still active — leave page anyway?", + "systemProxyExitWarning": "System-wide proxy still active \u2014 leave page anyway?", "customHostsTitle": "Custom Hosts", - "loading": "Loading…", + "loading": "Loading\u2026", "copied": "Copied!", "copy": "Copy", - "httpProxyTitle": "HTTP Proxy Snippet — port {port}", + "httpProxyTitle": "HTTP Proxy Snippet \u2014 port {port}", "notRecording": "Not recording", "anyStatus": "Any status", "liveOnly": "Live", @@ -12006,7 +12006,7 @@ "modelResponse": "Model Response", "conversationNoMessages": "No messages found in this request.", "conversationNotAvailable": "Conversation data not available. This may not be an LLM request or the body could not be parsed.", - "loadingCharts": "Loading charts…", + "loadingCharts": "Loading charts\u2026", "statsErrors": "Errors", "statsLatency": "Latency (last 50 requests)", "statsNoData": "No requests yet. Start a session recording to capture data for stats.", @@ -12041,12 +12041,12 @@ "roleTool": "Tool", "expand": "Expand", "collapse": "Collapse", - "systemPromptHidden": "System prompt hidden — click to expand", + "systemPromptHidden": "System prompt hidden \u2014 click to expand", "sessionName": "Session {id}", "sessions": "Sessions", "requestCountShort": "{count} reqs", "deleteSession": "Delete session", - "saving": "Saving…", + "saving": "Saving\u2026", "sensitiveHeaders": "Sensitive headers", "show": "Show", "hide": "Hide", @@ -12063,43 +12063,43 @@ "code": { "title": "CLI Code", "phrase": "Code tools you point at OmniRoute", - "flow": "You → CLI Code → OmniRoute → Provider", - "seeOther": "See →" + "flow": "You \u2192 CLI Code \u2192 OmniRoute \u2192 Provider", + "seeOther": "See \u2192" }, "agent": { "title": "CLI Agents", "phrase": "Broad-purpose autonomous CLI agents you point at OmniRoute", - "flow": "You → CLI Agent → OmniRoute → Provider", - "seeOther": "See →" + "flow": "You \u2192 CLI Agent \u2192 OmniRoute \u2192 Provider", + "seeOther": "See \u2192" }, "acp": { "title": "ACP Agents", "phrase": "Run complex, long-running logic in the backend", - "warning": "Most users can ignore this — use only when an integration requires it.", - "flow": "Client → OmniRoute → spawn CLI (stdio/ACP) → response", - "seeOther": "See →" + "warning": "Most users can ignore this \u2014 use only when an integration requires it.", + "flow": "Client \u2192 OmniRoute \u2192 spawn CLI (stdio/ACP) \u2192 response", + "seeOther": "See \u2192" } }, "comparison": { "title": "Understand the 3 CLI types in OmniRoute", "thisPage": "This page", - "open": "Open →", + "open": "Open \u2192", "code": { "title": "Code tool", "desc": "Points to Omni", - "flow": "you → CLI → Omni → provider", + "flow": "you \u2192 CLI \u2192 Omni \u2192 provider", "examples": "e.g.: claude, codex" }, "agent": { "title": "Broad autonomous agent", "desc": "Points to Omni", - "flow": "you → agent → Omni", + "flow": "you \u2192 agent \u2192 Omni", "examples": "e.g.: hermes, goose" }, "acp": { "title": "CLI used as backend by Omni", "desc": "Reverse execution", - "flow": "Omni → spawn CLI → resp", + "flow": "Omni \u2192 spawn CLI \u2192 resp", "examples": "e.g.: claude, codex (ACP)" } }, @@ -12108,8 +12108,8 @@ "notDetected": "Not detected", "configured": "Configured", "notConfigured": "Not configured", - "configure": "Configure →", - "howToInstall": "How to install →", + "configure": "Configure \u2192", + "howToInstall": "How to install \u2192", "versionNotFound": "not found", "manualConfig": "Manual config", "installGuide": "Install guide", @@ -12134,13 +12134,13 @@ "modelMappingLabel": "Model mapping", "noActiveProviders": "No active providers.", "noActiveProvidersDesc": "Go to Providers to connect at least 1 provider before configuring the CLI.", - "openProviders": "Open Providers →" + "openProviders": "Open Providers \u2192" } }, "cliCode": { "pageTitle": "CLI Code's", "pageSubtitle": "Code tools pointing to OmniRoute", - "searchPlaceholder": "Search CLI…", + "searchPlaceholder": "Search CLI\u2026", "filterDetectionLabel": "Detection", "filterBaseUrlLabel": "Base URL", "detectionAll": "All", @@ -12154,7 +12154,7 @@ "pageTitle": "CLI Agents", "pageSubtitle": "Broad-purpose autonomous CLI agents", "refreshDetection": "Refresh detection", - "searchPlaceholder": "Search agent…", + "searchPlaceholder": "Search agent\u2026", "detectionFilterLabel": "Detection", "detectionAll": "All", "detectionInstalled": "Installed", @@ -12165,7 +12165,7 @@ "acpAgents": { "pageTitle": "ACP Agents", "pageSubtitle": "CLIs that OmniRoute spawns as execution backend", - "scanning": "Detecting agents…", + "scanning": "Detecting agents\u2026", "refresh": "Refresh", "setupGuideTitle": "Setup guide", "setupGuideDetectCliTitle": "Detect CLI", @@ -12175,7 +12175,7 @@ "setupGuideCommandMissingTitle": "Command not found", "setupGuideCommandMissingDesc": "Check that the binary is in PATH.", "fingerprintSettingsHint": "Configure routing and fingerprints in", - "settingsRoutingLink": "Settings → Routing", + "settingsRoutingLink": "Settings \u2192 Routing", "installed": "Installed", "notFound": "Not found", "builtIn": "Built-in", @@ -12239,7 +12239,7 @@ }, "omni-compression": { "name": "Compression", - "description": "Configure RTK (command output), Caveman (prose), and stacked compression modes. Manage language packs, custom rules, and test prompt compression reducing tokens by 60–90%." + "description": "Configure RTK (command output), Caveman (prose), and stacked compression modes. Manage language packs, custom rules, and test prompt compression reducing tokens by 60\u201390%." }, "omni-context-rtk": { "name": "Context & RTK", @@ -12267,7 +12267,7 @@ }, "omni-webhooks": { "name": "Webhooks", - "description": "Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, request.failed, quota.exceeded, etc.) and manage delivery retries." + "description": "Omni webhooks catalog skill description" }, "omni-mcp": { "name": "MCP Server", @@ -12379,17 +12379,17 @@ } }, "pageTitle": "Agent Skills", - "pageSubtitle": "Teach your agent to operate OmniRoute — 23 API areas + 21 CLI families", + "pageSubtitle": "Teach your agent to operate OmniRoute \u2014 23 API areas + 21 CLI families", "conceptCard": { "agent": { - "title": "Agent Skills — Outbound", - "description": "Agent Skills are machine-readable SKILL.md documents that external AI agents (Claude Code, Cursor, Copilot…) fetch from GitHub to learn how to operate OmniRoute via REST or CLI. They are read by the agent, not executed by OmniRoute.", - "crossLinkLabel": "Understand the difference →" + "title": "Agent Skills \u2014 Outbound", + "description": "Agent Skills are machine-readable SKILL.md documents that external AI agents (Claude Code, Cursor, Copilot\u2026) fetch from GitHub to learn how to operate OmniRoute via REST or CLI. They are read by the agent, not executed by OmniRoute.", + "crossLinkLabel": "Understand the difference \u2192" }, "omni": { - "title": "Omni Skills — Inbound", + "title": "Omni Skills \u2014 Inbound", "description": "Omni Skills are sandbox tools that OmniRoute injects into the model's context on every request. They are executed by OmniRoute, not read by the agent.", - "crossLinkLabel": "Understand the difference →" + "crossLinkLabel": "Understand the difference \u2192" }, "comparison": { "colAgent": "Agent Skills", @@ -12401,17 +12401,17 @@ }, "direction": { "label": "Direction", - "agent": "Outbound — external agent learns to control OmniRoute", - "omni": "Inbound — OmniRoute gives tools to models passing through it" + "agent": "Outbound \u2014 external agent learns to control OmniRoute", + "omni": "Inbound \u2014 OmniRoute gives tools to models passing through it" }, "executor": { "label": "Executor", - "agent": "External agent (reads markdown → acts via API/CLI)", + "agent": "External agent (reads markdown \u2192 acts via API/CLI)", "omni": "OmniRoute itself (intercepts tool_calls, Docker sandbox)" }, "storage": { "label": "Storage", - "agent": "Dynamic catalog (/api/agent-skills) → SKILL.md on GitHub", + "agent": "Dynamic catalog (/api/agent-skills) \u2192 SKILL.md on GitHub", "omni": "SQLite (SkillsMP / skills.sh / local)" }, "tagline": { @@ -12424,7 +12424,7 @@ "filters": { "category": "Category", "area": "Area", - "searchPlaceholder": "Search skills…" + "searchPlaceholder": "Search skills\u2026" }, "categoryApi": "API", "categoryCli": "CLI", @@ -12438,7 +12438,7 @@ "refresh": "Refresh", "copyUrl": "Copy URL", "viewOnGithub": "View on GitHub", - "previewLoading": "Loading skill documentation…", + "previewLoading": "Loading skill documentation\u2026", "previewError": "Failed to load skill documentation.", "previewEmpty": "Select a skill to preview its documentation.", "generateButton": "Generate missing skills", @@ -12448,7 +12448,7 @@ }, "noSkillsFound": "No skills found matching your filters.", "regenerateConfirm": "This will regenerate all missing SKILL.md files. Continue?", - "regenerateRunning": "Regenerating skills…", + "regenerateRunning": "Regenerating skills\u2026", "regenerateSuccess": "Skills regenerated successfully.", "regenerateError": "Failed to regenerate skills." }, @@ -12457,7 +12457,7 @@ "subtitle": "Best free providers ranked by model ELO scores from Arena AI leaderboards", "loading": "Loading rankings...", "errorLoading": "Failed to load rankings", - "emptyState": "No rankings available yet. Arena ELO data syncs on startup (daily) — check back shortly, or trigger a manual sync. Disable Arena ELO Sync in Feature Flags or set ARENA_ELO_SYNC_ENABLED=false to opt out.", + "emptyState": "No rankings available yet. Arena ELO data syncs on startup (daily) \u2014 check back shortly, or trigger a manual sync. Disable Arena ELO Sync in Feature Flags or set ARENA_ELO_SYNC_ENABLED=false to opt out.", "bestModel": "Best", "allCategories": "All Categories", "categoryDefault": "Default", @@ -12484,8 +12484,8 @@ "typeOauth": "OAuth Login", "typeApikey": "API Key", "sortTypeFirst": "Easiest first", - "sortTypeFirstHelp": "Group by signup effort (No Signup → OAuth Login → API Key), keeping quality order within each group", - "typeLegend": "No Signup = zero setup · OAuth Login = sign in with your own account · API Key = bring your own key or that provider's free tier" + "sortTypeFirstHelp": "Group by signup effort (No Signup \u2192 OAuth Login \u2192 API Key), keeping quality order within each group", + "typeLegend": "No Signup = zero setup \u00b7 OAuth Login = sign in with your own account \u00b7 API Key = bring your own key or that provider's free tier" }, "discovery": { "title": "Provider Discovery", @@ -12493,7 +12493,7 @@ "scanLabel": "Provider to scan", "scanPlaceholder": "e.g. huggingchat", "scan": "Scan", - "scanning": "Scanning…", + "scanning": "Scanning\u2026", "scanQueued": "Scan complete for {provider}.", "scanFailed": "Scan failed.", "loadFailed": "Failed to load discovery results.", @@ -12514,20 +12514,20 @@ }, "noAuthProvider": { "title": "No authentication required", - "description": "This provider is ready to use immediately — no signup or API key needed.", - "accountDescription": "Ready to use — no signup needed. Add accounts for rate-limit rotation.", + "description": "This provider is ready to use immediately \u2014 no signup or API key needed.", + "accountDescription": "Ready to use \u2014 no signup needed. Add accounts for rate-limit rotation.", "addAccount": "Add Account", "accountName": "{provider} Account {number}", "accounts": "Accounts ({count})", "adding": "Adding...", - "autoGeneratedAccount": "Using an auto-generated account. Select “{addLabel}” for rate-limit rotation.", + "autoGeneratedAccount": "Using an auto-generated account. Select \u201c{addLabel}\u201d for rate-limit rotation.", "configureProxy": "Configure proxy", "proxyConfigured": "Proxy configured: {host}", "removeAccount": "Remove account", "proxyForAccount": "Proxy for Account {number}", "saved": "Saved", "custom": "Custom", - "noSavedProxies": "No saved proxies — add one in Settings → Proxy", + "noSavedProxies": "No saved proxies \u2014 add one in Settings \u2192 Proxy", "directConnection": "Direct (no proxy)", "host": "Host", "port": "Port", @@ -12539,7 +12539,7 @@ "createConnectionFailed": "Failed to create connection", "updateConnectionFailed": "Failed to update connection", "fetchProxiesFailed": "Failed to fetch proxies", - "noSavedProxiesError": "No saved proxies found. Add proxies in Settings → Proxy first.", + "noSavedProxiesError": "No saved proxies found. Add proxies in Settings \u2192 Proxy first.", "updateProviderFailed": "Failed to update provider", "providerEnabled": "{provider} enabled", "providerDisabled": "{provider} disabled", @@ -12583,7 +12583,7 @@ }, "tierLabel": "Tier: {tier}", "dayStreak": "{count}-day streak", - "levelProgress": "Level {current} → {next}", + "levelProgress": "Level {current} \u2192 {next}", "totalXpEarned": "{count} total XP earned", "maintainStreak": "Keep using OmniRoute every day to maintain your streak!", "badgesTitle": "Badges ({earned}/{total})", @@ -12745,7 +12745,7 @@ "danger": "Danger", "requiresRestart": "Requires restart", "source": "Source", - "ccDiscoveryAliasesEnvWarning": "Active via environment variable (EXPOSE_CC_DISCOVERY_ALIASES) — this overrides any dashboard toggle below.", + "ccDiscoveryAliasesEnvWarning": "Active via environment variable (EXPOSE_CC_DISCOVERY_ALIASES) \u2014 this overrides any dashboard toggle below.", "resetFlag": "Reset {label} to default", "reset": "Reset", "loadFailed": "Failed to load feature flags", @@ -12756,12 +12756,12 @@ "resetOverridesFailedHttp": "Failed to reset overrides: HTTP {status}", "resetOverridesFailed": "Failed to reset overrides", "restartRequiredCount": "{count, plural, one {# change requires} other {# changes require}} a server restart to take effect.", - "restartRequiredDescription": "These flags only apply after the process reloads. Restart now or continue editing — pending flags stay queued until you confirm.", + "restartRequiredDescription": "These flags only apply after the process reloads. Restart now or continue editing \u2014 pending flags stay queued until you confirm.", "restartServer": "Restart Server", "cancel": "Cancel", - "restarting": "Restarting…", + "restarting": "Restarting\u2026", "confirmRestart": "Confirm Restart", - "restartViewDescription": "These flags only take effect after the server restarts. Toggle them like any other flag — the change is persisted immediately, but the new value is only read at process startup. Use the Restart Server banner above to apply.", + "restartViewDescription": "These flags only take effect after the server restarts. Toggle them like any other flag \u2014 the change is persisted immediately, but the new value is only read at process startup. Use the Restart Server banner above to apply.", "retry": "Retry", "noSearchResults": "No flags match your search", "resetAllOverrides": "Reset All Overrides", @@ -12938,7 +12938,7 @@ "strategy": "Strategy", "targets": "Targets", "providers": "Providers", - "targetCounts": "{configured} configured · {resolved} resolved", + "targetCounts": "{configured} configured \u00b7 {resolved} resolved", "healthReasons": "Health reasons", "healthReason": { "noRecentTraffic": "No recent combo traffic", @@ -12983,7 +12983,7 @@ "unknown": "unknown", "unknownProvider": "unknown provider", "unknownModel": "unknown model", - "resolvedTargetMetrics": "{requests} req · {success} success · {latency} · quota {quota}" + "resolvedTargetMetrics": "{requests} req \u00b7 {success} success \u00b7 {latency} \u00b7 quota {quota}" }, "usageLimits": { "usdUsageQuota": "USD usage quota", @@ -13022,19 +13022,19 @@ "fromReset": "From {quota} reset", "quotaEstimator": "Quota estimator", "noApiKeyUsage": "No API key usage in this provider window.", - "requestTokenCounts": "{requests} requests · {tokens} tokens", + "requestTokenCounts": "{requests} requests \u00b7 {tokens} tokens", "noUsdLimit": "No USD limit" }, "freeBudget": { "title": "Free-token budget", - "remaining": "{remaining} remaining · {percent}% of {total}", + "remaining": "{remaining} remaining \u00b7 {percent}% of {total}", "steadyMonth": "Steady / month", "firstMonth": "First month (+ credits)", "usedThisMonth": "Used this month", - "segmentHint": "Each segment = one free pool · pool-deduped, honest counting (no inflated rate-limit ceilings).", - "boost": "Unlock ~{tokens} more/mo with a one-time $10 OpenRouter top-up (50 → 1000 req/day)", - "uncapped": "Permanently free, no published cap (rate-limited) — real access, not counted in the headline:", - "tosRestricted": "{count, plural, one {# model} other {# models}} flagged as ToS-restricted — you decide", + "segmentHint": "Each segment = one free pool \u00b7 pool-deduped, honest counting (no inflated rate-limit ceilings).", + "boost": "Unlock ~{tokens} more/mo with a one-time $10 OpenRouter top-up (50 \u2192 1000 req/day)", + "uncapped": "Permanently free, no published cap (rate-limited) \u2014 real access, not counted in the headline:", + "tosRestricted": "{count, plural, one {# model} other {# models}} flagged as ToS-restricted \u2014 you decide", "provider": "Provider", "model": "Model", "modelName": "Model name", @@ -13053,8 +13053,8 @@ "discontinued": "discontinued" }, "tosTitle": { - "avoid": "ToS-restricted — review terms", - "caution": "Caution — personal-use / proxy clauses", + "avoid": "ToS-restricted \u2014 review terms", + "caution": "Caution \u2014 personal-use / proxy clauses", "ok": "Generally permissive" }, "allProviders": "All providers", @@ -13079,7 +13079,7 @@ }, "loadingRecommendations": "Loading provider recommendations...", "noRecommendations": "No provider health recommendations right now.", - "providerMetrics": "score {score}% · active {active}/{total} · cooldown {cooldown} · model lockouts {lockouts}", + "providerMetrics": "score {score}% \u00b7 active {active}/{total} \u00b7 cooldown {cooldown} \u00b7 model lockouts {lockouts}", "providerState": { "healthy": "healthy", "degraded": "degraded", @@ -13216,8 +13216,8 @@ "mode": "Default Mode", "modeParallel": "Parallel", "modeCollaborative": "Collaborative", - "modeParallelDesc": "All models run simultaneously — fastest results", - "modeCollaborativeDesc": "Models chain outputs — each sees the previous result", + "modeParallelDesc": "All models run simultaneously \u2014 fastest results", + "modeCollaborativeDesc": "Models chain outputs \u2014 each sees the previous result", "timeout": "Timeout (ms)", "timeoutDesc": "Max time per model call (5000-600000ms)", "systemPrompt": "System Prompt (optional)", @@ -13247,7 +13247,7 @@ "on": "ON", "off": "OFF", "availableProviders": "Available providers ({count})", - "noProviderOverrides": "No overrides — all active providers will participate with their default models", + "noProviderOverrides": "No overrides \u2014 all active providers will participate with their default models", "resetFailed": "Reset failed", "resetSuccess": "Config reset to defaults", "running": "Running...", @@ -13264,7 +13264,7 @@ "title": "OmniRoute now runs inside VS Code Copilot Chat", "description": "Install the free OmniCopilot extension and every OmniRoute model shows up right in the Copilot Chat model picker you already use.", "cta": "Get the Extension", - "secondaryNote": "Also on Open VSX (Cursor, Windsurf, VSCodium…)", + "secondaryNote": "Also on Open VSX (Cursor, Windsurf, VSCodium\u2026)", "dismissAriaLabel": "Dismiss" }, "featureFlagExposeFunctionalGatewayMirrorsDescription": "Advertise / mirror ids on /v1/models for models whose canonical owner has no active credential but a passthrough gateway with an active credential routes them. Warning: adds catalog entries for all clients when enabled globally.", @@ -13286,8 +13286,8 @@ "tierLive": "Live (real-time)", "tierCommunity": "Community (30-day delay)", "activateTitle": "Activate Radar", - "activateDescription": "Radar enriches the free model catalog with community-sourced intelligence — updated model availability, quotas, and setup guides.", - "privacyNoUpload": "Nothing is uploaded — your data stays local", + "activateDescription": "Radar enriches the free model catalog with community-sourced intelligence \u2014 updated model availability, quotas, and setup guides.", + "privacyNoUpload": "Nothing is uploaded \u2014 your data stays local", "privacyOnlySigned": "Only cryptographically signed metadata is downloaded", "privacyLocalOnly": "All processing happens on your OmniRoute instance", "activateButton": "Activate", @@ -13298,7 +13298,7 @@ "changeKeyButton": "Change key", "claimSectionTitle": "Don't have a supporter key yet?", "contributorButton": "I'm a contributor", - "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "contributorHint": "Verify your GitHub account \u2014 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", "supporterButton": "Support the project", "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Provider", @@ -13315,12 +13315,12 @@ "capThinking": "Thinking", "catalogTab": "Catalog", "freeCreditsTab": "Free credits", - "freeCreditsSubtitle": "Referral links from Radar-tracked providers — using them costs you nothing extra and helps support OmniRoute.", + "freeCreditsSubtitle": "Referral links from Radar-tracked providers \u2014 using them costs you nothing extra and helps support OmniRoute.", "fixedLinksEmpty": "No referral links available yet. Sync Radar to check for updates.", "requiredActionLabel": "What to do:", "claimButton": "Claim credits", "campaignsTitle": "Limited-time campaigns", - "campaignsEmpty": "No active campaigns right now — check back later.", + "campaignsEmpty": "No active campaigns right now \u2014 check back later.", "campaignsUpsellCommunity": "Limited-time campaigns are a supporter extra. Everything on this page's fixed links stays free for everyone.", "campaignsValidUntil": "Valid until {date}", "colActions": "Actions", @@ -13379,7 +13379,7 @@ "testFailed": "Connection test failed", "addConnection": "Add connection", "addConnectionDescription": "Don't have a connection yet? Add one in the providers dashboard.", - "addConnectionLink": "Go to providers →" + "addConnectionLink": "Go to providers \u2192" }, "resilienceConnections": { "title": "Connection Resilience", @@ -13688,18 +13688,18 @@ }, "traeAuthModal": { "errorAuthorizationFailed": "Authorization failed", - "errorPopupBlocked": "Popup blocked — allow popups for this site, or paste the token manually below.", + "errorPopupBlocked": "Popup blocked \u2014 allow popups for this site, or paste the token manually below.", "errorPopupClosed": "Authorization window was closed before completing.", "errorAccessTokenRequired": "Access token is required.", "errorImportFailed": "Import failed", "title": "Connect Trae SOLO", "authorizeDescription": "Authorize via trae.ai in a popup. The popup will close itself once the token has been imported.", - "authorizeImportant": "Important: you must be logged in to trae.ai in this browser first. The authorize page only confirms an existing session — it cannot sign you in. If you see \"Login Failed\", click \"Open solo.trae.ai\", sign in, then return here.", - "waitingForAuthorization": "Waiting for trae.ai…", + "authorizeImportant": "Important: you must be logged in to trae.ai in this browser first. The authorize page only confirms an existing session \u2014 it cannot sign you in. If you see \"Login Failed\", click \"Open solo.trae.ai\", sign in, then return here.", + "waitingForAuthorization": "Waiting for trae.ai\u2026", "authorizeWithBrowser": "Authorize with Browser", "openSoloTrae": "Open solo.trae.ai", "orPasteManually": "or paste a token manually", - "manualInstructions": "Sign in to solo.trae.ai, open DevTools → Network, send any chat message, and copy the JWT from the Authorization: Cloud-IDE-JWT {tokenPlaceholder} request header. JWT lifetime is ~14 days. Optional identity fields come from common_params in the same request body.", + "manualInstructions": "Sign in to solo.trae.ai, open DevTools \u2192 Network, send any chat message, and copy the JWT from the Authorization: Cloud-IDE-JWT {tokenPlaceholder} request header. JWT lifetime is ~14 days. Optional identity fields come from common_params in the same request body.", "accessTokenLabel": "Access Token (Cloud-IDE-JWT)", "webId": "Web ID", "optional": "optional", @@ -13708,7 +13708,7 @@ "scope": "Scope", "tenant": "Tenant", "region": "Region", - "importing": "Importing…", + "importing": "Importing\u2026", "importToken": "Import Token", "cancel": "Cancel" }, @@ -13733,10 +13733,10 @@ "test": "Test", "logs": "Logs" }, - "loadingLogs": "Loading logs…", + "loadingLogs": "Loading logs\u2026", "failedToLoadLogs": "Failed to load logs", "noLogsYet": "No logs yet", - "noLogsDescription": "Send a test message from the Test tab — logs for this provider will appear here.", + "noLogsDescription": "Send a test message from the Test tab \u2014 logs for this provider will appear here.", "openFullLogs": "Open full logs page", "live": "Live", "tailingLast": "tailing last {count}", @@ -13800,13 +13800,13 @@ "metadata": { "compressionTitle": "Compression", "compressionDescription": "Configure context compression settings to reduce token usage and costs.", - "relayTitle": "OmniRoute — Relay Proxies", + "relayTitle": "OmniRoute \u2014 Relay Proxies", "relayDescription": "Serverless relay proxy endpoints for your AI infrastructure", - "trafficInspectorTitle": "Traffic Inspector — OmniRoute", + "trafficInspectorTitle": "Traffic Inspector \u2014 OmniRoute", "trafficInspectorDescription": "Monitor LLM calls + debug any application's HTTPS traffic" }, "conductor": { - "title": "Conductor — CLI-agent fleet", + "title": "Conductor \u2014 CLI-agent fleet", "subtitle": "OmniConductor hub: runners, task queue and councils, live", "runners": "Runners", "tasks": "Tasks", @@ -13820,7 +13820,7 @@ "online": "online", "offline": "offline", "draining": "draining", - "hubOffline": "Conductor hub offline or not configured (CONDUCTOR_HUB_URL) — showing nothing rather than stale data.", + "hubOffline": "Conductor hub offline or not configured (CONDUCTOR_HUB_URL) \u2014 showing nothing rather than stale data.", "noRunners": "No runners registered", "noTasks": "No tasks yet", "detailTitle": "Task detail", @@ -13835,11 +13835,11 @@ "cancelFailed": "The hub refused the cancellation", "close": "Close", "error": "Error", - "faroTitle": "Faro — fleet spokesperson", + "faroTitle": "Faro \u2014 fleet spokesperson", "faroSubtitle": "Chat and voice, anchored in real fleet events. Destructive commands always ask for confirmation.", - "faroEmpty": "Ask about the fleet — e.g. \"how is the fleet?\"", + "faroEmpty": "Ask about the fleet \u2014 e.g. \"how is the fleet?\"", "faroPending": "Faro is asking for confirmation", - "faroPlaceholder": "talk to Faro…", + "faroPlaceholder": "talk to Faro\u2026", "faroSend": "send", "faroOffline": "Faro (spokesperson) is offline", "yes": "Yes", @@ -13848,9 +13848,9 @@ "speakAnswers": "speak answers", "voiceModels": "Voice models (provider/model of this OmniRoute)", "voiceIdle": "talk", - "voiceListening": "listening…", - "voiceThinking": "thinking…", - "voiceSpeaking": "speaking…", + "voiceListening": "listening\u2026", + "voiceThinking": "thinking\u2026", + "voiceSpeaking": "speaking\u2026", "sttFailed": "Transcription failed (check the STT model/provider)", "ttsFailed": "Speech synthesis failed (check the TTS model/provider)", "micDenied": "Microphone unavailable or permission denied" @@ -13861,7 +13861,7 @@ "label": "Expose as cliproxyapi/...", "hint": "When enabled, discovered models appear in provider selects across OmniRoute.", "updateFailed": "Failed to update (HTTP {status})", - "networkError": "Network error — could not update provider exposure setting" + "networkError": "Network error \u2014 could not update provider exposure setting" }, "radarOffersPage": { "title": "Supporter offers", @@ -13925,7 +13925,7 @@ }, "cheaperInferenceSponsorBanner": { "title": "Cheaper Inference is an OmniRoute Open Source Friend", - "description": "A cost-ranked gateway reselling dozens of frontier models behind one OpenAI-compatible endpoint — routing each request to the cheapest eligible provider, never above list price.", + "description": "A cost-ranked gateway reselling dozens of frontier models behind one OpenAI-compatible endpoint \u2014 routing each request to the cheapest eligible provider, never above list price.", "cta": "Get an API Key", "partnerLinkNote": "Partner link", "dismissAriaLabel": "Dismiss"