Development

دليل دمج next-intl مع Next.js 15 لدعم الترجمة والتدويل خطوة بخطوة

شرح خطوة بخطوة لدمج next-intl من أجل الترجمة في مشروع Next.js 15.

By Mohamed DjoudirJune 07, 20255 min read
مشاركة:
دليل دمج next-intl مع Next.js 15 لدعم الترجمة والتدويل خطوة بخطوة
#Next.js#next-intl#i18n#Internationalization#React

الترجمة (i18n) ضرورية لتطبيقات الويب الحديثة. مع Next.js 15، الطريقة الموصى بها لإضافة الترجمة هي استخدام next-intl. في هذا الدليل، ستتعلم كيفية إعداد next-intl في مشروع Next.js 15 بطريقة عملية وفعالة.

🚀 هل تبحث عن قالب Next.js مع next-intl مدمج مسبقاً؟

قالب لوحة التحكم المالية Next.js

إذا كنت تريد تخطي الإعداد والبدء بقالب جاهز للإنتاج يتضمن بالفعل التدويل next-intl، تحقق من قالب لوحة التحكم المالية Next.js.

🎯 يتضمن هذا القالب المميز:

  • next-intl مُعد مسبقاً وجاهز للاستخدام
  • ✅ تبديل المظهر المظلم/الفاتح
  • ✅ إدارة الحالة Redux Toolkit
  • ✅ واجهة لوحة تحكم مالية حديثة
  • ✅ المصادقة بالبريد الإلكتروني
  • ✅ تصميم متجاوب
  • ✅ دعم TypeScript

مثالي لتطبيقات التكنولوجيا المالية ولوحات التحكم المصرفية وأنظمة الإدارة المالية.

مشاهدة التجربة | الحصول على القالب


1. تثبيت next-intl

أولاً، قم بتثبيت الحزمة:

npm install next-intl

2. إعداد التوجيه (Routing)

أنشئ ملف إعداد التوجيه لتعريف اللغات المدعومة. في Aniq-UI، يتم ذلك في src/i18n/routing.ts:

// src/i18n/routing.ts
import {defineRouting} from 'next-intl/routing';

export const routing = defineRouting({
  locales: ['en', 'fr', 'ar', 'es'],
  defaultLocale: 'en'
});

3. إضافة أدوات التنقل

قم بإعداد أدوات التنقل في src/i18n/navigation.ts:

// src/i18n/navigation.ts
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';

export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing);

4. تحميل الرسائل على الخادم

قم بإعداد تحميل الرسائل في src/i18n/request.ts:

// src/i18n/request.ts
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;

    const [seoMessages] = await Promise.all([
        import(`../messages/seo/${locale}.json`).then(module => module.default).catch(() => ({})),
    ]);

    return {
        locale,
        messages: {
            seo: seoMessages,
        }
    };
});

5. تمرير الرسائل إلى التطبيق

قم بلف صفحاتك بـ NextIntlClientProvider. على سبيل المثال، في src/app/[locale]/about/layout.tsx:

// src/app/[locale]/about/layout.tsx
import { NextIntlClientProvider } from "next-intl";

export default async function AboutLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;

  const [about] = await Promise.all([
    import(`@/messages/about/${locale}.json`).then((mod) => mod.default),
  ]);

  const messages = { about };
  return (
    <NextIntlClientProvider locale={locale} messages={messages}>
      {children}
    </NextIntlClientProvider>
  );
}

6. تنظيم ملفات الرسائل

احفظ ملفات الترجمة في src/messages/، مثلاً:

src/messages/about/en.json
src/messages/about/fr.json
src/messages/about/ar.json
src/messages/about/es.json

كل ملف يحتوي على ترجمات لمساحة اسم (namespace)، مثلاً:

{
  "header_story": "قصتنا",
  "header_title1": "نبني",
  // ...
}

7. استخدام الترجمات في المكونات

