محللات اللغة المخصصة
قم بتخصيص كيفية اكتشاف اللغات والاحتفاظ بها من خلال توفير ملفات محلل مخصصة.
بشكل افتراضي، يستخدم المترجم الاحتفاظ باللغة المستند إلى ملفات تعريف الارتباط. تتيح لك المحللات المخصصة تنفيذ استراتيجيات بديلة مثل 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
تستدعي دالة setLocale() من useLingoContext() تلقائيًا 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).
كيف أختبر المحللات المخصصة؟
- أنشئ ملفات المحلل
- نفذ منطقك
- شغل خادم التطوير
- اختبر تبديل اللغة مع الحفظ المخصص الخاص بك
هل يمكنني الوصول إلى واجهات برمجة التطبيقات الخاصة بـ Next.js؟ نعم. استورد أدوات Next.js (headers، cookies، إلخ) مباشرة في ملفات المحلل الخاصة بك.
ماذا لو أرجعت getServerLocale لغة غير صالحة؟
يعود المترجم إلى sourceLocale إذا لم تكن اللغة المرجعة ضمن targetLocales.
أمثلة حسب حالة الاستخدام
التوجيه المستند إلى النطاق الفرعي
الخادم:
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";
الخطوات التالية
- تبديل اللغة — تنفيذ أدوات تبديل اللغة
- تكامل إطار العمل — أنماط خاصة بإطار العمل
- أفضل الممارسات — استراتيجيات موصى بها لاكتشاف اللغة