Shubham Soni Product Engineer @ threedots | Google India Scholar 2018 | Technical Author @ LogRocket

Internationalization for Flutter apps

6 min read 1708

Flutter Logo

The recent release of Flutter 3, with its wonderful development experience and promise of a single codebase that works on many platforms, has become a Swiss Army knife for many iOS and Android apps, along with desktop and web apps slowly picking up the pace.

When you’re publishing an app that will be used all over the globe, having it in only one language doesn’t provide the same experience to every end user. While English is one of the most widely spoken languages, translating the app makes it more accessible and understandable to all users. For that reason, we’ll learn about localizing our Flutter apps in this article.

What will we build?

We’ll try to understand localization by using a counter app that comes in handy whenever you create a Flutter project, with some changes in the counterexample to demonstrate localization.

We’ll use the Flutter localizations package to successfully translate our app into another language, including interpolation for words and phrases and correct translation of singles and plurals.

Table of contents

Project configuration

  • Add required dependencies to pubspec.yaml:
    environment:
      sdk: ">=2.16.1 <3.0.0"
    
    dependencies:
      flutter:
        sdk: flutter
      flutter_localizations:
        sdk: flutter
      intl: ^0.17.0  
      cupertino_icons: ^1.0.2
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
      flutter_lints: ^1.0.0
    
    flutter:
      generate: true
      uses-material-design: true
    

Change your pubspec.yaml file as indicated above. flutter localizations includes a native localization package and intl that allows for internationalization and localization, including message translation, plurals, and genders. The generate: true line is essential for the localization packages’ automatic code generation and saves a lot of time.

Create a l10n.yaml file to the root of our project; i.e., lib/l10n.yaml. This file specifies the location of our translation files as well as the names of autogenerated Dart files:

arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

By default, Flutter localization stores its translations in ARB (Application Resource Bundle) files. These are simply key-value paired files like JSON. Let’s start by creating an arb file for our default English called app_en.arb and put this in lib/l10n/app_en.arb.

Since we’re supporting multiple locales, we’ll have to create a separate file for each of them that our app supports. In this article, we’ll only support English and Hindi as an example.

You can add as many arb files as the number of locales you want to support. For the time being, let’s make two separate arb files because we only support two locales in this example :

lib/l10n/app_en.arb

{
  "appTitle": "Demo App"
}

lib/l10n/app_hi.arb

{
  "appTitle": "डेमो ऐप"
}

Next, let’s add localization to MaterialApp:

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

void main() {
  runApp(const 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) {
    return MaterialApp(
      title: 'Flutter Demo',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo'),
    );
  }
}

AppLocalizations.localizationsDelegates is responsible for localizing our app. Localizations for Flutter widgets, Material and Cupertino, have already been provided by the Flutter team. For example, if you open DatePicker using showDatePicker(), you’ll find that the dialogue is already translated to the device’s locale without the need for a localization file. With AppLocalizations.supportedLocales, Flutter only rebuilds our app’s UI when a new locale is detected and added to the MaterialApp‘s supportedLocales.

To support localization in iOS, we’ll need to make the following changes to Info.plist:

<key>CFBundleLocalizations</key>
<array>
    <string>en</string>
    <string>hi</string>
</array>

Localization code generation

To use the translations from the ARB files that we added earlier, we need to generate Dart files alternate to the ARB files that can be imported wherever we want to use localization values. To generate these files, just start the app once you’re done with the configuration changes stated above. Once the code generation is finished, you should see the following files:

  • .dart_tool/flutter_gen/gen_l10n/app_localizations.dart
  • .dart_tool/flutter_gen/gen_l10n/app_localizations_en.dart
  • .dart_tool/flutter_gen/gen_l10n/app_localizations_hi.dart

Note: If localization code generation doesn’t happen automatically, running flutter gen-l10n will do the trick.

If you open app_localizations.dart, you’ll see that the file contains an abstract class AppLocalizations having localizationsDelegates and supportedLocales along with the locale values that you added in the ARB files. The other files app_localizations_en.dart and app_localizations_hi.dart contain classes that extend AppLocalizations with locale-specific variables and methods.



Let’s localize our app

In order to use the AppLocalizaions class, you’ll have to first import this class:

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

After that, you can access the locale value using AppLocalizations.of(context).appTitle. Our MaterialApp now looks like this:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: AppLocalizations.of(context).appTitle),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Padding(
              padding:  EdgeInsets.only(top: 32),
              child: Text(
                'Flutter is Awesome',
                style:  TextStyle(fontSize: 24),
              ),
            ),
            Expanded(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text('You have pushed this button :',
                      style: Theme.of(context).textTheme.headline6),
                  Text(
                    '$_counter times',
                    style: Theme.of(context).textTheme.headline4,
                  )
                ],
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Congratulations, our app’s title has now been successfully localized. We just need to update the other String values.

