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

Использование 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. Проверка 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).*)',
  ],
};

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