How to Translate a Flutter App: Complete Guide

Learn how to localize your Flutter app using Dart and the intl package. This comprehensive guide covers string localization, pluralization, date formatting, and best practices for Flutter ap

  • date icon

    Friday, Nov 14, 2025

How to Translate a Flutter App: Complete Guide

Translating your Flutter app is essential for reaching a global audience. Flutter provides excellent built-in support for localization through the intl package and ARB (Application Resource Bundle) files. This guide will walk you through the process of translating a Flutter app using Dart.

Understanding Flutter Localization

Flutter uses ARB files (Application Resource Bundle) to store translations. These JSON-like files contain key-value pairs for each language. Flutter’s intl package handles the localization automatically based on the user’s device language settings.

Step 1: Add Dependencies

First, add the necessary packages to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^0.19.0

flutter:
  generate: true

Run flutter pub get to install the packages.

Step 2: Configure l10n.yaml

Create a l10n.yaml file in your project root to configure localization:

arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

Step 3: Create ARB Files

Create a lib/l10n directory and add ARB files for each language:

lib/l10n/app_en.arb (English - default):

{
  "@@locale": "en",
  "appName": "My App",
  "@appName": {
    "description": "The application name"
  },
  "welcomeMessage": "Welcome to our app!",
  "@welcomeMessage": {
    "description": "Welcome message displayed on home screen"
  },
  "buttonSubmit": "Submit",
  "@buttonSubmit": {
    "description": "Submit button label"
  },
  "errorNetwork": "Network error. Please try again.",
  "@errorNetwork": {
    "description": "Network error message"
  },
  "itemsCount": "{count, plural, =0{No items} one{1 item} other{{count} items}}",
  "@itemsCount": {
    "description": "Number of items",
    "placeholders": {
      "count": {
        "type": "int"
      }
    }
  },
  "userGreeting": "Hello, {name}! You have {messageCount, plural, =0{no messages} one{1 message} other{{messageCount} messages}}.",
  "@userGreeting": {
    "description": "Greeting with user name and message count",
    "placeholders": {
      "name": {
        "type": "String"
      },
      "messageCount": {
        "type": "int"
      }
    }
  }
}

lib/l10n/app_es.arb (Spanish):

{
  "@@locale": "es",
  "appName": "Mi Aplicación",
  "welcomeMessage": "¡Bienvenido a nuestra aplicación!",
  "buttonSubmit": "Enviar",
  "errorNetwork": "Error de red. Por favor, inténtalo de nuevo.",
  "itemsCount": "{count, plural, =0{No hay elementos} one{1 elemento} other{{count} elementos}}",
  "userGreeting": "¡Hola, {name}! Tienes {messageCount, plural, =0{no hay mensajes} one{1 mensaje} other{{messageCount} mensajes}}."
}

lib/l10n/app_fr.arb (French):

{
  "@@locale": "fr",
  "appName": "Mon Application",
  "welcomeMessage": "Bienvenue dans notre application!",
  "buttonSubmit": "Soumettre",
  "errorNetwork": "Erreur réseau. Veuillez réessayer.",
  "itemsCount": "{count, plural, =0{Aucun élément} one{1 élément} other{{count} éléments}}",
  "userGreeting": "Bonjour, {name}! Vous avez {messageCount, plural, =0{aucun message} one{1 message} other{{messageCount} messages}}."
}

Step 4: Generate Localization Files

Run the code generator:

flutter gen-l10n

Or use:

flutter pub run intl_utils:generate

This generates app_localizations.dart and language-specific files in .dart_tool/flutter_gen/gen_l10n/.

Step 5: Configure MaterialApp

Update your main.dart to enable localization:

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 Localization Demo',
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: const [
        Locale('en'), // English
        Locale('es'), // Spanish
        Locale('fr'), // French
      ],
      home: const HomePage(),
    );
  }
}

Step 6: Use Localized Strings

Access localized strings using AppLocalizations.of(context):

Before:

Text('Welcome to our app!')

After:

Text(AppLocalizations.of(context)!.welcomeMessage)

Or with a null-safe approach:

final l10n = AppLocalizations.of(context);
if (l10n != null) {
  Text(l10n.welcomeMessage)
}

Step 7: Handle String Formatting with Variables

Use placeholders in ARB files and Flutter handles the formatting:

ARB file:

