top of page

Flutter Internationlisation

In Software, Internationlisation refers to the ability to provide multiple languages in an application, so that users with different language reference can use the same app in their preferred language. This helps the app to reach out to wider users and expand the market for the app.


Internationlisation, however, is different from the translation activity. Internationlisation is related to cultural adaptation and translation of an application from 1 language to another. This includes the language translation, the presentation of Text and images, and more.


Within this context, I’m going to focus on application’s translation for a Flutter application.

There are 3 ways to implement Internationlisation in Flutter app: using Dart’s intl package, using flutter_localisation package, and using Flutter Intl plugin in Android Studio or VS Code. I will introduce the 2 other methods in my followup posts.


First method: Dart’s intl package


This is one of the most important package to be used in any Flutter project. It provides a complete set of internationalisation, from language translation to cultural formatting for date, number and currency. However, to use it for language translation, there’s a little complex in term of generating dart file and update arb file.


Install ‘intl’ package

To install intl package to your project, edit your pubspec.yaml and add intl package under your dependencies. You also need to include intl_translation package to provide message extraction and code generation from translated messages for the intl package.

dependencies:  intl: 0.16.0
intl_translation: ^0.17.10+1

Run command line below

$ flutter pub get

Alternatively, in Android Studio, go to Tools -> Flutter -> and run Flutter Pub Get


Implementation

Step 1: Initial setup for your App

You will need to create your own Localisation class, and implement LocalizationsDelegate for your project. I recommend to keep both these classes in the same dart file for simple implementation, but separate them from any other classes.

First, let’s create our localisation class where all strings are defined. For our own convenience, we will also include 2 helper methods to easily load localisation base on Locale and BuildContext.


NOTE: Keep track of this file’s location, you will need it to run a command line later

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

class DemoLocalizations {
  static Future<DemoLocalizations> load(Locale locale) {
    final String name = locale.countryCode == null ? locale.languageCode : locale.toString();
    final String localeName = Intl.canonicalizedLocale(name ?? 'en');
    return initializeMessages(localeName).then((bool _) {
      Intl.defaultLocale = localeName;
      return new DemoLocalizations();
    });
  }

  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
  }
}

Do note that at this stage, there is compile error at initializeMessages(localeName) because we will need this method from 1 of a generated class in our later step.

We also need a localisation delegate class where we define our supported language, a load method, and a shouldReload to indicate how our localisation behaves when its load method is called.

class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();

  @override  bool isSupported(Locale locale) {
    return ['en', 'ja', 'vi'].contains(locale.languageCode);
  }

  @override  Future<DemoLocalizations> load(Locale locale) {
    return DemoLocalizations.load(locale);
  }

  @override  bool shouldReload(LocalizationsDelegate<DemoLocalizations> old) {
    // always reload localization when load(locale) is called
    return true;
  }
}

At this point, your setup is mostly done, however there’s still compile error because of initializeMessages(localeName) above. To fix that, we need to run 2 command lines to generate message_all.dart file, then include this file in our localisation class.

First command line is to generate arb file:

$ flutter pub run intl_translation:extract_to_arb  --output-dir=assets/l10n/ lib/l10n/demo_localisation.dart

After this command line is run, the first arb file is generated at

assets/l10n/intl_messages.arb

And the second command line is to generate translated dart file base on this arb file.

$ flutter pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/l10n/demo_localisation.dart assets/l10n/intl_*.arb

These 2 command lines are very important in this implementation, and you will need to use them over and over again during your work with intl package.

At this point, the project can be compiled without issue, however, there’s no translated string, and your Flutter App is not supporting language translation either.

To add translation into your app, update your App to add supported localizationsDelegates and supportedLocales.


To simplify the list of supportedLocales, I’ll add this info into DemoLocalizationsDelegate

static const supportedLocale = [
  const Locale('en'), // English
  const Locale('ja'), // Japanese
  const Locale('vi'), // Vietnamese
];

Then in MyApp, declare it under supportedLocales

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Intl Demo Home Page'),
      // add below 2 fields to declare supported locales
      localizationsDelegates: [
        DemoLocalizationsDelegate()
      ],
      supportedLocales: DemoLocalizationsDelegate.supportedLocale,
    );
  }
}

Step 2: Add strings, and translate them

