If your app might be deployed to users who speak another language then you'll need to internationalize it. You'll need to write the app in a way that makes it possible to localize values like text and layouts for each language that the app supports.
So here's an easy step by step tutorial for internationalization of your flutter app, also we'll see how to manually switch your App's Locale on specific events. I'll assume you know how to setup a flutter app so we’ll skip that part.
Localization in Flutter is like any other thing, a Widget. We're going to use the package flutter_localizations that is based on Dart intl package.
So first step is to include the packages in pubspec.yaml file. For that just add the 2 packages name under the dependencies. It would look something like this.
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
dev_dependencies:
intl_translation: ^0.17.6
The first one says our package needs the localization Flutter package to run, and the second give us the tools to generate dart code with the messages from .arb files. You can now use it in your app anywhere simply by importing it. We'll import it in the main.dart file first. Goto your main.dart file and add import the package.
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart';
void main() => runApp(MyApp());
The next step is to setup the Localization delegates in your main.dart file. For that you need to specify the 3 delegates, and supported locales.
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
MyLocalizationsDelegate()
],
supportedLocales: [
const Locale('en', 'US'),
const Locale('vi', 'VN'),
],
//...
);
As you can see here, we added 3 lists to our Material app localizationsDelegates & supportedLocales. The supportedLocales parameter holds the list of languages that our app will support. Here you will specify list of languages_codes.
- GlobalMaterialLocalizations.delegate provides localized strings and other values for the Material Components library.
- GlobalWidgetsLocalizations.delegate defines the default text direction, either left to right or right to left, for the widgets library.
- MyLocalizationsDelegate to provide this resources to our app.
Next step is to create an MyLocalizations & MyLocalizationsDelegate. Classes in which we'll be defining our keywords and their translations. I suggest you create this class in a folder in your lib directory. For example /lib/l10n/localization_intl.dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class MyLocalizations {
static Future<MyLocalizations> load(Locale locale) {
final String name = locale.countryCode.isEmpty
? locale.languageCode
: locale.toString();
final String localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((b) {
Intl.defaultLocale = localeName;
return new MyLocalizations();
});
}
static MyLocalizations of(BuildContext context) {
return Localizations.of<MyLocalizations>(context, MyLocalizations);
}
//your list of locale here...
}
class MyLocalizationsDelegate extends LocalizationsDelegate<MyLocalizations> {
const MyLocalizationsDelegate();
bool isSupported(Locale locale) => [
'en',
'vi'
].contains(locale.languageCode);
Future<MyLocalizations> load(Locale locale) {
return MyLocalizations.load(locale);
}
bool shouldReload(MyLocalizationsDelegate old) => false;
}
The MyLocalizations class will only have 2 static methods for now:
- load function will load the string resources from the desired Locale as you can see in the parameter.
- of function will be a helper to facilitate the access to any string from any part of the app code.
NOTE: initializeMessages method will be generated by the intl tool, note the import l10n/messages_all.dart , this file contains the method that effectively load the translated messages.
The MyLocalizationsDelegate can be divided in three main pieces:
- load method must return an object that contains a collection of related resources. We return our MyLocalizations.load
- isSupported with boolean return type, it returns true if the app has support for the received locale
- shouldReload simply, if this method returns true then all the app widgets will be rebuilt after the load of resources.
And here, the list of locales in this article:
//your list of locale here...
String get welcome {
return Intl.message(
'Hello! ',
name: 'welcome',
desc: 'welcome',
);
}
String taskItems(int taskNumbers) {
return Intl.plural(taskNumbers,
zero: "You have never written a list of tasks.\nLet's get started soon.",
one: "This is your todo-list,\nToday, you have 1 task to complete. ",
many: "This is your todo-list,\nToday, you have $taskNumbers tasks to complete. ",
other: "This is your todo-list,\nToday, you have $taskNumbers tasks to complete. ",
args: [
taskNumbers
],
name: "taskItems");
}
String get languageTitle {
return Intl.message(
'Change Language',
name: 'languageTitle',
desc: 'languageTitle',
);
}
The final step is to create the string translations. Create a res directory in your project folder. This directory will hold all the translated values as per your Locale. We run this dart intl tool command to generate an .arb template.
$ flutter pub pub run intl_translation:extract_to_arb \
--output-dir=res/ \
lib/l10n/localization_intl.dart
This command will generate a file called intl_messages.arb file into res directory and this file serves as a template for the English language, we have to create the desired translations based on this file:
├── res
├──── intl_messages.arb
NOTE: The Dart intl package only creates one template file of .arb from your MyLocalizations class and names it intl_messages.arb. From here you now have to manually create other resource files with convention intl_[LANGUAGE_CODE].arb and copy the content of intl_messages.arb directly with updated the values.
For example, in this article, we'll create files and copy content from intl_messages.arb like this:
├── res
├──── intl_messages.arb
├──── intl_en_US.arb
├──── intl_vi_VN.arb
/res/intl_en_US.arb
{
"@@last_modified": "2019-08-27T12:45:48.112434",
"welcome": "Hello! ",
"@welcome": {
"description": "welcome",
"type": "text",
"placeholders": {}
},
"taskItems": "{taskNumbers,plural, =0{You have never written a list of tasks.\nLet''s get started soon.}=1{This is your todo-list,\nToday, you have 1 task to complete. }many{This is your todo-list,\nToday, you have {taskNumbers} tasks to complete. }other{This is your todo-list,\nToday, you have {taskNumbers} tasks to complete. }}",
"@taskItems": {
"type": "text",
"placeholders": {
"taskNumbers": {}
}
},
"languageTitle": "Change Language",
"@languageTitle": {
"description": "languageTitle",
"type": "text",
"placeholders": {}
}
}
Okay, with the translations ready, we are going to link the initializeMessages. This is made with the follow command:
$ flutter pub pub run intl_translation:generate_from_arb \
--output-dir=lib/l10n \
--no-use-deferred-loading \
lib/l10n/localization_intl.dart \
res/intl_en_US.arb \
res/intl_vi_VN.arb
You can see that all the necessary files were generate. In this case, the l10n directory look like:
├── l10n
├──── localization_intl.dart
├──── messages_all.dart
├──── messages_en_US.dart
├──── messages_vi_VN.dart
To use language specific text in your app, you'll simply need to follow the two steps:
- Import MyLocalizations class where you want to use the text.
- Call localized value of text via:
MyLocalizations.of(context).welcome
// or
MyLocalizations.of(context).taskItems(model.taskNumbers)
When you call the above methods, it calls the getter of the MyLocalizations class and returns its value based on the Locale of the context. If the context is en (en_US) it will return the value from intl_en_US.dart and same things with another Locale.
That's all for this article, you can checkout the source code of the example at here. This localization method is a little bit complicated and also boring but I think this will improve with time.
Okay, I'll see you next article and enjoy!