diff --git a/app/[locale]/page.jsx b/app/[locale]/page.jsx index 1fbb4a1..70d28fa 100644 --- a/app/[locale]/page.jsx +++ b/app/[locale]/page.jsx @@ -5,7 +5,7 @@ import Link from "next/link"; import seriesData from "@/public/series.json"; import frameworksData from "@/public/frameworks.json"; import { SITE } from "@/lib/seo"; -import { getLocalizedHref } from "@/components/LanguageSelector"; +import { getLocalizedHref } from "@/i18n/links"; import { AlertTriangle, ArrowRight, diff --git a/components/Footer.jsx b/components/Footer.jsx index 821c88d..1263955 100644 --- a/components/Footer.jsx +++ b/components/Footer.jsx @@ -6,16 +6,16 @@ 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"; +import { LOCALES, getLocalizedHref } from "@/i18n/links"; -const BASE_COLUMNS = [ +const 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", livroOnly: true }, + { href: "/livro", label: "O livro" }, ], }, { @@ -48,9 +48,9 @@ export default function Footer() { const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale); - const columns = BASE_COLUMNS.map((col) => ({ - ...col, - links: col.links.filter((l) => !l.livroOnly || isPt), + const columns = COLUMNS.map((column) => ({ + ...column, + links: column.links.filter((link) => link.href !== "/livro" || isPt), })); return ( @@ -139,40 +139,45 @@ export default function Footer() {
Ciência Embarcada

-
- - Site pessoal +
+ + - - LinkedIn + +
-
-

© {year} ESPDocs · Conteúdo sob licença MIT. Feito para a comunidade de sistemas embarcados.

-
- - - · - +
+

© {year} ESPDocs. Projeto independente, sem afiliação com a Espressif Systems.

+

+ Ciência Embarcada + -

+ + lucasrguerra.dev.br + + Fontes oficiais: Espressif Systems. +

diff --git a/components/Header.jsx b/components/Header.jsx index 309fa63..5372f9c 100644 --- a/components/Header.jsx +++ b/components/Header.jsx @@ -5,7 +5,8 @@ import { createPortal } from "react-dom"; import Link from "next/link"; import Logo from "@/components/Logo"; import SearchDialog from "@/components/SearchDialog"; -import LanguageSelector, { LOCALES, getLocalizedHref } from "@/components/LanguageSelector"; +import LanguageSelector from "@/components/LanguageSelector"; +import { LOCALES, getLocalizedHref } from "@/i18n/links"; import { usePathname } from "next/navigation"; import { useTheme } from "next-themes"; import { diff --git a/components/LanguageSelector.jsx b/components/LanguageSelector.jsx index 5d43e9a..ba81f97 100644 --- a/components/LanguageSelector.jsx +++ b/components/LanguageSelector.jsx @@ -3,131 +3,9 @@ import { useEffect, useRef, useState } from "react"; import { usePathname } from "next/navigation"; import { ChevronDown, Globe } from "lucide-react"; +import { LOCALES, getLocalizedHref } from "@/i18n/links"; -export const LOCALES = [ - { code: "pt", label: "Português", short: "PT", lang: "pt-BR" }, - { code: "en", label: "English", short: "EN", lang: "en" }, - { code: "es", label: "Español", short: "ES", lang: "es-419" }, - { code: "fr", label: "Français", short: "FR", lang: "fr" }, - { code: "de", label: "Deutsch", short: "DE", lang: "de" }, - { code: "zh-CN", label: "简体中文", short: "中", lang: "zh-CN" }, -]; - -const CANONICAL_ROUTES = [ - { - id: "series_item", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:series|serien)\/([^/]+)/, - getSlug: (loc, key) => (loc === "de" ? `/serien/${key}` : `/series/${key}`), - }, - { - id: "frameworks_item", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/frameworks\/([^/]+)/, - getSlug: (loc, key) => `/frameworks/${key}`, - }, - { - id: "series", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:series|serien)(?:\/|$)/, - getSlug: (loc) => (loc === "de" ? "/serien" : "/series"), - }, - { - id: "catalogo", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:catalogo|catalog|catalogue|katalog)(?:\/|$)/, - getSlug: (loc) => { - if (loc === "de") return "/katalog"; - if (loc === "fr") return "/catalogue"; - if (loc === "en" || loc === "zh-CN") return "/catalog"; - return "/catalogo"; - }, - }, - { - id: "comparacao", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:comparacao|compare|comparacion|comparaison|vergleich)(?:\/|$)/, - getSlug: (loc) => { - if (loc === "en" || loc === "zh-CN") return "/compare"; - if (loc === "es") return "/comparacion"; - if (loc === "fr") return "/comparaison"; - if (loc === "de") return "/vergleich"; - return "/comparacao"; - }, - }, - { - id: "componentes", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:componentes|components|composants|komponenten)(?:\/|$)/, - getSlug: (loc) => { - if (loc === "en" || loc === "zh-CN") return "/components"; - if (loc === "fr") return "/composants"; - if (loc === "de") return "/komponenten"; - return "/componentes"; - }, - }, - { - id: "diagnostico", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:diagnostico|diagnostics|diagnostic|diagnose)(?:\/|$)/, - getSlug: (loc) => { - if (loc === "en" || loc === "zh-CN") return "/diagnostics"; - if (loc === "fr") return "/diagnostic"; - if (loc === "de") return "/diagnose"; - return "/diagnostico"; - }, - }, - { - id: "frameworks", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/frameworks(?:\/|$)/, - getSlug: () => "/frameworks", - }, - { - id: "glossario", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:glossario|glossary|glosario|glossaire|glossar)(?:\/|$)/, - getSlug: (loc) => { - if (loc === "en" || loc === "zh-CN") return "/glossary"; - if (loc === "es") return "/glosario"; - if (loc === "fr") return "/glossaire"; - if (loc === "de") return "/glossar"; - return "/glossario"; - }, - }, - { - id: "seletor", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:seletor|selector|selecteur|auswahl)(?:\/|$)/, - getSlug: (loc) => { - if (loc === "en" || loc === "es" || loc === "zh-CN") return "/selector"; - if (loc === "fr") return "/selecteur"; - if (loc === "de") return "/auswahl"; - return "/seletor"; - }, - }, - { - id: "sobre", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:sobre|about|acerca|a-propos|ueber-uns)(?:\/|$)/, - getSlug: (loc) => { - if (loc === "en" || loc === "zh-CN") return "/about"; - if (loc === "es") return "/acerca"; - if (loc === "fr") return "/a-propos"; - if (loc === "de") return "/ueber-uns"; - return "/sobre"; - }, - }, - { - id: "livro", - match: /^\/(?:pt|en|es|fr|de|zh-CN)\/livro(?:\/|$)/, - getSlug: (loc) => (loc === "pt" ? "/livro" : ""), - }, -]; - -export function getLocalizedHref(pathname, targetLocale) { - if (!pathname || pathname === "/" || pathname === `/${targetLocale}` || pathname === `/${targetLocale}/`) { - return `/${targetLocale}/`; - } - - for (const route of CANONICAL_ROUTES) { - const m = pathname.match(route.match); - if (m) { - const slug = route.getSlug(targetLocale, m[1]); - return slug ? `/${targetLocale}${slug}` : `/${targetLocale}/`; - } - } - return `/${targetLocale}/`; -} +export { LOCALES, getLocalizedHref }; export default function LanguageSelector() { const pathname = usePathname() || ""; diff --git a/i18n/links.js b/i18n/links.js new file mode 100644 index 0000000..9ff221e --- /dev/null +++ b/i18n/links.js @@ -0,0 +1,129 @@ +/** + * Utilitários de rotas e navegação internacionalizada. + * Módulo puro (sem 'use client') seguro para Server e Client Components. + */ + +export const LOCALES = [ + { code: "pt", label: "Português", short: "PT", lang: "pt-BR" }, + { code: "en", label: "English", short: "EN", lang: "en" }, + { code: "es", label: "Español", short: "ES", lang: "es-419" }, + { code: "fr", label: "Français", short: "FR", lang: "fr" }, + { code: "de", label: "Deutsch", short: "DE", lang: "de" }, + { code: "zh-CN", label: "简体中文", short: "中", lang: "zh-CN" }, +]; + +export const CANONICAL_ROUTES = [ + { + id: "series_item", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:series|serien)\/([^/]+)/, + getSlug: (loc, key) => (loc === "de" ? `/serien/${key}` : `/series/${key}`), + }, + { + id: "frameworks_item", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/frameworks\/([^/]+)/, + getSlug: (loc, key) => `/frameworks/${key}`, + }, + { + id: "series", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:series|serien)(?:\/|$)/, + getSlug: (loc) => (loc === "de" ? "/serien" : "/series"), + }, + { + id: "catalogo", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:catalogo|catalog|catalogue|katalog)(?:\/|$)/, + getSlug: (loc) => { + if (loc === "de") return "/katalog"; + if (loc === "fr") return "/catalogue"; + if (loc === "en" || loc === "zh-CN") return "/catalog"; + return "/catalogo"; + }, + }, + { + id: "comparacao", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:comparacao|compare|comparacion|comparaison|vergleich)(?:\/|$)/, + getSlug: (loc) => { + if (loc === "en" || loc === "zh-CN") return "/compare"; + if (loc === "es") return "/comparacion"; + if (loc === "fr") return "/comparaison"; + if (loc === "de") return "/vergleich"; + return "/comparacao"; + }, + }, + { + id: "componentes", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:componentes|components|composants|komponenten)(?:\/|$)/, + getSlug: (loc) => { + if (loc === "en" || loc === "zh-CN") return "/components"; + if (loc === "fr") return "/composants"; + if (loc === "de") return "/komponenten"; + return "/componentes"; + }, + }, + { + id: "diagnostico", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:diagnostico|diagnostics|diagnostic|diagnose)(?:\/|$)/, + getSlug: (loc) => { + if (loc === "en" || loc === "zh-CN") return "/diagnostics"; + if (loc === "fr") return "/diagnostic"; + if (loc === "de") return "/diagnose"; + return "/diagnostico"; + }, + }, + { + id: "frameworks", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/frameworks(?:\/|$)/, + getSlug: () => "/frameworks", + }, + { + id: "glossario", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:glossario|glossary|glosario|glossaire|glossar)(?:\/|$)/, + getSlug: (loc) => { + if (loc === "en" || loc === "zh-CN") return "/glossary"; + if (loc === "es") return "/glosario"; + if (loc === "fr") return "/glossaire"; + if (loc === "de") return "/glossar"; + return "/glossario"; + }, + }, + { + id: "seletor", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:seletor|selector|selecteur|auswahl)(?:\/|$)/, + getSlug: (loc) => { + if (loc === "en" || loc === "es" || loc === "zh-CN") return "/selector"; + if (loc === "fr") return "/selecteur"; + if (loc === "de") return "/auswahl"; + return "/seletor"; + }, + }, + { + id: "sobre", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:sobre|about|acerca|a-propos|ueber-uns)(?:\/|$)/, + getSlug: (loc) => { + if (loc === "en" || loc === "zh-CN") return "/about"; + if (loc === "es") return "/acerca"; + if (loc === "fr") return "/a-propos"; + if (loc === "de") return "/ueber-uns"; + return "/sobre"; + }, + }, + { + id: "livro", + match: /^\/(?:pt|en|es|fr|de|zh-CN)\/livro(?:\/|$)/, + getSlug: (loc) => (loc === "pt" ? "/livro" : ""), + }, +]; + +export function getLocalizedHref(pathname, targetLocale) { + if (!pathname || pathname === "/" || pathname === `/${targetLocale}` || pathname === `/${targetLocale}/`) { + return `/${targetLocale}/`; + } + + for (const route of CANONICAL_ROUTES) { + const m = pathname.match(route.match); + if (m) { + const slug = route.getSlug(targetLocale, m[1]); + return slug ? `/${targetLocale}${slug}` : `/${targetLocale}/`; + } + } + return `/${targetLocale}/`; +}