保存用户的语言选择

使用 Cookie 记住选择

问题

用户在网站上手动选择了“法语”。当他们关闭浏览器并稍后输入网站的主地址(例如,example.com)时,应用程序会恢复为默认语言(例如,英语)或自动检测的语言。这种未能记住用户选择的情况迫使用户每次开始新会话时都需要找到语言切换器并重新选择语言。

解决方案

当用户选择一种语言时,将该选择存储在一个 cookie 中。在中间件中,当用户访问根路径(/)时,先检查此 cookie,再检查 Accept-Language 头。如果找到有效的 cookie,则将用户重定向到其选择语言的根路径(例如,/fr),覆盖任何浏览器默认设置。

步骤

1. 定义语言配置

创建一个中央配置文件,用于存储支持的语言、默认语言以及用于存储用户偏好的 cookie 名称。

// i18n.config.ts

export const locales = ['en', 'es', 'fr'];
export const defaultLocale = 'en';
export const localeCookieName = 'NEXT_LOCALE';

2. 安装语言解析器

您仍然需要一个解析器来解析 Accept-Language 头,当未设置 cookie 时将作为后备。

npm install accept-language-parser

3. 创建中间件

在项目根目录下创建一个 middleware.ts 文件。此中间件将首先检查 NEXT_LOCALE cookie。如果未找到,则回退到检查 Accept-Language 头。此逻辑仅适用于对根路径(/)的请求。

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import parser from 'accept-language-parser';
import {
  locales,
  defaultLocale,
  localeCookieName,
} from './i18n.config';

function getPreferredLocale(request: NextRequest) {
  // 1. 检查 cookie
  const cookie = request.cookies.get(localeCookieName);
  if (cookie) {
    const locale = cookie.value;
    if (locales.includes(locale)) {
      return locale;
    }
  }

  // 2. 检查 Accept-Language 头
  const acceptLang = request.headers.get('Accept-Language');
  if (acceptLang) {
    const bestMatch = parser.pick(locales, acceptLang, {
      loose: true,
    });
    if (bestMatch) {
      return bestMatch;
    }
  }

  // 3. 返回默认值
  return defaultLocale;
}

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 1. 检查请求是否为根路径
  if (pathname === '/') {
    // 获取用户的首选语言(从 cookie 或头中)
    const bestLocale = getPreferredLocale(request);

    // 重定向到最佳匹配的语言路径
    request.nextUrl.pathname = `/${bestLocale}`;
    return NextResponse.redirect(request.nextUrl);
  }

  // 2. 对于所有其他路径,正常继续
  return NextResponse.next();
}

export const config = {
  matcher: [
    // 我们目前只希望在根路径上运行此逻辑
    '/',
    // 我们还需要匹配非根路径以允许它们通过
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

此中间件的逻辑现在正确地优先考虑用户的显式选择(cookie)而非隐式偏好(浏览器头),当他们访问网站的根路径时。