chore(i18n): replace hardcoded UI text with t() calls across dashboard (round 6 — 10 parallel agents)

Round-6 dispatched 10 parallel subagents covering all 57 remaining
dashboard files. Each agent worked on a disjoint file set to avoid
en.json race conditions. Added ~60 new i18n keys across 9 namespaces
covering small UI labels, table headers, search placeholders, and
empty-state messages.

Major changes:
- analytics: SearchAnalyticsTab, ProviderUtilizationTab, DiversityScoreCard, CompressionAnalyticsTab (new useTranslations + keys)
- batch: BatchDetailModal, BatchListTab, FileDetailModal, FilesListTab (new useTranslations + keys)
- settings: CliproxyapiSettingsTab, PayloadRulesTab, ModelCooldownsCard, AppearanceTab, PricingTab (mostly new useTranslations)
- endpoint: TokenSaverCard, ApiEndpointsTab, EndpointPageClient
- cache: CachePerformance, IdempotencyLayer, ReasoningCacheTab, MediaPageClient, page
- combos: IntelligentComboPanel, page
- playground: ChatPlayground, SearchPlayground
- providers: ProviderCard
- onboarding: TierFlowDiagram
- changelog: ChangelogViewer
- home: ProviderTopology, TierCoverageWidget, BootstrapBanner, BadgeToast
- usage: BudgetTab, BudgetTelemetryCards, QuotaTable
- quotaShare: QuotaSharePageClient
- profile: page
- leaderboard: page
- skills: page

Hardcoded total: 131 → 60. Real missing keys: 0 plus 1 false-positive
for combos.modePack (lookup via prop-passed t).
This commit is contained in:
diegosouzapw
2026-05-19 23:47:16 -03:00
parent c0c2efff97
commit 6e1105e2c2
35 changed files with 234 additions and 98 deletions

View File

