دليل React Native للعربية (2): تخطيط RTL دون نسخ الواجهة

استخدام الاتجاه وخصائص start و end لبناء تخطيط React Native يعمل بالعربية والإنجليزية مع اختبارات واضحة.

كل المقالات
دليل React Native للعربية (2): تخطيط RTL دون نسخ الواجهة

أكثر أخطاء RTL شيوعاً تأتي من خلط اتجاهين: اتجاه واجهة Yoga، وترتيب صممه المطور يدوياً باستخدام left وright وrow-reverse. عندما يعمل الاثنان معاً قد تنقلب الشاشة مرتين، فتبدو صحيحة في جهاز وخاطئة في آخر.

ابدأ بمصدر واحد للاتجاه، ثم استخدم خصائص منطقية كلما كان المعنى هو بداية السطر أو نهايته.

ما معنى start و end؟

في اتجاه LTR تشير start إلى اليسار وend إلى اليمين. في RTL تنعكس الدلالة. ينطبق ذلك على خصائص مثل:

  • marginStart وmarginEnd
  • paddingStart وpaddingEnd
  • borderStartWidth وborderEndWidth
  • start وend للعناصر ذات التموضع

بدلاً من ربط المسافة بجانب ثابت:

const styles = StyleSheet.create({
  avatar: {
    marginRight: 12,
  },
});

اربطها بعلاقة العنصر بالنص:

const styles = StyleSheet.create({
  avatar: {
    marginEnd: 12,
  },
});

في بطاقة تبدأ بالصورة ثم النص، marginEnd تعني المسافة بعد الصورة في الاتجاهين.

اضبط الاتجاه عند جذر المنطقة

إذا كانت لغة التطبيق تتبع إعداد الجهاز، يستطيع I18nManager توفير اتجاه الجذر عند بدء التطبيق. إذا كان للمستخدم اختيار لغة مستقل، مرر الاتجاه إلى جذر الشاشة أو المنطقة التي تملكها، واختبر دعم نسخة React Native المستخدمة في المشروع:

type Direction = "rtl" | "ltr";

export function ProfileCard({ direction }: { direction: Direction }) {
  return (
    <View style={[styles.card, { direction }]}>
      <View style={styles.row}>
        <Image source={avatar} style={styles.avatar} />
        <View style={styles.copy}>
          <Text style={styles.name}>أحمد محمد</Text>
          <Text style={styles.email}>ahmed@example.com</Text>
        </View>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    paddingStart: 16,
    paddingEnd: 16,
  },
  row: {
    flexDirection: "row",
    alignItems: "center",
  },
  avatar: {
    width: 48,
    height: 48,
    marginEnd: 12,
  },
  copy: {
    flex: 1,
  },
  name: {
    textAlign: "auto",
  },
  email: {
    textAlign: "auto",
  },
});

توثيق Flexbox في React Native يوضح أن اتجاه التخطيط يغير معنى البداية والنهاية وترتيب الأطفال والنص. لا تضف row-reverse تلقائياً إلى كل صف عربي. استخدمه فقط عندما يكون ترتيب المنتج نفسه معكوساً، وليس لتعويض اتجاه جذر غير مضبوط.

العناصر المطلقة تحتاج معنى واضحاً

شارة توضع عند نهاية البطاقة يمكن كتابتها هكذا:

const styles = StyleSheet.create({
  badge: {
    position: "absolute",
    top: 8,
    end: 8,
  },
});

أما عنصر مرتبط بجانب مادي ثابت، مثل مقبض رسم أو عنصر يطابق حافة صورة لا تتغير، فقد يحتاج left أو right. لا تستبدل كل قيمة ثابتة آلياً. اسأل هل المقصود بداية القراءة أم الجانب الفيزيائي للشاشة.

إذا كانت مكتبة قديمة لا تحترم start وend، فلا تستبدلهما مباشرة بشرط بين left وright. يستطيع React Native تبديل هاتين الخاصيتين أصلاً عندما تكون I18nManager.doLeftAndRightSwapInRTL مفعلة، وقد يؤدي الشرط اليدوي إلى انعكاس مزدوج. كذلك قد تختلف I18nManager.isRTL عن اتجاه اللغة الذي اختاره المستخدم داخل التطبيق.

اعزل المكتبة خلف مكون adapter يقرأ direction نفسه الذي يملكه التطبيق. افحص إعداد التبديل الأصلي ونسخة المكتبة على iOS و Android، ثم اكتب اختباراً للموضع النهائي قبل إضافة أي تحويل مادي. لا يوجد fallback عام آمن من دون معرفة كيف تتعامل تلك المكتبة مع RTL.

محاذاة النص ليست اتجاه التخطيط

