# Add multiple languages to your Flutter app

Whenever you build a mobile application, you may not have to care about adding multiple languages into your app. But, if you are creating an application that needs to focus on a specific locality or country, it is pivotal to make your application available in multiple languages so that you don’t miss out on potential users avoiding the app due to language barriers. In this blog post, I will guide you through the process of adding multiple language support to your Flutter application.

# What we will build

This article will guide you step by step through the process of creating a complete application with the following features:

* Multiple languages support
    
* A settings page that has an option to change the language
    
* Reflect the changes made in the Settings page throughout the app
    
* Persist the users’ desired languages in future sessions
    
    ![GIF image of the final app](https://cdn.hashnode.com/res/hashnode/image/upload/v1748704892258/937e1db5-64d0-4ba9-bd96-b6d0ed14942f.gif align="center")
    

You will notice the application also has a toggle to select the theme. Theme selection will not be covered in this article but it also follows most of the processes described in the article. The full code for the final application can be found on GitHub in the [flutter-base-app GitHub repository](https://github.com/mukulboro/flutter-base-app).

<details data-node-type="hn-details-summary"><summary>Disclaimer</summary><div data-type="detailsContent">Before you proceed, the blog post assumes that you have a basic understanding of Flutter and its workings. If you are unfamiliar with basic Flutter concepts, I recommend you go through the official <a target="_self" rel="noopener noreferrer nofollow" href="https://docs.flutter.dev/get-started/learn-flutter" style="pointer-events: none">Learn Flutter</a> part of the Flutter documentation. The code snippets included in the blog post have been tested on Flutter 3.29 along with Dart version 3.7 on an Android device running Android 15. Some code snippets may need some tweaking depending on your development setup. Any concepts related to localization in the article will be clearly explained in a beginner-level language. We will skim through other topics like global state management and preference persistence.</div></details>

# Introduction

In the early days of mobile application development, you could assume that everyone who is tech literate also has at least a basic understanding of the English language. But in today’s growing digital landscape, more and more people have access to technology even if they only speak one language.

So, it is extremely important to design your software to be adaptable to various languages. This process of creating a “language modular“ software product is known as **internationalization**. Whereas, adding a specific language to your pre-existing software product in called **localization**. Internationalization and localization are often abbreviated as **i18n** and **l10n** respectively for convenience. These abbreviations may seem weird at first, but seem perfectly normal when you realize there are 18 characters between i and n in internationalization and there are 10 characters between l and n in localization.

# Installing packages

To build the full application with settings page, you will need to install three packages.

* `flutter_localizations` and `intl` for all the l10n code.
    
* `provider` to handle the global state management.
    
* `shared_preferences` to persist the users’ language choice.
    

You can install all the packages through the command line easily, but for the localization packages, please use the following command:

```bash
$ flutter pub add flutter_localizations --sdk=flutter
$ flutter pub add intl:any
```

After installing all required packages, manually enable the `generate` flag in the `flutter` section of the `pubspec.yaml` file. This will enable our code to generate usable localizations. Finally, your `pubspec.yaml` file should look like this:

```yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  cupertino_icons: ^1.0.8
  intl: any
  provider: ^6.1.2
  shared_preferences: ^2.3.2

# Other options........
flutter:
    generate: true
```

# Localization

## Basic Setup

After all packages have been installed, open your `main.dart` file and import the `flutter_localizations` package.

```dart
import 'package:flutter_localizations/flutter_localizations.dart';
```

Then, in your `MaterialApp` or `CupertinoApp` object, specify the `supportedLocales` and `localizationsDelegates`.

```dart
return MaterialApp(
      title: 'Flutter Base App',
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: const [Locale('en'), Locale('ne')],
      locale: const Locale('en'),
      // Other parameters
)
```

**Note**: You may want to comment out the `AppLocalizations.delegate` item in the list. This will throw an error until we have created the specific translated strings (which we will do in a while).

### What is happening here?

Localizing an application is not as simple as basic string replacement. How a language is written and presented depends highly on the specific language. For example, Arabic is written right-to-left while majority of other languages are written left-to-right.

The `localizationDelegates` parameter specifies who is responsible for providing the various translations and behaviours.

* `AppLocalizations` is responsible for providing the specific string translation (which we will generate and import in a moment).
    
* `Global[X]Localizations` are responsible for handling widget-specific behaviors based on the language.
    

The `supportedLocales` parameter is very straight forward, it is a list of languages your application supports. To add any new language, add the language code within the `Locale` object in the list. The language codes used follows the two letter [ISO 639-1 language codes](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) standard. As you can see, my current app supports two languages; English and Nepali.

For now, we are currently setting the default language as English with the `locale: Locale('en')` parameter. You may change it if you want

## Creating necessary files and folders

In the root directory of your project (on the same level as `pubspec.yaml`), create a new file `l10.yaml`. Also, create a new folder named `l10n` inside the `lib` directory. Create a new file `app_en.arb` inside the newly created `l10n` folder.

### The l10.yaml file

The `l10.yaml` file is basically a config file that tells flutter where to look for the string translations and where to store the generated usable strings. Add the following lines to the `l10.yaml` file:

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

* `arb-dir` specifies where to find the arb files. We will expand on what arb is in a little bit.
    
* `tempale-arb-file` is used to locate the template file for the translations. Again, we will expand on this in the next section.
    
* `output-localiztion-file` specifies the name of the module we can import to finally use the localizations.
    

### What is app\_en.arb?

First of all, arb is an abbreviation for App Resource Bundle. It simply a JSON-like file that will store the localization strings. We have specified `app_en.arb` as the special “template file“ meaning that this file will contain localization strings as well as define the “keys“ that all other arb files will use. Simply put, you can only use a specific key in the Nepali arb if the key is defined in the English arb.

**Note:** The naming convention for the arb files is `app_languageCode.arb`.

Since, `app_en.arb` is the template file, add the following sample key value pairs in the file:

```json
{
    "@@locale": "en",
    "mainScreenTitle": "This is the main screen",
    "@mainScreenTitle": {
        "description": "The title of the main screen"
    },
    "settingsButtonText": "Settings",
    "@settingsButtonText": {
        "description": "The text of the settings button"
    },
    "settingsScreenTitle": "This is the Settings screen",
    "@settingsScreenTitle": {
        "description": "The title of the settings screen"
    },
    "settingsLanguageLabel": "Language",
    "@settingsLanguageLabel": {
        "description": "The label of the language setting"
    }
}
```

In this file, we have specified many normal looking `“key“: “value“` pairs but you may also notice some weird looking `“@key“: ”value”` pairs. These special keys define the specific keys all other arbs will have to use. Also, other `“@@key“: “value“` pairs are used to specify metadata.

Now, since we have the template arb file, we can proceed to creating arb files for our Nepali localization strings. For that, we will create a new file `app_ne.arb` in the same directory as `app_en.arb` with the following content:

```json
{
    "@@locale": "ne",
    "mainScreenTitle": "यो मुख्य पृष्ठ हो",
    "settingsButtonText": "सेटिङ्स",
    "settingsScreenTitle": "यो सेटिङ्स पृष्ठ हो",
    "settingsLanguageLabel": "भाषा"
}
```

Notice how this file is much simpler than the template file and contains only those keys we specified with `@key` in the template arb file. This example shows a very simple usage of the localization strings, if you want to explore other complex things like placeholders, refer to the [official i18n Flutter docs](https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization).

## Using the localization strings

Make sure to hot restart your applications which will create the usable localization files. Firstly, in the `main.dart` file, import the generated files.

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

After this, you may un-comment the `AppLocalizations.delegate` which we made sure to comment out earlier.

Now, import the `app_localizations.dart` file where ever you want to use localization strings, and place them where you would normally use constant strings.

For example, in my main screen instead of using

```dart
// Other code
child: Text("This is the main screen")
```

I would use

```dart
// Other code
child: Text(AppLocalizations.of(context)!.mainScreenTitle))
```

The attribute of the `AppLocalizations` you can use depends on the keys you defined in the arb files.

To check if everything is working, you can manually update the `locale` paramater in your `MaterialApp` or `CupertinoApp`.

# Settings Page

Now that we have added multiple languages to our Flutter app, we want a way for the user to choose what language they want to view the language in. This can be presented as an option to the user during the first start of the application as well. In this article, we will create a simple settings page with one toggle to change the language.

Firstly, we will create a simple settings page with a drop down menu with the choice to select the language. If you do not know how to create a drop down menu, the [DropdownMenu (Widget of the Week) video](https://www.youtube.com/watch?v=giV9AbM2gd8) on the official Flutter YouTube channel is a great resource.

![Screenshot of the settings page](https://cdn.hashnode.com/res/hashnode/image/upload/v1748712857314/41c8d9d6-5875-4d35-a453-ca34349e4a4b.jpeg align="center")

Make sure you are using the localization strings here as well instead of using constant strings. I have taken the liberty to add a label and icon alongside the drop down menu to make things visually appealing.

Each `DropdownMenuItem` within the `DropdoownButton` has a value property. The value of each button is passed to the `onChanged` function. So the `value` for each corresponding item is the language code for the language it represents. Write a placeholder function for now.

Currently, the app does nothing whenever we select a new language. This seems to be a challenging task as we need to change the value of the `locale` parameter on our `MaterialApp` or `CupertinoApp` from within the settings page. For this, we need to take the help of global state management.

# Global State Management

There are many packages you can use for global state management. For the purposes of this tutorial, we are using the very beginner friendly `provider` package. If you are not familiar with this package, I highly recommend reading [Simple app state management](https://docs.flutter.dev/data-and-backend/state-mgmt/simple) from the official Flutter docs.

In the `lib` folder, create a new folder named `providers`. This folder will contain all the provider classes we will use for global state management. Create a file `language_provider.dart` within this new folder.

Add the following code to the new file:

```dart
import 'package:provider/provider.dart';

class LanguageProvider extends ChangeNotifier {
  String languageCode;

  LanguageProvider({required this.languageCode});

  void setLanguage({required String newLanguageCode}) async {
    languageCode = newLanguageCode;
    notifyListeners();
  }
}
```

### What is happening here?

The code here is very simple to understand. Here is a line by line breakdown of the code:

* First of all, we have imported the `provider` package.
    
* We have created a new class `LanguageProvider` which inherits from the `ChangeNotifier` class.
    
* The class only has a single attribute; a string named `languageCode`.
    
* The constructor has a compulsory requirement of a `languageCode`.
    
* The `setLanguage` function is an asynchronous function that takes in a `newLanguageCode` parameter and updates the current attribute with the new language code. Finally, it notifies all widgets that are “listening“ to this provider and forces them to update their states.
    

## Updating the settings page

In the settings page, import the newly created provider along with the base `provider` package.

```dart
import 'package:flutter_base_app/providers/language_provider.dart';
import 'package:provider/provider.dart';
```

In the `onChanged` parameter which we left empty earlier, add the following code:

```dart
void __languageHandler(String? value) async {
    if (value != null) {
        context
            .read<LanguageProvider>()
            .setLanguage(newLanguageCode: _language);
    }
  }
// Other code....
onChanged: __languageHandler;
```

### What is happening here?

In the function, the following this are happening:

* We are taking the new value provided by the change in the drop down menu.
    
* If the `value` is not null, we are tapping into our LanguageProvider provider and updating the current language code.
    

## Updating the locale

Currently, we are successfully broadcasting the change in language to all listeners. We now need a listener that can react to these changes. The appropriate place for these changes to happen is in the `locale` parameter of our `MaterialApp` or `CupertinoApp`. In the `main.dart` file, make the following changes:

```dart
// Import the appropriate provider and base provider class
import 'package:flutter_base_app/providers/language_provider.dart';
import 'package:provider/provider.dart';
// Other Code....

void main(){
// Other Code....
runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => LanguageProvider(languageCode: 'en'))
      ],
      child: const MainApp(),
    ),
  );
}

// Other Code...

return MaterialApp(
    // Other Parameters....
    locale: Locale(context.watch<LanguageProvider>().languageCode),
    // Other Parameters.....
)
```

### What is happening here?

The following changes have been made to `main.dart`:

* The appropriate provider and base `provider` package have been imported.
    
* In the `runApp` function, rather than supplying the main app directly, it has first been wrapped in a `MultiProvider` object with out new `LanguageProvider`. The default language when the app runs first is set to English as a language code is required by the class. The `MultiProvider` object helps to define the scope till where our providers can broadcast their messages.
    
* Finally, rather than supply a default language code string in the `Locale` object, we tap into the `languageCode` attribute of the `LanguageProvider`.
    

Now, you can go to the settings page to choose a different language and you will notice that the language of the application changes as you please!

But, once you restart the app, the changes you made are lost. For this, we will have to save the user preferences somewhere.

# Persisting the changes

We have already installed the `shared_preferences` package. This package allows us to store key value pairs on disk. It is very similar to the `window.localStorage` function you would find on websites. If you want to learn more about this package, I would recommend you read the [Store key-value data on disk](https://docs.flutter.dev/cookbook/persistence/key-value) section of the official Flutter docs.

## Updating the settings page

Make the following changes to your settings page:

```dart
import 'package:shared_preferences/shared_preferences.dart';

void __languageHandler(String? value) async {
    final prefs = await SharedPreferences.getInstance();
    if (value != null) {
      setState(() {
        _language = value;
        context
            .read<LanguageProvider>()
            .setLanguage(newLanguageCode: _language);
      });
    }
    await prefs.setString('language', _language);
  }
```

### What is happening here?

We have made the following changes to the settings page:

* We have imported the `shared_preferences` package.
    
* The `__languageHandler` function has been made asynchronous.
    
* Inside the function, we have:
    
    * Created a new instance of the `SharedPreferences` key value store.
        
    * After the state of the language has been updated, we have persisted the new value of the language in the `language` key of the key value store.
        

## Updating main.dart

Finally, in `main.dart` we have to make some very small changes.

```dart
import 'package:shared_preferences/shared_preferences.dart';

void main() async {

  WidgetsFlutterBinding.ensureInitialized();
  final prefs = await SharedPreferences.getInstance();
  // Get language config from shared preferences if available
  final String? languageCode = prefs.getString('language');
  runApp(
    MultiProvider(
      providers: [
        // if shared preferences is empty, default value (english lang) will be used
        ChangeNotifierProvider(create: (context) => LanguageProvider(languageCode: languageCode ?? 'en'))
      ],
      child: const MainApp(),
    ),
  );
}
```

### What is happening here?

The following changes have been made:

* We have imported the `shared_preferences` package.
    
* The `main` function has been made asynchronous.
    
* Inside the function, we have:
    
    * Created a new instance of the `SharedPreferences` key value store.
        
    * Fetched the value of the `language` key from the key value store in the `languageCode` variable.
        
    * If `languageCode` is null, we will use the default language of English, otherwise we will choose whatever language the user last chose in the settings page.
        

***And voila! Everything we set out to do works!***

# Conclusion

Congratulations! You have reached the end of this blog post. I hope that this post was helpful to you. If you hope to read more posts like this, consider following me on Hashnode, and joining the newsletter.

If you have any queries or suggestions, please leave a comment or reach out to me directly by sending an email to mukul.development@gmail.com.
