الوجهات العالمية i18n routing في Next.js

من موسوعة حسوب

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

إن القصد من دعم التوجيه i18n حاليًا هو إضافة حلول المكتبة i18n مثل react-intl و react-i18next و lingui و rosetta و next-intl وغيرها من خلال تبسيط الوجهات وتفسيرها وفق النطاق المحلي.

البدء مع وجهات i18n

أضف حقل التهيئة i18n إلى الملف next.config.js. وتُعدُّ الموراد المحلية locales عبارة عن معرّفات UTS وفق تنسيق معياري تُستخدم لتعريف إعداد محلي. يتألف المعرّف المحلي عادة من لغة ومنطقة ولهجة تفصل بينها شرطة (-) كالتالي language-region-script وتحديد المنطقة region واللهجة script أمر اختياري. إليك بعض الأمثلة:

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

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

// 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 يمكن استخدام حقل 
        // https بدلًا من  http باستخدام 
        http: true,
      },
    ],
  },
}

استراتيجات التعامل مع الإعدادات المحلية

هنالك استراتيجيتين أساسيتين هما: التوجه بمسارات فرعية Sub-path Routing والتوجه عبر النطاقات Domain Routing.

التوجه باستخدام المسارات الفرعية

يضع المسار الفرعي الإعداد المحلي ضمن عنوان 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) والبرمجيات الوسطية. لنتأمل على سبيل المثال الملف next.config.js الذي يدعم عدة لغات وقد أُضيف الإعداد المحلي "default" عمدًا:

// next.config.js

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

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

// 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') || 'en'

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

تتجاوز البرمجية الوسطية إضافة البادئة الافتراضية إلى مسار API وإلى الملفات العامة مثل الخطوط والصور. وإن وصل طلب إلى الإعداد المحلي الافتراضي سيُعاد توجيهه إلى البادئة en/.

تعطيل ميزة الكشف التلقائي عن الإعداد المحلي

يمكن تعطيل ميزة الكشف التلقائي عن الإعدادات المحلية كالتالي:

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

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

الوصول إلى المعلومات المحلية

يمكن الوصول إلى الإعدادت المحلية من خلال موجّه Next.js. استخدم مثلًا الخطاف ()useRouter الذي يتيح لك الخاصيات التالية:

  • locale: يعيد الإعداد المحلي المُفعَّل.
  • locales: ويتضمن كل اللغات المهيأة.
  • defaultLocale: ويتضمن كل الإعداد المحلي الافتراضي المهيأ.

عند التصيير المسبق للصفحات باستخدام getStaticProps أو getServerSideProps، ستجد المعلومات المحلية ضمن السياق الذي تُزوَّد به هاتين الدالتين.

تزوّد الدالة عند استخدامها بالإعدادات المحلية من خلال معامل السياق الخاص بالدالة والخاصية locales أو الخاصية defaultLocale في حال أردت تمرير الإعدادات المحلية الافتراضية.

الانتقال بين الإعدادات المحلية

بإمكانك استخدام المكوّنين next/link أو next/router للتنقل بين الإعدادات المحلية، إذ يمكن تزويد المكوّن next/link بالخاصية locale وذلك للانتقال إلى إعدادات محلية أخرى انطلاقًا من الحالية. وإن لم يزوّد بهذه الخاصية، تُستخدم الإعدادات الحالية أثناء الإنتقال ما بين الصفحات في جانب العميل. إليك مثالًا:

import Link from 'next/link'

export default function IndexPage(props) {
  return (
    <Link href="/another" locale="fr">
      <a>To /fr/another</a>
    </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' })
      }}
    >
      to /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}>
      <a>To /fr/another</a>
    </Link>
  )
}

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

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

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

تحدد Next.js اللغة التي استخدمها الزائر ولهذا ستضيف السمة lang تلقائيًا إلى العنصر <html>. لكنها لا تعرف النسخ المختلفة من الصفحة (التي تدعم إعدادات محلية مختلفة) لهذا يقع على عاتقك إضافة البيانات الوصفية hreflang باستخدام المكوّن next/head. راجع توثيق Google Webmasters لمعلومات أكثر عن hreflang.

الإعدادات المحلية والتوليد الساكن في Next.js

لا يمكن دمج الوجهات العالمية مع المكوّن next export لأنه لا يعدّل في طبقة التوجّه الخاصة باللغة. لهذا تُدعم تطبيقات Next.js الهجينة التي لا تستخدم next export بشكل كامل.

الوجهات الديناميكية والصفحات التي تستخدم getStaticProps

لا بد من إعادة جميع نسخ الصفحات ذات الإعدادات المحلية المختلفة من الدالة getStaticPaths وذلك في حال استخدمت هذه الصفحات الدالة getStaticProps مع الوجهات الديناميكية. بإمكانك إعادة الحقل locale مع الكائن params الخاص بالمسارات paths لكي تحدد الإعداد المحلي الذي ترغب بتصييره.

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

أما بالنسبة للصفحات غير الديناميكية التي تستخدم getStaticProps، ولأغراض التحسين التلقائي الساكن، تولَّد نسخة من الصفحة لكل إعداد محلي. ومن المهم معرفة ذلك، لأنه سيزيد من وقت البناء وفقًا لعدد الإعدادات المحلية التي هُيِّئت ضمن الدالة getStaticProps. فإن هيّأت مثلًا 50 إعداد محلي لعشر صفحات ليست ديناميكية باستخدام getStaticProps فهذا يعني استدعاء الدالة getStaticProps قرابة 500 مرة، أي 50 نسخة لكل صفحة من الصفحات العشر خلال زمن البناء.

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

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

لأغراض التحسين التلقائي الساكن، تولَّد نسخة من الصفحة لكل إعداد محلي.

الصفحات غير الديناميكية التي تستخدم 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 سيتلقى المكوّن
  // المنشورات كخاصية في زمن البناء
  return {
    props: {
      posts,
    },
  }
}

محدودية إعدادات i18n

  • locales: تتسع فقط 100 إعداد محلي.
  • domains: تتسع فقط 100 نطاق محلي.

ملاحظة: أضيفت هذه الحدود عمدًا لمنع مشاكل الأداء أثناء بناء التطبيق. بإمكانك الإلتفاف على هذه المحدودية باستخدام آلية توجّه مخصص باستخدام البرمجيات الوسطية ابتداءً من النسخة 12 للغة Next.js.

أمثلة

المصادر