diff --git a/src/app/(dashboard)/dashboard/BootstrapBanner.tsx b/src/app/(dashboard)/dashboard/BootstrapBanner.tsx index f0300ea364..a819d82d1d 100644 --- a/src/app/(dashboard)/dashboard/BootstrapBanner.tsx +++ b/src/app/(dashboard)/dashboard/BootstrapBanner.tsx @@ -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() { diff --git a/src/app/(dashboard)/dashboard/TierCoverageWidget.tsx b/src/app/(dashboard)/dashboard/TierCoverageWidget.tsx index 6d08ae16b5..6ce971b063 100644 --- a/src/app/(dashboard)/dashboard/TierCoverageWidget.tsx +++ b/src/app/(dashboard)/dashboard/TierCoverageWidget.tsx @@ -65,8 +65,8 @@ export function TierCoverageWidget() {
-

Tier coverage

-

Providers configured per fallback tier

+

{t("tierCoverageTitle")}

+

{t("tierCoverageSubtitle")}

("24h"); const [aggregateBy, setAggregateBy] = useState<"provider" | "connection">("provider"); const [data, setData] = useState(null); @@ -191,7 +193,7 @@ export default function ProviderUtilizationTab() { return (
error
-

Failed to load utilization data

+

+ {t("providerUtilizationFailedToLoad")} +

{error}

{point.remainingPct.toFixed(point.remainingPct < 10 ? 1 : 0)}%

-

Remaining capacity

+

+ {t("providerUtilizationRemainingCapacity")} +

{formatTooltipTimestamp(point.timestamp, range)}

diff --git a/src/app/(dashboard)/dashboard/analytics/SearchAnalyticsTab.tsx b/src/app/(dashboard)/dashboard/analytics/SearchAnalyticsTab.tsx index 1d0e371bbc..da9ca45094 100644 --- a/src/app/(dashboard)/dashboard/analytics/SearchAnalyticsTab.tsx +++ b/src/app/(dashboard)/dashboard/analytics/SearchAnalyticsTab.tsx @@ -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(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -122,25 +124,25 @@ export default function SearchAnalyticsTab() {
0 ? `${stats.errors} errors` : "No errors"} /> @@ -173,7 +175,7 @@ export default function SearchAnalyticsTab() { travel_explore -

No searches yet

+

{t("searchAnalyticsNoSearchesYet")}

Use POST /v1/search to start routing web searches. diff --git a/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx b/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx index 3d21b80b41..fc4dce9d04 100644 --- a/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx +++ b/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx @@ -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(null); const [loading, setLoading] = useState(true); @@ -82,7 +84,7 @@ export default function DiversityScoreCard() {

pie_chart -

Provider Diversity

+

{t("diversityScoreTitle")}

— Provider concentration snapshot for the recent traffic window. diff --git a/src/app/(dashboard)/dashboard/batch/BatchDetailModal.tsx b/src/app/(dashboard)/dashboard/batch/BatchDetailModal.tsx index 6f5cd42768..005b931b95 100644 --- a/src/app/(dashboard)/dashboard/batch/BatchDetailModal.tsx +++ b/src/app/(dashboard)/dashboard/batch/BatchDetailModal.tsx @@ -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")} > content_copy @@ -191,7 +193,7 @@ export default function BatchDetailModal({ batch, files, onClose }: BatchDetailM
- - {batch.model && } - + + {batch.model && } + {relativeTime(batch.createdAt)}} />
diff --git a/src/app/(dashboard)/dashboard/batch/BatchListTab.tsx b/src/app/(dashboard)/dashboard/batch/BatchListTab.tsx index eca2569383..a5444f1f52 100644 --- a/src/app/(dashboard)/dashboard/batch/BatchListTab.tsx +++ b/src/app/(dashboard)/dashboard/batch/BatchListTab.tsx @@ -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) { + const t = useTranslations("common"); const [selectedBatch, setSelectedBatch] = useState(null); const [statusFilter, setStatusFilter] = useState("all"); const [searchQuery, setSearchQuery] = useState(""); @@ -199,7 +201,7 @@ export default function BatchListTab({
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")} > {removingCompleted ? "hourglass_empty" : "delete_sweep"} @@ -230,7 +232,7 @@ export default function BatchListTab({ {/* Table */}
- +
@@ -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")} > {deletingId === batch.id ? "hourglass_empty" : "delete"} diff --git a/src/app/(dashboard)/dashboard/batch/FileDetailModal.tsx b/src/app/(dashboard)/dashboard/batch/FileDetailModal.tsx index 4281178a7d..0bde086453 100644 --- a/src/app/(dashboard)/dashboard/batch/FileDetailModal.tsx +++ b/src/app/(dashboard)/dashboard/batch/FileDetailModal.tsx @@ -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) { + 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")} > content_copy @@ -149,7 +151,7 @@ export default function FileDetailModal({ @@ -117,7 +119,9 @@ export default function CachePerformance({ {!loading && !error && stats !== null && ( <> {/* Hit rate bar */} - {hitRate !== undefined && } + {hitRate !== undefined && ( + + )} {/* Hit / Miss / Total breakdown */}
@@ -141,13 +145,17 @@ export default function CachePerformance({ {avgLatencyMs !== undefined && (
{avgLatencyMs}
-
Avg Latency (ms)
+
+ {t("cachePerformanceAvgLatency")} +
)} {p95LatencyMs !== undefined && (
{p95LatencyMs}
-
P95 Latency (ms)
+
+ {t("cachePerformanceP95Latency")} +
)}
diff --git a/src/app/(dashboard)/dashboard/changelog/components/ChangelogViewer.tsx b/src/app/(dashboard)/dashboard/changelog/components/ChangelogViewer.tsx index be6f89bc85..b9ef3c8668 100644 --- a/src/app/(dashboard)/dashboard/changelog/components/ChangelogViewer.tsx +++ b/src/app/(dashboard)/dashboard/changelog/components/ChangelogViewer.tsx @@ -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() { sync -

Loading changelog from GitHub...

+

{t("changelogViewerLoading")}

); } diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.tsx b/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.tsx index 4a45800a58..2beae12af7 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.tsx +++ b/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.tsx @@ -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" > - - + + diff --git a/src/app/(dashboard)/dashboard/combos/IntelligentComboPanel.tsx b/src/app/(dashboard)/dashboard/combos/IntelligentComboPanel.tsx index 9a8140d06e..08c3875553 100644 --- a/src/app/(dashboard)/dashboard/combos/IntelligentComboPanel.tsx +++ b/src/app/(dashboard)/dashboard/combos/IntelligentComboPanel.tsx @@ -287,7 +287,9 @@ export default function IntelligentComboPanel({
-

Mode Pack

+

+ {t("modePack")} +

{normalizedConfig.modePack}

diff --git a/src/app/(dashboard)/dashboard/components/BadgeToast.tsx b/src/app/(dashboard)/dashboard/components/BadgeToast.tsx index e1695876dd..24c4e96a9d 100644 --- a/src/app/(dashboard)/dashboard/components/BadgeToast.tsx +++ b/src/app/(dashboard)/dashboard/components/BadgeToast.tsx @@ -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([]); const timeoutIds = useRef>>(new Set()); @@ -92,7 +94,7 @@ export function BadgeToast({ apiKeyId }: { apiKeyId: string }) { > 🏆
-
Badge Unlocked!
+
{t("badgeToastUnlocked")}
{toast.badgeName}
diff --git a/src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx b/src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx index ed9bc88b28..81c77eb6fb 100644 --- a/src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx +++ b/src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx @@ -339,11 +339,8 @@ export default function QuotaSharePageClient() {
science
- Beta — UI preview. A configuração é salva em localStorage{" "} - (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. + {t("betaPreviewLabel")} {t("betaConfigSavedPrefix")}{" "} + localStorage {t("betaConfigSavedSuffix")}
@@ -598,7 +595,9 @@ function PoolCard({
- Policy: + + {t("policyLabel")} + {(["hard", "soft", "burst"] as PoolPolicy[]).map((p) => (
-

API catalog unavailable

+

+ {t("apiEndpointsCatalogUnavailable")} +

{catalogError || "The OpenAPI specification could not be loaded."}

@@ -254,7 +258,7 @@ export default function ApiEndpointsTab() { 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 && ( lock @@ -482,7 +486,7 @@ export default function ApiEndpointsTab() { search_off -

No endpoints match your filter

+

{t("apiEndpointsNoMatch")}

)} diff --git a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx index 476744f3af..096406ca41 100644 --- a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx +++ b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx @@ -1281,7 +1281,7 @@ export default function APIPageClient({ machineId }: Readonly
- Local Server + {t("localServer")} {resolvedMachineId && ( · {resolvedMachineId.slice(0, 8)} )} diff --git a/src/app/(dashboard)/dashboard/endpoint/components/TokenSaverCard.tsx b/src/app/(dashboard)/dashboard/endpoint/components/TokenSaverCard.tsx index fb8e79e333..f9bff8d02e 100644 --- a/src/app/(dashboard)/dashboard/endpoint/components/TokenSaverCard.tsx +++ b/src/app/(dashboard)/dashboard/endpoint/components/TokenSaverCard.tsx @@ -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(null); const [loading, setLoading] = useState(true); @@ -182,14 +184,14 @@ export default function TokenSaverCard() { )} -

Spend less tokens on every request.

+

{t("tokenSaverSubtitle")}

save({ enabled: v })} />
("global"); const [entries, setEntries] = useState([]); const [myRank, setMyRank] = useState(null); @@ -110,7 +112,7 @@ export default function LeaderboardPage() {
-

Your Rank

+

{t("leaderboardYourRank")}

#{myRank}

@@ -125,7 +127,7 @@ export default function LeaderboardPage() { {loading ? (
-
Loading leaderboard...
+
{t("leaderboardLoading")}
) : ( <> diff --git a/src/app/(dashboard)/dashboard/mcp/page.tsx b/src/app/(dashboard)/dashboard/mcp/page.tsx index bb6d677505..c19127ab13 100644 --- a/src/app/(dashboard)/dashboard/mcp/page.tsx +++ b/src/app/(dashboard)/dashboard/mcp/page.tsx @@ -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 diff --git a/src/app/(dashboard)/dashboard/onboarding/components/TierFlowDiagram.tsx b/src/app/(dashboard)/dashboard/onboarding/components/TierFlowDiagram.tsx index 74bfbff6d6..d2cc88ad13 100644 --- a/src/app/(dashboard)/dashboard/onboarding/components/TierFlowDiagram.tsx +++ b/src/app/(dashboard)/dashboard/onboarding/components/TierFlowDiagram.tsx @@ -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() {
OmniRoute 3-tier fallback diagram
chat -

Conversational Chat

+

{t("conversationalChat")}

{responseStatus !== null && ( {responseStatus} @@ -235,7 +235,7 @@ export default function ChatPlayground({ @@ -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} diff --git a/src/app/(dashboard)/dashboard/playground/SearchPlayground.tsx b/src/app/(dashboard)/dashboard/playground/SearchPlayground.tsx index 6003cba660..d778aca937 100644 --- a/src/app/(dashboard)/dashboard/playground/SearchPlayground.tsx +++ b/src/app/(dashboard)/dashboard/playground/SearchPlayground.tsx @@ -175,7 +175,7 @@ export default function SearchPlayground() { @@ -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")} > restart_alt diff --git a/src/app/(dashboard)/dashboard/profile/page.tsx b/src/app/(dashboard)/dashboard/profile/page.tsx index 10ed9dd0d3..328dfdabe5 100644 --- a/src/app/(dashboard)/dashboard/profile/page.tsx +++ b/src/app/(dashboard)/dashboard/profile/page.tsx @@ -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 = { }; export default function ProfilePage() { + const t = useTranslations("common"); const [userLevel, setUserLevel] = useState(null); const [allBadges, setAllBadges] = useState([]); const [earnedBadges, setEarnedBadges] = useState([]); @@ -99,7 +101,7 @@ export default function ProfilePage() { if (loading) { return (
-
Loading profile...
+
{t("profileLoading")}
); } @@ -269,7 +271,7 @@ export default function ProfilePage() { {selectedBadge.criteria && (
-

How to earn

+

{t("profileHowToEarn")}

{selectedBadge.criteria}

)} diff --git a/src/app/(dashboard)/dashboard/settings/components/CliproxyapiSettingsTab.tsx b/src/app/(dashboard)/dashboard/settings/components/CliproxyapiSettingsTab.tsx index 43ee16d302..172f8a1c49 100644 --- a/src/app/(dashboard)/dashboard/settings/components/CliproxyapiSettingsTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/CliproxyapiSettingsTab.tsx @@ -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({}); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); @@ -139,7 +141,7 @@ export default function CliproxyapiSettingsTab() { swap_horiz
-

CLIProxyAPI Fallback

+

{t("cliproxyapiFallback")}

When enabled, failed requests are retried through CLIProxyAPI (localhost:8317)

@@ -148,7 +150,7 @@ export default function CliproxyapiSettingsTab() {
- + updateSetting("cliproxyapi_fallback_enabled", checked)} @@ -158,7 +160,9 @@ export default function CliproxyapiSettingsTab() { {cpaEnabled && ( <>
- + updateSetting("cliproxyapi_url", e.target.value)} @@ -184,7 +188,7 @@ export default function CliproxyapiSettingsTab() { -

CLIProxyAPI Status

+

{t("cliproxyapiStatus")}

{loading ? (
@@ -237,7 +241,7 @@ export default function CliproxyapiSettingsTab() {
) : ( -

CLIProxyAPI not detected

+

{t("cliproxyapiNotDetected")}

)}
diff --git a/src/app/(dashboard)/dashboard/settings/components/ModelCooldownsCard.tsx b/src/app/(dashboard)/dashboard/settings/components/ModelCooldownsCard.tsx index 5baed73d09..fba5a80a73 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ModelCooldownsCard.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ModelCooldownsCard.tsx @@ -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([]); const [loading, setLoading] = useState(true); @@ -95,7 +97,7 @@ export default function ModelCooldownsCard() {
-

Models in cooldown

+

{t("modelCooldownsTitle")}

Models temporarily isolated after a failure. When the cooldown expires they come back automatically. @@ -120,7 +122,7 @@ export default function ModelCooldownsCard() { {loading ? (

Loading...

) : !hasItems ? ( -

No models in cooldown right now.

+

{t("modelCooldownsEmpty")}

) : ( sorted.map((item) => { const rowKey = `${item.provider}::${item.model}`; diff --git a/src/app/(dashboard)/dashboard/settings/components/PayloadRulesTab.tsx b/src/app/(dashboard)/dashboard/settings/components/PayloadRulesTab.tsx index 31af651749..d56d6cc724 100644 --- a/src/app/(dashboard)/dashboard/settings/components/PayloadRulesTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/PayloadRulesTab.tsx @@ -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() {
-

Payload Rules

+

{t("payloadRulesTitle")}

Configure request payload mutations by model and protocol. Changes are persisted in settings and hot reloaded into the runtime immediately after save. diff --git a/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx b/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx index 14b3610a67..0de01dab8e 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx @@ -874,7 +874,7 @@ export default function ProxyRegistryManager() { value={bulkProxyId} onChange={(e) => setBulkProxyId(e.target.value)} > - + {items.map((item) => (

{staleAfterReset ? ( -
⟳ Refreshing...
+
{t("quotaTableRefreshing")}
) : countdown !== t("notAvailableSymbol") || resetDisplay ? (
{countdown !== t("notAvailableSymbol") && ( diff --git a/src/app/(dashboard)/home/ProviderTopology.tsx b/src/app/(dashboard)/home/ProviderTopology.tsx index b2ec6b7bfe..e201bbc966 100644 --- a/src/app/(dashboard)/home/ProviderTopology.tsx +++ b/src/app/(dashboard)/home/ProviderTopology.tsx @@ -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 ? (
device_hub -

No providers connected yet

+

{t("providerTopologyEmpty")}

) : (