Détecter la langue préférée d'un utilisateur

Rediriger les nouveaux visiteurs vers leur langue la plus probable

Problème

Lorsqu'un utilisateur visite pour la première fois la racine d'une application (par exemple, /), celle-ci affiche une langue par défaut, comme l'anglais. Cela crée immédiatement une friction pour les utilisateurs qui parlent d'autres langues, les obligeant à trouver manuellement un sélecteur de langue, même si leur navigateur communique déjà leur préférence.

Solution

Utilisez un middleware pour intercepter les requêtes vers le chemin racine (/). Vérifiez l'en-tête HTTP Accept-Language de l'utilisateur pour trouver sa langue préférée. Si cette langue est prise en charge par l'application, redirigez l'utilisateur vers la racine de cette langue (par exemple, /fr). Sinon, redirigez-le vers une langue par défaut (par exemple, /en).

Étapes

1. Installer un analyseur de langue

L'en-tête Accept-Language peut être complexe (par exemple, fr-CH, fr;q=0.9, en;q=0.8). Une petite bibliothèque aide à analyser cet en-tête et à trouver la meilleure correspondance parmi notre liste de langues prises en charge.

Exécutez cette commande dans votre terminal :

npm install accept-language-parser

2. Définir vos langues et la langue par défaut

Créez un fichier de configuration central pour stocker votre liste de langues prises en charge et définir une langue par défaut. Cette langue par défaut sera utilisée si les préférences du navigateur d'un utilisateur ne correspondent à aucune langue que vous prenez en charge.

// i18n.config.ts

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

3. Créer le middleware

Créez un fichier middleware.ts à la racine de votre projet. Ce fichier s'exécutera sur les requêtes entrantes, vous permettant de vérifier le chemin et les en-têtes.

// 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).*)',
  ],
};

Ce code n'exécute la logique que pour le chemin racine (/). Si un utilisateur visite /, il vérifie son en-tête Accept-Language, trouve la meilleure correspondance (par exemple, es), et le redirige vers /es. Toutes les autres requêtes, comme /en/about, sont ignorées par cette logique et passent directement.