← Back to Blog

Flutter intl Package Guide 2026: Setup, Formatting, Plurals & Best Practices

flutterintlinternationalizationdate-formatnumber-formati18npluralization2026

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.