التدويل
يُولّد FORGE دعماً كاملاً متعدد اللغات عبر كلا تطبيقي الواجهة الأمامية. الترجمات تُحمّل من ملفات JSON ثابتة (مُنشورة من لوحة الإدارة)، hook useTranslation يوفر دالة t() مع interpolation، وتخطيطات RTL تُعالج تلقائياً بناءً على اتجاه اللغة النشطة.
المعمارية
┌────────────────────────────────────────────────────────────┐
│ لوحة الإدارة │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ صفحة إدارة الترجمات │ │
│ │ key: "auth.login_title" │ │
│ │ en: "Login to your account" │ │
│ │ ar: "تسجيل الدخول إلى حسابك" │ │
│ │ │ │
│ │ [نشر] → يُصدّر /messages/en.json │ │
│ │ /messages/ar.json │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ I18n Provider │
│ │
│ 1. قراءة اللغة من cookie/localStorage │
│ 2. جلب /messages/{locale}.json │
│ 3. جلب /api/v1/languages (لمعلومات الاتجاه) │
│ 4. توفير دالة t()، اللغة، الاتجاه │
│ 5. تعيين <html lang="..." dir="..."> │
└────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ المكونات │
│ │
│ const { t } = useTranslation(); │
│ <h1>{t("auth.login_title")}</h1> │
│ // → "Login to your account" or "تسجيل الدخول إلى حسابك" │
└────────────────────────────────────────────────────────────┘I18n Provider
كلا التطبيقين يغلفان شجرة مكوناتهما بـ I18nProvider الذي يُدير حالة اللغة ويُحمّل الترجمات ويتتبع اتجاه النص:
// app/layout.tsx
import { I18nProvider } from "@/components/providers/i18n-provider";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<I18nProvider defaultLocale="en" fallbackLocale="en">
{children}
</I18nProvider>
</body>
</html>
);
}الخصائص
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
defaultLocale | string | "en" | اللغة الابتدائية إذا لم يُخزّن تفضيل |
fallbackLocale | string | "en" | اللغة المُستخدمة إذا فشل تحميل ملف الترجمة |
كيف يعمل
عند التحميل، الـ provider:
- يقرأ اللغة من cookie
locale، يتراجع لـlocalStorage، ثم لـdefaultLocale - يقرأ الاتجاه من cookie
direction(يُعيّن أثناء تبديل اللغة) - يجلب قائمة اللغات من
/api/v1/languagesللحصول على بيانات الاتجاه الوصفية - يُحمّل الترجمات من
/messages/{locale}.json(ملف JSON ثابت) - يُعيّن خصائص
<html>--langوdir-- لتطابق اللغة النشطة
Translation Hook
hook useTranslation هو الواجهة الرئيسية للوصول للترجمات في المكونات:
import { useTranslation } from "@/components/providers/i18n-provider";
function LoginPage() {
const { t, locale, direction, isLoading } = useTranslation();
return (
<div>
<h1>{t("auth.login_title")}</h1>
<p>{t("auth.login_subtitle")}</p>
</div>
);
}القيم المُرجعة
| الخاصية | النوع | الوصف |
|---|---|---|
t | (key: string, params?: Record<string, string | number>) => string | دالة الترجمة |
locale | string | كود اللغة الحالية (مثل "en"، "ar") |
direction | "ltr" | "rtl" | اتجاه النص الحالي |
isLoading | boolean | هل الترجمات لا تزال تُحمّل |
currentLanguage | string | اسم بديل لـ locale |
Interpolation
دالة t() تدعم interpolation المعاملات باستخدام صيغة :
// Translation: "Welcome, {{name}}! You have {{count}} messages."
t("dashboard.welcome", { name: "Ahmed", count: 5 });
// → "Welcome, Ahmed! You have 5 messages."اصطلاح المفاتيح
مفاتيح الترجمة تتبع اصطلاح namespace مفصول بنقاط:
{module}.{section}.{key}أمثلة:
| المفتاح | الإنجليزية | العربية |
|---|---|---|
auth.login_title | Login to your account | تسجيل الدخول إلى حسابك |
admin.users.Users | Users | المستخدمون |
admin.users.Add_User | Add User | إضافة مستخدم |
admin.nav.Dashboard | Dashboard | لوحة التحكم |
common.Delete | Delete | حذف |
common.Save | Save | حفظ |
نصيحة
إذا لم يُعثر على مفتاح ترجمة، دالة t() تُرجع المفتاح نفسه. هذا يُسهّل تحديد الترجمات المفقودة أثناء التطوير -- النص غير المُترجم يظهر كمسار مفتاحه.
مُبدّل اللغات
مكون LanguageSwitcher يعرض قائمة منسدلة باللغات المتاحة. عند اختيار لغة:
- يُعيّن cookies
localeوdirection(مشتركة عبر النطاقات الفرعية) - يُحدّث
localStorageكاحتياط - يُعيّن
document.documentElement.dirوdocument.documentElement.lang - يُحدّث حالة سياق I18n (يُطلق إعادة عرض كل المحتوى المُترجم)
- يستدعي
router.refresh()لتحديث المحتوى المُعروض من الخادم
import { LanguageSwitcher } from "@/components/language-switcher";
// Displays a Globe icon button with dropdown menu
<LanguageSwitcher />المكون يُخفي نفسه تلقائياً عندما تكون لغة واحدة فقط مُعدّة. كل لغة تُعرض باستخدام خاصية native_name (مثل "English"، "العربية"، "Français").
الحفظ المبني على Cookies
تفضيل اللغة يُحفظ في cookies لتتمكن الصفحات المُعروضة من الخادم من الوصول للغة قبل تحميل JavaScript:
// Set during language switch
document.cookie = `locale=${langCode};path=/;max-age=31536000`;
document.cookie = `direction=${direction};path=/;max-age=31536000`;الـ middleware (أو Server Component) يقرأ هذه الـ cookies لتحديد اللغة الصحيحة للعرض من جانب الخادم:
// In Server Component
const headersList = await headers();
const locale = headersList.get("x-locale") || "en";دعم RTL
دعم RTL (من اليمين لليسار) مبني في كل طبقة من الواجهة الأمامية.
1. اتجاه HTML
خاصية dir لعنصر <html> تُعيّن بواسطة I18n provider:
<!-- الإنجليزية -->
<html lang="en" dir="ltr">
<!-- العربية -->
<html lang="ar" dir="rtl">2. خصائص CSS المنطقية
قوالب FORGE تستخدم خصائص CSS المنطقية (ms-، me-، ps-، pe-، start، end) بدلاً من خصائص الاتجاه الفيزيائية (ml-، mr-، pl-، pr-، left، right). هذا يضمن انعكاس التخطيطات بشكل صحيح في وضع RTL:
// Instead of ml-2 (always left margin)
<Icon className="me-2 h-4 w-4" /> // margin-end: adapts to direction
// Instead of pl-8 (always left padding)
<div className="ps-8"> // padding-start: adapts to direction
// Instead of text-left
<span className="text-start"> // start: adapts to directionتحذير
عند إضافة CSS مخصص، استخدم دائماً الخصائص المنطقية (ms، me، ps، pe، start، end) بدلاً من خصائص الاتجاه الفيزيائية (ml، mr، pl، pr، left، right). هذا يضمن عمل تخطيطك بشكل صحيح في كلا وضعي LTR و RTL.
3. مكونات واعية بالاتجاه
عدة مكونات تُكيّف سلوكها بناءً على الاتجاه:
- Sidebar -- يُوضع على اليسار في LTR، على اليمين في RTL
- DirectionAwareToaster -- إشعارات Toast تظهر في الزاوية الصحيحة
- RichTextEditor -- اتجاه محتوى TinyMCE يُطابق اللغة النشطة
- ترقيم DataTable -- أسهم التنقل تنعكس في RTL
المحتوى القابل للترجمة
الموارد مثل الأدوار وصفحات المحتوى والقوائم والبيانات المرجعية تدعم ترجمات لكل حقل مُخزّنة كـ JSONB في قاعدة البيانات. لوحة الإدارة توفر واجهات تحرير بتبويبات لهذه.
TranslationsInput
للموارد البسيطة (الأدوار، الصلاحيات) مع display_name و description:
<TranslationsInput
value={role.translations || {}}
onChange={(translations) => setRole({ ...role, translations })}
fields={["display_name", "description"]}
/>شكل البيانات:
{
"en": { "display_name": "Admin", "description": "Full access" },
"ar": { "display_name": "مشرف", "description": "وصول كامل" }
}ContentTranslationsInput
لصفحات المحتوى مع حقول غنية (عنوان، ملخص، تفاصيل، نص الزر، SEO):
<ContentTranslationsInput
value={content.translations || {}}
onChange={(translations) =>
setContent({ ...content, translations })
}
/>شكل البيانات:
{
"en": {
"title": "About Us",
"summary": "Learn about our company",
"details": "<p>We are a...</p>",
"button_text": "Contact Us",
"seo": {
"title": "About - MyApp",
"description": "Learn about our company"
}
},
"ar": {
"title": "من نحن",
"summary": "تعرف على شركتنا",
"details": "<p>نحن شركة...</p>",
"button_text": "تواصل معنا",
"seo": {
"title": "من نحن - تطبيقي",
"description": "تعرف على شركتنا"
}
}
}إدارة الترجمات
لوحة الإدارة تتضمن صفحة ترجمات مخصصة (/translations) حيث يمكن للمديرين:
- عرض جميع مفاتيح الترجمة مُجمّعة حسب الوحدة
- تحرير قيم الترجمة لكل لغة نشطة
- البحث عن مفاتيح ترجمة محددة
- نشر الترجمات لملفات JSON ثابتة تُقدّمها الواجهة الأمامية
إجراء النشر يُصدّر جميع الترجمات لكل لغة في ملف /messages/{locale}.json الذي يُحمّله I18n provider وقت التشغيل.
تنسيق التاريخ والأرقام
لتنسيق التاريخ والأرقام الواعي باللغة، استخدم APIs Intl المدمجة في المتصفح مع اللغة الحالية:
import { useTranslation } from "@/components/providers/i18n-provider";
function FormattedDate({ date }: { date: string }) {
const { locale } = useTranslation();
const formatted = new Intl.DateTimeFormat(locale, {
year: "numeric",
month: "long",
day: "numeric",
}).format(new Date(date));
return <time dateTime={date}>{formatted}</time>;
}
// English: "January 15, 2025"
// Arabic: "١٥ يناير ٢٠٢٥"تنسيق الأرقام:
function FormattedNumber({ value }: { value: number }) {
const { locale } = useTranslation();
const formatted = new Intl.NumberFormat(locale).format(value);
return <span>{formatted}</span>;
}
// English: "1,234,567"
// Arabic: "١٬٢٣٤٬٥٦٧"تنسيق العملات:
function FormattedCurrency({
value,
currency = "USD",
}: {
value: number;
currency?: string;
}) {
const { locale } = useTranslation();
const formatted = new Intl.NumberFormat(locale, {
style: "currency",
currency,
}).format(value);
return <span>{formatted}</span>;
}
// English: "$1,234.56"
// Arabic: "١٬٢٣٤٫٥٦ US$"إضافة لغة جديدة
لإضافة لغة جديدة لتطبيق FORGE:
- أضف اللغة في الإدارة -- اذهب إلى الإعدادات > اللغات وأنشئ إدخال لغة جديد بكود اللغة والاسم والاسم الأصلي والاتجاه:
الكود: fr
الاسم: French
الاسم الأصلي: Français
الاتجاه: ltr
نشط: نعمأضف الترجمات -- اذهب لصفحة الترجمات وأضف ترجمات لجميع المفاتيح في اللغة الجديدة. استخدم ميزات الإنشاء الجماعي أو الترجمة التلقائية لتسريع هذا.
انشر الترجمات -- انقر زر "نشر" لتوليد ملف
/messages/fr.jsonالذي ستُحمّله الواجهة الأمامية.أضف ترجمات المحتوى -- لكل صفحة محتوى وعنصر قائمة ودور، أضف ترجمات تحت تبويب اللغة الجديدة في نموذج التحرير.
نصيحة
مُبدّل اللغات يلتقط اللغات الجديدة تلقائياً من نقطة نهاية /api/v1/languages. لا حاجة لتغييرات كود -- فقط أضف اللغة عبر لوحة الإدارة وانشر الترجمات.
Hooks إضافية
I18n provider يُصدّر hookين إضافيين لحالات استخدام محددة:
useI18n
وصول كامل لسياق I18n، بما في ذلك setLocale:
import { useI18n } from "@/components/providers/i18n-provider";
const { locale, direction, languages, isLoading, t, setLocale } = useI18n();useLanguages
hook مركز لإدارة اللغات:
import { useLanguages } from "@/components/providers/i18n-provider";
const { languages, locale, setLocale, isLoading } = useLanguages();ما التالي
- المكونات — LanguageSwitcher و TranslationsInput ومكونات i18n الأخرى
- النماذج والتحقق — حقول النماذج متعددة اللغات
- تطبيق الويب — صفحات المحتوى الواعية باللغة و SEO