feat(i18n): migrate providers/new + AuditLogTab + remaining keys

Phase 4: providers/new/page.tsx — 15 strings (form labels, errors, buttons)
Phase 5: logs/AuditLogTab.tsx — 15 strings (headers, filters, pagination)
Phase 6: Added ~50 new keys to en.json + pt-BR.json

Providers namespace: selectProvider, apiKeyRequired, authMethod, etc.
Logs namespace: auditLogDesc, filterByAction, filterByActor, etc.
This commit is contained in:
diegosouzapw
2026-02-25 15:08:38 -03:00
parent 0844659e00
commit 3d86ad7dc8
4 changed files with 102 additions and 42 deletions
@@ -6,6 +6,7 @@
*/
import { useState, useEffect, useCallback } from "react";
import { useTranslations } from "next-intl";
interface AuditEntry {
id: number;
@@ -27,6 +28,7 @@ export default function AuditLogTab() {
const [actorFilter, setActorFilter] = useState("");
const [offset, setOffset] = useState(0);
const [hasMore, setHasMore] = useState(false);
const t = useTranslations("logs");
const fetchEntries = useCallback(async () => {
setLoading(true);
@@ -85,10 +87,8 @@ export default function AuditLogTab() {
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold text-[var(--color-text-main)]">Audit Log</h2>
<p className="text-sm text-[var(--color-text-muted)] mt-1">
Administrative actions and security events
</p>
<h2 className="text-xl font-bold text-[var(--color-text-main)]">{t("auditLog")}</h2>
<p className="text-sm text-[var(--color-text-muted)] mt-1">{t("auditLogDesc")}</p>
</div>
<button
onClick={fetchEntries}
@@ -96,7 +96,7 @@ export default function AuditLogTab() {
aria-label="Refresh audit log"
className="px-4 py-2 rounded-lg text-sm font-medium bg-[var(--color-surface)] border border-[var(--color-border)] text-[var(--color-text-main)] hover:bg-[var(--color-bg-alt)] transition-colors disabled:opacity-50"
>
{loading ? "Loading..." : "Refresh"}
{loading ? t("loading") : t("refresh")}
</button>
</div>
@@ -108,7 +108,7 @@ export default function AuditLogTab() {
>
<input
type="text"
placeholder="Filter by action..."
placeholder={t("filterByAction")}
value={actionFilter}
onChange={(e) => setActionFilter(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
@@ -117,7 +117,7 @@ export default function AuditLogTab() {
/>
<input
type="text"
placeholder="Filter by actor..."
placeholder={t("filterByActor")}
value={actorFilter}
onChange={(e) => setActorFilter(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
@@ -128,7 +128,7 @@ export default function AuditLogTab() {
onClick={handleSearch}
className="px-4 py-2 rounded-lg text-sm font-medium bg-[var(--color-accent)] text-white hover:bg-[var(--color-accent-hover)] transition-colors focus:outline-2 focus:outline-offset-2 focus:outline-[var(--color-accent)]"
>
Search
{t("search")}
</button>
</div>
@@ -148,19 +148,19 @@ export default function AuditLogTab() {
<thead>
<tr className="bg-[var(--color-bg-alt)] border-b border-[var(--color-border)]">
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">
Timestamp
{t("timestamp")}
</th>
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">
Action
{t("action")}
</th>
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">
Actor
{t("actor")}
</th>
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">
Target
{t("target")}
</th>
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">
Details
{t("details")}
</th>
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">IP</th>
</tr>
@@ -169,7 +169,7 @@ export default function AuditLogTab() {
{entries.length === 0 && !loading ? (
<tr>
<td colSpan={6} className="px-4 py-8 text-center text-[var(--color-text-muted)]">
No audit log entries found
{t("noEntries")}
</td>
</tr>
) : (
@@ -216,14 +216,14 @@ export default function AuditLogTab() {
disabled={offset === 0}
className="px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--color-surface)] border border-[var(--color-border)] text-[var(--color-text-main)] hover:bg-[var(--color-bg-alt)] disabled:opacity-30 transition-colors"
>
Previous
{t("previous")}
</button>
<button
onClick={() => setOffset(offset + PAGE_SIZE)}
disabled={!hasMore}
className="px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--color-surface)] border border-[var(--color-border)] text-[var(--color-text-main)] hover:bg-[var(--color-bg-alt)] disabled:opacity-30 transition-colors"
>
Next
{t("next")}
</button>
</div>
</div>
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import Link from "next/link";
import { Card, Button, Input, Select, Toggle } from "@/shared/components";
import { AI_PROVIDERS, AUTH_METHODS } from "@/shared/constants/config";
import { useTranslations } from "next-intl";
const providerOptions = Object.values(AI_PROVIDERS).map((p) => ({
value: p.id,
@@ -19,6 +20,7 @@ const authMethodOptions = Object.values(AUTH_METHODS).map((m) => ({
export default function NewProviderPage() {
const router = useRouter();
const [loading, setLoading] = useState(false);
const t = useTranslations("providers");
const [formData, setFormData] = useState({
provider: "",
authMethod: "api_key",
@@ -37,9 +39,9 @@ export default function NewProviderPage() {
const validate = () => {
const newErrors: any = {};
if (!formData.provider) newErrors.provider = "Please select a provider";
if (!formData.provider) newErrors.provider = t("selectProvider");
if (formData.authMethod === "api_key" && !formData.apiKey) {
newErrors.apiKey = "API Key is required";
newErrors.apiKey = t("apiKeyRequired");
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
@@ -61,10 +63,10 @@ export default function NewProviderPage() {
router.push("/dashboard/providers");
} else {
const data = await response.json();
setErrors({ submit: data.error || "Failed to create provider" });
setErrors({ submit: data.error || t("failedCreateProvider") });
}
} catch (error) {
setErrors({ submit: "An error occurred. Please try again." });
setErrors({ submit: t("errorOccurredRetry") });
} finally {
setLoading(false);
}
@@ -81,12 +83,10 @@ export default function NewProviderPage() {
className="inline-flex items-center gap-1 text-sm text-text-muted hover:text-primary transition-colors mb-4"
>
<span className="material-symbols-outlined text-lg">arrow_back</span>
Back to Providers
{t("backToProviders")}
</Link>
<h1 className="text-3xl font-semibold tracking-tight">Add New Provider</h1>
<p className="text-text-muted mt-2">
Configure a new AI provider to use with your applications.
</p>
<h1 className="text-3xl font-semibold tracking-tight">{t("addNewProvider")}</h1>
<p className="text-text-muted mt-2">{t("configureNewProvider")}</p>
</div>
{/* Form */}
@@ -98,7 +98,7 @@ export default function NewProviderPage() {
options={providerOptions}
value={formData.provider}
onChange={(e) => handleChange("provider", e.target.value)}
placeholder="Select a provider"
placeholder={t("selectProviderPlaceholder")}
error={errors.provider as string}
required
/>
@@ -116,7 +116,7 @@ export default function NewProviderPage() {
</div>
<div>
<p className="font-medium">{selectedProvider.name}</p>
<p className="text-sm text-text-muted">Selected provider</p>
<p className="text-sm text-text-muted">{t("selectedProvider")}</p>
</div>
</Card.Section>
)}
@@ -124,7 +124,7 @@ export default function NewProviderPage() {
{/* Auth Method */}
<div className="flex flex-col gap-3">
<label className="text-sm font-medium">
Authentication Method <span className="text-red-500">*</span>
{t("authMethod")} <span className="text-red-500">*</span>
</label>
<div className="flex gap-3">
{authMethodOptions.map((method) => (
@@ -152,11 +152,11 @@ export default function NewProviderPage() {
<Input
label="API Key"
type="password"
placeholder="Enter your API key"
placeholder={t("enterApiKey")}
value={formData.apiKey}
onChange={(e) => handleChange("apiKey", e.target.value)}
error={errors.apiKey as string}
hint="Your API key will be encrypted and stored securely."
hint={t("apiKeyEncrypted")}
required
/>
)}
@@ -164,11 +164,9 @@ export default function NewProviderPage() {
{/* OAuth2 Button */}
{formData.authMethod === "oauth2" && (
<Card.Section className="">
<p className="text-sm text-text-muted mb-4">
Connect your account using OAuth2 authentication.
</p>
<p className="text-sm text-text-muted mb-4">{t("connectOAuth2")}</p>
<Button type="button" variant="secondary" icon="link">
Connect with OAuth2
{t("connectWithOAuth2")}
</Button>
</Card.Section>
)}
@@ -186,8 +184,8 @@ export default function NewProviderPage() {
<Toggle
checked={formData.isActive}
onChange={(checked) => handleChange("isActive", checked)}
label="Active"
description="Enable this provider for use in your applications"
label={t("activeLabel")}
description={t("activeDescription")}
/>
{/* Error Message */}
@@ -201,11 +199,11 @@ export default function NewProviderPage() {
<div className="flex gap-3 pt-4 border-t border-border">
<Link href="/dashboard/providers" className="flex-1">
<Button type="button" variant="ghost" fullWidth>
Cancel
{t("cancel")}
</Button>
</Link>
<Button type="submit" loading={loading} fullWidth className="flex-1">
Create Provider
{t("createProvider")}
</Button>
</div>
</form>
+33 -2
View File
@@ -439,7 +439,21 @@
"requestLogs": "Request Logs",
"proxyLogs": "Proxy Logs",
"auditLog": "Audit Log",
"console": "Console"
"console": "Console",
"auditLogDesc": "Administrative actions and security events",
"loading": "Loading...",
"refresh": "Refresh",
"filterByAction": "Filter by action...",
"filterByActor": "Filter by actor...",
"search": "Search",
"timestamp": "Timestamp",
"action": "Action",
"actor": "Actor",
"target": "Target",
"details": "Details",
"noEntries": "No audit log entries found",
"previous": "Previous",
"next": "Next"
},
"onboarding": {
"welcome": "Welcome",
@@ -598,7 +612,24 @@
"disableRateLimitProtection": "Click to disable rate limit protection",
"productionKey": "Production Key",
"enterNewApiKey": "Enter new API key",
"optional": "Optional"
"optional": "Optional",
"selectProvider": "Please select a provider",
"apiKeyRequired": "API Key is required",
"failedCreateProvider": "Failed to create provider",
"errorOccurredRetry": "An error occurred. Please try again.",
"addNewProvider": "Add New Provider",
"configureNewProvider": "Configure a new AI provider to use with your applications.",
"selectProviderPlaceholder": "Select a provider",
"selectedProvider": "Selected provider",
"authMethod": "Authentication Method",
"enterApiKey": "Enter your API key",
"apiKeyEncrypted": "Your API key will be encrypted and stored securely.",
"connectOAuth2": "Connect your account using OAuth2 authentication.",
"connectWithOAuth2": "Connect with OAuth2",
"activeLabel": "Active",
"activeDescription": "Enable this provider for use in your applications",
"cancel": "Cancel",
"createProvider": "Create Provider"
},
"settings": {
"title": "Settings",
+33 -2
View File
@@ -439,7 +439,21 @@
"requestLogs": "Logs de Requisições",
"proxyLogs": "Logs do Proxy",
"auditLog": "Log de Auditoria",
"console": "Console"
"console": "Console",
"auditLogDesc": "Ações administrativas e eventos de segurança",
"loading": "Carregando...",
"refresh": "Atualizar",
"filterByAction": "Filtrar por ação...",
"filterByActor": "Filtrar por ator...",
"search": "Buscar",
"timestamp": "Data/Hora",
"action": "Ação",
"actor": "Ator",
"target": "Alvo",
"details": "Detalhes",
"noEntries": "Nenhuma entrada de log de auditoria encontrada",
"previous": "Anterior",
"next": "Próximo"
},
"onboarding": {
"welcome": "Bem-vindo",
@@ -598,7 +612,24 @@
"disableRateLimitProtection": "Clique para desabilitar proteção de limite de taxa",
"productionKey": "Chave de Produção",
"enterNewApiKey": "Insira a nova chave API",
"optional": "Opcional"
"optional": "Opcional",
"selectProvider": "Por favor, selecione um provedor",
"apiKeyRequired": "Chave API é obrigatória",
"failedCreateProvider": "Falha ao criar provedor",
"errorOccurredRetry": "Ocorreu um erro. Tente novamente.",
"addNewProvider": "Adicionar Novo Provedor",
"configureNewProvider": "Configure um novo provedor de IA para usar com suas aplicações.",
"selectProviderPlaceholder": "Selecione um provedor",
"selectedProvider": "Provedor selecionado",
"authMethod": "Método de Autenticação",
"enterApiKey": "Insira sua chave API",
"apiKeyEncrypted": "Sua chave API será criptografada e armazenada com segurança.",
"connectOAuth2": "Conecte sua conta usando autenticação OAuth2.",
"connectWithOAuth2": "Conectar com OAuth2",
"activeLabel": "Ativo",
"activeDescription": "Habilitar este provedor para uso em suas aplicações",
"cancel": "Cancelar",
"createProvider": "Criar Provedor"
},
"settings": {
"title": "Configurações",