كيفية تنفيذ التدويل (Internationalization) في Next.js

أمثلة

يوفر Next.js دعمًا مدمجًا لتوجيه التدويل (i18n) منذ الإصدار v10.0.0. يمكنك توفير قائمة باللغات، اللغة الافتراضية، ولغات محددة للنطاقات، وسيتولى Next.js توجيه الطلبات تلقائيًا.

يدعم توجيه i18n حاليًا حلول مكتبات التدويل الحالية مثل react-intl، react-i18next، lingui، rosetta، next-intl، next-translate، next-multilingual، tolgee، paraglide-next، next-intlayer وغيرها من خلال تبسيط المسارات وتحليل اللغة.

البدء

للبدء، أضف تكوين i18n إلى ملف next.config.js الخاص بك.

اللغات هي معرفات لغة UTS، وهي تنسيق معياري لتعريف اللغات.

بشكل عام، يتكون معرف اللغة من لغة ومنطقة ونص مفصولة بشرطة: language-region-script. المنطقة والنص اختيارية. مثال:

  • en-US - الإنجليزية كما تُستخدم في الولايات المتحدة
  • nl-NL - الهولندية كما تُستخدم في هولندا
  • nl - الهولندية بدون منطقة محددة

إذا كانت لغة المستخدم هي nl-BE ولم تكن مدرجة في التكوين الخاص بك، فسيتم توجيههم إلى nl إذا كانت متاحة، أو إلى اللغة الافتراضية بخلاف ذلك. إذا كنت لا تخطط لدعم جميع مناطق بلد ما، فمن الجيد تضمين لغات البلد التي ستكون بمثابة احتياطي.

next.config.js
module.exports = {
  i18n: {
    // هذه هي جميع اللغات التي تريد دعمها في تطبيقك
    locales: ['en-US', 'fr', 'nl-NL'],
    // هذه هي اللغة الافتراضية التي تريد استخدامها عند زيارة
    // مسار بدون بادئة لغة مثل `/hello`
    defaultLocale: 'en-US',
    // هذه قائمة بنطاقات اللغة واللغة الافتراضية التي
    // يجب أن تتعامل معها (مطلوبة فقط عند إعداد توجيه النطاق)
    // ملاحظة: يجب تضمين النطاقات الفرعية في قيمة النطاق لمطابقتها مثل "fr.example.com".
    domains: [
      {
        domain: 'example.com',
        defaultLocale: 'en-US',
      },
      {
        domain: 'example.nl',
        defaultLocale: 'nl-NL',
      },
      {
        domain: 'example.fr',
        defaultLocale: 'fr',
        // يمكن أيضًا استخدام حقل http اختياري لاختبار
        // نطاقات اللغة محليًا باستخدام http بدلاً من https
        http: true,
      },
    ],
  },
}

استراتيجيات اللغة

هناك استراتيجيتان للتعامل مع اللغة: توجيه المسار الفرعي وتوجيه النطاق.

توجيه المسار الفرعي

يضع توجيه المسار الفرعي اللغة في مسار URL.

next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'fr', 'nl-NL'],
    defaultLocale: 'en-US',
  },
}

مع التكوين أعلاه، ستكون en-US و fr و nl-NL متاحة للتوجيه إليها، و en-US هي اللغة الافتراضية. إذا كان لديك pages/blog.js، فستكون عناوين URL التالية متاحة:

  • /blog
  • /fr/blog
  • /nl-nl/blog

اللغة الافتراضية ليس لها بادئة.

توجيه النطاق

باستخدام توجيه النطاق، يمكنك تكوين اللغات ليتم تقديمها من نطاقات مختلفة:

