دعم تعدد اللغات في Next.js من خلال مكتبة next-translate

سليمان العثمان
سليمان العثمان
٢٧ نوفمبر ٢٠٢٣
Cover Image for دعم تعدد اللغات في Next.js من خلال مكتبة next-translate

تعدد اللغات لأي موقع من الأمور المهمة و المطلوبة، في هذه التدوينة سنتطرق لمكتبة next-translate المختصة في تعدد اللغات و توافقها مع Next.js، كما إنها تمتاز بسهولة ضبطها و إعدادها مقارنة بقريناتها، بالإضافة إلى مميزاتها الآخرى، و الآن لنبدأ!

تثبيت مكتبة next-translate

في البداية نفتح Terminal أو CMD في مسار مشروع Next.js ثم ننفّذ الأمر التالي :

yarn add next-translate

ثم ننفذ الأمر التالي لتثبيت إضافة أخرى للمكتبة السابقة :

yarn add -D next-translate-plugin

إعداد ملف i18n

في البداية لننشئ ملف i18n.js في المسار الرئيسي لمشروعنا، ثم نضيف الكود التالي :

module.exports = {

"locales": ["ar", "en"],

"defaultLocale": "ar",

"pages": {

"*": ["common"],

},

"loadLocaleFrom": (lang, ns) => import(`./public/locales/${lang}/${ns}.json`).then((m) => m.default),

}

شرح الكود

كل ما يهمنا هو المفاتيح الموجودة في الكود، و هي كالتالي :

locales : هنا نمرر مصفوفة اللغات التي سندعمها في موقعنا.

defaultLocale : من خلال هذا المفتاح نحدّد اللغة الإفتراضية و في مثالنا تم تحديد اللغة العربية كلغة إفتراضية للموقع.

pages هنا يمكننا تحديد ملفات الترجمة لكل صفحة فمثلًا يمكن تحديد ملف ترجمة أو أكثر لكل صفحة على حده فقط، مثال :

"pages" : {
"*" : ["common"],
"/": ["home", "example"],
"/about": ["about"]
}

loadLocaleFrom دالة لتحديد مسار المخصص لملفات الترجمة، سيتم شرحها لاحقًا في قسم الخاص بها.

ملاحظة : إذا كنت لا ترغب بتغيير مسار مجلد locales فيمكنك إعداد ملف i18n.json بدل i18n.js.

ضبط ملف next.config

نحتاج إلى تعديل ملف next.config.js ليصبح كالتالي :

/** @type {import('next').NextConfig} */
const nextTranslate = require('next-translate-plugin')

const nextConfig = {
  reactStrictMode: false,
  i18n: {
    locales: ['ar', 'en'],
    defaultLocale: 'ar',
    localeDetection: false,
  },
}
module.exports = nextTranslate(nextConfig);

ملاحظة : ستلاحظ إختلاف بين الكود السابق و الكود المرادف له في وثائق next-translate و يعود سبب ذلك، هو تجربتي حيث ضبط اللغة الإفتراضية و إلغاء معرفة اللغة تلقائيًا لم يضبط إلا بهذه الطريقة.

يهمّنا من الكود السابق هو الكائن i18n و سأشرح كل مفتاح و وظيفته :

locales : هنا نمرر مصفوفة اللغات التي سندعمها في موقعنا.

defaultLocale : من خلال هذا المفتاح نحدّد اللغة الإفتراضية و في مثالنا تم تحديد اللغة العربية كلغة إفتراضية للموقع.

localeDetection : لإلغاء خاصية تحديد اللغة تلقائيًا من خلال لغة المتصفح.

إنشاء ملفات الترجمة

في البداية ننشئ مجلد locales في مسار الرئيسي للمشروع حيث يكون :

/locales/

و هو المسار الإفتراضي لـ next-translate، ثم ننشئ مجلد لكل لغة بداخله حيث يصبح مسارهم :

/locales/ar/
/locales/en/

بداخل كل مجلد لغة ننشئ ملفات json التي نحتاجها، مثال على إنشاء ملف common :

/locales/ar/common.json
/locales/en/common.json

