How to detect user language preferences in React Router v7

Auto-redirect based on browser preferences

Problem

Every browser sends an Accept-Language header with each HTTP request, indicating the user's preferred languages in order of preference. This header contains valuable information about what language the user expects to see, yet most applications ignore it entirely. Instead, they show a default language—typically English—to every visitor, forcing users to manually search for a language switcher even though their browser has already communicated their preference. This creates unnecessary friction and a poor first impression, especially for international users.

Solution

Create a loader for the root path that reads the Accept-Language header from the incoming request. Parse the header to extract the user's preferred languages and their quality values. Compare the preferred languages against your application's supported locales. If a match is found, redirect the user to that locale's path. If no supported language matches, redirect to a default locale. This ensures users land on a localized version of your site automatically, based on preferences they've already configured in their browser.

Steps

1. Install a library to parse the Accept-Language header

The Accept-Language header has a specific format with quality values that requires careful parsing. Use a dedicated library to handle this correctly.

npm install accept-language-parser

This library parses the header string into an ordered list of language preferences, handling quality values and edge cases according to the HTTP specification.

2. Define your supported locales

Create a helper file that lists the locales your application supports and specifies a default fallback.

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

export const defaultLocale = "en";

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

This provides a single source of truth for which languages your application can serve and ensures type safety throughout your codebase.

3. Create a locale detection helper

Build a function that takes the Accept-Language header value and returns the best matching supported locale.

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;
}

This function parses the header, iterates through the user's language preferences in order of quality, and returns the first match with your supported locales or falls back to the default.

4. Configure the root index route

Add an index route to your routes configuration that will handle requests to the root path.

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

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

The index route will intercept root path requests before any other route matches, allowing you to perform the language detection and redirect.

5. Implement the index route loader with language detection

Create the index route module that reads the Accept-Language header and redirects to the appropriate locale path.

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}`);
}

When a user visits the root path, this loader extracts the Accept-Language header from the request, determines the best locale, and redirects them to that locale's root path, ensuring they see content in their preferred language from the first page load.