Résolveurs de locale personnalisés

Personnalisez la détection et la persistance des locales en fournissant des fichiers de résolveur personnalisés.

Par défaut, le compilateur utilise la persistance de locale basée sur les cookies. Les résolveurs personnalisés vous permettent d'implémenter des stratégies alternatives comme localStorage, les paramètres d'URL, la recherche en base de données ou la détection de sous-domaine.

Fonctionnement

Créez des fichiers optionnels dans le répertoire .lingo/ :

  • .lingo/locale-resolver.server.ts — Détection de locale côté serveur
  • .lingo/locale-resolver.client.ts — Détection et persistance de locale côté client

Si ces fichiers n'existent pas, le compilateur utilise l'implémentation par défaut basée sur les cookies.

Résolveur de locale côté serveur

Créez .lingo/locale-resolver.server.ts pour une détection de locale personnalisée côté serveur :

// .lingo/locale-resolver.server.ts
export async function getServerLocale(): Promise<string> {
  // Your custom logic
  return "en";
}

Exemple : en-tête Accept-Language (Next.js)

import { headers } from "next/headers";

export async function getServerLocale(): Promise<string> {
  const headersList = await headers();
  const acceptLanguage = headersList.get("accept-language");

  // Parse accept-language: "en-US,en;q=0.9,es;q=0.8"
  const locale = acceptLanguage
    ?.split(",")[0]
    ?.split("-")[0]
    ?.trim() || "en";

  return locale;
}

Exemple : recherche en base de données

import { cookies } from "next/headers";
import { db } from "@/lib/db";

export async function getServerLocale(): Promise<string> {
  const cookieStore = await cookies();
  const sessionToken = cookieStore.get("session")?.value;

  if (!sessionToken) return "en";

  // Query user preferences from database
  const user = await db.user.findUnique({
    where: { sessionToken },
    select: { preferredLocale: true },
  });

  return user?.preferredLocale || "en";
}

Exemple : détection de sous-domaine

import { headers } from "next/headers";

export async function getServerLocale(): Promise<string> {
  const headersList = await headers();
  const host = headersList.get("host") || "";

  // Extract subdomain: es.example.com → es
  const subdomain = host.split(".")[0];

  // Map subdomain to locale
  const localeMap: Record<string, string> = {
    es: "es",
    de: "de",
    fr: "fr",
  };

  return localeMap[subdomain] || "en";
}

Résolveur de locale côté client

Créez .lingo/locale-resolver.client.ts pour une détection et persistance de locale personnalisées côté client :

// .lingo/locale-resolver.client.ts
export function getClientLocale(): string {
  // Detect locale
  return "en";
}

export function persistLocale(locale: string): void {
  // Save locale preference
}

Exemple : localStorage

export function getClientLocale(): string {
  if (typeof window === "undefined") return "en";

  // Check localStorage
  const stored = localStorage.getItem("user-locale");
  if (stored) return stored;

  // Fall back to browser language
  return navigator.language.split("-")[0] || "en";
}

export function persistLocale(locale: string): void {
  if (typeof window === "undefined") return;

  localStorage.setItem("user-locale", locale);

  // Optionally reload page to apply new locale
  window.location.reload();
}

Exemple : paramètres d'URL

export function getClientLocale(): string {
  if (typeof window === "undefined") return "en";

  // Check URL parameter: ?lang=es
  const params = new URLSearchParams(window.location.search);
  const urlLocale = params.get("lang");

  if (urlLocale) return urlLocale;

  // Fall back to localStorage
  return localStorage.getItem("locale") || "en";
}

export function persistLocale(locale: string): void {
  if (typeof window === "undefined") return;

  // Update URL parameter
  const url = new URL(window.location.href);
  url.searchParams.set("lang", locale);
  window.history.replaceState({}, "", url.toString());

  // Also save to localStorage
  localStorage.setItem("locale", locale);

  // Reload to apply new locale
  window.location.reload();
}

Exemple : stratégie combinée

export function getClientLocale(): string {
  if (typeof window === "undefined") return "en";

  // Priority 1: URL parameter
  const params = new URLSearchParams(window.location.search);
  const urlLocale = params.get("lang");
  if (urlLocale) return urlLocale;

  // Priority 2: localStorage
  const stored = localStorage.getItem("locale");
  if (stored) return stored;

  // Priority 3: Browser language
  const browserLocale = navigator.language.split("-")[0];
  const supportedLocales = ["en", "es", "de", "fr"];
  if (supportedLocales.includes(browserLocale)) {
    return browserLocale;
  }

  // Priority 4: Default
  return "en";
}

