Next.js: التدويل (i18n) والوسيط (Middleware)

آخر تحديث: 2026-08-26

التدويل ليس مجرد ترجمة — إنه عملية هندسية منهجية تشمل التوجيه واكتشاف اللغة وتخطيط RTL.

1. ما ستتعلمه



2. القصة الحقيقية لمدير منتج دولي

(1) نقطة الألم: 47 تقييماً سلبياً من مستخدمين في الشرق الأوسط

بعد إطلاق TaskFlow الخاص بأليس، تلقت عدداً كبيراً من الشكاوى من مستخدمين في السعودية والإمارات:

"لوحة التحكم لا تزال بالإنجليزية، صيغة التاريخ 07/06/2026 لا معنى لها بالنسبة لهم (السعودية تستخدم 06/07/2026)، والرقم 1,234.56 يظهر بشكل خاطئ في الدول العربية. الأسوأ من ذلك — الصفحة تنتقل من اليمين إلى اليسار، والتخطيط بالكامل معكوس."

المشكلة ملاحظات المستخدمين عدد المستخدمين المتأثرين
الصفحة غير مترجمة للعربية "لا يمكن فهم القائمة" 47 تقييماً سلبياً
صيغة التاريخ غير صحيحة "التاريخ معكوس" 32 شكوى
التخطيط غير محسّن لـ RTL "الزر في الجهة اليمنى" 28 شكوى
خطأ في رمز العملة "السعر معروض بالدولار" 15 شكوى

(2) حل next-intl + Middleware

استخدم next-intl لتوفير إطار عمل دولي؛ الوسيط يقوم بالتبديل تلقائياً بناءً على لغة المتصفح.

TS
// 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) التثبيت وهيكل المجلدات

BASH
npm install next-intl
100%
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) الإعداد الأولي

TS
// 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
  }
})
TS
// 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 كامل

المخرجات:

TEXT 📖 للعرض فقط
يصدر: routing.
TS
// 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).*)']
}

المخرجات:

TEXT 📖 للعرض فقط
يعالج الوسيط الطلبات قبل وصولها إلى معالجات المسارات.


4. سياسات توجيه Middleware

(1) ثلاثة أنماط للتوجيه

100%
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 (غير موصى به)
TS
// 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).*)'] }
💡 نصيحة: استمرارية Cookie تضمن الاحتفاظ باختيار اللغة عند التنقل بين الصفحات. عندما يقوم المستخدم بتبديل اللغة يدوياً، قم بتحديث قيمة الـ cookie.

▶ مثال: مكون تبديل اللغة

المخرجات:

TEXT 📖 للعرض فقط
يعرض واجهة مكون middleware.
TSX
// 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>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
يعرض واجهة مكون ▶ مثال: مكون تبديل اللغة كما هو موضح في القسم.


5. إدارة القواميس متعددة اللغات

(1) هيكل ملفات القاموس

JSON
// 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": "تأكيد"
  }
}
JSON
// 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) استخدام الترجمات في المكونات

TSX
// مكون خادم: استخدام دالة ()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>
  )
}
TSX
// مكون عميل: خطاف 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

▶ مثال: ترجمات تحتوي على صيغ الجمع

TSX
// القاموس
{
  "tasks": {
    "remaining": "{count, plural, =0 {لا توجد مهام} one {# مهمة واحدة} other {# مهام}}"
  }
}
TSX
import { useTranslations } from 'next-intl'

export default function TaskCount({ count }: { count: number }) {
  const t = useTranslations('tasks')

  return <span>{t('remaining', { count })}</span>
}

المخرجات:

TEXT 📖 للعرض فقط
يعرض واجهة مكون TaskCount.
💻 المخرجات (count=0): لا توجد مهام 💻 المخرجات (count=1): 1 مهمة واحدة 💻 المخرجات (count=5): 5 مهام

💡 نصيحة: next-intl يستخدم صيغة ICU MessageFormat ويدعم ميزات متقدمة مثل صيغ الجمع والخيارات وتنسيق الأرقام.



6. generateStaticParams: التوليد المسبق للصفحات متعددة اللغات

(1) مبادئ التوليد المسبق الثابت

100%
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" في التخطيط الجذري

TSX
// 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>
  )
}

▶ مثال: توليد ثابت لمقالات المدونة متعددة اللغات

المخرجات:

