- Translate content to English across messages/en.json, data/en, lib/data/en, and public/exemplos/en - Implement localized routing, slugs, and language negotiation with fallback to en - Build dedicated search index for English with strict book exclusion - Update components and pages to dynamically render localized strings and datasets - Verify full structural parity and exact 1:1 match on Portuguese pages via comparator Co-Authored-By: Antigravity <noreply@google.com>
152 lines
4.8 KiB
JavaScript
152 lines
4.8 KiB
JavaScript
/**
|
|
* Utilitários de rotas e navegação internacionalizada.
|
|
* Módulo puro (sem 'use client') seguro para Server e Client Components.
|
|
*/
|
|
|
|
// Ponto único de configuração de idiomas ATIVOS.
|
|
// Para ativar um novo idioma no site, basta adicionar seu código aqui (ex.: ["pt", "en"]).
|
|
export const ENABLED_LOCALES = ["pt", "en"];
|
|
|
|
export const ALL_LOCALES = [
|
|
{ code: "pt", label: "Português", short: "PT", lang: "pt-BR", og: "pt_BR" },
|
|
{ code: "en", label: "English", short: "EN", lang: "en", og: "en_US" },
|
|
{ code: "es", label: "Español", short: "ES", lang: "es-419", og: "es_419" },
|
|
{ code: "fr", label: "Français", short: "FR", lang: "fr", og: "fr_FR" },
|
|
{ code: "de", label: "Deutsch", short: "DE", lang: "de", og: "de_DE" },
|
|
{ code: "zh-CN", label: "简体中文", short: "中", lang: "zh-CN", og: "zh_CN" },
|
|
];
|
|
|
|
export const ALL_LOCALE_CODES = ALL_LOCALES.map((l) => l.code);
|
|
|
|
export const LOCALES = ALL_LOCALES.filter((l) => ENABLED_LOCALES.includes(l.code));
|
|
|
|
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 === "/") {
|
|
return `/${targetLocale}`;
|
|
}
|
|
|
|
const [cleanPath, ...queryParts] = pathname.split("?");
|
|
const query = queryParts.length > 0 ? `?${queryParts.join("?")}` : "";
|
|
|
|
for (const loc of ALL_LOCALE_CODES) {
|
|
if (cleanPath === `/${loc}` || cleanPath === `/${loc}/`) {
|
|
return `/${targetLocale}${query}`;
|
|
}
|
|
}
|
|
|
|
for (const route of CANONICAL_ROUTES) {
|
|
const m = cleanPath.match(route.match);
|
|
if (m) {
|
|
const slug = route.getSlug(targetLocale, m[1]);
|
|
return slug ? `/${targetLocale}${slug}${query}` : `/${targetLocale}${query}`;
|
|
}
|
|
}
|
|
|
|
return `/${targetLocale}${query}`;
|
|
}
|
|
|
|
export function getPtPath(pathname) {
|
|
return getLocalizedHref(pathname, "pt");
|
|
}
|