Development

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

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

By Mohamed DjoudirJune 07, 20256 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 بطريقة عملية وفعالة.

1. تثبيت next-intl

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

npm install next-intl

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

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

1// src/i18n/routing.ts
2import {defineRouting} from 'next-intl/routing';
3
4export const routing = defineRouting({
5  locales: ['en', 'fr', 'ar', 'es'],
6  defaultLocale: 'en'
7});

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:

1// src/i18n/request.ts
2import { getRequestConfig } from 'next-intl/server';
3import { hasLocale } from 'next-intl';
4import { routing } from './routing';
5
6export default getRequestConfig(async ({ requestLocale }) => {
7    const requested = await requestLocale;
8    const locale = hasLocale(routing.locales, requested)
9        ? requested
10        : routing.defaultLocale;
11
12    const [seoMessages] = await Promise.all([
13        import(`../messages/seo/${locale}.json`).then(module => module.default).catch(() => ({})),
14    ]);
15
16    return {
17        locale,
18        messages: {
19            seo: seoMessages,
20        }
21    };
22});

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

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

1// src/app/[locale]/about/layout.tsx
2import { NextIntlClientProvider } from "next-intl";
3
4export default async function AboutLayout({
5  children,
6  params,
7}: {
8  children: React.ReactNode;
9  params: Promise<{ locale: string }>;
10}) {
11  const { locale } = await params;
12
13  const [about] = await Promise.all([
14    import(`@/messages/about/${locale}.json`).then((mod) => mod.default),
15  ]);
16
17  const messages = { about };
18  return (
19    <NextIntlClientProvider locale={locale} messages={messages}>
20      {children}
21    </NextIntlClientProvider>
22  );
23}

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:

1import { useTranslations } from "next-intl";
2
3export default function AboutHeader() {
4  const t = useTranslations("about");
5  return <h1>{t("header_title1")}</h1>;
6}

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

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

1// src/components/LanguageSelector/index.tsx
2"use client";
3
4import React, { useState, useRef, useEffect } from "react";
5import { useParams, useRouter } from "next/navigation";
6import { cn } from "@/lib/utils";
7import { IconLanguage, IconChevronDown } from "@/lib/icons";
8
9interface Language {
10  code: string;
11  name: string;
12  rtl: boolean;
13}
14
15const languages: Language[] = [
16  { code: "en", name: "English", rtl: false },
17  { code: "fr", name: "Français", rtl: false },
18  { code: "es", name: "Español", rtl: false },
19  { code: "ar", name: "العربية", rtl: true },
20];
21
22export default function LanguageSelector() {
23  const [isOpen, setIsOpen] = useState(false);
24  const dropdownRef = useRef<HTMLDivElement>(null);
25  const router = useRouter();
26  const params = useParams();
27  const currentLocale = typeof params.locale === "string" ? params.locale : "en";
28  
29  const currentLanguage = languages.find((lang) => lang.code === currentLocale) || languages[0];
30
31  useEffect(() => {
32    const handleClickOutside = (event: MouseEvent) => {
33      if (
34        dropdownRef.current &&
35        !dropdownRef.current.contains(event.target as Node)
36      ) {
37        setIsOpen(false);
38      }
39    };
40
41    document.addEventListener("mousedown", handleClickOutside);
42    return () => {
43      document.removeEventListener("mousedown", handleClickOutside);
44    };
45  }, []);
46
47  const changeLanguage = (langCode: string) => {
48    const currentPath = window.location.pathname;
49    // استبدال جزء اللغة في المسار
50    const pathSegments = currentPath.split('/');
51    pathSegments[1] = langCode;
52    const newPath = pathSegments.join('/');
53    router.push(newPath);
54    setIsOpen(false);
55  };
56
57  return (
58    <div className="relative" ref={dropdownRef}>
59      <button
60        onClick={() => setIsOpen(!isOpen)}
61        className="flex items-center  cursor-pointer px-2 py-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 text-sm"
62        aria-expanded={isOpen}
63        aria-haspopup="true"
64      >
65        <IconLanguage className="w-4 h-4 mr-1" />
66        <span className="font-medium text-xs mr-1">{currentLanguage.code.toUpperCase()}</span>
67        <IconChevronDown className={cn("w-3 h-3 transition-transform", isOpen ? "rotate-180" : "")} />
68      </button>
69
70      {isOpen && (
71        <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">
72          <ul className="py-1">
73            {languages.map((language) => (
74              <li key={language.code}>
75                <button
76                  onClick={() => changeLanguage(language.code)}
77                  className={cn(
78                    "w-full text-left cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center",
79                    language.code === currentLocale && "bg-gray-100 dark:bg-gray-800"
80                  )}
81                 >
82                  <span className="inline-block me-2 w-max font-medium text-xs">{language.code.toUpperCase()}</span>
83                  {language.name}
84                </button>
85              </li>
86            ))}
87          </ul>
88        </div>
89      )}
90    </div>
91  );
92}

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

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

الخلاصة

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


المراجع:

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

مشاركة:

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

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

world map