next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'],
    defaultLocale: 'en-US',

    domains: [
      {
        // ملاحظة: يجب تضمين النطاقات الفرعية في قيمة النطاق لمطابقتها
        // مثل www.example.com يجب استخدامها إذا كان هذا هو اسم المضيف المتوقع
        domain: 'example.com',
        defaultLocale: 'en-US',
      },
      {
        domain: 'example.fr',
        defaultLocale: 'fr',
      },
      {
        domain: 'example.nl',
        defaultLocale: 'nl-NL',
        // تحديد اللغات الأخرى التي يجب إعادة توجيهها
        // إلى هذا النطاق
        locales: ['nl-BE'],
      },
    ],
  },
}

على سبيل المثال، إذا كان لديك pages/blog.js، فستكون عناوين URL التالية متاحة:

  • example.com/blog
  • www.example.com/blog
  • example.fr/blog
  • example.nl/blog
  • example.nl/nl-BE/blog

الكشف التلقائي عن اللغة

عندما يزور المستخدم جذر التطبيق (عادة /)، سيحاول Next.js اكتشاف اللغة التي يفضلها المستخدم تلقائيًا بناءً على رأس Accept-Language والنطاق الحالي.

إذا تم اكتشاف لغة غير اللغة الافتراضية، فسيتم إعادة توجيه المستخدم إلى:

  • عند استخدام توجيه المسار الفرعي: المسار مع بادئة اللغة
  • عند استخدام توجيه النطاق: النطاق مع تلك اللغة المحددة كلغة افتراضية

عند استخدام توجيه النطاق، إذا زار مستخدم برأس Accept-Language بقيمة fr;q=0.9 الموقع example.com، فسيتم إعادة توجيهه إلى example.fr حيث أن هذا النطاق يتعامل مع اللغة fr افتراضيًا.

عند استخدام توجيه المسار الفرعي، سيتم إعادة توجيه المستخدم إلى /fr.

إضافة بادئة للغة الافتراضية

مع Next.js 12 والوسيط (Middleware)، يمكننا إضافة بادئة للغة الافتراضية باستخدام حل بديل.

على سبيل المثال، إليك ملف next.config.js مع دعم لعدد من اللغات. لاحظ أن اللغة "default" قد تمت إضافتها عمدًا.

next.config.js
module.exports = {
  i18n: {
    locales: ['default', 'en', 'de', 'fr'],
    defaultLocale: 'default',
    localeDetection: false,
  },
  trailingSlash: true,
}

بعد ذلك، يمكننا استخدام الوسيط (Middleware) لإضافة قواعد توجيه مخصصة:

middleware.ts
import { NextRequest, NextResponse } from 'next/server'

const PUBLIC_FILE = /\.(.*)$/

export async function middleware(req: NextRequest) {
  if (
    req.nextUrl.pathname.startsWith('/_next') ||
    req.nextUrl.pathname.includes('/api/') ||
    PUBLIC_FILE.test(req.nextUrl.pathname)
  ) {
    return
  }

  if (req.nextUrl.locale === 'default') {
    const locale = req.cookies.get('NEXT_LOCALE')?.value || 'en'

    return NextResponse.redirect(
      new URL(`/${locale}${req.nextUrl.pathname}${req.nextUrl.search}`, req.url)
    )
  }
}

يتخطى هذا الوسيط (Middleware) إضافة البادئة الافتراضية إلى مسارات API وملفات العامة مثل الخطوط أو الصور. إذا تم إجراء طلب إلى اللغة الافتراضية، نقوم بإعادة التوجيه إلى البادئة /en.

تعطيل الكشف التلقائي عن اللغة

يمكن تعطيل الكشف التلقائي عن اللغة باستخدام:

next.config.js
module.exports = {
  i18n: {
    localeDetection: false,
  },
}

عند تعيين localeDetection على false، لن يقوم Next.js بعد الآن بإعادة التوجيه تلقائيًا بناءً على اللغة المفضلة للمستخدم وسيوفر فقط معلومات اللغة المكتشفة إما من نطاق اللغة أو مسار اللغة كما هو موضح أعلاه.

الوصول إلى معلومات اللغة

