chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 2)

Continues round 1 (commit 8d34f4c65). Round-2 subagents refactored
additional dashboard pages, replacing 77 more hardcoded strings with
useTranslations() lookups. Added 79 corresponding keys to en.json
across the a2aDashboard, agents, analytics, apiManager, cliTools,
common, and settings namespaces.

Pages affected:
- a2a/page (new useTranslations + 6 keys)
- agent-skills/page (new useTranslations + 9 keys)
- AutoRoutingAnalyticsTab (new useTranslations + 6 keys)
- AppearanceTab (8 → 6 remaining)
- OneproxyTab (10 → 0)
- ResilienceTab (18 → 0 missing key)
- RoutingTab (7 → 0 missing key)
- VisionBridgeSettingsTab (new useTranslations + 6 keys)
- CopilotToolCard (7 → 0 missing key)
- ApiManagerPageClient (13 → 0 missing key)
- gamification/admin (new useTranslations + 7 keys)

Hardcoded total: 326 → 249. Real missing keys: 0 (the 6 still flagged
are false positives in exampleTemplates.tsx where t is passed as a
parameter — keys exist at translator.templatePayloads.*).
This commit is contained in:
diegosouzapw
2026-05-19 21:50:02 -03:00
parent 8d34f4c650
commit f8d9873e27
12 changed files with 206 additions and 97 deletions

View File

