feat(i18n): routing and localization infrastructure (phase 1)
Implement Phase 1 of i18n spec: - Configure next-intl with 6 locales (pt, en, es, fr, de, zh-CN) and pathnames - Move routes to app/[locale] structure with localized layouts and metadata - Add middleware for root language negotiation (307), legacy redirects (308), and 404 for non-pt book - Implement language selector dropdown in header with cookie persistence - Add system typography rules for zh-CN and word-break/hyphenation for de/fr Co-Authored-By: Antigravity <noreply@google.com>
This commit is contained in:
1 parent
e92aea0c84
commit
83c0ba3e74
38 files changed
+1699
-324
No files matched your search
+80
-65
@@ -1,17 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Logo from "@/components/Logo";
|
||||
import GiteaMark from "@/components/GiteaMark";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import { SITE } from "@/lib/seo";
|
||||
import { LOCALES, getLocalizedHref } from "@/components/LanguageSelector";
|
||||
|
||||
const COLUMNS = [
|
||||
const BASE_COLUMNS = [
|
||||
{
|
||||
title: "Explorar",
|
||||
links: [
|
||||
{ href: "/series", label: "Séries ESP32" },
|
||||
{ href: "/comparacao", label: "Comparador" },
|
||||
{ href: "/catalogo", label: "Catálogo de placas" },
|
||||
{ href: "/livro", label: "O livro" },
|
||||
{ href: "/livro", label: "O livro", livroOnly: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -35,14 +39,26 @@ const COLUMNS = [
|
||||
];
|
||||
|
||||
export default function Footer() {
|
||||
const pathname = usePathname() || "";
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
const currentLocale =
|
||||
LOCALES.find((l) => pathname === `/${l.code}` || pathname.startsWith(`/${l.code}/`))?.code || "pt";
|
||||
const isPt = currentLocale === "pt";
|
||||
|
||||
const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
|
||||
|
||||
const columns = BASE_COLUMNS.map((col) => ({
|
||||
...col,
|
||||
links: col.links.filter((l) => !l.livroOnly || isPt),
|
||||
}));
|
||||
|
||||
return (
|
||||
<footer className="mt-20 border-t border-line md:mt-28" aria-label="Rodapé do ESPDocs">
|
||||
<div className="shell py-14 md:py-20">
|
||||
<div className="grid gap-12 lg:grid-cols-[1.1fr_2fr]">
|
||||
<div>
|
||||
<Link href="/" className="inline-flex min-h-11 items-center gap-2.5" aria-label="ESPDocs, página inicial">
|
||||
<Link href={getLinkHref("/")} className="inline-flex min-h-11 items-center gap-2.5" aria-label="ESPDocs, página inicial">
|
||||
<Logo className="h-7 w-7" />
|
||||
<span className="font-display text-2xl font-semibold tracking-[-0.045em] text-ink">ESPDocs</span>
|
||||
</Link>
|
||||
@@ -51,41 +67,45 @@ export default function Footer() {
|
||||
Dados públicos, curadoria editorial e links para as fontes oficiais.
|
||||
</p>
|
||||
|
||||
<Link
|
||||
href="/livro"
|
||||
className="group mt-7 grid grid-cols-[3rem_1fr_auto] items-center gap-3.5 rounded-2xl border border-line bg-surface p-3.5 transition-colors duration-300 hover:border-line-strong"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={SITE.livroCapa}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
width={48}
|
||||
height={72}
|
||||
className="h-[72px] w-12 rounded-md object-cover shadow-md"
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="text-xs font-semibold text-accent">Livro novo</span>
|
||||
<span className="mt-0.5 block font-display text-base font-semibold leading-tight text-ink">Microcontroladores ESP32</span>
|
||||
<span className="mt-1 block text-xs text-muted">Conectividade, eficiência e desempenho</span>
|
||||
</span>
|
||||
<ArrowUpRight className="h-4 w-4 text-faint transition-transform duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-accent" aria-hidden="true" />
|
||||
</Link>
|
||||
{isPt && (
|
||||
<>
|
||||
<Link
|
||||
href="/pt/livro"
|
||||
className="group mt-7 grid grid-cols-[3rem_1fr_auto] items-center gap-3.5 rounded-2xl border border-line bg-surface p-3.5 transition-colors duration-300 hover:border-line-strong"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={SITE.livroCapa}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
width={48}
|
||||
height={72}
|
||||
className="h-[72px] w-12 rounded-md object-cover shadow-md"
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="text-xs font-semibold text-accent">Livro novo</span>
|
||||
<span className="mt-0.5 block font-display text-base font-semibold leading-tight text-ink">Microcontroladores ESP32</span>
|
||||
<span className="mt-1 block text-xs text-muted">Conectividade, eficiência e desempenho</span>
|
||||
</span>
|
||||
<ArrowUpRight className="h-4 w-4 text-faint transition-transform duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-accent" aria-hidden="true" />
|
||||
</Link>
|
||||
|
||||
<div className="mt-4 flex flex-wrap gap-x-5 gap-y-2 text-sm">
|
||||
<a href={SITE.livroAmazon} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 text-muted transition-colors hover:text-accent">
|
||||
Amazon
|
||||
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</a>
|
||||
<a href={SITE.livroUiclap} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 text-muted transition-colors hover:text-accent">
|
||||
UICLAP
|
||||
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap gap-x-5 gap-y-2 text-sm">
|
||||
<a href={SITE.livroAmazon} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 text-muted transition-colors hover:text-accent">
|
||||
Amazon
|
||||
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</a>
|
||||
<a href={SITE.livroUiclap} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 text-muted transition-colors hover:text-accent">
|
||||
UICLAP
|
||||
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-8 sm:grid-cols-4">
|
||||
{COLUMNS.map((column) => (
|
||||
{columns.map((column) => (
|
||||
<nav key={column.title} aria-label={column.title}>
|
||||
<h2 className="text-sm font-semibold text-ink">{column.title}</h2>
|
||||
<ul className="mt-2 space-y-0">
|
||||
@@ -102,7 +122,7 @@ export default function Footer() {
|
||||
<ArrowUpRight className="h-3 w-3" aria-hidden="true" />
|
||||
</a>
|
||||
) : (
|
||||
<Link href={link.href} className="inline-flex min-h-11 min-w-11 items-center text-sm text-muted transition-colors hover:text-accent">
|
||||
<Link href={getLinkHref(link.href)} className="inline-flex min-h-11 min-w-11 items-center text-sm text-muted transition-colors hover:text-accent">
|
||||
{link.label}
|
||||
</Link>
|
||||
)}
|
||||
@@ -119,45 +139,40 @@ export default function Footer() {
|
||||
<br />
|
||||
<span className="text-xs text-faint">Ciência Embarcada</span>
|
||||
</p>
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
<a
|
||||
href={SITE.repositorio}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Gitea do projeto (abre em nova aba)"
|
||||
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" />
|
||||
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
||||
<a href={SITE.autorUrl} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center text-muted transition-colors hover:text-accent">
|
||||
Site pessoal
|
||||
</a>
|
||||
<a
|
||||
href={SITE.autorLinkedin}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="LinkedIn do autor (abre em nova aba)"
|
||||
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">
|
||||
<path d="M4.98 3.5a2.5 2.5 0 1 1 0 5.001 2.5 2.5 0 0 1 0-5zM3 8.98h4v12H3v-12zm6.5 0h3.8v1.6h.1c.5-.9 1.8-1.9 3.6-1.9 3.8 0 4.5 2.5 4.5 5.8v6.5h-4v-5.8c0-1.4-.1-3.2-2-3.2-2 0-2.3 1.6-2.3 3.1v5.9h-4v-12z" />
|
||||
</svg>
|
||||
<a href={SITE.autorLinkedin} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center text-muted transition-colors hover:text-accent">
|
||||
LinkedIn
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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} ESPDocs. Projeto independente, sem afiliação com a 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">
|
||||
<div className="mt-12 flex flex-col items-center justify-between gap-4 border-t border-line pt-8 text-xs text-muted sm:flex-row">
|
||||
<p>© {year} ESPDocs · Conteúdo sob licença MIT. Feito para a comunidade de sistemas embarcados.</p>
|
||||
<div className="flex items-center gap-6">
|
||||
<a
|
||||
href={SITE.repositorio}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex min-h-11 items-center gap-1.5 transition-colors hover:text-accent"
|
||||
>
|
||||
<GiteaMark className="h-4 w-4" aria-hidden="true" />
|
||||
<span>Gitea</span>
|
||||
</a>
|
||||
<span>·</span>
|
||||
<a
|
||||
href={SITE.blog}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex min-h-11 items-center transition-colors hover:text-accent"
|
||||
>
|
||||
Ciência Embarcada
|
||||
<ArrowUpRight className="h-3 w-3" aria-hidden="true" />
|
||||
</a>
|
||||
<a href={SITE.autorUrl} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 transition-colors hover:text-accent">
|
||||
lucasrguerra.dev.br
|
||||
<ArrowUpRight className="h-3 w-3" aria-hidden="true" />
|
||||
</a>
|
||||
<span>Fontes oficiais: Espressif Systems.</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
Reference in new issue
Block a user