العربي والـ RTL في React Native و Expo: إيه اللي بيبوظ وبصلّحه إزاي
دروس من تلات تطبيقات عربي: تغيير الاتجاه من غير ما التطبيق يفضل يعيد تشغيل نفسه، أرقام بتنط للناحية الغلط، رسوم بيانية ما ينفعش تتعكس، وأيقونات بتتقلب مرتين.
أحمد عبد المجيد
قراءة 3 دقايق
كل تطبيق ببنيه لازم يشتغل عربي وإنجليزي، والعربي بيتقري من اليمين للشمال. React Native بيقدر يعكس الشاشة لوحده، وده بيوصّلك لمعظم الطريق وبعدين يسيبلك قايمة أخطاء صغيرة وغريبة. دي الأخطاء اللي قابلتها في تلات تطبيقات، Gym Buddy وقهوتي و Sima Track، ومع كل واحدة الحل اللي دخل الكود.

١. تغيير الاتجاه محتاج إعادة تشغيل، فاحمي إعادة التشغيل#
اتجاه الشاشة على الموبايل بيتحدد بـ I18nManager.forceRTL، ومش بيتطبق غير بعد ما التطبيق يعيد التشغيل. ده تمام لحد ما حاجة تتجاهل الأمر: التطبيق يعيد التشغيل، يلاقي الاتجاه لسه غلط، فيعيد تاني، وهكذا للأبد. قهوتي بيفتكر إنه جرّب مرة قبل كده للغة دي ويقف.
I18nManager.allowRTL(true);
if (directionMatches(lang)) {
void AsyncStorage.removeItem(RELOAD_GUARD_KEY);
return false;
}
I18nManager.forceRTL(wantsRTL(lang));
// One attempt per language, so a host that ignores forceRTL can't loop forever.
const tried = await AsyncStorage.getItem(RELOAD_GUARD_KEY).catch(() => null);
if (tried === lang) return false;
await AsyncStorage.setItem(RELOAD_GUARD_KEY, lang).catch(() => {});
return reloadApp();فيه فخ تاني في إعدادات Expo. مكتبة expo-localization فيها اختيار supportsRTL، ولما كان مفعّل الاتجاه كان بيرجع للغة الموبايل مع كل فتحة للتطبيق. اللي اختار عربي جوه التطبيق وموبايله إنجليزي كان بيرجعله الشكل من الشمال لليمين. لو التطبيق فيه اختيار لغة خاص بيه، سيب الاختيار ده.
٢. الأرقام والكلام اللاتيني بينطوا للناحية الغلط#
حط "0 km/h" جوه جملة عربي وهتطلع "km/h 0". محرّك النصوص بيعتبر الرقم والوحدة حتتين منفصلتين ويرتبهم من اليمين للشمال. الحل إنك تحاوط الحتة دي بحروف Unicode اسمها isolates، ومعناها: اكتب ده من الشمال لليمين كحتة واحدة. وبتشتغل زي بعض على آيفون وأندرويد والويب، عكس خاصية writingDirection اللي على آيفون بس.
const LRI = '\u2066';
const PDI = '\u2069';
export function ltr(value: string | number | null | undefined): string {
const s = value == null ? '' : String(value);
return s ? `${LRI}${s}${PDI}` : s;
}نفس الكلام على أرقام التليفونات ومواعيد العمل وأكواد التأكيد: بتتقري من الشمال لليمين في اللغتين. وللتواريخ والأسعار، الـ locale اللي اسمه "ar-u-nu-latn" بيدّيك كلام عربي بأرقام لاتيني، فالأرقام تبقى شكلها واحد في كل التطبيق. والعربي بيعدّ بطريقة غير الإنجليزي: واحد، اتنين، من تلاتة لعشرة، ومن حداشر وطالع، كل واحدة ليها صيغة.
function arCount(n: number, forms: { one: string; two: string; few: string; many: string; hundred?: string }): string {
const m = n % 100;
const s = n === 1 ? forms.one : n === 2 ? forms.two : m >= 3 && m <= 10 ? forms.few : m >= 11 && m <= 99 ? forms.many : (forms.hundred ?? forms.many);
return s.replace("{n}", String(n));
}٣. الأماكن الثابتة والرسوم البيانية ما ينفعش تتعكس#
في الـ RTL، محرّك التخطيط في React Native بيبدّل اليمين والشمال، وده بيشمل العناصر اللي مكانها ثابت. زرار على الخريطة مكتوب له right: 16 بيروح للحافة الشمال. لما حاجة لازم تفضل في ناحية معيّنة من الشاشة فعلاً، سمّي الحافة حسب الاتجاه.
const PHYSICAL_RIGHT = I18nManager.isRTL ? { left: 16 } : { right: 16 };الرسوم البيانية هي الحالة التانية. محور الوقت بيتقري من الشمال لليمين في اللغتين؛ لو اتعكس، أحدث يوم هييجي على الشمال والاتجاه هيمشي بالعكس. عشان كده الرسوم بتتحط جوه عنصر عليه direction: 'ltr'. والنص جوه الـ SVG مشكلة لوحده: مش بيشكّل العربي، فوحدة زي "لتر" كانت بتطلع حروف مفكوكة ومعكوسة. فعناوين الرسم بقت عناصر Text عادية محطوطة حوالين الرسم.
اعكس الشاشة. وعمرك ما تعكس البيانات.
٤. الأيقونات تتقلب مرة واحدة، مش مرتين#
React Native بيعكس التخطيط، مش الرسمة اللي جوه الأيقونة، فسهم "قدّام" بيفضل باصص يمين في العربي. Sima Track بيقلب قايمة قصيرة من أيقونات الاتجاه جوه مكوّن الأيقونة نفسه ومفيش غيرها: السهم اللي معناه "في ارتفاع" ما ينفعش يتقلب.
const RTL_MIRROR = new Set<IconSymbolName>([
'chevron.left',
'chevron.right',
'arrow.right',
'rectangle.portrait.and.arrow.right',
]);
const mirror = I18nManager.isRTL && RTL_MIRROR.has(name as keyof typeof MAPPING);الغلطة اللي لازم تتجنبها إنك تعملها مرتين. لو المكوّن بيقلب الأسهم لوحده، واخترت اتجاه السهم حسب اللغة وانت بتستخدمه، هيتقلب تاني ويرجع غلط.
٥. الخطوط العربي محتاجة أوزانها ومساحة أكبر#
الخطوط الخاصة بتيجي ملف لكل وزن. لو حطيت fontWeight فوق خط محدد الوزن، أندرويد بيرجع لخط النظام، فـ Gym Buddy بيختار الوزن عن طريق fontFamily ويصفّر fontWeight.
export function fontStyle(lang: Lang, weight: TextStyle['fontWeight'] = 'normal'): TextStyle {
if (lang !== 'ar') return {};
return { fontFamily: CAIRO_BY_WEIGHT[String(weight)] ?? 'Cairo_400Regular', fontWeight: 'normal' };
}والحروف العربي كمان أطول، وفيها نقط فوق السطر وتحته. عناوين شريط التبويبات كانت بتتقص لحد ما الشريط بقى أطول في العربي، وارتفاع السطور في قهوتي أكبر من اللي تصميم لاتيني بس كان هيستخدمه.
نصيحتي لأي حد بيبني حاجة زي كده#
- أعد التشغيل مرة واحدة عشان تغيّر الاتجاه، واتأكد إنه مش هيلف في دايرة.
- حاوط الأرقام والوحدات والأكواد بـ isolates عشان تحافظ على ترتيبها.
- قرّر لكل عنصر: اعكس الشاشة، وعمرك ما تعكس البيانات.
- اقلب أيقونات الاتجاه في مكان واحد بس.
- افتح كل شاشة بالعربي قبل ما تقول خلصت.


