diff --git a/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx b/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx index 5c75932dc8..4494ab357e 100644 --- a/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx @@ -3,6 +3,7 @@ import { useState, useEffect } from "react"; import { Card, Input, Toggle, Button } from "@/shared/components"; import FallbackChainsEditor from "./FallbackChainsEditor"; +import { useTranslations } from "next-intl"; const STRATEGIES = [ { @@ -34,6 +35,7 @@ export default function RoutingTab() { const [aliases, setAliases] = useState([]); const [newPattern, setNewPattern] = useState(""); const [newTarget, setNewTarget] = useState(""); + const t = useTranslations("settings"); useEffect(() => { fetch("/api/settings") @@ -86,7 +88,7 @@ export default function RoutingTab() { route -

Routing Strategy

+

{t("routingStrategy")}

@@ -123,8 +125,8 @@ export default function RoutingTab() { {settings.fallbackStrategy === "round-robin" && (
-

Sticky Limit

-

Calls per account before switching

+

{t("stickyLimit")}

+

{t("stickyLimitDesc")}

-

Model Aliases

-

- Wildcard patterns to remap model names • Use * and ? -

+

{t("modelAliases")}

+

{t("modelAliasesDesc")}

@@ -198,7 +198,7 @@ export default function RoutingTab() {
setNewPattern(e.target.value)} @@ -206,14 +206,14 @@ export default function RoutingTab() {
setNewTarget(e.target.value)} />
diff --git a/src/app/(dashboard)/dashboard/settings/components/SecurityTab.tsx b/src/app/(dashboard)/dashboard/settings/components/SecurityTab.tsx index dcdb1efe08..954dcdee75 100644 --- a/src/app/(dashboard)/dashboard/settings/components/SecurityTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/SecurityTab.tsx @@ -5,6 +5,7 @@ import { Card, Button, Input, Toggle } from "@/shared/components"; import { AI_PROVIDERS } from "@/shared/constants/providers"; import IPFilterSection from "./IPFilterSection"; import SessionInfoCard from "./SessionInfoCard"; +import { useTranslations } from "next-intl"; export default function SecurityTab() { const [settings, setSettings] = useState({ requireLogin: false, hasPassword: false }); @@ -12,6 +13,7 @@ export default function SecurityTab() { const [passwords, setPasswords] = useState({ current: "", new: "", confirm: "" }); const [passStatus, setPassStatus] = useState({ type: "", message: "" }); const [passLoading, setPassLoading] = useState(false); + const t = useTranslations("settings"); useEffect(() => { fetch("/api/settings") @@ -64,7 +66,7 @@ export default function SecurityTab() { const handlePasswordChange = async (e) => { e.preventDefault(); if (passwords.new !== passwords.confirm) { - setPassStatus({ type: "error", message: "Passwords do not match" }); + setPassStatus({ type: "error", message: t("passwordsNoMatch") }); return; } @@ -82,13 +84,13 @@ export default function SecurityTab() { }); const data = await res.json(); if (res.ok) { - setPassStatus({ type: "success", message: "Password updated successfully" }); + setPassStatus({ type: "success", message: t("passwordUpdated") }); setPasswords({ current: "", new: "", confirm: "" }); } else { - setPassStatus({ type: "error", message: data.error || "Failed to update password" }); + setPassStatus({ type: "error", message: data.error || t("failedUpdatePassword") }); } } catch { - setPassStatus({ type: "error", message: "An error occurred" }); + setPassStatus({ type: "error", message: t("errorOccurred") }); } finally { setPassLoading(false); } @@ -105,15 +107,13 @@ export default function SecurityTab() { shield -

Security

+

{t("security")}

-

Require login

-

- When ON, dashboard requires password. When OFF, access without login. -

+

{t("requireLogin")}

+

{t("requireLoginDesc")}

{settings.hasPassword && ( setPasswords({ ...passwords, current: e.target.value })} required @@ -138,17 +138,17 @@ export default function SecurityTab() { )}
setPasswords({ ...passwords, new: e.target.value })} required /> setPasswords({ ...passwords, confirm: e.target.value })} required @@ -165,7 +165,7 @@ export default function SecurityTab() {
@@ -181,13 +181,13 @@ export default function SecurityTab() { api
-

API Endpoint Protection

+

{t("apiEndpointProtection")}

{/* Require auth for /models */}
-

Require API key for /models

+

{t("requireAuthModels")}

When ON, the{" "} @@ -207,7 +207,7 @@ export default function SecurityTab() { {/* Blocked Providers */}

-

Blocked Providers

+

{t("blockedProviders")}

Hide specific providers from the{" "} diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index e311bb6ff8..c09704a8e5 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -666,7 +666,47 @@ "tokenBudget": "Token Budget", "tokens": "tokens", "baseEffortLevel": "Base Effort Level", - "adaptiveHint": "Adaptive mode scales from this base level based on message count, tool usage, and prompt length." + "adaptiveHint": "Adaptive mode scales from this base level based on message count, tool usage, and prompt length.", + "requireLogin": "Require login", + "requireLoginDesc": "When ON, dashboard requires password. When OFF, access without login.", + "currentPassword": "Current Password", + "enterCurrentPassword": "Enter current password", + "newPassword": "New Password", + "enterNewPassword": "Enter new password", + "confirmPassword": "Confirm New Password", + "confirmPasswordPlaceholder": "Confirm new password", + "passwordsNoMatch": "Passwords do not match", + "passwordUpdated": "Password updated successfully", + "failedUpdatePassword": "Failed to update password", + "errorOccurred": "An error occurred", + "updatePassword": "Update Password", + "setPassword": "Set Password", + "apiEndpointProtection": "API Endpoint Protection", + "requireAuthModels": "Require API key for /models", + "requireAuthModelsDesc": "When ON, the /v1/models endpoint returns 404 for unauthenticated requests. Prevents model discovery by unauthorized users.", + "blockedProviders": "Blocked Providers", + "blockedProvidersDesc": "Hide specific providers from the /v1/models response. Blocked providers will not appear in model listings.", + "providersBlocked": "{count} provider(s) blocked from /models", + "routingStrategy": "Routing Strategy", + "fillFirst": "Fill First", + "fillFirstDesc": "Use accounts in priority order", + "roundRobin": "Round Robin", + "roundRobinDesc": "Cycle through all accounts", + "p2c": "P2C", + "p2cDesc": "Pick 2 random, use the healthier one", + "random": "Random", + "randomDesc": "Random account each request", + "leastUsed": "Least Used", + "leastUsedDesc": "Pick least recently used account", + "costOpt": "Cost Opt", + "costOptDesc": "Prefer cheapest available account", + "stickyLimit": "Sticky Limit", + "stickyLimitDesc": "Calls per account before switching", + "modelAliases": "Model Aliases", + "modelAliasesDesc": "Wildcard patterns to remap model names • Use * and ?", + "pattern": "Pattern", + "targetModel": "Target Model", + "add": "+ Add" }, "translator": { "title": "Translator", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index f67d532085..3664c46f09 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -666,7 +666,47 @@ "tokenBudget": "Orçamento de Tokens", "tokens": "tokens", "baseEffortLevel": "Nível de Esforço Base", - "adaptiveHint": "Modo adaptativo escala a partir deste nível base baseado na quantidade de mensagens, uso de ferramentas e tamanho do prompt." + "adaptiveHint": "Modo adaptativo escala a partir deste nível base baseado na quantidade de mensagens, uso de ferramentas e tamanho do prompt.", + "requireLogin": "Exigir login", + "requireLoginDesc": "Quando ATIVADO, dashboard exige senha. Quando DESATIVADO, acesso sem login.", + "currentPassword": "Senha Atual", + "enterCurrentPassword": "Digite a senha atual", + "newPassword": "Nova Senha", + "enterNewPassword": "Digite a nova senha", + "confirmPassword": "Confirmar Nova Senha", + "confirmPasswordPlaceholder": "Confirme a nova senha", + "passwordsNoMatch": "As senhas não coincidem", + "passwordUpdated": "Senha atualizada com sucesso", + "failedUpdatePassword": "Falha ao atualizar senha", + "errorOccurred": "Ocorreu um erro", + "updatePassword": "Atualizar Senha", + "setPassword": "Definir Senha", + "apiEndpointProtection": "Proteção de Endpoint da API", + "requireAuthModels": "Exigir chave de API para /models", + "requireAuthModelsDesc": "Quando ATIVADO, o endpoint /v1/models retorna 404 para requisições não autenticadas. Impede descoberta de modelos por usuários não autorizados.", + "blockedProviders": "Provedores Bloqueados", + "blockedProvidersDesc": "Ocultar provedores específicos da resposta /v1/models. Provedores bloqueados não aparecerão nas listagens de modelos.", + "providersBlocked": "{count} provedor(es) bloqueado(s) do /models", + "routingStrategy": "Estratégia de Roteamento", + "fillFirst": "Preencher Primeiro", + "fillFirstDesc": "Usar contas em ordem de prioridade", + "roundRobin": "Round Robin", + "roundRobinDesc": "Ciclar entre todas as contas", + "p2c": "P2C", + "p2cDesc": "Escolher 2 aleatórias, usar a mais saudável", + "random": "Aleatório", + "randomDesc": "Conta aleatória em cada requisição", + "leastUsed": "Menos Usado", + "leastUsedDesc": "Escolher a conta usada menos recentemente", + "costOpt": "Custo Otimizado", + "costOptDesc": "Preferir conta mais barata disponível", + "stickyLimit": "Limite Fixo", + "stickyLimitDesc": "Chamadas por conta antes de trocar", + "modelAliases": "Aliases de Modelo", + "modelAliasesDesc": "Padrões coringa para remapear nomes de modelos • Use * e ?", + "pattern": "Padrão", + "targetModel": "Modelo Alvo", + "add": "+ Adicionar" }, "translator": { "title": "Tradutor",