middleware.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. // middleware.ts
  2. import { createI18nMiddleware } from "next-international/middleware";
  3. import { NextRequest, NextResponse } from "next/server";
  4. import { getSessionCookie } from "better-auth/cookies";
  5. function detectUserLocale(request: NextRequest): string {
  6. const acceptLanguage = request.headers.get("accept-language");
  7. if (!acceptLanguage) return "en";
  8. // Parse Accept-Language header
  9. const languages = acceptLanguage
  10. .split(",")
  11. .map((lang) => {
  12. const [locale, quality = "1"] = lang.trim().split(";q=");
  13. return { locale: locale.toLowerCase(), quality: parseFloat(quality) };
  14. })
  15. .sort((a, b) => b.quality - a.quality);
  16. // Map browser locales to supported locales
  17. const supportedLocales = ["en", "fr", "es", "zh-cn", "ru", "pt"];
  18. for (const { locale } of languages) {
  19. // Exact match
  20. if (supportedLocales.includes(locale)) {
  21. return locale === "zh-cn" ? "zh-CN" : locale;
  22. }
  23. // Language match (fr-FR -> fr)
  24. const lang = locale.split("-")[0];
  25. if (supportedLocales.includes(lang)) {
  26. return lang;
  27. }
  28. // Chinese variants
  29. if (locale.startsWith("zh")) {
  30. return "zh-CN";
  31. }
  32. }
  33. return "en"; // fallback
  34. }
  35. const I18nMiddleware = createI18nMiddleware({
  36. locales: ["en", "fr", "es", "zh-CN", "ru", "pt"],
  37. defaultLocale: "en",
  38. urlMappingStrategy: "rewrite",
  39. });
  40. export async function middleware(request: NextRequest) {
  41. const pathname = request.nextUrl.pathname;
  42. const detectedLocale = detectUserLocale(request);
  43. // If on root path and no locale detected yet, redirect to detected locale
  44. if (pathname === "/" && !request.cookies.get("detected-locale")) {
  45. const url = new URL(`/${detectedLocale}`, request.url);
  46. const response = NextResponse.redirect(url);
  47. response.cookies.set("detected-locale", detectedLocale, {
  48. maxAge: 60 * 60 * 24 * 365, // 1 year
  49. httpOnly: false,
  50. secure: process.env.NODE_ENV === "production",
  51. sameSite: "lax",
  52. });
  53. return response;
  54. }
  55. // Normal i18n middleware processing
  56. const response = I18nMiddleware(request);
  57. // Store detected locale in cookie for future visits
  58. if (!request.cookies.get("detected-locale")) {
  59. response.cookies.set("detected-locale", detectedLocale, {
  60. maxAge: 60 * 60 * 24 * 365, // 1 year
  61. httpOnly: false,
  62. secure: process.env.NODE_ENV === "production",
  63. sameSite: "lax",
  64. });
  65. }
  66. const searchParams = request.nextUrl.searchParams.toString();
  67. response.headers.set("searchParams", searchParams);
  68. if (request.nextUrl.pathname.includes("/dashboard")) {
  69. const session = getSessionCookie(request);
  70. if (!session) {
  71. return NextResponse.redirect(new URL("/", request.url));
  72. }
  73. }
  74. return response;
  75. }
  76. export const config = {
  77. matcher: [
  78. "/((?!api|static|_next|manifest.json|favicon.ico|robots.txt|sw.js|apple-touch-icon.png|android-chrome-.*\\.png|images|icons|sitemap.xml).*)",
  79. ],
  80. };