التعريب وتعدد اللغات — 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('النص'),
],
),
);
}
}اختبر فهمك