사용자 정의 로케일 리졸버

사용자 정의 리졸버 파일을 제공하여 로케일이 감지되고 유지되는 방식을 커스터마이징하세요.

기본적으로 컴파일러는 쿠키 기반 로케일 지속성을 사용합니다. 사용자 정의 리졸버를 사용하면 localStorage, URL 매개변수, 데이터베이스 조회 또는 서브도메인 감지와 같은 대체 전략을 구현할 수 있습니다.

작동 방식

.lingo/ 디렉토리에 선택적 파일을 생성하세요:

  • .lingo/locale-resolver.server.ts — 서버 측 로케일 감지
  • .lingo/locale-resolver.client.ts — 클라이언트 측 로케일 감지 및 지속성

이러한 파일이 존재하지 않으면 컴파일러는 기본 쿠키 기반 구현을 사용합니다.

서버 로케일 리졸버

사용자 정의 서버 측 로케일 감지를 위해 .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 등)의 경우:

  • 서버 리졸버는 모든 요청마다 실행됩니다
  • 클라이언트 리졸버는 하이드레이션 후 브라우저에서 실행됩니다
  • 서버와 클라이언트 감지 간의 일관성을 보장해야 합니다

일반적인 패턴: 서버는 쿠키/헤더에서 읽고, 클라이언트는 쿠키/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";

다음 단계