カスタムロケールリゾルバー

カスタムリゾルバーファイルを提供することで、ロケールの検出と永続化の方法をカスタマイズできます。

デフォルトでは、コンパイラはCookieベースのロケール永続化を使用します。カスタムリゾルバーを使用すると、localStorage、URLパラメータ、データベース検索、サブドメイン検出などの代替戦略を実装できます。

仕組み

.lingo/ディレクトリにオプションファイルを作成します:

  • .lingo/locale-resolver.server.ts — サーバーサイドのロケール検出
  • .lingo/locale-resolver.client.ts — クライアントサイドのロケール検出と永続化

これらのファイルが存在しない場合、コンパイラはデフォルトのCookieベースの実装を使用します。

サーバーロケールリゾルバー

カスタムサーバーサイドロケール検出のために.lingo/locale-resolver.server.tsを作成します:

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

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

例: データベース検索

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

例: サブドメイン検出

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

クライアントロケールリゾルバー

カスタムクライアントサイドロケール検出と永続化のために.lingo/locale-resolver.client.tsを作成します:

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

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

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

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

例: 組み合わせ戦略

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

TypeScript型

両方のリゾルバーは完全に型付けされています:

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

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

setLocaleとの統合

useLingoContext()setLocale()関数は、カスタムpersistLocale()を自動的に呼び出します。

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

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

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

SSRに関する考慮事項

SSRフレームワーク(Next.js、Remixなど)の場合:

  • サーバーリゾルバーはリクエストごとに実行されます
  • クライアントリゾルバーはハイドレーション後にブラウザで実行されます
  • サーバーとクライアントの検出の一貫性を確保してください

一般的なパターン: サーバーはcookie/headerから読み取り、クライアントはcookie/localStorageに永続化します。

デフォルト実装

カスタムリゾルバーが提供されていない場合、コンパイラは次のデフォルトを使用します:

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

よくある質問

サーバーとクライアントの両方のリゾルバーが必要ですか? いいえ。カスタマイズが必要なものだけを提供してください。欠落しているファイルはデフォルトの動作を使用します。

SPAアプリでカスタムリゾルバーを使用できますか? はい。SPAアプリではクライアントリゾルバーのみが関連します。サーバーリゾルバーはSSR用です。

Viteで動作しますか? はい。クライアントリゾルバーは同じように動作します。サーバーリゾルバーはNext.js専用です(SSR用)。

カスタムリゾルバーをテストするにはどうすればよいですか?

  1. リゾルバーファイルを作成します
  2. ロジックを実装します
  3. 開発サーバーを実行します
  4. カスタム永続化でロケール切り替えをテストします

Next.js固有のAPIにアクセスできますか? はい。リゾルバーファイル内でNext.jsユーティリティ(headers、cookiesなど)を直接インポートできます。

getServerLocaleが無効なロケールを返した場合はどうなりますか? 返されたロケールがtargetLocalesにない場合、コンパイラはsourceLocaleにフォールバックします。

ユースケース別の例

サブドメインベースのルーティング

サーバー:

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

クライアント:

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

データベースベースのユーザー設定

サーバー:

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

クライアント:

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

パスベースのルーティング (/en/about, /es/about)

サーバー:

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

クライアント:

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

次のステップ