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
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.
Disclaimer
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
andintl
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:
$ 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:
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.
import 'package:flutter_localizations/flutter_localizations.dart';
Then, in your MaterialApp
or CupertinoApp
object, specify the supportedLocales
and localizationsDelegates
.
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 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:
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:
{
"@@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:
{
"@@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.
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.
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
// Other code
child: Text("This is the main screen")
I would use
// 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 on the official Flutter YouTube channel is a great resource.
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 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:
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 theChangeNotifier
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 anewLanguageCode
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.
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:
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:
// 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 aMultiProvider
object with out newLanguageProvider
. The default language when the app runs first is set to English as a language code is required by the class. TheMultiProvider
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 thelanguageCode
attribute of theLanguageProvider
.
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 section of the official Flutter docs.
Updating the settings page
Make the following changes to your settings page:
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.
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 thelanguageCode
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.
Subscribe to my newsletter
Read articles from Mukul Aryal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
