Custom Locale Resolvers

Max PrilutskiyCEO & Co-FounderUpdated 6 months ago · 3 min read

Alpha

The Lingo.dev Compiler is in alpha. It is unstable, not recommended for production use, and APIs may change between releases.

Custom locale resolvers let you override how the Lingo.dev Compiler detects and persists the user's locale. By default, the compiler uses cookie-based persistence configured via the localePersistence option. For more control - URL-based routing, header detection, localStorage, or any custom logic - create resolver files in the .lingo/ directory.

Resolver files#

The compiler looks for two optional files:

FileEnvironmentExports
.lingo/locale-resolver.server.tsServer-side (SSR, RSC)resolveLocale(request: Request): string
.lingo/locale-resolver.client.tsClient-side (browser)resolveLocale(): string and persistLocale(locale: string): void

If a resolver file exists, the compiler uses it instead of the default cookie-based behavior. If only one file exists, the other environment falls back to the default.

Server-side resolver#

The server resolver receives the incoming Request object and returns a locale code string:

ts
// .lingo/locale-resolver.server.ts

export function resolveLocale(request: Request): string {
  const url = new URL(request.url);

  // Check URL path prefix: /es/about -> "es"
  const pathLocale = url.pathname.split("/")[1];
  const supportedLocales = ["en", "es", "de", "fr", "ja"];

  if (supportedLocales.includes(pathLocale)) {
    return pathLocale;
  }

  // Fall back to Accept-Language header
  const acceptLanguage = request.headers.get("Accept-Language");
  if (acceptLanguage) {
    const preferred = acceptLanguage.split(",")[0].split("-")[0];
    if (supportedLocales.includes(preferred)) {
      return preferred;
    }
  }

  return "en";
}

Client-side resolver#

The client resolver has two functions: one to read the current locale and one to persist a locale change:

ts
// .lingo/locale-resolver.client.ts

export function resolveLocale(): string {
  // Check URL path prefix
  const pathLocale = window.location.pathname.split("/")[1];
  const supportedLocales = ["en", "es", "de", "fr", "ja"];

  if (supportedLocales.includes(pathLocale)) {
    return pathLocale;
  }

  // Fall back to localStorage
  const stored = localStorage.getItem("locale");
  if (stored && supportedLocales.includes(stored)) {
    return stored;
  }

  return "en";
}

export function persistLocale(locale: string): void {
  localStorage.setItem("locale", locale);

  // Navigate to the locale-prefixed URL
  const path = window.location.pathname.replace(/^\/[a-z]{2}/, "");
  window.location.href = `/${locale}${path}`;
}

Common resolver patterns#

Route by URL path prefix (/es/about, /de/pricing):

ts
// .lingo/locale-resolver.server.ts
export function resolveLocale(request: Request): string {
  const url = new URL(request.url);
  const locale = url.pathname.split("/")[1];
  const supported = ["en", "es", "de", "fr"];
  return supported.includes(locale) ? locale : "en";
}
ts
// .lingo/locale-resolver.client.ts
export function resolveLocale(): string {
  const locale = window.location.pathname.split("/")[1];
  const supported = ["en", "es", "de", "fr"];
  return supported.includes(locale) ? locale : "en";
}

export function persistLocale(locale: string): void {
  const path = window.location.pathname.replace(/^\/[a-z]{2}/, "");
  window.location.href = `/${locale}${path}`;
}

The resolveLocale function must return a locale code that matches one of your configured targetLocales or sourceLocale. Returning an unsupported locale code causes the compiler to fall back to the source locale.

Next Steps#