Add i18n foundation (fa-IR/en-US) and localize landing + auth.
Introduce path-prefixed locale routing under app/[lang] with a middleware that detects locale from cookie/Accept-Language (default fa-IR) and redirects. Add fa-IR (source of truth) and en-US dictionaries, a server getDictionary, a client I18nProvider/useT, locale-aware Link + router helpers, and a language switcher. The root [lang] layout sets html lang/dir and the per-locale font (Peyda for fa, Inter for en). Landing sections and the login/register/auth shell now read all copy from the dictionaries; dashboard localization follows in a later commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { locales, defaultLocale, isLocale, LOCALE_COOKIE, type Locale } from '@/i18n/config';
|
||||
|
||||
// Parse the Accept-Language header into an ordered list of base language codes.
|
||||
function parseAcceptLanguage(header: string | null): string[] {
|
||||
if (!header) return [];
|
||||
return header
|
||||
.split(',')
|
||||
.map((part) => {
|
||||
const [tag, q] = part.trim().split(';q=');
|
||||
return { tag: tag.trim().toLowerCase(), q: q ? parseFloat(q) : 1 };
|
||||
})
|
||||
.sort((a, b) => b.q - a.q)
|
||||
.map((x) => x.tag);
|
||||
}
|
||||
|
||||
function detectLocale(request: NextRequest): Locale {
|
||||
// 1. Explicit choice via cookie
|
||||
const cookieLocale = request.cookies.get(LOCALE_COOKIE)?.value;
|
||||
if (isLocale(cookieLocale)) return cookieLocale;
|
||||
|
||||
// 2. Browser preference via Accept-Language
|
||||
const accepted = parseAcceptLanguage(request.headers.get('accept-language'));
|
||||
for (const tag of accepted) {
|
||||
const exact = locales.find((l) => l.toLowerCase() === tag);
|
||||
if (exact) return exact;
|
||||
const base = locales.find((l) => l.toLowerCase().split('-')[0] === tag.split('-')[0]);
|
||||
if (base) return base;
|
||||
}
|
||||
|
||||
// 3. Fallback
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
const pathnameHasLocale = locales.some(
|
||||
(locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`),
|
||||
);
|
||||
if (pathnameHasLocale) return;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export const config = {
|
||||
// Skip Next internals, the API, and any path containing a dot (static files).
|
||||
matcher: ['/((?!_next|api|.*\\..*).*)'],
|
||||
};
|
||||
Reference in New Issue
Block a user