@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
import A2ADashboardPage from "../endpoint/components/A2ADashboard";
@@ -118,6 +119,7 @@ function DisabledPanel() {
}
export default function A2APage() {
const t = useTranslations("a2aDashboard");
const [a2aStatus, setA2aStatus] = useState<ServiceStatus>({ online: false, loading: true });
const [a2aEnabled, setA2aEnabled] = useState(false);
const [a2aToggling, setA2aToggling] = useState(false);
@@ -192,19 +194,19 @@ export default function A2APage() {
Discover the agent card at <code className="text-xs">/.well-known/agent.json</code>.
</li>
<li>
Send JSON-RPC to <code className="text-xs">POST /a2a</code> using{" "}
<code className="text-xs">message/send</code> or{" "}
<code className="text-xs">message/stream</code>.
Send JSON-RPC to <code className="text-xs">{t("rpcEndpoint")}</code> using{" "}
<code className="text-xs">{t("rpcMethodSend")}</code> or{" "}
<code className="text-xs">{t("rpcMethodStream")}</code>.
</li>
<li>
Track and cancel tasks with <code className="text-xs">tasks/get</code> and{" "}
<code className="text-xs">tasks/cancel</code>.
Track and cancel tasks with <code className="text-xs">{t("rpcMethodGet")}</code> and{" "}
<code className="text-xs">{t("rpcMethodCancel")}</code>.
</li>
</ol>
</div>
<div className="shrink-0">
<ServiceToggle
label="A2A"
label={t("serviceLabel")}
status={a2aStatus}
enabled={a2aEnabled}
onToggle={() => void toggleA2a()}

View File

@@ -1,5 +1,6 @@
"use client";
import { useTranslations } from "next-intl";
import {
AGENT_SKILLS,
AGENT_SKILLS_REPO_URL,
@@ -10,6 +11,7 @@ import {
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
function CopyButton({ url }: { url: string }) {
const t = useTranslations("agents");
const { copied, copy } = useCopyToClipboard();
const isCopied = copied === url;
@@ -21,17 +23,18 @@ function CopyButton({ url }: { url: string }) {
? "bg-emerald-500/10 text-emerald-700 dark:text-emerald-400"
: "bg-bg-subtle text-text-muted hover:text-text-main"
}`}
title="Copy raw URL to clipboard"
title={t("copyRawUrlTitle")}
>
<span className="material-symbols-outlined text-[14px]">
{isCopied ? "check" : "content_copy"}
</span>
{isCopied ? "Copied!" : "Copy URL"}
{isCopied ? t("copied") : t("copyUrl")}
</button>
);
}
function SkillRow({ skill }: { skill: AgentSkill }) {
const t = useTranslations("agents");
const rawUrl = getAgentSkillRawUrl(skill.id);
const blobUrl = getAgentSkillBlobUrl(skill.id);
@@ -46,12 +49,12 @@ function SkillRow({ skill }: { skill: AgentSkill }) {
<span className="text-sm font-semibold text-text-main">{skill.name}</span>
{skill.isEntry && (
<span className="rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary">
Start Here
{t("startHere")}
</span>
)}
{skill.isNew && (
<span className="rounded-full bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-amber-700 dark:text-amber-400">
New
{t("badgeNew")}
</span>
)}
{skill.endpoint && (
@@ -69,7 +72,7 @@ function SkillRow({ skill }: { skill: AgentSkill }) {
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 rounded px-2 py-1 text-xs font-medium text-text-muted transition-colors hover:bg-bg-subtle hover:text-text-main"
title="View on GitHub"
title={t("viewOnGithub")}
>
<span className="material-symbols-outlined text-[14px]">open_in_new</span>
</a>
@@ -109,6 +112,7 @@ function SkillSection({
}
export default function AgentSkillsPage() {
const t = useTranslations("agents");
const apiSkills = AGENT_SKILLS.filter((s) => s.category === "api");
const cliSkills = AGENT_SKILLS.filter((s) => s.category === "cli");
@@ -118,12 +122,12 @@ export default function AgentSkillsPage() {
<div className="rounded-xl border border-border bg-bg-subtle/50 p-4">
<div className="mb-2 flex items-center gap-2">
<span className="material-symbols-outlined text-[18px] text-primary">info</span>
<span className="text-sm font-semibold text-text-main">How to use</span>
<span className="text-sm font-semibold text-text-main">{t("howToUse")}</span>
</div>
<ol className="space-y-1 text-xs text-text-muted">
<li>
1. Click <strong className="text-text-main">Copy URL</strong> on the skill you want your
agent to know about.
1. Click <strong className="text-text-main">{t("copyUrl")}</strong> on the skill you
want your agent to know about.
</li>
<li>
2. In your AI agent (Claude, Cursor, Cline), say:
@@ -144,20 +148,20 @@ export default function AgentSkillsPage() {
className="mt-3 inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline"
>
<span className="material-symbols-outlined text-[14px]">open_in_new</span>
Browse all skills on GitHub
{t("browseAllSkillsOnGithub")}
</a>
</div>
{/* Two-column grid: API Skills | CLI Skills */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
<SkillSection
title="API Skills"
title={t("apiSkills")}
subtitle={`${apiSkills.length} skills — control OmniRoute via REST / HTTP`}
icon="api"
skills={apiSkills}
/>
<SkillSection
title="CLI Skills"
title={t("cliSkills")}
subtitle={`${cliSkills.length} skills — control OmniRoute via the omniroute terminal binary`}
icon="terminal"
skills={cliSkills}

View File

@@ -59,7 +59,7 @@ export default function AutoRoutingAnalyticsTab() {
<span className="material-symbols-outlined text-[20px]">auto_awesome</span>
</div>
<div>
<p className="text-sm text-text-muted">Total Auto Requests</p>
<p className="text-sm text-text-muted">{t("autoRoutingTotalAutoRequests")}</p>
<p className="text-2xl font-bold">{stats.totalRequests.toLocaleString()}</p>
</div>
</div>
@@ -71,7 +71,7 @@ export default function AutoRoutingAnalyticsTab() {
<span className="material-symbols-outlined text-[20px]">target</span>
</div>
<div>
<p className="text-sm text-text-muted">Avg Selection Score</p>
<p className="text-sm text-text-muted">{t("autoRoutingAvgSelectionScore")}</p>
<p className="text-2xl font-bold">{(stats.avgSelectionScore * 100).toFixed(1)}%</p>
</div>
</div>
@@ -83,7 +83,7 @@ export default function AutoRoutingAnalyticsTab() {
<span className="material-symbols-outlined text-[20px]">explore</span>
</div>
<div>
<p className="text-sm text-text-muted">Exploration Rate</p>
<p className="text-sm text-text-muted">{t("autoRoutingExplorationRate")}</p>
<p className="text-2xl font-bold">{(stats.explorationRate * 100).toFixed(1)}%</p>
</div>
</div>
@@ -95,7 +95,7 @@ export default function AutoRoutingAnalyticsTab() {
<span className="material-symbols-outlined text-[20px]">history</span>
</div>
<div>
<p className="text-sm text-text-muted">LKGP Hit Rate</p>
<p className="text-sm text-text-muted">{t("autoRoutingLkgpHitRate")}</p>
<p className="text-2xl font-bold">{(stats.lkgpHitRate * 100).toFixed(1)}%</p>
</div>
</div>
@@ -104,7 +104,7 @@ export default function AutoRoutingAnalyticsTab() {
{/* Variant Breakdown */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-4">Requests by Variant</h3>
<h3 className="text-lg font-semibold mb-4">{t("autoRoutingRequestsByVariant")}</h3>
<div className="space-y-3">
{Object.entries(stats.variantBreakdown).map(([variant, count]) => {
const percentage = stats.totalRequests > 0 ? (count / stats.totalRequests) * 100 : 0;
@@ -128,7 +128,7 @@ export default function AutoRoutingAnalyticsTab() {
{/* Top Providers */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-4">Top Routed Providers</h3>
<h3 className="text-lg font-semibold mb-4">{t("autoRoutingTopRoutedProviders")}</h3>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>

View File

@@ -1302,10 +1302,10 @@ const PermissionsModal = memo(function PermissionsModal({
<div className="flex flex-col gap-2 p-3 rounded-lg border border-border bg-surface/40">
<div className="flex items-start justify-between gap-3">
<div className="flex flex-col gap-1">
<p className="text-sm font-medium text-text-main">Custom Rate Limits</p>
<p className="text-xs text-text-muted">
Override global default limits. Leave empty to use defaults.
<p className="text-sm font-medium text-text-main">
{t("apiManagerCustomRateLimits")}
</p>
<p className="text-xs text-text-muted">{t("apiManagerCustomRateLimitsDesc")}</p>
</div>
<button
type="button"
@@ -1332,9 +1332,11 @@ const PermissionsModal = memo(function PermissionsModal({
return next;
});
}}
placeholder="Requests"
placeholder={t("apiManagerRateLimitRequestsPlaceholder")}
/>
<span className="text-sm text-text-muted shrink-0">req /</span>
<span className="text-sm text-text-muted shrink-0">
{t("apiManagerRateLimitReqPer")}
</span>
<Input
type="number"
min={1}
@@ -1347,14 +1349,14 @@ const PermissionsModal = memo(function PermissionsModal({
return next;
});
}}
placeholder="Seconds"
placeholder={t("apiManagerRateLimitSecondsPlaceholder")}
/>
<span className="text-sm text-text-muted shrink-0">sec</span>
<button
type="button"
onClick={() => setRateLimits((prev) => prev.filter((_, i) => i !== index))}
className="p-2 text-red-500 hover:bg-red-500/10 rounded transition-colors shrink-0"
title="Remove limit"
title={t("apiManagerRemoveLimitTitle")}
>
<span className="material-symbols-outlined text-[18px]">delete</span>
</button>
@@ -1454,7 +1456,7 @@ const PermissionsModal = memo(function PermissionsModal({
type="text"
value={scheduleTz}
onChange={(e) => setScheduleTz(e.target.value)}
placeholder="America/Sao_Paulo"
placeholder={t("apiManagerTimezonePlaceholder")}
className="w-full px-2 py-1.5 text-sm border border-border rounded-md bg-background text-text-main font-mono"
/>
<p className="text-[10px] text-text-muted mt-1">{t("scheduleTimezoneHint")}</p>

View File

@@ -193,7 +193,7 @@ export default function CopilotToolCard({
<div className="flex items-start gap-3 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
<span className="material-symbols-outlined text-blue-500 text-lg">info</span>
<div className="text-sm text-blue-700 dark:text-blue-300">
<p className="font-medium">GitHub Copilot Config Generator</p>
<p className="font-medium">{t("copilotConfigGenerator")}</p>
<p className="mt-1 text-xs opacity-80">
Generates the{" "}
<code className="px-1 py-0.5 rounded bg-black/5 dark:bg-white/10">
@@ -226,7 +226,7 @@ export default function CopilotToolCard({
>
1
</div>
<span className="font-medium text-sm">API Key</span>
<span className="font-medium text-sm">{t("copilotApiKey")}</span>
</div>
<select
value={selectedApiKeyId}
@@ -278,7 +278,7 @@ export default function CopilotToolCard({
type="text"
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
placeholder="Filter models..."
placeholder={t("copilotFilterModelsPlaceholder")}
className="w-full px-3 py-1.5 bg-bg-secondary rounded-lg text-sm border border-border focus:outline-none focus:ring-1 focus:ring-primary/50"
/>
</div>
@@ -327,7 +327,9 @@ export default function CopilotToolCard({
</summary>
<div className="mt-3 grid grid-cols-2 gap-3 pl-6">
<div>
<label className="text-xs text-text-muted block mb-1">Max Input Tokens</label>
<label className="text-xs text-text-muted block mb-1">
{t("copilotMaxInputTokens")}
</label>
<input
type="number"
value={maxInputTokens}
@@ -336,7 +338,9 @@ export default function CopilotToolCard({
/>
</div>
<div>
<label className="text-xs text-text-muted block mb-1">Max Output Tokens</label>
<label className="text-xs text-text-muted block mb-1">
{t("copilotMaxOutputTokens")}
</label>
<input
type="number"
value={maxOutputTokens}
@@ -351,7 +355,7 @@ export default function CopilotToolCard({
onChange={(e) => setToolCalling(e.target.checked)}
className="rounded border-border accent-[#1F6FEB]"
/>
<span className="text-sm">Tool Calling</span>
<span className="text-sm">{t("copilotToolCalling")}</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
@@ -401,7 +405,7 @@ export default function CopilotToolCard({
{/* Usage instructions */}
<div className="mt-3 p-3 bg-bg-secondary rounded-lg border border-border">
<p className="text-xs text-text-muted">
<span className="font-medium text-text-main">Paste into: </span>
<span className="font-medium text-text-main">{t("copilotPasteInto")} </span>
<code className="px-1 py-0.5 rounded bg-black/5 dark:bg-white/10">
~/.config/Code/User/chatLanguageModels.json
</code>

View File

@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
interface Anomaly {
@@ -10,6 +11,7 @@ interface Anomaly {
}
export default function GamificationAdminPage() {
const t = useTranslations("common");
const [anomalies, setAnomalies] = useState<Anomaly[]>([]);
const [loading, setLoading] = useState(true);
@@ -33,24 +35,24 @@ export default function GamificationAdminPage() {
return (
<div className="flex flex-col gap-6">
<div>
<h1 className="text-2xl font-bold">Gamification Admin</h1>
<p className="text-sm text-text-muted mt-1">Monitor anomalies and system health</p>
<h1 className="text-2xl font-bold">{t("gamificationAdmin")}</h1>
<p className="text-sm text-text-muted mt-1">{t("monitorAnomaliesAndHealth")}</p>
</div>
<Card>
<h2 className="text-lg font-semibold mb-3">Flagged Anomalies</h2>
<h2 className="text-lg font-semibold mb-3">{t("flaggedAnomalies")}</h2>
{loading ? (
<div className="text-text-muted">Loading...</div>
) : anomalies.length === 0 ? (
<div className="text-text-muted">No anomalies detected</div>
<div className="text-text-muted">{t("noAnomaliesDetected")}</div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border">
<th className="text-left p-2 font-medium text-text-muted">API Key</th>
<th className="text-right p-2 font-medium text-text-muted">XP (1h)</th>
<th className="text-right p-2 font-medium text-text-muted">Z-Score</th>
<th className="text-left p-2 font-medium text-text-muted">{t("apiKey")}</th>
<th className="text-right p-2 font-medium text-text-muted">{t("xpLastHour")}</th>
<th className="text-right p-2 font-medium text-text-muted">{t("zScore")}</th>
<th className="text-center p-2 font-medium text-text-muted">Status</th>
</tr>
</thead>

View File

@@ -492,7 +492,7 @@ export default function AppearanceTab() {
{(settings.customLogoUrl || settings.customLogoBase64) && (
<img
src={settings.customLogoBase64 || settings.customLogoUrl}
alt="Logo preview"
alt={t("appearanceLogoPreviewAlt")}
className="h-10 w-10 rounded border border-border object-contain bg-surface"
onError={(e) => {
e.currentTarget.style.display = "none";
@@ -562,7 +562,7 @@ export default function AppearanceTab() {
<p className="text-xs text-text-muted mb-2">{t("logoPreview")}</p>
<img
src={settings.customLogoBase64 || settings.customLogoUrl}
alt="Logo preview"
alt={t("appearanceLogoPreviewAlt")}
className="h-12 w-auto max-w-full rounded"
/>
</div>
@@ -586,7 +586,7 @@ export default function AppearanceTab() {
{(settings.customFaviconUrl || settings.customFaviconBase64) && (
<img
src={settings.customFaviconBase64 || settings.customFaviconUrl}
alt="Favicon preview"
alt={t("appearanceFaviconPreviewAlt")}
className="h-10 w-10 rounded border border-border object-contain bg-surface"
onError={(e) => {
e.currentTarget.style.display = "none";
@@ -658,7 +658,7 @@ export default function AppearanceTab() {
<p className="text-xs text-text-muted mb-2">{t("faviconPreview")}</p>
<img
src={settings.customFaviconBase64 || settings.customFaviconUrl}
alt="Favicon preview"
alt={t("appearanceFaviconPreviewAlt")}
className="h-8 w-8 rounded"
/>
</div>

View File

@@ -187,9 +187,11 @@ export default function OneproxyTab() {
</Card>
<Card className="p-4">
<div className="text-2xl font-bold text-text-main">
{status?.lastSyncAt ? new Date(status.lastSyncAt).toLocaleTimeString() : "Never"}
{status?.lastSyncAt
? new Date(status.lastSyncAt).toLocaleTimeString()
: t("oneproxyNever")}
</div>
<div className="text-sm text-text-muted">Last Sync</div>
<div className="text-sm text-text-muted">{t("oneproxyLastSync")}</div>
</Card>
</div>
)}
@@ -201,7 +203,7 @@ export default function OneproxyTab() {
onChange={(e) => setFilterProtocol(e.target.value)}
className="px-3 py-2 rounded-lg bg-black/5 dark:bg-white/5 text-text-main text-sm border border-border"
>
<option value="">All Protocols</option>
<option value="">{t("oneproxyAllProtocols")}</option>
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
<option value="socks4">SOCKS4</option>
@@ -209,14 +211,14 @@ export default function OneproxyTab() {
</select>
<input
type="text"
placeholder="Country code (e.g. US)"
placeholder={t("oneproxyCountryCodePlaceholder")}
value={filterCountry}
onChange={(e) => setFilterCountry(e.target.value)}
className="px-3 py-2 rounded-lg bg-black/5 dark:bg-white/5 text-text-main text-sm border border-border w-40"
/>
<input
type="number"
placeholder="Min quality"
placeholder={t("oneproxyMinQualityPlaceholder")}
value={minQuality}
onChange={(e) => setMinQuality(e.target.value)}
className="px-3 py-2 rounded-lg bg-black/5 dark:bg-white/5 text-text-main text-sm border border-border w-32"
@@ -226,7 +228,7 @@ export default function OneproxyTab() {
<Card className="p-4">
{loading ? (
<div className="text-center py-8 text-text-muted">Loading proxies...</div>
<div className="text-center py-8 text-text-muted">{t("oneproxyLoadingProxies")}</div>
) : proxies.length === 0 ? (
<div className="text-center py-8 text-text-muted">
No 1proxy proxies found. Click &quot;Sync Now&quot; to fetch free proxies.
@@ -300,25 +302,27 @@ export default function OneproxyTab() {
{status && (
<Card className="p-4">
<h3 className="text-sm font-semibold text-text-main mb-2">Sync Status</h3>
<h3 className="text-sm font-semibold text-text-main mb-2">
{t("oneproxySyncStatusTitle")}
</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 text-sm">
<div>
<span className="text-text-muted">Last sync: </span>
<span className="text-text-muted">{t("oneproxyLastSyncLabel")} </span>
<span className={status.lastSyncSuccess ? "text-green-600" : "text-red-600"}>
{status.lastSyncSuccess ? "Success" : "Failed"}
{status.lastSyncSuccess ? t("oneproxySuccess") : t("oneproxyFailed")}
</span>
</div>
<div>
<span className="text-text-muted">Proxies fetched: </span>
<span className="text-text-muted">{t("oneproxyProxiesFetched")} </span>
<span className="text-text-main">{status.lastSyncCount}</span>
</div>
<div>
<span className="text-text-muted">Consecutive failures: </span>
<span className="text-text-muted">{t("oneproxyConsecutiveFailures")} </span>
<span className="text-text-main">{status.consecutiveFailures}</span>
</div>
{status.lastSyncError && (
<div className="col-span-full">
<span className="text-text-muted">Error: </span>
<span className="text-text-muted">{t("oneproxyErrorLabel")} </span>
<span className="text-red-600 text-xs">{status.lastSyncError}</span>
</div>
)}

View File

@@ -416,7 +416,7 @@ function ConnectionCooldownCard({
</p>
</div>
<NumberField
label="Max backoff steps"
label={t("resilienceMaxBackoffSteps")}
value={current.maxBackoffSteps}
min={0}
onChange={(maxBackoffSteps) =>
@@ -427,27 +427,27 @@ function ConnectionCooldownCard({
) : (
<>
<div className="flex items-center justify-between text-sm">
<span className="text-text-muted">Base cooldown</span>
<span className="text-text-muted">{t("resilienceBaseCooldownLabel")}</span>
<span className="font-mono text-text-main">{formatMs(current.baseCooldownMs)}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-text-muted">Use upstream retry hints</span>
<span className="text-text-muted">{t("resilienceUseUpstreamRetryHintsLabel")}</span>
<span className="font-mono text-text-main">
{current.useUpstreamRetryHints ? "Yes" : "No"}
{current.useUpstreamRetryHints ? t("resilienceYes") : t("resilienceNo")}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-text-muted">Use upstream 429 hints (breaker)</span>
<span className="text-text-muted">{t("resilienceUseUpstream429BreakerLabel")}</span>
<span className="font-mono text-text-main">
{current.useUpstream429BreakerHints === true
? "Yes"
? t("resilienceYes")
: current.useUpstream429BreakerHints === false
? "No"
: "Default"}
? t("resilienceNo")
: t("resilienceDefault")}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-text-muted">Max backoff steps</span>
<span className="text-text-muted">{t("resilienceMaxBackoffStepsLabel")}</span>
<span className="font-mono text-text-main">{current.maxBackoffSteps}</span>
</div>
</>
@@ -462,7 +462,7 @@ function ConnectionCooldownCard({
<div className="space-y-2">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-xl text-primary">timer_off</span>
<h2 className="text-lg font-bold">Connection Cooldown</h2>
<h2 className="text-lg font-bold">{t("resilienceConnectionCooldownTitle")}</h2>
</div>
<SectionDescription
scope="Individual connection"
@@ -526,6 +526,7 @@ function ProviderBreakerCard({
onSave: (next: ResilienceResponse["providerBreaker"]) => Promise<void>;
saving: boolean;
}) {
const t = useTranslations("settings");
const [editing, setEditing] = useState(false);
const [draft, setDraft] = useState(value);
@@ -540,7 +541,7 @@ function ProviderBreakerCard({
{editing ? (
<>
<NumberField
label="Failure threshold"
label={t("resilienceFailureThreshold")}
value={current.failureThreshold}
min={1}
onChange={(failureThreshold) =>
@@ -548,7 +549,7 @@ function ProviderBreakerCard({
}
/>
<NumberField
label="Reset timeout"
label={t("resilienceResetTimeout")}
value={current.resetTimeoutMs}
min={1000}
suffix="ms"
@@ -560,11 +561,11 @@ function ProviderBreakerCard({
) : (
<>
<div className="flex items-center justify-between text-sm">
<span className="text-text-muted">Failure threshold</span>
<span className="text-text-muted">{t("resilienceFailureThresholdLabel")}</span>
<span className="font-mono text-text-main">{current.failureThreshold}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-text-muted">Reset timeout</span>
<span className="text-text-muted">{t("resilienceResetTimeoutLabel")}</span>
<span className="font-mono text-text-main">{formatMs(current.resetTimeoutMs)}</span>
</div>
</>
@@ -581,7 +582,7 @@ function ProviderBreakerCard({
<span className="material-symbols-outlined text-xl text-primary">
electrical_services
</span>
<h2 className="text-lg font-bold">Circuit Breaker per Provider</h2>
<h2 className="text-lg font-bold">{t("resilienceProviderBreakerTitle")}</h2>
</div>
<SectionDescription
scope="Entire provider"

View File

@@ -996,7 +996,7 @@ export default function RoutingTab() {
</span>
</div>
<div>
<h3 className="text-lg font-semibold">Antigravity Signature Cache Mode</h3>
<h3 className="text-lg font-semibold">{t("routingAntigravitySignatureTitle")}</h3>
<p className="text-sm text-text-muted">
Control whether OmniRoute reuses only stored Gemini thought signatures or accepts
validated client-provided signatures in Antigravity-compatible tool-call flows.
@@ -1070,7 +1070,7 @@ export default function RoutingTab() {
</div>
<div className="mb-5">
<h4 className="text-sm font-semibold mb-2">Header fingerprint (per provider)</h4>
<h4 className="text-sm font-semibold mb-2">{t("routingHeaderFingerprintTitle")}</h4>
<p className="text-xs text-text-muted mb-2">
{t("cliFingerprintEnabled", { count: cliCompatProviderSet.size })}
</p>
@@ -1230,7 +1230,7 @@ export default function RoutingTab() {
role="alert"
className="mb-3 rounded border border-red-500/40 bg-red-500/10 p-2 text-xs text-red-300"
>
<span className="font-medium"> Server rejected save:</span>{" "}
<span className="font-medium">{t("routingServerRejectedSave")}</span>{" "}
<span className="break-words font-mono">{providerSaveErrors[providerId]}</span>
<p className="mt-1 text-[11px] text-red-200/80">
Your local edits are kept. Fix the field above and the next change will
@@ -1301,7 +1301,7 @@ export default function RoutingTab() {
{/* Add op row */}
<div className="flex items-end gap-2 mb-3">
<Select
label="Add a transform op"
label={t("routingAddTransformOp")}
className="flex-1"
value={selectedKind}
onChange={(e) =>
@@ -1400,7 +1400,7 @@ export default function RoutingTab() {
</span>
</div>
<div>
<h3 className="text-lg font-semibold">Client Cache Control</h3>
<h3 className="text-lg font-semibold">{t("routingClientCacheControlTitle")}</h3>
<p className="text-sm text-text-muted">
Configure whether OmniRoute preserves client-provided cache_control markers
</p>
@@ -1468,7 +1468,7 @@ export default function RoutingTab() {
</span>
</div>
<div>
<h3 className="text-lg font-semibold">Zero-Config Auto-Routing</h3>
<h3 className="text-lg font-semibold">{t("routingZeroConfigTitle")}</h3>
<p className="text-sm text-text-muted mt-1">
Enable automatic provider selection using the auto/ prefix. When enabled, requests
to auto, auto/coding, auto/fast, etc. will dynamically route across all connected
@@ -1490,7 +1490,7 @@ export default function RoutingTab() {
</div>
</div>
<div className="mt-4 pt-4 border-t border-border/30">
<label className="block text-sm font-medium mb-2">Default Auto Variant</label>
<label className="block text-sm font-medium mb-2">{t("routingDefaultAutoVariant")}</label>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
{[
{ value: "lkgp", label: "LKGP", desc: "Last Known Good Provider" },

View File

@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import { Card, Toggle } from "@/shared/components";
import { VISION_BRIDGE_DEFAULTS } from "@/shared/constants/visionBridgeDefaults";
@@ -13,6 +14,7 @@ type SettingsState = {
};
export default function VisionBridgeSettingsTab() {
const t = useTranslations("settings");
const [settings, setSettings] = useState<SettingsState>({
visionBridgeEnabled: VISION_BRIDGE_DEFAULTS.enabled,
visionBridgeModel: VISION_BRIDGE_DEFAULTS.model,
@@ -63,7 +65,7 @@ export default function VisionBridgeSettingsTab() {
</span>
</div>
<div>
<h3 className="text-lg font-semibold">Vision Bridge</h3>
<h3 className="text-lg font-semibold">{t("visionBridge")}</h3>
<p className="text-sm text-text-muted">
Run an automatic vision-to-text fallback before routing image requests to text-only
models.
@@ -88,7 +90,7 @@ export default function VisionBridgeSettingsTab() {
<div className="pt-4 border-t border-border space-y-4">
<div>
<label className="block text-sm font-medium mb-1">Bridge Model</label>
<label className="block text-sm font-medium mb-1">{t("visionBridgeModel")}</label>
<input
type="text"
value={settings.visionBridgeModel}
@@ -97,7 +99,7 @@ export default function VisionBridgeSettingsTab() {
}
onBlur={() => updateSetting({ visionBridgeModel: settings.visionBridgeModel.trim() })}
className="w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm"
placeholder="openai/gpt-4o-mini"
placeholder={t("visionBridgeModelPlaceholder")}
/>
<p className="text-xs text-text-muted mt-1">
Any OmniRoute model ID that supports vision can be used here.
@@ -105,7 +107,7 @@ export default function VisionBridgeSettingsTab() {
</div>
<div>
<label className="block text-sm font-medium mb-1">Bridge Prompt</label>
<label className="block text-sm font-medium mb-1">{t("visionBridgePrompt")}</label>
<textarea
value={settings.visionBridgePrompt}
onChange={(e) =>
@@ -115,7 +117,7 @@ export default function VisionBridgeSettingsTab() {
updateSetting({ visionBridgePrompt: settings.visionBridgePrompt.trim() })
}
className="min-h-[100px] w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm"
placeholder="Describe this image concisely."
placeholder={t("visionBridgePromptPlaceholder")}
/>
<p className="text-xs text-text-muted mt-1">
Sent to the vision model before the extracted description is injected back into the
@@ -125,7 +127,7 @@ export default function VisionBridgeSettingsTab() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-1">Timeout (ms)</label>
<label className="block text-sm font-medium mb-1">{t("visionBridgeTimeoutMs")}</label>
<input
type="number"
min={1000}
@@ -152,7 +154,9 @@ export default function VisionBridgeSettingsTab() {
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Max Images Per Request</label>
<label className="block text-sm font-medium mb-1">
{t("visionBridgeMaxImagesPerRequest")}
</label>
<input
type="number"
min={1}

View File

@@ -656,7 +656,14 @@
"audioTranscriptionDesc": "Audio Transcription Desc",
"learnedFromHeaders": "Learned From Headers",
"totalRequests": "Total Requests",
"cloudUnstableNote": "Cloud Unstable Note"
"cloudUnstableNote": "Cloud Unstable Note",
"gamificationAdmin": "Gamification Admin",
"monitorAnomaliesAndHealth": "Monitor anomalies and system health",
"flaggedAnomalies": "Flagged Anomalies",
"noAnomaliesDetected": "No anomalies detected",
"apiKey": "API Key",
"xpLastHour": "XP (1h)",
"zScore": "Z-Score"
},
"sidebar": {
"home": "Home",
@@ -1122,7 +1129,13 @@
"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 — token savings, mode breakdown, and provider stats.",
"autoRoutingTotalAutoRequests": "Total Auto Requests",
"autoRoutingAvgSelectionScore": "Avg Selection Score",
"autoRoutingExplorationRate": "Exploration Rate",
"autoRoutingLkgpHitRate": "LKGP Hit Rate",
"autoRoutingRequestsByVariant": "Requests by Variant",
"autoRoutingTopRoutedProviders": "Top Routed Providers"
},
"apiManager": {
"title": "API Keys",
@@ -1234,7 +1247,14 @@
"allowAllDesc": "This key can access all available models.",
"restrictDesc": "This key can access {selectedCount} of {totalModels} models.",
"selectedCount": "{count} selected",
"maxActiveSessions": "Max Active Sessions"
"maxActiveSessions": "Max Active Sessions",
"apiManagerCustomRateLimits": "Custom Rate Limits",
"apiManagerCustomRateLimitsDesc": "Override global default limits. Leave empty to use defaults.",
"apiManagerRateLimitRequestsPlaceholder": "Requests",
"apiManagerRateLimitReqPer": "req /",
"apiManagerRateLimitSecondsPlaceholder": "Seconds",
"apiManagerRemoveLimitTitle": "Remove limit",
"apiManagerTimezonePlaceholder": "America/Sao_Paulo"
},
"auditLog": {
"title": "Audit Log",
@@ -1661,7 +1681,14 @@
"customCliEndpointHintLabel": "How to wire the endpoint",
"customCliEndpointHint": "Point any OpenAI-compatible client to the OmniRoute /v1 base URL. The raw chat completions endpoint is {endpoint}. Use the JSON block when the tool wants a provider object, or the env script when it reads OPENAI_* variables.",
"customCliEnvBlockTitle": "Env / shell snippet",
"customCliJsonBlockTitle": "Provider JSON block"
"customCliJsonBlockTitle": "Provider JSON block",
"copilotConfigGenerator": "GitHub Copilot Config Generator",
"copilotApiKey": "API Key",
"copilotFilterModelsPlaceholder": "Filter models...",
"copilotMaxInputTokens": "Max Input Tokens",
"copilotMaxOutputTokens": "Max Output Tokens",
"copilotToolCalling": "Tool Calling",
"copilotPasteInto": "Paste into: "
},
"combos": {
"title": "Combos",
@@ -2523,7 +2550,13 @@
"tablePhase": "Table Phase",
"offset": "Offset",
"limit": "Limit",
"skill": "Skill"
"skill": "Skill",
"rpcEndpoint": "POST /a2a",
"rpcMethodSend": "message/send",
"rpcMethodStream": "message/stream",
"rpcMethodGet": "tasks/get",
"rpcMethodCancel": "tasks/cancel",
"serviceLabel": "A2A"
},
"memory": {
"title": "Memory Management",
@@ -4275,7 +4308,50 @@
"alwaysPreserveClientCacheLabel": "Always Preserve Client Cache",
"alwaysPreserveClientCacheDesc": "Client cache preservation policy",
"logRetentionPolicyTitle": "Log retention policy",
"resilienceUseUpstream429HintsForBreaker": "Use upstream 429 hints (breaker)"
"resilienceUseUpstream429HintsForBreaker": "Use upstream 429 hints (breaker)",
"appearanceLogoPreviewAlt": "Logo preview",
"appearanceFaviconPreviewAlt": "Favicon preview",
"oneproxyLastSync": "Last Sync",
"oneproxyAllProtocols": "All Protocols",
"oneproxyCountryCodePlaceholder": "Country code (e.g. US)",
"oneproxyMinQualityPlaceholder": "Min quality",
"oneproxyLoadingProxies": "Loading proxies...",
"oneproxyLastSyncLabel": "Last sync:",
"oneproxyProxiesFetched": "Proxies fetched:",
"oneproxyConsecutiveFailures": "Consecutive failures:",
"oneproxyErrorLabel": "Error:",
"oneproxyNever": "Never",
"oneproxySyncStatusTitle": "Sync Status",
"oneproxySuccess": "Success",
"oneproxyFailed": "Failed",
"routingAntigravitySignatureTitle": "Antigravity Signature Cache Mode",
"routingHeaderFingerprintTitle": "Header fingerprint (per provider)",
"routingServerRejectedSave": "⚠ Server rejected save:",
"routingAddTransformOp": "Add a transform op",
"routingClientCacheControlTitle": "Client Cache Control",
"visionBridge": "Vision Bridge",
"routingZeroConfigTitle": "Zero-Config Auto-Routing",
"routingDefaultAutoVariant": "Default Auto Variant",
"visionBridgeModel": "Bridge Model",
"resilienceMaxBackoffSteps": "Max backoff steps",
"resilienceBaseCooldownLabel": "Base cooldown",
"resilienceUseUpstreamRetryHintsLabel": "Use upstream retry hints",
"resilienceYes": "Yes",
"resilienceNo": "No",
"resilienceUseUpstream429BreakerLabel": "Use upstream 429 hints (breaker)",
"resilienceDefault": "Default",
"resilienceMaxBackoffStepsLabel": "Max backoff steps",
"visionBridgePrompt": "Bridge Prompt",
"visionBridgeTimeoutMs": "Timeout (ms)",
"resilienceConnectionCooldownTitle": "Connection Cooldown",
"visionBridgeMaxImagesPerRequest": "Max Images Per Request",
"resilienceFailureThreshold": "Failure threshold",
"resilienceResetTimeout": "Reset timeout",
"resilienceFailureThresholdLabel": "Failure threshold",
"resilienceResetTimeoutLabel": "Reset timeout",
"visionBridgeModelPlaceholder": "openai/gpt-4o-mini",
"visionBridgePromptPlaceholder": "Describe this image concisely.",
"resilienceProviderBreakerTitle": "Circuit Breaker per Provider"
},
"contextRtk": {
"title": "RTK Engine",
@@ -5449,7 +5525,17 @@
"flowDiagramCliDesc": "Processes with own auth/model",
"fingerprintSettingsHint": "CLI Fingerprint matching (disguise requests as specific CLI tools) can be configured in",
"settingsRoutingLink": "Settings/Routing",
"openSettings": "Settings"
"openSettings": "Settings",
"copyRawUrlTitle": "Copy raw URL to clipboard",
"copied": "Copied!",
"copyUrl": "Copy URL",
"startHere": "Start Here",
"badgeNew": "New",
"viewOnGithub": "View on GitHub",
"howToUse": "How to use",
"browseAllSkillsOnGithub": "Browse all skills on GitHub",
"apiSkills": "API Skills",
"cliSkills": "CLI Skills"
},
"cloudAgents": {
"title": "Cloud Agents",