استخدم هوك useTranslations من next-intl:

import { useTranslations } from "next-intl";

export default function AboutHeader() {
  const t = useTranslations("about");
  return <h1>{t("header_title1")}</h1>;
}

8. إضافة محول اللغة (Language Switcher)

إليك الكود الكامل لمكون LanguageSelector المستخدم في Aniq-UI:

// src/components/LanguageSelector/index.tsx
"use client";

import React, { useState, useRef, useEffect } from "react";
import { useParams, useRouter } from "next/navigation";
import { cn } from "@/lib/utils";
import { IconLanguage, IconChevronDown } from "@/lib/icons";

interface Language {
  code: string;
  name: string;
  rtl: boolean;
}

const languages: Language[] = [
  { code: "en", name: "English", rtl: false },
  { code: "fr", name: "Français", rtl: false },
  { code: "es", name: "Español", rtl: false },
  { code: "ar", name: "العربية", rtl: true },
];

export default function LanguageSelector() {
  const [isOpen, setIsOpen] = useState(false);
  const dropdownRef = useRef<HTMLDivElement>(null);
  const router = useRouter();
  const params = useParams();
  const currentLocale = typeof params.locale === "string" ? params.locale : "en";
  
  const currentLanguage = languages.find((lang) => lang.code === currentLocale) || languages[0];

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (
        dropdownRef.current &&
        !dropdownRef.current.contains(event.target as Node)
      ) {
        setIsOpen(false);
      }
    };

    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, []);

  const changeLanguage = (langCode: string) => {
    const currentPath = window.location.pathname;
    // استبدال جزء اللغة في المسار
    const pathSegments = currentPath.split('/');
    pathSegments[1] = langCode;
    const newPath = pathSegments.join('/');
    router.push(newPath);
    setIsOpen(false);
  };

  return (
    <div className="relative" ref={dropdownRef}>
      <button
        onClick={() => setIsOpen(!isOpen)}
        className="flex items-center  cursor-pointer px-2 py-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 text-sm"
        aria-expanded={isOpen}
        aria-haspopup="true"
      >
        <IconLanguage className="w-4 h-4 mr-1" />
        <span className="font-medium text-xs mr-1">{currentLanguage.code.toUpperCase()}</span>
        <IconChevronDown className={cn("w-3 h-3 transition-transform", isOpen ? "rotate-180" : "")} />
      </button>

      {isOpen && (
        <div className="absolute z-50 mt-1 bg-white dark:bg-neutral-900 border border-gray-200 dark:border-gray-700 rounded-md shadow-lg py-1 w-40 min-w-max right-0">
          <ul className="py-1">
            {languages.map((language) => (
              <li key={language.code}>
                <button
                  onClick={() => changeLanguage(language.code)}
                  className={cn(
                    "w-full text-left cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center",
                    language.code === currentLocale && "bg-gray-100 dark:bg-gray-800"
                  )}
                 >
                  <span className="inline-block me-2 w-max font-medium text-xs">{language.code.toUpperCase()}</span>
                  {language.name}
                </button>
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

9. اختبار الإعداد

شغل الخادم المحلي وزر /en/about أو /fr/about أو غيرها. يجب أن ترى الترجمات الصحيحة ويمكنك التبديل بين اللغات.

الخلاصة

مع next-intl، يصبح التدويل في Next.js 15 سهلاً وقويًا. لمثال عملي كامل، راجع مستودع Aniq-UI.


المراجع:

هل وجدت هذا المقال مفيدًا؟

مشاركة:
world map

مجتمع المستخدمين العالمي

انضم إلى آلاف المطورين في جميع أنحاء العالم الذين يثقون بـ Aniq-UI لمشاريعهم. يتم استخدام قوالبنا في جميع أنحاء العالم لإنشاء تجارب ويب مذهلة.

تواصل معنا

Need custom work or reskin? Get in touch with us

Aniq-uiAniq-uiAniq-ui