إنتقل إلى المحتوى الرئيسي

الدولية (i18n) والتعريب (l10n) في Cursor: react-intl و i18next ودعم RTL

الدولية في Cursor

تجعل الدولية (i18n) والتعريب (l10n) تطبيقك متاحًا للمستخدمين عبر لغات ومناطق واتفاقيات ثقافية مختلفة. Cursor، وهو محرر IDE مدعوم بالذكاء الاصطناعي، يسرّع كل مرحلة من مراحل سير عمل i18n: استخراج السلاسل القابلة للترجمة، وتكوين مكتبات الترجمة، وتوليد ملفات اللغة، وتنفيذ تخطيطات من اليمين إلى اليسار (RTL)، وأتمتة الترجمة بخطوط أنابيب مدعومة بالذكاء الاصطناعي. يغطي هذا الدليل react-intl و i18next وإدارة ملفات الترجمة ودعم RTL والتنسيق المراعي للغة وسير عمل الترجمة بمساعدة الذكاء الاصطناعي.

المتطلبات الأساسية​

قبل البدء، تأكد من أن لديك ما يلي:

  • Cursor (يُوصى بأحدث إصدار) كمحرر IDE الخاص بك
  • Node.js 18 أو أحدث
  • مشروع React (يُوصى بـ Vite أو Next.js)
  • معرفة أساسية بـ JSON وأنماط مكونات React
تلميح

خطط لاستراتيجية i18n الخاصة بك قبل كتابة أي كود واجهة. إضافة i18n إلى تطبيق موجود أصعب بكثير من بنائه من البداية. اطلب من Cursor إعداد البنية التحتية للترجمة في السباق الأول بحيث يكون كل مكون دوليًا منذ اليوم الأول.

فهم i18n مقابل l10n​

  • الدولية (i18n): عمل الهندسة لإعداد تطبيقك لدعم لغات متعددة. يشمل ذلك استخراج السلاسل، واستخدام منسقات مراعية للغة، ودعم تخطيطات RTL، وتصميم مكونات واجهة مرنة.
  • التعريب (l10n): الترجمة الفعلية والتكيف الثقافي للمحتوى للغة معينة، بما في ذلك ترجمة السلاسل، وتكييف تنسيقات التاريخ والعملة، وتعديل الصور لتتوافق مع المعايير المحلية.

يساعدك Cursor في كليهما: فهو يولد البنية التحتية لـ i18n ويساعد في l10n من خلال الترجمة المدعومة بالذكاء الاصطناعي وتوليد ملفات اللغة.

إعداد i18next​

i18next هو إطار العمل الأكثر شيوعًا لـ i18n في JavaScript. رفيقه React، react-i18next، يوفر hooks ومكونات للتكامل السلس.

التثبيت والتكوين​

اطلب من Cursor إعداد i18next في مشروع React الخاص بك:

Set up i18next with react-i18next in my Vite + React + TypeScript project.
Support English (en), Spanish (es), Japanese (ja), and Arabic (ar).
Include a language switcher component, lazy-loaded translation
namespaces, and locale-aware date formatting. Configure the
detector to remember the user's choice in localStorage.

يقوم Cursor بتوليد التكوين:

// src/i18n/index.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpBackend from 'i18next-http-backend';

export const supportedLanguages = [
{ code: 'en', name: 'English', dir: 'ltr' as const },
{ code: 'es', name: 'Espanol', dir: 'ltr' as const },
{ code: 'ja', name: '日本語', dir: 'ltr' as const },
{ code: 'ar', name: 'العربية', dir: 'rtl' as const },
];

export const defaultLanguage = 'en';

i18n
.use(HttpBackend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: defaultLanguage,
supportedLngs: supportedLanguages.map((l) => l.code),
ns: ['common', 'navigation', 'forms', 'errors'],
defaultNS: 'common',
backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
detection: {
order: ['localStorage', 'navigator', 'htmlTag'],
lookupLocalStorage: 'i18nextLng',
caches: ['localStorage'],
},
interpolation: { escapeValue: false },
react: { useSuspense: true, bindI18n: 'languageChanged loaded' },
});

export default i18n;

هيكل ملفات الترجمة​

public/
locales/
en/
common.json
navigation.json
forms.json
errors.json
es/
common.json
...
ja/
common.json
...
ar/
common.json
...

