استخدم مساعدك المفضل للملخص واستخدم هذه الصفحة والموفر AI الذي تريده
تاريخ الإصدارات
- "إضافة مقارنة نجوم GitHub"v8.9.8١٨/٥/٢٠٢٦
- "بدء المقارنة"v8.7.5٦/١/٢٠٢٦
تمت ترجمة محتوى هذه الصفحة باستخدام الذكاء الاصطناعي.
اعرض آخر نسخة المحتوى الأصلي باللغة الإنكليزيةIf you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.
GitHub link to the documentationCopy doc Markdown to clipboard
مكتبات i18n لـ Next.js - تقرير مقارنة 2026
هذه الصفحة عبارة عن تقرير مقارنة لحلول i18n على Next.js.
جدول المحتويات
مقارنة تفاعلية
مرجع النتائج:
شاهد بيانات المقارنة الكاملة
شاهد مستودع المقارنة الكامل هنا.
مقدمة
مكتبات التدويل لها تأثير كبير على تطبيقك. الخطر الرئيسي هو تحميل المحتوى لجميع الصفحات وجميع اللغات بينما يزور المستخدم صفحة واحدة فقط.
مع نمو تطبيقك، يمكن أن ينمو حجم الحزمة بشكل كبير، مما قد يضر بالأداء بشكل ملحوظ.
على سبيل المثال، بالنسبة لأسوأ الحالات، يمكن أن تصبح صفحتك بعد تدويلها أكبر بـ 4 مرات تقريبًا.
تأثير آخر لمكتبات i18n هو بطء التطوير. تحويل المكونات إلى محتوى متعدد اللغات يستغرق وقتًا طويلاً.
بما أن المشكلة صعبة، توجد العديد من الحلول - بعضها يركز على تجربة المطور (DX)، والبعض الآخر على الأداء أو القابلية للتوسع، وهكذا.
يحاول Intlayer التحسين عبر هذه الأبعاد.
TL;DR
- Intlayer و next-translate: أفضل الخيارات لأداء Next.js، حيث يقدمان أصغر حجم وأفضل دعم للرندرة الستاتيكية.
- next-intl: الخيار الأكثر رواجًا ولكنه ثقيل ومعقد في التحسين للتطبيقات الكبيرة.
- next-i18next: شائع وغني بالإضافات، ولكنه يأتي بحجم حزمة كبير (~3 أضعاف Intlayer).
- تجنب: gt-next و lingo.dev بسبب مشكلات الأداء الخطيرة، والارتباط بالبائع (vendor lock-in)، والأخطاء التي تعطل البناء.
اختبر تطبيقك
لتسليط الضوء على هذه المشكلات، قمت ببناء ماسح ضوئي مجاني يمكنك تجربته هنا.
المشكلة
هناك طريقتان رئيستان للحد من تأثير التطبيق متعدد اللغات على حجم الحزمة الخاص بك:
- تقسيم ملفات JSON (أو المحتوى) عبر ملفات / متغيرات / مساحات أسماء (namespaces) حتى يتمكن المجمع (bundler) من إزالة المحتوى غير المستخدم لصفحة معينة عبر Tree-shaking.
- تحميل محتوى الصفحة ديناميكيًا بلغة المستخدم فقط.
القيود الفنية لهذه الأساليب:
التحميل الديناميكي
حتى عندما تعلن عن مسارات مثل [locale]/page.tsx باستخدام Webpack أو Turbopack، وحتى إذا تم تعريف generateStaticParams ، فإن المجمع لا يعامل locale كثابت ستاتيكي. وهذا يعني أنه قد يسحب المحتوى لجميع اللغات في كل صفحة. الطريقة الرئيسية للحد من ذلك هي تحميل المحتوى عبر الاستيراد الديناميكي (مثل import('./locales/${locale}.json')).
ما يحدث في وقت البناء هو أن Next.js يصدر حزمة JS واحدة لكل لغة (مثل ./locales_fr_12345.js). بعد إرسال الموقع إلى العميل، وعند تشغيل الصفحة، يقوم المتصفح بإجراء طلب HTTP إضافي لملف JS المطلوب (مثل ./locales_fr_12345.js).
طريقة أخرى لمعالجة نفس المشكلة هي استخدامfetch()لتحميل JSON ديناميكيًا. هذه هي الطريقة التي يعمل بهاTolgeeعندما يكون ملف JSON تحت/public، أوnext-translateالذي يعتمد علىgetStaticPropsلتحميل المحتوى. التدفق هو نفسه: المتصفح يقوم بطلب HTTP إضافي لتحميل الأصول.
تقسيم المحتوى
إذا كنت تستخدم صياغة مثل const t = useTranslation() + t('my-object.my-sub-object.my-key') ، فيجب عادةً أن يكون ملف JSON بالكامل في الحزمة حتى تتمكن المكتبة من تحليله والوصول إلى المفتاح. يتم شحن الكثير من هذا المحتوى حتى عندما يكون غير مستخدم في الصفحة.
للتخفيف من ذلك، تطلب منك بعض المكتبات الإعلان لكل صفحة عن مساحات الأسماء التي يجب تحميلها - مثل next-i18next و next-intl و lingui و next-translate و next-international.
في المقابل، يضيف Paraglide خطوة إضافية قبل البناء لتحويل JSON إلى رموز مسطحة مثل const en_my_var = () => 'my value'. نظريًا، يتيح ذلك إزالة المحتوى غير المستخدم في الصفحة. وكما سنرى، لا تزال لهذه الطريقة عيوب.
أخيرًا، يطبق Intlayer تحسينًا في وقت البناء بحيث يتم استبدال useIntlayer('my-key') بالمحتوى المقابل مباشرة.
المنهجية
في هذه المقارنة، قمنا بمقارنة المكتبات التالية:
Base App(بدون مكتبة i18n)next-intlayer(v8.7.12)next-i18next(v16.0.5)next-intl(v4.9.1)@lingui/core(v5.3.0)next-translate(v3.1.2)next-international(v1.3.1)@inlang/paraglide-js(v2.15.1)@tolgee/react(v7.0.0)@lingo.dev/compiler(v0.4.0)wuchale(v0.22.11)gt-next(v6.16.5)
استخدمت إصدار Next.js رقم 16.2.4 مع App Router.
قمت ببناء تطبيق متعدد اللغات يحتوي على 10 صفحات و 10 لغات.
قارنت بين أربع استراتيجيات تحميل:
افتح الجدول في نافذة منبثقة لعرض جميع محتويات البيانات بوضوح
| الاستراتيجية | بدون مساحات أسماء (عام) | مع مساحات أسماء (محدد) |
|---|---|---|
| التحميل الستاتيكي | Static: كل شيء في الذاكرة عند البدء. | Scoped static: تقسيم حسب مساحة الأسماء؛ تحميل كل شيء عند البدء. |
| التحميل الديناميكي | Dynamic: التحميل عند الطلب لكل لغة. | Scoped dynamic: تحميل دقيق حسب مساحة الأسماء واللغة. |
ملخص الاستراتيجيات
- Static: بسيط؛ لا يوجد تأخير في الشبكة بعد التحميل الأولي. الجانب السلبي: حجم حزمة كبير.
- Dynamic: يقلل الوزن الأولي (التحميل الكسول). مثالي عندما يكون لديك العديد من اللغات.
- Scoped static: يحافظ على تنظيم الكود (فصل منطقي) بدون طلبات شبكة إضافية معقدة.
- Scoped dynamic: أفضل نهج لتقسيم الكود والأداء. يقلل استهلاك الذاكرة عن طريق تحميل ما تحتاجه الرؤية الحالية واللغة النشطة فقط.
ما قمت بقياسه:
قمت بتشغيل نفس التطبيق متعدد اللغات في متصفح حقيقي لكل مكتبة، ثم سجلت ما ظهر بالفعل عبر الشبكة والوقت الذي استغرقته العمليات. يتم الإبلاغ عن الأحجام بعد ضغط الويب العادي ، لأن هذا أقرب لما يقوم الأشخاص بتنزيله بالفعل.
حجم مكتبة التدويل: بعد التجميع والضغط، حجم مكتبة i18n هو حجم المزودين (مثل
NextIntlClientProvider) + كود الخطافات (مثلuseTranslations) في مكون فارغ. لا يتضمن تحميل ملفات الترجمة. هذا يوضح مدى "ثقل" المكتبة قبل دخول المحتوى الخاص بك.JavaScript لكل صفحة: لكل مسار في المقارنة، مقدار السكربت الذي يسحبه المتصفح لزيارة تلك الصفحة، محسوبًا كمتوسط عبر صفحات الاختبار (وعبر اللغات). الصفحات الثقيلة هي صفحات بطيئة.
التسرب من اللغات الأخرى: هو محتوى نفس الصفحة ولكن بلغة أخرى يتم تحميله عن طريق الخطأ في الصفحة التي يتم فحصها. هذا المحتوى غير ضروري ويجب تجنبه (مثل محتوى صفحة
/fr/aboutفي حزمة صفحة/en/about).التسرب من المسارات الأخرى: نفس الفكرة لـ الشاشات الأخرى في التطبيق: ما إذا كانت نصوصها تظهر عندما فتحت صفحة واحدة فقط (مثل محتوى صفحة
/en/aboutفي حزمة صفحة/en/contact). تشير النتيجة العالية إلى ضعف التقسيم أو حزم واسعة بشكل مفرط.متوسط حجم حزمة المكون: يتم قياس مكونات واجهة المستخدم الشائعة واحدًا تلو الآخر بدلاً من الاختباء داخل رقم واحد ضخم للتطبيق. يوضح ما إذا كان التدويل يضخم المكونات اليومية بهدوء. على سبيل المثال، إذا أعيد رندرة المكون الخاص بك، فسيقوم بتحميل كل تلك البيانات من الذاكرة. ربط ملف JSON عملاق بأي مكون يشبه ربط مخزن كبير من البيانات غير المستخدمة التي ستبطئ أداء مكوناتك.
سرعة تبديل اللغة: أقوم بتبديل اللغة باستخدام عنصر التحكم الخاص بالتطبيق ذاته وأحسب الوقت الذي يستغرقه حتى تتبدل الصفحة بوضوح - وهو ما يلاحظه الزائر.
جهد الرندرة بعد تغيير اللغة: مراقبة أدق: مقدار الجهد الذي بذلته الواجهة لإعادة الرسم باللغة الجديدة بمجرد بدء التبديل. مفيد عندما يختلف الوقت "المحسوس" عن تكلفة إطار العمل.
وقت تحميل الصفحة الأولي: من الانتقال حتى اعتبار المتصفح أن الصفحة محملة بالكامل للسيناريوهات التي اختبرتها. جيد لمقارنة بدايات التشغيل الباردة (cold starts).
وقت الهيدرة (Hydration): عندما يوفره التطبيق، الوقت الذي يقضيه العميل في تحويل كود HTML من الخادم إلى شيء يمكنك النقر عليه فعليًا. تعني الشرطة في الجداول أن تلك المكتبة لم توفر رقمًا موثوقًا للهيدرة في هذه المقارنة.
نجوم GitHub
تعد نجوم GitHub مؤشرًا قويًا على شعبية المشروع وثقة المجتمع وأهميته على المدى الطويل. على الرغم من أنها ليست مقياسًا مباشرًا للجودة التقنية، إلا أنها تعكس عدد المطورين الذين يجدون المشروع مفيدًا ويتابعون تقدمه ومن المحتمل أن يتبنوه. لتقدير قيمة المشروع، تساعد النجوم في مقارنة الجاذبية عبر البدائل وتوفر رؤى حول نمو النظام البيئي.
النتائج بالتفصيل
1 - حلول يجب تجنبها
بعض الحلول، مثل gt-next أو lingo.dev ، يجب تجنبها بوضوح. فهي تجمع بين الارتباط الكامل بالبائع (vendor lock-in) وتشويه الكود الخاص بك. على الرغم من قضاء ساعات عديدة في محاولة تنفيذها، لم أتمكن أبدًا من جعلها تعمل - لا على TanStack Start ولا على Next.js.
المشكلات التي تمت مواجهتها:
(General Translation) ([email protected]):
- لتطبيق بحجم 110 كيلوبايت، تضيف
gt-nextأكثر من 440 كيلوبايت إضافية. Quota Exceeded, please upgrade your plan(تم تجاوز الحصة، يرجى ترقية خطتك) في أول بناء للمشروع.- لا يتم رندرة الترجمات؛ أحصل على خطأ
Error: <T> used on the client-side outside of <GTProvider>، والذي يبدو أنه خلل في المكتبة. - أثناء تنفيذ gt-next ، صادفت أيضًا مشكلة في المكتبة:
does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser، والتي كانت تؤدي إلى تعطل التطبيق. بعد الإبلاغ عن هذه المشكلة، قام المطور بإصلاحها خلال 24 ساعة. - المكتبة تمنع الرندرة الستاتيكية لصفحات Next.js.
(Lingo.dev) (@lingo.dev/[email protected]):
- تجاوز حصة الذكاء الاصطناعي، مما يمنع البناء تمامًا - لذا لا يمكنك الإطلاق للإنتاج دون دفع مبالغ مالية.
- كان المترجم (compiler) يفتقر إلى ما يقرب من 40% من المحتوى المترجم. اضطررت لإعادة كتابة جميع وظائف
.mapإلى كتل مكونات مسطحة لجعلها تعمل. - واجهة السطر البرمجي (CLI) الخاصة بهم مليئة بالأخطاء وكانت تقوم بإعادة ضبط ملف الإعدادات بدون سبب.
- عند البناء، قامت بمسح ملفات JSON التي تم إنشاؤها تمامًا عند إضافة محتوى جديد. ونتيجة لذلك، يمكن لعدد قليل من المفاتيح مسح أكثر من 300 مفتاح موجود.
2 - حلول تجريبية
(Wuchale) ([email protected]):
الفكرة وراء Wuchale مثيرة للاهتمام ولكنها ليست قابلة للتطبيق بعد. واجهت مشكلات في التفاعلية واضطررت لفرض إعادة رندرة المزود لجعل التطبيق يعمل. التوثيق أيضًا غير واضح تمامًا، مما يجعل البدء صعبًا.
(Paraglide) (@inlang/[email protected]):
يقدم Paraglide نهجًا مبتكرًا ومدروسًا جيدًا. ومع ذلك، في هذه المقارنة، لم تعمل ميزة Tree-shaking التي تروج لها الشركة في إعدادات Next.js أو TanStack Start الخاصة بي. سير العمل وتجربة المطور أكثر تعقيدًا من الخيارات الأخرى.
أنا شخصياً لا أحب الاضطرار إلى إعادة إنشاء ملفات JS قبل كل رفع للكود، مما يخلق خطراً دائماً لتضارب الدمج (merge conflicts). كما يبدو أن الأداة تركز على Vite أكثر من Next.js.
أخيرًا، مقارنة بالحلول الأخرى، لا يستخدم Paraglide مخزنًا (مثل React context) لجلب اللغة الحالية لرندرة المحتوى. لكل عقدة يتم تحليلها، سيطلب اللغة من localStorage / cookie وما إلى ذلك. يؤدي ذلك إلى تنفيذ منطق غير ضروري يؤثر على تفاعلية المكونات.
ملاحظة حول paraglide: يقوم الحل بحقن الكود في قاعدة الكود الخاصة بك للاستيراد، ونتيجة لذلك يكون مقياس 'حجم المكتبة' في تقرير المقارنة 0 تقريبًا. توليد الكود أمر جيد، لأن الدالة المستخدمة ستتضمن فقط المنطق الضروري (البادئة الكاملة مقابل عدم وجود بادئة، الكوكيز مقابل التخزين، إلخ). في المقابل، يقوم Intlayer بتنفيذ هذا التصفية عبر حقن متغيرات البيئة في البناء لإجبار المجمع على إزالة المحتوى حسب المنطق. بفضل هذا، ينتهي الأمر بـ paraglide و intlayer كحلول أخف بـ 6 إلى 10 مرات من i18next أو next-intl.
3 - حلول مقبولة
(Tolgee) (@tolgee/[email protected]):
يعالج Tolgee العديد من المشكلات المذكورة سابقًا. وجدت صعوبة في اعتماده أكثر من الأدوات المماثلة. لا يوفر سلامة الأنواع (type safety) ، مما يجعل اكتشاف المفاتيح المفقودة وقت البناء أكثر صعوبة. اضطررت لتغليف دوال Tolgee بدوالي الخاصة لإضافة ميزة اكتشاف المفاتيح المفقودة.
(Next Intl) ([email protected]):
يعد next-intl الخيار الأكثر رواجًا والحل الذي تدفعه أنظمة الذكاء الاصطناعي، ولكن في نظري بشكل خاطئ. البدء سهل، ولكن في الممارسة العملية، فإن التحسين للحد من التسرب معقد. إن الجمع بين التحميل الديناميكي + مساحات الأسماء + أنواع TypeScript يبطئ التطوير كثيرًا. الحزمة ثقيلة أيضًا (~13 كيلوبايت لـ NextIntlClientProvider + useTranslations ، وهو أكثر من ضعف next-intlayer). كان next-intl يمنع الرندرة الستاتيكية لصفحات Next.js. يوفر وظيفة مساعدة تسمى setRequestLocale(). يبدو أن هذا قد تمت معالجته جزئيًا للملفات المركزية مثل en.json / fr.json ، لكن الرندرة الستاتيكية لا تزال تتعطل عندما يتم تقسيم المحتوى إلى مساحات أسماء مثل en/shared.json / fr/shared.json / es/shared.json.
(Next I18next) ([email protected]):
ربما يكون next-i18next هو الخيار الأكثر شعبية لأنه كان من أوائل حلول i18n لتطبيقات JavaScript. يحتوي على العديد من الإضافات المجتمعية. يشارك نفس السلبيات الرئيسية مثل next-intl. الحزمة ثقيلة بشكل خاص (~18 كيلوبايت لـ I18nProvider + useTranslation ، حوالي 3 أضعاف next-intlayer).
تختلف تنسيقات الرسائل أيضًا: يستخدم next-intl خدمة ICU MessageFormat، بينما يستخدم i18next تنسيقه الخاص.
(Next International) ([email protected]):
يعالج next-international أيضًا المشكلات المذكورة أعلاه ولكنه لا يختلف كثيرًا عن next-intl أو next-i18next. يتضمن scopedT() للترجمات الخاصة بمساحة الأسماء، لكن استخدامه ليس له تأثير يذكر على حجم الحزمة.
(Lingui) (@lingui/[email protected]):
غالبًا ما يتم الإشادة بـ Lingui. أنا شخصياً وجدت سير عمل lingui extract / lingui compile أكثر تعقيدًا من البدائل، دون ميزة واضحة. لاحظت أيضًا صياغات غير متسقة تربك الذكاء الاصطناعي (مثل t() و t'' و i18n.t() و <Trans>).
4 - التوصيات
(Next Translate) ([email protected]):
خيار next-translate هو توصيتي الرئيسية إذا كنت تحب واجهة برمجية بأسلوب t(). إنه أنيق عبر next-translate-plugin ، حيث يحمل مساحات الأسماء من خلال getStaticProps مع مجمع Webpack / Turbopack. إنه أيضًا الخيار الأخف هنا (~2.5 كيلوبايت). بالنسبة لمساحات الأسماء، فإن تحديدها لكل صفحة أو مسار في الإعدادات مدروس جيدًا وأسهل في الصيانة من البدائل الرئيسية مثل next-intl أو next-i18next. في الإصدار 3.1.2 ، لاحظت أن الرندرة الستاتيكية لم تعمل؛ حيث تراجع Next.js إلى الرندرة الديناميكية.
(Intlayer) ([email protected]):
لن أحكم شخصيًا على next-intlayer من أجل الموضوعية، لأنه حلي الخاص.
ملاحظة شخصية
هذه الملاحظة شخصية ولا تؤثر على نتائج المقارنة. في عالم i18n، غالبًا ما ترى إجماعًا حول نمط const t = useTranslation('xx') + <>{t('xx.xx')}</>.
في تطبيقات React، يعد حقن دالة كـ ReactNode ، في نظري، نمطًا مضادًا (anti-pattern). كما أنه يضيف تعقيدًا يمكن تجنبه وعبئًا على تنفيذ JavaScript (حتى لو كان غير ملحوظ تقريبًا).