Определение предпочтительного языка пользователя

Перенаправление новых посетителей на наиболее подходящий язык

Проблема

Когда пользователь впервые заходит на корень приложения (например, /), по умолчанию отображается определённый язык, например, английский. Это сразу создаёт неудобство для тех, кто говорит на других языках: им приходится вручную искать переключатель языка, хотя браузер уже сообщает о предпочтениях пользователя.

Решение

Используйте middleware, чтобы перехватывать запросы к корневому пути (/). Проверьте HTTP-заголовок Accept-Language, чтобы узнать предпочтительный язык пользователя. Если этот язык поддерживается приложением, перенаправьте пользователя на корень этого языка (например, /fr). Если нет — перенаправьте на язык по умолчанию (например, /en).

Шаги

1. Установите парсер языков

Заголовок Accept-Language может быть сложным (например, fr-CH, fr;q=0.9, en;q=0.8). Небольшая библиотека поможет разобрать этот заголовок и найти наилучшее совпадение из списка поддерживаемых языков.

Выполните эту команду в терминале:

npm install accept-language-parser

2. Определите поддерживаемые языки и язык по умолчанию

Создайте центральный конфигурационный файл, где будет храниться список поддерживаемых языков и определён язык по умолчанию. Этот язык будет использоваться, если предпочтения браузера пользователя не совпадают ни с одним из поддерживаемых.

// i18n.config.ts

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

3. Создайте middleware

Создайте файл middleware.ts в корне проекта. Этот файл будет запускаться при входящих запросах, позволяя проверять путь и заголовки.

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

// Helper function to find the best language match
function getBestLocale(acceptLangHeader: string | null) {
  if (!acceptLangHeader) {
    return defaultLocale;
  }

  // Use the parser to find the best supported language
  const bestMatch = parser.pick(locales, acceptLangHeader, {
    loose: true,
  });

  return bestMatch || 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
    const acceptLang = request.headers.get('Accept-Language');
    const bestLocale = getBestLocale(acceptLang);

    // 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: [
    // Skip all paths that start with:
    // - api (API routes)
    // - _next/static (static files)
    // - _next/image (image optimization files)
    // - favicon.ico (favicon file)
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

Этот код выполняет логику только для корневого пути (/). Если пользователь заходит на /, проверяется его заголовок Accept-Language, находится наилучшее совпадение (например, es) и происходит редирект на /es. Все остальные запросы, например, /en/about, этой логикой не обрабатываются и проходят дальше.