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.
Table of contents
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 thefallbackLocale
. However, it is also possible to set a default Locale using thestartLocale
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.