يمكنك الوصول إلى معلومات اللغة عبر موجه Next.js. على سبيل المثال، باستخدام خطاف useRouter()، تكون الخصائص التالية متاحة:

  • locale تحتوي على اللغة النشطة حاليًا.
  • locales تحتوي على جميع اللغات المكونة.
  • defaultLocale تحتوي على اللغة الافتراضية المكونة.

عند التقديم المسبق للصفحات باستخدام getStaticProps أو getServerSideProps، يتم توفير معلومات اللغة في السياق المقدم للدالة.

عند الاستفادة من getStaticPaths، يتم توفير اللغات المكونة في معلمة السياق للدالة تحت locales واللغة الافتراضية المكونة تحت defaultLocale.

الانتقال بين اللغات

يمكنك استخدام next/link أو next/router للانتقال بين اللغات.

بالنسبة لـ next/link، يمكن توفير خاصية locale للانتقال إلى لغة مختلفة عن اللغة النشطة حاليًا. إذا لم يتم توفير خاصية locale، فسيتم استخدام اللغة النشطة حاليًا locale أثناء انتقالات العميل. على سبيل المثال:

import Link from 'next/link'

export default function IndexPage(props) {
  return (
    <Link href="/another" locale="fr">
      إلى /fr/another
    </Link>
  )
}

عند استخدام طرق next/router مباشرة، يمكنك تحديد اللغة locale التي يجب استخدامها عبر خيارات الانتقال. على سبيل المثال:

import { useRouter } from 'next/router'

export default function IndexPage(props) {
  const router = useRouter()

  return (
    <div
      onClick={() => {
        router.push('/another', '/another', { locale: 'fr' })
      }}
    >
      إلى /fr/another
    </div>
  )
}

لاحظ أنه للتعامل مع تبديل اللغة locale فقط مع الحفاظ على جميع معلومات التوجيه مثل قيم استعلام المسار الديناميكي أو قيم استعلام href المخفية، يمكنك توفير معلمة href ككائن:

import { useRouter } from 'next/router'
const router = useRouter()
const { pathname, asPath, query } = router
// تغيير اللغة فقط مع الحفاظ على جميع معلومات المسار الأخرى بما في ذلك استعلام href
router.push({ pathname, query }, asPath, { locale: nextLocale })

راجع هنا لمزيد من المعلومات حول هيكل الكائن لـ router.push.

إذا كان لديك href يتضمن بالفعل اللغة، فيمكنك إلغاء التعامل التلقائي مع بادئة اللغة:

import Link from 'next/link'

export default function IndexPage(props) {
  return (
    <Link href="/fr/another" locale={false}>
      إلى /fr/another
    </Link>
  )
}

الاستفادة من ملف تعريف الارتباط NEXT_LOCALE

يسمح Next.js بتعيين ملف تعريف ارتباط NEXT_LOCALE=the-locale، والذي يأخذ الأولوية فوق رأس accept-language. يمكن تعيين ملف تعريف الارتباط هذا باستخدام مبدل اللغة ثم عندما يعود المستخدم إلى الموقع، سوف يستفيد من اللغة المحددة في ملف تعريف الارتباط عند إعادة التوجيه من / إلى موقع اللغة الصحيح.

على سبيل المثال، إذا كان المستخدم يفضل اللغة fr في رأس accept-language الخاص به ولكن تم تعيين ملف تعريف ارتباط NEXT_LOCALE=en، فسيتم توجيه المستخدم إلى موقع اللغة en عند زيارة / حتى يتم إزالة ملف تعريف الارتباط أو انتهاء صلاحيته.

تحسين محركات البحث (SEO)

نظرًا لأن Next.js يعرف اللغة التي يزورها المستخدم، فسيضيف تلقائيًا سمة lang إلى علامة <html>.

لا يعرف Next.js عن متغيرات الصفحة، لذا فإن الأمر يعود إليك لإضافة علامات hreflang باستخدام next/head. يمكنك معرفة المزيد عن hreflang في توثيق Google Webmasters.

