feat(i18n): Spanish localization (phase 4)
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:
1 parent
0940814522
commit
660b28c09d
72 files changed
+17848
-947
No files matched your search
+113
-21
@@ -6,6 +6,8 @@ import seriesDataPt from "@/public/series.json";
|
||||
import frameworksDataPt from "@/public/frameworks.json";
|
||||
import seriesDataEn from "@/data/en/series.json";
|
||||
import frameworksDataEn from "@/data/en/frameworks.json";
|
||||
import seriesDataEs from "@/data/es/series.json";
|
||||
import frameworksDataEs from "@/data/es/frameworks.json";
|
||||
import { SITE } from "@/lib/seo";
|
||||
import { getLocalizedHref } from "@/i18n/links";
|
||||
import {
|
||||
@@ -112,6 +114,51 @@ const TOOLS_EN = [
|
||||
},
|
||||
];
|
||||
|
||||
const TOOLS_ES = [
|
||||
{
|
||||
href: "/seletor",
|
||||
title: "Selector inteligente",
|
||||
desc: "Responda a ocho preguntas sobre su proyecto y reciba los chips más compatibles, con justificación técnica.",
|
||||
icon: SlidersHorizontal,
|
||||
action: "Encontrar mi chip",
|
||||
},
|
||||
{
|
||||
href: "/comparacao",
|
||||
title: "Comparador de series",
|
||||
desc: "Compare hasta cuatro modelos frente a frente: núcleos, radio, memoria, periféricos y seguridad.",
|
||||
icon: Scale,
|
||||
action: "Comparar ahora",
|
||||
},
|
||||
{
|
||||
href: "/diagnostico",
|
||||
title: "Diagnóstico de fallos",
|
||||
desc: "Causas y soluciones para brownout, bootloop, Guru Meditation, watchdog y fallos de flash.",
|
||||
icon: AlertTriangle,
|
||||
action: "Abrir diagnóstico",
|
||||
},
|
||||
{
|
||||
href: "/componentes",
|
||||
title: "Component Registry",
|
||||
desc: "Drivers, bibliotecas y BSPs del registro oficial de Espressif, con manifiesto listo para su proyecto.",
|
||||
icon: Box,
|
||||
action: "Explorar componentes",
|
||||
},
|
||||
{
|
||||
href: "/frameworks",
|
||||
title: "Frameworks y SDKs",
|
||||
desc: "ESP-IDF, Arduino, Rust, Matter, MicroPython y las herramientas que rodean al silicio.",
|
||||
icon: Terminal,
|
||||
action: "Ver frameworks",
|
||||
},
|
||||
{
|
||||
href: "/catalogo",
|
||||
title: "Catálogo de placas",
|
||||
desc: "DevKits y módulos reales, con interfaz USB, sensores integrados y enlace directo a la tienda oficial.",
|
||||
icon: Cpu,
|
||||
action: "Ver placas",
|
||||
},
|
||||
];
|
||||
|
||||
const ERRORS_PT = [
|
||||
{
|
||||
code: "Brownout detector was triggered",
|
||||
@@ -158,15 +205,40 @@ const ERRORS_EN = [
|
||||
},
|
||||
];
|
||||
|
||||
const ERRORS_ES = [
|
||||
{
|
||||
code: "Brownout detector was triggered",
|
||||
cause: "Caída de tensión al inicializar la etapa de radio.",
|
||||
fix: "Condensador de 10–100 µF junto al chip y fuente de 5 V con corriente suficiente.",
|
||||
},
|
||||
{
|
||||
code: "Guru Meditation Error",
|
||||
cause: "Puntero inválido, desbordamiento de pila o uso incorrecto de interrupciones.",
|
||||
fix: "Analice el backtrace con addr2line y dimensione la pila de la tarea en FreeRTOS.",
|
||||
},
|
||||
{
|
||||
code: "ADC2 no disponible con Wi-Fi",
|
||||
cause: "El controlador de radio reserva ADC2 mientras permanece activo.",
|
||||
fix: "Utilice los pines de ADC1 para sensores analógicos que funcionen junto con Wi-Fi.",
|
||||
},
|
||||
{
|
||||
code: "Flash Read err / bootloader",
|
||||
cause: "Tensión o modo de flash incorrectos, o pin de strapping forzado en el arranque.",
|
||||
fix: "Compruebe la línea de 3,3 V, los pull-ups de arranque y la configuración de flash en esptool.",
|
||||
},
|
||||
];
|
||||
|
||||
export default async function Home({ params }) {
|
||||
const { locale } = await params;
|
||||
const isPt = locale === "pt";
|
||||
const isEn = locale === "en";
|
||||
const isEs = locale === "es";
|
||||
const getLinkHref = (href) => getLocalizedHref(`/${locale}${href}`, locale);
|
||||
|
||||
const seriesData = isPt ? seriesDataPt : seriesDataEn;
|
||||
const frameworksData = isPt ? frameworksDataPt : frameworksDataEn;
|
||||
const tools = isPt ? TOOLS_PT : TOOLS_EN;
|
||||
const errors = isPt ? ERRORS_PT : ERRORS_EN;
|
||||
const seriesData = isPt ? seriesDataPt : isEs ? seriesDataEs : seriesDataEn;
|
||||
const frameworksData = isPt ? frameworksDataPt : isEs ? frameworksDataEs : frameworksDataEn;
|
||||
const tools = isPt ? TOOLS_PT : isEs ? TOOLS_ES : TOOLS_EN;
|
||||
const errors = isPt ? ERRORS_PT : isEs ? ERRORS_ES : ERRORS_EN;
|
||||
|
||||
const seriesCount = Object.keys(seriesData).length;
|
||||
const frameworkCount = Object.keys(frameworksData).length;
|
||||
@@ -181,7 +253,7 @@ export default async function Home({ params }) {
|
||||
<div className="mx-auto max-w-4xl text-center">
|
||||
<span className="chip chip-accent">
|
||||
<Cpu className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{isPt ? "Documentação aberta do ESP32" : "Open ESP32 documentation"}
|
||||
{isPt ? "Documentação aberta do ESP32" : isEs ? "Documentación abierta de ESP32" : "Open ESP32 documentation"}
|
||||
</span>
|
||||
<h1 className="display mt-6 text-[clamp(2.9rem,7.4vw,5.4rem)]">
|
||||
{isPt ? (
|
||||
@@ -189,6 +261,11 @@ export default async function Home({ params }) {
|
||||
O ESP32,
|
||||
<br className="sm:hidden" /> sem ruído.
|
||||
</>
|
||||
) : isEs ? (
|
||||
<>
|
||||
El ESP32,
|
||||
<br className="sm:hidden" /> sin ruido.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
The ESP32,
|
||||
@@ -199,17 +276,21 @@ export default async function Home({ params }) {
|
||||
<p className="lede mx-auto mt-6 max-w-2xl">
|
||||
{isPt
|
||||
? "Especificações, pinagem, comparações e diagnóstico em uma leitura só. Menos tradução de datasheet, mais tempo com o protótipo na bancada."
|
||||
: isEs
|
||||
? "Especificaciones, patillaje, comparaciones y diagnóstico en una sola lectura. Menos traducción de hojas de datos, más tiempo con el prototipo en el banco de trabajo."
|
||||
: "Specifications, pinouts, comparisons and diagnostics in a single read. Less datasheet translation, more workbench time."}
|
||||
</p>
|
||||
|
||||
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
||||
<Link href={getLinkHref("/seletor")} className="btn btn-primary w-full sm:w-auto">
|
||||
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
|
||||
{isPt ? "Escolher meu chip" : "Choose my chip"}
|
||||
{isPt ? "Escolher meu chip" : isEs ? "Elegir mi chip" : "Choose my chip"}
|
||||
</Link>
|
||||
<Link href={getLinkHref("/series")} className="btn btn-ghost w-full sm:w-auto">
|
||||
{isPt ? (
|
||||
<>Explorar as {seriesCount} séries</>
|
||||
) : isEs ? (
|
||||
<>Explorar las {seriesCount} series</>
|
||||
) : (
|
||||
<>Explore the {seriesCount} series</>
|
||||
)}
|
||||
@@ -218,7 +299,7 @@ export default async function Home({ params }) {
|
||||
</div>
|
||||
|
||||
<p className="mt-7 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-sm text-muted">
|
||||
<span><strong className="font-semibold text-ink">{seriesCount}</strong> {isPt ? "séries" : "series"}</span>
|
||||
<span><strong className="font-semibold text-ink">{seriesCount}</strong> {isPt ? "séries" : isEs ? "series" : "series"}</span>
|
||||
<span aria-hidden="true" className="text-line-strong">·</span>
|
||||
<span><strong className="font-semibold text-ink">{frameworkCount}</strong> frameworks</span>
|
||||
{isPt ? (
|
||||
@@ -228,6 +309,11 @@ export default async function Home({ params }) {
|
||||
<span aria-hidden="true" className="text-line-strong">·</span>
|
||||
<span>100% em português</span>
|
||||
</>
|
||||
) : isEs ? (
|
||||
<>
|
||||
<span aria-hidden="true" className="text-line-strong">·</span>
|
||||
<span>100% en español</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span aria-hidden="true" className="text-line-strong">·</span>
|
||||
@@ -240,13 +326,13 @@ export default async function Home({ params }) {
|
||||
<div className="panel mt-12 overflow-hidden sm:mt-16">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 px-5 py-4 sm:px-7">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold text-ink">{isPt ? "Séries em destaque" : "Featured series"}</h2>
|
||||
<h2 className="text-sm font-semibold text-ink">{isPt ? "Séries em destaque" : isEs ? "Series destacadas" : "Featured series"}</h2>
|
||||
<p className="mt-0.5 text-xs text-muted">
|
||||
{isPt ? "O essencial de cada família, antes de abrir o datasheet." : "The essentials of each family before opening the datasheet."}
|
||||
{isPt ? "O essencial de cada família, antes de abrir o datasheet." : isEs ? "Lo esencial de cada familia, antes de abrir la hoja de datos." : "The essentials of each family before opening the datasheet."}
|
||||
</p>
|
||||
</div>
|
||||
<Link href={getLinkHref("/series")} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent transition-colors hover:text-accent-strong">
|
||||
{isPt ? "Índice completo" : "Full index"}
|
||||
{isPt ? "Índice completo" : isEs ? "Índice completo" : "Full index"}
|
||||
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
@@ -271,7 +357,7 @@ export default async function Home({ params }) {
|
||||
<div className="mt-5 flex items-center justify-between gap-3 border-t border-line pt-3.5">
|
||||
<span className="truncate font-mono text-[0.6875rem] text-ink-soft">{serie.frequencia}</span>
|
||||
<span className="inline-flex shrink-0 items-center gap-1 text-xs font-medium text-accent">
|
||||
{isPt ? "Ficha" : "Spec sheet"}
|
||||
{isPt ? "Ficha" : isEs ? "Ficha" : "Spec sheet"}
|
||||
<ArrowUpRight className="h-3.5 w-3.5 transition-transform duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
@@ -352,13 +438,15 @@ export default async function Home({ params }) {
|
||||
|
||||
<section className="shell section" aria-labelledby="ferramentas-titulo">
|
||||
<div className="mx-auto max-w-2xl text-center">
|
||||
<p className="eyebrow">{isPt ? "Ferramentas" : "Tools"}</p>
|
||||
<p className="eyebrow">{isPt ? "Ferramentas" : isEs ? "Herramientas" : "Tools"}</p>
|
||||
<h2 id="ferramentas-titulo" className="display mt-4 text-[clamp(2rem,4.8vw,3.4rem)]">
|
||||
{isPt ? "Comece pelo problema." : "Start with the problem."}
|
||||
{isPt ? "Comece pelo problema." : isEs ? "Empiece por el problema." : "Start with the problem."}
|
||||
</h2>
|
||||
<p className="lede mt-4">
|
||||
{isPt
|
||||
? "Cada ferramenta resolve uma etapa concreta do projeto, sem cadastro e sem rodeio."
|
||||
: isEs
|
||||
? "Cada herramienta resuelve una fase concreta del proyecto, sin registros ni rodeos."
|
||||
: "Each tool addresses a concrete development phase, with no sign-ups and zero fluff."}
|
||||
</p>
|
||||
</div>
|
||||
@@ -386,18 +474,20 @@ export default async function Home({ params }) {
|
||||
<section className="shell section pt-0" aria-labelledby="diagnostico-titulo">
|
||||
<div className="grid items-start gap-10 lg:grid-cols-[0.78fr_1.22fr] lg:gap-16">
|
||||
<div className="lg:sticky lg:top-28">
|
||||
<p className="eyebrow">{isPt ? "Diagnóstico" : "Diagnostics"}</p>
|
||||
<p className="eyebrow">{isPt ? "Diagnóstico" : isEs ? "Diagnóstico" : "Diagnostics"}</p>
|
||||
<h2 id="diagnostico-titulo" className="display mt-4 text-[clamp(2rem,4.8vw,3.4rem)]">
|
||||
{isPt ? "O primeiro boot não precisa virar noite." : "First boot doesn't have to take all night."}
|
||||
{isPt ? "O primeiro boot não precisa virar noite." : isEs ? "El primer arranque no tiene por qué desvelarlo." : "First boot doesn't have to take all night."}
|
||||
</h2>
|
||||
<p className="lede mt-4">
|
||||
{isPt
|
||||
? "Os erros mais comuns têm causa conhecida. O guia parte da mensagem no terminal e chega ao ajuste de hardware ou firmware."
|
||||
: isEs
|
||||
? "Los errores más comunes tienen una causa conocida. Esta guía parte del mensaje en el terminal y llega al ajuste de hardware o firmware."
|
||||
: "Common errors have known causes. This guide starts with the serial terminal log and leads to the hardware or firmware adjustment."}
|
||||
</p>
|
||||
<Link href={getLinkHref("/diagnostico")} className="btn btn-accent mt-7">
|
||||
<AlertTriangle className="h-4 w-4" aria-hidden="true" />
|
||||
{isPt ? "Abrir os 30+ diagnósticos" : "Open the 30+ diagnostics"}
|
||||
{isPt ? "Abrir os 30+ diagnósticos" : isEs ? "Abrir los 30+ diagnósticos" : "Open the 30+ diagnostics"}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -407,7 +497,7 @@ export default async function Home({ params }) {
|
||||
<Terminal className="h-4 w-4" aria-hidden="true" />
|
||||
boot.log
|
||||
</span>
|
||||
<span className="text-xs text-[#a6afbd]">{isPt ? "4 exemplos de 30+" : "4 examples of 30+"}</span>
|
||||
<span className="text-xs text-[#a6afbd]">{isPt ? "4 exemplos de 30+" : isEs ? "4 ejemplos de 30+" : "4 examples of 30+"}</span>
|
||||
</div>
|
||||
<div className="divide-y divide-white/10">
|
||||
{errors.map((item) => (
|
||||
@@ -424,22 +514,24 @@ export default async function Home({ params }) {
|
||||
|
||||
<section className="shell pb-20 pt-4 md:pb-28">
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
<p className="eyebrow">{isPt ? "Curadoria independente" : "Independent curation"}</p>
|
||||
<p className="eyebrow">{isPt ? "Curadoria independente" : isEs ? "Curaduría independiente" : "Independent curation"}</p>
|
||||
<h2 className="display mt-4 text-[clamp(2rem,4.5vw,3.2rem)]">
|
||||
{isPt ? "Feito para consultar no meio do projeto." : "Built for quick lookup mid-project."}
|
||||
{isPt ? "Feito para consultar no meio do projeto." : isEs ? "Diseñado para consultar en mitad del proyecto." : "Built for quick lookup mid-project."}
|
||||
</h2>
|
||||
<p className="lede mx-auto mt-4 max-w-2xl">
|
||||
{isPt
|
||||
? "O ESPDocs organiza dados públicos da Espressif e aponta sempre para a fonte oficial. Para decisões críticas, o datasheet e o Technical Reference Manual continuam sendo a palavra final."
|
||||
: isEs
|
||||
? "ESPDocs organiza datos públicos de Espressif y apunta siempre a la fuente oficial. Para decisiones críticas, la hoja de datos y el Technical Reference Manual continúan teniendo la última palabra."
|
||||
: "ESPDocs organizes public Espressif data and points directly to official sources. For critical engineering decisions, the datasheet and Technical Reference Manual remain the definitive authority."}
|
||||
</p>
|
||||
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
||||
<Link href={getLinkHref("/glossario")} className="btn btn-ghost w-full sm:w-auto">
|
||||
<BookOpen className="h-4 w-4" aria-hidden="true" />
|
||||
{isPt ? "Glossário" : "Glossary"}
|
||||
{isPt ? "Glossário" : isEs ? "Glosario" : "Glossary"}
|
||||
</Link>
|
||||
<Link href={getLinkHref("/sobre")} className="btn btn-primary w-full sm:w-auto">
|
||||
{isPt ? "Sobre o projeto" : "About the project"}
|
||||
{isPt ? "Sobre o projeto" : isEs ? "Acerca del proyecto" : "About the project"}
|
||||
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user