اترك textAlign: 'auto' للنصوص العادية عندما يستطيع النظام تحديدها. لا تجعل كل نص عربي right وكل نص إنجليزي left دون النظر إلى المحتوى، لأن حقول أسماء المستخدمين أو رسائل الدردشة قد تحتوي اللغتين.

<Text style={{ textAlign: "auto" }}>{message}</Text>

يوفر React Native خاصية writingDirection على iOS، لكن توثيق الخصائص يبين أنها خاصة بالمنصة. لا تعتمد عليها وحدها لحل سلوك Android. اختبر النصوص المختلطة، خصوصاً الروابط والبريد والأرقام وعلامات الترقيم.

لا تقلب كل أيقونة

الأيقونات التي تصف حركة نسبية قد تحتاج الانعكاس، مثل رجوع وتقدم والسهم الذي يفتح لوحة من البداية. الأيقونات ذات المعنى الثابت لا تنعكس، مثل تشغيل الفيديو والكاميرا وعلامة الصح والشعارات.

import { Image, type ImageSourcePropType } from "react-native";

type Direction = "rtl" | "ltr";

export function BackIcon({
  direction,
  source,
}: {
  direction: Direction;
  source: ImageSourcePropType;
}) {
  const isRTL = direction === "rtl";

  return (
    <Image
      source={source}
      style={{ transform: [{ scaleX: isRTL ? -1 : 1 }] }}
    />
  );
}

اختر اسماً دلالياً مثل BackArrow بدلاً من ArrowLeft. الاسم الدلالي يقلل احتمال استخدام سهم مادي خاطئ في العربية.

اجعل الحركة تتبع الاتجاه

الحركة التي تدخل من بداية الشاشة تحتاج إشارة مختلفة في RTL:

import { useEffect, useRef, type ReactNode } from "react";
import { Animated } from "react-native";

type Direction = "rtl" | "ltr";

export function DirectionalEntrance({
  direction,
  children,
}: {
  direction: Direction;
  children: ReactNode;
}) {
  const isRTL = direction === "rtl";
  const translateX = useRef(new Animated.Value(isRTL ? 24 : -24)).current;

  useEffect(() => {
    translateX.setValue(isRTL ? 24 : -24);

    const animation = Animated.timing(translateX, {
      toValue: 0,
      duration: 180,
      useNativeDriver: true,
    });

    animation.start();
    return () => animation.stop();
  }, [isRTL, translateX]);

  return (
    <Animated.View style={{ transform: [{ translateX }] }}>
      {children}
    </Animated.View>
  );
}

يعيد المثال ضبط القيمة الابتدائية عند تغير الاتجاه، ويربطها فعلياً بالعنصر المتحرك، ويوقف الحركة عند إزالة المكون. لا تقلب كل translateX; سحب صورة أو تحريك خريطة قد يكون مرتبطاً بالإيماءة الفعلية لا باتجاه اللغة.

الشبكات والمسافات الطويلة

النص العربي والإنجليزي لا يشغلان المساحة نفسها. تجنب ارتفاعات ثابتة للبطاقات والعناوين. استخدم flexShrink: 1 للنص داخل الصف، وحدد minWidth: 0 عندما تمنع قيود التخطيط الانكماش.

const styles = StyleSheet.create({
  copy: {
    flex: 1,
    minWidth: 0,
  },
  title: {
    flexShrink: 1,
  },
});

اختبر ترجمة أطول من النص الحقيقي المتوقع. زر يبدو جيداً مع “حفظ” قد ينكسر مع عبارة إنجليزية أطول أو مع تكبير خط النظام.

مصفوفة اختبار التخطيط

راجع كل شاشة في أربع حالات على الأقل:

  1. تطبيق عربي واتجاه RTL على جهاز لغته عربية.
  2. تطبيق إنجليزي واتجاه LTR على جهاز لغته إنجليزية.
  3. تطبيق عربي واتجاه RTL على جهاز لغته إنجليزية.
  4. تطبيق إنجليزي واتجاه LTR على جهاز لغته عربية.
  5. عربية مع نص لاتيني داخل الحقول.
  6. إنجليزية مع اسم أو عنوان عربي.

ثم اختبر:

  • هاتفاً ضيقاً وجهازاً أكبر.
  • تكبير خط النظام.
  • دوران الشاشة إذا كان التطبيق يدعمه.
  • الأيقونات الانتقالية والثابتة.
  • التموضع المطلق والحوارات والقوائم.
  • الحركة والإيماءات في الاتجاهين.
  • قيمة I18nManager.isRTL وإعداد doLeftAndRightSwapInRTL مقابل اتجاه التطبيق الفعلي.

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

المراجع الرسمية