feat(i18n): Spanish localization (phase 4)
CI / Lint e auditorias (push) Successful in 1m31s
CI / Trivy (dependências e segredos) (push) Successful in 10s
CI / Imagem Docker (linux/arm64) (push) Successful in 2m23s

Complete implementation of Spanish (es-419) language support:
- Canonical catalogs and data: messages/es.json, data/es/**, lib/data/es/**, public/exemplos/es/**
- Canonical slugs: /es/series, /es/catalogo, /es/comparacion, /es/componentes, /es/diagnostico, /es/frameworks, /es/glosario, /es/selector, /es/acerca
- Dedicated search index at /api/busca/es with Spanish accent normalization
- Strict exclusion of book references in Spanish routes and catalogs
- Language selector and root negotiation with es active (ENABLED_LOCALES = ['pt', 'en', 'es'])
- Extended verification suite: check-i18n, smoke-imagem and verifica-vazamentos

Co-Authored-By: Antigravity <noreply@google.com>
This commit is contained in:
Lucas RayanandAntigravity committed 2026-11-01 19:50:20 -03:00
1 parent 0940814522
commit 660b28c09d
72 files changed
+17848 -947

No files matched your search

+29 -17
View File
@@ -7,6 +7,8 @@ import { usePathname } from "next/navigation";
export default function CodeExample({ framework }) {
const pathname = usePathname() || "";
const isEn = pathname === "/en" || pathname.startsWith("/en/");
const isEs = pathname === "/es" || pathname.startsWith("/es/");
const loc = isEn ? "en" : isEs ? "es" : "pt";
const examplesUrl = framework?.exemplos;
@@ -27,15 +29,17 @@ export default function CodeExample({ framework }) {
setSample(
isEn
? `// Full examples: ${examplesUrl || "see repository"}\n`
: `// Exemplos completos: ${examplesUrl || "ver repositório"}\n`
: isEs
? `// Ejemplos completos: ${examplesUrl || "ver repositorio"}\n`
: `// Exemplos completos: ${examplesUrl || "ver repositório"}\n`
);
return;
}
// Se locale for en, busca primeiro em /exemplos/en/
// Se locale for diferente de pt, busca primeiro em /exemplos/<loc>/
let url = exemploPath.startsWith("/") ? exemploPath : `/${exemploPath}`;
if (isEn && url.startsWith("/exemplos/") && !url.startsWith("/exemplos/en/")) {
url = url.replace("/exemplos/", "/exemplos/en/");
if (loc !== "pt" && url.startsWith("/exemplos/") && !url.startsWith(`/exemplos/${loc}/`)) {
url = url.replace("/exemplos/", `/exemplos/${loc}/`);
}
let cancelled = false;
@@ -64,9 +68,11 @@ export default function CodeExample({ framework }) {
setSample(
isEn
? "// The example file is empty. Refer to the official repository.\n"
: "// O arquivo de exemplo está vazio. Consulte o repositório oficial.\n"
: isEs
? "// El archivo de ejemplo está vacío. Consulte el repositorio oficial.\n"
: "// O arquivo de exemplo está vazio. Consulte o repositório oficial.\n"
);
setError(isEn ? "empty file" : "arquivo vazio");
setError(isEn ? "empty file" : isEs ? "archivo vacío" : "arquivo vazio");
}
})
.catch((err) => {
@@ -74,9 +80,11 @@ export default function CodeExample({ framework }) {
setSample(
isEn
? "// Could not load example.\n// Refer to official repository for complete examples.\n"
: "// Não foi possível carregar o exemplo.\n// Consulte o repositório oficial para exemplos completos.\n"
: isEs
? "// No fue posible cargar el ejemplo.\n// Consulte el repositorio oficial para ver los ejemplos completos.\n"
: "// Não foi possível carregar o exemplo.\n// Consulte o repositório oficial para exemplos completos.\n"
);
setError(err.message || (isEn ? "load failure" : "falha ao carregar"));
setError(err.message || (isEn ? "load failure" : isEs ? "fallo al cargar" : "falha ao carregar"));
})
.finally(() => {
if (!cancelled) setLoading(false);
@@ -85,7 +93,7 @@ export default function CodeExample({ framework }) {
return () => {
cancelled = true;
};
}, [framework?.exemplo, framework?.exemplos, isEn]);
}, [framework?.exemplo, framework?.exemplos, isEn, isEs, loc]);
const lines = (sample || "").replace(/\n$/, "").split("\n");
@@ -102,25 +110,27 @@ export default function CodeExample({ framework }) {
<div className="flex flex-wrap items-start justify-between gap-4">
<div>
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">
{isEn ? "Code example" : "Exemplo de código"}
{isEn ? "Code example" : isEs ? "Ejemplo de código" : "Exemplo de código"}
</h2>
<p className="mt-1 text-sm text-muted">
{isEn
? `Starter snippet in ${framework?.linguagem}. Adjust pins and configuration for your board.`
: `Trecho de partida em ${framework?.linguagem}. Ajuste pinos e configuração à sua placa.`}
: isEs
? `Fragmento de inicio en ${framework?.linguagem}. Ajuste los pines y la configuración para su placa.`
: `Trecho de partida em ${framework?.linguagem}. Ajuste pinos e configuração à sua placa.`}
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<button type="button" onClick={handleCopy} className="btn btn-ghost btn-sm">
{copied ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
<span role="status">{copied ? (isEn ? "Copied" : "Copiado") : (isEn ? "Copy" : "Copiar")}</span>
<span role="status">{copied ? (isEn ? "Copied" : isEs ? "Copiado" : "Copiado") : (isEn ? "Copy" : isEs ? "Copiar" : "Copiar")}</span>
</button>
{examplesUrl && (
<a href={examplesUrl} target="_blank" rel="noopener noreferrer" className="btn btn-ghost btn-sm">
{isEn ? "More examples" : "Mais exemplos"}
{isEn ? "More examples" : isEs ? "Más ejemplos" : "Mais exemplos"}
<ExternalLink className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">{isEn ? " (opens in new tab)" : " (abre em nova aba)"}</span>
<span className="sr-only">{isEn ? " (opens in new tab)" : isEs ? " (abre en una pestaña nueva)" : " (abre em nova aba)"}</span>
</a>
)}
</div>
@@ -129,9 +139,9 @@ export default function CodeExample({ framework }) {
<div className="code-panel mt-5" aria-busy={loading}>
<div className="flex items-center justify-between border-b border-white/10 px-4 py-2.5 text-xs text-[#a6afbd]">
<span className="font-mono">{framework?.nome}</span>
<span>{loading ? (isEn ? "Loading example…" : "Carregando exemplo…") : framework?.linguagem}</span>
<span>{loading ? (isEn ? "Loading example…" : isEs ? "Cargando ejemplo…" : "Carregando exemplo…") : framework?.linguagem}</span>
</div>
<pre tabIndex={0} aria-label={isEn ? `Code example in ${framework?.linguagem}` : `Código de exemplo em ${framework?.linguagem}`}>
<pre tabIndex={0} aria-label={isEn ? `Code example in ${framework?.linguagem}` : isEs ? `Código de ejemplo en ${framework?.linguagem}` : `Código de exemplo em ${framework?.linguagem}`}>
<code>
{lines.map((line, idx) => (
<span key={idx} className="flex">
@@ -147,7 +157,9 @@ export default function CodeExample({ framework }) {
<p role="alert" className="callout callout-warn mt-4">
{isEn
? `Could not load local example (${error}). Use “More examples” to view official code.`
: `Não foi possível carregar o exemplo local (${error}). Use “Mais exemplos” para ver o código oficial.`}
: isEs
? `No se pudo cargar el ejemplo local (${error}). Utilice “Más ejemplos” para ver el código oficial.`
: `Não foi possível carregar o exemplo local (${error}). Use “Mais exemplos” para ver o código oficial.`}
</p>
)}
</div>