feat(i18n): restrict public routes and metadata to enabled locales
CI / Lint e auditorias (push) Successful in 41s
CI / Trivy (dependências e segredos) (push) Successful in 10s
CI / Imagem Docker (linux/arm64) (push) Successful in 2m5s

- Add ENABLED_LOCALES configuration in i18n/links.js (currently ["pt"])
- Redirect inactive locale prefixes to /pt via 307 in middleware.js
- Restrict root locale negotiation to active locales with fallback to pt
- Hide LanguageSelector when only one locale is enabled
- Generate sitemap, hreflang alternates, and og:locale only for active locales
- Generate static SSG pages only for active locales in build
- Adapt check:i18n and smoke-imagem.sh to validate active vs inactive locales

Co-Authored-By: Antigravity <noreply@google.com>
This commit is contained in:
Lucas RayanandAntigravity committed 2026-11-01 12:10:25 -03:00
1 parent 3a1a095e97
commit d479a8d361
9 files changed
+219 -79

No files matched your search

+36 -13
View File
@@ -1,11 +1,10 @@
import createMiddleware from "next-intl/middleware";
import { NextResponse } from "next/server";
import { routing } from "./i18n/routing";
import { ALL_LOCALE_CODES, ENABLED_LOCALES, getPtPath } from "./i18n/links";
const intlMiddleware = createMiddleware(routing);
const SUPPORTED_LOCALES = routing.locales;
function parseAcceptLanguage(header) {
if (!header) return [];
return header
@@ -37,16 +36,18 @@ function matchLocale(lang) {
function resolveRootLocale(request) {
const cookieValue = request.cookies.get("NEXT_LOCALE")?.value;
if (cookieValue && SUPPORTED_LOCALES.includes(cookieValue)) {
if (cookieValue && ENABLED_LOCALES.includes(cookieValue)) {
return cookieValue;
}
const acceptLang = request.headers.get("accept-language");
const parsed = parseAcceptLanguage(acceptLang);
for (const { lang } of parsed) {
const matched = matchLocale(lang);
if (matched) return matched;
if (matched && ENABLED_LOCALES.includes(matched)) {
return matched;
}
}
return "en";
return ENABLED_LOCALES.includes("en") ? "en" : (ENABLED_LOCALES[0] || "pt");
}
export default function middleware(request) {
@@ -64,7 +65,7 @@ export default function middleware(request) {
return NextResponse.next();
}
// 1. Raiz: negociação de idioma (307)
// 1. Raiz: negociação de idioma (307) considerando apenas idiomas ATIVOS
if (pathname === "/") {
const targetLocale = resolveRootLocale(request);
const url = new URL(`/${targetLocale}`, request.url);
@@ -75,23 +76,45 @@ export default function middleware(request) {
return response;
}
// 2. Livro fora do português: 404
if (/^\/(en|es|fr|de|zh-CN)\/livro(\/.*)?$/.test(pathname)) {
return new NextResponse(null, { status: 404 });
// 2. Idiomas INATIVOS: prefixos (/en, /es, /fr, /de, /zh-CN e qualquer subcaminho)
// Redirecionam com 307 para o mesmo caminho em /pt quando o slug existir lá; se não souber mapear o slug, para /pt.
// O 404 do livro fora de pt não se aplica a inativos (redirecionam para /pt/livro aqui).
const inactiveLocales = ALL_LOCALE_CODES.filter((l) => !ENABLED_LOCALES.includes(l));
if (inactiveLocales.length > 0) {
const isInactive = inactiveLocales.some(
(loc) => pathname === `/${loc}` || pathname.startsWith(`/${loc}/`)
);
if (isInactive) {
const targetPath = getPtPath(pathname);
const url = new URL(targetPath, request.url);
url.search = search;
return NextResponse.redirect(url, 307);
}
}
// 3. URLs antigas sem prefixo reconhecido: redirecionamento 308 para /pt/<caminho>
const hasLocalePrefix = SUPPORTED_LOCALES.some(
// 3. Livro fora do português para idiomas ATIVOS que não são pt: 404
const activeNonPt = ENABLED_LOCALES.filter((l) => l !== "pt");
if (activeNonPt.length > 0) {
const isBookInOtherActiveLocale = activeNonPt.some(
(loc) => pathname === `/${loc}/livro` || pathname.startsWith(`/${loc}/livro/`)
);
if (isBookInOtherActiveLocale) {
return new NextResponse(null, { status: 404 });
}
}
// 4. URLs antigas sem prefixo reconhecido: redirecionamento 308 para /pt/<caminho>
const hasActivePrefix = ENABLED_LOCALES.some(
(loc) => pathname === `/${loc}` || pathname.startsWith(`/${loc}/`)
);
if (!hasLocalePrefix) {
if (!hasActivePrefix) {
const url = new URL(`/pt${pathname}`, request.url);
url.search = search;
return NextResponse.redirect(url, 308);
}
// 4. Delegação para o roteamento do next-intl
// 5. Delegação para o roteamento do next-intl para idiomas ativos
return intlMiddleware(request);
}