---
createdAt: 2026-04-20
updatedAt: 2026-04-21
title: أفضل حل i18n لـ Next.js في 2026 - تقرير المقارنة
description: قارن بين مكتبات تدويل (i18n) Next.js مثل next-intl و next-i18next و Intlayer. تقرير مفصل للأداء حول حجم الحزمة، التسرب، والتفاعلية.
keywords:
- benchmark
- i18n
- intl
- nextjs
- performance
- intlayer
slugs:
- doc
- benchmark
- nextjs
author: Aymeric PINEAU
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n
history:
- version: 8.7.5
date: 2026-01-06
changes: "بدء المقارنة"
---
# مكتبات i18n لـ Next.js — تقرير مقارنة 2026
هذه الصفحة عبارة عن تقرير مقارنة لحلول i18n على Next.js.
## جدول المحتويات
## مقارنة تفاعلية
## مرجع النتائج:
> https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.md
شاهد مستودع المقارنة الكامل [هنا](https://github.com/intlayer-org/benchmark-i18n).
## مقدمة
مكتبات التدويل لها تأثير كبير على تطبيقك. الخطر الرئيسي هو تحميل المحتوى لجميع الصفحات وجميع اللغات بينما يزور المستخدم صفحة واحدة فقط.
مع نمو تطبيقك، يمكن أن ينمو حجم الحزمة بشكل كبير، مما قد يضر بالأداء بشكل ملحوظ.
على سبيل المثال، بالنسبة لأسوأ الحالات، يمكن أن تصبح صفحتك بعد تدويلها أكبر بـ 4 مرات تقريبًا.
تأثير آخر لمكتبات i18n هو بطء التطوير. تحويل المكونات إلى محتوى متعدد اللغات يستغرق وقتًا طويلاً.
بما أن المشكلة صعبة، توجد العديد من الحلول — بعضها يركز على تجربة المطور (DX)، والبعض الآخر على الأداء أو القابلية للتوسع، وهكذا.
يحاول Intlayer التحسين عبر هذه الأبعاد.
## اختبر تطبيقك
لتسليط الضوء على هذه المشكلات، قمت ببناء ماسح ضوئي مجاني يمكنك تجربته [هنا](https://intlayer.org/i18n-seo-scanner).
## المشكلة
هناك طريقتان رئيستان للحد من تأثير التطبيق متعدد اللغات على حجم الحزمة الخاص بك:
- تقسيم ملفات 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.5)
- `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` (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 من الخادم إلى شيء يمكنك النقر عليه فعليًا. تعني الشرطة في الجداول أن تلك المكتبة لم توفر رقمًا موثوقًا للهيدرة في هذه المقارنة.
## النتائج بالتفصيل
### 1 — حلول يجب تجنبها
بعض الحلول، مثل `gt-next` أو `lingo.dev` ، يجب تجنبها بوضوح. فهي تجمع بين الارتباط الكامل بالبائع (vendor lock-in) وتشويه الكود الخاص بك. على الرغم من قضاء ساعات عديدة في محاولة تنفيذها، لم أتمكن أبدًا من جعلها تعمل — لا على TanStack Start ولا على Next.js.
المشكلات التي تمت مواجهتها:
**(General Translation)** (`gt-next@6.16.5`):
- لتطبيق بحجم 110 كيلوبايت، تضيف `gt-react` أكثر من 440 كيلوبايت إضافية.
- `Quota Exceeded, please upgrade your plan` (تم تجاوز الحصة، يرجى ترقية خطتك) في أول بناء للمشروع.
- لا يتم رندرة الترجمات؛ أحصل على خطأ `Error: used on the client-side outside of ` ، والذي يبدو أنه خلل في المكتبة.
- أثناء تنفيذ **gt-tanstack-start-react** ، صادفت أيضًا [مشكلة](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) في المكتبة: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser` ، والتي كانت تؤدي إلى تعطل التطبيق. بعد الإبلاغ عن هذه المشكلة، قام المطور بإصلاحها خلال 24 ساعة.
- المكتبة تمنع الرندرة الستاتيكية لصفحات Next.js.
**(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):
- تجاوز حصة الذكاء الاصطناعي، مما يمنع البناء تمامًا — لذا لا يمكنك الإطلاق للإنتاج دون دفع مبالغ مالية.
- كان المترجم (compiler) يفتقر إلى ما يقرب من 40% من المحتوى المترجم. اضطررت لإعادة كتابة جميع وظائف `.map` إلى كتل مكونات مسطحة لجعلها تعمل.
- واجهة السطر البرمجي (CLI) الخاصة بهم مليئة بالأخطاء وكانت تقوم بإعادة ضبط ملف الإعدادات بدون سبب.
- عند البناء، قامت بمسح ملفات JSON التي تم إنشاؤها تمامًا عند إضافة محتوى جديد. ونتيجة لذلك، يمكن لعدد قليل من المفاتيح مسح أكثر من 300 مفتاح موجود.
### 2 — حلول تجريبية
**(Wuchale)** (`wuchale@0.22.11`):
الفكرة وراء `Wuchale` مثيرة للاهتمام ولكنها ليست قابلة للتطبيق بعد. واجهت مشكلات في التفاعلية واضطررت لفرض إعادة رندرة المزود لجعل التطبيق يعمل. التوثيق أيضًا غير واضح تمامًا، مما يجعل البدء صعبًا.
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
يقدم `Paraglide` نهجًا مبتكرًا ومدروسًا جيدًا. ومع ذلك، في هذه المقارنة، لم تعمل ميزة Tree-shaking التي تروج لها الشركة في إعدادات Next.js أو TanStack Start الخاصة بي. سير العمل وتجربة المطور أكثر تعقيدًا من الخيارات الأخرى.
أنا شخصياً لا أحب الاضطرار إلى إعادة إنشاء ملفات JS قبل كل رفع للكود، مما يخلق خطراً دائماً لتضارب الدمج (merge conflicts). كما يبدو أن الأداة تركز على Vite أكثر من Next.js.
أخيرًا، مقارنة بالحلول الأخرى، لا يستخدم Paraglide مخزنًا (مثل React context) لجلب اللغة الحالية لرندرة المحتوى. لكل عقدة يتم تحليلها، سيطلب اللغة من localStorage / cookie وما إلى ذلك. يؤدي ذلك إلى تنفيذ منطق غير ضروري يؤثر على تفاعلية المكونات.
### 3 — حلول مقبولة
**(Tolgee)** (`tolgee@7.0.0`):
يعالج `Tolgee` العديد من المشكلات المذكورة سابقًا. وجدت صعوبة في اعتماده أكثر من الأدوات المماثلة. لا يوفر سلامة الأنواع (type safety) ، مما يجعل اكتشاف المفاتيح المفقودة وقت البناء أكثر صعوبة. اضطررت لتغليف دوال Tolgee بدوالي الخاصة لإضافة ميزة اكتشاف المفاتيح المفقودة.
**(Next Intl)** (`next-intl@4.9.1`):
يعد `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)** (`next-i18next@16.0.5`):
ربما يكون `next-i18next` هو الخيار الأكثر شعبية لأنه كان من أوائل حلول i18n لتطبيقات JavaScript. يحتوي على العديد من الإضافات المجتمعية. يشارك نفس السلبيات الرئيسية مثل `next-intl`. الحزمة ثقيلة بشكل خاص (~18 كيلوبايت لـ `I18nProvider` + `useTranslation` ، حوالي 3 أضعاف `next-intlayer`).
تختلف تنسيقات الرسائل أيضًا: يستخدم `next-intl` خدمة ICU MessageFormat، بينما يستخدم `i18next` تنسيقه الخاص.
**(Next International)** (`next-international@1.3.1`):
يعالج `next-international` أيضًا المشكلات المذكورة أعلاه ولكنه لا يختلف كثيرًا عن `next-intl` أو `next-i18next`. يتضمن `scopedT()` للترجمات الخاصة بمساحة الأسماء، لكن استخدامه ليس له تأثير يذكر على حجم الحزمة.
**(Lingui)** (`@lingui/core@5.3.0`):
غالبًا ما يتم الإشادة بـ `Lingui`. أنا شخصياً وجدت سير عمل `lingui extract` / `lingui compile` أكثر تعقيدًا من البدائل، دون ميزة واضحة. لاحظت أيضًا صياغات غير متسقة تربك الذكاء الاصطناعي (مثل `t()` و `t''` و `i18n.t()` و ``).
### 4 — التوصيات
**(Next Translate)** (`next-translate@3.1.2`):
خيار `next-translate` هو توصيتي الرئيسية إذا كنت تحب واجهة برمجية بأسلوب `t()`. إنه أنيق عبر `next-translate-plugin` ، حيث يحمل مساحات الأسماء من خلال `getStaticProps` مع مجمع Webpack / Turbopack. إنه أيضًا الخيار الأخف هنا (~2.5 كيلوبايت). بالنسبة لمساحات الأسماء، فإن تحديدها لكل صفحة أو مسار في الإعدادات مدروس جيدًا وأسهل في الصيانة من البدائل الرئيسية مثل **next-intl** أو **next-i18next**. في الإصدار `3.1.2` ، لاحظت أن الرندرة الستاتيكية لم تعمل؛ حيث تراجع Next.js إلى الرندرة الديناميكية.
**(Intlayer)** (`next-intlayer@8.7.5`):
لن أحكم شخصيًا على `next-intlayer` من أجل الموضوعية، لأنه حلي الخاص.
### ملاحظة شخصية
هذه الملاحظة شخصية ولا تؤثر على نتائج المقارنة. في عالم i18n، غالبًا ما ترى إجماعًا حول نمط `const t = useTranslation('xx')` + `<>{t('xx.xx')}>`.
في تطبيقات React، يعد حقن دالة كـ `ReactNode` ، في نظري، نمطًا مضادًا (anti-pattern). كما أنه يضيف تعقيدًا يمكن تجنبه وعبئًا على تنفيذ JavaScript (حتى لو كان غير ملحوظ تقريبًا).