Как определить языковые предпочтения пользователя в React Router v7

Автоматическое перенаправление на основе настроек браузера

Проблема

Каждый браузер отправляет заголовок Accept-Language с каждым HTTP-запросом, указывая предпочитаемые пользователем языки в порядке приоритета. В этом заголовке содержится ценная информация о том, какой язык ожидает увидеть пользователь, но большинство приложений полностью игнорируют её. Вместо этого всем посетителям показывается язык по умолчанию — обычно английский, — и пользователям приходится вручную искать переключатель языка, хотя их браузер уже сообщил о предпочтениях. Это создаёт лишние сложности и оставляет плохое первое впечатление, особенно для международных пользователей.

Решение

Создайте загрузчик (loader) для корневого пути, который будет считывать заголовок Accept-Language из входящего запроса. Разберите этот заголовок, чтобы извлечь предпочитаемые языки пользователя и их quality-значения. Сравните эти языки с поддерживаемыми локалями вашего приложения. Если найдено совпадение, перенаправьте пользователя на путь соответствующей локали. Если ни один язык не поддерживается, перенаправьте на локаль по умолчанию. Это позволит пользователям автоматически попадать на локализованную версию сайта, основываясь на уже настроенных в браузере предпочтениях.

Шаги

1. Установите библиотеку для разбора заголовка Accept-Language

Заголовок Accept-Language имеет определённый формат с quality-значениями, который требует внимательного разбора. Используйте специализированную библиотеку, чтобы корректно обработать этот процесс.

npm install accept-language-parser

Эта библиотека разбирает строку заголовка в упорядоченный список языковых предпочтений, корректно обрабатывая quality-значения и нестандартные случаи в соответствии со спецификацией HTTP.

2. Определите поддерживаемые локали

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

export const supportedLocales = ["en", "fr", "de", "es", "ja"] as const;

export const defaultLocale = "en";

export type Locale = (typeof supportedLocales)[number];

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

3. Создайте вспомогательную функцию для определения локали

Напишите функцию, которая принимает значение заголовка Accept-Language и возвращает наиболее подходящую поддерживаемую локаль.

import parser from "accept-language-parser";
import { supportedLocales, defaultLocale, type Locale } from "./locales";

export function detectLocale(acceptLanguageHeader: string | null): Locale {
  if (!acceptLanguageHeader) {
    return defaultLocale;
  }

  const languages = parser.parse(acceptLanguageHeader);

  for (const lang of languages) {
    const code = lang.code.toLowerCase();
    if (supportedLocales.includes(code as Locale)) {
      return code as Locale;
    }
  }

  return defaultLocale;
}

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

4. Настройте корневой индексный маршрут

Добавьте индексный маршрут в конфигурацию маршрутов, чтобы обрабатывать запросы к корневому пути.

import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
  index("routes/index.tsx"),
  route(":locale", "routes/locale-root.tsx", []),
] satisfies RouteConfig;

Индексный маршрут будет перехватывать запросы к корневому пути до совпадения с другими маршрутами, что позволит выполнить определение языка и перенаправление.

5. Реализуйте загрузчик индексного маршрута с определением языка

Создайте модуль индексного маршрута, который читает заголовок Accept-Language и перенаправляет на соответствующий путь локали.

import { redirect } from "react-router";
import type { Route } from "./+types/index";
import { detectLocale } from "~/utils/detect-locale";

export async function loader({ request }: Route.LoaderArgs) {
  const acceptLanguage = request.headers.get("Accept-Language");
  const locale = detectLocale(acceptLanguage);
  return redirect(`/${locale}`);
}

Когда пользователь заходит на корневой путь, этот загрузчик извлекает заголовок Accept-Language из запроса, определяет лучшую локаль и перенаправляет на корневой путь этой локали, чтобы с первой загрузки страницы показывать контент на предпочитаемом языке.