language Switcher added
This commit is contained in:
136
middleware.ts
Normal file
136
middleware.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
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|.*\\..*).*)',
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user