كيفية تحميل الترجمات من الملفات في React Router v7
فصل المحتوى القابل للترجمة عن الكود
المشكلة
إدراج النصوص الموجهة للمستخدم مباشرة في المكونات يخلق ارتباطاً وثيقاً بين المحتوى والكود. كل لغة جديدة تتطلب من المطورين تعديل ملفات التنفيذ، مما يوسع المنطق الشرطي ويزيد من التعقيد. عندما يتغير النص، حتى التعديلات الطفيفة في الصياغة تتطلب نشر الكود. هذا النهج يجعل سير عمل الترجمة معتمداً على دورات الهندسة ويمنع أعضاء الفريق غير التقنيين من إدارة المحتوى بشكل مستقل.
مع نمو التطبيقات، تصبح النصوص المتناثرة صعبة التتبع والصيانة. العثور على كل ظهور لعبارة عبر قاعدة الكود معرض للأخطاء، وضمان الاتساق عبر الرسائل المتشابهة يصبح شبه مستحيل بدون مصدر مركزي موحد.
الحل
استخراج جميع النصوص القابلة للترجمة إلى ملفات JSON خارجية منظمة حسب اللغة، مع ملف واحد لكل لغة. استبدال النصوص المدرجة في المكونات بمعرفات رسائل تشير إلى إدخالات في هذه الملفات. في وقت التشغيل، يقوم التطبيق بتحميل ملف الترجمة المناسب بناءً على لغة المستخدم ويوفر تلك الرسائل لمكتبة التدويل، والتي تحول المعرفات إلى قيمها المترجمة.
هذا الفصل يسمح للمترجمين بالعمل مباشرة مع ملفات JSON دون لمس الكود، ويمكّن من تحديثات المحتوى من خلال تغييرات بسيطة في الملفات، ويوفر مصدراً موحداً لنصوص كل لغة.
الخطوات
1. إنشاء ملفات الترجمة لكل لغة
تنظيم ملفات الترجمة في دليل مخصص، مع ملف JSON واحد لكل لغة. هيكلة كل ملف ككائن مسطح يربط معرفات الرسائل بالنصوص المترجمة.
{
"welcome.title": "Welcome back",
"welcome.subtitle": "Continue where you left off",
"nav.home": "Home",
"nav.about": "About",
"nav.contact": "Contact"
}
احفظ هذا كـ app/translations/en.json للإنجليزية، ثم أنشئ ملفات موازية مثل app/translations/es.json و app/translations/fr.json مع ترجمات للغات أخرى. استخدم مفاتيح متسقة عبر جميع الملفات بحيث يتم تحويل نفس المعرف إلى الترجمة المناسبة في كل لغة.
2. تحميل الترجمات في محمّل المسار
استخدم محمّل المسار لجلب ملف الترجمة للغة الحالية قبل العرض. يضمن ذلك توفر الرسائل عند تحميل المكونات.
import type { Route } from "./+types/root";
import enMessages from "./translations/en.json";
import esMessages from "./translations/es.json";
import frMessages from "./translations/fr.json";
const messages: Record<string, Record<string, string>> = {
en: enMessages,
es: esMessages,
fr: frMessages,
};
export async function loader({ request }: Route.LoaderArgs) {
const url = new URL(request.url);
const locale = url.searchParams.get("locale") || "en";
return {
locale,
messages: messages[locale] || messages.en,
};
}
يقرأ المحمّل اللغة من معامل استعلام عنوان URL ويُرجع كلاً من اللغة والرسائل المقابلة لها. يمكن للمكونات الوصول إلى هذه البيانات من خلال loaderData لتكوين موفر التدويل.
3. تكوين IntlProvider بالرسائل المحملة
قم بتغليف تطبيقك بـ IntlProvider من react-intl، مع تمرير اللغة والرسائل من بيانات المحمّل.
import { IntlProvider } from "react-intl";
import { Outlet } from "react-router";
import type { Route } from "./+types/root";
export default function Root({ loaderData }: Route.ComponentProps) {
return (
<IntlProvider locale={loaderData.locale} messages={loaderData.messages}>
<html lang={loaderData.locale}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<Outlet />
</body>
</html>
</IntlProvider>
);
}
يجعل IntlProvider اللغة والرسائل متاحة لجميع المكونات الفرعية من خلال سياق React. تُعرض المسارات الفرعية من خلال Outlet وترث إمكانية الوصول إلى بيانات الترجمة.
4. الإشارة إلى الرسائل بالمعرّف في المكونات
استبدل النصوص المشفرة بمكونات FormattedMessage التي تشير إلى معرّفات الرسائل من ملفات الترجمة الخاصة بك.
import { FormattedMessage } from "react-intl";
export default function Welcome() {
return (
<div>
<h1>
<FormattedMessage id="welcome.title" />
</h1>
<p>
<FormattedMessage id="welcome.subtitle" />
</p>
<nav>
<a href="/">
<FormattedMessage id="nav.home" />
</a>
<a href="/about">
<FormattedMessage id="nav.about" />
</a>
<a href="/contact">
<FormattedMessage id="nav.contact" />
</a>
</nav>
</div>
);
}
يبحث كل مكون FormattedMessage عن id الخاص به في كائن الرسائل المقدم من IntlProvider ويعرض النص المترجم المقابل. عندما تتغير اللغة ويعمل المحمّل مرة أخرى برسائل مختلفة، تعرض جميع المكونات تلقائياً الترجمات الجديدة دون تغييرات في الكود.