- 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>
126 lines
4.0 KiB
JavaScript
126 lines
4.0 KiB
JavaScript
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);
|
|
|
|
function parseAcceptLanguage(header) {
|
|
if (!header) return [];
|
|
return header
|
|
.split(",")
|
|
.map((part) => {
|
|
const [langRange, qPart] = part.trim().split(";");
|
|
let q = 1.0;
|
|
if (qPart) {
|
|
const match = qPart.match(/q=([\d.]+)/);
|
|
if (match) {
|
|
const parsed = parseFloat(match[1]);
|
|
if (!isNaN(parsed)) q = parsed;
|
|
}
|
|
}
|
|
return { lang: langRange.trim().toLowerCase(), q };
|
|
})
|
|
.sort((a, b) => b.q - a.q);
|
|
}
|
|
|
|
function matchLocale(lang) {
|
|
if (lang.startsWith("pt")) return "pt";
|
|
if (lang.startsWith("es")) return "es";
|
|
if (lang.startsWith("fr")) return "fr";
|
|
if (lang.startsWith("de")) return "de";
|
|
if (lang.startsWith("zh")) return "zh-CN";
|
|
if (lang.startsWith("en")) return "en";
|
|
return null;
|
|
}
|
|
|
|
function resolveRootLocale(request) {
|
|
const cookieValue = request.cookies.get("NEXT_LOCALE")?.value;
|
|
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 && ENABLED_LOCALES.includes(matched)) {
|
|
return matched;
|
|
}
|
|
}
|
|
return ENABLED_LOCALES.includes("en") ? "en" : (ENABLED_LOCALES[0] || "pt");
|
|
}
|
|
|
|
export default function middleware(request) {
|
|
const { pathname, search } = request.nextUrl;
|
|
|
|
// Exclusões de caminhos e arquivos estáticos
|
|
if (
|
|
pathname.startsWith("/api") ||
|
|
pathname.startsWith("/_next") ||
|
|
pathname === "/robots.txt" ||
|
|
pathname === "/sitemap.xml" ||
|
|
pathname === "/google21a790a2c23c0ec5.html" ||
|
|
/\.[a-zA-Z0-9]+$/.test(pathname)
|
|
) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// 1. Raiz: negociação de idioma (307) considerando apenas idiomas ATIVOS
|
|
if (pathname === "/") {
|
|
const targetLocale = resolveRootLocale(request);
|
|
const url = new URL(`/${targetLocale}`, request.url);
|
|
url.search = search;
|
|
const response = NextResponse.redirect(url, 307);
|
|
response.headers.set("Vary", "Accept-Language, Cookie");
|
|
response.headers.set("Cache-Control", "private, no-store");
|
|
return response;
|
|
}
|
|
|
|
// 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. 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 (!hasActivePrefix) {
|
|
const url = new URL(`/pt${pathname}`, request.url);
|
|
url.search = search;
|
|
return NextResponse.redirect(url, 308);
|
|
}
|
|
|
|
// 5. Delegação para o roteamento do next-intl para idiomas ativos
|
|
return intlMiddleware(request);
|
|
}
|
|
|
|
export const config = {
|
|
matcher: [
|
|
"/((?!api|_next|_vercel|.*\\..*|robots\\.txt|sitemap\\.xml|google21a790a2c23c0ec5\\.html).*)",
|
|
],
|
|
};
|