136 lines
3.7 KiB
TypeScript
136 lines
3.7 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { match as matchLocale } from "@formatjs/intl-localematcher";
|
|
import Negotiator from "negotiator";
|
|
const PUBLIC_PAGES = ["/login", "/register"];
|
|
|
|
const LOCALES = ["uz", "ru", "en"];
|
|
const DEFAULT_LOCALE = "uz";
|
|
|
|
type Locale = (typeof LOCALES)[number];
|
|
|
|
function getLocaleFromPathname(pathname: string): Locale | null {
|
|
const segments = pathname.split("/").filter(Boolean);
|
|
const firstSegment = segments[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 unknown as string[],
|
|
DEFAULT_LOCALE
|
|
) as Locale;
|
|
} catch {
|
|
return DEFAULT_LOCALE;
|
|
}
|
|
}
|
|
|
|
export function middleware(request: NextRequest) {
|
|
const { pathname, search } = request.nextUrl;
|
|
|
|
// Skip public files and API routes
|
|
if (
|
|
pathname.includes(".") ||
|
|
pathname.startsWith("/api") ||
|
|
pathname.startsWith("/_next")
|
|
) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// 1. Check URL locale
|
|
const localeFromPath = getLocaleFromPathname(pathname);
|
|
|
|
// 2. Check cookie locale
|
|
const localeFromCookie = getLocaleFromCookie(request);
|
|
|
|
// 3. Check browser locale
|
|
const localeFromBrowser = getLocaleFromHeaders(request);
|
|
|
|
// Priority: URL > Cookie > Browser
|
|
const preferredLocale =
|
|
localeFromPath || localeFromCookie || localeFromBrowser;
|
|
|
|
// Faqat kerakli sahifalarni redirect qilamiz
|
|
const isPublicPage = PUBLIC_PAGES.some((page) => pathname === page);
|
|
|
|
if (isPublicPage) {
|
|
const url = request.nextUrl.clone();
|
|
url.pathname = `/${DEFAULT_LOCALE}/verify-otp`;
|
|
url.search = search; // ?code=1111&phone=...
|
|
|
|
return NextResponse.redirect(url);
|
|
}
|
|
|
|
// If URL has no locale, redirect with preferred locale
|
|
if (!localeFromPath) {
|
|
const newUrl = new URL(`/${preferredLocale}/${pathname}`, request.url);
|
|
return NextResponse.redirect(newUrl);
|
|
}
|
|
|
|
// If URL locale differs from cookie, update cookie
|
|
if (localeFromPath !== localeFromCookie) {
|
|
const response = NextResponse.next();
|
|
|
|
// ✅ Set cookie on server side
|
|
response.cookies.set("NEXT_LOCALE", localeFromPath, {
|
|
path: "/",
|
|
maxAge: 31536000, // 1 year
|
|
sameSite: "lax",
|
|
});
|
|
|
|
// ✅ Pass locale to request headers for server components
|
|
const requestHeaders = new Headers(request.headers);
|
|
requestHeaders.set("x-locale", localeFromPath);
|
|
requestHeaders.set("x-pathname", pathname);
|
|
|
|
return NextResponse.next({
|
|
request: {
|
|
headers: requestHeaders,
|
|
},
|
|
});
|
|
}
|
|
|
|
// Normal flow - just pass locale in headers
|
|
const response = NextResponse.next();
|
|
const requestHeaders = new Headers(request.headers);
|
|
requestHeaders.set("x-locale", localeFromPath);
|
|
requestHeaders.set("x-pathname", pathname);
|
|
|
|
return NextResponse.next({
|
|
request: {
|
|
headers: requestHeaders,
|
|
},
|
|
});
|
|
}
|
|
|
|
export const config = {
|
|
matcher: [
|
|
// Match all pathnames except for
|
|
// - … if they start with `/api`, `/_next` or `/_vercel`
|
|
// - … the ones containing a dot (e.g. `favicon.ico`)
|
|
'/((?!api|_next|_vercel|.*\\..*).*)',
|
|
],
|
|
}; |