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:
diegosouzapw
2026-02-25 15:00:12 -03:00
parent 481a630273
commit 5f38173387
8 changed files with 72 additions and 31 deletions

View File

@@ -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",

View File

@@ -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>
);

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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";

View File

@@ -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",

View File

@@ -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",