TEXT 📖 للعرض فقط
يعرض واجهة مكون LocaleLayout.
TSX
// 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>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
يعرض: صفحة بعنوان "{t('title')}" والمحتوى المرتبط.


7. دعم تخطيط RTL

(1) الفروقات بين RTL و LTR

100%
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

TSX
// 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>
  )
}
CSS
/* 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

المخرجات:

TEXT 📖 للعرض فقط
تم تطبيق أنماط CSS على: .nav-items, .mr-2, .sidebar, .rtl, before, .main-content
TSX
// 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>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
يعرض: تخطيط لوحة التحكم مع شريط جانبي داكن (روابط لوحة التحكم/المشاريع/الإعدادات) + منطقة المحتوى الرئيسية.
⚠️ تنبيه: RTL ليس مجرد direction: rtl؛ بل تحتاج أيضاً إلى عكس جميع اتجاهات flex والهوامش/المسافات وأيقونات الأسهم ومحاذاة النص.



8. مثال كامل: موقع رسمي بأربع لغات + لوحة تحكم RTL

TSX
// 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>
  )
}
TSX
// 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>
  )
}
TEXT 📖 للعرض فقط
# نتائج المخرجات (الصينية):
TaskFlow
[Language: Chinese ▼]

مرحباً بعودتك، Alice
[12 أعضاء] [45 مشروعاً] [230 مهمة]

النشاطات الأخيرة
أليس قامت بإنشاء مشروع "إعادة تصميم الموقع"
بوب أكمل المهمة رقم 47
تشارلي انضم إلى المنظمة
TEXT 📖 للعرض فقط
# نتائج المخرجات (العربية):
TaskFlow
[اختيار اللغة: العربية ▼]

مرحباً بعودتك، Alice
[12 أعضاء] [45 مشروعاً] [230 مهمة]

النشاطات الأخيرة
أليس قامت بإنشاء مشروع "إعادة تصميم الموقع"
بوب أكمل المهمة رقم 47
تشارلي انضم إلى المنظمة

❓ أسئلة شائعة

س أيهما أختار، next-intl أم next-international؟
ج next-intl لديه مجتمع أكبر (5k⭐) وميزات أكثر (صيغ الجمع والاختيار وتنسيق الأرقام واكتشاف RTL). next-international أخف وزناً. هذا البرنامج التعليمي يستخدم next-intl.
س لماذا نستخدم إعادة كتابة URL بدلاً من إعادة التوجيه في middleware؟
ج rewrite تحافظ على محتوى شريط عنوان المتصفح دون تغيير مع التوجيه داخلياً إلى /{locale}/path، وهذا أفضل لـ SEO وتجربة المستخدم. redirect تغير URL، مما قد يكسر الروابط التي شاركها المستخدمون.
س ما العمل الإضافي المطلوب لتخطيطات RTL العربية؟
ج بالإضافة إلى 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 منفصلة. كل ملف يحتوي فقط على محتوى اللغة المقابلة له، مما يضمن أسرع سرعة تحميل.
س كيف يجب التعامل مع المعاملات المتغيرة في قاموس الترجمة؟
ج استخدم صيغة ICU MessageFormat: {name} لإدراج المتغيرات، و {count, plural, =0 {لا شيء} one {# مهمة} other {# مهام}} للتعامل مع صيغ الجمع. خطافات t() و useTranslations() في next-intl تحلل هذه القوالب تلقائياً.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): قم بتثبيت next-intl في مشروع قائم، وإعداد قواميس بالصينية والإنجليزية، وتنفيذ مكون تبديل اللغة، واستخدام t() لعرض نص شريط التنقل على الصفحة.

  2. تمرين متقدم (⭐⭐): قم بتنفيذ مدونة SSG كاملة متعددة اللغات: generateStaticParams يولّد مسبقاً صفحات المقالات لأربع لغات؛ كل مقالة تحمّل قاموس اللغة المقابل أثناء عملية البناء؛ وتحقق من هيكل ملفات HTML المُنشأة.

  3. تحدٍ (⭐⭐⭐): قم ببناء صفحة لوحة تحكم تدعم RTL: قم بإعدادها للعربية والإنجليزية، واستخدم بادئة rtl: وعكس flex-direction في Tailwind CSS لتنفيذ تكييف كامل للتخطيط للشريط الجانبي والتنقل وبطاقات الإحصائيات. تحقق من أن جميع العناصر معكوسة بشكل صحيح تحت dir="rtl".

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%