دليل React Native للعربية (3): الخطوط والقياس عبر iOS و Android

اختيار خط عربي وتضمينه واختبار أوزانه وارتفاع السطر وتكبير النص في تطبيقات React Native.

كل المقالات
دليل React Native للعربية (3): الخطوط والقياس عبر iOS و Android

اسم الخط وحده لا يضمن أن النص العربي سيعمل. الملف يجب أن يحتوي المحارف المطلوبة والتشكيل والأرقام، وأن تكون أوزانه مسجلة بأسماء تستطيع iOS و Android قراءتها. بعد ذلك يأتي التخطيط: ارتفاع السطر، تكبير خط النظام، والنص المختلط.

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

افحص الملف والترخيص أولاً

تحقق من أربعة أمور قبل إضافة الخط:

  1. يسمح الترخيص بتضمينه داخل تطبيق موزع.
  2. يغطي العربية واللاتينية والرموز التي يستخدمها المنتج.
  3. توجد ملفات فعلية للأوزان المطلوبة، مثل 400 و600 و700.
  4. الاسم الداخلي للخط معروف، وليس اسم الملف فقط.

اختبر نصوصاً فيها تشكيل وأرقام وعلامات ترقيم وأسماء لاتينية:

الطَّلَب رقم QA-2048 جاهز للاستلام الساعة 10:30.
لا إله إلا الله، محمد رسول الله
البريد: user@example.com
السعر: ١٬٢٥٠٫٥٠ ر.ق

راقب اتصال الحروف وموقع التشكيل والمسافات وارتفاع السطر. قد ينجح الوزن العادي بينما يفشل الوزن الغامق بسبب ملف ناقص أو اسم عائلة مختلف.

اختر طريقة التضمين حسب المشروع

في مشروع Expo، توصي وثائق Expo بتضمين الخط عبر config plugin عندما يكون ذلك ممكناً. يصبح الخط جزءاً من البناء الأصلي ومتاحاً عند بدء التطبيق:

npx expo install expo-font
{
  "expo": {
    "plugins": [
      [
        "expo-font",
        {
          "fonts": [
            "./assets/fonts/NotoSansArabic-Regular.ttf",
            "./assets/fonts/NotoSansArabic-Bold.ttf"
          ]
        }
      ]
    ]
  }
}

يتطلب تغيير config plugin بناء development build جديداً؛ لن يظهر بمجرد تحديث JavaScript في Expo Go.

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

const [loaded, error] = useFonts({
  "NotoSansArabic-Regular": require("../assets/fonts/NotoSansArabic-Regular.ttf"),
  "NotoSansArabic-Bold": require("../assets/fonts/NotoSansArabic-Bold.ttf"),
});

if (error) {
  throw error;
}

if (!loaded) {
  return null;
}

في مشروع React Native أصلي، تتغير الخطوات بحسب إعداد البناء. على iOS يجب أن يكون الملف عضواً في target ومسجلاً ضمن UIAppFonts. على Android يمكن تضمين الخطوط كموارد أو أصول وفق إعداد المشروع. لا تعتمد على أمر نسخ قديم دون التحقق من نسخة CLI. وثائق Apple و Android هي المرجع عندما يحتاج الربط الأصلي إلى تشخيص.

اسم العائلة قد يختلف بين المنصتين

على iOS يقرأ النظام اسم الخط الداخلي، وقد لا يطابق اسم الملف. توضح وثائق Apple كيفية عرض أسماء العائلات والخطوط المسجلة. في Android قد تعتمد التسمية على طريقة التضمين.

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

يمكن لمكون نص مركزي أن يمنع استخدام أسماء مختلفة في عشرات الشاشات:

import { Text, type TextProps } from "react-native";

type AppTextProps = TextProps & {
  weight?: "regular" | "bold";
};

const fontByWeight = {
  regular: "NotoSansArabic-Regular",
  bold: "NotoSansArabic-Bold",
} as const;

export function AppText({ weight = "regular", style, ...props }: AppTextProps) {
  return (
    <Text {...props} style={[{ fontFamily: fontByWeight[weight] }, style]} />
  );
}

لا تفترض أن fontWeight: '700' سيختار الملف الصحيح مع كل طريقة ربط. إذا عرّفت عائلة بأوزان أصلية بطريقة موثقة، استخدم الوزن. إذا كانت الملفات مسجلة كعائلات منفصلة، استخدم الاسم الصحيح لكل ملف واختبره.

includeFontPadding ليس علاجاً عاماً

على Android تكون قيمة includeFontPadding الافتراضية true. توضح وثائق React Native أن ضبطها إلى false يزيل مساحة إضافية مخصصة للصواعد والنوازل، وقد يحسن المحاذاة العمودية لبعض الخطوط مع textAlignVertical: 'center'.

label: {
  includeFontPadding: false,
  textAlignVertical: 'center',
}

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

ارتفاع السطر يحتاج اختباراً عربياً

قيمة lineHeight الضيقة التي تبدو جيدة بالإنجليزية قد تقص التشكيل أو تجعل أسطر العربية متداخلة. اربطها بحجم الخط ضمن token واضح، ثم اختبر فقرتين على الأقل:

const typography = StyleSheet.create({
  body: {
    fontFamily: "NotoSansArabic-Regular",
    fontSize: 16,
    lineHeight: 26,
    textAlign: "auto",
  },
  title: {
    fontFamily: "NotoSansArabic-Bold",
    fontSize: 24,
    lineHeight: 36,
    textAlign: "auto",
  },
});

الأرقام هنا نقطة بداية للمثال وليست معياراً لكل خط. بعض الخطوط تحتاج مسافة أكبر بسبب مقاييسها الداخلية.

لا تعطل تكبير النص لتثبيت التصميم

قيمة allowFontScaling الافتراضية في <Text> هي true. حافظ عليها في النصوص وعناصر التحكم. يمكن لـ maxFontSizeMultiplier وضع حد عند وجود قيد حقيقي، لكنه قد يمنع مستخدماً من الوصول إلى الحجم الذي يحتاجه.

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

على iOS يمكن استخدام dynamicTypeRamp لربط النص بنمط من أنماط Dynamic Type. اختبر النتيجة على الجهاز بدلاً من افتراض أن حجم المحاكي الافتراضي يمثل المستخدمين.

تعامل مع النص المختلط بواقعية

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

اترك textAlign: 'auto' في المحتوى المختلط ما لم يكن المنتج يفرض محاذاة محددة. خاصية writingDirection متاحة على iOS فقط حسب توثيق React Native، لذلك لا تجعلها الحل الوحيد.

اختبار قبول الخط

استخدم مصفوفة صغيرة لكل وزن معتمد:

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

لا تعتمد الخط إذا سقط أحد الأوزان إلى خط النظام أو ظهر قص للحروف في مكون أساسي.

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