في ملف common.json المتواجد في مجلد ar نضع ترجمات اللغة العربية، مثال :

{
    "hello" : "مرحبًا",
    "language" : "اللغة",
    "change_language" : "تغيير اللغة",
    "arabic" : "العربية",
    "english" : "English"
}

اللغة الإنجليزية en :

{
    "hello" : "Hello",
    "language" : "Language",
    "change_language" : "Change language",
    "arabic" : "العربية",
    "english" : "English"
}

في حال أردت تغيير مسار المجلد locales لمسار آخر، مثلًا بداخل مجلد public حينئذ تحتاج إلى استخدام دالة loadLocaleFrom و ضبط المسار، مثال على ذلك :

module.exports = {
  "locales": ["ar", "en"],
  "defaultLocale": "ar",
  "pages": {
    "*": ["common"],
  },
  "loadLocaleFrom": (lang, ns) => import(`./public/locales/${lang}/${ns}.json`).then((m) => m.default),
}

إعداد ملف middleware.ts

هذه الخطوة فقط لمن يستخدم الهيكلة الجديدة App router لـ Next.js، في حال كُنت تستخدم src directory فيمكن التخطّي و الذهاب للخطوة التالية.

هذه الخطوة من أجل حل مشكلة ظهور صفحة 404 عند تغيير اللغة الإفتراضية التي أخترناها للغة أخرى أو العكس.

في البداية ننشئ ملف middleware.ts في الجذر الرئيسي للمشروع بحيث يكون :

/middleware.ts

ثم نضيف له الكود التالي :

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import i18n from "./i18n";

export function middleware(request: NextRequest) {
  const locale = request.nextUrl.locale || i18n.defaultLocale;
  request.nextUrl.searchParams.set("lang", locale);
  request.nextUrl.href = request.nextUrl.href.replace(`/${locale}`, "");
  return NextResponse.rewrite(request.nextUrl);
}

و بهذه الطريقة لن تظهر لنا صفحة 404 مجددًا عند تغيير اللغة.

إضافة تعدد اللغات للصفحات

بعد ضبط الإعدادات و إنشاء ملفات اللغات، كل ما تبقّى لدينا هو جعل الصفحات تدعم تعدد اللغات، و يمكن ذلك من خلال استخدام useTranslation التي توفّرها المكتبة، و مثال على استخدامها :

import useTranslation from 'next-translate/useTranslation';

export default function Home() {
  const { t } = useTranslation('common');
  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-24 gap-4">
      <div className='font-noto-sans font-bold text-2xl'>
        {t('hello')}
      </div>
    </main>
  )
}

الآن كما نرى أضفناها للصفحة، و تم تمرير اسم namespace و الذي بمثالنا اسمه common، و تمرير مفتاح الترجمة إلى t، الآن عند الذهاب إلى الصفحة سنجد بأن الترجمة قد ظهرت.

تمرير نصوص و قيّم إلى الترجمات

يمكنك تمرير النصوص أو الأرقام إلى الترجمات لتكون متضمنة في الترجمة من خلال الطريقة التالية :

{
    "welcome_user": "مرحبًا {{name}} مُجددًا",
    "your_age": "عمرك {{age}} سنة",
}

حيث نفتح أقواس معكوفة متداخلة و نمرر اسم المفتاح لنتعامل معه من خلال t، كما سيتوضح معنا في المثال التالي :

import useTranslation from 'next-translate/useTranslation';

export default function Home() {
  const { t } = useTranslation('common');
  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-24 gap-4">
      <div className='font-noto-sans font-bold text-2xl'>
        {t('welcome_user', { name: "سليمان" })}
      </div>
      <div className='font-noto-sans font-semibold text-xl'>
        {t('your_age', { age: 30 })}
      </div>
    </main>
  )
}

كل ما نحتاجه هو تمرير كائن كمعلّم ثاني parameter لـ t و نعطيه قيمة كما في المثال السابق.

يمكنك ضبط ترجمة للمعدودات

