mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix(i18n): settings remnants — PoliciesPanel, PricingTab desc, ThinkingBudget, FallbackChains, Resilience
- PoliciesPanel.tsx: full migration (10 strings + 2 notify.error)
- PricingTab.tsx: translate description block, confirm, subtitle
- ThinkingBudgetTab.tsx: translate 'Off' label
- FallbackChainsEditor.tsx: translate confirm() string
- ResilienceTab.tsx: translate 2 notify.error('Failed to unlock')
- Added 25 keys to en.json + pt-BR.json settings namespace
This commit is contained in:
@@ -94,7 +94,7 @@ export default function FallbackChainsEditor() {
|
||||
};
|
||||
|
||||
const handleDelete = async (model) => {
|
||||
if (!confirm(`Delete fallback chain for "${model}"?`)) return;
|
||||
if (!confirm(t("deleteChainConfirm", { model }))) return;
|
||||
try {
|
||||
const res = await fetch("/api/fallback/chains", {
|
||||
method: "DELETE",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { Card, Button, EmptyState } from "@/shared/components";
|
||||
import { useNotificationStore } from "@/store/notificationStore";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const CB_STATUS = {
|
||||
closed: { icon: "check_circle", color: "#22c55e", label: "Closed" },
|
||||
@@ -23,6 +24,7 @@ export default function PoliciesPanel() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [unlocking, setUnlocking] = useState(null);
|
||||
const notify = useNotificationStore();
|
||||
const t = useTranslations("settings");
|
||||
|
||||
const fetchPolicies = useCallback(async () => {
|
||||
try {
|
||||
@@ -56,10 +58,10 @@ export default function PoliciesPanel() {
|
||||
notify.success(`Unlocked: ${identifier}`);
|
||||
await fetchPolicies();
|
||||
} else {
|
||||
notify.error("Failed to unlock");
|
||||
notify.error(t("failedUnlock"));
|
||||
}
|
||||
} catch {
|
||||
notify.error("Failed to unlock");
|
||||
notify.error(t("failedUnlock"));
|
||||
} finally {
|
||||
setUnlocking(null);
|
||||
}
|
||||
@@ -70,7 +72,7 @@ export default function PoliciesPanel() {
|
||||
<Card className="p-6 mt-6">
|
||||
<div className="flex items-center gap-2 text-text-muted animate-pulse">
|
||||
<span className="material-symbols-outlined text-[20px]">security</span>
|
||||
Loading policies...
|
||||
{t("loadingPolicies")}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
@@ -88,10 +90,8 @@ export default function PoliciesPanel() {
|
||||
<span className="material-symbols-outlined text-[20px]">verified_user</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-text-main">Policies & Circuit Breakers</h3>
|
||||
<p className="text-sm text-text-muted">
|
||||
All systems operational — no lockouts or tripped breakers
|
||||
</p>
|
||||
<h3 className="text-lg font-semibold text-text-main">{t("policiesCircuitBreakers")}</h3>
|
||||
<p className="text-sm text-text-muted">{t("allOperational")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -106,8 +106,8 @@ export default function PoliciesPanel() {
|
||||
<span className="material-symbols-outlined text-[20px]">gpp_maybe</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-text-main">Policies & Circuit Breakers</h3>
|
||||
<p className="text-sm text-text-muted">Active issues detected</p>
|
||||
<h3 className="text-lg font-semibold text-text-main">{t("policiesCircuitBreakers")}</h3>
|
||||
<p className="text-sm text-text-muted">{t("activeIssuesDetected")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button size="sm" variant="ghost" onClick={fetchPolicies}>
|
||||
@@ -118,7 +118,7 @@ export default function PoliciesPanel() {
|
||||
{/* Circuit Breakers */}
|
||||
{circuitBreakers.filter((cb) => cb.state !== "closed").length > 0 && (
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-medium text-text-muted mb-2">Circuit Breakers</p>
|
||||
<p className="text-sm font-medium text-text-muted mb-2">{t("circuitBreakers")}</p>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{circuitBreakers
|
||||
.filter((cb) => cb.state !== "closed")
|
||||
@@ -162,7 +162,7 @@ export default function PoliciesPanel() {
|
||||
{/* Locked Identifiers */}
|
||||
{lockedIds.length > 0 && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-text-muted mb-2">Locked Identifiers</p>
|
||||
<p className="text-sm font-medium text-text-muted mb-2">{t("lockedIdentifiers")}</p>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{lockedIds.map((id, i) => {
|
||||
const identifier = typeof id === "string" ? id : id.identifier || id.id;
|
||||
@@ -187,7 +187,7 @@ export default function PoliciesPanel() {
|
||||
disabled={unlocking === identifier}
|
||||
className="text-xs"
|
||||
>
|
||||
{unlocking === identifier ? "Unlocking..." : "Force Unlock"}
|
||||
{unlocking === identifier ? t("unlocking") : t("forceUnlock")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -142,7 +142,7 @@ export default function PricingTab() {
|
||||
);
|
||||
|
||||
const resetProvider = useCallback(async (providerAlias) => {
|
||||
if (!confirm(`Reset all pricing for ${providerAlias.toUpperCase()} to defaults?`)) return;
|
||||
if (!confirm(t("resetPricingConfirm", { provider: providerAlias.toUpperCase() }))) return;
|
||||
try {
|
||||
const response = await fetch(`/api/pricing?provider=${providerAlias}`, { method: "DELETE" });
|
||||
if (response.ok) {
|
||||
@@ -187,9 +187,7 @@ export default function PricingTab() {
|
||||
<div className="flex items-start justify-between flex-wrap gap-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold">{t("modelPricing")}</h2>
|
||||
<p className="text-text-muted text-sm mt-1">
|
||||
Configure cost rates per model • All rates in <strong>$/1M tokens</strong>
|
||||
</p>
|
||||
<p className="text-text-muted text-sm mt-1">{t("modelPricingDesc")}</p>
|
||||
</div>
|
||||
<div className="flex gap-3 text-sm">
|
||||
<div className="bg-bg-subtle rounded-lg px-3 py-2 text-center">
|
||||
@@ -290,15 +288,10 @@ export default function PricingTab() {
|
||||
</h3>
|
||||
<div className="text-xs text-text-muted space-y-1">
|
||||
<p>
|
||||
<strong>Input</strong>: tokens sent to the model • <strong>Output</strong>: tokens
|
||||
generated • <strong>Cached</strong>: reused input (~50% of input rate) •{" "}
|
||||
<strong>Reasoning</strong>: thinking tokens (falls back to Output) •{" "}
|
||||
<strong>Cache Write</strong>: creating cache entries (falls back to Input)
|
||||
</p>
|
||||
<p>
|
||||
Cost = (input × input_rate) + (output × output_rate) + (cached × cached_rate) per
|
||||
million tokens.
|
||||
{t("pricingDescInput")} • {t("pricingDescOutput")} • {t("pricingDescCached")} •{" "}
|
||||
{t("pricingDescReasoning")} • {t("pricingDescCacheWrite")}
|
||||
</p>
|
||||
<p>{t("pricingDescFormula")}</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -374,10 +374,10 @@ function PoliciesCard() {
|
||||
notify.success(`Unlocked: ${identifier}`);
|
||||
await fetchPolicies();
|
||||
} else {
|
||||
notify.error("Failed to unlock");
|
||||
notify.error(t("failedUnlock"));
|
||||
}
|
||||
} catch {
|
||||
notify.error("Failed to unlock");
|
||||
notify.error(t("failedUnlock"));
|
||||
} finally {
|
||||
setUnlocking(null);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ export default function ThinkingBudgetTab() {
|
||||
className="w-full accent-violet-500"
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-text-muted mt-1">
|
||||
<span>Off</span>
|
||||
<span>{t("off")}</span>
|
||||
<span>1K</span>
|
||||
<span>10K</span>
|
||||
<span>64K</span>
|
||||
|
||||
@@ -9,7 +9,7 @@ export const LANGUAGES: readonly {
|
||||
flag: string;
|
||||
}[] = [
|
||||
{ code: "en", label: "EN", name: "English", flag: "🇺🇸" },
|
||||
{ code: "pt-BR", label: "PT", name: "Português", flag: "🇧🇷" },
|
||||
{ code: "pt-BR", label: "PT-BR", name: "Português (Brasil)", flag: "🇧🇷" },
|
||||
] as const;
|
||||
|
||||
export const LOCALE_COOKIE = "NEXT_LOCALE";
|
||||
|
||||
@@ -852,7 +852,31 @@
|
||||
"models": "models",
|
||||
"withPricing": "with pricing configured",
|
||||
"loadingPricing": "Loading pricing data...",
|
||||
"databaseSize": "Database Size"
|
||||
"databaseSize": "Database Size",
|
||||
"policiesCircuitBreakers": "Policies & Circuit Breakers",
|
||||
"activeIssuesDetected": "Active issues detected",
|
||||
"off": "Off",
|
||||
"resetPricingConfirm": "Reset all pricing for {provider} to defaults?",
|
||||
"pricingDescInput": "Input: tokens sent to the model",
|
||||
"pricingDescOutput": "Output: tokens generated",
|
||||
"pricingDescCached": "Cached: reused input (~50% of input rate)",
|
||||
"pricingDescReasoning": "Reasoning: thinking tokens (falls back to Output)",
|
||||
"pricingDescCacheWrite": "Cache Write: creating cache entries (falls back to Input)",
|
||||
"pricingDescFormula": "Cost = (input × input_rate) + (output × output_rate) + (cached × cached_rate) per million tokens.",
|
||||
"pricingSettingsTitle": "Pricing Settings",
|
||||
"totalModels": "Total Models",
|
||||
"active": "Active",
|
||||
"costCalculation": "Cost Calculation",
|
||||
"costCalculationDesc": "Costs are calculated based on token usage and pricing rates configured for each model.",
|
||||
"pricingFormat": "Pricing Format",
|
||||
"pricingFormatDesc": "All rates are in $/1M tokens (dollars per million tokens).",
|
||||
"tokenTypes": "Token Types",
|
||||
"inputTokenDesc": "Standard prompt tokens",
|
||||
"outputTokenDesc": "Completion/response tokens",
|
||||
"cachedTokenDesc": "Cached input tokens (typically 50% of input rate)",
|
||||
"reasoningTokenDesc": "Special reasoning/thinking tokens (fallback to output rate)",
|
||||
"cacheCreationTokenDesc": "Tokens used to create cache entries (fallback to input rate)",
|
||||
"customPricingNote": "You can override default pricing for specific models. Custom overrides take priority over auto-detected pricing."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Translator",
|
||||
|
||||
@@ -852,7 +852,31 @@
|
||||
"models": "modelos",
|
||||
"withPricing": "com preço configurado",
|
||||
"loadingPricing": "Carregando dados de preços...",
|
||||
"databaseSize": "Tamanho do Banco de Dados"
|
||||
"databaseSize": "Tamanho do Banco de Dados",
|
||||
"policiesCircuitBreakers": "Políticas e Disjuntores",
|
||||
"activeIssuesDetected": "Problemas ativos detectados",
|
||||
"off": "Desligado",
|
||||
"resetPricingConfirm": "Resetar todos os preços de {provider} para os padrões?",
|
||||
"pricingDescInput": "Input: tokens enviados ao modelo",
|
||||
"pricingDescOutput": "Output: tokens gerados",
|
||||
"pricingDescCached": "Cached: input reutilizado (~50% da taxa de input)",
|
||||
"pricingDescReasoning": "Reasoning: tokens de raciocínio (fallback para Output)",
|
||||
"pricingDescCacheWrite": "Cache Write: criação de entradas de cache (fallback para Input)",
|
||||
"pricingDescFormula": "Custo = (input × taxa_input) + (output × taxa_output) + (cached × taxa_cached) por milhão de tokens.",
|
||||
"pricingSettingsTitle": "Configurações de Preços",
|
||||
"totalModels": "Total de Modelos",
|
||||
"active": "Ativo",
|
||||
"costCalculation": "Cálculo de Custo",
|
||||
"costCalculationDesc": "Custos são calculados com base no uso de tokens e taxas de preço configuradas para cada modelo.",
|
||||
"pricingFormat": "Formato de Preços",
|
||||
"pricingFormatDesc": "Todas as taxas são em $/1M tokens (dólares por milhão de tokens).",
|
||||
"tokenTypes": "Tipos de Token",
|
||||
"inputTokenDesc": "Tokens de prompt padrão",
|
||||
"outputTokenDesc": "Tokens de conclusão/resposta",
|
||||
"cachedTokenDesc": "Tokens de input em cache (tipicamente 50% da taxa de input)",
|
||||
"reasoningTokenDesc": "Tokens especiais de raciocínio (fallback para taxa de output)",
|
||||
"cacheCreationTokenDesc": "Tokens usados para criar entradas de cache (fallback para taxa de input)",
|
||||
"customPricingNote": "Você pode sobrescrever preços padrão para modelos específicos. Sobrescritas personalizadas têm prioridade sobre preços detectados automaticamente."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Tradutor",
|
||||
|
||||
Reference in New Issue
Block a user