After setting up your project, it’s now time to start the actual work. From the above step, the most important part is the 2 command lines. You will need to use them whenever you add new strings to your DemoLocalizations class, and after translating them in the arb file.

In class DemoLocalizations, we add our first string in this format:

String get counter => Intl.message('You have pushed the button this many times:', name: "counter", desc: "Counter string to be displayed on UI");

This is a very simple string without any argument to it. After adding a string, we will need to run the first command above to generate arb file.

flutter pub run intl_translation:extract_to_arb --output-dir=assets/l10n lib/l10n/demo_localisation.dart

After this step, the arb file intl_messages.arb is now updated

{
  "@@last_modified": "2021-04-26T12:18:28.484537",
  "counter": "You have pushed the button this many times:",
  "@counter": {
    "description": "Counter string to be displayed on UI",
    "type": "text",
    "placeholders": {}
  }
}

You can see that your string with name “counter” is now added to the arb file.

Now let’s do translation. As previous step, we already defined our supported language to be English (code ‘en’), Japanese (code ‘ja’) and Vietnamese (code ‘vi’), we now need to create arb files for each language we supported. To do so, we copy intl_messages.arb to be intl_en.arb, intl_ja.arb and intl_vi.arb, all in the same folder as intl_messages.arb.

After translation is done, here is the content of each arb file:

intl_en.arb

{
  "@@last_modified": "2021-04-26T12:18:28.484537",
  "counter": "You have pushed the button this many times:",
  "@counter": {
    "description": "Counter string to be displayed on UI",
    "type": "text",
    "placeholders": {}
  }
}

intl_ja.arb

{
  "@@last_modified": "2021-04-26T12:18:28.484537",
  "counter": "あなたはこれまで何度もボタンを押しました:",
  "@counter": {
    "description": "Counter string to be displayed on UI",
    "type": "text",
    "placeholders": {}
  }
}

intl_vi.arb

{
  "@@last_modified": "2021-04-26T12:18:28.484537",
  "counter": "Bạn đã nhấn nút này bao nhiêu đây lần:",
  "@counter": {
    "description": "Counter string to be displayed on UI",
    "type": "text",
    "placeholders": {}
  }
}

Note that you only need to translate the actual string content, the description can be left as the default language (which is English in this case)

It’s time to run second command above now to generate dart strings from arb files

$ flutter pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/l10n/demo_localisation.dart assets/l10n/intl_*.arb

Well, it’s almost done now. However there’s still some warning when you run this command line.

No @@locale or _locale field found in intl_en, assuming 'en' based on the file name.No @@locale or _locale field found in intl_ja, assuming 'ja' based on the file name.No @@locale or _locale field found in intl_messages, assuming 'messages' based on the file name.No @@locale or _locale field found in intl_vi, assuming 'vi' based on the file name.

You can choose to ignore these warnings for now, but if you want to fix it, simply add “@@locale”: “en” as the next line after “@@last_modified” to file intl_messages.arb and intl_en.arb, and add the same line to other 2 arb files, but need to change ‘en’ to be ‘ja’ for intl_ja.arb, and to ‘vi’ for intl_vi.arb.


Step 3: Use the strings

To use your string in your code, you can simply replace your hard-coded string to be DemoLocalizations.of(context).counter, and that’s done.

To run your app in different language, change your device language to expected language and see the magic happens.


Step 4: Strings with arguments

Sometimes, you may want to add arguments in within your string, as in the string above, it makes better understanding if your string is like “You have pushed this button 10 times”, in which the number can be changed in real time.

To do that, you need to use argument in string. Argument can be any object with proper toString() implementation, and that string will be used to show as part of your string. For primitive type, toString() method is pre-implemented.

To add argument, we change our string definition above to be

String counter(number) => Intl.message('You have pushed the button $number times:',  name: "counter",  desc: "Counter string to be displayed on UI",    args: [number]);

In which, (number) is a dynamic object. We can define (int number) to clearly identity the data type input to this string.

Redo step 2, then change your string usage to be

DemoLocalizations.of(context).counter(_counter)

And you’re done.

Conclusion

intl package is a core internationalisation package, which provides not only text translation, but also date format, number and currency format, and more. However, the steps to maintain localisation using this method is complex and not favourable for a large project.


7 views0 comments
bottom of page