Complete Guide to RTL Language Support in Flutter Apps
Building a truly global Flutter app means supporting languages that read right-to-left (RTL). Arabic, Hebrew, Persian, and Urdu represent over 400 million native speakers worldwide. Getting RTL support right isn't just about flipping layouts — it's about creating an authentic experience for these users.
Understanding RTL Languages
Right-to-left languages require more than simple text direction changes. Your entire UI needs to adapt:
| Language | Native Speakers | Script Direction |
|---|---|---|
| Arabic | 310 million | Right-to-left |
| Hebrew | 9 million | Right-to-left |
| Persian (Farsi) | 70 million | Right-to-left |
| Urdu | 70 million | Right-to-left |
Key insight: These markets are often underserved in mobile apps, creating a competitive advantage for apps that support them properly.
Setting Up RTL Support in Flutter
Step 1: Configure Your l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
synthetic-package: false
Step 2: Add RTL Locales to MaterialApp
import 'package:flutter_localizations/flutter_localizations.dart';
MaterialApp(
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('en'),
Locale('ar'), // Arabic
Locale('he'), // Hebrew
Locale('fa'), // Persian
Locale('ur'), // Urdu
],
)
Step 3: Create Your Arabic .arb File
app_ar.arb:
{
"@@locale": "ar",
"welcomeMessage": "مرحباً بك في تطبيقنا",
"@welcomeMessage": {
"description": "Welcome message shown on the home screen"
},
"loginButton": "تسجيل الدخول",
"@loginButton": {
"description": "Button to log into the account"
},
"itemCount": "{count, plural, =0{لا توجد عناصر} =1{عنصر واحد} =2{عنصران} few{{count} عناصر} many{{count} عنصراً} other{{count} عنصر}}",
"@itemCount": {
"description": "Shows the number of items",
"placeholders": {
"count": {"type": "int"}
}
}
}
Important: Arabic has 6 plural forms (zero, one, two, few, many, other). Your translations must handle all of them.
Automatic Layout Mirroring
Flutter automatically mirrors layouts for RTL locales. Here's what happens:
What Flutter Mirrors Automatically
Rowchildren order reversesListView.builderhorizontal scroll direction flipsPaddingwithEdgeInsetsdirectional values swapAlignmentvalues likestartandendswap- Icons in
ListTileposition correctly
What You Must Handle Manually
// Use directional edge insets
Padding(
padding: EdgeInsetsDirectional.only(start: 16, end: 8),
child: Text('Properly padded content'),
)
// Use TextDirection-aware alignment
Align(
alignment: AlignmentDirectional.centerStart,
child: Text('Aligned to start'),
)
Common RTL Mistakes and Fixes
Mistake 1: Hardcoded Left/Right Values
Wrong:
Padding(
padding: EdgeInsets.only(left: 16),
child: Text('This breaks in RTL'),
)
Correct:
Padding(
padding: EdgeInsetsDirectional.only(start: 16),
child: Text('This works in both directions'),
)
Mistake 2: Non-Directional Icons
Wrong:
Icon(Icons.arrow_back)
Correct:
Icon(Icons.arrow_back_ios) // Automatically mirrors in RTL
// Or use directional icons explicitly
Icon(
Directionality.of(context) == TextDirection.rtl
? Icons.arrow_forward_ios
: Icons.arrow_back_ios,
)
Mistake 3: Ignoring Text Alignment
Wrong:
Text(
'Some text',
textAlign: TextAlign.left,
)
Correct:
Text(
'Some text',
textAlign: TextAlign.start, // Respects text direction
)
Mistake 4: Hardcoded Layout Direction
Wrong:
Row(
textDirection: TextDirection.ltr, // Forces LTR always
children: [...],
)
Correct:
Row(
// Let Flutter handle direction based on locale
children: [...],
)
Testing RTL Layouts
Force RTL for Testing
MaterialApp(
builder: (context, child) {
return Directionality(
textDirection: TextDirection.rtl,
child: child!,
);
},
)
Check Current Direction in Widgets
Widget build(BuildContext context) {
final isRTL = Directionality.of(context) == TextDirection.rtl;
return Container(
child: Text(isRTL ? 'RTL Mode Active' : 'LTR Mode Active'),
);
}
Debug Layout Issues
Add this to quickly visualize directional problems:
Directionality(
textDirection: TextDirection.rtl,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.red),
),
child: YourWidget(),
),
)
Bidirectional Text (BiDi) Handling
Sometimes you need to mix RTL and LTR content:
Embedding LTR in RTL Context
Text(
'السعر: \$99.99', // Price in Arabic with English currency
textDirection: TextDirection.rtl,
)
Using Unicode Directional Characters
{
"priceLabel": "السعر: \u200F{price}\u200F دولار",
"@priceLabel": {
"description": "Price label with RTL marks around the number",
"placeholders": {
"price": {"type": "String"}
}
}
}
Unicode control characters:
\u200F(RLM) - Right-to-Left Mark\u200E(LRM) - Left-to-Right Mark
Font Considerations for RTL
Arabic Font Requirements
Arabic text requires fonts that support:
- Character shaping (letters change form based on position)
- Ligatures (connected letters)
- Diacritics (vowel marks)
Recommended Arabic fonts:
- Noto Sans Arabic (Google Fonts)
- Amiri
- Cairo
- Tajawal
Loading Arabic Fonts
// pubspec.yaml
fonts:
- family: NotoSansArabic
fonts:
- asset: fonts/NotoSansArabic-Regular.ttf
- asset: fonts/NotoSansArabic-Bold.ttf
weight: 700
// Usage
Text(
'مرحباً',
style: TextStyle(
fontFamily: 'NotoSansArabic',
),
)
Automatic Font Selection
TextStyle(
fontFamilyFallback: ['NotoSansArabic', 'Roboto'],
)
RTL-Specific UI Patterns
Navigation Drawer
Scaffold(
// Drawer automatically appears on the correct side
drawer: Drawer(...), // Left in LTR, Right in RTL
endDrawer: Drawer(...), // Right in LTR, Left in RTL
)
Swipe Gestures
Dismissible(
// Swap directions based on locale
direction: Directionality.of(context) == TextDirection.rtl
? DismissDirection.startToEnd
: DismissDirection.endToStart,
child: ListTile(...),
)
Progress Indicators
LinearProgressIndicator(
// Automatically respects text direction
value: 0.5,
)
Performance Tips for RTL
Avoid Runtime Direction Checks
Less efficient:
Widget build(BuildContext context) {
final isRTL = Directionality.of(context) == TextDirection.rtl;
return Padding(
padding: EdgeInsets.only(
left: isRTL ? 0 : 16,
right: isRTL ? 16 : 0,
),
);
}
More efficient:
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsetsDirectional.only(start: 16),
);
}
Cache Directional Values
class _MyWidgetState extends State<MyWidget> {
late TextDirection _direction;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_direction = Directionality.of(context);
}
}
RTL Localization Checklist
Before launching your app with RTL support:
- All
EdgeInsetsconverted toEdgeInsetsDirectional - All
Alignmentvalues use directional variants - Icons that indicate direction are mirrored correctly
- Text alignment uses
start/endinstead ofleft/right - Arabic plural forms (all 6) are properly handled
- Fonts support Arabic/Hebrew character shaping
- Bidirectional text displays correctly
- Swipe gestures work intuitively in RTL
- Navigation drawer appears on correct side
- Numbers and prices display properly with mixed directions
Real-World Success Story
"Adding Arabic support to our e-commerce app increased downloads in MENA region by 340% within two months. The key was proper RTL implementation — users immediately noticed the attention to detail."
— Ahmed Hassan, Mobile Lead at RetailTech
Automating RTL Translation Management
Managing translations for RTL languages adds complexity:
- Multiple plural forms to track
- Character encoding issues
- Bidirectional text validation
FlutterLocalisation handles these challenges with:
- Visual editors that display RTL text correctly
- Automatic plural form validation for Arabic
- AI translation with RTL language support
- Real-time preview of translations in context
Related Resources:
Ready to expand into Arabic, Hebrew, and other RTL markets? Start with proper localization infrastructure — your users will notice the difference.