ما الجديد

Next.js دعم أكثر من لغة لتطبيق Next.js

بداية سلسلة الدروس
الدرس السابق

Abu Ahmed

الإدارة
إنضم
16 أبريل 2023
المشاركات
5
مستوى التفاعل
0
الإقامة
KSA
لغة البرمجة المفضلة
C#
الجنس
ذكر

في الدرس الساق , قمنا بإنشاء تطبيق Next.js جديد , و شرحنا هيكل ملفات و مجلدات التطبيق. في هذا الدرس بإذن اللله , سوف نقوم بالنظر و تنفيذ التعريب أو التدويل (i18n) , أو بالأصح جعل التطبيق يدعم أكثر من لغة.

أكواد الدرس​

يمكنك البداء من هذا المصدر:
Git:
git clone -b start-here https://github.com/abu-ahmed-bs/tutorial-shop-app-nextjs.git
و للحصول على الكود بعد تطبيق الدرس:
Git:
git clone -b 2-implement-i18n https://github.com/abu-ahmed-bs/tutorial-shop-app-nextjs.git

إضافة المكتبات اللازمة​

أولا يجب أن نقوم بإضافة بعض المكتبات الازمة التي سوف تساعدنا للوصول الى الهدف من الدرس , وهو دعم أكثر من لغة في تطبيق Next.js. لإضافة المكتبات نقوم بفتح Terminal جديد و إصدار الأمر التالي:

Bash:
npm install @formatjs/intl-localematcher negotiator

  • formatjs : هي مكتبة javascript تساعد على تعريب و تدويل تطبيقات javascript.
  • negotiator : هي مكتبة Node.js تساعد على قراءة Accept HTTP headers و هي مجموعة من ال Headers يقوم المتصف بإرساله للخوادم لتساعد الخوادم في تحديد لغة المتصفح على سبيل المثال.

خطوات العمل​

بعد إضافة المكتبات اللازمة , يجب ان نقوم بإنشاء الملفات التالية في المكان المخصص لها (في حال المجلد غير موجود , قم بإنشائه):

إسم الملف او المجلدمكان الملفالغرض من الملف
ملف middleware.ts
<root>​
الـ middleware هو عبارة عن أكواد يتم تشغيلها عندما يتم زيارة صفحة معينة. سنقوم بكتباة أكواد لاحقا للتعرف على لغة المتصفح و تحويل المستخدم للغه المفضل لديه.
ملف i18n-config.ts
<root>​
هنا يتم تحديد إعدادات اللغات التي يدعمها التطبيق. مع تحديد اللغة الإفتراضيه.
ملف locals.ts
root>/types>​
سوف نقوم بكتابة دالة هنا للحصول على العبارات حسب اللغة المختارة.
ملف ar.json
root>/dictionaries>​
سيحتوي الملف على مفاتيح العبارات عربية.
ملف en.json
root>/dictionaries>​
سيحتوي الملف على مفاتيح العبارات الإنجليزية.
مجلد [lang]
<root>/app​
المجلد سيحتوي على صفحات الموقع.
مجلد middlewares
<root>/middleswares​
سيحتوي المجلد على الملفات المتعلقة بـ middlewares.
ملف types.ts
<root>/middlesware​
يحتوي على تعريف انواع الـ middlesware
ملف stackMiddlewares.ts
<root>/middlesware​
هذا النوع من الـ middleware سيتم إستخدامه لإضافة الـmiddleware الذي سيحدد لغة التطبيق حسب لغة المتصفح المفضلة.
ملف withLocalization.ts
<root>/middlesware​
الـ middleware الذي سيحتوي على أكواد تحديد غلة التطبيق حسب لغة المتصفح.


إنشاء ملف local.ts​

نقوم بإنشاء الملف local.ts و نضع الكود التالي بداخله:

JavaScript:
export const i18n = {
  defaultLocale: 'ar', // اللغة الإفتراضية هنا
  locales: ['en', 'ar'], // اللغات المدعومة من التطبيق هنا
} as const

export type Locale = typeof i18n['locales'][number]


إنشاء ملف ar.json و ملف en.json​

نقوم بإنشاء الملفين حسب الجدول أعلاه و نضع أكواد json التالية:

JSON:
// <root>/dictionaries/ar.json
{
"direction": "rtl",
  "pages": {
    "home": {
      "welcome" : "مرحبا بك في متجرنا الجديد."
    }
  }
}


//<root>/dictionaries/en.json
{
"direction": "ltr",
  "pages": {
    "home": {
      "welcome" : "Welcome to our new shop."
    }
  }
}


إنشاء مجلد middlewares​

نقوم بإنشاء مجلد middlewares و من ثم نقوم بإنشاء ملف types.ts و stackMiddleware.ts و أيضا ملف withLocalization.ts و من ثم نضع الأكواد التالية في كل ملف:

