Flutter MultiLanguage Development: A Step-by-Step Localization Tutorial

Let’s delve into the world of localization by building a simple counter app in Flutter. This app is a handy starting point for any Flutter project, and we’ll make some modifications to showcase the power of localization.

To achieve this, we’ll leverage the Flutter localizations package, a tool that enables us to seamlessly translate our app into different languages. This process includes features like interpolation for words and phrases, ensuring accurate translations for both singular and plural scenarios.

In simpler terms, localization helps us make our app accessible to users from different linguistic backgrounds. We’ll make our counter app speak various languages and handle different language intricacies, like when we want to insert specific words or phrases and correctly translate singular and plural forms.

Related read: Creating and Uploading a Flutter Package: A Step-by-Step Guide

Project Configuration

Let’s make your `pubspec.yaml` file changes more straightforward. Open your `pubspec.yaml` file and update it with the following content:

# Ensure compatibility with Flutter version 2.16.1 or newer but less than 3.0.0
environment:
sdk: ">=2.16.1 <3.0.0"

# Dependencies for localization
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.17.0 # Package for internationalization and localization
cupertino_icons: ^1.0.2

dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0

# Flutter configuration
flutter:
generate: true # Enables automatic code generation for localization
uses-material-design: true

These changes tell Flutter to use the necessary packages for localization, including `intl` for internationalization support. The `generate: true` line is crucial for automating the code generation process, making your life easier during localization. Now, your project is all set to handle translation, plurals, and genders.

Let’s simplify the process of creating localization configuration and translation files in plain language.

Create Localization Configuration

Make a file named `l10n.yaml` in your project’s root, specifically in the `lib` directory. This file guides Flutter on where to find translation files and what to name the generated Dart files.

arb-dir: lib/l10n # Location of translation files
template-arb-file: app_en.arb # Default translation file for English
output-localization-file: app_localizations.dart # Generated Dart file

Create Translation Files

We use ARB (Application Resource Bundle) files to store translations. These are simple key-value pairs like JSON. Start by making an ARB file for the default language, which is English in this case. Name it `app_en.arb` and place it in the `lib/l10n` directory.

// lib/l10n/app_en.arb
{
"appTitle": "Demo App"
}

Since we’re supporting multiple languages, create separate ARB files for each. For example, we’ll also support Hindi.

// lib/l10n/app_hi.arb
{
"appTitle": "डेमो ऐप"
}

You can add more files for additional languages based on your project’s needs.

By following these steps, you’re setting up your Flutter project for localization. The configuration file (`l10n.yaml`) helps Flutter understand where to find translations, and the ARB files hold the actual translated content for each supported language.

Next, let’s add localization to MaterialApp:

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

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

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo'),
);
}
}

Let’s break down the role of `AppLocalizations.localizationsDelegates` and `AppLocalizations.supportedLocales` in simpler terms.

Localizing Our App

`AppLocalizations.localizationsDelegates` is like the maestro directing the localization orchestra. It’s responsible for coordinating the process of making our app multilingual. Flutter already provides built-in localizations for widgets, Material design, and Cupertino (iOS-style design). So, when you use something like `showDatePicker()`, the dialogue is magically translated to your device’s language without you needing to create a separate localization file for it.

Updating UI with New Locale

`AppLocalizations.supportedLocales` plays the role of a guidebook for Flutter. It tells Flutter which locales (languages) our app supports. Flutter is smart enough to detect when a user switches their device language, and it only bothers to rebuild our app’s user interface when it detects a new supported locale has been added to `supportedLocales` in the `MaterialApp`.

In simpler terms, these two aspects ensure that our app can speak different languages, and Flutter knows when to update the display based on the user’s language preference. It’s like having a translator who not only knows various languages but also adapts to the user’s language choice seamlessly.

To support localization in iOS, we’ll need to make the following changes to Info.plist:

<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>hi</string>
</array>

Localization Code Generation

Let’s break down the process of using the translations from ARB files and generating Dart files in simpler terms.

1. Generating Dart Files:

  • To make use of the translations stored in ARB files, Flutter needs to create corresponding Dart files.
  • When you’ve completed the setup changes in your configuration files (like `pubspec.yaml` and `l10n.yaml`), kick off your app by running it.

2. Check for Generated Files:

  • Once the app has started, Flutter will automatically detect the ARB files and generate Dart files accordingly.
  • You should find these generated Dart files in a directory called `.dart_tool/flutter_gen/gen_l10n` within your project.
    – Look for files like `app_localizations.dart`, `app_localizations_en.dart`, and `app_localizations_hi.dart`.

3. Understanding the Generated Files:

  • Open the `app_localizations.dart` file. In there, you’ll find an abstract class named `AppLocalizations`.
  • This abstract class includes information about localizations, delegates, supported locales, and the specific locale values you added in your ARB files.
  • Additionally, you’ll notice other files like `app_localizations_en.dart` and `app_localizations_hi.dart`. These files contain classes that extend `AppLocalizations`, providing locale-specific variables and methods.

4. Manual Code Generation (If Needed):

  • In case Flutter doesn’t automatically generate the localization code, you can run `flutter gen-l10n` to trigger the process manually.
  • By going through these steps, you allow Flutter to automatically generate the necessary Dart files, making it convenient for you to incorporate and use localized content in your Flutter app.

Discover and Hire Proven Flutter Developers for Success

Let’s Localize Our App

In order to use the AppLocalizaions class, you’ll have to first import this class:

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

After that, you can access the locale value using;

AppLocalizations.of(context).appTitle.