@@ -1,12 +1,14 @@
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
/**
* Shown when OmniRoute was started with auto-generated secrets (zero-config mode).
* The banner is dismissable and persists only for the current session.
*/
export default function BootstrapBanner() {
const t = useTranslations("common");
const [dismissed, setDismissed] = useState(false);
if (dismissed) return null;
@@ -46,7 +48,7 @@ export default function BootstrapBanner() {
<button
onClick={() => setDismissed(true)}
className="shrink-0 text-amber-600/60 hover:text-amber-700 dark:text-amber-400/60 dark:hover:text-amber-300 transition-colors ml-1"
aria-label="Dismiss"
aria-label={t("bootstrapBannerDismiss")}
>
</button>

View File

@@ -65,8 +65,8 @@ export function TierCoverageWidget() {
<div className="rounded-xl border border-white/[0.06] bg-surface p-5">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="font-semibold text-sm">Tier coverage</h3>
<p className="text-xs text-text-muted mt-0.5">Providers configured per fallback tier</p>
<h3 className="font-semibold text-sm">{t("tierCoverageTitle")}</h3>
<p className="text-xs text-text-muted mt-0.5">{t("tierCoverageSubtitle")}</p>
</div>
<Link
href="/dashboard/providers"

View File

@@ -1,5 +1,6 @@
"use client";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useMemo, useState } from "react";
import {
CartesianGrid,
@@ -89,6 +90,7 @@ function getLatestPoints(points: ProviderUtilizationPoint[]) {
}
export default function ProviderUtilizationTab() {
const t = useTranslations("analytics");
const [range, setRange] = useState<UtilizationTimeRange>("24h");
const [aggregateBy, setAggregateBy] = useState<"provider" | "connection">("provider");
const [data, setData] = useState<ProviderUtilizationResponse | null>(null);
@@ -191,7 +193,7 @@ export default function ProviderUtilizationTab() {
return (
<div className="flex flex-col gap-6">
<Card
title="Provider utilization"
title={t("providerUtilizationTitle")}
subtitle={RANGE_LABELS[range]}
icon="monitoring"
action={
@@ -236,7 +238,9 @@ export default function ProviderUtilizationTab() {
<div className="flex min-h-80 flex-col items-center justify-center gap-4 text-center">
<span className="material-symbols-outlined text-[32px] text-error">error</span>
<div className="flex flex-col gap-1">
<p className="text-sm font-medium text-text-main">Failed to load utilization data</p>
<p className="text-sm font-medium text-text-main">
{t("providerUtilizationFailedToLoad")}
</p>
<p className="text-sm text-text-muted">{error}</p>
</div>
<button
@@ -266,13 +270,15 @@ export default function ProviderUtilizationTab() {
timeline
</span>
<div className="flex flex-col gap-2">
<p className="text-sm font-medium text-text-main">No utilization data available</p>
<p className="text-sm font-medium text-text-main">{t("providerUtilizationNoData")}</p>
<p className="max-w-md text-sm text-text-muted">
Provider quota snapshots will appear here after utilization data is collected.
</p>
</div>
<div className="rounded-lg border border-black/5 bg-black/[0.02] p-4 dark:border-white/5 dark:bg-white/[0.02]">
<p className="text-xs font-medium text-text-main">Getting started</p>
<p className="text-xs font-medium text-text-main">
{t("providerUtilizationGettingStarted")}
</p>
<ul className="mt-2 text-left text-xs text-text-muted">
<li className="flex items-start gap-2">
<span className="material-symbols-outlined text-[14px] text-primary">
@@ -369,7 +375,9 @@ export default function ProviderUtilizationTab() {
</div>
<div>
<p className="text-sm font-semibold text-text-main">{point.provider}</p>
<p className="text-xs text-text-muted">Latest quota snapshot</p>
<p className="text-xs text-text-muted">
{t("providerUtilizationLatestSnapshot")}
</p>
</div>
</div>
<span
@@ -390,7 +398,9 @@ export default function ProviderUtilizationTab() {
<p className="text-3xl font-bold text-text-main">
{point.remainingPct.toFixed(point.remainingPct < 10 ? 1 : 0)}%
</p>
<p className="mt-1 text-xs text-text-muted">Remaining capacity</p>
<p className="mt-1 text-xs text-text-muted">
{t("providerUtilizationRemainingCapacity")}
</p>
</div>
<div className="text-right text-xs text-text-muted">
<p>{formatTooltipTimestamp(point.timestamp, range)}</p>

View File

@@ -7,6 +7,7 @@
"use client";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
interface SearchStats {
@@ -76,6 +77,7 @@ function ProviderBar({
}
export default function SearchAnalyticsTab() {
const t = useTranslations("analytics");
const [stats, setStats] = useState<SearchStats | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@@ -122,25 +124,25 @@ export default function SearchAnalyticsTab() {
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<StatCard
icon="manage_search"
label="Total Searches"
label={t("searchAnalyticsTotalSearches")}
value={stats.total.toLocaleString()}
sub={`${stats.today} today`}
/>
<StatCard
icon="cached"
label="Cache Hit Rate"
label={t("searchAnalyticsCacheHitRate")}
value={`${stats.cacheHitRate}%`}
sub={`${stats.cached} cached requests`}
/>
<StatCard
icon="attach_money"
label="Total Cost"
label={t("searchAnalyticsTotalCost")}
value={`$${stats.totalCostUsd.toFixed(4)}`}
sub="search API costs"
/>
<StatCard
icon="timer"
label="Avg Response"
label={t("searchAnalyticsAvgResponse")}
value={`${stats.avgDurationMs}ms`}
sub={stats.errors > 0 ? `${stats.errors} errors` : "No errors"}
/>
@@ -173,7 +175,7 @@ export default function SearchAnalyticsTab() {
<span className="material-symbols-outlined text-[48px] mb-3 block text-primary opacity-50">
travel_explore
</span>
<p className="font-medium text-text">No searches yet</p>
<p className="font-medium text-text">{t("searchAnalyticsNoSearchesYet")}</p>
<p className="text-sm mt-1">
Use <code className="bg-bg-muted px-1 rounded">POST /v1/search</code> to start routing
web searches.

View File

@@ -1,5 +1,6 @@
"use client";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import { Card } from "@/shared/components";
@@ -15,6 +16,7 @@ interface DiversityReport {
}
export default function DiversityScoreCard() {
const t = useTranslations("analytics");
const [data, setData] = useState<DiversityReport | null>(null);
const [loading, setLoading] = useState(true);
@@ -82,7 +84,7 @@ export default function DiversityScoreCard() {
<div className="flex items-center justify-between gap-3 mb-4">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-[20px] text-primary">pie_chart</span>
<h3 className="font-semibold text-text-main">Provider Diversity</h3>
<h3 className="font-semibold text-text-main">{t("diversityScoreTitle")}</h3>
<span className="text-xs text-text-muted hidden sm:inline">
Provider concentration snapshot for the recent traffic window.
</span>

View File

@@ -1,6 +1,7 @@
"use client";
import { useEffect } from "react";
import { useTranslations } from "next-intl";
function relativeTime(ts: number): string {
const diffMs = Date.now() - ts * 1000;
@@ -135,6 +136,7 @@ function formatTs(ts: number | null | undefined): string {
}
export default function BatchDetailModal({ batch, files, onClose }: BatchDetailModalProps) {
const t = useTranslations("common");
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
@@ -182,7 +184,7 @@ export default function BatchDetailModal({ batch, files, onClose }: BatchDetailM
navigator.clipboard.writeText(batch.id);
}}
className="text-[var(--color-text-muted)] hover:text-[var(--color-text-main)] transition-colors"
title="Copy ID"
title={t("batchDetailCopyId")}
>
<span className="material-symbols-outlined text-[12px]">content_copy</span>
</button>
@@ -191,7 +193,7 @@ export default function BatchDetailModal({ batch, files, onClose }: BatchDetailM
</div>
<button
onClick={onClose}
aria-label="Close"
aria-label={t("batchDetailClose")}
className="p-1.5 rounded-lg text-[var(--color-text-muted)] hover:bg-[var(--color-bg-alt)] transition-colors"
>
<span className="material-symbols-outlined text-[20px]">close</span>
@@ -208,11 +210,11 @@ export default function BatchDetailModal({ batch, files, onClose }: BatchDetailM
</span>
<StatusBadge batch={batch} />
</div>
<Field label="Endpoint" value={batch.endpoint} />
{batch.model && <Field label="Model" value={batch.model} />}
<Field label="Window" value={batch.completionWindow} />
<Field label={t("batchDetailEndpoint")} value={batch.endpoint} />
{batch.model && <Field label={t("batchDetailModel")} value={batch.model} />}
<Field label={t("batchDetailWindow")} value={batch.completionWindow} />
<Field
label="Created"
label={t("batchDetailCreated")}
value={<span title={formatTs(batch.createdAt)}>{relativeTime(batch.createdAt)}</span>}
/>
</div>

View File

@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
import BatchDetailModal from "./BatchDetailModal";
function relativeTime(ts: number): string {
@@ -131,6 +132,7 @@ export default function BatchListTab({
loading,
onRefresh,
}: Readonly<BatchListTabProps>) {
const t = useTranslations("common");
const [selectedBatch, setSelectedBatch] = useState<BatchRecord | null>(null);
const [statusFilter, setStatusFilter] = useState("all");
const [searchQuery, setSearchQuery] = useState("");
@@ -199,7 +201,7 @@ export default function BatchListTab({
<div className="flex flex-wrap gap-3 p-4 rounded-xl bg-[var(--color-surface)] border border-[var(--color-border)]">
<input
type="text"
placeholder="Search by ID, endpoint, model…"
placeholder={t("batchListSearchPlaceholder")}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="flex-1 min-w-[200px] px-3 py-2 rounded-lg text-sm bg-[var(--color-bg)] border border-[var(--color-border)] text-[var(--color-text-main)] placeholder:text-[var(--color-text-muted)] focus:outline-2 focus:outline-[var(--color-accent)]"
@@ -219,7 +221,7 @@ export default function BatchListTab({
onClick={handleRemoveCompleted}
disabled={removingCompleted}
className="flex items-center gap-1.5 px-3 py-2 text-sm rounded-lg bg-red-500/10 border border-red-500/25 text-red-400 hover:text-red-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
title="Delete all completed batches"
title={t("batchListDeleteAllCompletedTitle")}
>
<span className="material-symbols-outlined text-[16px]">
{removingCompleted ? "hourglass_empty" : "delete_sweep"}
@@ -230,7 +232,7 @@ export default function BatchListTab({
{/* Table */}
<div className="overflow-x-auto overflow-y-hidden rounded-xl border border-[var(--color-border)]">
<table className="w-full text-sm" role="table" aria-label="Batches">
<table className="w-full text-sm" role="table" aria-label={t("batchListBatchesTable")}>
<thead>
<tr className="bg-[var(--color-bg-alt)] border-b border-[var(--color-border)]">
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)] uppercase text-xs tracking-wider">
@@ -338,7 +340,7 @@ export default function BatchListTab({
onClick={(e) => handleDeleteBatch(e, batch)}
disabled={deletingId === batch.id}
className="flex items-center gap-1 px-2 py-1 text-xs rounded bg-red-500/10 border border-red-500/25 text-red-400 hover:text-red-300 transition-colors whitespace-nowrap disabled:opacity-50"
title="Delete batch and its files"
title={t("batchListDeleteBatchTitle")}
>
<span className="material-symbols-outlined text-[13px]">
{deletingId === batch.id ? "hourglass_empty" : "delete"}

View File

@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import { Button } from "@/shared/components";
function relativeTime(ts: number): string {
@@ -69,6 +70,7 @@ export default function FileDetailModal({
batches,
onClose,
}: Readonly<FileDetailModalProps>) {
const t = useTranslations("common");
const [copied, setCopied] = useState(false);
const relatedBatches = (batches ?? []).filter(
@@ -140,7 +142,7 @@ export default function FileDetailModal({
navigator.clipboard.writeText(file.id);
}}
className="text-[var(--color-text-muted)] hover:text-[var(--color-text-main)] transition-colors"
title="Copy ID"
title={t("batchFileDetailCopyId")}
>
<span className="material-symbols-outlined text-[12px]">content_copy</span>
</button>
@@ -149,7 +151,7 @@ export default function FileDetailModal({
</div>
<button
onClick={onClose}
aria-label="Close"
aria-label={t("batchFileDetailClose")}
className="p-1.5 rounded-lg text-[var(--color-text-muted)] hover:bg-[var(--color-bg-alt)] transition-colors"
>
<span className="material-symbols-outlined text-[20px]">close</span>
@@ -266,7 +268,7 @@ export default function FileDetailModal({
<span className="material-symbols-outlined text-[40px] mb-2 opacity-20">
find_in_page
</span>
<p className="text-sm">Failed to load file contents</p>
<p className="text-sm">{t("batchFileDetailFailedToLoad")}</p>
</div>
)}
</div>

View File

@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
import FileDetailModal from "./FileDetailModal";
function relativeTime(ts: number): string {
@@ -84,6 +85,7 @@ export default function FilesListTab({
onRefresh,
batches,
}: Readonly<FilesListTabProps>) {
const t = useTranslations("common");
const [searchQuery, setSearchQuery] = useState("");
const [purposeFilter, setPurposeFilter] = useState("all");
const [selectedFileId, setSelectedFileId] = useState<string | null>(null);

View File

@@ -1,6 +1,7 @@
"use client";
import React from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
interface CachePerformanceProps {
@@ -68,6 +69,7 @@ export default function CachePerformance({
onRetry,
stats,
}: CachePerformanceProps) {
const t = useTranslations("cache");
// Parse hitRate string (e.g. "85.0%") to number for the bar
const hitRateNum = hitRate ? parseFloat(hitRate) : 0;
@@ -87,7 +89,7 @@ export default function CachePerformance({
<button
onClick={onRetry}
className="self-start text-xs px-3 py-1.5 rounded bg-surface border border-border/50 hover:bg-surface/80 transition-colors"
aria-label="Retry"
aria-label={t("cachePerformanceRetry")}
>
Retry
</button>
@@ -117,7 +119,9 @@ export default function CachePerformance({
{!loading && !error && stats !== null && (
<>
{/* Hit rate bar */}
{hitRate !== undefined && <HitRateBar hitRate={hitRateNum} label="Hit Rate" />}
{hitRate !== undefined && (
<HitRateBar hitRate={hitRateNum} label={t("cachePerformanceHitRate")} />
)}
{/* Hit / Miss / Total breakdown */}
<div className="grid grid-cols-3 gap-4 pt-3 border-t border-border/30 text-center">
@@ -141,13 +145,17 @@ export default function CachePerformance({
{avgLatencyMs !== undefined && (
<div>
<div className="text-lg font-semibold tabular-nums">{avgLatencyMs}</div>
<div className="text-xs text-text-muted mt-0.5">Avg Latency (ms)</div>
<div className="text-xs text-text-muted mt-0.5">
{t("cachePerformanceAvgLatency")}
</div>
</div>
)}
{p95LatencyMs !== undefined && (
<div>
<div className="text-lg font-semibold tabular-nums">{p95LatencyMs}</div>
<div className="text-xs text-text-muted mt-0.5">P95 Latency (ms)</div>
<div className="text-xs text-text-muted mt-0.5">
{t("cachePerformanceP95Latency")}
</div>
</div>
)}
</div>

View File

@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import ReactMarkdown, { type Components } from "react-markdown";
import { Button } from "@/shared/components";
import {
@@ -80,6 +81,7 @@ const markdownComponents: Components = {
};
export default function ChangelogViewer() {
const t = useTranslations("common");
const [markdown, setMarkdown] = useState("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
@@ -109,7 +111,7 @@ export default function ChangelogViewer() {
<span className="material-symbols-outlined animate-spin text-[32px] text-text-muted/50">
sync
</span>
<p className="text-sm text-text-muted">Loading changelog from GitHub...</p>
<p className="text-sm text-text-muted">{t("changelogViewerLoading")}</p>
</div>
);
}

View File

@@ -649,8 +649,8 @@ openai_base_url = "${getEffectiveBaseUrl()}"
onChange={(e) => setWireApi(e.target.value)}
className="flex-1 px-2 py-1.5 bg-surface rounded text-xs border border-border focus:outline-none focus:ring-1 focus:ring-primary/50"
>
<option value="chat">Chat Completions (/chat/completions)</option>
<option value="responses">Responses API (/responses)</option>
<option value="chat">{t("wireApiChatCompletions")}</option>
<option value="responses">{t("wireApiResponses")}</option>
</select>
</div>

View File

@@ -287,7 +287,9 @@ export default function IntelligentComboPanel({
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2">
<div className="rounded-lg border border-black/8 bg-white/60 p-3 dark:border-white/8 dark:bg-white/[0.03]">
<p className="text-[11px] uppercase tracking-wide text-text-muted">Mode Pack</p>
<p className="text-[11px] uppercase tracking-wide text-text-muted">
{t("modePack")}
</p>
<p className="mt-1 text-sm font-semibold text-text-main">
{normalizedConfig.modePack}
</p>

View File

@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect, useCallback, useRef } from "react";
import { useTranslations } from "next-intl";
interface BadgeUnlockEvent {
badgeId: string;
@@ -20,6 +21,7 @@ const RECONNECT_BASE_MS = 1000;
const RECONNECT_MAX_MS = 30000;
export function BadgeToast({ apiKeyId }: { apiKeyId: string }) {
const t = useTranslations("common");
const [toasts, setToasts] = useState<BadgeUnlockEvent[]>([]);
const timeoutIds = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());
@@ -92,7 +94,7 @@ export function BadgeToast({ apiKeyId }: { apiKeyId: string }) {
>
<span className="text-2xl">🏆</span>
<div>
<div className="font-semibold text-white">Badge Unlocked!</div>
<div className="font-semibold text-white">{t("badgeToastUnlocked")}</div>
<div className="text-sm text-text-muted">{toast.badgeName}</div>
</div>
</div>

View File

@@ -339,11 +339,8 @@ export default function QuotaSharePageClient() {
<div className="rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-xs text-amber-700 dark:text-amber-300 flex items-start gap-2">
<span className="material-symbols-outlined text-[18px] shrink-0">science</span>
<div>
<strong>Beta UI preview.</strong> A configuração é salva em <code>localStorage</code>{" "}
(não persiste no servidor ainda). A aplicação dos caps por request ainda não está
conectada ao pipeline da proxy. Esta tela permite desenhar e visualizar a divisão de cota;
a aplicação real virá em uma próxima iteração com persistência no banco e interceptação na
chamada upstream.
<strong>{t("betaPreviewLabel")}</strong> {t("betaConfigSavedPrefix")}{" "}
<code>localStorage</code> {t("betaConfigSavedSuffix")}
</div>
</div>
@@ -598,7 +595,9 @@ function PoolCard({
<div className="mt-3 flex items-center justify-between gap-2 flex-wrap text-[11px]">
<div className="flex items-center gap-1">
<span className="text-text-muted font-semibold uppercase tracking-wide">Policy:</span>
<span className="text-text-muted font-semibold uppercase tracking-wide">
{t("policyLabel")}
</span>
{(["hard", "soft", "burst"] as PoolPolicy[]).map((p) => (
<button
key={p}

View File

@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect, useMemo } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
import { useDisplayBaseUrl } from "@/shared/hooks";
@@ -44,6 +45,7 @@ const METHOD_COLORS: Record<string, string> = {
/* ─── Main Component ─────────────────────────────────── */
export default function ApiEndpointsTab() {
const t = useTranslations("endpoint");
const baseUrl = useDisplayBaseUrl();
const [catalog, setCatalog] = useState<CatalogData | null>(null);
const [catalogError, setCatalogError] = useState<string | null>(null);
@@ -224,7 +226,9 @@ export default function ApiEndpointsTab() {
<span className="material-symbols-outlined text-[20px] text-red-500">error</span>
</div>
<div>
<h3 className="text-sm font-semibold text-text-main">API catalog unavailable</h3>
<h3 className="text-sm font-semibold text-text-main">
{t("apiEndpointsCatalogUnavailable")}
</h3>
<p className="text-xs text-text-muted mt-1">
{catalogError || "The OpenAPI specification could not be loaded."}
</p>
@@ -254,7 +258,7 @@ export default function ApiEndpointsTab() {
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search endpoints..."
placeholder={t("apiEndpointsSearchPlaceholder")}
className="w-full pl-9 pr-3 py-2 text-xs rounded-lg border border-black/10 dark:border-white/10
bg-white dark:bg-black/20 focus:outline-none focus:ring-1 focus:ring-primary"
/>
@@ -334,7 +338,7 @@ export default function ApiEndpointsTab() {
{ep.security && (
<span
className="material-symbols-outlined text-[12px] text-amber-500"
title="Requires auth"
title={t("apiEndpointsRequiresAuth")}
>
lock
</span>
@@ -482,7 +486,7 @@ export default function ApiEndpointsTab() {
<span className="material-symbols-outlined text-[32px] text-text-muted">
search_off
</span>
<p className="text-sm text-text-muted mt-2">No endpoints match your filter</p>
<p className="text-sm text-text-muted mt-2">{t("apiEndpointsNoMatch")}</p>
</Card>
)}

View File

@@ -1281,7 +1281,7 @@ export default function APIPageClient({ machineId }: Readonly<APIPageClientProps
</span>
<div className="flex-1 min-w-0">
<div className="flex items-baseline gap-1 flex-wrap">
<span className="text-sm font-medium">Local Server</span>
<span className="text-sm font-medium">{t("localServer")}</span>
{resolvedMachineId && (
<span className="text-xs text-text-muted">· {resolvedMachineId.slice(0, 8)}</span>
)}

View File

@@ -2,6 +2,7 @@
import { useCallback, useEffect, useState } from "react";
import Link from "next/link";
import { useTranslations } from "next-intl";
import { Card, Toggle } from "@/shared/components";
import { useNotificationStore } from "@/store/notificationStore";
@@ -111,6 +112,7 @@ function EngineRow({
}
export default function TokenSaverCard() {
const t = useTranslations("endpoint");
const notify = useNotificationStore();
const [config, setConfig] = useState<CompressionConfig | null>(null);
const [loading, setLoading] = useState(true);
@@ -182,14 +184,14 @@ export default function TokenSaverCard() {
</span>
)}
</h2>
<p className="text-sm text-text-muted mt-1">Spend less tokens on every request.</p>
<p className="text-sm text-text-muted mt-1">{t("tokenSaverSubtitle")}</p>
</div>
<Toggle size="md" checked={masterEnabled} onChange={(v) => save({ enabled: v })} />
</div>
<div className="divide-y divide-border mt-4">
<EngineRow
title="Tool output"
title={t("tokenSaverToolOutput")}
badge="RTK"
href="/dashboard/context/rtk"
description="git/grep/ls/tree/logs cleaner → 60-90% fewer input tokens"
@@ -206,7 +208,7 @@ export default function TokenSaverCard() {
}
/>
<EngineRow
title="LLM output"
title={t("tokenSaverLlmOutput")}
badge="Caveman"
href="/dashboard/context/caveman"
description="Terse-style system prompt → ~65% fewer output tokens (up to 87%)"
@@ -223,7 +225,7 @@ export default function TokenSaverCard() {
}
/>
<EngineRow
title="Input compression"
title={t("tokenSaverInputCompression")}
badge="Caveman"
href="/dashboard/context/caveman"
description="Rewrite chat history → ~50% fewer input tokens"

View File

@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect, useCallback, useRef } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
type LeaderboardScope = "global" | "weekly" | "monthly" | "tokens_shared";
@@ -26,6 +27,7 @@ const MEDAL_COLORS = [
const MEDAL_EMOJI = ["🥇", "🥈", "🥉"];
export default function LeaderboardPage() {
const t = useTranslations("common");
const [scope, setScope] = useState<LeaderboardScope>("global");
const [entries, setEntries] = useState<LeaderboardEntry[]>([]);
const [myRank, setMyRank] = useState<number | null>(null);
@@ -110,7 +112,7 @@ export default function LeaderboardPage() {
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-text-muted">Your Rank</p>
<p className="text-sm text-text-muted">{t("leaderboardYourRank")}</p>
<p className="text-3xl font-bold mt-1">#{myRank}</p>
</div>
<div className="text-right">
@@ -125,7 +127,7 @@ export default function LeaderboardPage() {
{loading ? (
<div className="flex items-center justify-center min-h-[200px]">
<div className="text-text-muted">Loading leaderboard...</div>
<div className="text-text-muted">{t("leaderboardLoading")}</div>
</div>
) : (
<>

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 { copyToClipboard } from "@/shared/utils/clipboard";
import McpDashboardPage from "../endpoint/components/MCPDashboard";
@@ -98,6 +99,7 @@ function TransportSelector({
disabled: boolean;
baseUrl: string;
}) {
const t = useTranslations("mcpDashboard");
const options: { value: McpTransport; label: string; desc: string }[] = [
{ value: "stdio", label: "stdio", desc: "Local — IDE spawns process via omniroute --mcp" },
{ value: "sse", label: "SSE", desc: "Remote — Server-Sent Events over HTTP" },
@@ -181,7 +183,7 @@ function TransportSelector({
className="ml-auto text-xs px-2 py-0.5 rounded border hover:opacity-80 transition-opacity"
style={{ borderColor: "var(--color-border)", color: "var(--color-text-muted)" }}
onClick={() => void copyToClipboard(urlMap[value])}
title="Copy URL"
title={t("mcpDashboardCopyUrl")}
>
Copy
</button>

View File

@@ -1,9 +1,11 @@
"use client";
import { useTheme } from "next-themes";
import { useTranslations } from "next-intl";
import Image from "next/image";
export function TierFlowDiagram() {
const t = useTranslations("onboarding");
const { resolvedTheme } = useTheme();
const src =
resolvedTheme === "dark" ? "/images/tier-flow-dark.svg" : "/images/tier-flow-light.svg";
@@ -12,7 +14,7 @@ export function TierFlowDiagram() {
<div className="flex flex-col items-center gap-3 my-4">
<Image
src={src}
alt="OmniRoute 3-tier fallback diagram"
alt={t("tierFlowDiagramAlt")}
width={800}
height={420}
priority

View File

@@ -222,7 +222,7 @@ export default function ChatPlayground({
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-[18px] text-text-muted">chat</span>
<h3 className="text-sm font-semibold text-text-main">Conversational Chat</h3>
<h3 className="text-sm font-semibold text-text-main">{t("conversationalChat")}</h3>
{responseStatus !== null && (
<Badge variant={responseStatus < 400 ? "success" : "error"} size="sm">
{responseStatus}
@@ -235,7 +235,7 @@ export default function ChatPlayground({
<button
onClick={handleClear}
className="p-1.5 rounded hover:bg-red-500/10 text-text-muted hover:text-red-500 transition-colors"
title="Clear chat"
title={t("clearChat")}
>
<span className="material-symbols-outlined text-[16px]">delete</span>
</button>
@@ -292,7 +292,7 @@ export default function ChatPlayground({
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Type a message... (Shift+Enter for new line)"
placeholder={t("typeMessagePlaceholder")}
className="flex-1 min-h-[44px] max-h-[120px] bg-surface border border-border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary resize-y"
rows={1}
disabled={loading || noModels}

View File

@@ -175,7 +175,7 @@ export default function SearchPlayground() {
<button
onClick={() => navigator.clipboard.writeText(requestBody)}
className="p-1.5 rounded hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-text-main transition-colors"
title="Copy"
title={t("copy")}
>
<span className="material-symbols-outlined text-[16px]">content_copy</span>
</button>
@@ -194,7 +194,7 @@ export default function SearchPlayground() {
)
}
className="p-1.5 rounded hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-text-main transition-colors"
title="Reset to default"
title={t("resetToDefault")}
>
<span className="material-symbols-outlined text-[16px]">restart_alt</span>
</button>

View File

@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { useTranslations } from "next-intl";
import { Card, Badge } from "@/shared/components";
import {
xpForLevel,
@@ -57,6 +58,7 @@ const RARITY_COLORS: Record<string, string> = {
};
export default function ProfilePage() {
const t = useTranslations("common");
const [userLevel, setUserLevel] = useState<UserLevel | null>(null);
const [allBadges, setAllBadges] = useState<BadgeDef[]>([]);
const [earnedBadges, setEarnedBadges] = useState<UserBadge[]>([]);
@@ -99,7 +101,7 @@ export default function ProfilePage() {
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-text-muted">Loading profile...</div>
<div className="text-text-muted">{t("profileLoading")}</div>
</div>
);
}
@@ -269,7 +271,7 @@ export default function ProfilePage() {
{selectedBadge.criteria && (
<div className="p-3 rounded-lg bg-surface/50 border border-border/50">
<p className="text-xs font-medium text-text-muted mb-1">How to earn</p>
<p className="text-xs font-medium text-text-muted mb-1">{t("profileHowToEarn")}</p>
<p className="text-sm">{selectedBadge.criteria}</p>
</div>
)}

View File

@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { useTranslations } from "next-intl";
import { Card, Button, Input, Toggle } from "@/shared/components";
interface Settings {
@@ -28,6 +29,7 @@ function isValidUrl(value: string): boolean {
}
export default function CliproxyapiSettingsTab() {
const t = useTranslations("settings");
const [settings, setSettings] = useState<Settings>({});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@@ -139,7 +141,7 @@ export default function CliproxyapiSettingsTab() {
<span className="material-symbols-outlined text-indigo-500 text-xl">swap_horiz</span>
</div>
<div>
<h3 className="font-medium text-sm">CLIProxyAPI Fallback</h3>
<h3 className="font-medium text-sm">{t("cliproxyapiFallback")}</h3>
<p className="text-xs text-text-muted">
When enabled, failed requests are retried through CLIProxyAPI (localhost:8317)
</p>
@@ -148,7 +150,7 @@ export default function CliproxyapiSettingsTab() {
<div className="space-y-4">
<div className="flex items-center justify-between">
<label className="text-sm text-text-main">Enable CLIProxyAPI Fallback</label>
<label className="text-sm text-text-main">{t("cliproxyapiEnableFallback")}</label>
<Toggle
checked={cpaEnabled}
onChange={(checked) => updateSetting("cliproxyapi_fallback_enabled", checked)}
@@ -158,7 +160,9 @@ export default function CliproxyapiSettingsTab() {
{cpaEnabled && (
<>
<div>
<label className="text-xs text-text-muted mb-1.5 block">CLIProxyAPI URL</label>
<label className="text-xs text-text-muted mb-1.5 block">
{t("cliproxyapiUrl")}
</label>
<Input
value={cpaUrl}
onChange={(e) => updateSetting("cliproxyapi_url", e.target.value)}
@@ -184,7 +188,7 @@ export default function CliproxyapiSettingsTab() {
</Card>
<Card padding="md">
<h3 className="font-medium text-sm mb-4">CLIProxyAPI Status</h3>
<h3 className="font-medium text-sm mb-4">{t("cliproxyapiStatus")}</h3>
{loading ? (
<div className="flex items-center gap-2 text-text-muted text-sm">
<span className="material-symbols-outlined animate-spin text-base">
@@ -237,7 +241,7 @@ export default function CliproxyapiSettingsTab() {
</div>
</div>
) : (
<p className="text-sm text-text-muted">CLIProxyAPI not detected</p>
<p className="text-sm text-text-muted">{t("cliproxyapiNotDetected")}</p>
)}
</Card>
</div>

View File

@@ -1,6 +1,7 @@
"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslations } from "next-intl";
import { Button, Card } from "@/shared/components";
import { useNotificationStore } from "@/store/notificationStore";
@@ -20,6 +21,7 @@ function formatRemaining(ms: number): string {
}
export default function ModelCooldownsCard() {
const t = useTranslations("settings");
const notify = useNotificationStore();
const [items, setItems] = useState<CooldownItem[]>([]);
const [loading, setLoading] = useState(true);
@@ -95,7 +97,7 @@ export default function ModelCooldownsCard() {
<Card className="p-6">
<div className="flex items-start justify-between gap-4">
<div>
<h2 className="text-lg font-bold text-text-main">Models in cooldown</h2>
<h2 className="text-lg font-bold text-text-main">{t("modelCooldownsTitle")}</h2>
<p className="mt-1 text-sm text-text-muted">
Models temporarily isolated after a failure. When the cooldown expires they come back
automatically.
@@ -120,7 +122,7 @@ export default function ModelCooldownsCard() {
{loading ? (
<p className="text-sm text-text-muted">Loading...</p>
) : !hasItems ? (
<p className="text-sm text-text-muted">No models in cooldown right now.</p>
<p className="text-sm text-text-muted">{t("modelCooldownsEmpty")}</p>
) : (
sorted.map((item) => {
const rowKey = `${item.provider}::${item.model}`;

View File

@@ -1,6 +1,7 @@
"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslations } from "next-intl";
import { Card, Button } from "@/shared/components";
const EMPTY_PAYLOAD_RULES_TEMPLATE = {
@@ -55,6 +56,7 @@ function getErrorMessage(payload: unknown): string {
}
export default function PayloadRulesTab() {
const t = useTranslations("settings");
const [editorValue, setEditorValue] = useState(EMPTY_PAYLOAD_RULES_TEXT);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@@ -162,7 +164,7 @@ export default function PayloadRulesTab() {
</span>
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold">Payload Rules</h3>
<h3 className="text-lg font-semibold">{t("payloadRulesTitle")}</h3>
<p className="text-sm text-text-muted mt-1">
Configure request payload mutations by model and protocol. Changes are persisted in
settings and hot reloaded into the runtime immediately after save.

View File

@@ -874,7 +874,7 @@ export default function ProxyRegistryManager() {
value={bulkProxyId}
onChange={(e) => setBulkProxyId(e.target.value)}
>
<option value="">(clear assignment)</option>
<option value="">{t("clearAssignment")}</option>
{items.map((item) => (
<option key={item.id} value={item.id}>
{item.name} ({item.type}://{item.host}:{item.port})

View File

@@ -372,7 +372,7 @@ export default function SkillsPage() {
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Filter skills by name, description, or tag"
placeholder={t("filterSkillsPlaceholder")}
className="px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
/>
<select
@@ -380,7 +380,7 @@ export default function SkillsPage() {
onChange={(e) => setModeFilter(e.target.value as "all" | "on" | "off" | "auto")}
className="px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
>
<option value="all">All modes</option>
<option value="all">{t("allModes")}</option>
<option value="on">On</option>
<option value="auto">Auto</option>
<option value="off">Off</option>
@@ -659,7 +659,7 @@ export default function SkillsPage() {
{activeTab === "marketplace" && (
<div className="grid gap-4">
<Card>
<h3 className="font-semibold mb-2">Skills Marketplace</h3>
<h3 className="font-semibold mb-2">{t("skillsMarketplace")}</h3>
<p className="text-sm text-text-muted mb-4">
Active provider:{" "}
<span className="font-medium">
@@ -775,7 +775,7 @@ export default function SkillsPage() {
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="bg-surface border border-border rounded-xl p-6 w-full max-w-lg mx-4">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">Install Skill</h2>
<h2 className="text-lg font-semibold">{t("installSkill")}</h2>
<button
onClick={() => {
setShowInstallModal(false);

View File

@@ -573,7 +573,7 @@ export default function PlaygroundMode() {
</div>
{compressionResult.techniquesUsed.length > 0 && (
<div className="text-xs text-text-muted">
<span className="font-semibold">Techniques:</span>{" "}
<span className="font-semibold">{t("techniques")}</span>{" "}
{compressionResult.techniquesUsed.join(", ")}
</div>
)}

View File

@@ -892,7 +892,7 @@ function BudgetRowExpanded({
{t("budgetLoading")}
</div>
) : breakdown.length === 0 ? (
<div className="text-[11px] text-text-muted py-2">No spend in last 30 days</div>
<div className="text-[11px] text-text-muted py-2">{t("noSpendLast30Days")}</div>
) : (
<div className="space-y-1.5">
{breakdown.slice(0, 5).map((b) => (

View File

@@ -52,11 +52,11 @@ export default function BudgetTelemetryCards() {
<span className="font-mono">{telemetry.totalRequests ?? 0}</span>
</div>
<div className="flex justify-between">
<span className="text-text-muted">Active sessions</span>
<span className="text-text-muted">{t("activeSessions")}</span>
<span className="font-mono">{telemetry.sessions?.activeCount ?? 0}</span>
</div>
<div className="flex justify-between">
<span className="text-text-muted">Quota alerts</span>
<span className="text-text-muted">{t("quotaAlerts")}</span>
<span className="font-mono">{telemetry.quotaMonitor?.alerting ?? 0}</span>
</div>
</div>

View File

@@ -142,7 +142,7 @@ export default function QuotaTable({ quotas = [] }) {
{/* Reset Time */}
<td className="py-2 px-3">
{staleAfterReset ? (
<div className="text-xs text-text-muted"> Refreshing...</div>
<div className="text-xs text-text-muted">{t("quotaTableRefreshing")}</div>
) : countdown !== t("notAvailableSymbol") || resetDisplay ? (
<div className="space-y-0.5">
{countdown !== t("notAvailableSymbol") && (

View File

@@ -1,6 +1,7 @@
"use client";
import { useMemo, useState, useEffect, useCallback, useRef } from "react";
import { useTranslations } from "next-intl";
import {
ReactFlow,
Handle,
@@ -280,6 +281,7 @@ export default function ProviderTopology({
lastProvider = "",
errorProvider = "",
}: Props) {
const t = useTranslations("common");
const activeKey = useMemo(
() =>
activeRequests
@@ -377,7 +379,7 @@ export default function ProviderTopology({
{providers.length === 0 ? (
<div className="h-full flex flex-col items-center justify-center gap-2 text-text-muted">
<span className="material-symbols-outlined text-[32px]">device_hub</span>
<p className="text-sm">No providers connected yet</p>
<p className="text-sm">{t("providerTopologyEmpty")}</p>
</div>
) : (
<ReactFlow

View File

@@ -679,7 +679,30 @@
"tokensMaxUses": "Max Uses",
"tokensRedeemCode": "Redeem Code",
"tokensRedeemCodePlaceholder": "Enter invite code",
"tokensYourActiveInvites": "Your Active Invites"
"tokensYourActiveInvites": "Your Active Invites",
"tierCoverageTitle": "Tier coverage",
"tierCoverageSubtitle": "Providers configured per fallback tier",
"batchDetailCopyId": "Copy ID",
"batchDetailClose": "Close",
"batchDetailEndpoint": "Endpoint",
"batchDetailModel": "Model",
"batchDetailWindow": "Window",
"batchDetailCreated": "Created",
"providerTopologyEmpty": "No providers connected yet",
"badgeToastUnlocked": "Badge Unlocked!",
"batchListSearchPlaceholder": "Search by ID, endpoint, model…",
"batchListDeleteAllCompletedTitle": "Delete all completed batches",
"batchListBatchesTable": "Batches",
"changelogViewerLoading": "Loading changelog from GitHub...",
"profileLoading": "Loading profile...",
"profileHowToEarn": "How to earn",
"bootstrapBannerDismiss": "Dismiss",
"batchListDeleteBatchTitle": "Delete batch and its files",
"leaderboardYourRank": "Your Rank",
"leaderboardLoading": "Loading leaderboard...",
"batchFileDetailCopyId": "Copy ID",
"batchFileDetailClose": "Close",
"batchFileDetailFailedToLoad": "Failed to load file contents"
},
"sidebar": {
"home": "Home",
@@ -1177,7 +1200,19 @@
"compressionAnalyticsCompletionTokens": "Completion tokens",
"compressionAnalyticsTotalTokens": "Total tokens",
"compressionAnalyticsCacheTokens": "Cache tokens",
"compressionAnalyticsNoDataYet": "No compression data yet"
"compressionAnalyticsNoDataYet": "No compression data yet",
"searchAnalyticsTotalSearches": "Total Searches",
"searchAnalyticsCacheHitRate": "Cache Hit Rate",
"searchAnalyticsTotalCost": "Total Cost",
"searchAnalyticsAvgResponse": "Avg Response",
"searchAnalyticsNoSearchesYet": "No searches yet",
"providerUtilizationTitle": "Provider utilization",
"providerUtilizationFailedToLoad": "Failed to load utilization data",
"providerUtilizationNoData": "No utilization data available",
"providerUtilizationGettingStarted": "Getting started",
"providerUtilizationLatestSnapshot": "Latest quota snapshot",
"providerUtilizationRemainingCapacity": "Remaining capacity",
"diversityScoreTitle": "Provider Diversity"
},
"apiManager": {
"title": "API Keys",
@@ -1390,7 +1425,9 @@
"rerank": "Rerank",
"rerankModel": "Rerank Model",
"positionDelta": "Position Change",
"emptyState": "Send a search query to see results"
"emptyState": "Send a search query to see results",
"copy": "Copy",
"resetToDefault": "Reset to default"
},
"cliTools": {
"title": "CLI Tools",
@@ -1736,7 +1773,9 @@
"copilotMaxInputTokens": "Max Input Tokens",
"copilotMaxOutputTokens": "Max Output Tokens",
"copilotToolCalling": "Tool Calling",
"copilotPasteInto": "Paste into: "
"copilotPasteInto": "Paste into: ",
"wireApiChatCompletions": "Chat Completions (/chat/completions)",
"wireApiResponses": "Responses API (/responses)"
},
"combos": {
"title": "Combos",
@@ -2201,7 +2240,8 @@
"agentFeaturesContextLengthHint": "Defines the context window for this combo in /v1/models.",
"agentFeaturesContextLengthErrorInteger": "Context length must be a valid integer",
"agentFeaturesContextLengthErrorRange": "Context length must be between 1000 and 2000000",
"compressionOverride": "Compression Override"
"compressionOverride": "Compression Override",
"modePack": "Mode Pack"
},
"costs": {
"title": "Costs",
@@ -2457,7 +2497,15 @@
"ngrokLastError": "Last error: {error}",
"ngrokStarted": "ngrok tunnel started",
"ngrokStopped": "ngrok tunnel stopped",
"ngrokRequestFailed": "Failed to update ngrok tunnel"
"ngrokRequestFailed": "Failed to update ngrok tunnel",
"tokenSaverSubtitle": "Spend less tokens on every request.",
"tokenSaverToolOutput": "Tool output",
"tokenSaverLlmOutput": "LLM output",
"tokenSaverInputCompression": "Input compression",
"apiEndpointsCatalogUnavailable": "API catalog unavailable",
"apiEndpointsSearchPlaceholder": "Search endpoints...",
"apiEndpointsRequiresAuth": "Requires auth",
"apiEndpointsNoMatch": "No endpoints match your filter"
},
"endpoints": {
"tabProxy": "Endpoint Proxy",
@@ -2541,7 +2589,8 @@
"apiKeyId": "Api Key Id",
"offset": "Offset",
"limit": "Limit",
"tool": "Tool"
"tool": "Tool",
"mcpDashboardCopyUrl": "Copy URL"
},
"a2aDashboard": {
"loading": "Loading A2A dashboard...",
@@ -2672,7 +2721,10 @@
"networkAccess": "Network Access",
"networkAccessDesc": "Allow outbound network requests",
"mode": "Mode",
"q": "Q"
"q": "Q",
"filterSkillsPlaceholder": "Filter skills by name, description, or tag",
"allModes": "All modes",
"skillsMarketplace": "Skills Marketplace"
},
"health": {
"title": "System Health",
@@ -2962,7 +3014,8 @@
"description": "Locally-hosted or specialty endpoints used as fallbacks."
},
"configure": "Configure providers"
}
},
"tierFlowDiagramAlt": "OmniRoute 3-tier fallback diagram"
},
"providers": {
"title": "Providers",
@@ -4448,7 +4501,15 @@
"memorySkillsSkillsmpMarketplace": "SkillsMP Marketplace",
"memorySkillsFailedToSave": "Failed to save",
"memorySkillsApiKey": "API Key",
"memorySkillsActiveSkillsProvider": "Active Skills Provider"
"memorySkillsActiveSkillsProvider": "Active Skills Provider",
"cliproxyapiFallback": "CLIProxyAPI Fallback",
"cliproxyapiEnableFallback": "Enable CLIProxyAPI Fallback",
"cliproxyapiUrl": "CLIProxyAPI URL",
"cliproxyapiStatus": "CLIProxyAPI Status",
"cliproxyapiNotDetected": "Not detected",
"payloadRulesTitle": "Payload Rules",
"modelCooldownsTitle": "Models in cooldown",
"modelCooldownsEmpty": "No models in cooldown right now."
},
"contextRtk": {
"title": "RTK Engine",
@@ -4785,7 +4846,8 @@
"routeEndpointLabel": "Endpoint",
"routeConnectionLabel": "Connection",
"scenarioVision": "Vision (image understanding)",
"scenarioSchemaCoercion": "Schema coercion (structured output)"
"scenarioSchemaCoercion": "Schema coercion (structured output)",
"techniques": "Techniques:"
},
"usage": {
"title": "Usage",
@@ -5086,7 +5148,9 @@
"budgetDailyDollar": "Daily $",
"budgetWeeklyDollar": "Weekly $",
"budgetMonthlyDollar": "Monthly $",
"budgetWarnAtPct": "Warn at %"
"budgetWarnAtPct": "Warn at %",
"quotaAlerts": "Quota alerts",
"quotaTableRefreshing": "⟳ Refreshing..."
},
"modals": {
"waitingAuth": "Waiting for Authorization",
@@ -5847,7 +5911,10 @@
"reasoningClearAll": "Clear Reasoning Cache",
"reasoningClearSuccess": "Cleared {count} reasoning cache entries",
"reasoningClearError": "Failed to clear reasoning cache",
"reasoningNoData": "No reasoning entries cached yet. Entries appear when thinking models use tool calling."
"reasoningNoData": "No reasoning entries cached yet. Entries appear when thinking models use tool calling.",
"cachePerformanceRetry": "Retry",
"cachePerformanceHitRate": "Hit Rate",
"cachePerformanceAvgLatency": "Avg Latency (ms)"
},
"proxyConfigModal": {
"levelGlobal": "Global",
@@ -6027,7 +6094,8 @@
"bulkImportErrorMissingHost": "Missing HOST",
"bulkImportErrorInvalidPort": "Invalid PORT (must be 1-65535)",
"bulkImportErrorInvalidType": "Invalid TYPE (use http, https, or socks5)",
"bulkImportErrorInvalidStatus": "Invalid STATUS (use active or inactive)"
"bulkImportErrorInvalidStatus": "Invalid STATUS (use active or inactive)",
"clearAssignment": "(clear assignment)"
},
"playground": {
"title": "Model Playground",
@@ -6068,7 +6136,10 @@
"music": "Music generation",
"rerank": "Rerank",
"search": "Web search"
}
},
"conversationalChat": "Conversational Chat",
"clearChat": "Clear chat",
"typeMessagePlaceholder": "Type a message... (Shift+Enter for new line)"
},
"requestLogger": {
"recording": "Recording",
@@ -6306,6 +6377,10 @@
"totalExceeded": "⚠ exceeds 100%",
"addKey": "+ Add key…",
"equalSplit": "Equal split",
"save": "Save allocations"
"save": "Save allocations",
"betaPreviewLabel": "Beta — UI preview.",
"betaConfigSavedPrefix": "A configuração é salva em",
"betaConfigSavedSuffix": "(não persiste no servidor ainda). A aplicação dos caps por request ainda não está conectada ao pipeline da proxy. Esta tela permite desenhar e visualizar a divisão de cota;",
"policyLabel": "Policy:"
}
}