585fdbc9b7
Drop Accept-Language detection from the locale middleware. A first visit (no NEXT_LOCALE cookie) now always serves the default locale (fa-IR) instead of guessing from the browser. The user's last explicit choice, persisted in the cookie by the language switcher, remains the sole signal on subsequent visits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.4 KiB
TypeScript
86 lines
3.4 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { locales, defaultLocale, isLocale, LOCALE_COOKIE, type Locale } from '@/i18n/config';
|
|
|
|
// Host-based separation between the marketing landing site and the
|
|
// authenticated panel. Configured at runtime (server-side) so the same
|
|
// frontend image can serve both hosts. When PANEL_HOST is unset (local dev),
|
|
// host routing is disabled and everything is served from a single origin.
|
|
const PANEL_HOST = process.env.PANEL_HOST?.trim().toLowerCase();
|
|
const LANDING_HOST = process.env.LANDING_HOST?.trim().toLowerCase();
|
|
|
|
// Locale-stripped path prefixes that belong to the authenticated panel.
|
|
const PANEL_PATH_PREFIXES = ['/dashboard', '/login', '/register'];
|
|
|
|
function stripLocale(pathname: string): string {
|
|
for (const l of locales) {
|
|
if (pathname === `/${l}`) return '/';
|
|
if (pathname.startsWith(`/${l}/`)) return pathname.slice(l.length + 1);
|
|
}
|
|
return pathname;
|
|
}
|
|
|
|
function isPanelPath(pathname: string): boolean {
|
|
const p = stripLocale(pathname);
|
|
return PANEL_PATH_PREFIXES.some((pre) => p === pre || p.startsWith(`${pre}/`));
|
|
}
|
|
|
|
function hostOf(request: NextRequest): string {
|
|
return (request.headers.get('host') || '').split(':')[0].toLowerCase();
|
|
}
|
|
|
|
function detectLocale(request: NextRequest): Locale {
|
|
// Honour the user's last explicit choice, persisted in a cookie by the
|
|
// language switcher. On the very first visit (no cookie) we always serve the
|
|
// default locale (fa-IR) rather than guessing from Accept-Language, so the
|
|
// browser's language preference never overrides the default. From then on,
|
|
// the user's last selection is what determines the language.
|
|
const cookieLocale = request.cookies.get(LOCALE_COOKIE)?.value;
|
|
if (isLocale(cookieLocale)) return cookieLocale;
|
|
|
|
return defaultLocale;
|
|
}
|
|
|
|
export function middleware(request: NextRequest) {
|
|
const { pathname, search } = request.nextUrl;
|
|
|
|
// 1. Ensure a locale prefix (e.g. /login -> /fa-IR/login). On the redirect
|
|
// the host is preserved, so host routing (below) runs on the next request.
|
|
const pathnameHasLocale = locales.some(
|
|
(locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`),
|
|
);
|
|
if (!pathnameHasLocale) {
|
|
const locale = detectLocale(request);
|
|
request.nextUrl.pathname = `/${locale}${pathname === '/' ? '' : pathname}`;
|
|
const response = NextResponse.redirect(request.nextUrl);
|
|
response.cookies.set(LOCALE_COOKIE, locale, { path: '/', maxAge: 60 * 60 * 24 * 365 });
|
|
return response;
|
|
}
|
|
|
|
// 2. Host-based separation between the landing site and the panel.
|
|
// Disabled entirely unless PANEL_HOST is configured (local dev).
|
|
if (PANEL_HOST) {
|
|
const host = hostOf(request);
|
|
const panelPath = isPanelPath(pathname);
|
|
|
|
// Authenticated routes requested on the landing host -> move to the panel.
|
|
if (LANDING_HOST && host === LANDING_HOST && panelPath) {
|
|
return NextResponse.redirect(`https://${PANEL_HOST}${pathname}${search}`, 308);
|
|
}
|
|
|
|
// Marketing pages requested on the panel host -> send to the dashboard.
|
|
if (host === PANEL_HOST && !panelPath) {
|
|
const locale = pathname.split('/')[1]; // path is already locale-prefixed
|
|
const target = request.nextUrl.clone();
|
|
target.pathname = `/${locale}/dashboard`;
|
|
return NextResponse.redirect(target);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
export const config = {
|
|
// Skip Next internals, the API, and any path containing a dot (static files).
|
|
matcher: ['/((?!_next|api|.*\\..*).*)'],
|
|
};
|