Flutter intl Package Guide 2026: Setup, Formatting, Plurals & Best Practices
The intl package is the official foundation of Flutter internationalization — and it's more powerful than most developers realize. Beyond basic translations, it handles locale-aware date formatting, currency display, complex plural rules (including Arabic's six forms), gender-aware messages, and bidirectional text.
This guide goes beyond the basics. You'll get copy-paste setup code, real-world formatting examples for every use case, and solutions for the errors that waste hours of debugging time.
What is the Flutter intl Package?
The intl package (maintained by the Dart team) provides internationalization and localization support for Dart applications. It includes:
- Message formatting with placeholders, plurals, and gender
- Date and time formatting with 40+ named patterns
- Number and currency formatting for every locale
- Bidirectional text handling (Arabic, Hebrew, Persian, Urdu)
- ICU message syntax — the same standard used by Android, iOS, and web frameworks
Quick Setup Guide
Step 1: Add Dependencies
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.19.0
flutter:
generate: true
Step 2: Configure l10n.yaml
Create l10n.yaml in your project root:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
Step 3: Create Your First ARB File
Create lib/l10n/app_en.arb:
{
"@@locale": "en",
"appTitle": "My App",
"@appTitle": {
"description": "The application title"
},
"welcomeMessage": "Welcome, {username}!",
"@welcomeMessage": {
"description": "Welcome message with user name",
"placeholders": {
"username": {
"type": "String",
"example": "John"
}
}
}
}
Step 4: Configure MaterialApp
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Intl Demo',
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('es'),
Locale('fr'),
Locale('ar'),
],
home: const HomePage(),
);
}
}
Step 5: Use Translations in Widgets
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
),
body: Center(
child: Text(l10n.welcomeMessage('John')),
),
);
}
}
Date and Time Formatting with intl
The intl package excels at locale-aware date formatting:
import 'package:intl/intl.dart';
class DateFormattingExample {
void formatDates() {
final now = DateTime.now();
// Short date format
final shortDate = DateFormat.yMd('en_US').format(now);
// Output: 12/4/2025
// Long date format
final longDate = DateFormat.yMMMMd('en_US').format(now);
// Output: December 4, 2025
// Time format
final time = DateFormat.jm('en_US').format(now);
// Output: 3:30 PM
// Custom pattern
final custom = DateFormat('EEEE, MMMM d, y').format(now);
// Output: Thursday, December 4, 2025
// Different locale
final frenchDate = DateFormat.yMMMMd('fr_FR').format(now);
// Output: 4 décembre 2025
}
}
Number and Currency Formatting
import 'package:intl/intl.dart';
class NumberFormattingExample {
void formatNumbers() {
// Number formatting
final number = NumberFormat('#,##0.00', 'en_US').format(1234567.89);
// Output: 1,234,567.89
// Currency formatting
final usd = NumberFormat.currency(locale: 'en_US', symbol: '\$').format(99.99);
// Output: $99.99
final euro = NumberFormat.currency(locale: 'de_DE', symbol: '€').format(99.99);
// Output: 99,99 €
// Compact numbers
final compact = NumberFormat.compact(locale: 'en_US').format(1500000);
// Output: 1.5M
// Percentage
final percent = NumberFormat.percentPattern('en_US').format(0.75);
// Output: 75%
}
}
Message Formatting with Placeholders
Simple Placeholders
{
"greeting": "Hello, {name}!",
"@greeting": {
"placeholders": {
"name": {"type": "String"}
}
}
}
Numeric Placeholders
{
"itemsInCart": "You have {count} items in your cart",
"@itemsInCart": {
"placeholders": {
"count": {"type": "int"}
}
}
}
Date Placeholders
{
"lastLogin": "Last login: {date}",
"@lastLogin": {
"placeholders": {
"date": {
"type": "DateTime",
"format": "yMMMd"
}
}
}
}
Plural Support with intl
Handle complex plural rules correctly:
{
"messageCount": "{count, plural, =0{No messages} =1{One message} other{{count} messages}}",
"@messageCount": {
"placeholders": {
"count": {"type": "int"}
}
}
}
For languages with complex plural rules like Arabic:
{
"messageCount": "{count, plural, =0{لا رسائل} =1{رسالة واحدة} =2{رسالتان} few{{count} رسائل} many{{count} رسالة} other{{count} رسالة}}",
"@messageCount": {
"placeholders": {
"count": {"type": "int"}
}
}
}
Gender Support
{
"userWelcome": "{gender, select, male{Welcome, Mr. {name}} female{Welcome, Ms. {name}} other{Welcome, {name}}}",
"@userWelcome": {
"placeholders": {
"gender": {"type": "String"},
"name": {"type": "String"}
}
}
}
intl_utils vs flutter_intl: Which to Choose?
| Feature | intl_utils | flutter_intl (IDE Plugin) |
|---|---|---|
| CLI support | Yes | No |
| IDE integration | No | Yes (VS Code, Android Studio) |
| Watch mode | Yes | Yes |
| ARB generation | Yes | Yes |
| Best for | CI/CD, Teams | Individual developers |
Common intl Package Errors and Fixes
Error: "Getter not found: 'of'"
Solution: Run flutter gen-l10n to generate localization files.
flutter gen-l10n
Error: "The argument type 'String' can't be assigned"
Solution: Check your placeholder types match the ARB definition.
Error: "LocalizationsDelegate not found"
Solution: Ensure you've added the delegate to MaterialApp:
localizationsDelegates: [
AppLocalizations.delegate,
// ... other delegates
],
Performance Best Practices
1. Lazy Load Locales
supportedLocales: AppLocalizations.supportedLocales,
localeResolutionCallback: (locale, supportedLocales) {
return supportedLocales.firstWhere(
(supported) => supported.languageCode == locale?.languageCode,
orElse: () => supportedLocales.first,
);
},
2. Cache DateFormat Instances
class DateFormatters {
static final Map<String, DateFormat> _cache = {};
static DateFormat getFormatter(String pattern, String locale) {
final key = '$pattern-$locale';
return _cache.putIfAbsent(key, () => DateFormat(pattern, locale));
}
}
3. Use const Constructors
const Locale('en'); // Good - creates compile-time constant
Locale('en'); // Works but creates new instance each time
FlutterLocalisation: The Better Way
While the intl package is powerful, managing ARB files manually across multiple languages becomes tedious. FlutterLocalisation automates the entire workflow:
- Visual editor for translations (no JSON editing)
- AI-powered translations across 50+ languages
- Automatic ARB file generation
- Git integration for seamless syncing
- Real-time collaboration for teams
Select Messages (Beyond Gender)
The select ICU syntax isn't just for gender — use it for any enumerated choice:
{
"orderStatus": "{status, select, pending{Order is pending} shipped{Order has shipped} delivered{Order delivered!} other{Unknown status}}",
"@orderStatus": {
"placeholders": {
"status": {"type": "String"}
}
}
}
// Usage
Text(l10n.orderStatus('shipped'))
// Output: "Order has shipped"
Nested ICU Messages
Combine plural and select for complex messages:
{
"activitySummary": "{gender, select, male{{count, plural, =0{He has no posts} =1{He has one post} other{He has {count} posts}}} female{{count, plural, =0{She has no posts} =1{She has one post} other{She has {count} posts}}} other{{count, plural, =0{They have no posts} =1{They have one post} other{They have {count} posts}}}}",
"@activitySummary": {
"placeholders": {
"gender": {"type": "String"},
"count": {"type": "int"}
}
}
}
Common intl Version Conflicts and Fixes
intl 0.19.0 vs 0.18.x
The intl package version must be compatible with flutter_localizations:
# If you get version conflicts, check:
dependency_overrides:
intl: ^0.19.0 # Only if needed — prefer letting Flutter resolve this
Better approach: Let Flutter manage the version:
dependencies:
intl: any # Resolves to whatever flutter_localizations needs
"Null check operator used on a null value"
This usually means AppLocalizations.of(context) returned null:
// Common cause: using context before MaterialApp sets up delegates
// Fix: ensure you're below MaterialApp in the widget tree
// Wrong — context is from above MaterialApp
MaterialApp(
home: Text(AppLocalizations.of(context)!.hello), // CRASH
);
// Correct — use a separate widget
MaterialApp(
home: const HomePage(), // Gets its own context below MaterialApp
);
Testing with intl
Testing localized widgets requires setting up delegates:
Widget buildTestableWidget(Widget child) {
return MaterialApp(
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('en'), Locale('es')],
locale: const Locale('en'),
home: child,
);
}
testWidgets('shows localized greeting', (tester) async {
await tester.pumpWidget(buildTestableWidget(const HomePage()));
expect(find.text('Welcome!'), findsOneWidget);
});
Conclusion
The intl package is essential for Flutter internationalization, providing robust support for message formatting, dates, numbers, plurals, and more. While it's powerful, the complexity grows with each language you add — especially when managing ARB files across 10+ locales.
For professional apps targeting multiple markets, FlutterLocalisation automates the entire workflow: visual ARB editing, AI-powered translations across 50+ languages, and team collaboration — all while generating the same intl-compatible ARB files your project already uses.
Ready to simplify your Flutter internationalization? Try FlutterLocalisation free and automate your intl workflow with AI-powered translations and visual editing.