112 lines
3.0 KiB
TypeScript
Executable File
112 lines
3.0 KiB
TypeScript
Executable File
import { NextResponse } from 'next/server';
|
|
import type { NextRequest } from 'next/server';
|
|
import { I18N_CONFIG } from '@nice/i18n';
|
|
|
|
// 使用统一的 i18n 配置
|
|
const { supportedLocales, defaultLocale } = I18N_CONFIG;
|
|
export type Locale = typeof supportedLocales[number];
|
|
|
|
function getLocale(request: NextRequest): string {
|
|
// 从 Cookie 获取
|
|
const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;
|
|
if (cookieLocale && (supportedLocales as readonly string[]).includes(cookieLocale)) {
|
|
return cookieLocale;
|
|
}
|
|
|
|
// 从 Accept-Language 头获取
|
|
const acceptLanguage = request.headers.get('accept-language') || '';
|
|
const browserLocales = acceptLanguage
|
|
.split(',')
|
|
.map(lang => lang.split(';')[0]?.trim())
|
|
.filter(Boolean)
|
|
.map(lang => lang?.split('-')[0])
|
|
.filter(Boolean);
|
|
|
|
for (const browserLocale of browserLocales) {
|
|
if (browserLocale && (supportedLocales as readonly string[]).includes(browserLocale)) {
|
|
return browserLocale;
|
|
}
|
|
}
|
|
|
|
return defaultLocale;
|
|
}
|
|
|
|
export function middleware(request: NextRequest) {
|
|
const { pathname, search } = request.nextUrl;
|
|
|
|
// 跳过不需要处理的路径
|
|
if (
|
|
// API 路由
|
|
pathname.startsWith('/api/') ||
|
|
// Next.js 内部路径
|
|
pathname.startsWith('/_next/') ||
|
|
// 翻译文件路径
|
|
pathname.startsWith('/locales/') ||
|
|
// 静态文件
|
|
pathname.includes('.') ||
|
|
// 特定文件
|
|
pathname === '/favicon.ico' ||
|
|
pathname === '/sitemap.xml' ||
|
|
pathname === '/robots.txt' ||
|
|
// 图片文件
|
|
/\.(jpg|jpeg|png|gif|svg|ico|webp)$/i.test(pathname) ||
|
|
// CSS/JS 文件
|
|
/\.(css|js|mjs)$/i.test(pathname)
|
|
) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// 检查是否已经有语言前缀
|
|
const hasLocalePrefix = supportedLocales.some(
|
|
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
|
|
);
|
|
|
|
if (!hasLocalePrefix) {
|
|
// 获取用户的语言偏好
|
|
const locale = getLocale(request);
|
|
|
|
// 重定向到带语言前缀的 URL
|
|
const newUrl = new URL(`/${locale}${pathname}`, request.url);
|
|
newUrl.search = search;
|
|
|
|
const response = NextResponse.redirect(newUrl);
|
|
|
|
// 设置 Cookie 保存语言偏好
|
|
response.cookies.set('NEXT_LOCALE', locale, {
|
|
maxAge: 365 * 24 * 60 * 60, // 1 year
|
|
path: '/',
|
|
sameSite: 'lax'
|
|
});
|
|
|
|
return response;
|
|
}
|
|
|
|
// 验证语言是否支持
|
|
const pathLocale = pathname.split('/')[1];
|
|
if (pathLocale && !(supportedLocales as readonly string[]).includes(pathLocale)) {
|
|
// 不支持的语言,重定向到默认语言
|
|
const newPathname = pathname.replace(`/${pathLocale}`, `/${defaultLocale}`);
|
|
const newUrl = new URL(newPathname, request.url);
|
|
newUrl.search = search;
|
|
return NextResponse.redirect(newUrl);
|
|
}
|
|
|
|
// 添加语言信息到请求头,供服务端组件使用
|
|
const requestHeaders = new Headers(request.headers);
|
|
requestHeaders.set('x-pathname', pathname);
|
|
|
|
return NextResponse.next({
|
|
request: {
|
|
headers: requestHeaders,
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// Middleware matcher configuration
|
|
export const config = {
|
|
matcher: [
|
|
'/((?!api|_next/static|_next/image|favicon.ico|locales|.*\\..*).*)'
|
|
],
|
|
};
|