export function persistLocale(locale: string): void {
  if (typeof window === "undefined") return;

  // Save to localStorage
  localStorage.setItem("locale", locale);

  // Update URL
  const url = new URL(window.location.href);
  url.searchParams.set("lang", locale);
  window.history.replaceState({}, "", url.toString());

  // Reload page
  window.location.reload();
}

Types TypeScript

Les deux résolveurs sont entièrement typés :

// Server resolver
export async function getServerLocale(): Promise<string>;

// Client resolver
export function getClientLocale(): string;
export function persistLocale(locale: string): void;

Intégration avec setLocale

La fonction setLocale() de useLingoContext() appelle automatiquement votre persistLocale() personnalisé :

import { useLingoContext } from "@lingo.dev/compiler/react";

function MyComponent() {
  const { setLocale } = useLingoContext();

  // Calls your persistLocale() under the hood
  setLocale("es");
}

Considérations SSR

Pour les frameworks SSR (Next.js, Remix, etc.) :

  • Le résolveur serveur s'exécute à chaque requête
  • Le résolveur client s'exécute dans le navigateur après l'hydratation
  • Assurez la cohérence entre la détection serveur et client

Modèle courant : le serveur lit depuis un cookie/en-tête, le client persiste dans un cookie/localStorage.

Implémentation par défaut

Si aucun résolveur personnalisé n'est fourni, le compilateur utilise cette configuration par défaut :

// Default server resolver
export async function getServerLocale(): Promise<string> {
  const cookies = await import("next/headers").then((m) => m.cookies());
  return cookies().get("locale")?.value || "en";
}

// Default client resolver
export function getClientLocale(): string {
  return document.cookie
    .split("; ")
    .find((row) => row.startsWith("locale="))
    ?.split("=")[1] || "en";
}

export function persistLocale(locale: string): void {
  document.cookie = `locale=${locale}; path=/; max-age=31536000`;
  window.location.reload();
}

Questions fréquentes

Ai-je besoin des résolveurs serveur et client ? Non. Fournissez uniquement ce que vous devez personnaliser. Les fichiers manquants utilisent le comportement par défaut.

Puis-je utiliser des résolveurs personnalisés avec des applications SPA ? Oui. Seul le résolveur client est pertinent pour les applications SPA. Le résolveur serveur est pour le SSR.

Cela fonctionne-t-il avec Vite ? Oui. Le résolveur client fonctionne de manière identique. Le résolveur serveur est spécifique à Next.js (pour le SSR).

Comment tester les résolveurs personnalisés ?

  1. Créez les fichiers de résolveur
  2. Implémentez votre logique
  3. Lancez le serveur de développement
  4. Testez le changement de locale avec votre persistance personnalisée

Puis-je accéder aux API spécifiques à Next.js ? Oui. Importez les utilitaires Next.js (headers, cookies, etc.) directement dans vos fichiers de résolveur.

Que se passe-t-il si getServerLocale retourne une locale invalide ? Le compilateur revient à sourceLocale si la locale retournée n'est pas dans targetLocales.

Exemples par cas d'usage

Routage basé sur les sous-domaines

Serveur :

const host = (await headers()).get("host") || "";
const locale = host.split(".")[0]; // es.example.com → es
return supportedLocales.includes(locale) ? locale : "en";

Client :

const locale = window.location.hostname.split(".")[0];
return supportedLocales.includes(locale) ? locale : "en";

Préférences utilisateur stockées en base de données

Serveur :

const session = await getSession();
const user = await db.user.findUnique({
  where: { id: session.userId },
});
return user.locale || "en";

Client :

// After user changes locale in UI
await fetch("/api/user/locale", {
  method: "POST",
  body: JSON.stringify({ locale }),
});
window.location.reload();

Routage basé sur le chemin (/en/about, /es/about)

Serveur :

const pathname = (await headers()).get("x-pathname") || "/";
const locale = pathname.split("/")[1];
return supportedLocales.includes(locale) ? locale : "en";

Client :

const locale = window.location.pathname.split("/")[1];
return supportedLocales.includes(locale) ? locale : "en";

Prochaines étapes