التوجيه الدولي (i18n) للروابط

أمثلة

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

يهدف دعم توجيه i18n حاليًا إلى تكامل مع حلول مكتبات i18n الحالية مثل react-intl، react-i18next، lingui، rosetta، next-intl، next-translate، next-multilingual، 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,
      },
    ],
  },
}

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

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

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

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

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 فستكون الروابط التالية متاحة:

  • /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 فستكون الروابط التالية متاحة:

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

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

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

بما أن 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.