مثال على الترجمات الإنجليزية:

// public/locales/en/common.json
{
"app": {
"title": "My Application",
"tagline": "Build better, ship faster"
},
"welcome": "Welcome, {{name}}!",
"items_count": "You have {{count}} item",
"items_count_other": "You have {{count}} items",
"lastUpdated": "Last updated: {{date}}"
}
// public/locales/en/forms.json
{
"labels": {
"email": "Email Address",
"password": "Password",
"firstName": "First Name",
"lastName": "Last Name"
},
"placeholders": {
"email": "[email protected]",
"password": "Enter your password"
},
"buttons": {
"submit": "Submit",
"cancel": "Cancel"
},
"validation": {
"emailRequired": "Email is required",
"emailInvalid": "Please enter a valid email address",
"passwordMinLength": "Password must be at least {{min}} characters"
}
}
تلميح

نظم الترجمات في مساحات أسماء (common و navigation و forms و errors) بدلاً من ملف واحد ضخم. تتيح لك مساحات الأسماء التحميل الكسول فقط للترجمات اللازمة للصفحة الحالية، مما يقلل حجم الحزمة الأولي. اطلب من Cursor اقتراح حدود مساحات الأسماء بناءً على هيكل المسارات الخاص بك.

warning

لا تقم أبدًا بتضمين السلاسل المرئية للمستخدم مباشرة في مكوناتك. حتى لو كنت تدعم لغة واحدة فقط في البداية، فإن استخراج السلاسل إلى ملفات ترجمة منذ البداية هو استثمار بـ 10 دقائق يوفر أسابيع من إعادة البناء لاحقًا. استخدم الدالة t() لكل سلسلة مرئية، بما في ذلك تسميات الأزرار ورسائل الخطأ والتلميحات.

استخدام react-i18next Hooks​

الـ hook useTranslation هو الطريقة الأساسية للوصول إلى الترجمات في مكونات الدالة.

الاستخدام الأساسي​

// src/components/LoginForm.tsx
import { useTranslation } from 'react-i18next';
import { useState } from 'react';

export function LoginForm() {
const { t } = useTranslation(['forms', 'errors']);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState<Record<string, string>>({});

function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const newErrors: Record<string, string> = {};

if (!email) {
newErrors.email = t('forms:validation.emailRequired');
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
newErrors.email = t('forms:validation.emailInvalid');
}

if (password.length < 8) {
newErrors.password = t('forms:validation.passwordMinLength', { min: 8 });
}

setErrors(newErrors);
}

return (
<form onSubmit={handleSubmit} className="max-w-md space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium">
{t('forms:labels.email')}
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder={t('forms:placeholders.email')}
className="mt-1 w-full rounded border px-3 py-2"
aria-invalid={!!errors.email}
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600" role="alert">
{errors.email}
</p>
)}
</div>

<div>
<label htmlFor="password" className="block text-sm font-medium">
{t('forms:labels.password')}
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={t('forms:placeholders.password')}
className="mt-1 w-full rounded border px-3 py-2"
aria-invalid={!!errors.password}
/>
{errors.password && (
<p className="mt-1 text-sm text-red-600" role="alert">
{errors.password}
</p>
)}
</div>

<button type="submit" className="w-full rounded bg-blue-600 px-4 py-2 text-white">
{t('forms:buttons.submit')}
</button>
</form>
);
}

الجمع والاستيفاء​

يتعامل i18next مع قواعد الجمع تلقائيًا بناءً على اللغة. اللغات المختلفة لها أشكال جمع مختلفة (الإنجليزية لها 2، العربية لها 6، اليابانية لها 1).

// src/components/ShoppingCart.tsx
import { useTranslation } from 'react-i18next';

export function ShoppingCart({ itemCount }: { itemCount: number }) {
const { t } = useTranslation('common');

return (
<div className="p-4">
<h2 className="text-lg font-bold">{t('shoppingCart')}</h2>
{/* i18next automatically selects singular or plural form */}
<p>{t('items_count', { count: itemCount })}</p>
</div>
);
}

يتعامل ملف الترجمة العربية مع جميع أشكال الجمع الستة:

// public/locales/ar/common.json (Arabic has 6 plural forms per CLDR rules)
{
"items_count_0": "لا توجد عناصر",
"items_count_1": "لديك عنصر واحد",
"items_count_2": "لديك عنصران",
"items_count_3": "لديك {{count}} عناصر",
"items_count_4": "لديك {{count}} عنصرًا",
"items_count_5": "لديك {{count}} عنصر"
}
تلميح

اطلب من Cursor توليد قواعد الجمع لكل لغة تدعمها. أمر مثل "Create the correct plural form keys for Arabic, Russian, and Polish for the items_count translation key, following CLDR plural rules" ينتج أشكال جمع صحيحة للغة دون الحاجة لحفظ قواعد CLDR.

إعداد react-intl​

react-intl هو مكتبة i18n بديلة من مجموعة FormatJS. وهي شائعة في التطبيقات المؤسسية بسبب أمان الأنواع الصارم ودعمها لـ ICU Message Format.

تكوين Provider​

// src/i18n/IntlProvider.tsx
import { IntlProvider } from 'react-intl';
import { useState, useEffect, ReactNode, createContext, useContext } from 'react';

export type Locale = 'en' | 'es' | 'ja' | 'ar';

const messages: Record<Locale, () => Promise<Record<string, string>>> = {
en: () => import('../locales/en.json').then((m) => m.default),
es: () => import('../locales/es.json').then((m) => m.default),
ja: () => import('../locales/ja.json').then((m) => m.default),
ar: () => import('../locales/ar.json').then((m) => m.default),
};

export function IntlAppProvider({ children }: { children: ReactNode }) {
const storedLocale = localStorage.getItem('locale') as Locale | null;
const browserLocale = navigator.language.split('-')[0] as Locale;
const [locale, setLocale] = useState<Locale>(storedLocale || browserLocale || 'en');
const [localeMessages, setLocaleMessages] = useState<Record<string, string>>({});

useEffect(() => {
messages[locale]().then((msgs) => {
setLocaleMessages(msgs);
document.documentElement.dir = locale === 'ar' ? 'rtl' : 'ltr';
document.documentElement.lang = locale;
localStorage.setItem('locale', locale);
});
}, [locale]);

return (
<IntlProvider
locale={locale}
messages={localeMessages}
defaultLocale="en"
onError={(err) => import.meta.env.DEV && console.warn('[Intl]', err.message)}
>
<LanguageContext.Provider value={{ locale, changeLocale: setLocale }}>
{children}
</LanguageContext.Provider>
</IntlProvider>
);
}

interface LanguageContextType { locale: Locale; changeLocale: (locale: Locale) => void; }
const LanguageContext = createContext<LanguageContextType | null>(null);
export function useLanguage() {
const ctx = useContext(LanguageContext);
if (!ctx) throw new Error('useLanguage must be used within IntlAppProvider');
return ctx;
}

استخدام مكونات react-intl​

يوفر react-intl مكونات تصريحية للتنسيق. يتعامل المكون FormattedMessage مع ICU Message Format، بينما تتكيف FormattedNumber و FormattedDate و FormattedTime مع لغة المستخدم تلقائيًا.

