← Back to Blog

How to Build a Multi-Language Flutter App: Step-by-Step Tutorial

fluttermulti-languagetutoriallocalizationarbrtl

How to Build a Multi-Language Flutter App: Step-by-Step Tutorial

Building a Flutter app that supports multiple languages opens your app to billions of potential users worldwide. This comprehensive tutorial walks you through creating a fully functional multi-language Flutter app from scratch.

What We'll Build

By the end of this tutorial, you'll have a Flutter app that:

  • Supports English, Spanish, French, and Arabic
  • Automatically detects the user's preferred language
  • Allows manual language switching
  • Persists language preference
  • Handles RTL layouts for Arabic
  • Formats dates and numbers per locale

Prerequisites

  • Flutter SDK installed (3.0+)
  • Basic Flutter knowledge
  • 30 minutes of your time

Step 1: Create a New Flutter Project

flutter create multilingual_app
cd multilingual_app

Step 2: Add Dependencies

Update your pubspec.yaml:

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

flutter:
  generate: true
  uses-material-design: true

Run:

flutter pub get

Step 3: Configure Localization

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
nullable-getter: false

Step 4: Create Translation Files

Create the lib/l10n/ directory and add your ARB files:

English (lib/l10n/app_en.arb)

{
  "@@locale": "en",
  "appTitle": "My Multilingual App",
  "@appTitle": {
    "description": "The title of the application"
  },
  "welcomeMessage": "Welcome!",
  "@welcomeMessage": {
    "description": "Welcome message on home screen"
  },
  "selectLanguage": "Select Language",
  "@selectLanguage": {
    "description": "Language selection header"
  },
  "currentLanguage": "Current language: {language}",
  "@currentLanguage": {
    "description": "Shows current language",
    "placeholders": {
      "language": {
        "type": "String",
        "example": "English"
      }
    }
  },
  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCount": {
    "description": "Item count with pluralization",
    "placeholders": {
      "count": {
        "type": "int"
      }
    }
  },
  "lastUpdated": "Last updated: {date}",
  "@lastUpdated": {
    "description": "Shows last update date",
    "placeholders": {
      "date": {
        "type": "DateTime",
        "format": "yMMMd"
      }
    }
  },
  "english": "English",
  "spanish": "Spanish",
  "french": "French",
  "arabic": "Arabic",
  "settings": "Settings",
  "home": "Home",
  "profile": "Profile",
  "greeting": "Hello, {name}!",
  "@greeting": {
    "placeholders": {
      "name": {
        "type": "String"
      }
    }
  }
}

Spanish (lib/l10n/app_es.arb)

{
  "@@locale": "es",
  "appTitle": "Mi App Multilingüe",
  "welcomeMessage": "¡Bienvenido!",
  "selectLanguage": "Seleccionar Idioma",
  "currentLanguage": "Idioma actual: {language}",
  "itemCount": "{count, plural, =0{Sin elementos} =1{1 elemento} other{{count} elementos}}",
  "lastUpdated": "Última actualización: {date}",
  "english": "Inglés",
  "spanish": "Español",
  "french": "Francés",
  "arabic": "Árabe",
  "settings": "Configuración",
  "home": "Inicio",
  "profile": "Perfil",
  "greeting": "¡Hola, {name}!"
}

French (lib/l10n/app_fr.arb)

{
  "@@locale": "fr",
  "appTitle": "Mon App Multilingue",
  "welcomeMessage": "Bienvenue!",
  "selectLanguage": "Choisir la Langue",
  "currentLanguage": "Langue actuelle: {language}",
  "itemCount": "{count, plural, =0{Aucun élément} =1{1 élément} other{{count} éléments}}",
  "lastUpdated": "Dernière mise à jour: {date}",
  "english": "Anglais",
  "spanish": "Espagnol",
  "french": "Français",
  "arabic": "Arabe",
  "settings": "Paramètres",
  "home": "Accueil",
  "profile": "Profil",
  "greeting": "Bonjour, {name}!"
}

Arabic (lib/l10n/app_ar.arb)

{
  "@@locale": "ar",
  "appTitle": "تطبيقي متعدد اللغات",
  "welcomeMessage": "!مرحبًا",
  "selectLanguage": "اختر اللغة",
  "currentLanguage": "اللغة الحالية: {language}",
  "itemCount": "{count, plural, =0{لا عناصر} =1{عنصر واحد} =2{عنصران} few{{count} عناصر} many{{count} عنصرًا} other{{count} عنصر}}",
  "lastUpdated": "آخر تحديث: {date}",
  "english": "الإنجليزية",
  "spanish": "الإسبانية",
  "french": "الفرنسية",
  "arabic": "العربية",
  "settings": "الإعدادات",
  "home": "الرئيسية",
  "profile": "الملف الشخصي",
  "greeting": "مرحبًا، {name}!"
}

