Abu Ahmed
الإدارة
في الدرس الساق , قمنا بإنشاء تطبيق Next.js جديد , و شرحنا هيكل ملفات و مجلدات التطبيق. في هذا الدرس بإذن اللله , سوف نقوم بالنظر و تنفيذ التعريب أو التدويل (i18n) , أو بالأصح جعل التطبيق يدعم أكثر من لغة.
github.com
و للحصول على الكود بعد تطبيق الدرس:
github.com
بعد ذلك نقوم بالتعديل على ملف layout.tsx :
بعد ذالك نقوم بالتعديل على ملف page.tsx لنقوم بوضع المحتوى حسب لغة الموقع حسب الرابط:
و بهذا , اذا قمنا بالذهاب للرابط http://localhost:3000/ar سيظهر الموقع باللغة العربية و اذا ذهبنا الى http://localhost:3000/en سيظهر باللغة الإنجليزية
سنقوم في الدروس القادمة بتطوير التطبيق و إضافة زر لتغيير اللغة و نقوم بالدخول في تفاصيل كيفية قيام Next.js بالتعرف على لغة المتصفح بشكل آلي
أكواد الدرس
يمكنك البداء من هذا المصدر:GitHub - abu-ahmed-bs/tutorial-shop-app-nextjs: Repository to hold source code for next.js shop app tutorial
Repository to hold source code for next.js shop app tutorial - abu-ahmed-bs/tutorial-shop-app-nextjs
Git:
git clone -b start-here https://github.com/abu-ahmed-bs/tutorial-shop-app-nextjs.git
GitHub - abu-ahmed-bs/tutorial-shop-app-nextjs at 2-implement-i18n
Repository to hold source code for next.js shop app tutorial - GitHub - abu-ahmed-bs/tutorial-shop-app-nextjs at 2-implement-i18n
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 |
| يحتوي على تعريف انواع الـ middlesware | |
ملف stackMiddlewares.ts |
| هذا النوع من الـ middleware سيتم إستخدامه لإضافة الـmiddleware الذي سيحدد لغة التطبيق حسب لغة المتصفح المفضلة. | |
ملف withLocalization.ts |
| الـ 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
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>
</>
)
}
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 بالتعرف على لغة المتصفح بشكل آلي
التعديل الأخير: