feat(i18n): French and German localization (phase 5)
- 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:
1 parent
7b673aeb4b
commit
7fce46b9e1
104 files changed
+33369
-686
No files matched your search
+149
-7
@@ -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'é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>
|
||||
|
||||
Reference in new issue
Block a user