Step 5: Generate Localization Files

flutter gen-l10n

This generates the AppLocalizations class in .dart_tool/flutter_gen/gen_l10n/.

Step 6: Create a Locale Provider

Create lib/providers/locale_provider.dart:

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;

  static const String _localeKey = 'selected_locale';

  LocaleProvider() {
    _loadSavedLocale();
  }

  Future<void> _loadSavedLocale() async {
    final prefs = await SharedPreferences.getInstance();
    final savedLocale = prefs.getString(_localeKey);
    if (savedLocale != null) {
      _locale = Locale(savedLocale);
      notifyListeners();
    }
  }

  Future<void> setLocale(Locale locale) async {
    if (_locale == locale) return;

    _locale = locale;
    notifyListeners();

    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(_localeKey, locale.languageCode);
  }

  static const List<Locale> supportedLocales = [
    Locale('en'),
    Locale('es'),
    Locale('fr'),
    Locale('ar'),
  ];

  String getLanguageName(String code) {
    switch (code) {
      case 'en': return 'English';
      case 'es': return 'Español';
      case 'fr': return 'Français';
      case 'ar': return 'العربية';
      default: return code;
    }
  }
}

Step 7: Update main.dart

Replace lib/main.dart:

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'providers/locale_provider.dart';
import 'screens/home_screen.dart';

void main() {
  runApp(const MyApp());
}

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

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

class _MyAppState extends State<MyApp> {
  final LocaleProvider _localeProvider = LocaleProvider();

  @override
  void initState() {
    super.initState();
    _localeProvider.addListener(() {
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Multilingual App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),

      // Localization configuration
      locale: _localeProvider.locale,
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: LocaleProvider.supportedLocales,

      home: HomeScreen(localeProvider: _localeProvider),
    );
  }
}

Step 8: Create the Home Screen

Create lib/screens/home_screen.dart:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../providers/locale_provider.dart';
import 'language_selector_screen.dart';

class HomeScreen extends StatefulWidget {
  final LocaleProvider localeProvider;

  const HomeScreen({super.key, required this.localeProvider});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  int _selectedIndex = 0;
  int _itemCount = 5;

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context);
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.appTitle),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [
          IconButton(
            icon: const Icon(Icons.language),
            onPressed: () => _openLanguageSelector(context),
          ),
        ],
      ),
      body: _buildBody(context, l10n),
      bottomNavigationBar: NavigationBar(
        selectedIndex: _selectedIndex,
        onDestinationSelected: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
        destinations: [
          NavigationDestination(
            icon: const Icon(Icons.home_outlined),
            selectedIcon: const Icon(Icons.home),
            label: l10n.home,
          ),
          NavigationDestination(
            icon: const Icon(Icons.person_outlined),
            selectedIcon: const Icon(Icons.person),
            label: l10n.profile,
          ),
          NavigationDestination(
            icon: const Icon(Icons.settings_outlined),
            selectedIcon: const Icon(Icons.settings),
            label: l10n.settings,
          ),
        ],
      ),
    );
  }

  Widget _buildBody(BuildContext context, AppLocalizations l10n) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Welcome Card
          Card(
            child: Padding(
              padding: const EdgeInsets.all(20),
              child: Column(
                children: [
                  Text(
                    l10n.welcomeMessage,
                    style: Theme.of(context).textTheme.headlineMedium,
                  ),
                  const SizedBox(height: 8),
                  Text(
                    l10n.greeting('Flutter Developer'),
                    style: Theme.of(context).textTheme.bodyLarge,
                  ),
                ],
              ),
            ),
          ),

          const SizedBox(height: 20),

          // Current Language Display
          Card(
            child: ListTile(
              leading: const Icon(Icons.language),
              title: Text(l10n.currentLanguage(
                widget.localeProvider.getLanguageName(
                  widget.localeProvider.locale.languageCode,
                ),
              )),
              trailing: const Icon(Icons.chevron_right),
              onTap: () => _openLanguageSelector(context),
            ),
          ),

          const SizedBox(height: 20),

          // Item Count Demo (Pluralization)
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Pluralization Demo',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  const SizedBox(height: 12),
                  Text(
                    l10n.itemCount(_itemCount),
                    style: Theme.of(context).textTheme.bodyLarge,
                  ),
                  Slider(
                    value: _itemCount.toDouble(),
                    min: 0,
                    max: 100,
                    divisions: 100,
                    label: _itemCount.toString(),
                    onChanged: (value) {
                      setState(() {
                        _itemCount = value.toInt();
                      });
                    },
                  ),
                ],
              ),
            ),
          ),

          const SizedBox(height: 20),

          // Date Formatting Demo
          Card(
            child: ListTile(
              leading: const Icon(Icons.calendar_today),
              title: Text(l10n.lastUpdated(DateTime.now())),
            ),
          ),
        ],
      ),
    );
  }

  void _openLanguageSelector(BuildContext context) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => LanguageSelectorScreen(
          localeProvider: widget.localeProvider,
        ),
      ),
    );
  }
}