{
  "userGreeting": "Hello, {name}!",
  "@userGreeting": {
    "placeholders": {
      "name": {
        "type": "String"
      }
    }
  }
}

Dart:

Text(AppLocalizations.of(context)!.userGreeting('John'))
// English: "Hello, John!"
// Spanish: "¡Hola, John!"

Step 8: Pluralization

Flutter supports pluralization using ICU message format:

ARB file:

{
  "itemsCount": "{count, plural, =0{No items} one{1 item} other{{count} items}}"
}

Dart:

Text(AppLocalizations.of(context)!.itemsCount(5))
// English: "5 items"
// Spanish: "5 elementos"

Text(AppLocalizations.of(context)!.itemsCount(1))
// English: "1 item"
// Spanish: "1 elemento"

Text(AppLocalizations.of(context)!.itemsCount(0))
// English: "No items"
// Spanish: "No hay elementos"

Step 9: Format Dates and Numbers

Use intl package for localized formatting:

Dates:

import 'package:intl/intl.dart';

final date = DateTime.now();
final formatter = DateFormat.yMMMMEEEEd(Intl.getCurrentLocale());
final localizedDate = formatter.format(date);
// English: "Wednesday, March 20, 2025"
// Spanish: "miércoles, 20 de marzo de 2025"

Numbers:

import 'package:intl/intl.dart';

final number = 1234.56;
final formatter = NumberFormat.decimalPattern(Intl.getCurrentLocale());
final localizedNumber = formatter.format(number);
// English: "1,234.56"
// Spanish: "1.234,56"

Currency:

import 'package:intl/intl.dart';

final amount = 1234.56;
final formatter = NumberFormat.currency(locale: Intl.getCurrentLocale());
final localizedCurrency = formatter.format(amount);
// English (US): "$1,234.56"
// Spanish (ES): "1.234,56 €"

Step 10: Handle Right-to-Left (RTL) Languages

Flutter automatically handles RTL layouts. Use Directionality widget if needed:

Directionality(
  textDirection: TextDirection.rtl, // or TextDirection.ltr
  child: YourWidget(),
)

Or detect automatically:

final locale = Localizations.localeOf(context);
final isRTL = locale.languageCode == 'ar' || locale.languageCode == 'he';

Step 11: Test Your Localization

Using Flutter DevTools:

  1. Run your app
  2. Open DevTools
  3. Go to Inspector tab
  4. Change locale in device settings

Using Device Settings:

  1. Go to device Settings > Language & Region
  2. Change the device language
  3. Restart your app

Programmatically (for testing):

import 'package:flutter/material.dart';

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Locale _locale = const Locale('en');

  void _changeLanguage(Locale locale) {
    setState(() {
      _locale = locale;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      locale: _locale,
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: const [
        Locale('en'),
        Locale('es'),
        Locale('fr'),
      ],
      home: LanguageSelector(
        onLanguageChanged: _changeLanguage,
      ),
    );
  }
}

Best Practices

1. Use Descriptive Keys

Bad:

{
  "msg1": "Submit"
}

Good:

{
  "buttonSubmit": "Submit",
  "@buttonSubmit": {
    "description": "Submit button label"
  }
}

2. Provide Context in ARB Files

Always include @ metadata entries to help translators:

{
  "deleteButton": "Delete",
  "@deleteButton": {
    "description": "Button to delete an item. Shown in the item detail view."
  }
}

3. Avoid String Concatenation

Bad:

Text(AppLocalizations.of(context)!.hello + ' ' + name)

Good:

{
  "helloUser": "Hello, {name}!",
  "@helloUser": {
    "placeholders": {
      "name": {"type": "String"}
    }
  }
}
Text(AppLocalizations.of(context)!.helloUser(name))

4. Use Pluralization Correctly

Always use ICU plural format for countable items:

{
  "messagesCount": "{count, plural, =0{No messages} one{1 message} other{{count} messages}}"
}

5. Test String Lengths

Some languages are longer than others. Design your UI to accommodate:

  • German and Finnish: 30-50% longer than English
  • Asian languages: May need more vertical space
  • Arabic and Hebrew: RTL layout considerations

6. Handle Missing Translations

Flutter falls back to the default locale if a translation is missing. Always provide a fallback:

final l10n = AppLocalizations.of(context);
final text = l10n?.welcomeMessage ?? 'Welcome'; // Fallback

