Next.js: التدويل (i18n) والوسيط (Middleware)
آخر تحديث: 2026-08-26
التدويل ليس مجرد ترجمة — إنه عملية هندسية منهجية تشمل التوجيه واكتشاف اللغة وتخطيط RTL.
1. ما ستتعلمه
next-intlتثبيت ودمج إطار العمل الدولي- Middleware سياسات التوجيه (اكتشاف
Accept-Language+ استمرارية Cookie + إعادة كتابة URL) - إدارة قواميس متعددة اللغات (
en.json/ar.jsonترجمة منظمة) generateStaticParamsالتوليد المسبق للصفحات الثابتة متعددة اللغات- دعم تخطيط RTL (تكييف اتجاه النص العربي)
- التنسيق الدولي للتواريخ والأرقام والعملات
2. القصة الحقيقية لمدير منتج دولي
(1) نقطة الألم: 47 تقييماً سلبياً من مستخدمين في الشرق الأوسط
بعد إطلاق TaskFlow الخاص بأليس، تلقت عدداً كبيراً من الشكاوى من مستخدمين في السعودية والإمارات:
"لوحة التحكم لا تزال بالإنجليزية، صيغة التاريخ 07/06/2026 لا معنى لها بالنسبة لهم (السعودية تستخدم 06/07/2026)، والرقم 1,234.56 يظهر بشكل خاطئ في الدول العربية. الأسوأ من ذلك — الصفحة تنتقل من اليمين إلى اليسار، والتخطيط بالكامل معكوس."
| المشكلة | ملاحظات المستخدمين | عدد المستخدمين المتأثرين |
|---|---|---|
| الصفحة غير مترجمة للعربية | "لا يمكن فهم القائمة" | 47 تقييماً سلبياً |
| صيغة التاريخ غير صحيحة | "التاريخ معكوس" | 32 شكوى |
| التخطيط غير محسّن لـ RTL | "الزر في الجهة اليمنى" | 28 شكوى |
| خطأ في رمز العملة | "السعر معروض بالدولار" | 15 شكوى |
(2) حل next-intl + Middleware
استخدم
next-intlلتوفير إطار عمل دولي؛ الوسيط يقوم بالتبديل تلقائياً بناءً على لغة المتصفح.
// middleware.ts — نواة التوجيه الدولي
import createMiddleware from 'next-intl/middleware'
export default createMiddleware({
locales: ['zh', 'en', 'ja', 'ar'],
defaultLocale: 'zh',
localePrefix: 'always' // URL يتضمن دائماً بادئة اللغة
})
export const config = {
matcher: ['/((?!_next|api|favicon.ico).*)']
}
(3) النتائج
| البعد | قبل إعادة التصميم | بعد إعادة التصميم |
|---|---|---|
| اللغات المدعومة | الصينية فقط | 4 لغات: الصينية والإنجليزية واليابانية والعربية |
| اكتشاف اللغة | اختيار يدوي | اكتشاف تلقائي للغة المتصفح |
| دعم RTL | ❌ غير مدعوم | ✅ تخطيط معكوس بالكامل |
| التاريخ/الأرقام | صيغة صينية مبرمجة يدوياً | تنسيق تلقائي عبر Intl API |
| صافي نقاط المروجين (NPS) للمستخدمين السعوديين | 32 | 78 |
3. تثبيت next-intl وهيكل المشروع
(1) التثبيت وهيكل المجلدات
npm install next-intl
graph TB
A[next-intl] --> B[i18n/request.ts<br/>إعدادات الخادم]
A --> C[messages/<br/>قواميس الترجمة]
C --> D[en.json<br/>الإنجليزية]
C --> E[zh.json<br/>الصينية]
C --> F[ja.json<br/>اليابانية]
C --> G[ar.json<br/>العربية]
A --> H[middleware.ts<br/>اكتشاف اللغة + إعادة الكتابة]
A --> I[app/[locale]/<br/>مقاطع التوجيه الديناميكية]
style A fill:#cce5ff
style H fill:#d4edda
style I fill:#fff3cd
| الملف | الغرض |
|---|---|
i18n/request.ts |
نقطة دخول إعداد i18n من جانب الخادم |
messages/en.json |
قاموس اللغة الإنجليزية |
messages/ar.json |
قاموس اللغة العربية (مع ترميز RTL) |
middleware.ts |
اكتشاف تلقائي للغة + إعادة كتابة URL |
app/[locale]/ |
مقطع توجيه ديناميكي للغة |
(2) الإعداد الأولي
// i18n/request.ts — إعدادات خادم next-intl
import { getRequestConfig } from 'next-intl/server'
import { hasLocale } from 'next-intl'
import { routing } from './routing'
export default getRequestConfig(async ({ requestLocale }) => {
const requested = await requestLocale
const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default
}
})
// i18n/routing.ts — مشاركة إعدادات التوجيه
import { defineRouting } from 'next-intl/routing'
export const routing = defineRouting({
locales: ['zh', 'en', 'ja', 'ar'],
defaultLocale: 'zh',
localeDetection: true,
localePrefix: 'as-needed' // حذف البادئة للغة الافتراضية
})
▶ مثال: إعداد Middleware كامل
المخرجات:
يصدر: routing.
// middleware.ts — اكتشاف اللغة + استمرارية Cookie
import createMiddleware from 'next-intl/middleware'
import { NextRequest, NextResponse } from 'next/server'
const intlMiddleware = createMiddleware({
locales: ['zh', 'en', 'ja', 'ar'],
defaultLocale: 'zh',
localeDetection: true,
localePrefix: 'always'
})
export default function middleware(req: NextRequest) {
const { pathname } = req.nextUrl
// API / _next لا تقم بالمعالجة
if (pathname.startsWith('/api') || pathname.startsWith('/_next') || pathname === '/favicon.ico') {
return NextResponse.next()
}
return intlMiddleware(req)
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}
المخرجات:
يعالج الوسيط الطلبات قبل وصولها إلى معالجات المسارات.
4. سياسات توجيه Middleware
(1) ثلاثة أنماط للتوجيه
graph TB
A[وصول المستخدم] --> B{فحص Middleware}
B --> C[فحص Cookie<br/>next-intl-locale]
B --> D[فحص Accept-Language<br/>ترويسة HTTP]
B --> E[اللغة الافتراضية]
C --> F[تم الاختيار → استخدام الخيار]
D --> G[زيارة أولى → اكتشاف لغة المتصفح]
E --> H[لا يوجد تطابق → اللغة الافتراضية]
F --> I[إعادة كتابة URL /{locale}/...]
G --> I
H --> I
I --> J[كتابة Cookie<br/>استمرارية الخيار]
style B fill:#fff3cd
style I fill:#d4edda
style J fill:#cce5ff
| نمط التوجيه | صيغة URL | localePrefix |
السيناريوهات المناسبة |
|---|---|---|---|
| بادئة دائمة | /en/about / /ar/about |
'always' |
عرض متساوٍ لجميع اللغات |
| بادئة حسب الحاجة | /about (افتراضي) / /en/about |
'as-needed' |
اللغة الافتراضية صديقة لـ SEO |
| بدون بادئة | /about |
'never' |
مدفوع بـ Cookie (غير موصى به) |
(2) اكتشاف Accept-Language + استمرارية Cookie
// middleware.ts — تحكم يدوي في منطق الاكتشتف
import { NextRequest, NextResponse } from 'next/server'
import { routing } from './i18n/routing'
const COOKIE_NAME = 'NEXT_LOCALE'
export default function middleware(req: NextRequest) {
const { pathname } = req.nextUrl
const cookieLocale = req.cookies.get(COOKIE_NAME)?.value
const acceptLanguage = req.headers.get('Accept-Language')?.split(',')[0]?.split('-')[0]
const browserLocale = acceptLanguage && routing.locales.includes(acceptLanguage as any) ? acceptLanguage : null
// مطابقة اللغات
const matchedLocale = cookieLocale || browserLocale || routing.defaultLocale
// المسار يحتوي بالفعل على بادئة لغة
const pathLocale = routing.locales.find((l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`)
if (pathLocale) return NextResponse.next()
// إعادة كتابة URL وتعيين Cookie
const url = new URL(`/${matchedLocale}${pathname}`, req.url)
const res = NextResponse.rewrite(url)
res.cookies.set(COOKIE_NAME, matchedLocale, { maxAge: 60 * 60 * 24 * 365 })
return res
}
export const config = { matcher: ['/((?!api|_next|favicon.ico).*)'] }
▶ مثال: مكون تبديل اللغة
المخرجات:
يعرض واجهة مكون middleware.
// components/LocaleSwitcher.tsx
'use client'
import { useLocale } from 'next-intl'
import { usePathname, useRouter } from '@/i18n/routing'
import { useTransition } from 'react'
const locales = [
{ code: 'zh', label: 'Chinese' },
{ code: 'en', label: 'English' },
{ code: 'ja', label: 'Japanese' },
{ code: 'ar', label: 'العربية' }
]
export default function LocaleSwitcher() {
const locale = useLocale()
const router = useRouter()
const pathname = usePathname()
const [isPending, startTransition] = useTransition()
const switchLocale = (nextLocale: string) => {
startTransition(() => {
router.replace(pathname, { locale: nextLocale })
})
}
return (
<select
value={locale}
onChange={(e) => switchLocale(e.target.value)}
disabled={isPending}
style={{ padding: '0.25rem 0.5rem' }}
>
{locales.map((l) => (
<option key={l.code} value={l.code}>{l.label}</option>
))}
</select>
)
}
المخرجات:
يعرض واجهة مكون ▶ مثال: مكون تبديل اللغة كما هو موضح في القسم.
5. إدارة القواميس متعددة اللغات
(1) هيكل ملفات القاموس
// messages/zh.json
{
"nav": {
"home": "الرئيسية",
"dashboard": "لوحة التحكم",
"projects": "المشاريع",
"settings": "الإعدادات",
"signIn": "تسجيل الدخول",
"signOut": "تسجيل الخروج"
},
"home": {
"title": "TaskFlow - منصة التعاون الجماعي",
"subtitle": "نساعد أكثر من 10,000 فريق على العمل بكفاءة",
"cta": "ابدأ مجاناً"
},
"dashboard": {
"welcome": "مرحباً بعودتك، {name}",
"stats": {
"members": "{count} أعضاء",
"projects": "{count} مشروعاً",
"tasks": "{count} مهمة"
},
"activity": "النشاطات الأخيرة"
},
"common": {
"loading": "جارٍ التحميل...",
"error": "حدث خطأ",
"save": "حفظ",
"cancel": "إلغاء",
"delete": "حذف",
"confirm": "تأكيد"
}
}
// messages/ar.json
{
"nav": {
"home": "الرئيسية",
"dashboard": "لوحة التحكم",
"projects": "المشاريع",
"settings": "الإعدادات",
"signIn": "تسجيل الدخول",
"signOut": "تسجيل الخروج"
},
"home": {
"title": "TaskFlow - منصة التعاون الجماعي",
"subtitle": "نساعد أكثر من 10,000 فريق على العمل بكفاءة",
"cta": "ابدأ مجاناً"
},
"dashboard": {
"welcome": "مرحباً بعودتك، {name}",
"stats": {
"members": "{count} أعضاء",
"projects": "{count} مشروعاً",
"tasks": "{count} مهمة"
},
"activity": "النشاطات الأخيرة"
},
"common": {
"loading": "جارٍ التحميل...",
"error": "حدث خطأ",
"save": "حفظ",
"cancel": "إلغاء",
"delete": "حذف",
"confirm": "تأكيد"
}
}
(2) استخدام الترجمات في المكونات
// مكون خادم: استخدام دالة ()t
import { getTranslations } from 'next-intl/server'
export default async function HomePage() {
const t = await getTranslations('home')
return (
<div>
<h1>{t('title')}</h1>
<p>{t('subtitle')}</p>
<button>{t('cta')}</button>
</div>
)
}
// مكون عميل: خطاف useTranslations
'use client'
import { useTranslations } from 'next-intl'
export default function DashboardHeader({ name }: { name: string }) {
const t = useTranslations('dashboard')
return (
<h1>{t('welcome', { name })}</h1>
)
}
مرحباً بعودتك، Alice
💻 المخرجات (العربية): مرحباً بعودتك، Alice
▶ مثال: ترجمات تحتوي على صيغ الجمع
// القاموس
{
"tasks": {
"remaining": "{count, plural, =0 {لا توجد مهام} one {# مهمة واحدة} other {# مهام}}"
}
}
import { useTranslations } from 'next-intl'
export default function TaskCount({ count }: { count: number }) {
const t = useTranslations('tasks')
return <span>{t('remaining', { count })}</span>
}
المخرجات:
يعرض واجهة مكون TaskCount.
لا توجد مهام
💻 المخرجات (count=1): 1 مهمة واحدة
💻 المخرجات (count=5): 5 مهام
next-intl يستخدم صيغة ICU MessageFormat ويدعم ميزات متقدمة مثل صيغ الجمع والخيارات وتنسيق الأرقام.
6. generateStaticParams: التوليد المسبق للصفحات متعددة اللغات
(1) مبادئ التوليد المسبق الثابت
graph LR
A[generateStaticParams] --> B[إرجاع قائمة اللغات]
B --> C[zh: /zh/blog/post-1]
B --> D[en: /en/blog/post-1]
B --> E[ja: /ja/blog/post-1]
B --> F[ar: /ar/blog/post-1]
C --> G[يتم التوليد أثناء البناء: 4 ملفات HTML]
D --> G
E --> G
F --> G
style A fill:#cce5ff
style G fill:#d4edda
| اللغة | ملف HTML | المسار |
|---|---|---|
| الصينية | out/zh/blog/post-1.html |
/zh/blog/post-1 |
| الإنجليزية | out/en/blog/post-1.html |
/en/blog/post-1 |
| اليابانية | out/ja/blog/post-1.html |
/ja/blog/post-1 |
| العربية | out/ar/blog/post-1.html |
/ar/blog/post-1 |
(2) معامل "locale" في التخطيط الجذري
// app/[locale]/layout.tsx — التخطيط الجذري يستقبل معامل locale
import { NextIntlClientProvider } from 'next-intl'
import { getMessages, getTranslations } from 'next-intl/server'
import { notFound } from 'next/navigation'
import { routing } from '@/i18n/routing'
type Props = { children: React.ReactNode; params: Promise<{ locale: string }> }
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params
// التحقق من صحة locale
if (!routing.locales.includes(locale as any)) notFound()
const messages = await getMessages()
return (
<html lang={locale} dir={locale === 'ar' ? 'rtl' : 'ltr'}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
)
}
▶ مثال: توليد ثابت لمقالات المدونة متعددة اللغات
المخرجات:
يعرض واجهة مكون LocaleLayout.
// app/[locale]/blog/[slug]/page.tsx — SSG متعدد اللغات
import { getTranslations } from 'next-intl/server'
import { routing } from '@/i18n/routing'
type Props = { params: Promise<{ locale: string; slug: string }> }
// توليد صفحات المقالات مسبقاً لجميع اللغات
export async function generateStaticParams() {
const posts = await fetch('https://api.taskflow.io/blog/posts').then(r => r.json())
return routing.locales.flatMap((locale) =>
posts.map((post: { slug: string }) => ({ locale, slug: post.slug }))
)
}
export async function generateMetadata({ params }: Props) {
const { locale, slug } = await params
const post = await fetch(`https://api.taskflow.io/blog/${slug}`).then(r => r.json())
return {
title: locale === 'en' ? post.title : post.titleLocalized[locale],
alternates: {
languages: Object.fromEntries(
routing.locales.map((l) => [l, `https://taskflow.io/${l}/blog/${slug}`])
)
}
}
}
export default async function BlogPostPage({ params }: Props) {
const { locale, slug } = await params
const t = await getTranslations('blog')
return (
<article>
<h1>{t('title')}</h1>
<p>Locale: {locale} | Slug: {slug}</p>
</article>
)
}
المخرجات:
يعرض: صفحة بعنوان "{t('title')}" والمحتوى المرتبط.
7. دعم تخطيط RTL
(1) الفروقات بين RTL و LTR
graph TB
subgraph تخطيط LTR
A1[الشعار<br/>محاذاة لليسار] --> A2[شريط التنقل<br/>من اليسار إلى اليمين]
A2 --> A3[الشريط الجانبي<br/>محاذاة لليسار]
A3 --> A4[المحتوى<br/>إلى اليمين]
end
subgraph تخطيط RTL
B1[الشعار<br/>محاذاة لليمين] --> B2[شريط التنقل<br/>من اليمين إلى اليسار]
B2 --> B3[الشريط الجانبي<br/>محاذاة لليمين]
B3 --> B4[المحتوى<br/>محاذاة لليسار]
end
style B1 fill:#d4edda
style B2 fill:#d4edda
style B3 fill:#d4edda
| البعد | LTR (الصينية/الإنجليزية/اليابانية) | RTL (العربية) |
|---|---|---|
| اتجاه النص | من اليسار إلى اليمين | من اليمين إلى اليسار |
| شريط التنقل | الشعار على اليسار → القائمة على اليمين | الشعار على اليمين → القائمة على اليسار |
| الشريط الجانبي | يسار | يمين |
| أيقونة السهم | → للأمام |
← للأمام |
| هامش البطاقة | margin-left: auto |
margin-right: auto |
| زر إرسال النموذج | أسفل اليمين | أسفل اليسار |
(2) دعم Tailwind CSS لـ RTL
// app/[locale]/layout.tsx — اختبار RTL وأسماء فئات CSS
export default async function LocaleLayout({ children, params }: {
children: React.ReactNode
params: Promise<{ locale: string }>
}) {
const { locale } = await params
const isRtl = locale === 'ar'
return (
<html lang={locale} dir={isRtl ? 'rtl' : 'ltr'}>
<body className={isRtl ? 'rtl' : 'ltr'}>
{children}
</body>
</html>
)
}
/* app/globals.css — أنماط RTL الأساسية */
.ltr { direction: ltr; }
.rtl { direction: rtl; }
/* عكس شريط التنقل */
.rtl .nav-items { flex-direction: row-reverse; }
.rtl .sidebar { right: 0; left: auto; }
.rtl .main-content { margin-right: 260px; margin-left: 0; }
/* عكس الأيقونات */
.rtl .icon-arrow::before { content: '←'; }
.ltr .icon-arrow::before { content: '→'; }
/* عكس المسافات */
.rtl .ml-auto { margin-right: auto; margin-left: 0; }
.rtl .mr-2 { margin-left: 0.5rem; margin-right: 0; }
▶ مثال: تخطيط لوحة تحكم محسّن لـ RTL
المخرجات:
تم تطبيق أنماط CSS على: .nav-items, .mr-2, .sidebar, .rtl, before, .main-content
// components/DashboardLayout.tsx — تخطيط متوافق مع RTL
export default function DashboardLayout({
sidebar,
children,
locale
}: {
sidebar: React.ReactNode
children: React.ReactNode
locale: string
}) {
const isRtl = locale === 'ar'
return (
<div style={{
display: 'flex',
flexDirection: isRtl ? 'row-reverse' : 'row',
minHeight: '100vh'
}}>
{/* الشريط الجانبي: LTR على اليسار، RTL على اليمين */}
<aside style={{
width: 260,
borderRight: isRtl ? 'none' : '1px solid #e5e7eb',
borderLeft: isRtl ? '1px solid #e5e7eb' : 'none'
}}>
{sidebar}
</aside>
{/* منطقة المحتوى الرئيسية */}
<main style={{
flex: 1,
padding: '2rem',
textAlign: isRtl ? 'right' : 'left'
}}>
{children}
</main>
</div>
)
}
المخرجات:
يعرض: تخطيط لوحة التحكم مع شريط جانبي داكن (روابط لوحة التحكم/المشاريع/الإعدادات) + منطقة المحتوى الرئيسية.
direction: rtl؛ بل تحتاج أيضاً إلى عكس جميع اتجاهات flex والهوامش/المسافات وأيقونات الأسهم ومحاذاة النص.
8. مثال كامل: موقع رسمي بأربع لغات + لوحة تحكم RTL
// app/[locale]/layout.tsx — التخطيط الجذري للتدويل
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
import { routing } from '@/i18n/routing'
import { notFound } from 'next/navigation'
import LocaleSwitcher from '@/components/LocaleSwitcher'
import type { Metadata } from 'next'
type Props = { children: React.ReactNode; params: Promise<{ locale: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params
return {
title: 'TaskFlow',
alternates: {
canonical: `https://taskflow.io/${locale}`,
languages: Object.fromEntries(
routing.locales.map((l) => [l, `https://taskflow.io/${l}`])
)
}
}
}
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params
if (!routing.locales.includes(locale as any)) notFound()
const messages = await getMessages()
const isRtl = locale === 'ar'
return (
<html lang={locale} dir={isRtl ? 'rtl' : 'ltr'}>
<head>
{/* RTL تحميل CSS الخاص بـ */}
{isRtl && <link rel="stylesheet" href="/rtl.css" />}
</head>
<body style={{ margin: 0, fontFamily: 'system-ui, sans-serif' }}>
<NextIntlClientProvider messages={messages}>
<nav style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '1rem 2rem',
borderBottom: '1px solid #e5e7eb',
flexDirection: isRtl ? 'row-reverse' : 'row'
}}>
<strong>TaskFlow</strong>
<LocaleSwitcher />
</nav>
<main style={{ padding: '2rem' }}>{children}</main>
</NextIntlClientProvider>
</body>
</html>
)
}
// app/[locale]/dashboard/page.tsx — لوحة تحكم RTL
import { getTranslations } from 'next-intl/server'
type Props = { params: Promise<{ locale: string }> }
export default async function DashboardPage({ params }: Props) {
const { locale } = await params
const t = await getTranslations('dashboard')
const isRtl = locale === 'ar'
return (
<div>
<h1 style={{ textAlign: isRtl ? 'right' : 'left' }}>{t('welcome', { name: 'Alice' })}</h1>
<div style={{
display: 'flex',
gap: '1rem',
flexDirection: isRtl ? 'row-reverse' : 'row'
}}>
<div style={{ flex: 1, padding: '1.5rem', background: '#f0f9ff', borderRadius: 8 }}>
<h3>{t('stats.members', { count: 12 })}</h3>
</div>
<div style={{ flex: 1, padding: '1.5rem', background: '#fef3c7', borderRadius: 8 }}>
<h3>{t('stats.projects', { count: 45 })}</h3>
</div>
<div style={{ flex: 1, padding: '1.5rem', background: '#ecfdf5', borderRadius: 8 }}>
<h3>{t('stats.tasks', { count: 230 })}</h3>
</div>
</div>
<h2 style={{ textAlign: isRtl ? 'right' : 'left' }}>{t('activity')}</h2>
<div style={{
padding: '1rem',
border: '1px solid #e5e7eb',
borderRadius: 8,
textAlign: isRtl ? 'right' : 'left'
}}>
<p>أليس قامت بإنشاء مشروع "إعادة تصميم الموقع"</p>
<p>بوب أكمل المهمة رقم 47</p>
<p>تشارلي انضم إلى المنظمة</p>
</div>
</div>
)
}
# نتائج المخرجات (الصينية):
TaskFlow
[Language: Chinese ▼]
مرحباً بعودتك، Alice
[12 أعضاء] [45 مشروعاً] [230 مهمة]
النشاطات الأخيرة
أليس قامت بإنشاء مشروع "إعادة تصميم الموقع"
بوب أكمل المهمة رقم 47
تشارلي انضم إلى المنظمة
# نتائج المخرجات (العربية):
TaskFlow
[اختيار اللغة: العربية ▼]
مرحباً بعودتك، Alice
[12 أعضاء] [45 مشروعاً] [230 مهمة]
النشاطات الأخيرة
أليس قامت بإنشاء مشروع "إعادة تصميم الموقع"
بوب أكمل المهمة رقم 47
تشارلي انضم إلى المنظمة
❓ أسئلة شائعة
next-intl أم next-international؟next-intl لديه مجتمع أكبر (5k⭐) وميزات أكثر (صيغ الجمع والاختيار وتنسيق الأرقام واكتشاف RTL). next-international أخف وزناً. هذا البرنامج التعليمي يستخدم next-intl.rewrite تحافظ على محتوى شريط عنوان المتصفح دون تغيير مع التوجيه داخلياً إلى /{locale}/path، وهذا أفضل لـ SEO وتجربة المستخدم. redirect تغير URL، مما قد يكسر الروابط التي شاركها المستخدمون.direction: rtl، يجب عكس اتجاه flex (flex-direction: row-reverse)، وضبط اتجاه الهوامش والمسافات، وعكس أيقونات الأسهم، واستخدام text-align: start/end بدلاً من left/right. يُوصى بالتعامل مع هذا بشكل موحد باستخدام المحدد [dir="rtl"] في globals.css.generateStaticParams يولد ملف HTML منفصلاً لكل لغة؟/zh/blog/post-1 و /en/blog/post-1 و /ar/blog/post-1 هي ثلاثة ملفات HTML منفصلة. كل ملف يحتوي فقط على محتوى اللغة المقابلة له، مما يضمن أسرع سرعة تحميل.{name} لإدراج المتغيرات، و {count, plural, =0 {لا شيء} one {# مهمة} other {# مهام}} للتعامل مع صيغ الجمع. خطافات t() و useTranslations() في next-intl تحلل هذه القوالب تلقائياً.📖 ملخص
next-intlيوفر حلاً شاملاً للتدويل: Middleware (اكتشاف اللغة) + إدارة القواميس + دعم RTL- Middleware يتحقق حسب أولوية: Cookie → Accept-Language → اللغة الافتراضية؛ والنتيجة تُكتب في Cookie للاستمرارية
- القاموس يُدار باستخدام هيكل JSON متداخل ويدعم ICU MessageFormat لصيغ الجمع والمتغيرات والاختيارات
generateStaticParamsيولّد مسبقاً HTML منفصلاً لكل لغة؛ بدون حمل في وقت التشغيل في وضع SSG- دعم RTL يتطلب خاصية
dir+ عكس CSS (اتجاه flex / الهوامش / المسافات / الأيقونات) - التواريخ والأرقام والعملات تُنسق تلقائياً باستخدام
IntlAPI في JavaScript، دون الحاجة لمكتبات إضافية
📝 تمارين
-
تمرين أساسي (⭐): قم بتثبيت
next-intlفي مشروع قائم، وإعداد قواميس بالصينية والإنجليزية، وتنفيذ مكون تبديل اللغة، واستخدامt()لعرض نص شريط التنقل على الصفحة. -
تمرين متقدم (⭐⭐): قم بتنفيذ مدونة SSG كاملة متعددة اللغات:
generateStaticParamsيولّد مسبقاً صفحات المقالات لأربع لغات؛ كل مقالة تحمّل قاموس اللغة المقابل أثناء عملية البناء؛ وتحقق من هيكل ملفات HTML المُنشأة. -
تحدٍ (⭐⭐⭐): قم ببناء صفحة لوحة تحكم تدعم RTL: قم بإعدادها للعربية والإنجليزية، واستخدم بادئة
rtl:وعكسflex-directionفي Tailwind CSS لتنفيذ تكييف كامل للتخطيط للشريط الجانبي والتنقل وبطاقات الإحصائيات. تحقق من أن جميع العناصر معكوسة بشكل صحيح تحتdir="rtl".