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
@@ -8,6 +8,7 @@ import PageHero from "@/components/PageHero";
|
||||
import Link from "next/link";
|
||||
import * as glossarioPt from "@/lib/glossarioData";
|
||||
import * as glossarioEn from "@/lib/data/en/glossario";
|
||||
import * as glossarioEs from "@/lib/data/es/glossario";
|
||||
import { getLocalizedHref } from "@/i18n/links";
|
||||
import {
|
||||
ArrowRight,
|
||||
@@ -40,8 +41,9 @@ const nomeCurto = (nome) => nome.split(/,| &/)[0].trim();
|
||||
export default function GlossarioPage() {
|
||||
const pathname = usePathname();
|
||||
const isEn = pathname?.startsWith("/en");
|
||||
const currentLocale = isEn ? "en" : "pt";
|
||||
const { categoriasGlossario } = isEn ? glossarioEn : glossarioPt;
|
||||
const isEs = pathname?.startsWith("/es");
|
||||
const currentLocale = isEs ? "es" : isEn ? "en" : "pt";
|
||||
const { categoriasGlossario } = isEs ? glossarioEs : isEn ? glossarioEn : glossarioPt;
|
||||
|
||||
const [busca, setBusca] = useState("");
|
||||
const [categoriaSelecionada, setCategoriaSelecionada] = useState("todas");
|
||||
@@ -109,22 +111,25 @@ export default function GlossarioPage() {
|
||||
<main id="conteudo">
|
||||
<PageHero
|
||||
isEn={isEn}
|
||||
trail={[{ label: isEn ? "Glossary" : "Glossário" }]}
|
||||
isEs={isEs}
|
||||
trail={[{ label: isEs ? "Glosario" : isEn ? "Glossary" : "Glossário" }]}
|
||||
icon={BookOpen}
|
||||
label={isEn ? `${todosOsTermos.length} terms explained` : `${todosOsTermos.length} termos explicados`}
|
||||
title={isEn ? "ESP32 Technical Glossary" : "Glossário técnico do ESP32"}
|
||||
label={isEs ? `${todosOsTermos.length} términos explicados` : isEn ? `${todosOsTermos.length} terms explained` : `${todosOsTermos.length} termos explicados`}
|
||||
title={isEs ? "Glosario técnico de ESP32" : isEn ? "ESP32 Technical Glossary" : "Glossário técnico do ESP32"}
|
||||
lede={
|
||||
isEn
|
||||
isEs
|
||||
? "Definiciones directas, parámetros de hardware y un ejemplo práctico para cada término del ecosistema, desde ADC hasta Secure Boot."
|
||||
: isEn
|
||||
? "Direct definitions, hardware parameters, and practical examples for every ecosystem term, from ADC to Secure Boot."
|
||||
: "Definições diretas, parâmetros de hardware e um exemplo prático para cada termo do ecossistema, de ADC a Secure Boot."
|
||||
}
|
||||
/>
|
||||
|
||||
<section className="shell pt-10 sm:pt-12" aria-label={isEn ? "Search and filters" : "Busca e filtros"}>
|
||||
<section className="shell pt-10 sm:pt-12" aria-label={isEs ? "Búsqueda y filtros" : isEn ? "Search and filters" : "Busca e filtros"}>
|
||||
<div className="panel p-5 sm:p-7">
|
||||
<div className="relative">
|
||||
<label htmlFor="busca-glossario" className="sr-only">
|
||||
{isEn ? "Search term, acronym, or concept" : "Buscar termo, sigla ou conceito"}
|
||||
{isEs ? "Buscar término, sigla o concepto" : isEn ? "Search term, acronym, or concept" : "Buscar termo, sigla ou conceito"}
|
||||
</label>
|
||||
<Search className="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted" aria-hidden="true" />
|
||||
<input
|
||||
@@ -133,7 +138,7 @@ export default function GlossarioPage() {
|
||||
type="search"
|
||||
value={busca}
|
||||
onChange={(e) => setBusca(e.target.value)}
|
||||
placeholder={isEn ? "Search term, acronym, or concept" : "Buscar termo, sigla ou conceito"}
|
||||
placeholder={isEs ? "Buscar término, sigla o concepto" : isEn ? "Search term, acronym, or concept" : "Buscar termo, sigla ou conceito"}
|
||||
autoComplete="off"
|
||||
className={`field pl-12 text-base ${busca ? "pr-28" : "pr-4"}`}
|
||||
/>
|
||||
@@ -141,14 +146,14 @@ export default function GlossarioPage() {
|
||||
{busca && (
|
||||
<button type="button" onClick={() => { setBusca(""); searchInputRef.current?.focus(); }} className="btn btn-ghost btn-sm">
|
||||
<X className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{isEn ? "Clear" : "Limpar"}
|
||||
{isEs ? "Limpiar" : isEn ? "Clear" : "Limpar"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-wrap items-center gap-2">
|
||||
<span className="mr-1 text-sm text-muted">{isEn ? "Popular searches" : "Mais buscados"}</span>
|
||||
<span className="mr-1 text-sm text-muted">{isEs ? "Más buscados" : isEn ? "Popular searches" : "Mais buscados"}</span>
|
||||
{BUSCAS_POPULARES.map((rotulo) => (
|
||||
<button
|
||||
key={rotulo}
|
||||
@@ -162,7 +167,7 @@ export default function GlossarioPage() {
|
||||
</div>
|
||||
|
||||
<div className="mt-5 border-t border-line pt-5">
|
||||
<h2 className="text-sm font-semibold text-ink">{isEn ? "Category" : "Categoria"}</h2>
|
||||
<h2 className="text-sm font-semibold text-ink">{isEs ? "Categoría" : isEn ? "Category" : "Categoria"}</h2>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@@ -171,7 +176,7 @@ export default function GlossarioPage() {
|
||||
className="chip-btn"
|
||||
>
|
||||
<LayoutGrid className="h-4 w-4" aria-hidden="true" />
|
||||
{isEn ? "All" : "Todas"}
|
||||
{isEs ? "Todas" : isEn ? "All" : "Todas"}
|
||||
<span className="tabular-nums opacity-80">{todosOsTermos.length}</span>
|
||||
</button>
|
||||
{categoriasGlossario.map((cat) => {
|
||||
@@ -194,10 +199,10 @@ export default function GlossarioPage() {
|
||||
</div>
|
||||
|
||||
<div className="mt-6 border-t border-line pt-6">
|
||||
<h2 className="text-sm font-semibold text-ink">{isEn ? "Alphabetical order" : "Ordem alfabética"}</h2>
|
||||
<div className="mt-3 flex flex-wrap gap-1.5" role="group" aria-label={isEn ? "Filter by first letter" : "Filtrar pela letra inicial"}>
|
||||
<h2 className="text-sm font-semibold text-ink">{isEs ? "Orden alfabético" : isEn ? "Alphabetical order" : "Ordem alfabética"}</h2>
|
||||
<div className="mt-3 flex flex-wrap gap-1.5" role="group" aria-label={isEs ? "Filtrar por letra inicial" : isEn ? "Filter by first letter" : "Filtrar pela letra inicial"}>
|
||||
<button type="button" onClick={() => setLetraFiltro(null)} aria-pressed={letraFiltro === null} className="chip-btn">
|
||||
{isEn ? "All" : "Todas"}
|
||||
{isEs ? "Todas" : isEn ? "All" : "Todas"}
|
||||
</button>
|
||||
{alfabeto.map(({ letra, temTermos }) => (
|
||||
<button
|
||||
@@ -219,13 +224,13 @@ export default function GlossarioPage() {
|
||||
<section className="shell section pt-8 sm:pt-10" aria-labelledby="resultado-titulo">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<h2 id="resultado-titulo" className="text-base font-semibold text-ink" role="status" aria-live="polite">
|
||||
{total} {total === 1 ? (isEn ? "term" : "termo") : (isEn ? "terms" : "termos")}
|
||||
{temFiltroAtivo && <span className="font-normal text-muted">{isEn ? " with current filters" : " com os filtros atuais"}</span>}
|
||||
{total} {total === 1 ? (isEs ? "término" : isEn ? "term" : "termo") : (isEs ? "términos" : isEn ? "terms" : "termos")}
|
||||
{temFiltroAtivo && <span className="font-normal text-muted">{isEs ? " con los filtros actuales" : isEn ? " with current filters" : " com os filtros atuais"}</span>}
|
||||
</h2>
|
||||
{temFiltroAtivo && (
|
||||
<button type="button" onClick={limparTodosFiltros} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
|
||||
<RotateCcw className="h-4 w-4" aria-hidden="true" />
|
||||
{isEn ? "Reset filters" : "Redefinir filtros"}
|
||||
{isEs ? "Restablecer filtros" : isEn ? "Reset filters" : "Redefinir filtros"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -234,16 +239,18 @@ export default function GlossarioPage() {
|
||||
<div className="card mt-6 px-6 py-14 text-center">
|
||||
<HelpCircle className="mx-auto h-10 w-10 text-muted" aria-hidden="true" />
|
||||
<h3 className="mt-4 font-display text-xl font-semibold text-ink">
|
||||
{isEn ? "No terms found" : "Nenhum termo encontrado"}
|
||||
{isEs ? "No se encontraron términos" : isEn ? "No terms found" : "Nenhum termo encontrado"}
|
||||
</h3>
|
||||
<p className="mx-auto mt-2 max-w-md text-sm text-muted">
|
||||
{isEn
|
||||
{isEs
|
||||
? "Nada coincide con la búsqueda y los filtros actuales. Pruebe con una sigla más corta o vuelva a todas las categorías."
|
||||
: isEn
|
||||
? "Nothing matches your current search and filters. Try a shorter acronym or reset to all categories."
|
||||
: "Nada combina com a busca e os filtros atuais. Tente uma sigla mais curta ou volte para todas as categorias."}
|
||||
</p>
|
||||
<button type="button" onClick={limparTodosFiltros} className="btn btn-primary mt-6">
|
||||
<RotateCcw className="h-4 w-4" aria-hidden="true" />
|
||||
{isEn ? "Clear filters" : "Limpar filtros"}
|
||||
{isEs ? "Limpiar filtros" : isEn ? "Clear filters" : "Limpar filtros"}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
@@ -260,7 +267,7 @@ export default function GlossarioPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copiarLinkTermo(item.slug)}
|
||||
aria-label={isEn ? `Copy link for ${item.termo}` : `Copiar link para ${item.termo}`}
|
||||
aria-label={isEs ? `Copiar enlace para ${item.termo}` : isEn ? `Copy link for ${item.termo}` : `Copiar link para ${item.termo}`}
|
||||
className="btn-icon inline-flex h-11 w-11 items-center justify-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-accent"
|
||||
>
|
||||
{copiadoSlug === item.slug ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Link2 className="h-4 w-4" aria-hidden="true" />}
|
||||
@@ -275,7 +282,7 @@ export default function GlossarioPage() {
|
||||
<div className="callout mt-5">
|
||||
<p className="flex items-center gap-1.5 text-sm font-semibold text-ink">
|
||||
<Lightbulb className="h-4 w-4 text-accent" aria-hidden="true" />
|
||||
{isEn ? "In practice" : "Na prática"}
|
||||
{isEs ? "En la práctica" : isEn ? "In practice" : "Na prática"}
|
||||
</p>
|
||||
<p className="mt-1">
|
||||
<TextoComLinks texto={item.exemplo} currentLocale={currentLocale} />
|
||||
@@ -291,20 +298,22 @@ export default function GlossarioPage() {
|
||||
<section className="shell pb-20 md:pb-28">
|
||||
<div className="panel p-8 text-center sm:p-12">
|
||||
<h2 className="section-title mx-auto max-w-2xl">
|
||||
{isEn ? "From concept to the right chip." : "Do conceito ao chip certo."}
|
||||
{isEs ? "Del concepto al chip adecuado." : isEn ? "From concept to the right chip." : "Do conceito ao chip certo."}
|
||||
</h2>
|
||||
<p className="lede mx-auto mt-4 max-w-2xl">
|
||||
{isEn
|
||||
{isEs
|
||||
? "Con los términos a mano, compare las series lado a lado o responda al selector para encontrar el modelo que se adapta a su proyecto."
|
||||
: isEn
|
||||
? "With the terms in hand, compare series side by side or answer the selector to find the model that fits your project."
|
||||
: "Com os termos em mãos, compare as séries lado a lado ou responda ao seletor para chegar ao modelo que cabe no seu projeto."}
|
||||
</p>
|
||||
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
||||
<Link href={getLocalizedHref("/seletor", currentLocale)} className="btn btn-primary w-full sm:w-auto">
|
||||
<Sliders className="h-4 w-4" aria-hidden="true" />
|
||||
{isEn ? "Choose my chip" : "Escolher meu chip"}
|
||||
{isEs ? "Elegir mi chip" : isEn ? "Choose my chip" : "Escolher meu chip"}
|
||||
</Link>
|
||||
<Link href={getLocalizedHref("/series", currentLocale)} className="btn btn-ghost w-full sm:w-auto">
|
||||
{isEn ? "Explore series" : "Explorar as séries"}
|
||||
{isEs ? "Explorar las series" : isEn ? "Explore series" : "Explorar as séries"}
|
||||
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user