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:
Lucas RayanandAntigravity committed 2026-11-01 09:31:22 -03:00
1 parent e92aea0c84
commit 83c0ba3e74
38 files changed
+1699 -324

No files matched your search

+9 -2
View File
@@ -92,6 +92,7 @@ function obterRotasExistentes() {
if (arq.endsWith("page.jsx") || arq.endsWith("page.js")) {
let rota = arq
.replace(/^app/, "")
.replace(/\/\[locale\]/, "")
.replace(/\/page\.(jsx|js)$/, "");
if (rota === "") rota = "/";
rotas.add(rota);
@@ -132,7 +133,12 @@ function auditarPerformanceConfigs() {
// Checar globals.css e layout.jsx para transições no body
const globalsCss = existsSync("app/globals.css") ? readFileSync("app/globals.css", "utf8") : "";
const layoutJsx = existsSync("app/layout.jsx") ? readFileSync("app/layout.jsx", "utf8") : "";
const layoutPath = existsSync("app/[locale]/layout.jsx")
? "app/[locale]/layout.jsx"
: existsSync("app/layout.jsx")
? "app/layout.jsx"
: null;
const layoutJsx = layoutPath ? readFileSync(layoutPath, "utf8") : "";
if (globalsCss.includes("body {\n\t\t@apply bg-slate-50 text-slate-900 transition-colors")) {
falha("globals.css tem transition-colors global no body (provoca animações não compostas no load).");
@@ -299,7 +305,8 @@ function auditarRotasInternas() {
if (caminho.startsWith("/api") || caminho.startsWith("/favicon") || caminho.startsWith("/icon") || caminho.startsWith("/logo") || caminho.startsWith("/site.webmanifest")) {
continue;
}
const existe = rotasValidas.has(caminho) || rotasDinamicas.some((rx) => rx.test(caminho));
const caminhoSemLocale = caminho.replace(/^\/(?:pt|en|es|fr|de|zh-CN)(?=\/|$)/, "") || "/";
const existe = rotasValidas.has(caminho) || rotasValidas.has(caminhoSemLocale) || rotasDinamicas.some((rx) => rx.test(caminho) || rx.test(caminhoSemLocale));
if (!existe) {
falha(`${arq} -> Link interno aponta para rota inexistente: '${caminho}'`);
rotasQuebradas++;