This commit is contained in:
a2a-platform
2026-06-11 23:28:20 +00:00
parent 7aab2dccb9
commit 34675d168a
2 changed files with 126 additions and 2 deletions

View File

@@ -9,7 +9,7 @@ export const profileContent = {
en: { en: {
locale: "en", locale: "en",
htmlLang: "en", htmlLang: "en",
alternateHref: "/pt/", alternateHref: "/pt?lang=pt",
alternateLabel: "PT", alternateLabel: "PT",
currentLabel: "EN", currentLabel: "EN",
navAria: "Primary navigation", navAria: "Primary navigation",
@@ -139,7 +139,7 @@ export const profileContent = {
pt: { pt: {
locale: "pt", locale: "pt",
htmlLang: "pt-BR", htmlLang: "pt-BR",
alternateHref: "/", alternateHref: "/?lang=en",
alternateLabel: "EN", alternateLabel: "EN",
currentLabel: "PT", currentLabel: "PT",
navAria: "Navegação principal", navAria: "Navegação principal",

124
frontend/middleware.js Normal file
View File

@@ -0,0 +1,124 @@
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*"],
};