// src/components/ProductCard.tsx
import { FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl';
import { useLanguage } from '../i18n/IntlProvider';

export function ProductCard({ product }: { product: Product }) {
const { locale } = useLanguage();

return (
<div className="rounded-lg border p-4">
<h3 className="text-lg font-bold">{product.name}</h3>
{/* Currency adapts to locale automatically */}
<p className="text-xl text-green-600">
<FormattedNumber
value={product.price}
style="currency"
currency={locale === 'ja' ? 'JPY' : locale === 'ar' ? 'SAR' : 'USD'}
/>
</p>
<p className="text-sm text-gray-500">
<FormattedMessage
id="product.addedOn"
defaultMessage="Added on {date}"
values={{
date: <FormattedDate value={new Date(product.addedAt)} year="numeric" month="long" day="numeric" />,
}}
/>
</p>
<div className="mt-2">
<FormattedMessage
id="product.reviews"
defaultMessage="{count, plural, one {# review} other {# reviews}}"
values={{ count: product.reviewCount }}
/>
</div>
</div>
);
}
تلميح

يستخدم react-intl صيغة ICU Message Format، التي تضمّن الجمع والاختيار والتنسيق مباشرة في سلسلة الرسالة. هذا يعني أن المترجمين يمكنهم تعديل بنية الجملة دون تعديل الكود. اطلب من Cursor شرح صياغة ICU Message Format بأمثلة عملية إذا كنت تنتقل من ملفات ترجمة بسيطة من مفتاح-قيمة.

دعم من اليمين إلى اليسار (RTL)​

دعم RTL ضروري للغات مثل العربية والعبرية والفارسية. يساعدك Cursor في تنفيذ تخطيطات مراعية لـ RTL تعمل بشكل صحيح في كلا الاتجاهين.

خصائص CSS المنطقية​

استخدم الخصائص المنطقية (margin-inline-start) بدلاً من الخصائص الفيزيائية (margin-left) بحيث تنقلب التخطيطات تلقائيًا في وضع RTL.

/* src/styles/rtl.css */
.card {
padding-inline-start: 1rem;
padding-inline-end: 1rem;
border-inline-start: 3px solid var(--accent);
}
.flex-container { display: flex; flex-direction: row; gap: 0.5rem; }
.nav-bar { inset-inline-start: 0; inset-inline-end: 0; }

/* Flip directional icons in RTL */
[dir="rtl"] .icon-rtl-flip { transform: scaleX(-1); }

مكون React مراعي لـ RTL​

// src/components/RTLNavigation.tsx
import { useTranslation } from 'react-i18next';
import { useMemo } from 'react';

export function RTLNavigation({ items }: { items: NavItem[] }) {
const { i18n } = useTranslation();
const isRTL = i18n.dir() === 'rtl';
const flipIcons = useMemo(() => new Set(['arrow', 'chevron', 'back', 'forward']), []);

return (
<nav
className="flex items-center gap-4 px-4 py-2"
style={{ direction: isRTL ? 'rtl' : 'ltr' }}
aria-label="Main navigation"
>
{items.map((item) => (
<a key={item.id} href={item.href} className="flex items-center gap-2 rounded px-3 py-1 hover:bg-gray-100">
{item.icon && (
<span
className="inline-block"
style={{ transform: isRTL && flipIcons.has(item.icon) ? 'scaleX(-1)' : 'scaleX(1)' }}
>
<Icon name={item.icon} />
</span>
)}
<span>{i18n.t(item.labelKey)}</span>
</a>
))}
</nav>
);
}
warning

لا تستخدم text-align: left أو text-align: right في CSS الخاص بك. استخدم text-align: start و text-align: end بدلاً من ذلك. الخصائص الفيزيائية لا تنقلب تلقائيًا في وضع RTL، مما يسبب نصًا غير محاذى وتخطيطات مكسورة. اطلب من Cursor فحص CSS الخاص بك للبحث عن الخصائص الفيزيائية الاتجاهية وتحويلها إلى مكافئات منطقية.

تلميح

اختبر تخطيط RTL بإضافة العربية كلغة في وقت مبكر من التطوير. قلب تخطيطك إلى RTL يكشف الخصائص الفيزيائية المضمّنة في الكود، وتخطيطات flex غير المعكوسة، وأيقونات يجب أن تنقلب. إصلاح هذه المشكلات أثناء التطوير أرخص بكثير من اكتشافها بعد الإطلاق.

مكون مبدّل اللغة​

// src/components/LanguageSwitcher.tsx
import { useTranslation } from 'react-i18next';
import { supportedLanguages } from '../i18n';

export function LanguageSwitcher() {
const { i18n } = useTranslation();
const currentLanguage = supportedLanguages.find((l) => l.code === i18n.language) || supportedLanguages[0];

function changeLanguage(code: string) {
i18n.changeLanguage(code);
const lang = supportedLanguages.find((l) => l.code === code);
document.documentElement.dir = lang?.dir || 'ltr';
document.documentElement.lang = code;
}

return (
<div className="flex items-center gap-2">
<select
id="language-select"
value={currentLanguage.code}
onChange={(e) => changeLanguage(e.target.value)}
className="rounded border px-2 py-1 text-sm"
aria-label="Select language"
>
{supportedLanguages.map((lang) => (
<option key={lang.code} value={lang.code}>
{lang.name}
</option>
))}
</select>
</div>
);
}

التنسيق المراعي للغة​

يجب أن يتكيف تنسيق التاريخ والأرقام والعملة مع لغة المستخدم. يوفر كل من i18next و react-intl أدوات تنسيق.

استخدام واجهة Intl مباشرة​

// src/lib/locale-formatters.ts

export function formatDate(date: Date, locale: string, options?: Intl.DateTimeFormatOptions): string {
return new Intl.DateTimeFormat(locale, options || { year: 'numeric', month: 'long', day: 'numeric' }).format(date);
}

export function formatNumber(value: number, locale: string, options?: Intl.NumberFormatOptions): string {
return new Intl.NumberFormat(locale, options).format(value);
}

export function formatCurrency(value: number, locale: string, currency: string): string {
return new Intl.NumberFormat(locale, { style: 'currency', currency, minimumFractionDigits: 2 }).format(value);
}

export function formatRelativeTime(date: Date, locale: string): string {
const diff = (date.getTime() - Date.now()) / 1000;
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
const units: Array<[Intl.RelativeTimeFormatUnit, number]> = [
['year', 60 * 60 * 24 * 365], ['month', 60 * 60 * 24 * 30],
['day', 60 * 60 * 24], ['hour', 60 * 60], ['minute', 60], ['second', 1],
];
for (const [unit, secs] of units) {
if (Math.abs(diff) >= secs) return rtf.format(Math.round(diff / secs), unit);
}
return rtf.format(0, 'second');
}

أمثلة التنسيق عبر اللغات​

// Dates adapt to locale conventions automatically:
formatDate(new Date('2026-09-02'), 'en-US'); // "September 2, 2026"
formatDate(new Date('2026-09-02'), 'es-ES'); // "2 de septiembre de 2026"
formatDate(new Date('2026-09-02'), 'ja-JP'); // "2026年9月2日"
formatDate(new Date('2026-09-02'), 'ar-SA'); // "٢ سبتمبر ٢٠٢٦"

// Currency formatting respects local symbols and decimal rules:
formatCurrency(1234.56, 'en-US', 'USD'); // "$1,234.56"
formatCurrency(1234.56, 'ja-JP', 'JPY'); // "¥1,235" (no decimals for JPY)
formatCurrency(1234.56, 'ar-SA', 'SAR'); // "١٬٢٣٤٫٥٦ ر.س"

// Relative time uses natural language per locale:
formatRelativeTime(new Date(Date.now() - 3600000), 'en'); // "1 hour ago"
formatRelativeTime(new Date(Date.now() - 3600000), 'ja'); // "1時間前"
formatRelativeTime(new Date(Date.now() - 3600000), 'ar'); // "منذ ساعة واحدة"
تلميح

استخدم دائمًا واجهة Intl الأصلية للتنسيق بدلاً من كتابة منسقات مخصصة. تستخدم واجهة Intl بيانات CLDR (مستودع بيانات اللغة المشترك) المضمّنة في محرك JavaScript، التي يصونها مورّد المنصة وتتعامل مع الحالات الخاصة مثل التقويمات غير الميلادية (العصر الياباني، التقويم الهجري) تلقائيًا.

سير عمل الترجمة بمساعدة الذكاء الاصطناعي​

يمكن لـ Cursor توليد ترجمات لملفات لغة كاملة باستخدام الذكاء الاصطناعي. هذا ليس بديلاً عن المترجمين البشريين المحترفين، لكنه ينتج مسودات أولية عالية الجودة يمكن للمترجم مراجعتها وتحسينها.

توليد الترجمات من ملف مصدر​

Read my English translation file at src/locales/en/common.json and
generate equivalent translations for Spanish (es), Japanese (ja),
and Arabic (ar). Preserve all interpolation variables like {{name}},
pluralization keys, and namespace structure exactly. Output three
separate JSON files in the correct locale directories.

يقرأ Cursor الملف المصدر وينتج ثلاثة ملفات JSON مترجمة. يحافظ الذكاء الاصطناعي على متغيرات ICU Message Format ولاحقات الجمع وبنية JSON.

استخراج السلاسل من الكود المصدر​

Scan all .tsx files in my src/ directory and extract every hard-coded
user-facing string into a translation key. Create a new common.json
file with the extracted strings organized by component. Then replace
each hard-coded string in the source with a useTranslation() call.

ينفذ Cursor إعادة بناء على مستوى قاعدة الكود: يحدد السلاسل الحرفية في JSX، ويولد ملف ترجمة منظم، ويستبدل كل ظهور بالاستدعاء t() المناسب.

warning

يجب دائمًا مراجعة الترجمات المولدة بالذكاء الاصطناعي من قبل متحدث أصلي قبل إطلاقها للإنتاج. يمكن للذكاء الاصطناعي أن يترجم بشكل خاطئ مصطلحات تعتمد على السياق، ويفوّت الفروق الثقافية الدقيقة، وينتج صياغة صحيحة نحويًا ولكن غير طبيعية. استخدم ترجمات الذكاء الاصطناعي كمسودة، وليس كمنتج نهائي. علّم أي ملفات مترجمة بالذكاء الاصطناعي بحالة "تحتاج مراجعة" في سير عمل إدارة الترجمة.

نص برمجة الترجمة الدفعية​

// scripts/translate.ts
import * as fs from 'fs/promises';
import * as path from 'path';

interface TranslationFile {
[key: string]: string | TranslationFile;
}

const SOURCE_LOCALE = 'en';
const TARGET_LOCALES = ['es', 'ja', 'ar'];
const SOURCE_DIR = path.join(__dirname, '../public/locales', SOURCE_LOCALE);
const OUTPUT_BASE = path.join(__dirname, '../public/locales');

async function translateFile(
source: TranslationFile,
targetLocale: string
): Promise<TranslationFile> {
const result: TranslationFile = {};
for (const [key, value] of Object.entries(source)) {
if (typeof value === 'string') {
// Replace with actual AI translation API call
result[key] = `[${targetLocale}] ${value}`;
} else {
result[key] = await translateFile(value, targetLocale);
}
}
return result;
}

async function runBatchTranslation() {
const namespaces = await fs.readdir(SOURCE_DIR);

for (const locale of TARGET_LOCALES) {
const outputDir = path.join(OUTPUT_BASE, locale);
await fs.mkdir(outputDir, { recursive: true });

for (const nsFile of namespaces.filter((f) => f.endsWith('.json'))) {
const sourceData = JSON.parse(
await fs.readFile(path.join(SOURCE_DIR, nsFile), 'utf-8')
) as TranslationFile;

const translated = await translateFile(sourceData, locale);
const outputPath = path.join(outputDir, nsFile);
await fs.writeFile(outputPath, JSON.stringify(translated, null, 2), 'utf-8');
console.log(`Translated ${nsFile} for ${locale} -> ${outputPath}`);
}
}
console.log('Batch translation complete.');
}

runBatchTranslation().catch(console.error);
تلميح

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

كشف الترجمات المفقودة​

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

// src/components/MissingTranslationLogger.tsx
import { useTranslation } from 'react-i18next';
import { useEffect, useRef } from 'react';

export function MissingTranslationLogger() {
const { i18n } = useTranslation();
const reportedKeys = useRef(new Set<string>());

useEffect(() => {
i18n.on('missingKey', (lngs: string[], ns: string, key: string) => {
const fullKey = `${ns}:${key}`;
if (reportedKeys.current.has(fullKey)) return;
reportedKeys.current.add(fullKey);

if (import.meta.env.DEV) {
console.warn(`[i18n] Missing: ${fullKey} for ${lngs.join(', ')}`);
}
// In production: reportToErrorService({ type: 'missing_translation', key: fullKey, locales: lngs });
});
}, [i18n]);

return null;
}

الخلاصة​

تمنحك الدولية والتعريب في Cursor الأدوات لبناء تطبيقات عالمية حقًا. باتباع أنماط هذا الدليل، تكوين i18next أو react-intl، وملفات الترجمة القائمة على مساحات الأسماء، والجمع المراعي لـ ICU، وخصائص CSS المنطقية لـ RTL، والتنسيق بواجهة Intl الأصلية، وخطوط أنابيب الترجمة بمساعدة الذكاء الاصطناعي، يمكنك دعم أي لغة ومنطقة من اليوم الأول. خطط لاستراتيجية i18n مبكرًا، استخدم خصائص CSS المنطقية بدلاً من الفيزيائية، دع الذكاء الاصطناعي يولد مسودات الترجمة الأولى، ودائمًا اطلب من متحدث أصلي المراجعة قبل الإطلاق للإنتاج.