JavaScript:
// <root>/middleswares/types.ts
import { NextMiddleware } from "next/server";
export type MiddlewareFactory = (middleware: NextMiddleware) => NextMiddleware;
}
JavaScript:
// <root>/middleswares/stackMiddlewares.ts
import { NextMiddleware, NextResponse } from "next/server";
import { MiddlewareFactory } from "./types";
export function stackMiddlewares(
  functions: MiddlewareFactory[] = [],
  index = 0
): NextMiddleware {
  const current = functions[index];
  if (current) {
    const next = stackMiddlewares(functions, index + 1);
    return current(next);
  }
  return () => NextResponse.next();
}
JavaScript:
import {NextFetchEvent, NextMiddleware, NextResponse} from 'next/server'
import type { NextRequest } from 'next/server'

import { i18n } from '@/i18n-config'

import { match as matchLocale } from '@formatjs/intl-localematcher'

// @ts-ignore
import Negotiator from 'negotiator'
import {MiddlewareFactory} from "@/middlewares/types";

function getLocale(request: NextRequest): string | undefined {
// Negotiator expects plain object so we need to transform headers
  const negotiatorHeaders: Record<string, string> = {}
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value))

// Use negotiator and intl-localematcher to get best locale
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages()
// @ts-ignore locales are readonly
  const locales: string[] = i18n.locales
  return matchLocale(languages, locales, i18n.defaultLocale)
}


export const withLocalization: MiddlewareFactory = (next:NextMiddleware) => {
return async (request: NextRequest, _next: NextFetchEvent) => {
const pathname = request.nextUrl.pathname



    // Check if there is any supported locale in the pathname
    const pathnameIsMissingLocale = i18n.locales.every(
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
    )

// Redirect if there is no locale
    if (pathnameIsMissingLocale) {
const locale = getLocale(request)

// e.g. incoming request is /products
      // The new URL is now /en-US/products
      return NextResponse.redirect(new URL(`/${locale}/${pathname}`, request.url))
    }

return next(request,_next);
  };
};

إنشاء ملف middleware.ts​

نقوم بإضافة الكود التالي في ملف middleware.ts , سيقوم Next.js بتشغيل الملف تلقئيا عند زيارة أي صفحة:

JavaScript:
// middleware.ts
import { stackMiddlewares } from "middlewares/stackMiddlewares";
import { withLocalization } from "middlewares/withLocalization";


const middlewares = [withLocalization];
export default stackMiddlewares(middlewares);

export const config = {
// Matcher ignoring `/_next/` and `/api/`
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}


إنشاء و تجهيز مجلد [lang]​

بعد إنشاء و تجهيز ملفات اللغة , يجب تغيير هيكل المجلد app بحيث نقوم بإنشاء مجلد مخصص ليقوم بإلتقاط اللغة من رابط الصفحة , فعلى سبيل المثال اذا كانت لغة المتصفح عربية , سيقوم الـ middleware بتوجيه المتصفح للرابط http://localhost:3000/ar , لكي يستطيع التطبيق قرأة الجزئية ar في الرابط , يجب إنشاء مجلد بهذا الإسم و نقل جميع الملفات من مجلد app الى مجلد [lang] و الذي سكون داخل مجلد app. سيصبح ترتيب المجلدات و الملفات كالتالي:
كود:
app
└───[lang]
        layout.tsx
        page.tsx
بعد ذلك نقوم بالتعديل على ملف layout.tsx :
JSX:
...
import {getDictionary} from "@/types/locals"; // أضفنا هذا السطر لجلب مفاتيح اللغة

...

interface RootLayoutProps {
  children: React.ReactNode,
  params: { lang: string } // أضفنا هذا السطر لإضافة اللغة المختارة من الرابط
}

export default async function RootLayout({ children,params }: RootLayoutProps) { // أضفنا params و async //
  // @ts-ignore
  const dictionary = await getDictionary(params.lang) // أضفنا هذا السطر لإختيار و جلب اللغة المختارة من الرابط للموقع

  return (
    <>
      <html lang={params.lang} dir={dictionary.direction} suppressHydrationWarning> // أضفنا لغة الموقع المخاترة و إتجاه الصفحة
      ...
      </html>
    </>
  )
}
بعد ذالك نقوم بالتعديل على ملف page.tsx لنقوم بوضع المحتوى حسب لغة الموقع حسب الرابط:
JSX:
import Link from "next/link"

import { siteConfig } from "@/config/site"
import { buttonVariants } from "@/components/ui/button"
import {getDictionary} from "@/types/locals";

interface IndexPageProps {
  params: { lang: string }
}

export default async  function IndexPage({params} : IndexPageProps) {

  // @ts-ignore
  const dictionary = await getDictionary(params.lang)
  return (
    <section className="container grid items-center gap-6 pb-8 pt-6 md:py-10">
      <div className="flex max-w-[980px] flex-col items-start gap-2">
        <h1 className="text-3xl font-extrabold leading-tight tracking-tighter md:text-4xl">
          {dictionary.pages.home.welcome}
        </h1>
      </div>
    </section>
  )
}


و بهذا , اذا قمنا بالذهاب للرابط http://localhost:3000/ar سيظهر الموقع باللغة العربية و اذا ذهبنا الى http://localhost:3000/en سيظهر باللغة الإنجليزية ;)

سنقوم في الدروس القادمة بتطوير التطبيق و إضافة زر لتغيير اللغة و نقوم بالدخول في تفاصيل كيفية قيام Next.js بالتعرف على لغة المتصفح بشكل آلي :geek:
 
التعديل الأخير:
أعلى