Our MaterialApp now looks like this:

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: AppLocalizations.of(context).appTitle),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);

final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;

void _incrementCounter() {
setState(() {
_counter++;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Padding(
padding: EdgeInsets.only(top: 32),
child: Text(
'Flutter is Awesome',
style: TextStyle(fontSize: 24),
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('You have pushed this button :',
style: Theme.of(context).textTheme.headline6),
Text(
'$_counter times',
style: Theme.of(context).textTheme.headline4,
)
],
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

Congratulations, our app’s title has now been successfully localized. We just need to update the other String values.

Interpolating Words and Phrases for Internationalization

Interpolation is essentially inserting one thing into another, especially when they are different. In the context of localization, interpolation becomes valuable when you wish to include a specific word or text in the default language, such as English, across all the supported languages.

Consider our demo app, where we aim to display a message like “Flutter is awesome.” The unique aspect here is that we want to ensure the word “Flutter” remains in English, regardless of the language selected on the user’s device.

To put it simply, interpolation allows us to seamlessly blend different languages. It’s akin to having a special word or phrase that stays the same, providing a consistent and unified message for users, irrespective of the language they choose. In our scenario, interpolation ensures that certain words, like “Flutter,” maintain their original language, contributing to a cohesive user experience across various supported languages.

Let’s Add the Interpolated Sentence to Our Locale ARB Files:

➡️ lib/l10n/app_en.arb:

{
"appTitle": "Demo App",
"appDescription": "{flutter} is Awesome",
"@appDescription": {
"placeholders": {
"flutter": {
"type": "String",
"example": "Flutter"
}
}
},
}

➡️ lib/l10n/app_hi.arb:

{
"appTitle": "डेमो ऐप",
"appDescription": "{flutter} बहुत बढ़िया है",
}

Now, we need to use the interpolated text description that we included in the ARB files. Since we’ll be accessing AppLocalizations in various parts of our code, let’s incorporate it at multiple points. To do this, we’ll create an instance variable named `_locale` and initialize it within the `didChangeDependencies()` method.

class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
late AppLocalizations _local;

@override
void didChangeDependencies() {
_local = AppLocalizations.of(context);
super.didChangeDependencies();
}

void _incrementCounter() {
setState(() {
_counter++;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_local.appTitle),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 32),
child: Text(
_local.appDescription('Flutter'),
style: const TextStyle(fontSize: 24),
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("You have pushed this button :",
style: Theme.of(context).textTheme.headline6),
Text(
"$_counter",
style: Theme.of(context).textTheme.headline4,
)
],
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: "Increment",
child: const Icon(Icons.add),
),
);
}
}

Run the app and you’ll see that no matter what language your device is set to, the word “Flutter” will always be in English.

Singular and Plural Support

In the realm of localization, managing singular and plural forms is a common task. For instance, expressions like “Congrats, you won a coupon” versus “Congrats , you won two coupons” highlight the need to handle both singular and plural cases. It’s crucial to recognize that various languages have distinct rules for plurals. As a result, navigating plural forms requires careful consideration, and a good understanding of the target language is essential during the translation process.

Let’s Make the Required Changes to Support Plurals in Our Localization Files:

➡️ lib/l10n/app_en.arb:

{
"appTitle": "Demo App",
"appDescription": "{flutter} is Awesome",
"@appDescription": {
"placeholders": {
"flutter": {
"type": "String",
"example": "Flutter"
}
}
},
"counterText": "You have pushed this button :",
"counter": "{count,plural, =0{0} =1{1 time} other{{count} times}}",
"@counter": {
"placeholders": {
"count": {
"type": "int",
"example": "count"
}
}
},
"counterButtonText": "Increment"
}

➡️ lib/l10n/app_hi.arb:

{
"appTitle": "डेमो ऐप",
"appDescription": "{flutter} बहुत बढ़िया है",
"counterText": "आपने यह बटन दबा दिया है :",
"counter": "{count,plural, =0{0} =1{1 बार} other{{count} बार}}",
"counterButtonText": "जोड़ें"
}

Now that we’ve added plurals in the key counter for both of our ARB files and also added counterButtonText that will be used as tooltip for the increment button, we can write this:

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
late AppLocalizations _local;

@override
void didChangeDependencies() {
_local = AppLocalizations.of(context);
super.didChangeDependencies();
}

void _incrementCounter() {

setState(() {
_counter++;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_local.appTitle),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 32),
child: Text(
_local.appDescription('Flutter'),
style: const TextStyle(fontSize: 24),
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_local.counterText,
style: Theme.of(context).textTheme.headline6),
Text(
_local.counter(_counter),
style: Theme.of(context).textTheme.headline4,
)
],
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: _local.counterButtonText,
child: const Icon(Icons.add),
),
);
}
}

Notice how we’re passing the _counter value to the AppLocalizations.counter(), which will eventually check whether the value is singular or plural, and it will return a String value based on that.

coma

Conclusion

In this Flutter MultiLanguage tutorial, we learned how to make a Flutter counter app that can speak different languages. We started by setting up our project and making it ready for translation. We also made sure our app can handle different languages by automatically generating the necessary code.

Then, we explored a cool feature called interpolation, which lets us smoothly mix different languages. For example, we can keep certain words, like “Flutter,” in English, no matter what language the user chooses. Lastly, we tackled the challenge of dealing with singular and plural forms in different languages, making our app even more adaptable.

Keep Reading

Keep Reading

Struggling with EHR integration? Learn about next-gen solutions in our upcoming webinar on Mar 6, at 11 AM EST.

Register Now

Let's create something together!