How to Internationalise a Flutter Mobile App
Reinhold Quillen
Chatbot & Mobile Apps Team Lead (Flutter, React Native & Native iOS/Android) - iOS & Flutter Developer IOT (Bluetooth) - - Loc: Melbourne - Designing & building Large Language Models.
When a new mobile App design is being carried out, it still surprises me to learn that quite often the App being able to display other languages doesn't seem to be on the radar of many design teams as most simply just consider the United States and other English-speaking countries to be the prime user markets.
That may be fine as US English is considered to be the prime de-facto international language, so why bother with other languages?
Please note: For some weird reason, the code blocks in this article are blank in some browsers if you are not logged into LinkedIn - this clearly is a LinkedIn issue. Hence please make sure that you are logged into LinkedIn when reading this article.
If you write medical Apps and you wish to sell your App into the European market and you need to register your medical App with the European authorities, it will need to be written in the language of the country into which you wish to sell your medical devices. An App which uses the native tongue of your user base will usually be more successful than a generic US-English App. It simply depends on your App and which markets are important to your organisation. Planning ahead and internationalising your mobile App even if US English is the prime market will have several advantages, viz.
With these thoughts in mind, this article will give you a simple step-by-step process which will allow you to internationalise a Flutter App to automatically change the user-visible text based for the regions or countries you choose to support.
The sample project is here - please study the source code comments which explain the various bits you will be able to adapt for your own projects. Rather than wrapping the code into a snazzy App, I have decided to use the standard App created by Flutter for a new project to allow you to focus on the language code rather than having to figure out how the demo App works.
$ flutter create --platforms=ios,android --org au.com.[yourdomain] yourprojectname
2. Required Packages: Next we need to add the flutter_localizations packages to the project which you can do by issuing the following command-line commands in the root directory of the project you have just created, viz.
$ flutter pub add flutter_localizations --sdk=flutter
$ flutter pub add intl
or
$ flutter pub add intl:any
The above adds the flutter_localizations which helps handling Map() data and the latest intl package which helps deal with locale-specific date and number formatting, message translation, message parsing and bi-directional text - left-to-right (LTR) and right-to-left (RTL). Sometimes you will see "flutter pub add intl:any" in some texts, which just means that it pulls in the intl library pinned by the flutter_localizations package when that was added to the pubspec.yaml file. Generally :any can be avoided as flutter will pull in the same version number of the intl package as was pinned earlier - it's personal preference.
3. Required Directories: The next step is to internationalise the App, which is done by first creating the lib/l10n/ directory (note lowercase letter l => ASCII code 108 in /l10n) which is added to the assets: section and adding the generate: true flag in pubspec.yaml file in the "flutter:" section generally above the "assets:" section:
# The following section is specific to Flutter
flutter:
? # The following line ensures that the Material Icons font is
? # included with your application, so that you can use the icons in
? # the material Icons class.
? uses-material-design: true
?
# Adds international code generation support.
? generate: true
? # To add assets to your application, add an assets section, like this:
? assets:
? ? - lib/l10n/ # This is where the .arb files will reside.
? ? - assets/images/
4. Configuration: Now we need to add the file "l10n.yaml" to the project directory (where pubspec.yaml is located) with the following content for that file:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
use-escaping: true
In the above, please note the "use-escaping: true" flag. We only add this flag if you may need to use tokens such as { and } as normal characters in your .arb files and you don't wish to have these tokens parsed. This forces the parser to ignore any tokens prefixed with a pair of single quote characters and if you wish to use a normal single quote character, use a pair of consecutive single quotes, e.g.
{
"helloWorld": "Hello! '{Isn''t}' this a wonderful day?"
}
This will produce the following output: "Hello! {Isn't} this a wonderful day?"
If you don't use tokens in your .arb file, then the use-escaping flag can be omitted or set to false.
5. JSON Language Resource File Locations:
In your ${FLUTTER_PROJECT}/lib/l10n directory, place the first template file app_en.arb and the output localizations file will be app_localizations.dart and our app_en.arb file will only contain a few lines (in a real project, this file will have many more JSON elements in it.)
NOTE: You are required to have this base *_en.arb file in your project - this is the default US English JSON language resource file, where * can be anything you like as long as the filename has the _en.arb component and the name must match the name specified in the "l10n.yaml" file as outlined earlier. In this case, I chose app_en.arb for the filename.
CAUTION: Because .arb files are JSON application resource bundle files, comments are not allowed, eg. // This is a comment => sorry not allowed.
?{
? "@@locale" : "en",
"appName" : "Test App",
? "main_line_1" : "This is the first line",
"@main_line_1": {
"description": "Optional meta-data description of the 1st line."
},
? "main_line_2" : "This is the second line",
"@main_line_2": {
"description": "Optional meta-data description of the 2nd line."
},
? "main_line_3" : "This is the third line",
"@main_line_3": {
"description": "Optional meta-data description of the 3rd line."
}
}
My suggestion is that for the keys you use widgetfilename_widgetname which allows you to quickly know where a line is used. For example, in the above the widget filename is main.dart and "line_1" is the designator, then the key name becomes "main_line_1" and the language-specific value text is "This is the first line".
The metadata description "@main_line_1", etc is optional - its purpose is to add a description to for the key "main_line_1" but if you follow the naming convention outlined, you can dispense with this because with the metadata descriptors, the .arb file can get very large and the keys may get lost in a lot of descriptors (not being able to see the forest due to all the trees). You can pretty much please yourself if you wish to use metadata descriptors or not - I generally don't bother because my template file key naming convention tells me exactly where each key belongs and what it stands for.
6. Default JSON Language Resource File:
The ${FLUTTER_PROJECT}/lib/l10n/app_en.arb file is the default JSON application resource fallback bundle file for the base locale, which is US English in our case - this file must always exist - your project will not compile without it. The naming convention we use for the .arb files requires that it must contain underscores and a string with one of the ISO 639-1 language codes but we can also add ISO 3166 regional codes for different language regions, like the US, Great Britain, Australia, etc.
For example, for different English-speaking locales, like for US English, we would have the filename whatever_en.arb ('whatever' can be anything you like), in our case we use app_en.arb, for Great Britain, we would have app_en_GB.arb, for Australia, app_en_AU.arb, for Malaysia app_en_MY.arb, for France, app_fr.arb, French Canada, we would have app_fr_CA.arb, for German-speaking Switzerland, app_de_CH.arb, for Germany itself, app_de.arb or app_de_DE.arb, etc. Here we choose 'app' for 'whatever', ie app_en.arb but 'app' can be anything you like as long as you use an underscore and the correct language identifier, 'en' in this case and the name must match what is listed in the "l10n.yaml" template file.
In summary, for a project, you will have the base fallback .arb file ${FLUTTER_PROJECT}/lib/l10n/app_en.arb for US default locale as well as specific .arb files for this sample project as we wish to support Australia, Great Britain and Malaysia (your project of course will differ):
${FLUTTER_PROJECT}/lib/l10n/app_en_AU.arb => for Australia
${FLUTTER_PROJECT}/lib/l10n/app_en_GB.arb => for Great Britain & Ireland
${FLUTTER_PROJECT}/lib/l10n/app_en_MY.arb => for Malaysia
If you are only supporting English for all country regions, then you would only have the base fallback file app_en.arb and nothing else.
If you are unsure what your locale name would be, create a sample project, set the region on your Android phone and in main.dart in the initState() method of class _MyHomePage extends State<MyHomePage> put in the following line:
import 'dart:io'; // Put this at the top of the file for 'Platform'
final String defaultLocale = Platform.localeName;
In Android -> System -> "Languages and input" for various regions, the above 'defaultLocale' will return en_AU for Australia, en_US for United States, en_GB for Great Britain and Ireland, en_ZA for South Africa, etc. Hence you can set your Android phone to the various languages and regions you wish to support to allow you to name your .arb files correctly by reading the 'defaultLocale' variable value.
NOTE: When adding support for a new language and region in Android settings (System-> Languages and input -> Languages), the chosen language and region, eg, English(Malaysia) needs to be set as the first language to be the device default language (the demo App will respond to this change.)
7. ARB File Structure:
The structure of .arb files is quite simple, in our app_en.arb file, "@@locale" JSON resource application key is optional - it simply forces another check to ensure that the app_en.arb file is named correctly. Note the "_en" is the important part of the file, as this identifies the language locale. If the "@@locale" : "en" key exists and it is not named *_en.arb, an exception is raised. In our case we are using the prefix 'app' - you can use anything you want here.
Take note of this link when creating your .arb filename.
8. Required Flutter Import Statements:
In your main.dart file, you need to add the following import statements as below:
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
and then also add the AppLocalizations.delegate in the MaterialApp() constructor:
import 'package:flutter/foundation.dart'
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
.
.
.
void main() {
WidgetsFlutterBinding.ensureInitialized();
.
.
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
//LocalJsonLocalization.delegate.directories = ['lib/l10n'];
return MaterialApp(
debugShowCheckedModeBanner: false, // Hides the red debug notifier.
theme: [put your theme here], // Put your theme here.
localizationsDelegates: const [
AppLocalizations.delegate, // Needed in the constructor to MaterialApp.
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const <Locale> [
Locale('en'), // English
],
title: "Language Neutral Test App", //Any App localizations can only be called outside this method,
home: const MyHomePage(),
);
}
};
9. Flutter Build Step:
Once the app_en.arb file has been created and the App localisation code initialised as outlined previously, then after flutter pub get and the build step, we need to add: flutter gen-l10n (note lowercase letter l => ASCII code 108 in gen-l10n), e.g. we would have:
flutter pub get
flutter gen-l10n
flutter build args .... (or Android Studio menu build step).
NOTE: Whenever you make any changes to the .arb files, you need to run the "flutter gen-l10n" step before your command-line build command or Android Studio build.
10. Project Code:
In the project code, when adding support for various languages (language codes) and regions (country codes), we use Locale() if we only need to add language and country codes:
localizationsDelegates: const
// The delegates are factories for producing localised values.
AppLocalizations.delegate, // Needed in the constructor to MaterialApp.
GlobalMaterialLocalizations.delegate, // Provide localized strings for up to 113 locales (as of June 2023) for Material components
GlobalWidgetsLocalizations.delegate, // Defines default text directions, either left-to-right or right-to-left for widgets lib
GlobalCupertinoLocalizations.delegate, // Provide localized strings for up to 113 locales (as of June 2023) for Cupertino widgets
],
supportedLocales: const <Locale> [
// You can set supported languages and regions but not script codes.
Locale('en'), // English - base file for all countries incl. US locale
Locale('en', 'AU'), // English - Australia (region)
Locale('en', 'GB'), // English - UK (region)
Locale('en', 'MY'), // English - Malaysia (region)
.
.
If however, you have languages like for example Mandarin, which also have ISO15924 script codes (a set of graphic characters used to represent a language), then we need to use Locale.fromSubtags():
localizationsDelegates: const
// The delegates are factories for producing localised values.
AppLocalizations.delegate, // Needed in the constructor to MaterialApp.
GlobalMaterialLocalizations.delegate, // Provide localized strings for up to 113 locales (as of June 2023) for Material components
GlobalWidgetsLocalizations.delegate, // Defines default text directions, either left-to-right or right-to-left for widgets lib
GlobalCupertinoLocalizations.delegate, // Provide localized strings for up to 113 locales (as of June 2023) for Cupertino widgets
],
// Full Chinese support for CN, TW, and HK - code courtesy localizely.com site
supportedLocales: const <Locale>[
Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), // generic simplified Chinese 'zh_Hans'
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), // generic traditional Chinese 'zh_Hant'
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'), // 'zh_Hans_CN'
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'), // 'zh_Hant_TW'
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'), // 'zh_Hant_HK'
],
The project main.dart file shows how to use the key-values from the JSON language resource files, e.g.
.
.
Text(AppLocalizations.of(context)!.main_line_1),
Text(AppLocalizations.of(context)!.main_line_2),
Text(AppLocalizations.of(context)!.main_line_3),
.
.
Please study the source code comments, as there is quite a bit of information contained therein. There are other concepts around how to display currency, numbers and dates for specific regions like NumberFormat but since the _locale variable gives you the language code, the country code and the script code, you can adjust your code according to these values to display fonts, currency, number strings, etc.
11. Log Files:
If the user is likely to see logging data or error messages, then of course we use the .arb files to allow your users to see those messages in their native tongue - e.g. users may also be able to access internal App logs for certain types of Apps. It really depends on your App functionality.
For non-user visible log files, it is your personal preference whether you use text literals in your code, rather than using .arb files to hold the text values. I use .arb files but I distinguish logs via the JSON key names, e.g. "main_user_widgetname_log" is for logging data the user may be able to see and something like "main_non_user_widgetname_log" for a logging message which users will never see as these messages will only be destined for your developers.
If say, your App development team language is English, then these non-user log and error messages would be in English in ALL your .arb files, as there is no point displaying non-user messages in the App user's native tongue for your development team(s). This would be an organisational policy depending on the App type and user market.
I am happy to consult for your organisation if you think that your Flutter developers may need some help in this area.
I have put tags, viz. AU, GB, MY, Base into the .arb language resource files to let you easily see which .arb file is being used when you run the App.
One point to note in our example is that Great Britain, Australia, Malaysia generally use Oxford English spelling, hence those .arb files would be the same. If you removed say the app_en_MY.arb file, then for Malaysia the JSON resource .arb file would be the default US English app_en.arb file. Just one point to keep in mind.
In this article I have tried to cover some concepts which sometimes get left out of articles dealing with internationalising a Flutter App and to make some things clearer for developers new to Flutter. There is still a bit to learn about internationalising a Flutter App but if you understand this article, then you have a good base for learning more from the Google Flutter documentation.
Please share this article if you find it useful. Thanks.
Project repository link: https://github.com/devrq26/internationalapp.git
Software Engineer | Mobile App Developer | Backend Developer | Flutter | Dart | Java | Spring boot| Python
1 年Nice one Reinhold Quillen.
Software Dev ?????? | AI ?? DNN
1 年Great solution to localisation! Localising an app is no simple task and easily overlooked as you mentioned. Another consideration is whether a font is compatible with a particular locale (just to add more complexity to the task)
Full-stack Senior Software Engineer
1 年Good article ??