Kodax

التعريب وتعدد اللغات — Flutter المتقدم

التعريب وتعدد اللغات

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

خياران رئيسيان

| الأداة | المزايا |

|---|---|

| flutter_localizations + gen-l10n (الرسمي) | ملفات ARB، توليد كود آمن الأنواع (AppLocalizations.of(context).welcome)، مدعوم من فريق Flutter |

| easy_localization (المثال الكامل) | ملفات JSON، إعداد أسرع، تبديل اللغة أثناء التشغيل بسهولة |

كلاهما يحتاج تفعيل flutter_localizations في MaterialApp (localizationsDelegates و supportedLocales) لتُترجم مكونات Material نفسها (أزرار الحوار، منتقي التاريخ).

RTL: ما يحدث تلقائياً وما لا يحدث

عند اختيار لغة عربية، يضبط MaterialApp الاتجاه TextDirection.rtl تلقائياً:

- Row تبدأ من اليمين، و ListTile تعكس leading/trailing، وأيقونة الرجوع تنقلب.

- استخدم EdgeInsetsDirectional.only(start: 16) بدل EdgeInsets.only(left: 16)، و AlignmentDirectional.centerStart بدل Alignment.centerLeft، و PositionedDirectional.

- ما لا ينقلب تلقائياً: الصور والأيقونات الاتجاهية المخصصة (اعكسها شرطياً حسب Directionality.of(context))، والرسوم البيانية.

صيغ الجمع العربية

العربية لها ست فئات جمع: zero (0)، one (1)، two (2)، few (3–10)، many (11–99)، other (100 فأكثر وغيرها). لهذا يحتوي ملف ar.json في المثال على المفاتيح الست، بينما الإنجليزية تكفيها one و other:

0 ← لا توجد عناصر     1 ← عنصر واحد     2 ← عنصران
5 ← 5 عناصر          11 ← 11 عنصراً    100 ← 100 عنصر

لا تبنِ الجمل بلصق الكلمات ("عدد" + n + "عناصر") — اترك صيغة الجمع لنظام الترجمة.

التواريخ والأرقام

حزمة intl: DateFormat.yMMMMd('ar') يعطي "1 أكتوبر 2026"، و NumberFormat.currency(locale: 'ar_IQ', symbol: 'د.ع') للعملة. قرر بوعي: أرقام عربية مشرقية (٠١٢) أم غربية (012)؟ كثير من التطبيقات العربية تستخدم الغربية للوضوح.

نصائح

- لا نصوص ثابتة داخل الـ Widgets: كل نص من ملفات الترجمة.

- مفاتيح وصفية (checkout_pay_button) لا (text1).

- اختبر كل شاشة بالعربية والإنجليزية: النصوص العربية والألمانية تختلف أطوالها وتكسر التخطيطات الضيقة.

تمرين

أضف للمثال مفتاح days_left بصيغ الجمع العربية الست، واعرضه لـ 0 و 1 و 2 و 7 و 15 و 100 يوماً. ثم استبدل كل EdgeInsets.only(left/right) في تطبيقك بالنسخة الاتجاهية.

// ===== ملفات الترجمة =====
// assets/translations/ar.json
{
  "app_name": "تطبيقي",
  "welcome": "مرحباً {name}!",
  "items_count": {
    "zero": "لا توجد عناصر",
    "one": "عنصر واحد",
    "two": "عنصران",
    "few": "{} عناصر",
    "many": "{} عنصراً",
    "other": "{} عنصر"
  }
}

// assets/translations/en.json
{
  "app_name": "My App",
  "welcome": "Welcome {name}!",
  "items_count": {
    "one": "One item",
    "other": "{} items"
  }
}

// ===== main.dart =====
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();
  
  runApp(
    EasyLocalization(
      supportedLocales: [Locale('ar'), Locale('en')],
      path: 'assets/translations',
      fallbackLocale: Locale('en'),
      startLocale: Locale('ar'),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      home: HomeScreen(),
    );
  }
}

// ===== استخدام الترجمة =====
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('app_name'.tr()),
      ),
      body: Column(
        children: [
          // نص بسيط
          Text('app_name'.tr()),
          
          // نص مع متغير
          Text('welcome'.tr(namedArgs: {'name': 'أحمد'})),
          
          // جمع (Plural)
          Text('items_count'.plural(5)),
          
          // تغيير اللغة
          ElevatedButton(
            onPressed: () {
              context.setLocale(Locale('en'));
            },
            child: Text('English'),
          ),
          
          ElevatedButton(
            onPressed: () {
              context.setLocale(Locale('ar'));
            },
            child: Text('العربية'),
          ),
        ],
      ),
    );
  }
}

// ===== RTL Aware Widgets =====
class RTLAwareWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final isRTL = Directionality.of(context) == TextDirection.rtl;
    
    return Padding(
      // استخدم start/end بدل left/right
      padding: EdgeInsetsDirectional.only(start: 16, end: 8),
      child: Row(
        children: [
          // يتغير تلقائياً حسب الاتجاه
          Icon(Icons.arrow_forward),
          Spacer(),
          Text('النص'),
        ],
      ),
    );
  }
}
اختبر فهمك