Common Pitfalls

1. Forgetting to Run Code Generation

After updating ARB files, always run:

flutter gen-l10n

2. Not Including All Required Delegates

Make sure to include all four delegates:

localizationsDelegates: const [
  AppLocalizations.delegate,
  GlobalMaterialLocalizations.delegate,
  GlobalWidgetsLocalizations.delegate,
  GlobalCupertinoLocalizations.delegate,
],

3. Hardcoding Format Strings

Bad:

Text('\$${amount.toStringAsFixed(2)}')

Good:

final formatter = NumberFormat.currency(locale: Intl.getCurrentLocale());
Text(formatter.format(amount))

4. Not Testing Edge Cases

Test with:

  • Very long strings
  • Special characters
  • Numbers and dates
  • Plural forms (0, 1, many)
  • RTL languages

Advanced: Dynamic Language Switching

To allow users to change language within the app:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class LocaleProvider extends ChangeNotifier {
  Locale _locale = const Locale('en');
  
  Locale get locale => _locale;
  
  Future<void> loadLocale() async {
    final prefs = await SharedPreferences.getInstance();
    final languageCode = prefs.getString('language_code') ?? 'en';
    _locale = Locale(languageCode);
    notifyListeners();
  }
  
  Future<void> setLocale(Locale locale) async {
    if (_locale == locale) return;
    
    _locale = locale;
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('language_code', locale.languageCode);
    notifyListeners();
  }
}

Usage:

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => LocaleProvider()..loadLocale(),
      child: Consumer<LocaleProvider>(
        builder: (context, localeProvider, _) {
          return MaterialApp(
            locale: localeProvider.locale,
            localizationsDelegates: const [
              AppLocalizations.delegate,
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate,
              GlobalCupertinoLocalizations.delegate,
            ],
            supportedLocales: const [
              Locale('en'),
              Locale('es'),
              Locale('fr'),
            ],
            home: const HomePage(),
          );
        },
      ),
    );
  }
}

Using with GetX (Alternative)

If you’re using GetX for state management:

import 'package:get/get.dart';

class LocaleController extends GetxController {
  var locale = const Locale('en').obs;
  
  void changeLocale(Locale newLocale) {
    locale.value = newLocale;
    Get.updateLocale(newLocale);
  }
}

Conclusion

Localizing your Flutter app is straightforward when you follow these steps:

  1. Add flutter_localizations and intl packages
  2. Create ARB files for each language in lib/l10n/
  3. Configure l10n.yaml and run code generation
  4. Set up localizationsDelegates in MaterialApp
  5. Use AppLocalizations.of(context) throughout your app
  6. Handle pluralization with ICU message format
  7. Format dates and numbers using intl package
  8. Test thoroughly in all supported languages

By following these practices, you’ll create an app that provides a native experience for users worldwide, significantly expanding your potential user base.

Streamline Your Flutter Localization Workflow

Managing translations for multiple languages can become complex as your app grows. Consider using a translation management platform to:

  • Collaborate with translators
  • Keep translations in sync with your codebase
  • Automate the translation workflow
  • Maintain consistency across all languages
  • Generate ARB files automatically

Ready to take your Flutter app global? Explore AZbox’s localization platform and streamline your translation workflow:

View AZbox Plans and Pricing

Blog

Latest Posts

Discover our latest articles and updates.

Gaming Localization Services: The 2026 Guide to Taking Your Game Global
date icon

Saturday, Feb 21, 2026

Gaming Localization Services: The 2026 Guide to Taking Your Game Global

Going global with your game isn’t just about translating text—it’s about making players in every market feel like the ga

Read More
App Localization Software: The 2026 Guide to Choosing and Using the Right Tool
date icon

Friday, Feb 20, 2026

App Localization Software: The 2026 Guide to Choosing and Using the Right Tool

If you're scaling your app beyond one language, app localization software is the lever that turns "we should go glob

Read More
Translation for SaaS: Complete Guide to Localizing Your Product
date icon

Sunday, Feb 15, 2026

Translation for SaaS: Complete Guide to Localizing Your Product

Translation for SaaS is no longer optional. If you want to grow beyond your home market, you need a clear strategy t

Read More
Call to action background

Start Global Growth Today

Join hundreds of successful companies already using AZbox to reach customers worldwide. Start with a free trial, no credit card required.

Get Started - It's Free