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() {