Сохранение выбора языка пользователя

Использование cookie для запоминания выбранного языка

Проблема

Пользователь вручную выбирает «французский» на сайте. Когда он закрывает браузер и позже снова заходит на главную страницу сайта (например, example.com), приложение возвращается к языку по умолчанию (например, английскому) или автоматически определяет язык. Из-за того, что выбор не запоминается, пользователю приходится каждый раз искать переключатель языка и заново выбирать нужный язык при каждом новом сеансе.

Решение

Когда пользователь выбирает язык, сохраняйте этот выбор в cookie. В middleware при заходе пользователя на корневой путь (/) сначала проверьте наличие этой 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

Создайте файл middleware.ts в корне проекта. Этот middleware сначала проверяет cookie NEXT_LOCALE. Если её нет, происходит проверка заголовка 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. Check for the cookie
  const cookie = request.cookies.get(localeCookieName);
  if (cookie) {
    const locale = cookie.value;
    if (locales.includes(locale)) {
      return locale;
    }
  }

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

  // 3. Return the default
  return defaultLocale;
}

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

  // 1. Check if the request is for the root path
  if (pathname === '/') {
    // Get the user's preferred language (from cookie or header)
    const bestLocale = getPreferredLocale(request);

    // Redirect to the best-matched language path
    request.nextUrl.pathname = `/${bestLocale}`;
    return NextResponse.redirect(request.nextUrl);
  }

  // 2. For all other paths, continue as normal
  return NextResponse.next();
}

export const config = {
  matcher: [
    // We only want to run this on the root path for now
    '/',
    // We also need to match non-root paths to let them pass
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

Теперь логика этого middleware правильно отдаёт приоритет явному выбору пользователя (cookie) над его неявным предпочтением (заголовок браузера) при посещении корня сайта.