Building Multi-Language Support in Flutter Applications

In today’s globalized world, providing multi-language support in your mobile application is crucial for reaching a wider audience and enhancing user experience. Flutter, with its rich set of tools and widgets, makes implementing internationalization (i18n) and localization (l10n) relatively straightforward. This blog post will guide you through the process of building multi-language support in your Flutter applications.

What is Internationalization (i18n) and Localization (l10n)?

  • Internationalization (i18n): The process of designing and developing an application to support multiple languages and regions without needing engineering changes. It involves abstracting all locale-specific data and formatting.
  • Localization (l10n): The process of adapting an internationalized application for a specific locale. It involves providing translations and formatting rules for that locale.

Why Implement Multi-Language Support?

  • Wider Audience Reach: Makes your application accessible to a global audience.
  • Enhanced User Experience: Provides users with a personalized experience in their native language.
  • Increased Engagement: Improves user satisfaction and encourages longer app usage.
  • Competitive Advantage: Distinguishes your application from competitors that only offer a single language.

How to Implement Multi-Language Support in Flutter

Implementing multi-language support in Flutter involves several key steps:

Step 1: Add Dependencies

First, add the flutter_localizations and intl dependencies to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^0.18.1 # Use the latest version

dev_dependencies:
  flutter_test:
    sdk: flutter
  intl_utils: ^2.8.5 # Use the latest version

Run flutter pub get to install the dependencies.

Step 2: Configure flutter_localizations

In your main.dart file, configure the MaterialApp to use flutter_localizations:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Localization Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('en', 'US'), // English
        const Locale('es', 'ES'), // Spanish
        const Locale('fr', 'FR'), // French
      ],
      home: MyHomePage(),
    );
  }
}

In this setup:

  • localizationsDelegates provide the necessary localization data.
  • supportedLocales lists the locales your application supports.

Step 3: Create Localization Files

Create a directory structure to hold your localization files. A common approach is:

lib/
  l10n/
    app_localizations.dart
    app_localizations_en.arb
    app_localizations_es.arb
    app_localizations_fr.arb
app_localizations.dart

Create an app_localizations.dart file to manage the localization process:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart';

class AppLocalizations {
  AppLocalizations(this.localeName);

  static Future load(Locale locale) {
    final String name = locale.countryCode == null ? locale.languageCode : locale.toString();
    final String localeName = Intl.canonicalizedLocale(name);

    return initializeMessages(localeName).then((_) {
      Intl.defaultLocale = localeName;
      return AppLocalizations(localeName);
    });
  }

  static AppLocalizations? of(BuildContext context) {
    return Localizations.of(context, AppLocalizations);
  }

  final String localeName;

  static const LocalizationsDelegate delegate =
    _AppLocalizationsDelegate();

  String get helloWorld {
    return Intl.message(
      'Hello World',
      name: 'helloWorld',
      desc: 'Simple hello world message',
    );
  }

  String greet(String name) {
    return Intl.message(
      'Hello, $name!',
      name: 'greet',
      desc: 'Greets the user with their name.',
      args: [name],
    );
  }
}

class _AppLocalizationsDelegate extends LocalizationsDelegate {
  const _AppLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) {
    return ['en', 'es', 'fr'].contains(locale.languageCode);
  }

  @override
  Future load(Locale locale) {
    return AppLocalizations.load(locale);
  }

  @override
  bool shouldReload(_AppLocalizationsDelegate old) => false;
}
ARB Files (.arb)

ARB files are JSON-based files that contain the translations for each locale. Here are example ARB files for English (app_localizations_en.arb), Spanish (app_localizations_es.arb), and French (app_localizations_fr.arb):

app_localizations_en.arb

{
  "helloWorld": "Hello World",
  "greet": "Hello, {name}!"
}

app_localizations_es.arb

{
  "helloWorld": "Hola Mundo",
  "greet": "¡Hola, {name}!"
}

app_localizations_fr.arb

{
  "helloWorld": "Bonjour le monde",
  "greet": "Bonjour, {name} !"
}

Step 4: Generate Localization Code

Run the following command to generate the localization code based on your ARB files:

flutter pub pub run intl_utils:generate

This command uses the intl_utils package to generate Dart code from the ARB files, which includes message lookup logic and type-safe access to translations.

Step 5: Use Localized Strings in Your UI

Access the localized strings in your Flutter widgets using the AppLocalizations class:

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

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(AppLocalizations.of(context)!.helloWorld),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              AppLocalizations.of(context)!.helloWorld,
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            SizedBox(height: 20),
            Text(
              AppLocalizations.of(context)!.greet('Flutter'),
              style: Theme.of(context).textTheme.titleLarge,
            ),
          ],
        ),
      ),
    );
  }
}

Now, your application will display text in the selected locale. You may need to restart the app to see the changes.

Step 6: Dynamically Change the Locale

To allow users to change the locale dynamically, you can use the setState method to update the MyApp widget. Here’s an example:

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

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State {
  Locale _locale = const Locale('en', 'US');

  void setLocale(Locale value) {
    setState(() {
      _locale = value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Localization Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      localizationsDelegates: [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('en', 'US'), // English
        const Locale('es', 'ES'), // Spanish
        const Locale('fr', 'FR'), // French
      ],
      locale: _locale,
      home: MyHomePage(setLocale: setLocale),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final Function(Locale) setLocale;

  MyHomePage({required this.setLocale});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(AppLocalizations.of(context)!.helloWorld),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              AppLocalizations.of(context)!.helloWorld,
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            SizedBox(height: 20),
            Text(
              AppLocalizations.of(context)!.greet('Flutter'),
              style: Theme.of(context).textTheme.titleLarge,
            ),
            SizedBox(height: 20),
            DropdownButton(
              value: Localizations.localeOf(context),
              items: [
                DropdownMenuItem(
                  value: const Locale('en', 'US'),
                  child: Text('English'),
                ),
                DropdownMenuItem(
                  value: const Locale('es', 'ES'),
                  child: Text('Spanish'),
                ),
                DropdownMenuItem(
                  value: const Locale('fr', 'FR'),
                  child: Text('French'),
                ),
              ],
              onChanged: (Locale? locale) {
                if (locale != null) {
                  setLocale(locale);
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}

Conclusion

Implementing multi-language support in Flutter enhances user experience, expands your app’s reach, and demonstrates professionalism. By following these steps, you can seamlessly integrate internationalization and localization into your Flutter applications. Utilizing Flutter’s localization packages and best practices ensures that your application is accessible and user-friendly for a global audience.