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/ 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).*)", ], };