توفّر مكتبة next-translate طريقة للتعامل مع الترجمات لصيغة الجمع "المعدود" من خلال إضافة لاحقة Suffix لمفتاح الترجمة، و هو أمر جيد لضبط ترجمات و بالذات صيغة المعدود في اللغة العربية مثل ( قلم - قلمان - أقلام )، و هذه اللواحق هي :

0 يمثّل صفر.

one يمثّل المعدود 1.

two يمثّل المعدود اثنان 2.

few تمثّل المعدود من 3 إلى 10.

many تمثّل المعدود من 11 إلى 99.

other من 100 فأعلى.

مثال على طريقة كتابتها :

{
    "cart_message_0": "السلة الفارغة",
    "cart_message_one": "تحتوي السلة على منتج واحد {{count}}",
    "cart_message_two": "تحتوي السلة على منتجان {{count}}",
    "cart_message_few": "تحتوي السلة على {{count}} منتجات",
    "cart_message_many": "تحتوي السلة على {{count}} منتج",
    "cart_message_other": "تحتوي السلة على {{count}} منتج",
}

طريقة الإستخدام مثل طريقة تمرير السابقة.

إضافة تغيير اللغة

هنا سأتطرّق إلى طريقتين، طريقة لـ app router و طريقة لـ src directory.

app router

الكود :

import useTranslation from 'next-translate/useTranslation';
import Link from 'next/link';

export default function Home() {
  const { t } = useTranslation('common');
  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-24 gap-4">
      <div className='font-noto-sans font-bold text-2xl'>
        {t('hello')}
      </div>
      <div className='font-noto-sans'>{t('change_language')}</div>
      <div className="flex gap-4">
        <Link
          className='bg-cyan-800 font-semibold font-noto-sans rounded-2xl w-28 text-center py-2'
          href="/?lang=ar" as="/">
          {t('arabic')}
        </Link>
        <Link
          className='bg-cyan-800 font-semibold font-noto-sans rounded-2xl w-28 text-center py-2'
          href="/?lang=en" as="/en">
          {t('english')}
        </Link>
      </div>
    </main>
  )
}

من أجل إضافة زر تغيير اللغة إلى اللغة العربية و كذلك زر تغيير اللغة إلى اللغة الإنجليزية سنستخدم مكوّن component و هو Link التي توفّره nextjs و نضبطه بحيث يكون قيمة href اسم الباراميتر و هو lang و قيمته هي اللغة المحددة، و من أجل ضبط url ليصبح فقط اللغة المختارة دون query param يكون ذلك من خلال as.

ملاحظة : في اللغة العربية مررنا قيمة / لـ خاصية as، و ذلك بأنّه لا توجد حاجة لإظهار ما هي اللغة الإفتراضية في الرابط، و نكتفي بإظهار لغات الأخرى في الرابط.

src directory

الكود

import useTranslation from 'next-translate/useTranslation';
import setLanguage from 'next-translate/setLanguage'

export default function Home() {
  const { t } = useTranslation('common');
  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-24 gap-4">
      <div className='font-noto-sans font-bold text-2xl'>
        {t('hello')}
      </div>
      <div className='font-noto-sans'>{t('change_language')}</div>
      <div className="flex gap-4">
        <button onClick={async () => await setLanguage('ar')}>{t('arabic')}</button>
        <button onClick={async () => await setLanguage('en')}>{t('english')}</button>
      </div>
    </main>
  )
}

كل ما نحتاجه هو إستيراد و استخدام دالة setLanguage، و طريقة استخدامها هو إضافتها لـ onClick و تمرير اللغة المراد استخدامها كما في الكود السابق.

الختام

في ختام التدوينة أود أن أنوّه بأنّي لم أشرح كافّة المكتبة و النقاط المهمة، فمثلًا هناك getT و التي تمكّننا من استخدام الترجمات من جهة server-side في api route و getStaticProps و غيرها، و كذلك مكوّن Trans و الذي يسمح لنا بتضمين وسوم html في الترجمات، و لكني أرجو إن وُفّقت بتغطية أهم النقاط التي يجب معرفتها في المكتبة.

رابط المكتبة

next-translate - Github

تصميم@sulealothman