التوجيه الدولي (i18n)

أمثلة

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

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

البدء

للبدء، أضف تكوين 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>
  )
}

لاحظ أنه للتعامل مع تبديل اللغة فقط مع الحفاظ على جميع معلومات التوجيه مثل قيم استعلام المسار الديناميكي أو قيم استعلام 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 تجاوز رأس accept-language باستخدام ملف تعريف الارتباط NEXT_LOCALE=the-locale. يمكن تعيين هذا الملف باستخدام مبدل اللغة ثم عندما يعود المستخدم إلى الموقع، سيستفيد من اللغة المحددة في الملف عند إعادة التوجيه من / إلى موقع اللغة الصحيح.

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

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

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

لا يعرف Next.js عن متغيرات الصفحة، لذا فإن الأمر يعود إليك لإضافة علامات meta 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 نسخة من الصفحات الـ 10 أثناء كل بناء.

لتقليل وقت بناء الصفحات الديناميكية مع 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 }، سيستلم مكون المدونة
  // `posts` كخاصية أثناء وقت البناء
  return {
    props: {
      posts,
    },
  }
}

حدود تكوين i18n

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

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