Step 9: Create the Language Selector Screen

Create lib/screens/language_selector_screen.dart:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../providers/locale_provider.dart';

class LanguageSelectorScreen extends StatelessWidget {
  final LocaleProvider localeProvider;

  const LanguageSelectorScreen({super.key, required this.localeProvider});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context);

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.selectLanguage),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: ListView(
        children: [
          _buildLanguageTile(
            context,
            locale: const Locale('en'),
            name: l10n.english,
            nativeName: 'English',
            flag: '🇺🇸',
          ),
          _buildLanguageTile(
            context,
            locale: const Locale('es'),
            name: l10n.spanish,
            nativeName: 'Español',
            flag: '🇪🇸',
          ),
          _buildLanguageTile(
            context,
            locale: const Locale('fr'),
            name: l10n.french,
            nativeName: 'Français',
            flag: '🇫🇷',
          ),
          _buildLanguageTile(
            context,
            locale: const Locale('ar'),
            name: l10n.arabic,
            nativeName: 'العربية',
            flag: '🇸🇦',
          ),
        ],
      ),
    );
  }

  Widget _buildLanguageTile(
    BuildContext context, {
    required Locale locale,
    required String name,
    required String nativeName,
    required String flag,
  }) {
    final isSelected = localeProvider.locale == locale;

    return ListTile(
      leading: Text(flag, style: const TextStyle(fontSize: 24)),
      title: Text(nativeName),
      subtitle: Text(name),
      trailing: isSelected
          ? Icon(Icons.check_circle, color: Theme.of(context).primaryColor)
          : null,
      onTap: () {
        localeProvider.setLocale(locale);
        Navigator.pop(context);
      },
    );
  }
}

Step 10: Run Your App

flutter run

You now have a fully functional multi-language Flutter app!

Testing Your Multi-Language App

// test/widget_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

void main() {
  testWidgets('App displays Spanish when locale is es', (tester) async {
    await tester.pumpWidget(
      MaterialApp(
        locale: const Locale('es'),
        localizationsDelegates: const [
          AppLocalizations.delegate,
          GlobalMaterialLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
        ],
        supportedLocales: const [Locale('en'), Locale('es')],
        home: Builder(
          builder: (context) {
            final l10n = AppLocalizations.of(context);
            return Text(l10n.welcomeMessage);
          },
        ),
      ),
    );

    expect(find.text('¡Bienvenido!'), findsOneWidget);
  });
}

Managing Translations at Scale

As your app grows to support more languages, manually managing ARB files becomes challenging. Common pain points include:

  • Keeping translations in sync across 10+ languages
  • Ensuring all keys are translated
  • Managing translator access without Git knowledge
  • Handling pluralization rules for complex languages

FlutterLocalisation: The Professional Solution

FlutterLocalisation eliminates these pain points with:

  • Visual Translation Editor: Translators edit in a clean UI, not JSON files
  • AI-Powered Translations: One-click translation to 50+ languages
  • Automatic ARB Sync: Changes automatically sync to your Git repository
  • Team Collaboration: Role-based access for developers, translators, and reviewers
  • Missing Key Detection: Instant alerts for untranslated strings

Conclusion

You've built a complete multi-language Flutter app with:

  • Four supported languages including RTL
  • Persistent language preferences
  • Pluralization support
  • Locale-aware date formatting
  • Clean architecture with provider pattern

This foundation scales to support dozens of languages. For production apps targeting global markets, consider using FlutterLocalisation to automate translation management.


Ready to scale your multi-language app? Try FlutterLocalisation free and manage translations across unlimited languages with AI-powered automation and Git integration.