From 660b28c09dbd5cefe2ce93f8eca0509f629da7f1 Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Sun, 1 Nov 2026 19:50:20 -0300 Subject: [PATCH] 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 --- CHANGELOG.md | 14 + app/[locale]/catalogo/layout.jsx | 19 +- app/[locale]/catalogo/page.jsx | 71 +- app/[locale]/comparacao/layout.jsx | 36 +- app/[locale]/comparacao/page.jsx | 147 +- app/[locale]/componentes/layout.jsx | 61 +- app/[locale]/componentes/page.jsx | 104 +- app/[locale]/diagnostico/layout.jsx | 56 +- app/[locale]/diagnostico/page.jsx | 133 +- app/[locale]/frameworks/[key]/page.jsx | 68 +- app/[locale]/frameworks/layout.jsx | 32 +- app/[locale]/frameworks/page.jsx | 73 +- app/[locale]/glossario/layout.jsx | 37 +- app/[locale]/glossario/page.jsx | 65 +- app/[locale]/layout.jsx | 16 + app/[locale]/not-found.jsx | 22 +- app/[locale]/page.jsx | 134 +- app/[locale]/seletor/layout.jsx | 45 +- app/[locale]/seletor/page.jsx | 764 +++++----- app/[locale]/series/[key]/page.jsx | 254 +++- app/[locale]/series/layout.jsx | 31 +- app/[locale]/series/page.jsx | 77 +- app/[locale]/sobre/page.jsx | 96 +- app/api/busca/[locale]/route.js | 4 +- components/BoardCard.jsx | 32 +- components/CodeExample.jsx | 46 +- components/ConnectionsDiagram.jsx | 107 +- components/Footer.jsx | 52 +- components/Header.jsx | 78 +- components/LanguageSelector.jsx | 4 +- components/PageHero.jsx | 13 +- components/PinRestrictions.jsx | 88 +- components/SearchDialog.jsx | 55 +- components/SeriesTabMenu.jsx | 6 +- data/es/conexoes/ESP32-C2.json | 422 ++++++ data/es/conexoes/ESP32-C3.json | 643 ++++++++ data/es/conexoes/ESP32-C5.json | 944 ++++++++++++ data/es/conexoes/ESP32-C6.json | 841 +++++++++++ data/es/conexoes/ESP32-C61.json | 729 +++++++++ data/es/conexoes/ESP32-H2.json | 603 ++++++++ data/es/conexoes/ESP32-P4.json | 1902 ++++++++++++++++++++++++ data/es/conexoes/ESP32-S2.json | 1197 +++++++++++++++ data/es/conexoes/ESP32-S3.json | 1236 +++++++++++++++ data/es/conexoes/ESP32-S31.json | 1326 +++++++++++++++++ data/es/conexoes/ESP32.json | 1132 ++++++++++++++ data/es/frameworks.json | 411 +++++ data/es/series.json | 774 ++++++++++ i18n/links.js | 4 +- lib/data/es/componentes.js | 1264 ++++++++++++++++ lib/data/es/diagnostico.js | 700 +++++++++ lib/data/es/glossario.js | 408 +++++ lib/data/es/topicos.js | 173 +++ lib/searchIndex.js | 68 +- lib/seo.js | 80 +- messages/es.json | 152 ++ public/exemplos/es/arduino.ino | 29 + public/exemplos/es/esp-adf.c | 72 + public/exemplos/es/esp-dsp.c | 37 + public/exemplos/es/esp-idf.c | 56 + public/exemplos/es/esp-iot-solution.c | 72 + public/exemplos/es/esp-matter.c | 44 + public/exemplos/es/esp-nn.c | 44 + public/exemplos/es/esp-now.c | 92 ++ public/exemplos/es/esp-rainmaker.c | 76 + public/exemplos/es/esp-sr.c | 98 ++ public/exemplos/es/esp-who.c | 71 + public/exemplos/es/esp-zigbee.c | 97 ++ public/exemplos/es/micropython.py | 11 + public/exemplos/es/rust-esp.rs | 48 + scripts/check-i18n.mjs | 20 +- scripts/smoke-imagem.sh | 19 +- scripts/verifica-vazamentos.py | 60 +- 72 files changed, 17848 insertions(+), 947 deletions(-) create mode 100644 data/es/conexoes/ESP32-C2.json create mode 100644 data/es/conexoes/ESP32-C3.json create mode 100644 data/es/conexoes/ESP32-C5.json create mode 100644 data/es/conexoes/ESP32-C6.json create mode 100644 data/es/conexoes/ESP32-C61.json create mode 100644 data/es/conexoes/ESP32-H2.json create mode 100644 data/es/conexoes/ESP32-P4.json create mode 100644 data/es/conexoes/ESP32-S2.json create mode 100644 data/es/conexoes/ESP32-S3.json create mode 100644 data/es/conexoes/ESP32-S31.json create mode 100644 data/es/conexoes/ESP32.json create mode 100644 data/es/frameworks.json create mode 100644 data/es/series.json create mode 100644 lib/data/es/componentes.js create mode 100644 lib/data/es/diagnostico.js create mode 100644 lib/data/es/glossario.js create mode 100644 lib/data/es/topicos.js create mode 100644 messages/es.json create mode 100644 public/exemplos/es/arduino.ino create mode 100644 public/exemplos/es/esp-adf.c create mode 100644 public/exemplos/es/esp-dsp.c create mode 100644 public/exemplos/es/esp-idf.c create mode 100644 public/exemplos/es/esp-iot-solution.c create mode 100644 public/exemplos/es/esp-matter.c create mode 100644 public/exemplos/es/esp-nn.c create mode 100644 public/exemplos/es/esp-now.c create mode 100644 public/exemplos/es/esp-rainmaker.c create mode 100644 public/exemplos/es/esp-sr.c create mode 100644 public/exemplos/es/esp-who.c create mode 100644 public/exemplos/es/esp-zigbee.c create mode 100644 public/exemplos/es/micropython.py create mode 100644 public/exemplos/es/rust-esp.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b077119..c1b9896 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ Tudo o que muda de relevante no ESPDocs está aqui. O formato segue o ## [Não lançado] +### Adicionado + +- Internacionalização completa para o idioma espanhol neutro latino-americano (`es`) (Fase 4): + - Ativação do espanhol em `ENABLED_LOCALES = ["pt", "en", "es"]` e no seletor de idiomas do cabeçalho. + - Tradução técnica integral e paridade estrutural em `messages/es.json` (15 namespaces) e arquivos de dados em `data/es/` (`series.json`, `frameworks.json`, `conexoes/*.json`) e `lib/data/es/` (`glossario.js`, `diagnostico.js`, `componentes.js`, `topicos.js`). + - 14 exemplos práticos de código com comentários em espanhol em `public/exemplos/es/`. + - Roteamento com slugs canônicos em espanhol (`/es/catalogo`, `/es/comparacion`, `/es/componentes`, `/es/diagnostico`, `/es/frameworks`, `/es/glosario`, `/es/selector`, `/es/acerca`). + - Busca por idioma em `/api/busca/es` com normalização de acentos e 'ñ', sem menção ao livro e com links para os slugs canônicos em espanhol. + - Exclusão total e estrita de referências ao livro no idioma espanhol (menu, rodapé, busca, sitemap e metadados), mantendo `/es/livro` respondendo HTTP 404. + - Tabela de catálogo com cabeçalhos traduzidos e mapeamento de valores na interface em espanhol. + - Negociação de idioma na raiz com roteamento para `es` para cabeçalhos Accept-Language `es-*` ou cookie `NEXT_LOCALE=es`, e fallback padrão para `en` para navegadores com idiomas inativos (`fr`, `de`, `zh-CN`). + - Metadados de SEO, Open Graph e Twitter Cards localizados com `og:locale` (`es_419`), `inLanguage` (`es-419`) e `hreflang` cruzado entre `pt-BR`, `en`, `es-419` e `x-default`. + - Extensão dos scripts de verificação: `scripts/check-i18n.mjs` valida `es`, `scripts/smoke-imagem.sh` testa as rotas espanholas e `scripts/verifica-vazamentos.py` varre as 36 rotas em espanhol (texto visível e JSON-LD). + ## [1.3.0] - 2026-11-01 ### Adicionado diff --git a/app/[locale]/catalogo/layout.jsx b/app/[locale]/catalogo/layout.jsx index 3349bb2..d3a284b 100644 --- a/app/[locale]/catalogo/layout.jsx +++ b/app/[locale]/catalogo/layout.jsx @@ -3,15 +3,20 @@ import { paginaMeta, jsonLd } from "@/lib/seo"; export async function generateMetadata({ params }) { const { locale } = await params; const isEn = locale === "en"; + const isEs = locale === "es"; return paginaMeta({ - titulo: isEn ? "ESP32 Board Catalog" : "Catálogo de placas ESP32", + titulo: isEn ? "ESP32 Board Catalog" : isEs ? "Catálogo de placas ESP32" : "Catálogo de placas ESP32", descricao: isEn ? "ESP32 development boards and DevKits available in Brazil, with filters by series, connector, and USB-serial converter." + : isEs + ? "Placas de desarrollo y DevKits con ESP32, con filtros por serie, conector y conversor USB-serial." : "Placas de desenvolvimento com ESP32 disponíveis no Brasil, com filtro por série, conector e conversor USB-serial.", caminho: "/catalogo", locale, keywords: isEn ? ["ESP32 boards", "ESP32 DevKit", "buy ESP32", "ESP32 DevKitC"] + : isEs + ? ["placas ESP32", "DevKit ESP32", "comprar ESP32", "ESP32 DevKitC"] : ["placas ESP32", "DevKit ESP32", "comprar ESP32", "ESP32 DevKitC"], }); } @@ -19,6 +24,8 @@ export async function generateMetadata({ params }) { export default async function Layout({ children, params }) { const { locale } = await params; const isEn = locale === "en"; + const isEs = locale === "es"; + const canonPath = isEn ? "/en/catalog" : isEs ? "/es/catalogo" : "/catalogo"; return ( <> @@ -27,11 +34,13 @@ export default async function Layout({ children, params }) { dangerouslySetInnerHTML={{ __html: JSON.stringify( jsonLd.aplicacaoWeb({ - nome: isEn ? "ESP32 Development Board Catalog" : "Catálogo de Placas ESP32", + nome: isEn ? "ESP32 Development Board Catalog" : isEs ? "Catálogo de placas de desarrollo ESP32" : "Catálogo de Placas ESP32", descricao: isEn ? "Interactive guide and catalog of development boards and DevKits featuring ESP32 microcontrollers." + : isEs + ? "Guía y catálogo interactivo de placas de desarrollo y DevKits con microcontroladores ESP32." : "Guia e catálogo interativo de placas de desenvolvimento e DevKits com microcontroladores ESP32.", - caminho: isEn ? "/en/catalog" : "/catalogo", + caminho: canonPath, locale, }) ), @@ -42,8 +51,8 @@ export default async function Layout({ children, params }) { dangerouslySetInnerHTML={{ __html: JSON.stringify( jsonLd.trilha([ - { nome: isEn ? "Home" : "Início", caminho: isEn ? "/en" : "/" }, - { nome: isEn ? "Board Catalog" : "Catálogo de Placas", caminho: isEn ? "/en/catalog" : "/catalogo" }, + { nome: isEn ? "Home" : isEs ? "Inicio" : "Início", caminho: isEn ? "/en" : isEs ? "/es" : "/" }, + { nome: isEn ? "Board Catalog" : isEs ? "Catálogo de placas" : "Catálogo de Placas", caminho: canonPath }, ]) ), }} diff --git a/app/[locale]/catalogo/page.jsx b/app/[locale]/catalogo/page.jsx index eda051c..dcef360 100644 --- a/app/[locale]/catalogo/page.jsx +++ b/app/[locale]/catalogo/page.jsx @@ -33,9 +33,35 @@ const VALUE_MAP_EN = { "Opcional": "Optional", }; +const HEADER_MAP_ES = { + "Nome": "Nombre", + "Descrição": "Descripción", + "Série": "Serie", + "Loja Oficial": "Tienda oficial", + "Fabricante": "Fabricante", + "Processador": "Procesador", + "Memória Flash": "Memoria flash", + "PSRAM": "PSRAM", + "Conector USB": "Conector USB", + "Interface USB": "Interfaz USB", + "Antena": "Antena", + "Bateria": "Batería", + "Display": "Pantalla", + "Sensores": "Sensores", + "Botões": "Botones", + "LEDs": "LEDs", +}; + +const VALUE_MAP_ES = { + "Sim": "Sí", + "Não": "No", + "Opcional": "Opcional", +}; + export default function PlacasPage() { const pathname = usePathname() || ""; const isEn = pathname === "/en" || pathname.startsWith("/en/"); + const isEs = pathname === "/es" || pathname.startsWith("/es/"); const [boards, setBoards] = useState([]); const [filteredBoards, setFilteredBoards] = useState([]); @@ -64,7 +90,7 @@ export default function PlacasPage() { const response = await fetch("/api/placas"); if (!response.ok) { - throw new Error(isEn ? "Failed to load board data" : "Erro ao carregar dados das placas"); + throw new Error(isEn ? "Failed to load board data" : isEs ? "Error al cargar los datos de las placas" : "Erro ao carregar dados das placas"); } const result = await response.json(); @@ -168,13 +194,16 @@ export default function PlacasPage() { const hero = ( @@ -186,14 +215,14 @@ export default function PlacasPage() {
{hero} -
+
{Array.from({ length: 6 }).map((_, i) => (
))}

- {isEn ? "Querying boards database…" : "Consultando o banco de placas…"} + {isEn ? "Querying boards database…" : isEs ? "Consultando la base de datos de placas…" : "Consultando o banco de placas…"}

@@ -213,14 +242,14 @@ export default function PlacasPage() {