محللات اللغة المخصصة

قم بتخصيص كيفية اكتشاف اللغات والاحتفاظ بها من خلال توفير ملفات محلل مخصصة.

بشكل افتراضي، يستخدم المترجم الاحتفاظ باللغة المستند إلى ملفات تعريف الارتباط. تتيح لك المحللات المخصصة تنفيذ استراتيجيات بديلة مثل 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).

كيف أختبر المحللات المخصصة؟

  1. أنشئ ملفات المحلل
  2. نفذ منطقك
  3. شغل خادم التطوير
  4. اختبر تبديل اللغة مع الحفظ المخصص الخاص بك

هل يمكنني الوصول إلى واجهات برمجة التطبيقات الخاصة بـ 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";

الخطوات التالية