feat(i18n): French and German localization (phase 5)
CI / Lint e auditorias (push) Successful in 49s
CI / Trivy (dependências e segredos) (push) Successful in 11s
CI / Imagem Docker (linux/arm64) (push) Successful in 3m14s

- Add full message catalogs and content for French (fr) and German (de)
- Implement ASCII canonical slugs for French and German routes
- Add language-specific search endpoints (/api/busca/fr, /api/busca/de)
- Configure strict 404 responses for /fr/livro and /de/livro
- Localize SEO, OpenGraph (fr_FR, de_DE), Twitter and JSON-LD schemas
- Add localized code example comments across 14 examples for fr and de
- Enable 'fr' and 'de' in ENABLED_LOCALES with root negotiation and redirect
- Extend test suite in 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 22:06:36 -03:00
1 parent 7b673aeb4b
commit 7fce46b9e1
104 files changed
+33369 -686

No files matched your search

+149 -7
View File
@@ -96,6 +96,64 @@ const COLUMNS_ES = [
},
];
const COLUMNS_FR = [
{
title: "Explorer",
links: [
{ href: "/series", label: "Séries ESP32" },
{ href: "/comparacao", label: "Comparateur" },
{ href: "/catalogo", label: "Catalogue de cartes" },
],
},
{
title: "Outils",
links: [
{ href: "/seletor", label: "Sélecteur intelligent" },
{ href: "/diagnostico", label: "Diagnostic" },
{ href: "/componentes", label: "Component Registry" },
{ href: "/frameworks", label: "Frameworks et SDKs" },
],
},
{
title: "Projet",
links: [
{ href: "/sobre", label: "À propos d'ESPDocs" },
{ href: "/glossario", label: "Glossaire" },
{ href: SITE.repositorio, label: "Dépôt", external: true },
{ href: `${SITE.repositorio}/issues`, label: "Signaler un problème", external: true },
],
},
];
const COLUMNS_DE = [
{
title: "Erkunden",
links: [
{ href: "/series", label: "ESP32-Serien" },
{ href: "/comparacao", label: "Vergleich" },
{ href: "/catalogo", label: "Board-Katalog" },
],
},
{
title: "Werkzeuge",
links: [
{ href: "/seletor", label: "Intelligente Auswahl" },
{ href: "/diagnostico", label: "Diagnose" },
{ href: "/componentes", label: "Component Registry" },
{ href: "/frameworks", label: "Frameworks & SDKs" },
],
},
{
title: "Projekt",
links: [
{ href: "/sobre", label: "Über ESPDocs" },
{ href: "/glossario", label: "Glossar" },
{ href: SITE.repositorio, label: "Repository", external: true },
{ href: `${SITE.repositorio}/issues`, label: "Problem melden", external: true },
],
},
];
export default function Footer() {
const pathname = usePathname() || "";
const year = new Date().getFullYear();
@@ -105,6 +163,8 @@ export default function Footer() {
const isPt = currentLocale === "pt";
const isEn = currentLocale === "en";
const isEs = currentLocale === "es";
const isFr = currentLocale === "fr";
const isDe = currentLocale === "de";
const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
@@ -115,14 +175,45 @@ export default function Footer() {
}))
: isEs
? COLUMNS_ES
: isFr
? COLUMNS_FR
: isDe
? COLUMNS_DE
: COLUMNS_EN;
return (
<footer className="mt-20 border-t border-line md:mt-28" aria-label={isPt ? "Rodapé do ESPDocs" : isEs ? "Pie de página de ESPDocs" : "ESPDocs footer"}>
<footer
className="mt-20 border-t border-line md:mt-28"
aria-label={
isPt
? "Rodapé do ESPDocs"
: isEs
? "Pie de página de ESPDocs"
: isFr
? "Pied de page d'ESPDocs"
: isDe
? "ESPDocs-Fußzeile"
: "ESPDocs footer"
}
>
<div className="shell py-14 md:py-20">
<div className="grid gap-12 lg:grid-cols-[1.1fr_2fr]">
<div>
<Link href={getLinkHref("/")} className="inline-flex min-h-11 items-center gap-2.5" aria-label={isPt ? "ESPDocs, página inicial" : isEs ? "ESPDocs, página de inicio" : "ESPDocs, homepage"}>
<Link
href={getLinkHref("/")}
className="inline-flex min-h-11 items-center gap-2.5"
aria-label={
isPt
? "ESPDocs, página inicial"
: isEs
? "ESPDocs, página de inicio"
: isFr
? "ESPDocs, page d'accueil"
: isDe
? "ESPDocs, Startseite"
: "ESPDocs, homepage"
}
>
<Logo className="h-7 w-7" />
<span className="font-display text-2xl font-semibold tracking-[-0.045em] text-ink">ESPDocs</span>
</Link>
@@ -137,6 +228,16 @@ export default function Footer() {
Documentación independiente del ecosistema ESP32.
Datos públicos, curaduría editorial y enlaces a las fuentes oficiales.
</>
) : isFr ? (
<>
Documentation indépendante de l&apos;écosystème ESP32.
Données publiques, sélection éditoriale et liens vers les sources officielles.
</>
) : isDe ? (
<>
Unabhängige Dokumentation des ESP32-Ökosystems.
Öffentliche Daten, redaktionelle Betreuung und Links zu offiziellen Quellen.
</>
) : (
<>
Independent documentation of the ESP32 ecosystem.
@@ -211,7 +312,7 @@ export default function Footer() {
))}
<div>
<h2 className="text-sm font-semibold text-ink">{isPt ? "Autor" : isEs ? "Autor" : "Author"}</h2>
<h2 className="text-sm font-semibold text-ink">{isPt ? "Autor" : isEs ? "Autor" : isFr ? "Auteur" : isDe ? "Autor" : "Author"}</h2>
<p className="mt-4 text-sm leading-relaxed text-muted">
Lucas Rayan Guerra
<br />
@@ -222,7 +323,17 @@ export default function Footer() {
href={SITE.repositorio}
target="_blank"
rel="noopener noreferrer"
aria-label={isPt ? "Gitea do projeto (abre em nova aba)" : isEs ? "Gitea del proyecto (abre en una pestaña nueva)" : "Project Gitea (opens in new tab)"}
aria-label={
isPt
? "Gitea do projeto (abre em nova aba)"
: isEs
? "Gitea del proyecto (abre en una pestaña nueva)"
: isFr
? "Gitea du projet (ouvre un nouvel onglet)"
: isDe
? "Projekt-Gitea (öffnet in neuem Tab)"
: "Project Gitea (opens in new tab)"
}
className="grid h-11 w-11 place-items-center rounded-full border border-line text-ink transition-colors hover:border-line-strong hover:bg-surface"
>
<GiteaMark className="h-4 w-4" />
@@ -231,7 +342,17 @@ export default function Footer() {
href={SITE.autorLinkedin}
target="_blank"
rel="noopener noreferrer"
aria-label={isPt ? "LinkedIn do autor (abre em nova aba)" : isEs ? "LinkedIn del autor (abre en una pestaña nueva)" : "Author LinkedIn (opens in new tab)"}
aria-label={
isPt
? "LinkedIn do autor (abre em nova aba)"
: isEs
? "LinkedIn del autor (abre en una pestaña nueva)"
: isFr
? "LinkedIn de l'auteur (ouvre un nouvel onglet)"
: isDe
? "LinkedIn des Autors (öffnet in neuem Tab)"
: "Author LinkedIn (opens in new tab)"
}
className="grid h-11 w-11 place-items-center rounded-full border border-line text-ink transition-colors hover:border-line-strong hover:bg-surface"
>
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
@@ -244,7 +365,18 @@ export default function Footer() {
</div>
<div className="mt-14 flex flex-col gap-4 border-t border-line pt-6 text-xs leading-relaxed text-faint md:flex-row md:items-center md:justify-between">
<p>© {year} {isPt ? "ESPDocs. Projeto independente, sem afiliação com a Espressif Systems." : isEs ? "ESPDocs. Proyecto independiente, sin afiliación con Espressif Systems." : "ESPDocs. Independent project, not affiliated with Espressif Systems."}</p>
<p>
© {year}{" "}
{isPt
? "ESPDocs. Projeto independente, sem afiliação com a Espressif Systems."
: isEs
? "ESPDocs. Proyecto independiente, sin afiliación con Espressif Systems."
: isFr
? "ESPDocs. Projet indépendant, sans affiliation avec Espressif Systems."
: isDe
? "ESPDocs. Unabhängiges Projekt, nicht verbunden mit Espressif Systems."
: "ESPDocs. Independent project, not affiliated with Espressif Systems."}
</p>
<p className="flex flex-wrap items-center gap-x-5 gap-y-2">
<a href={SITE.blog} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 transition-colors hover:text-accent">
Ciência Embarcada
@@ -254,7 +386,17 @@ export default function Footer() {
lucasrguerra.dev.br
<ArrowUpRight className="h-3 w-3" aria-hidden="true" />
</a>
<span>{isPt ? "Fontes oficiais: Espressif Systems." : isEs ? "Fuentes oficiales: Espressif Systems." : "Official sources: Espressif Systems."}</span>
<span>
{isPt
? "Fontes oficiais: Espressif Systems."
: isEs
? "Fuentes oficiales: Espressif Systems."
: isFr
? "Sources officielles : Espressif Systems."
: isDe
? "Offizielle Quellen: Espressif Systems."
: "Official sources: Espressif Systems."}
</span>
</p>
</div>
</div>