Internationalization Using JSON Files in Flutter

There are different ways of internationalizing your application in Flutter. One common approach is to use JSON files. JSON files are easy to use and most developers have worked with them. In this post, we will go over internationalization using JSON files in Flutter.

Install the Easy Localization Package

To start, we need to install the Easy Localization package into our project. The package can be easily installed by executing the following command inside our project:

flutter pub add easy_localization

Once the command is executed, make sure to check your pubspec.yaml file for the added dependencies. You should see the Easy Localization package included in the dependencies, like this.

dependencies:
  easy_localization: ^3.0.7

Setup the Folder Structure and Language Files

As mentioned in the introduction we will be using JSON files for our translations. Before we start creating those JSON files, we have to create a location inside our project to store them.

First of all we want to create an assets folder followed by a translations folder. Inside the translations folder we want to place our JSON language files, in this case: en.json and pt.json.

|-- assets/
  |-- translations/
    |-- en.json
    |-- pt.json

Feel free to change the language files according to the languages your application needs. Make sure to only use the language code for example: pt for Portuguese or the full locale code, for example: pt-BR for Brazilian Portuguese. This is a must or else the files cannot be found by the Easy Localization package.

Registering the Translations Folder

After creating the folders and files we have to register the path in our pubspec.yaml file. We can do this at the bottom of the file. There we can add an assets entry with the path to our new folder.

flutter:
  assets:
    - assets/translations/

Because we made changes to the pubspec.yaml file we need to execute the following command to get the dependencies:

flutter pub get

Adding Locales to the Info.plist for iOS

If you want your application to be supported on iOS you also need to add the supported locales to the ios/Runner/Info.plist file.

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

Implement the EasyLocalization Widget to Internationalize Using JSON files

To start implementing internationalization using JSON Files in Flutter the Easy Localization package has an EasyLocalization widget. This widget needs to wrap the whole application. Inside this widget we need to add the following required attributes: child, path and supportedLocales. Let us have a look at the example.

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

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await EasyLocalization.ensureInitialized();

  runApp(EasyLocalization(
    fallbackLocale: const Locale('en'),
    supportedLocales: const [
      Locale('en'),
      Locale('pt'),
    ],
    path: 'assets/translations',
    child: const MyApp(),
  ));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      locale: context.locale,
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      home: Scaffold(
        body: Center(
          child: const Text('hello').tr(),
        ),
      ),
    );
  }
}

In this example, we wrap our MyApp widget that contains the MaterialApp widget with the EasyLocalization widget. Inside the EasyLocalization we set the fallbackLocale to English, we set the supportedLocales to English and Portuguese, and set the path to assets/translations.

Afterward, we set the locale related attributes of our MaterialApp widget to use their equivalents using the Easy Localization package’s context extensions.

The EasyLocalization package will use the device’s locale as the initial locale when it is in the supportedLocales, otherwise it will change it to the fallbackLocale. However, it is also possible to set a default Locale using the startLocale attribute. Once the user changes their locale in the application the package will ensure that this locale is persisted.

Before we run the runApp function we also have to ensure that the EasyLocalization widget is initialized. We do this by calling the EasyLocalization.ensureInitialized function.

However, before we can call this function we need to ensure that the WidgetsFlutterBinding is also initialized. This is done by calling the WidgetsFlutterBinding.ensureInitialized function.

On line 31, you can see that we are calling the tr function. This function takes the given string of the Text widget and tries to translate it. However, we do not have any translations in our JSON files yet.

Adding Translation to JSON Files

In our current implementation we are trying to show a translation of “hello”. However, we need to ensure that the “hello” key exists inside our JSON files. We will also add some additional keys so that we do not have to adjust the JSON files again for one of the upcoming sections.

The JSON files should look like this:

en.json

{
  "hello": "Hello",
  "language": {
    "en": "English",
    "pt": "Portuguese"
  }
}

pt.json

{
  "hello": "Olá",
  "language": {
    "en": "Inglês",
    "pt": "Português"
  }
}

After changing the JSON files we can run the application and you will see that “hello” gets translated to Hello.

Different Translation Methods

In the previous section, we called the tr extension on the Text widget. Calling this extension ensures that the string inside the Text widget will be translated.

const Text('hello').tr()

The above method is not the only way to translate strings. We can also use the context and String extensions or the static tr function. Using these methods also allows us to translate strings outside of the Text widget.

In the below example, we are using the context extension to translate “hello”.

Text(context.tr('hello'))

We can also directly call the tr function on a string using the package’s String extension.

