自定义 Locale 解析器
通过提供自定义 resolver 文件,定制 locale 的检测与持久化方式。
默认情况下,编译器使用基于 cookie 的 locale 持久化机制。自定义 resolver 允许你实现如 localStorage、URL 参数、数据库查询或子域名检测等替代策略。
工作原理
在 .lingo/ 目录下创建可选文件:
.lingo/locale-resolver.server.ts— 服务器端 locale 检测.lingo/locale-resolver.client.ts— 客户端 locale 检测与持久化
如果这些文件不存在,编译器将使用默认的基于 cookie 的实现。
服务器端 Locale 解析器
创建 .lingo/locale-resolver.server.ts 以实现自定义的服务器端 locale 检测:
// .lingo/locale-resolver.server.ts
export async function getServerLocale(): Promise<string> {
// Your custom logic
return "en";
}
示例:Accept-Language Header(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";
}
客户端 Locale 解析器
创建 .lingo/locale-resolver.client.ts 以实现自定义的客户端 locale 检测与持久化:
// .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 类型
两个 resolver 都是完全类型化的:
// 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 等):
- 服务端 resolver 在每次请求时运行
- 客户端 resolver 在浏览器 hydration 后运行
- 确保服务端和客户端检测结果一致
常见模式: 服务端从 cookie/header 读取,客户端持久化到 cookie/localStorage。
默认实现
如果没有提供自定义 resolver,编译器会使用如下默认实现:
// 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();
}
常见问题
我需要同时实现服务端和客户端 resolver 吗? 不需要。只需根据自定义需求提供相应部分。缺失的文件会采用默认行为。
SPA 应用可以用自定义 resolver 吗? 可以。SPA 应用只需客户端 resolver。服务端 resolver 仅用于 SSR。
支持 Vite 吗? 支持。客户端 resolver 行为一致。服务端 resolver 仅适用于 Next.js(SSR 场景)。
如何测试自定义 resolver?
- 创建 resolver 文件
- 实现你的逻辑
- 运行开发服务器
- 测试自定义持久化下的语言切换
可以访问 Next.js 专用 API 吗? 可以。在 resolver 文件中直接引入 Next.js 工具(headers、cookies 等)。
如果 getServerLocale 返回了无效的 locale 怎么办?
如果返回的 locale 不在 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";