feat(i18n): migrate Providers page (1110 lines, 30+ strings)

- ProvidersPage: section headers, test buttons, compatible provider modals
- Expanded providers namespace from 16 to 73 keys in en.json and pt-BR.json
- Coverage: OAuth/Free/API Key/Compatible sections, batch test, modals
This commit is contained in:
diegosouzapw
2026-02-25 14:11:06 -03:00
parent 10d3120cdf
commit 87c7c83dd9
3 changed files with 163 additions and 28 deletions

View File

@@ -23,6 +23,7 @@ import Link from "next/link";
import { getErrorCode, getRelativeTime } from "@/shared/utils";
import { useNotificationStore } from "@/store/notificationStore";
import ModelAvailabilityBadge from "./components/ModelAvailabilityBadge";
import { useTranslations } from "next-intl";
// Shared helper function to avoid code duplication between ProviderCard and ApiKeyProviderCard
function getStatusDisplay(connected, error, errorCode) {
@@ -97,6 +98,8 @@ export default function ProvidersPage() {
const [testingMode, setTestingMode] = useState(null);
const [testResults, setTestResults] = useState(null);
const notify = useNotificationStore();
const t = useTranslations("providers");
const tc = useTranslations("common");
useEffect(() => {
const fetchData = async () => {
@@ -194,12 +197,12 @@ export default function ProvidersPage() {
setTestResults(data);
if (data.summary) {
const { passed, failed, total } = data.summary;
if (failed === 0) notify.success(`All ${total} tests passed`);
else notify.warning(`${passed}/${total} passed, ${failed} failed`);
if (failed === 0) notify.success(t("allTestsPassed", { total }));
else notify.warning(t("testSummary", { passed, failed, total }));
}
} catch (error) {
setTestResults({ error: "Test request failed" });
notify.error("Provider test failed");
setTestResults({ error: t("providerTestFailed") });
notify.error(t("providerTestFailed"));
} finally {
setTestingMode(null);
}
@@ -239,7 +242,8 @@ export default function ProvidersPage() {
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold flex items-center gap-2">
OAuth Providers <span className="size-2.5 rounded-full bg-blue-500" title="OAuth" />
{t("oauthProviders")}{" "}
<span className="size-2.5 rounded-full bg-blue-500" title="OAuth" />
</h2>
<div className="flex items-center gap-2">
<ModelAvailabilityBadge />
@@ -251,13 +255,13 @@ export default function ProvidersPage() {
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/40"
}`}
title="Test all OAuth connections"
aria-label="Test all OAuth connections"
title={t("testAllOAuth")}
aria-label={t("testAllOAuth")}
>
<span className="material-symbols-outlined text-[14px]">
{testingMode === "oauth" ? "sync" : "play_arrow"}
</span>
{testingMode === "oauth" ? "Testing..." : "Test All"}
{testingMode === "oauth" ? t("testing") : t("testAll")}
</button>
</div>
</div>
@@ -279,7 +283,8 @@ export default function ProvidersPage() {
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold flex items-center gap-2">
Free Providers <span className="size-2.5 rounded-full bg-green-500" title="Free" />
{t("freeProviders")}{" "}
<span className="size-2.5 rounded-full bg-green-500" title="Free" />
</h2>
<button
onClick={() => handleBatchTest("free")}
@@ -289,13 +294,13 @@ export default function ProvidersPage() {
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/40"
}`}
title="Test all Free connections"
aria-label="Test all Free provider connections"
title={t("testAllFree")}
aria-label={t("testAllFree")}
>
<span className="material-symbols-outlined text-[14px]">
{testingMode === "free" ? "sync" : "play_arrow"}
</span>
{testingMode === "free" ? "Testing..." : "Test All"}
{testingMode === "free" ? t("testing") : t("testAll")}
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
@@ -316,7 +321,7 @@ export default function ProvidersPage() {
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold flex items-center gap-2">
API Key Providers{" "}
{t("apiKeyProviders")}{" "}
<span className="size-2.5 rounded-full bg-amber-500" title="API Key" />
</h2>
<button
@@ -327,13 +332,13 @@ export default function ProvidersPage() {
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/40"
}`}
title="Test all API Key connections"
aria-label="Test all API Key connections"
title={t("testAllApiKey")}
aria-label={t("testAllApiKey")}
>
<span className="material-symbols-outlined text-[14px]">
{testingMode === "apikey" ? "sync" : "play_arrow"}
</span>
{testingMode === "apikey" ? "Testing..." : "Test All"}
{testingMode === "apikey" ? t("testing") : t("testAll")}
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
@@ -354,7 +359,7 @@ export default function ProvidersPage() {
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold flex items-center gap-2">
API Key Compatible Providers{" "}
{t("compatibleProviders")}{" "}
<span className="size-2.5 rounded-full bg-orange-500" title="Compatible" />
</h2>
<div className="flex gap-2">
@@ -367,16 +372,16 @@ export default function ProvidersPage() {
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/40"
}`}
title="Test all Compatible connections"
title={t("testAllCompatible")}
>
<span className="material-symbols-outlined text-[14px]">
{testingMode === "compatible" ? "sync" : "play_arrow"}
</span>
{testingMode === "compatible" ? "Testing..." : "Test All"}
{testingMode === "compatible" ? t("testing") : t("testAll")}
</button>
)}
<Button size="sm" icon="add" onClick={() => setShowAddAnthropicCompatibleModal(true)}>
Add Anthropic Compatible
{t("addAnthropicCompatible")}
</Button>
<Button
size="sm"
@@ -385,7 +390,7 @@ export default function ProvidersPage() {
onClick={() => setShowAddCompatibleModal(true)}
className="!bg-white !text-black hover:!bg-gray-100"
>
Add OpenAI Compatible
{t("addOpenAICompatible")}
</Button>
</div>
</div>
@@ -394,10 +399,8 @@ export default function ProvidersPage() {
<span className="material-symbols-outlined text-[32px] text-text-muted mb-2">
extension
</span>
<p className="text-text-muted text-sm">No compatible providers added yet</p>
<p className="text-text-muted text-xs mt-1">
Use the buttons above to add OpenAI or Anthropic compatible endpoints
</p>
<p className="text-text-muted text-sm">{t("noCompatibleYet")}</p>
<p className="text-text-muted text-xs mt-1">{t("compatibleHint")}</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
@@ -442,7 +445,7 @@ export default function ProvidersPage() {
onClick={(e) => e.stopPropagation()}
>
<div className="sticky top-0 z-10 flex items-center justify-between px-5 py-3 border-b border-border bg-bg-primary/95 backdrop-blur-sm rounded-t-xl">
<h3 className="font-semibold">Test Results</h3>
<h3 className="font-semibold">{t("testResults")}</h3>
<button
onClick={() => setTestResults(null)}
className="p-1 rounded-lg hover:bg-bg-subtle text-text-muted hover:text-text-primary transition-colors"

View File

@@ -498,7 +498,73 @@
"testFailed": "Connection failed",
"available": "Available",
"unavailable": "Unavailable",
"unknown": "Unknown"
"unknown": "Unknown",
"oauthProviders": "OAuth Providers",
"freeProviders": "Free Providers",
"apiKeyProviders": "API Key Providers",
"compatibleProviders": "API Key Compatible Providers",
"testAll": "Test All",
"testAllOAuth": "Test all OAuth connections",
"testAllFree": "Test all Free connections",
"testAllApiKey": "Test all API Key connections",
"testAllCompatible": "Test all Compatible connections",
"connected": "{count} Connected",
"errorCount": "{count} Error ({code})",
"errorCountNoCode": "{count} Error",
"noConnections": "No connections",
"disabled": "Disabled",
"enableProvider": "Enable provider",
"disableProvider": "Disable provider",
"testResults": "Test Results",
"noCompatibleYet": "No compatible providers added yet",
"compatibleHint": "Use the buttons above to add OpenAI or Anthropic compatible endpoints",
"addOpenAICompatible": "Add OpenAI Compatible",
"addAnthropicCompatible": "Add Anthropic Compatible",
"addNewProvider": "Add New Provider",
"backToProviders": "Back to Providers",
"configureNewProvider": "Configure a new AI provider to use with your applications.",
"selectProvider": "Select a provider",
"selectedProvider": "Selected provider",
"authMethod": "Authentication Method",
"apiKeyRequired": "API Key is required",
"selectProviderRequired": "Please select a provider",
"enterApiKey": "Enter your API key",
"apiKeySecure": "Your API key will be encrypted and stored securely.",
"oauth2Connect": "Connect with OAuth2",
"oauth2Desc": "Connect your account using OAuth2 authentication.",
"displayName": "Display Name",
"displayNamePlaceholder": "e.g., Production API, Dev Environment",
"displayNameHint": "Optional. A friendly name to identify this configuration.",
"active": "Active",
"activeDescription": "Enable this provider for use in your applications",
"createProvider": "Create Provider",
"failedCreate": "Failed to create provider",
"errorOccurred": "An error occurred. Please try again.",
"modelStatus": "Model Status",
"allModelsOperational": "All models operational",
"modelsWithIssues": "{count} model(s) with issues",
"allModelsNormal": "All models are responding normally.",
"cooldownCleared": "Cooldown cleared for {model}",
"failedClearCooldown": "Failed to clear cooldown",
"loadingAvailability": "Loading model availability...",
"clearCooldown": "Clear",
"clearing": "Clearing...",
"providerTestFailed": "Provider test failed",
"allTestsPassed": "All {total} tests passed",
"testSummary": "{passed}/{total} passed, {failed} failed",
"nameLabel": "Name",
"prefixLabel": "Prefix",
"baseUrlLabel": "Base URL",
"apiTypeLabel": "API Type",
"prefixHint": "Required. Unique prefix for model names.",
"nameHint": "Required. A friendly label for this node.",
"baseUrlHint": "Required. \u2003Provider API base URL.",
"validateConnection": "Validate Connection",
"validating": "Validating...",
"connectionValid": "Connection is valid!",
"connectionFailed": "Connection failed. Check URL and key.",
"testKeyLabel": "Test API Key",
"testKeyPlaceholder": "sk-... (for validation only)"
},
"settings": {
"title": "Settings",

View File

@@ -498,7 +498,73 @@
"testFailed": "Falha na conexão",
"available": "Disponível",
"unavailable": "Indisponível",
"unknown": "Desconhecido"
"unknown": "Desconhecido",
"oauthProviders": "Provedores OAuth",
"freeProviders": "Provedores Gratuitos",
"apiKeyProviders": "Provedores por Chave de API",
"compatibleProviders": "Provedores Compatíveis por Chave de API",
"testAll": "Testar Todos",
"testAllOAuth": "Testar todas as conexões OAuth",
"testAllFree": "Testar todas as conexões gratuitas",
"testAllApiKey": "Testar todas as conexões por chave de API",
"testAllCompatible": "Testar todas as conexões compatíveis",
"connected": "{count} Conectado(s)",
"errorCount": "{count} Erro ({code})",
"errorCountNoCode": "{count} Erro",
"noConnections": "Sem conexões",
"disabled": "Desativado",
"enableProvider": "Ativar provedor",
"disableProvider": "Desativar provedor",
"testResults": "Resultados do Teste",
"noCompatibleYet": "Nenhum provedor compatível adicionado",
"compatibleHint": "Use os botões acima para adicionar endpoints compatíveis com OpenAI ou Anthropic",
"addOpenAICompatible": "Adicionar Compatível OpenAI",
"addAnthropicCompatible": "Adicionar Compatível Anthropic",
"addNewProvider": "Adicionar Novo Provedor",
"backToProviders": "Voltar para Provedores",
"configureNewProvider": "Configure um novo provedor de IA para usar com suas aplicações.",
"selectProvider": "Selecione um provedor",
"selectedProvider": "Provedor selecionado",
"authMethod": "Método de Autenticação",
"apiKeyRequired": "Chave de API é obrigatória",
"selectProviderRequired": "Selecione um provedor",
"enterApiKey": "Digite sua chave de API",
"apiKeySecure": "Sua chave de API será criptografada e armazenada com segurança.",
"oauth2Connect": "Conectar com OAuth2",
"oauth2Desc": "Conecte sua conta usando autenticação OAuth2.",
"displayName": "Nome de Exibição",
"displayNamePlaceholder": "ex.: API de Produção, Ambiente Dev",
"displayNameHint": "Opcional. Um nome amigável para identificar esta configuração.",
"active": "Ativo",
"activeDescription": "Habilitar este provedor para uso em suas aplicações",
"createProvider": "Criar Provedor",
"failedCreate": "Falha ao criar provedor",
"errorOccurred": "Ocorreu um erro. Tente novamente.",
"modelStatus": "Status dos Modelos",
"allModelsOperational": "Todos os modelos operacionais",
"modelsWithIssues": "{count} modelo(s) com problemas",
"allModelsNormal": "Todos os modelos estão respondendo normalmente.",
"cooldownCleared": "Cooldown limpo para {model}",
"failedClearCooldown": "Falha ao limpar cooldown",
"loadingAvailability": "Carregando disponibilidade dos modelos...",
"clearCooldown": "Limpar",
"clearing": "Limpando...",
"providerTestFailed": "Teste de provedor falhou",
"allTestsPassed": "Todos os {total} testes passaram",
"testSummary": "{passed}/{total} passaram, {failed} falharam",
"nameLabel": "Nome",
"prefixLabel": "Prefixo",
"baseUrlLabel": "URL Base",
"apiTypeLabel": "Tipo de API",
"prefixHint": "Obrigatório. Prefixo único para nomes de modelos.",
"nameHint": "Obrigatório. Um rótulo amigável para este nó.",
"baseUrlHint": "Obrigatório. URL base da API do provedor.",
"validateConnection": "Validar Conexão",
"validating": "Validando...",
"connectionValid": "Conexão válida!",
"connectionFailed": "Falha na conexão. Verifique URL e chave.",
"testKeyLabel": "Chave de API de Teste",
"testKeyPlaceholder": "sk-... (apenas para validação)"
},
"settings": {
"title": "Configurações",