بسبب التحولات في التركيز والأولويات ، لم يعد بإمكاننا الحفاظ على هذه الحزمة. لن يتلقى التحديثات أو إصلاحات الأخطاء أو الميزات الجديدة وقد تصبح غير متوافقة مع مرور الوقت. نوصي بالتبديل إلى الحزم التي تدعم أحدث دليل تطبيق Next.js.
next-multilingual هو حل من طرف إلى طرف لتطبيقات next.js ✱ يتطلب لغات متعددة.
تحقق من التطبيق التجريبي لدينا!
✱ يعمل مع next-multilingual فقط؟ دليل pages . ما زلنا نخوض حلنا لدعم الجديد؟ دليل app منذ التدويل لم يعد جزءًا من تكوين Next.js.
npm install next-multilingual
useMessages قوي يدعم ICU MessageFormat و JSX حقن خارج الصندوق./en-us/contact-us بالنسبة للولايات المتحدة الإنجليزية و /fr-ca/nous-joindre للفرنسية الكندية) ✱ . ✱ يجب أن تتطابق الرخويات المحلية الافتراضية الخاصة بك مع نظام ملفات دليل pages (على سبيل المثال ، يجب أن يكون سبيكة لـ "About Us" في دليل about-us ). إذا كانت اللغة الافتراضية التي تحتاجها تستخدم أحرفًا تتجاوز تلك التي تدعمها نظام الملفات ، فلن يتم اختبارها ومن المحتمل ألا تعمل. طلبات السحب موضع ترحيب؟
لقد بذل next-multilingual الكثير من الجهد لإضافة TSDOC إلى جميع واجهات برمجة التطبيقات الخاصة به. يرجى التحقق مباشرة من IDE الخاص بك إذا كنت غير متأكد من كيفية استخدام واجهات برمجة تطبيقات معينة المقدمة في أمثلةنا.
أيضا ، وجود رأي في "أفضل الممارسات" ليس مهمة سهلة. لهذا السبب قمنا بتوثيق قرارات التصميم الخاصة بنا في وثيقة خاصة يمكن استشارتها هنا. إذا شعرت أن بعض واجهات برمجة التطبيقات الخاصة بنا لا تقدم ما تتوقعه ، فتأكد من استشارة هذا المستند قبل فتح مشكلة.
بالنسبة لأولئك الذين يفضلون القفز مباشرة إلى الإجراء ، ابحث في دليل example للتنفيذ الشامل next-multilingual . بالنسبة للباقي ، سيوفر القسم أدناه دليلًا كاملاً خطوة بخطوة.
هناك العديد من الخيارات لتكوينها في Next.js لتحقيق أهدافنا. يهتم next-multilingual في الغالب بالتهديد:
نحن نقدم اثنين من واجهات برمجة التطبيقات لتبسيط هذه الخطوة:
getConfig (تكوين بسيط) ستقوم هذه الوظيفة بإنشاء تكوين next.js الذي يلبي معظم حالات الاستخدام. يأخذ getConfig الحجج التالية:
applicationId - معرف التطبيق الفريد الذي سيتم استخدامه كبادئة مفتاح الرسائل.locales - أماكن طلبك.defaultLocale - اللغة الافتراضية للتطبيق الخاص بك (يجب أن يتم تضمينه أيضًا في locales )❗ فقط BCP 47 علامات اللغة التي تتبع
language- تنسيقcountryمقبولة. لمزيد من التفاصيل حول السبب ، راجع وثيقة قرارات التصميم.
options (اختيارية) - خيارات جزء من كائن تكوين NEXT.JS. سيقوم getConfig بإرجاع كائن تكوين Next.js.
لاستخدامه ، ما عليك سوى إضافة الرمز التالي في next.config.js الخاص بالتطبيق:
const { getConfig } = require ( 'next-multilingual/config' )
const config = getConfig ( 'exampleApp' , [ 'en-US' , 'fr-CA' ] , 'en-US' , {
// Put your optional options below.
poweredByHeader : false ,
} )
module . exports = config لا يتم دعم جميع خيارات التكوين بواسطة getConfig . إذا كنت تستخدم واحدة من أي وقت مضى ، فستوجهك رسالة خطأ مباشرة إلى القسم التالي: التكوين المتقدم.
Config (التكوين المتقدم) إذا كان لديك احتياجات أكثر تطوراً ، فيمكنك استخدام كائن Config مباشرة وإدراج التكوين المطلوب من خلال next-multilingual مباشرة في next.config.js . تكون وسيطات Config متطابقة تقريبًا لـ getConfig (ناقص options ) - تحقق من IDE (TSDOC) للحصول على التفاصيل. فيما يلي مثال على كيفية استخدامه:
const { Config , webpackConfigurationHandler } = require ( 'next-multilingual/config' )
const config = new Config ( 'exampleApp' , [ 'en-US' , 'fr-CA' ] , 'en-US' )
module . exports = {
reactStrictMode : true ,
i18n : {
locales : config . getUrlLocalePrefixes ( ) ,
defaultLocale : config . getDefaultUrlLocalePrefix ( ) ,
localeDetection : false ,
} ,
poweredByHeader : false ,
webpack : webpackConfigurationHandler ,
}إذا كنت بحاجة إلى تخصيص تكوين WebPack الخاص بك ، فإننا نوصي بتمديد معالجنا مثل هذا:
import Webpack from 'webpack'
import { webpackConfigurationHandler , WebpackContext } from 'next-multilingual/config'
export const myWebpackConfigurationHandler = (
config : Webpack . Configuration ,
context : WebpackContext
) : Webpack . Configuration => {
const myConfig = webpackConfigurationHandler ( config , context )
// Do stuff here.
return myConfig
} أو مباشرة في next.config.js :
// Webpack handler wrapping next-multilingual's handler.
const webpack = ( config , context ) => {
config = webpackConfigurationHandler ( config , context )
// Do stuff here.
return config
} يقوم next-multilingual/config يستفيدان من إمكانية التوجيه الحالية لـ NEXT.JS:
يتولى next-multilingual/config أيضًا تكوين WebPack الخاص المطلوب لتقديم جانب عناوين URL المترجمة باستخدام next-multilingual/link/ssr المترجمة باستخدام مكونات Link ومكونات الارتباط والاستثمار next-multilingual/head/ssr للروابط الكنسية والبديلة في مكون Head .
لمزيد من التفاصيل حول التنفيذ مثل سبب استخدامنا أحرف UTF-8 ، راجع مستند قرارات التصميم.
next-multilingual/messages/babel-plugin لعرض الرسائل المترجمة باستخدام خطاف useMessages() ، نحتاج إلى تكوين المكون الإضافي المخصص بابل الخاص بنا والذي سيحقق الأوتار تلقائيًا في الصفحات والمكونات. الطريقة الموصى بها للقيام بذلك هي تضمين .babelrc في قاعدة طلبك:
{
"presets" : [ " next/babel " ],
"plugins" : [ " next-multilingual/messages/babel-plugin " ]
} إذا لم تقم بتكوين المكون الإضافي ، فستحصل على خطأ عند محاولة استخدام useMessages .
App مخصص ( _app.tsx ) نحتاج إلى إنشاء App مخصص عن طريق إضافة _app.tsx في دليل pages :
import { useActualLocale } from 'next-multilingual'
import type { AppProps } from 'next/app'
const ExampleApp : React . FC < AppProps > = ( { Component , pageProps } ) => {
useActualLocale ( ) // Forces Next.js to use the actual (proper) locale.
return < Component { ... pageProps } / >
}
export default ExampleAppهذا يفعل شيئين بشكل أساسي ، كما ذكر في التعليقات:
/ ). إذا كنت لا ترغب في استخدام اكتشاف موقع next-multilingual ، فيمكنك استخدام useActualLocale(false) بدلاً من ذلك.Document مخصص ( _document.tsx ) نحتاج أيضًا إلى إنشاء Document مخصص عن طريق إضافة _document.tsx في دليل pages :
import { getHtmlLang } from 'next-multilingual'
import { DocumentProps , Head , Html , Main , NextScript } from 'next/document'
const Document : React . FC < DocumentProps > = ( documentProps ) => {
return (
< Html lang = { getHtmlLang ( documentProps ) } translate = "no" className = "notranslate" >
< Head >
< meta name = "google" content = "notranslate" / >
< / Head >
< body >
< Main / >
< NextScript / >
< / body >
< / Html >
)
}
export default Document هذا يخدم الغرض واحد فقط: عرض لغة جانب الخادم الصحيح في علامة <html> . نظرًا لأننا نستخدم لغة افتراضية "مزيفة" ، من المهم الحفاظ على علامة SSR الصحيحة ، خاصة عند حل لغة ديناميكية على / .
يوفر مكون next-multilingual/head مكونًا <Head> يقوم تلقائيًا بإنشاء رابط قانوني وروابط بديلة في الرأس. هذا شيء لم يتم توفيره خارج الصندوق بواسطة Next.js.
NEXT_PUBLIC_ORIGIN وفقًا لـ Google ، يجب أن تكون الروابط البديلة مؤهلة بالكامل ، بما في ذلك طريقة النقل (HTTP/HTTPS). نظرًا لأن next.js لا يعرفون أي عنوان URL المستخدم في وقت الإنشاء ، نحتاج إلى تحديد عنوان URL المطلق الذي سيتم استخدامه ، في متغير البيئة. على سبيل المثال ، بالنسبة لبيئة التطوير ، قم بإنشاء ملف .env.development في جذر التطبيق الخاص بك مع المتغير التالي (ضبط بناءً على الإعداد الخاص بك):
NEXT_PUBLIC_ORIGIN =http://localhost:3000 بغض النظر عن البيئة ، سوف يبحث next-multilingual عن متغير يسمى NEXT_PUBLIC_ORIGIN لإنشاء عناوين URL المؤهلة بالكامل. إذا كنت تستخدم basePath next.js ، فسيتم إضافته تلقائيًا إلى عنوان URL الأساسي.
لن يقبل NEXT_PUBLIC_ORIGIN سوى المجالات المؤهلة بالكامل (على سبيل المثال ، http://example.com ) ، دون أي مسارات.
next-multilingual ؟ الآن بعد أن تم تكوين كل شيء ، يمكننا التركيز على استخدام next-multilingual !
من أجل الحصول على next-multilingual للعمل على أنه مصمم ، كان علينا إيجاد حلول لمشكلتين:
undefined : لأن Next.js دعم مواقع بدون أماكن ، فإن أنواعها الأصلية تسمح بقيم undefined ، والتي بالنسبة لحالتنا أكثر إزعاجًا وتتطلب صبًا إضافيًا.next-multilingual إنشاء لغة افتراضية لا نستخدمها أبدًا. هذا يعني أنه للوصول إلى المعلومات المحلية ذات الصلة ، لا يمكننا الاعتماد على واجهات برمجة تطبيقات Next.js.لقد أنشأنا واجهات برمجة التطبيقات التالية للسماح بقيم لغات متسقة عبر تطبيقك:
useRouter هذا غلاف بسيط أعلى useRouter Next.js الذي يوفر كلاهما اللغات الصحيحة ولكن أيضًا لا يعيد أبدًا undefined .
import { NextPage } from 'next'
import { useRouter } from 'next-multilingual/router'
const Page : NextPage = ( ) => {
const router = useRouter ( )
return < > { router . locale } < / >
}
export default Page getStaticPropsLocales import { getStaticPropsLocales } from 'next-multilingual'
export const getStaticProps : GetStaticProps = async ( context ) => {
const { locale , locales , defaultLocale } = getStaticPropsLocales ( context )
// do stuff
return { props : { } }
} getStaticPathsLocales import { getStaticPathsLocales } from 'next-multilingual'
export const getStaticPaths : GetStaticProps = async ( context ) => {
const { locales , defaultLocale } = getStaticPathsLocales ( context )
// do stuff
return { props : { } }
} getServerSidePropsLocales import { getServerSidePropsLocales } from 'next-multilingual'
export const getServerSideProps : GetServerSideProps = async ( context ) => {
const { locale , locales , defaultLocale } = getServerSidePropsLocales ( context )
// do stuff
return { props : { } }
}
️ لاحظ أنه على الرغم من أننا نوصي باستخدام اكتشاف اللغة الذكية لتقديم الصفحة الرئيسية بشكل ديناميكي ، إلا أن هذا اختياري تمامًا. باستخدام التكوين المتقدم معlocaleDetection: true، ستقوم باستعادة سلوك Next.js الافتراضي دون الحاجة إلى استخدامgetServerSideProps.
الصفحة الرئيسية أكثر تعقيدًا قليلاً من الصفحات الأخرى ، لأننا نحتاج إلى تنفيذ اكتشاف تحديد المواقع الديناميكي (والعرض) للسبب التالي:
/ يمكن أن يكون لها تأثير سلبي على كبار المسئولين الاقتصاديين وليست أفضل تجربة للمستخدم.next-multilingual مع واجهة برمجة تطبيقات getPreferredLocale التي توفر كشفًا تلقائيًا أكثر ذكاءً من تطبيق Next.js الافتراضي.يمكنك العثور على تطبيق كامل في المثال ، ولكن إليك نسخة مجردة:
import type { GetServerSideProps , NextPage } from 'next'
import { ResolvedLocaleServerSideProps , resolveLocale , useResolvedLocale } from 'next-multilingual'
import { useRouter } from 'next-multilingual/router'
const Homepage : NextPage < ResolvedLocaleServerSideProps > = ( { resolvedLocale } ) => {
// Force Next.js to use a locale that was resolved dynamically on the homepage (this must be the first action on the homepage).
useResolvedLocale ( resolvedLocale )
const { locale } = useRouter ( )
return < h1 > { locale } </ h1 >
}
export default Homepage
export const getServerSideProps : GetServerSideProps < ResolvedLocaleServerSideProps > = async (
context
) => {
return {
props : {
resolvedLocale : resolveLocale ( context ) ,
} ,
}
}باختصار ، هذا ما يحدث:
next-multilingual .useResolvedLocale لجعل هذه الديناميكية عبر التطبيق. في كل مرة تقوم فيها بإنشاء ملف tsx أو ts أو jsx أو js (قابل للتجميع) وأنك تحتاج إلى رسائل محلية ، يمكنك ببساطة إنشاء ملف رسالة في أماكنك المدعومة التي لن تكون قابلة للاستخدام إلا من خلال هذه الملفات. تمامًا مثل وحدات CSS ، فإن الفكرة هي أنه يمكنك الحصول على ملفات رسائل مرتبطة بالنطاق المحلي لملف آخر. هذا له فائدة من جعل الرسائل أكثر وحدات ويتجنب مشاركة الرسائل عبر سياقات مختلفة (مزيد من التفاصيل في قرارات التصميم حول سبب كون هذا سيئًا).
تحتوي ملفات الرسائل على حالتين للاستخدام الرئيسي:
pages الخاص بك ، يمكنك تحديد مقطع عنوان URL الموضعي (جزء من عنوان URL بين / أو في نهاية المسار) باستخدام معرف مفتاح slug . مزيد من التفاصيل حول كيفية القيام بذلك أدناه.useMessages . تخيل CSS ولكن للسلاسل القابلة للترجمة.لتلخيص:
يعد إنشاء هذه الملفات وإدارتها أمرًا بسيطًا مثل إنشاء ورقة أنماط ، ولكن إليك التفاصيل المهمة:
.properties . نعم ، قد تتساءل عن السبب ، ولكن هناك أسباب وجيهة موثقة في وثيقة قرار التصميم.UTF-8 . عدم القيام بذلك سيحل محل الأحرف غير اللاتينية بـ �.properties <PageFilename>.<locale>.properties<applicationId>.<context>.<id> حيث:next-multilingual/configaboutUsPage أو footerComponent يمكن أن يكون أمثلة جيدة على السياق. يمكن أن يحتوي كل ملف فقط على سياق واحد ولا ينبغي استخدام سياق عبر العديد من الملفات لأن هذا قد يسبب "تصادمًا رئيسيًا" (مفاتيح غير متوفرة).. ويمكن أن تحتوي فقط على ما بين 1 إلى 50 حرفًا أبجديًا رقميًا - نوصي باستخدام حالة الجمل لقدرة على القراءة.slug .title .getTitle المتوفرة في next-multilingual/messages للتراجع تلقائيًا بين title ومفاتيح slug .useMessages .تأكد أيضًا من التحقق من سجل وحدة التحكم الخاصة بك للحصول على تحذيرات حول المشكلات المحتملة مع رسائلك. قد يكون من الصعب التعود على كيفية عمله أولاً ، لكننا حاولنا أن نجعل من السهل اكتشاف المشكلات وإصلاحها. لاحظ أن هذه السجلات ستظهر فقط في بيئات عدم الإنتاج.
ليس من غير المألوف أن تحتاج إلى رسائل محلية مع عدم القدرة على استخدام الخطافات. مثال على ذلك هو استخدام واحدة من ميزات Next.js الأساسية هو دعم API المبني. في هذا السياق ، بدلاً من استخدام useMessage ، يمكننا ببساطة استخدام getMessages مع تحديد وسيطة locale .
كما ذكرنا سابقًا ، هناك مفتاح خاص واحد pages ، حيث يكون id slug . على عكس الرخويات التقليدية التي تبدو مثل this-is-a-page ، نطلب منك أن تكتب سبيكة كجملة طبيعية وقابلة للقراءة ، بحيث يمكن ترجمتها مثل أي سلسلة أخرى. هذا يتجنب وجود عمليات خاصة للرببات التي يمكن أن تكون مكلفة ومعقدة لإدارة لغات متعددة.
في الأساس ، فإن slug هو "الوصف القصير" القابل للقراءة البشرية لصفحتك ، ويمثل شريحة (جزء بين / أو في نهاية المسار) لعنوان URL. عند استخدامها كقطعة عناوين URL ، يتم تطبيق التحول التالي:
- على سبيل المثال ، سيصبح About Us about-us .
بالنسبة للصفحة الرئيسية ، سيكون عنوان URL دائمًا / يعني أنه لن يتم استخدام مفاتيح slug لإنشاء شرائح URL الموضعية.
لا تنسى ، يجب كتابة الرخويات كوصفًا قصيرًا طبيعيًا ، مما يعني تخطي الكلمات للحفاظ عليها أقصر لكبار المسئولين الاقتصاديين. السبب الرئيسي لذلك ، هو أنه إذا كتبت "حفنة من الكلمات الرئيسية" ، فقد يواجه اللغوي الذي لا يعرف SEO وقتًا عصيبًا في ترجمة تلك الرسالة. سيكون وجود متخصصين في كبار المسئولين الاقتصاديين في العديد من اللغات مكلفة للغاية ويصعب توسيع نطاقه. في السيناريو المثالي ، من المحتمل أن يتم تأليف صفحات تحسين محركات البحث (SEO) الخاصة بالسوق وتحسينها باللغات الأصلية ، ولكن هذا لم يعد جزءًا من عملية الترجمة. يتمثل التركيز في next-multilingual على توفير حل سهل ومتبسط لتوطين عناوين URL بعدة لغات.
سيتم أيضًا استخدام مفتاح slug كإعداد لمفتاح title عند استخدام واجهة برمجة تطبيقات getTitle المقدمة في next-multilingual/messages . يجعل واجهة برمجة التطبيقات هذه من السهل تخصيص العناوين عندما يشعر الرخوة غير كافية.
️ لاحظ أن تغيير قيمةslugيعني أن عنوان URL سيتغير. نظرًا لأن هذه التغييرات تحدث فيnext.config.js، مثل أي تغيير تكوين Next.js ، يجب إعادة تشغيل الخادم لمعرفة التغييرات السارية. وينطبق الشيء نفسه إذا قمت بتغيير بنية المجلد لأن التكوين الأساسي يعتمد على هذا.
إذا كنت locale في الحصول على دليل بدون أي صفحات ، فلا يزال بإمكانك توطينه عن طريق إنشاء index.<locale>.properties على الرغم من دعم هذا الخيار ، فإننا لا نوصي باستخدامه لأن هذا سيجعل مسارات URL أطول مما يتعارض مع أفضل الممارسات SEO.
بشكل افتراضي ، سوف تستبعد next-multilingual بعض الملفات مثل صفحات الأخطاء المخصصة أو أي طرق API ضمن دليل /api . يمكنك دائمًا استخدام مفاتيح slug عند استخدام رسائل لهذه الملفات ، ولكن لن يتم استخدامها لإنشاء عناوين URL المترجمة.
يمكنك دائمًا النظر في المثال لرؤية ملفات الرسائل قيد التنفيذ ، ولكن إليك عينة يمكن استخدامها على الصفحة الرئيسية:
# Homepage title
exampleApp.homepage.title = Homepage
# Homepage headline
exampleApp.homepage.headline = Welcome to the homepage الآن بعد أن تعلمنا كيفية إنشاء الصفحة الرئيسية وبعض التفاصيل حول كيفية عمل الأشياء ، يمكننا بسهولة إنشاء صفحات أخرى. نقوم بإنشاء العديد من الصفحات في المثال ، ولكن إليك عينة من ما يمكن أن يبدو عليه about-us.jsx :
import { NextPage } from 'next'
import { getTitle , useMessages } from 'next-multilingual/messages'
import Layout from '@/layout'
import styles from './index.module.css'
const AboutUs : NextPage = ( ) => {
const messages = useMessages ( )
const title = getTitle ( messages )
return (
< Layout title = { title } >
< h1 className = { styles . headline } > { title } </ h1 >
< p > { messages . format ( 'details' ) } </ p >
</ Layout >
)
}
export default AboutUs وبالطبع سيكون لديك ملف الرسالة هذا about-us.en-US.properties :
# Page localized URL segment (slug) in (translatable) human readable format.
# This key will be "slugified" (e.g, "About Us" will become "about-us"). All non-alphanumeric characters will be replaced by "-".
exampleApp.aboutUsPage.slug = About Us
# Page details.
exampleApp.aboutUsPage.details = This is just some english boilerplate text. يأتي next-multilingual مع مكونه الخاص <Link> الخاص به يسمح بتقديم جانب العميل وجانب الخادم لعنوان URL المترجمة. الاستخدام بسيط ، إنه يعمل تمامًا مثل Next.js <Link> .
الشيء المهم الوحيد الذي يجب تذكره هو أن سمة href يجب أن تحتوي دائمًا على عنوان URL التالي. بمعنى ، يجب أن يكون بنية الملفات أسفل مجلد pages هو ما يتم استخدامه وليس الإصدارات المترجمة.
وبعبارة أخرى ، يعتبر بنية الملف بمثابة تمثيل عنوان URL "غير المحلي" ، وسيهتم <Link> باستبدال عناوين URL بالإصدارات المترجمة (من ملفات الرسائل) ، إذا كانت تختلف عن الهيكل.
واجهة برمجة التطبيقات متوفرة تحت next-multilingual/link ويمكنك استخدامه مثل هذا:
import Link from 'next-multilingual/link'
import { useMessages } from 'next-multilingual/messages'
const Menu : React . FC = ( ) => {
const messages = useMessages ( )
return (
< nav >
< Link href = "/" >
< a > { messages . format ( 'home' ) } </ a >
</ Link >
< Link href = "/about-us" >
< a > { messages . format ( 'aboutUs' ) } </ a >
</ Link >
< Link href = "/contact-us" >
< a > { messages . format ( 'contactUs' ) } </ a >
</ Link >
</ nav >
)
}
export default Menu سيتم ترجمة كل من هذه الروابط تلقائيًا عند تحديد مفتاح slug في ملف رسالة تلك الصفحة. على سبيل المثال ، في الولايات المتحدة ، سيكون مسار URL "اتصل بنا" /en-us/contact-us بينما في الفرنسية الكندية سيكون /fr-ca/nous-joindre .
نظرًا لأن البيانات الخاصة بهذا التعيين غير متوفرة على الفور أثناء التقديم ، ستعتني next-multilingual/link/ssr بتقديم جانب الخادم (SSR). باستخدام getConfig لـ next-multilingual/config ، سيتم إضافة تكوين WebPack تلقائيًا. إذا كنت تستخدم طريقة Config المتقدمة ، فإن هذا يفسر سبب حاجة إلى تكوين حزمة الويب الخاصة في المثال المقدم مسبقًا.
لا تستخدم جميع عناوين URL المترجمة مكون <Link> ، وهذا هو السبب أيضًا في أن Next.js لديه طريقة router.push . يمكن أن تدعم next-multilingual هذه حالات الاستخدام باستخدام خطاف useLocalizedUrl الذي سيعيد عنوان URL المترجمة ، قابل للاستخدام من قبل أي مكونات. فيما يلي مثال على كيفية الاستفادة منه:
import { NextPage } from 'next'
import { useMessages } from 'next-multilingual/messages'
import { useLocalizedUrl } from 'next-multilingual/url'
import router from 'next/router'
const Tests : NextPage = ( ) => {
const messages = useMessages ( )
const localizedUrl = useLocalizedUrl ( '/about-us' )
return < button onClick = { ( ) => router . push ( localizedUrl ) } > { messages . format ( 'clickMe' ) } </ button >
}
export default Tests إذا كنت تفضل تحديد عنوان URL الخاص بك بدلاً من ذلك في الجزء العلوي من المكون ، أو إذا كنت بحاجة إلى إجراء المزيد من معالجات URL المتقدمة ، فيمكنك أيضًا استخدام خطاف useGetLocalizedUrl الذي يعيد وظيفة للحصول على عناوين URL:
import { NextPage } from 'next'
import { useMessages } from 'next-multilingual/messages'
import { useGetLocalizedUrl } from 'next-multilingual/url'
import router from 'next/router'
const Tests : NextPage = ( ) => {
const messages = useMessages ( )
const { getLocalizedUrl } = useGetLocalizedUrl ( )
return (
< button onClick = { ( ) => router . push ( getLocalizedUrl ( '/about-us' ) ) } >
{ messages . format ( 'clickMe' ) }
</ button >
)
}
export default Testsكن حذرًا ، إذا كنت ترغب في استخدام قيمة السلسلة لعناصر عنوان URL داخل عناصر رد الفعل ، فسيكون لديك أخطاء لأن عناوين URL تختلف بين الإدانة المسبقة والمتصفح. والسبب في ذلك هو أنه على العميل ، على العرض الأول ، لا يمكن لـ Next.js الوصول إلى بيانات إعادة كتابة ، وبالتالي يستخدم مسارات URL "شبه المحلية" (على سبيل المثال
/fr-ca/about-us). نظرًا لأن هذا هو سلوك next.js الأصليين ، فإن أبسط طريقة للتغلب على هذا الآن هي إضافةsuppressHydrationWarning={true}إلى عنصرك. للتغلب على هذا ، يقومuseGetLocalizedUrlأيضًا بإرجاع خاصيةisLoadingالتي يمكن استخدامها لتتبع عندما تكون عناوين URL الموضعية متاحة لاستخدامها على العميل.
قد تصادف الموقف حيث تحتاج أيضًا إلى الحصول على عنوان URL الموضعي ولكن استخدام الخطاف ليس خيارًا. هذا هو المكان الذي يأتي فيه getLocalizedUrl في next-multilingual/url . إنه يتصرف مثل useLocalizedUrl ولكن حجة locale أمر إلزامي.
تخيل استخدام API Next.js لإرسال رسائل البريد الإلكتروني للمعاملات والرغبة في الاستفادة من عناوين URL المترجمة next-multilingual دون الحاجة إلى ترميزها في التكوين. فيما يلي مثال على كيفية استخدامه:
import type { NextApiRequest , NextApiResponse } from 'next'
import { isLocale } from 'next-multilingual'
import { getMessages } from 'next-multilingual/messages'
import { getLocalizedUrl } from 'next-multilingual/url'
import { sendEmail } from 'send-email'
/**
* The "/api/send-email" handler.
*/
const handler = ( request : NextApiRequest , response : NextApiResponse ) : Promise < void > => {
const locale = request . headers [ 'accept-language' ]
let emailAddress = ''
try {
emailAddress = JSON . parse ( request . body ) . emailAddress
} catch ( error ) {
response . status ( 400 )
return
}
if ( locale === undefined || ! isLocale ( locale ) || ! emailAddress . length ) {
response . status ( 400 )
return
}
const messages = getMessages ( locale )
sendEmail (
emailAddress ,
messages . format ( 'welcome' , { loginUrl : await getLocalizedUrl ( '/login' , locale , true ) } )
)
response . status ( 200 )
}
export default handler إنشاء المكونات هو نفس الصفحات ولكنهم يعيشون خارج دليل pages . أيضًا ، لن يكون لمفتاح slug (إذا تم استخدامه) أي تأثير على عناوين URL. لدينا عدد قليل من مكونات المثال التي يجب أن تكون واضحة للذات ولكن فيما يلي مثال على مكون Footer.tsx .
import { useMessages } from 'next-multilingual/messages'
const Footer : React . FC = ( ) => {
const messages = useMessages ( )
return < footer > { messages . format ( 'footerMessage' ) } </ footer >
}
export default Footerوملف رسائله:
# This is the message in the footer at the bottom of pages
exampleApp.footerComponent.footerMessage = © Footerتأكد أيضًا من إلقاء نظرة على مثال مكون Switcher للغة أمر لا بد منه في جميع التطبيقات متعددة اللغات.
لقد كنا واضحين أن مشاركة الرسائل هي ممارسة سيئة منذ البداية ، فما الذي نتحدث عنه هنا؟ في الواقع ، مشاركة الرسائل في حد ذاتها ليست سيئة. ما يمكن أن يسبب مشاكل هو عند مشاركة الرسائل في سياقات مختلفة. على سبيل المثال ، قد تميل إلى إنشاء Button.ts رسالة مشتركة تحتوي على yesButton ، مفاتيح noButton - ولكن هذا سيكون خطأ. في العديد من اللغات ، يمكن أن تحتوي الكلمات البسيطة مثل "نعم" و "لا" على تهجئة مختلفة اعتمادًا على السياق ، حتى لو كان زرًا.
متى من الجيد مشاركة الرسائل؟ لقوائم العناصر.
على سبيل المثال ، للحفاظ على عملية التوطين الخاصة بك بسيطة ، فأنت تريد تجنب تخزين السلاسل القابلة للترجمة في قاعدة البيانات الخاصة بك (مزيد من التفاصيل حول السبب في وثيقة قرار التصميم). في قاعدة البيانات الخاصة بك ، ستقوم بتحديد السياق باستخدام معرفات فريدة وستقوم بتخزين رسائلك في ملفات الرسائل المشتركة ، حيث ستطابق معرفات مفتاحك المعرفات من قاعدة البيانات.
لتوضيح ذلك ، أنشأنا مثالًا واحدًا باستخدام الفواكه. كل ما عليك فعله ، هو إنشاء خطاف يدعو useMessages مثل هذا:
export { useMessages as useFruitsMessages } from 'next-multilingual/messages'❗ إذا كنت بحاجة إلى الوصول إلى رسائلك خارج السنانير ، فأنت بحاجة أيضًا إلى تصدير
getMessages.
بالطبع ، سيكون لديك ملفات رسائلك في نفس الدليل:
exampleApp.fruits.banana = Banana
exampleApp.fruits.apple = Apple
exampleApp.fruits.strawberry = Strawberry
exampleApp.fruits.grape = Grape
exampleApp.fruits.orange = Orange
exampleApp.fruits.watermelon = Watermelon
exampleApp.fruits.blueberry = Blueberry
exampleApp.fruits.lemon = Lemonواستخدامه ، استيراد هذا الخطاف البسيط من أي مكان قد تحتاجه هذه القيم:
import { useFruitsMessages } from '../messages/useFruitsMessages'
const FruitList : React . FC ( ) => {
const fruitsMessages = useFruitsMessages ( )
return (
< >
{ fruitsMessages
. getAll ( )
. map ( ( message ) => message . format ( ) )
. join ( ', ' ) }
</ >
)
}
export default FruitListيمكنك أيضًا استدعاء رسائل فردية مثل هذه:
fruitsMessages . format ( 'banana' )فكرة مشاركة قوائم العناصر هذه هي أنه يمكنك الحصول على تجربة متسقة عبر مكونات مختلفة. تخيل منسدلة مع قائمة بالفواكه في صفحة واحدة ، وفي صفحة أخرى إدخال الإكمال التلقائي. لكن الجزء المهم الذي يجب تذكره هو أنه يجب دائمًا استخدام القائمة في نفس السياق ، وليس إعادة استخدام بعض الرسائل في سياق مختلف.
يعد استخدام العناصر النائبة في الرسائل وظيفة مهمة لأن جميع الرسائل لا تحتوي على نص ثابت. يدعم next-multilingual لبناء ICU MessageFormat خارج المربع مما يعني أنه يمكنك استخدام الرسالة التالية:
exampleApp.homepage.welcome = Hello {name}!وحقن القيم باستخدام:
messages . format ( 'welcome' , { name : 'John Doe' } ) format هناك بعض القواعد البسيطة التي يجب وضعها في الاعتبار عند استخدام format :
values عند تنسيق الرسالة ، فسيقوم ببساطة بإخراج الرسالة كنص ثابت.values عند تنسيق الرسالة ، فيجب عليك تضمين قيم جميع العناصر النائبة باستخدام بناء جملة {placeholder} في رسالتك. وإلا فلن يتم عرض الرسالة.values ليست في رسالتك ، فسيتم تجاهلها بصمت. واحدة من الفوائد الرئيسية لـ ICU Messageformat هي استخدام أدوات ومعايير Unicode لتمكين التطبيقات من الصوت بطلاقة في معظم اللغات. قد يعتقد الكثير من المهندسين أنه من خلال وجود رسالتين ، واحدة للفرد وواحدة للتجمع ، تكفي للبقاء بطلاقة بجميع اللغات. في الواقع ، وثق Unicode قواعد الجمع التي تزيد عن 200 لغة وبعض اللغات مثل العربية يمكن أن تحتوي على ما يصل إلى 6 أشكال متعددة.
لضمان أن تظل الجملة الخاصة بك بطلاقة بجميع اللغات ، يمكنك استخدام الرسالة التالية:
exampleApp.homepage.mfPlural = {count, plural, =0 {No candy left.} one {Got # candy left.} other {Got # candies left.}}وسيتم اختيار النموذج الجمع الصحيح ، باستخدام فئات الجمع الصحيحة المحددة بواسطة Unicode:
messages . format ( 'mfPlural' , { count } )هناك الكثير لنتعلمه حول هذا الموضوع. تأكد من قراءة وثائق Unicode وجرب بناء الجملة بنفسك للتعرف على قدرة I18N التي لا تحظى بها.
في حدث نادر حيث ستحتاج إلى استخدام كل من العناصر النائبة باستخدام بناء الجملة {placeholder} وأيضًا عرض الأحرف { و } في رسالة ، ستحتاج إلى استبدالها بـ { (لـ { ) و } (من أجل } ) كيانات HTML التي يتم التعرف عليها بواسطة أدوات الترجمة مثل هذا:
exampleApp.debuggingPage.variableInfo = Your variable contains the following values: & # x7b;{values}} إذا كان لديك رسالة بدون قيم (أصحاب نائبة) ، فإن الهروب من { و } مع كيانات HTML غير مطلوب وستعرض الكيانات كنص ثابت.
إنه موقف شائع للغاية نحتاج إلى الحصول على HTML مضمّن ، داخل رسالة واحدة. طريقة واحدة للقيام بذلك ستكون:
# Bad example, do not ever do this!
exampleApp.homepage.createAccount1 = Please
exampleApp.homepage.createAccount2 = create your account
exampleApp.homepage.createAccount3 = today for free.وثم:
< div >
{ messages . format ( 'createAccount1' ) }
< Link href = "/sign-up" > { messages . format ( 'createAccount2' ) } </ Link >
{ messages . format ( 'createAccount3' ) }
</ div >هناك مشكلتان في هذا النهج:
هذا هو في الواقع مضاد للنمط يسمى التسلس ويجب تجنبه دائمًا. هذه هي الطريقة الصحيحة للقيام بذلك ، باستخدام formatJsx :
exampleApp.homepage.createAccount = Please <link>create your account</link> today for free.وثم:
< div > { messages . formatJsx ( 'createAccount' , { link : < Link href = "/sign-up" > </ Link > } ) } </ div > formatJsx يدعم formatJsx كل من العناصر النائبة وعناصر JSX values مما يعني أنه يمكنك الاستفادة من ميزات format القياسية (على سبيل المثال ، مجموعات) أثناء حقن عناصر JSX.
هناك بعض القواعد البسيطة التي يجب وضعها في الاعتبار عند استخدام format :
formatJsx .<link> XML ، يجب توفير عنصر JSX باستخدام link: <Link href="/"></Link> .<i> مرات عديدة في جملة ، فستحتاج إلى إنشاء علامات فريدة مثل <i1> و <i2> ، وما إلى ذلك ، وتمرير قيمها في الوسيطة كعناصر JSX.Hello <name>{name}</name> ) غير مدعوم..properties .<Link href="/contact-us><a id="test"></a></Link> صالحة ولكن <div><span1></span1><span2></span2></div> غير صالح. بدلاً من ذلك ، يجب عليك استخدام markup من نفس المستوى في ملف .properties وليس كجليد Jsx. < و > عند استخدام formatJsx ، ستظل بحاجة إلى الهروب من قوسين مجعد إذا كنت ترغب في عرضها كنص. بالإضافة إلى ذلك ، نظرًا لأننا سنستخدم XML في رسائل formatJsx ، سيتم تطبيق قواعد مماثلة على < و > والتي يتم استخدامها لتحديد العلامات.
في حدث نادر حيث ستحتاج إلى ضخ JSX في رسالة باستخدام بناء جملة <element></element> (XML) وأيضًا عرض < و > الأحرف في رسالة ، ستحتاج إلى استبدالها بـ < (لـ < ) و > (لـ > ) كيانات HTML التي يتم التعرف عليها بواسطة أدوات الترجمة مثل هذا:
exampleApp.statsPage.targetAchieved = You achieved your weekly target (& # x3c;5) and are eligible for a <link>reward</link>.روابط المرساة عبارة عن روابط تنقلك إلى مكان معين في وثيقة بدلاً من الجزء العلوي منه.
واحدة من الميزة الأساسية لـ next-multilingual هي دعم عناوين URL الموضعية. تم بناء تصميمنا باستخدام جمل عادية يسهل توطينها ثم تحويلها إلى رخويات صديقة لكبار المسئولين الاقتصاديين. يمكننا استخدام نفس الوظيفة لتخليص روابط المرساة ، بحيث بدلاً من وجود /fr-ca/nous-joindre#our-team يمكنك الحصول على /fr-ca/nous-joindre#notre-équipe .
هناك نوعان من روابط المرساة:
إذا كانت روابط المرساة على نفس الصفحة ، ولم تتم إحالتها على أي صفحات أخرى ، يمكنك ببساطة إضافتها في ملف .properties مع هذه الصفحة مثل هذا:
# Table of content header
exampleApp.longPage.tableOfContent = Table of Content
# This key will be used both as content and "slugified". Make sure when translating that its value is unique.
exampleApp.longPage.p1Header = Paragraph 1
# "Lorem ipsum" text to make the (long) page scroll
exampleApp.longPage.p1 = Lorem ipsum dolor sit amet... ثم يمكن للصفحة استخدام وظيفة slugify للربط بالمعرف الفريد المرتبط بالعنصر الذي تريد توجيه جزء عنوان URL إلى:
import { NextPage } from 'next'
import Link from 'next-multilingual/link'
import { slugify , useMessages } from 'next-multilingual/messages'
import { useRouter } from 'next/router'
const LongPage : NextPage = ( ) => {
const messages = useMessages ( )
const { locale } = useRouter ( )
return (
< div >
< div >
< h2 > { messages . format ( 'tableOfContent' ) } </ h2 >
< ul >
< li >
< Link href = { `# ${ slugify ( messages . format ( 'p1Header' ) , locale ) } ` } >
{ messages . format ( 'p1Header' ) }
</ Link >
</ li >
</ ul >
</ div >
< div >
< h2 id = { slugify ( messages . format ( 'p1Header' ) , locale ) } > { messages . format ( 'p1Header' ) } </ h2 >
< p > { messages . format ( 'p1' ) } </ p >
</ div >
</ div >
)
}
export default LongPage من الشائع أيضًا استخدام روابط المرساة عبر الصفحات ، بحيث عند النقر فوق رابط ، سيعرض متصفحك مباشرة المحتوى ذي الصلة في تلك الصفحة. للقيام بذلك ، تحتاج إلى إتاحة رسالة صفحتك إلى صفحات أخرى عن طريق إضافة هذا التصدير البسيط الذي سيتصرف تمامًا مثل "الرسائل المشتركة":
export const useLongPageMessages = useMessagesوبعد ذلك يمكنك استخدام هذا الخطاف من صفحة أخرى مثل هذا:
import { NextPage } from 'next'
import Link from 'next-multilingual/link'
import { slugify , useMessages } from 'next-multilingual/messages'
import { useRouter } from 'next/router'
import { useLongPageMessages } from './long-page'
const AnchorLinks : NextPage = ( ) => {
const messages = useMessages ( )
const { locale , pathname } = useRouter ( )
const longPageMessages = useLongPageMessages ( )
return (
< div >
< div >
< Link
href = { ` ${ pathname } /long-page# ${ slugify ( longPageMessages . format ( 'p3Header' ) , locale ) } ` }
>
{ messages . format ( 'linkAction' ) }
</ Link >
</ div >
</ div >
)
}
export default AnchorLinksهذا النمط يعمل أيضا للمكونات. إن الاستفادة من ذلك هو أنه إذا قمت بحذف الصفحة ، أو إعادة صياغة الصفحة ، فإن روابط المرساة المرتبطة بها ستبقى دائمًا مع الصفحة.
يمكنك إنشاء مكون رسائل مشترك منفصل فقط لروابط المرساة ولكن هذا من شأنه أن يكسر مبدأ القرب.
يمكن العثور على مثال كامل لروابط المرساة في تطبيق المثال.
تدير إحدى الميزات المفقودة من Next.js علامات HTML المهمة المستخدمة لكبار المسئولين الاقتصاديين. أضفنا مكون <Head> للتعامل مع علامتين مهمتين للغاية يعيشان في HTML <head> :
<link rel=canonical> ): هذا يخبر محركات البحث بأن مصدر الحقيقة للصفحة التي يتم تصفحها هو عنوان URL هذا. من المهم للغاية تجنب معاقبة المحتوى المكررة ، خاصة وأن عناوين URL غير حساسة للحالة ، لكن Google تعاملها على أنها حساسة للحالة.<link rel=alternate> ): يخبر هذا محركات البحث أن الصفحة التي يتم تصفحها متوفرة أيضًا بلغات أخرى وتسهل تزوير الموقع. واجهة برمجة التطبيقات متوفرة تحت next-multilingual/head ويمكنك استيرادها على هذا النحو:
import Head from 'next-multilingual/head' تمامًا مثل <Link> ، من المفترض أن يكون <Head> بديلاً للمنزل لمكون <head> <Head> next.js. في مثالنا ، نستخدمه في مكون التصميم ، مثل هذا:
< Head >
< title > { title } </ title >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" > </ meta >
</ Head > كل هذا يفعل هو إدراج الروابط الكنسي والبديلة بحيث يمكن لمحركات البحث تزحف تطبيقك بشكل أفضل. على سبيل المثال ، إذا كنت على صفحة /en-us/about-us ، فسيتم إضافة HTML التالية تلقائيًا ضمن علامة HTML <head> الخاصة بك:
< link rel =" canonical " href =" http://localhost:3000/en-us/about-us " />
< link rel =" alternate " href =" http://localhost:3000/en-us/about-us " hreflang =" en-US " />
< link rel =" alternate " href =" http://localhost:3000/fr-ca/%C3%A0-propos-de-nous " hreflang =" fr-CA " /> للاستفادة الكاملة من ترميز كبار المسئولين الاقتصاديين ، يجب تضمين <Head> في جميع الصفحات. هناك طرق متعددة لتحقيق ذلك ، ولكن في المثال ، أنشأنا مكونًا <Layout> يتم استخدامه في جميع الصفحات.
مثل معظم المواقع ، سترغب في الاستفادة من قدرة خطأ في صفحات الأخطاء المخصصة لـ Next.js. مع useMessages() ، يكون الأمر سهلاً بنفس القدر من إنشاء أي صفحات أخرى. على سبيل المثال ، لخطأ 404 ، يمكنك إنشاء 404.tsx :
import { NextPage } from 'next'
import Link from 'next-multilingual/link'
import { getTitle , useMessages } from 'next-multilingual/messages'
import Layout from '@/layout'
const Error404 : NextPage = ( ) => {
const messages = useMessages ( )
const title = getTitle ( messages )
return (
< Layout title = { title } >
< h1 > { title } </ h1 >
< Link href = "/" >
< a > { messages . format ( 'goBack' ) } </ a >
</ Link >
</ Layout >
)
}
export default Error404 وبطبيعة الحال ، رسائلك ، على سبيل المثال 404.en-US.properties :
# Page title
exampleApp.pageNotFoundError.title = 404 - Page Not Found
# Go back link text
exampleApp.pageNotFoundError.goBack = Go back homeغالبًا ما تحتاج واجهات برمجة التطبيقات إلى أن تكون مترجمة. إليك مثال "Hello API":
import type { NextApiRequest , NextApiResponse } from 'next'
import { getMessages } from 'next-multilingual/messages'
/**
* Example API schema.
*/
type Schema = {
message : string
}
/**
* The "hello API" handler.
*/
const handler = ( request : NextApiRequest , response : NextApiResponse < Schema > ) : void => {
const locale = request . headers [ 'accept-language' ]
if ( locale === undefined || ! isLocale ( locale ) ) {
response . status ( 400 )
return
}
const messages = getMessages ( locale )
response . status ( 200 ) . json ( { message : messages . format ( 'message' ) } )
} هذا يشبه إلى حد كبير واجهة برمجة التطبيقات التي تم تنفيذها في تطبيق المثال. نحن نستخدم رأس HTTP Accept-Language لإخبار واجهة برمجة التطبيقات التي نريد أن تكون ردها. على عكس خطاف useMessages الذي يحتوي على سياق اللغة الحالية ، نحتاج إلى إخبار getMessages في أي لغة لإرجاع الرسائل.
تتصرف ملفات الرسائل تمامًا كما هو الحال مع useMessages فأنت ببساطة تحتاج إلى إنشاء واحد بجوار ملف API ، في حالتنا hello.en-US.properties :
# API message
exampleApp.helloApi.message = Hello, from API.يمكنك تنفيذ هذا في أي صفحات ، تمامًا مثل أي مكالمة API الأخرى المستندة إلى React ، مثل هذا:
const SomePage : NextPage = ( ) => {
const [ apiError , setApiError ] = useState ( null )
const [ isApiLoaded , setApiIsLoaded ] = useState ( false )
const [ apiMessage , setApiMessage ] = useState ( '' )
useEffect ( ( ) => {
setApiIsLoaded ( false )
const requestHeaders : HeadersInit = new Headers ( )
requestHeaders . set ( 'Accept-Language' , normalizeLocale ( router . locale as string ) )
fetch ( '/api/hello' , { headers : requestHeaders } )
. then ( ( result ) => result . json ( ) )
. then (
( result ) => {
setApiIsLoaded ( true )
setApiMessage ( result . message )
} ,
( apiError ) => {
setApiIsLoaded ( true )
setApiError ( apiError )
}
)
} , [ router . locale ] )
const showApiMessage : React . FC = ( ) => {
if ( apiError ) {
return (
< >
{ messages . format ( 'apiError' ) }
{ ( apiError as Error ) . message }
</ >
)
} else if ( ! isApiLoaded ) {
return < > { messages . format ( 'apiLoading' ) } </ >
} else {
return < > { apiMessage } </ >
}
}
return (
< div >
< h2 > { messages . format ( 'apiHeader' ) } </ h2 >
< div > { showApiMessage ( { } ) } </ div >
</ div >
)
} normalizeLocale ليست إلزامية ولكن اتفاقية ISO 3166 موصى بها. نظرًا لأن Next.js يستخدم اللغات كبادئات عناوين URL ، فهي مغلفة بالتكوين ويمكن إعادة تمييزها حسب الحاجة.
❗ الطرق الديناميكية معقدة وتضيف توطينها أكثر تعقيدًا. تأكد من أنك على دراية بكيفية عمل ميزة Next.js قبل محاولة إضافة التوطين.
الطرق الديناميكية شائعة جدًا ودعمها خارج الصندوق بواسطة Next.js. منذ الإصدار 3.0 ، يوفر next-multilingual نفس الدعم مثل Next.js من حيث الطرق الديناميكية. لجعل الطرق الديناميكية تعمل مع next-multilingual لدينا بعض الأنماط التي يجب متابعتها:
<Link> 's href وحجة url useLocalizedUrl / useGetLocalizedUrl / getLocalizedUrl فقط تقبل عناوين URL.<Link> component which accepts a UrlObject , we preferred to streamline our types since urlObject.href can easily be used instead.userRouter().asPath (most common scenario) by providing localized parameters directly in the URL. By using asPath you are using the localized URL which means that the URL you will use will be fully localized.userRouter().pathname is conjunction with hydrateRouteParameters by providing localized parameters. By using pathname you are using the non-localized URL which means that the URL you will use might be a mix of non-localized segments plus the localized parameters. This can be useful in cases where you have nested dynamic routes.We provided several examples of on on to use dynamic routes in our dynamic route test pages.
The main challenge with dynamic routes, is that if the value of the parameter needs to be localized, we need to keep a relation between languages so that we can correctly switch languages. next-multilingual solves this problem with its getLocalizedRouteParameters API that creates a LocalizedRouteParameters object used as a page props. This can work both with getStaticProps and getServerSideProps .
getStaticProps First you need to tell Next.js which predefined paths will be valid by using getStaticPaths (all imports are added in the first example):
import { getCitiesMessages } from '@/messages/cities/citiesMessages'
import { GetStaticPaths } from 'next'
import { slugify } from 'next-multilingual/messages'
export const getStaticPaths : GetStaticPaths = async ( context ) => {
const paths : MultilingualStaticPath [ ] = [ ]
const { locales } = getStaticPathsLocales ( context )
locales . forEach ( ( locale ) => {
const citiesMessages = getCitiesMessages ( locale )
citiesMessages . getAll ( ) . forEach ( ( cityMessage ) => {
paths . push ( {
params : {
city : slugify ( cityMessage . format ( ) , locale ) ,
} ,
locale ,
} )
} )
} )
return {
paths ,
fallback : false ,
}
} Then you have to pre-compute the localized route parameters and return them as props using getStaticProps and getLocalizedRouteParameters :
export type CityPageProps = { localizedRouteParameters : LocalizedRouteParameters }
export const getStaticProps : GetStaticProps < CityPageProps > = async ( context ) => {
const localizedRouteParameters = getLocalizedRouteParameters (
context ,
{
city : getCitiesMessages ,
} ,
import . meta . url
)
return { props : { localizedRouteParameters } }
}If you are using a catch-all dynamic route, you will need to pass your parameters as an array, for each URL segment that you want to support. For example, if you want to support 2 levels:
const localizedRouteParameters = getLocalizedRouteParameters ( context , {
city : [ getCitiesMessages , getCitiesMessages ] ,
} ) Note that since we need to use the getMessages API instead of the useMessages hook, you will also need to export it in the message file:
export {
getMessages as getCitiesMessages ,
useMessages as useCitiesMessages ,
} from 'next-multilingual/messages'Finally you have to pass down your localized route parameters down to your language switcher component when you create your page:
const CityPage : NextPage < CityPageProps > = ( { localizedRouteParameters } ) => {
const messages = useMessages ( )
const title = getTitle ( messages )
const { query } = useRouter ( )
return (
< Layout title = { title } localizedRouteParameters = { localizedRouteParameters } >
< h1 > { query [ 'city' ] } < / h1>
< / Layout>
)
}
export default CityPage The only part missing now is the language switcher which needs to leverage the localized route parameters by using getLanguageSwitcherUrl :
import { normalizeLocale , setCookieLocale } from 'next-multilingual'
import Link from 'next-multilingual/link'
import { KeyValueObject } from 'next-multilingual/messages'
import { LocalizedRouteParameters , useRouter } from 'next-multilingual/router'
import { getLanguageSwitcherUrl } from 'next-multilingual/url'
import { ReactElement } from 'react'
// Locales don't need to be localized.
const localeStrings : KeyValueObject = {
'en-US' : 'English (United States)' ,
'fr-CA' : 'Français (Canada)' ,
}
type LanguageSwitcherProps = {
/** Route parameters, if the page is using a dynamic route. */
localizedRouteParameters ?: LocalizedRouteParameters
}
export const LanguageSwitcher : React . FC < LanguageSwitcherProps > = ( { localizedRouteParameters } ) => {
const router = useRouter ( )
const { pathname , locale : currentLocale , locales , defaultLocale , query } = useRouter ( )
const href = getLanguageSwitcherUrl ( router , localizedRouteParameters )
return (
< div id = "language-switcher" >
< ul >
{ locales
. filter ( ( locale ) => locale !== currentLocale )
. map ( ( locale ) => {
return (
< li key = { locale } >
< Link href = { href } locale = { locale } >
< a
onClick = { ( ) => {
setCookieLocale ( locale )
} }
lang = { normalizeLocale ( locale ) }
>
{ localeStrings [ normalizeLocale ( locale ) ] }
< / a>
< / Link>
< / li >
)
} ) }
< / ul>
< / div >
)
}Check out our fully working examples:
Our ideal translation process is one where you send the modified files to your localization vendor (while working in a branch), and get back the translated files, with the correct locale in the filenames. Once you get the files back you basically submit them back in your branch which means localization becomes an integral part of the development process. Basically, the idea is:
We don't have any "export/import" tool to help as at the time of writing this document.
next-multilingual ? ?️Why did we put so much effort into these details? Because our hypothesis is that it can have a major impact on:
More details can be found on the implementation and design decision in the individual README files of each API and in the documentation directory.