كيفية تحميل الترجمات من الملفات في TanStack Start v1
فصل المحتوى القابل للترجمة عن الكود
المشكلة
إن كتابة النصوص المعروضة للمستخدم مباشرة في المكونات تخلق ارتباطًا وثيقًا بين المحتوى والكود. في كل مرة يتغير فيها نص أو تتم إضافة لغة جديدة، يجب على المطورين تحديد ملفات المصدر وتعديلها، ثم إعادة نشر التطبيق. يجعل هذا النهج سير عمل الترجمة معتمدًا على دورات الهندسة ويمنع أعضاء الفريق غير التقنيين من تحديث النصوص. مع نمو عدد اللغات المدعومة، يصبح المنطق الشرطي لاختيار النص الصحيح غير عملي وعرضة للأخطاء. والنتيجة هي تكرار أبطأ، وتكاليف صيانة أعلى، وقاعدة شيفرة مزدحمة بمحتوى قابل للترجمة يجب أن يكون موجودًا في مكان آخر.
الحل
فصل جميع النصوص القابلة للترجمة عن كود التطبيق عن طريق تخزينها في ملفات JSON خارجية، ملف واحد لكل لغة. تحديد مفتاح ثابت لكل رسالة والإشارة إلى تلك المفاتيح في المكونات بدلاً من النص الحرفي. في وقت التشغيل، يتم تحميل ملف الترجمة المناسب بناءً على لغة المستخدم وتوفير تلك الرسائل لـ IntlProvider من react-intl. هذا يفصل المحتوى عن الكود: يمكن للمترجمين العمل مباشرة مع ملفات JSON، وتغييرات النصوص لا تتطلب تعديلات في الكود، وإضافة لغة جديدة تعني إضافة ملف جديد دون المساس بالمكونات.
الخطوات
1. إنشاء ملفات ترجمة لكل لغة
تنظيم الترجمات كملفات JSON في دليل مخصص، مع ملف واحد لكل لغة يحتوي على أزواج المفتاح-القيمة لجميع الرسائل.
{
"welcome": "Welcome back",
"greeting": "Hello, {name}",
"itemCount": "{count, plural, =0 {No items} one {One item} other {# items}}"
}
احفظ هذا كـ app/translations/en.json. قم بإنشاء ملفات موازية للغات الأخرى، مثل app/translations/es.json و app/translations/fr.json، بنفس المفاتيح ولكن بقيم مترجمة.
2. تحميل الترجمات باستخدام دالة الخادم
استخدم دالة الخادم لقراءة ملف الترجمة من القرص بناءً على اللغة المطلوبة، مما يضمن تحميل الترجمات على جانب الخادم أثناء SSR وجلبها على العميل أثناء التنقل.
import { createServerFn } from "@tanstack/react-start";
import * as fs from "node:fs";
export const getMessages = createServerFn({ method: "GET" }).handler(
async ({ request }) => {
const url = new URL(request.url);
const locale = url.searchParams.get("locale") || "en";
const filePath = `app/translations/${locale}.json`;
const content = await fs.promises.readFile(filePath, "utf-8");
return JSON.parse(content);
},
);
تقوم هذه الدالة بقراءة ملف JSON للغة المحددة وإرجاع كائن الرسائل المحلل. تعمل فقط على الخادم، مما يحافظ على أمان الوصول إلى نظام الملفات.
3. إنشاء مساعد لتحديد لغة المستخدم
قم بتعريف أداة مساعدة صغيرة تستخرج اللغة من الطلب أو ترجع إلى الإعداد الافتراضي، مما يجعلها قابلة لإعادة الاستخدام عبر المسارات.
export function getLocaleFromRequest(request: Request): string {
const url = new URL(request.url);
const localeParam = url.searchParams.get("locale");
if (localeParam) return localeParam;
const acceptLanguage = request.headers.get("accept-language");
if (acceptLanguage) {
const match = acceptLanguage.split(",")[0].split("-")[0];
return match || "en";
}
return "en";
}
تتحقق هذه الدالة من معلمات الاستعلام أولاً، ثم رأس Accept-Language، وتعود افتراضيًا إلى اللغة الإنجليزية. توفر مصدرًا واحدًا للحقيقة لاكتشاف اللغة.
4. تحميل الرسائل في محمّل المسار
استخدم محمّل المسار لجلب الرسائل للغة الحالية قبل العرض، مما يجعلها متاحة لشجرة المكونات.
import { createFileRoute } from "@tanstack/react-router";
import { getMessages, getLocaleFromRequest } from "../lib/i18n";
export const Route = createFileRoute("/")({
loader: async ({ context }) => {
const locale = getLocaleFromRequest(context.request);
const messages = await getMessages({ data: { locale } });
return { locale, messages };
},
component: HomePage,
});
function HomePage() {
const { locale, messages } = Route.useLoaderData();
return (
<div>
<p>{messages.welcome}</p>
</div>
);
}
يقوم المحمّل باستدعاء دالة الخادم لاسترجاع الرسائل، ويصل المكون إليها عبر useLoaderData. يعمل هذا النمط لكل من SSR والتنقل على جانب العميل.
5. توفير الرسائل لـ react-intl
قم بتغليف شجرة المكونات الخاصة بك باستخدام IntlProvider، مع تمرير اللغة المحلية والرسائل المحملة حتى تتمكن جميع المكونات الفرعية من الوصول إلى الترجمات.
import { IntlProvider } from "react-intl";
function HomePage() {
const { locale, messages } = Route.useLoaderData();
return (
<IntlProvider locale={locale} messages={messages}>
<AppContent />
</IntlProvider>
);
}
function AppContent() {
return (
<div>
<FormattedMessage id="welcome" />
</div>
);
}
IntlProvider يجعل اللغة المحلية والرسائل متاحة لجميع مكونات وخطافات react-intl. يمكن للمكونات الآن الإشارة إلى الرسائل بواسطة المفتاح باستخدام FormattedMessage أو useIntl، ويتم عرض الترجمة الصحيحة بناءً على اللغة المحلية المحملة.
6. الإشارة إلى الرسائل بواسطة المفتاح في المكونات
استخدم مكون FormattedMessage أو خطاف useIntl من react-intl لعرض السلاسل النصية المترجمة، مع الإشارة إلى المفاتيح المحددة في ملفات JSON الخاصة بك.
import { FormattedMessage, useIntl } from "react-intl";
function UserGreeting({ name }: { name: string }) {
const intl = useIntl();
const title = intl.formatMessage({ id: "greeting" }, { name });
return (
<div>
<h1 title={title}>
<FormattedMessage id="greeting" values={{ name }} />
</h1>
<p>
<FormattedMessage id="itemCount" values={{ count: 5 }} />
</p>
</div>
);
}
FormattedMessage يعرض السلسلة النصية المترجمة مباشرة، بينما useIntl().formatMessage يعيد سلسلة نصية للاستخدام في السمات أو منطق JavaScript. كلاهما يقبل values للاستيفاء ويدعم بناء جملة رسائل ICU للصيغ الجمعية والتنسيق.