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

+69 -27
View File
@@ -11,13 +11,18 @@ const REPOSITORIO = SITE.repositorio;
export async function generateMetadata({ params }) {
const { locale } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
return paginaMeta({
titulo: isEn ? "About the Project" : "Sobre o projeto",
descricao: isEn
titulo: isEs ? "Acerca del proyecto" : isEn ? "About the Project" : "Sobre o projeto",
descricao: isEs
? "ESPDocs es una plataforma independiente que organiza la documentación técnica del ecosistema ESP32. Conozca el proyecto y cómo contribuir."
: isEn
? "ESPDocs is an independent platform organizing technical documentation of the ESP32 ecosystem. Learn about the project and how to contribute."
: "O ESPDocs é uma plataforma independente que reúne a documentação do ecossistema ESP32 em português brasileiro. Saiba como o projeto nasceu e como contribuir.",
caminho: "/sobre",
keywords: isEn
keywords: isEs
? ["ESPDocs", "documentación ESP32", "proyecto open source ESP32", "código abierto"]
: isEn
? ["ESPDocs", "ESP32 documentation", "open source ESP32 project"]
: ["ESPDocs", "documentação ESP32 português", "projeto open source ESP32"],
locale,
@@ -27,6 +32,8 @@ export async function generateMetadata({ params }) {
export default async function Sobre({ params }) {
const { locale } = await params;
const isPt = locale === "pt";
const isEn = locale === "en";
const isEs = locale === "es";
const featuresPt = [
{ icon: Languages, title: "Em português", desc: "Dados técnicos organizados e apresentados em português do Brasil." },
@@ -42,7 +49,14 @@ export default async function Sobre({ params }) {
{ icon: Code, title: "Open source", desc: "Contributions are welcome on Gitea." },
];
const features = isPt ? featuresPt : featuresEn;
const featuresEs = [
{ icon: Languages, title: "En español", desc: "Datos técnicos organizados y presentados de forma clara en español neutro." },
{ icon: GitCompare, title: "Comparaciones visuales", desc: "Tablas y diagramas para elegir la serie adecuada sin abrir diez hojas de datos." },
{ icon: Globe, title: "Acceso centralizado", desc: "Enlaces directos a la documentación oficial, repositorios y ejemplos." },
{ icon: Code, title: "Código abierto", desc: "Las contribuciones son bienvenidas en Gitea." },
];
const features = isEs ? featuresEs : isEn ? featuresEn : featuresPt;
return (
<div className="site-canvas min-h-screen">
@@ -56,8 +70,8 @@ export default async function Sobre({ params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
{ nome: isPt ? "Início" : "Home", caminho: isPt ? "/" : "/en" },
{ nome: isPt ? "Sobre o Projeto" : "About the Project", caminho: isPt ? "/sobre" : "/en/about" },
{ nome: isEs ? "Inicio" : isPt ? "Início" : "Home", caminho: isEn ? "/en" : isEs ? "/es" : "/" },
{ nome: isEs ? "Acerca del proyecto" : isPt ? "Sobre o Projeto" : "About the Project", caminho: isEn ? "/en/about" : isEs ? "/es/acerca" : "/sobre" },
])
),
}}
@@ -67,20 +81,23 @@ export default async function Sobre({ params }) {
<main id="conteudo">
<PageHero
isEn={!isPt}
trail={[{ label: isPt ? "Sobre" : "About" }]}
isEn={isEn}
isEs={isEs}
trail={[{ label: isEs ? "Acerca" : isPt ? "Sobre" : "About" }]}
icon={Info}
label={isPt ? "Projeto independente" : "Independent project"}
title={isPt ? "Sobre o ESPDocs" : "About ESPDocs"}
label={isEs ? "Proyecto independiente" : isPt ? "Projeto independente" : "Independent project"}
title={isEs ? "Acerca de ESPDocs" : isPt ? "Sobre o ESPDocs" : "About ESPDocs"}
lede={
isPt
isEs
? "Una plataforma no oficial, en español, que organiza la información técnica del ESP32 y le conecta con la documentación oficial de Espressif. Creada para la comunidad de hardware abierto."
: isPt
? "Uma plataforma não oficial, em português, que organiza as informações técnicas do ESP32 e leva você à documentação oficial da Espressif. Feita para a comunidade de hardware livre."
: "An unofficial platform organizing technical information about the ESP32 and linking directly to official Espressif documentation. Built for the open hardware community."
}
>
<a href={REPOSITORIO} target="_blank" rel="noopener noreferrer" className="btn btn-primary">
<GiteaMark className="h-4 w-4" />
{isPt ? "Ver o repositório" : "View repository"}
{isEs ? "Ver el repositorio" : isPt ? "Ver o repositório" : "View repository"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
</a>
</PageHero>
@@ -88,18 +105,22 @@ export default async function Sobre({ params }) {
<div className="shell section grid grid-cols-1 gap-10 pt-12 sm:pt-16 lg:grid-cols-[1fr_22rem] lg:gap-14">
<div className="min-w-0 max-w-3xl space-y-12">
<section aria-labelledby="missao">
<h2 id="missao" className="section-title">{isPt ? "Missão" : "Mission"}</h2>
<h2 id="missao" className="section-title">{isEs ? "Misión" : isPt ? "Missão" : "Mission"}</h2>
<p className="mt-4 text-base leading-relaxed text-muted">
{isPt
{isEs
? "Facilitar el acceso y la comprensión del ecosistema ESP32 con una interfaz en español que organiza especificaciones, compara series, presenta frameworks y centraliza los enlaces a la documentación oficial. El objetivo es ahorrar tiempo en el desarrollo de firmware e IoT."
: isPt
? "Facilitar o acesso e a compreensão do ecossistema ESP32 com uma interface em português que organiza especificações, compara séries, apresenta frameworks e centraliza os links da documentação oficial. O objetivo é economizar o seu tempo no desenvolvimento de firmware e IoT."
: "Facilitate access and understanding of the ESP32 ecosystem with an interface organizing specifications, comparing series, presenting frameworks and centralizing links to official documentation. The goal is to save development time in firmware and IoT projects."}
</p>
</section>
<section aria-labelledby="porque">
<h2 id="porque" className="section-title">{isPt ? "Por que o ESPDocs existe" : "Why ESPDocs exists"}</h2>
<h2 id="porque" className="section-title">{isEs ? "¿Por qué existe ESPDocs?" : isPt ? "Por que o ESPDocs existe" : "Why ESPDocs exists"}</h2>
<p className="mt-4 text-base leading-relaxed text-muted">
{isPt
{isEs
? "La información sobre el ESP32 suele estar dispersa entre hojas de datos densas, manuales de referencia técnica (TRM) y repositorios secundarios. ESPDocs condensa y estructura estos datos para una evaluación rápida antes de profundizar en la ingeniería oficial."
: isPt
? "As informações sobre os ESP32 costumam estar espalhadas entre datasheets densos, manuais de referência (TRM) e repositórios secundários, tudo em inglês. O ESPDocs condensa e estrutura esses dados para a triagem rápida, antes de você aprofundar na engenharia oficial."
: "Information regarding the ESP32 is often scattered across dense datasheets, Technical Reference Manuals (TRM), and secondary repositories. ESPDocs condenses and structures this data for rapid evaluation before diving into in-depth engineering documentation."}
</p>
@@ -119,14 +140,22 @@ export default async function Sobre({ params }) {
</section>
<section aria-labelledby="contribuir">
<h2 id="contribuir" className="section-title">{isPt ? "Como contribuir" : "How to contribute"}</h2>
<h2 id="contribuir" className="section-title">{isEs ? "Cómo contribuir" : isPt ? "Como contribuir" : "How to contribute"}</h2>
<p className="mt-4 text-base leading-relaxed text-muted">
{isPt
{isEs
? "Las correcciones de traducción, ajustes en tablas y nuevos ejemplos de código son muy bienvenidos. El proyecto crece junto con la comunidad."
: isPt
? "Correções de tradução, ajustes em tabelas e novos exemplos de código são muito bem-vindos. O projeto cresce com a comunidade."
: "Translation fixes, table refinements and new code examples are welcome. The project grows with the community."}
</p>
<ol className="mt-6 space-y-3">
{(isPt
{(isEs
? [
"Haga un fork del repositorio en Gitea.",
"Implemente las mejoras y verifique las referencias técnicas oficiales.",
"Abra una solicitud de extracción (Pull Request) describiendo los cambios realizados.",
]
: isPt
? [
"Faça o fork do repositório no Gitea.",
"Implemente as melhorias e confira as referências técnicas oficiais.",
@@ -149,16 +178,18 @@ export default async function Sobre({ params }) {
</section>
<section aria-labelledby="acessibilidade">
<h2 id="acessibilidade" className="section-title">{isPt ? "Acessibilidade" : "Accessibility"}</h2>
<h2 id="acessibilidade" className="section-title">{isEs ? "Accesibilidad" : isPt ? "Acessibilidade" : "Accessibility"}</h2>
<p className="mt-4 text-base leading-relaxed text-muted">
{isPt
{isEs
? "ESPDocs prioriza la legibilidad, el alto contraste (nivel AAA de WCAG) y la navegación completa por teclado. Si encuentra alguna barrera de lectura o navegación, abra un issue en el repositorio."
: isPt
? "O ESPDocs prioriza a legibilidade, o contraste alto (nível AAA da WCAG) e a navegação completa por teclado. Se encontrar alguma barreira de leitura ou navegação, abra uma issue no repositório."
: "ESPDocs prioritizes readability, high contrast (WCAG AAA level), and full keyboard navigation. If you encounter any accessibility or navigation hurdles, please open an issue in the repository."}
</p>
</section>
</div>
<aside className="space-y-5 lg:sticky lg:top-28 lg:self-start" aria-label={isPt ? "Autor e aviso legal" : "Author and legal notice"}>
<aside className="space-y-5 lg:sticky lg:top-28 lg:self-start" aria-label={isEs ? "Autor y aviso legal" : isPt ? "Autor e aviso legal" : "Author and legal notice"}>
<div className="panel p-6 sm:p-7">
<div className="flex items-center gap-4">
<span className="grid h-16 w-16 shrink-0 place-items-center rounded-2xl border border-line bg-white">
@@ -166,11 +197,20 @@ export default async function Sobre({ params }) {
</span>
<div>
<h2 className="font-display text-lg font-semibold text-ink">Lucas Rayan Guerra</h2>
<p className="text-sm text-muted">{isPt ? "Criador do Ciência Embarcada" : "Creator of Ciência Embarcada"}</p>
<p className="text-sm text-muted">{isEs ? "Creador de Ciência Embarcada" : isPt ? "Criador do Ciência Embarcada" : "Creator of Ciência Embarcada"}</p>
</div>
</div>
<div className="mt-5 space-y-3 text-sm leading-relaxed text-muted">
{isPt ? (
{isEs ? (
<>
<p>
Técnico en Desarrollo de Sistemas, estudiante de Ciencias de la Computación en la UFRPE e investigador en Blockchain y Criptografía.
</p>
<p>
Director de Software en Semine AgriTech y ponente en eventos como REC&apos;n&apos;Play, compartiendo contenido sobre hardware, IoT y ciberseguridad.
</p>
</>
) : isPt ? (
<>
<p>
Técnico em Desenvolvimento de Sistemas, estudante de Ciência da Computação na UFRPE e pesquisador de
@@ -193,15 +233,17 @@ export default async function Sobre({ params }) {
)}
</div>
<a href="https://cienciaembarcada.com.br" target="_blank" rel="noopener noreferrer" className="btn btn-ghost mt-6 w-full">
{isPt ? "Visitar o Ciência Embarcada" : "Visit Ciência Embarcada"}
{isEs ? "Visitar Ciência Embarcada" : isPt ? "Visitar o Ciência Embarcada" : "Visit Ciência Embarcada"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
</a>
</div>
<div className="callout callout-warn">
<p className="font-semibold text-ink">{isPt ? "Aviso legal" : "Legal notice"}</p>
<p className="font-semibold text-ink">{isEs ? "Aviso legal" : isPt ? "Aviso legal" : "Legal notice"}</p>
<p className="mt-1">
{isPt
{isEs
? "ESPDocs es un agregador y organizador. Los manuales y repositorios referenciados pertenecen a Espressif Systems. En proyectos críticos o comerciales, consulte siempre la documentación oficial."
: isPt
? "O ESPDocs é um agregador e organizador. Os manuais e repositórios referenciados pertencem à Espressif Systems. Em projetos críticos ou comerciais, consulte sempre a documentação oficial."
: "ESPDocs is an aggregator and curation platform. Referenced manuals and repositories are property of Espressif Systems. In mission-critical or commercial designs, always consult official documentation."}
</p>