كيف يعمل هذا مع التوليد الثابت؟

لاحظ أن توجيه التدويل لا يتكامل مع output: 'export' لأنه لا يستفيد من طبقة توجيه Next.js. يتم دعم تطبيقات Next.js الهجينة التي لا تستخدم output: 'export' بالكامل.

المسارات الديناميكية وصفحات getStaticProps

بالنسبة للصفحات التي تستخدم getStaticProps مع المسارات الديناميكية، يجب إرجاع جميع متغيرات اللغة للصفحة المراد تقديمها مسبقًا من getStaticPaths. إلى جانب كائن params الذي تم إرجاعه لـ paths، يمكنك أيضًا إرجاع حقل locale يحدد اللغة التي تريد تقديمها. على سبيل المثال:

pages/blog/[slug].js
export const getStaticPaths = ({ locales }) => {
  return {
    paths: [
      // إذا لم يتم توفير `locale`، فسيتم إنشاء اللغة الافتراضية فقط
      { params: { slug: 'post-1' }, locale: 'en-US' },
      { params: { slug: 'post-1' }, locale: 'fr' },
    ],
    fallback: true,
  }
}

بالنسبة للصفحات المحسنة ثابتًا تلقائيًا وصفحات getStaticProps غير الديناميكية، سيتم إنشاء نسخة من الصفحة لكل لغة. هذا مهم للنظر فيه لأنه يمكن أن يزيد من أوقات البناء اعتمادًا على عدد اللغات المكونة داخل getStaticProps.

على سبيل المثال، إذا كان لديك 50 لغة مكونة مع 10 صفحات غير ديناميكية تستخدم getStaticProps، فهذا يعني أن getStaticProps سيتم استدعاؤه 500 مرة. سيتم إنشاء 50 نسخة من الصفحات العشرة أثناء كل بناء.

لتقليل وقت بناء الصفحات الديناميكية مع getStaticProps، استخدم وضع fallback. هذا يسمح لك بإرجاع المسارات واللغات الأكثر شيوعًا فقط من getStaticPaths للتقديم المسبق أثناء البناء. بعد ذلك، سيقوم Next.js ببناء الصفحات المتبقية في وقت التشغيل عند طلبها.

الصفحات المحسنة ثابتًا تلقائيًا

بالنسبة للصفحات التي يتم تحسينها ثابتًا تلقائيًا، سيتم إنشاء نسخة من الصفحة لكل لغة.

صفحات getStaticProps غير الديناميكية

بالنسبة لصفحات getStaticProps غير الديناميكية، يتم إنشاء نسخة لكل لغة كما هو مذكور أعلاه. يتم استدعاء getStaticProps مع كل locale يتم تقديمها. إذا كنت ترغب في استبعاد لغة معينة من التقديم المسبق، فيمكنك إرجاع notFound: true من getStaticProps ولن يتم إنشاء هذا المتغير من الصفحة.

export async function getStaticProps({ locale }) {
  // استدعاء نقطة نهاية API خارجية للحصول على المشاركات.
  // يمكنك استخدام أي مكتبة لجلب البيانات
  const res = await fetch(`https://.../posts?locale=${locale}`)
  const posts = await res.json()

  if (posts.length === 0) {
    return {
      notFound: true,
    }
  }

  // بإرجاع { props: posts }، سيستلم مكون Blog
  // `posts` كخاصية في وقت البناء
  return {
    props: {
      posts,
    },
  }
}

حدود تكوين i18n

  • locales: 100 لغة إجمالية
  • domains: 100 عنصر نطاق لغة إجمالي

من الجيد معرفة: تمت إضافة هذه الحدود في البداية لمنع مشكلات الأداء المحتملة في وقت البناء. يمكنك تجاوز هذه الحدود باستخدام توجيه مخصص باستخدام الوسيط (Middleware) في Next.js 12.