Interpolating words and phrases for internationalization

Interpolation is just the insertion of something into something else of a different kind. Interpolation is useful in localization when you want a word or text to be in the default language (e.g., English) for all supported locale. For example, in our demo app, we want to show a text saying “Flutter is awesome,” but the catch is that we want the word Flutter to be in English no matter what locale is being used on the user’s device.

Let’s add the interpolated sentence to our locale ARB files:

lib/l10n/app_en.arb:

{
  "appTitle": "Demo App",
  "appDescription": "{flutter} is Awesome",
  "@appDescription": {
    "placeholders": {
      "flutter": {
        "type": "String",
        "example": "Flutter"
      }
    }
  },
}

lib/l10n/app_hi.arb:

{
  "appTitle": "डेमो ऐप",
  "appDescription": "{flutter} बहुत बढ़िया है",
}

Now, we’ll have to consume the interpolated text description that we added to the ARB files. Since we’re going access AppLocalizations at multiple places, we’ll make an instance variable _locale and initialize it in didChangeDependencies():

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  late AppLocalizations _local;

  @override
  void didChangeDependencies() {
    _local = AppLocalizations.of(context);
    super.didChangeDependencies();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_local.appTitle),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(top: 32),
              child: Text(
                _local.appDescription('Flutter'),
                style: const TextStyle(fontSize: 24),
              ),
            ),
            Expanded(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text("You have pushed this button :",
                      style: Theme.of(context).textTheme.headline6),
                  Text(
                    "$_counter",
                    style: Theme.of(context).textTheme.headline4,
                  )
                ],
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: "Increment",
        child: const Icon(Icons.add),
      ),
    );
  }
}

Run the app and you’ll see that no matter what language your device is set to, the word “Flutter” will always be in English.

Singular and plural support

In localization, we often have to deal with singular and plurals. For example – “Congrats 🎉, you won a coupon,” or, “Congrats 🎉, you won two coupons.” It’s important to note that different languages handle plurals differently, and you’ll need to be cautious when working with plurals because you’ll need some understanding of the language you’re translating to.

Let’s make the required changes to support plurals in our localization files:

lib/l10n/app_en.arb:

{
  "appTitle": "Demo App",
  "appDescription": "{flutter} is Awesome",
  "@appDescription": {
    "placeholders": {
      "flutter": {
        "type": "String",
        "example": "Flutter"
      }
    }
  },
  "counterText": "You have pushed this button :",
  "counter": "{count,plural, =0{0} =1{1 time} other{{count} times}}",
  "@counter": {
    "placeholders": {
      "count": {
        "type": "int",
        "example": "count"
      }
    }
  },
  "counterButtonText": "Increment"
}

lib/l10n/app_hi.arb:

{
  "appTitle": "डेमो ऐप",
  "appDescription": "{flutter} बहुत बढ़िया है",
  "counterText": "आपने यह बटन दबा दिया है :",
  "counter": "{count,plural, =0{0} =1{1 बार} other{{count} बार}}",
  "counterButtonText": "जोड़ें"
}

Now that we’ve added plurals in the key counter for both of our ARB files and also added counterButtonText that will be used as tooltip for the increment button, we can write this:

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  late AppLocalizations _local;

  @override
  void didChangeDependencies() {
    _local = AppLocalizations.of(context);
    super.didChangeDependencies();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_local.appTitle),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(top: 32),
              child: Text(
                _local.appDescription('Flutter'),
                style: const TextStyle(fontSize: 24),
              ),
            ),
            Expanded(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(_local.counterText,
                      style: Theme.of(context).textTheme.headline6),
                  Text(
                    _local.counter(_counter),
                    style: Theme.of(context).textTheme.headline4,
                  )
                ],
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: _local.counterButtonText,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Notice how we’re passing the _counter value to the AppLocalizations.counter(), which will eventually check whether the value is singular or plural, and it will return a String value based on that.

Conclusion

In this tutorial, you learned how to localize your flutter app and make it more accessible to users. I hope you keep trying new things!

Now that we have everything cooked and ready, all you have to do is run the application and enjoy.


More great articles from LogRocket:


Good luck! Happy Fluttering!

If you have any questions, feel free to post them. Any feedback is welcome.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Shubham Soni Product Engineer @ threedots | Google India Scholar 2018 | Technical Author @ LogRocket

One Reply to “Internationalization for Flutter apps”

  1. Hi!
    I liked the article.
    Could you explain how to set the default locale programatically?
    Could you explain how I change locale programatically?

Leave a Reply