125 lines
3.0 KiB
JavaScript
125 lines
3.0 KiB
JavaScript
import { NextResponse } from "next/server";
|
|
|
|
const LOCALE_COOKIE = "rs_locale";
|
|
const SUPPORTED_LOCALES = new Set(["en", "pt"]);
|
|
const PUBLIC_FILE = /\.(?:ico|png|jpg|jpeg|svg|webp|avif|gif|txt|xml|json|webmanifest|css|js|map|woff2?)$/i;
|
|
const RESERVED_PREFIXES = [
|
|
"/_next",
|
|
"/_a2a",
|
|
"/.well-known",
|
|
"/api",
|
|
"/auth",
|
|
"/invoke",
|
|
"/mcp",
|
|
"/message:send",
|
|
"/message:stream",
|
|
"/tasks",
|
|
"/answers",
|
|
"/input-requests",
|
|
"/scope-grants",
|
|
"/scope-denials",
|
|
];
|
|
|
|
function isReservedPath(pathname) {
|
|
if (PUBLIC_FILE.test(pathname)) {
|
|
return true;
|
|
}
|
|
return RESERVED_PREFIXES.some(
|
|
(prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`),
|
|
);
|
|
}
|
|
|
|
function normalizeLocale(value) {
|
|
const locale = String(value || "").trim().toLowerCase();
|
|
if (SUPPORTED_LOCALES.has(locale)) {
|
|
return locale;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function localeFromCountry(request) {
|
|
const country = (
|
|
request.headers.get("x-vercel-ip-country")
|
|
|| request.headers.get("cf-ipcountry")
|
|
|| request.headers.get("x-country-code")
|
|
|| ""
|
|
).trim().toUpperCase();
|
|
if (country === "BR" || country === "PT") {
|
|
return "pt";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function localeFromAcceptLanguage(request) {
|
|
const header = request.headers.get("accept-language") || "";
|
|
const ranked = header
|
|
.split(",")
|
|
.map((part) => {
|
|
const [tag, ...params] = part.trim().split(";");
|
|
const q = params
|
|
.map((param) => param.trim())
|
|
.find((param) => param.startsWith("q="));
|
|
const weight = q ? Number.parseFloat(q.slice(2)) : 1;
|
|
return {
|
|
language: tag.toLowerCase().split("-")[0],
|
|
weight: Number.isFinite(weight) ? weight : 1,
|
|
};
|
|
})
|
|
.filter((item) => SUPPORTED_LOCALES.has(item.language))
|
|
.sort((a, b) => b.weight - a.weight);
|
|
return ranked[0]?.language || null;
|
|
}
|
|
|
|
function preferredLocale(request) {
|
|
return (
|
|
normalizeLocale(request.cookies.get(LOCALE_COOKIE)?.value)
|
|
|| localeFromAcceptLanguage(request)
|
|
|| localeFromCountry(request)
|
|
|| "en"
|
|
);
|
|
}
|
|
|
|
function withLocaleCookie(response, locale) {
|
|
response.cookies.set(LOCALE_COOKIE, locale, {
|
|
path: "/",
|
|
maxAge: 60 * 60 * 24 * 365,
|
|
sameSite: "lax",
|
|
});
|
|
return response;
|
|
}
|
|
|
|
export function middleware(request) {
|
|
const url = request.nextUrl;
|
|
const { pathname, searchParams } = url;
|
|
|
|
if (isReservedPath(pathname)) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
const requestedLocale = normalizeLocale(searchParams.get("lang"));
|
|
if (requestedLocale) {
|
|
const cleanUrl = url.clone();
|
|
cleanUrl.searchParams.delete("lang");
|
|
const response =
|
|
cleanUrl.toString() === url.toString()
|
|
? NextResponse.next()
|
|
: NextResponse.redirect(cleanUrl);
|
|
return withLocaleCookie(response, requestedLocale);
|
|
}
|
|
|
|
if (pathname === "/") {
|
|
const locale = preferredLocale(request);
|
|
if (locale === "pt") {
|
|
const redirectUrl = url.clone();
|
|
redirectUrl.pathname = "/pt/";
|
|
return NextResponse.redirect(redirectUrl);
|
|
}
|
|
}
|
|
|
|
return NextResponse.next();
|
|
}
|
|
|
|
export const config = {
|
|
matcher: ["/:path*"],
|
|
};
|