76 lines
2.4 KiB
TypeScript
76 lines
2.4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { match as matchLocale } from "@formatjs/intl-localematcher";
|
|
import Negotiator from "negotiator";
|
|
|
|
const LOCALES = ["uz", "ru", "en"];
|
|
const DEFAULT_LOCALE = "uz";
|
|
|
|
type Locale = (typeof LOCALES)[number];
|
|
|
|
function getLocaleFromPathname(pathname: string): Locale | null {
|
|
const firstSegment = pathname.split("/").filter(Boolean)[0];
|
|
if (firstSegment && LOCALES.includes(firstSegment as Locale)) {
|
|
return firstSegment as Locale;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getLocaleFromCookie(request: NextRequest): Locale | null {
|
|
const cookieLocale = request.cookies.get("NEXT_LOCALE")?.value;
|
|
if (cookieLocale && LOCALES.includes(cookieLocale as Locale)) {
|
|
return cookieLocale as Locale;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getLocaleFromHeaders(request: NextRequest): Locale {
|
|
const negotiatorHeaders: Record<string, string> = {};
|
|
request.headers.forEach((value, key) => {
|
|
negotiatorHeaders[key] = value;
|
|
});
|
|
|
|
const languages = new Negotiator({ headers: negotiatorHeaders }).languages();
|
|
try {
|
|
return matchLocale(languages, LOCALES as string[], DEFAULT_LOCALE) as Locale;
|
|
} catch {
|
|
return DEFAULT_LOCALE;
|
|
}
|
|
}
|
|
|
|
export function middleware(request: NextRequest) {
|
|
const { pathname, search } = request.nextUrl;
|
|
|
|
// 1. If URL has a locale, pass it through with headers (+ sync cookie if needed)
|
|
const localeFromPath = getLocaleFromPathname(pathname);
|
|
const localeFromCookie = getLocaleFromCookie(request);
|
|
const preferredLocale = localeFromPath ?? localeFromCookie ?? getLocaleFromHeaders(request);
|
|
|
|
// 2. No locale in URL → redirect to preferred locale
|
|
if (!localeFromPath) {
|
|
const newUrl = new URL(`/${preferredLocale}${pathname}`, request.url);
|
|
newUrl.search = search;
|
|
return NextResponse.redirect(newUrl);
|
|
}
|
|
|
|
// 3. Build response with locale headers for server components
|
|
const requestHeaders = new Headers(request.headers);
|
|
requestHeaders.set("x-locale", localeFromPath);
|
|
requestHeaders.set("x-pathname", pathname);
|
|
|
|
const response = NextResponse.next({ request: { headers: requestHeaders } });
|
|
|
|
// 4. Sync cookie if it differs from URL locale
|
|
if (localeFromPath !== localeFromCookie) {
|
|
response.cookies.set("NEXT_LOCALE", localeFromPath, {
|
|
path: "/",
|
|
maxAge: 31_536_000, // 1 year
|
|
sameSite: "lax",
|
|
});
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
export const config = {
|
|
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
|
|
}; |