Resolutores de localización personalizados

Personaliza cómo se detectan y se guardan las configuraciones regionales proporcionando archivos de resolutores personalizados.

Por defecto, el compilador utiliza cookies para mantener la configuración regional. Los resolutores personalizados te permiten implementar estrategias alternativas como localStorage, parámetros en la URL, búsqueda en base de datos o detección por subdominio.

Cómo funciona

Crea archivos opcionales en el directorio .lingo/:

  • .lingo/locale-resolver.server.ts — Detección de localización en el servidor
  • .lingo/locale-resolver.client.ts — Detección y persistencia de localización en el cliente

Si estos archivos no existen, el compilador usará la implementación predeterminada basada en cookies.

Resolutor de localización en el servidor

Crea .lingo/locale-resolver.server.ts para una detección personalizada de localización en el servidor:

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

Ejemplo: Cabecera 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;
}

Ejemplo: Búsqueda en base de datos

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

Ejemplo: Detección por subdominio

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

Resolutor de localización en el cliente

Crea .lingo/locale-resolver.client.ts para una detección y persistencia personalizada de localización en el cliente:

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

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

Ejemplo: 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();
}

Ejemplo: Parámetros de 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();
}

Ejemplo: Estrategia combinada

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

Tipos de TypeScript

Ambos resolvers están completamente tipados:

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

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

Integración con setLocale

La función setLocale() de useLingoContext() llama automáticamente a tu persistLocale() personalizada:

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

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

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

Consideraciones SSR

Para frameworks SSR (Next.js, Remix, etc.):

  • El resolver de servidor se ejecuta en cada solicitud
  • El resolver de cliente se ejecuta en el navegador después de la hidratación
  • Asegúrate de que la detección del idioma sea consistente en servidor y cliente

Patrón común: El servidor lee de la cookie/cabecera, el cliente persiste en cookie/localStorage.

Implementación predeterminada

Si no se proporcionan resolvers personalizados, el compilador usa esta implementación predeterminada:

// 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();
}

Preguntas comunes

¿Necesito ambos resolvers, servidor y cliente? No. Solo proporciona lo que necesitas personalizar. Los archivos que falten usan el comportamiento predeterminado.

¿Puedo usar resolvers personalizados con apps SPA? Sí. Solo el resolver de cliente es relevante para las apps SPA. El resolver de servidor es para SSR.

¿Esto funciona con Vite? Sí. El resolver de cliente funciona igual. El resolver de servidor es específico de Next.js (para SSR).

¿Cómo pruebo resolvers personalizados?

  1. Crea los archivos del resolver
  2. Implementa tu lógica
  3. Ejecuta el servidor de desarrollo
  4. Prueba el cambio de idioma con tu propia persistencia

¿Puedo acceder a APIs específicas de Next.js? Sí. Importa utilidades de Next.js (headers, cookies, etc.) directamente en tus archivos resolvers.

¿Qué pasa si getServerLocale devuelve un locale no válido? El compilador recurre a sourceLocale si el locale devuelto no está en targetLocales.

Ejemplos por caso de uso

Enrutamiento basado en subdominios

Servidor:

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

Cliente:

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

Preferencias de usuario respaldadas por base de datos

Servidor:

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

Cliente:

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

Enrutamiento basado en ruta (/en/about, /es/about)

Servidor:

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

Cliente:

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

Próximos pasos