الوجهات العالمية 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.
أمثلة
المصادر
- الصفحة Internationalized Routing من توثيق Next.js الرسمي.