Text('hello'.tr())

At last, we can use the static tr function to translate “hello”.

Text(tr('hello'))

As you can see there 4 methods that can be used to translate strings. When using a Text widget, I recommend the first approach because it allows you to use the const keyword, which will prevent unnecessary rebuilds.

Switching Locales

As mentioned before the Easy Localization package automatically sets the application’s locale to the locale of the user’s device when available in the supportedLocales. However, we also want to allow the user to switch between languages.

Luckily the Easy Localization package has a convenient extension called setLocale which can be used to change the locale. To simplify switching locales we will create a dropdown that allows the user to select the language.

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

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await EasyLocalization.ensureInitialized();

  runApp(EasyLocalization(
    fallbackLocale: const Locale('en'),
    supportedLocales: const [
      Locale('en'),
      Locale('pt'),
    ],
    path: 'assets/translations',
    child: const MyApp(),
  ));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      locale: context.locale,
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      home: const HomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          DropdownButton<Locale>(
            value: context.locale,
            borderRadius: BorderRadius.circular(10),
            onChanged: (Locale? locale) async => context.setLocale(locale!),
            items: context.supportedLocales
                .map<DropdownMenuItem<Locale>>(
                  (Locale locale) => DropdownMenuItem<Locale>(
                      value: locale,
                      child: Text('language.${locale.languageCode}').tr()),
                )
                .toList()
              ..sort(
                (a, b) => tr('language.${a.value?.languageCode}').compareTo(
                  tr('language.${b.value?.languageCode}'),
                ),
              ),
          ),
        ],
      ),
      body: Center(
        child: const Text(
          'hello',
          style: TextStyle(fontSize: 30),
        ).tr(),
      ),
    );
  }
}

In the above example, we start by creating the HomePage widget. We have to extract this functionality to a separate widget because when the locale is changed we want the widget to rebuild to pick up the context changes.

Within our HomePage widget, we create a DropdownButton that contains all the supported locales. When the user selects a new language, the setLocale extension is called to change the current locale to the newly selected one.

To display the correct languages in the dropdown we use the tr function to dynamically translate the languages using the language key in combination with the languageCode. This will create the following keys: language.en and language.pt.

Now if we build the application again you will see that we can now switch between locales and that the translations will change accordingly.

Placeholders, Pluralization, Linked Translations

The easy Localization package offers much more than just regular translations. First of all it supports the use of placeholders so you can add dynamic values inside your translations. You can also use the plural extension to dynamically change the translations based on a given integer value. At last, you can reuse translations by linking them inside the JSON files.

Placeholders

With placeholders, you can insert values inside your translations, for example, if we have the following translation inside our JSON file.

{
  "hello": "Hello {name}",
}

We can insert a name inside the translation by using the namedArgs parameter of the tr function.

const Text('hello').tr(namedArgs: {'name': 'Tijn'})

This will result in the following output:

Hello Tijn

If you want to learn more about placeholders check out the Translate context.tr section of the package’s official documentation.

Pluralization

To pluralize your translations you can add the following keys inside your JSON translation file.

{
  "article":{
    "one": "Article",
    "other": "Articles"
  },
}

Afterward, we replace the tr extension with the plural extension. The plural extension takes an integer which is used to set the amount. The plural extension will determine which translation to use based on the given amount.

const Text('article').plural(2)

Using the above line will result in the following output:

Articles

If you want to learn more about pluralization check out the Plurals plural section of the package’s official documentation.

Linked Translations

In some cases, you might need to reuse existing translations inside your JSON files. This can easily be achieved by prefixing the key with the @: symbols.

{
  "hello": "Hello @:world",
  "world": "World",
}

To show linked translation we can just call the regular tr function.

const Text('hello').tr()

Changing the JSON file as shown will result in the following output:

Hello World

If you want to learn more about linked translations check out the Linked translations section of the package’s official documentation.

Managing JSON Translation Files

When working with multiple languages or large JSON files, managing them can become quite challenging. Luckily, there are great tools that can simplify this, one of these tools is BabelEdit: Manage Translation Files in Flutter Using BabelEdit

Conclusion

Internationalizing your Flutter application using JSON files and the Easy Localization package is straightforward. The discussed approach is easy to implement and understand. The package offers convenient extensions to translate and switch between locales. You also have additional functionalities like placeholders, pluralization, and linked translations out of the box.

Tijn van den Eijnde
Tijn van den Eijnde
Articles: 40

Leave a Reply

Your email address will not be published. Required fields are marked *