Flutter Internationalization With Crowdin: Part 1


I am mainly making this guide to help other Flutter developers who are interested in setting up localization via Crowdin, since I could not find any guides myself. In this guide I will be going over why internationalization is necessary and how to set up and use easy_localization. In part 2 I will go over how to set up Crowdin which is a localizaiton management tool that makes it easier to manage your app's translations and allows for other translators to collaborate on your project.

Why setup internationalization?

When it comes to mobile apps, users expect your app to support their language right out the box. Although English is considered as the universal language of the world, many people barely know any English and therefore need translations in their own language. Implementing internationalization can make your app available to a wider audience, and can make your users feel more comfortable while using your app.

Project Setup

To get started, we first need to install the easy_localization package. Add the following to your pubspec.yaml file:

dependencies:
  flutter_localizations:
    sdk: flutter
  easy_localization: ^2.3.3+1
Once you have installed the easy_localization package, create a folder called i18n in your flutter project. Then add your translation files into the i18n folder. Here is an example of what your directory might look like

i18n
  โ””โ”€โ”€ {languageCode}-{countryCode}.{ext} // Translation files

After creating the i18n folder, add the i18n directory to your pubspec.yaml:

flutter
  assets:
    - i18n/

If you are planning on publishing this app to iOS, you must add the supported locales lines to your Info.plist file which should be located in ios/Runner/Info.plist

Example:

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

Adding easy_localization widget

Add the following import to your dart file:

import 'package:easy_localization/easy_localization.dart';

Go to your MaterialApp Widget and wrap it around with the EasyLocalization widget like so:

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:easy_localization/easy_localization.dart';

void main() {
    runApp(
        EasyLocalization(
            supportedLocales: [Locale('en', 'US'), Locale('es', 'ES')],
            path: 'i18n',
            fallbackLocale: Locale('en', 'US'),
            child: MyApp()
        ),
    );
}

class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return MaterialApp(
        localizationsDelegates: context.localizationDelegates,
        supportedLocales: context.supportedLocales,
        locale: context.locale,
        home: MyHomePage()
    );
    }
} 
            

You can add certain properties to customize your widget such as the startLocale, the fallbackLocale, and the savedLocale. More info can be found here: https://pub.dev/packages/easy_localization

By default, this package only supports JSON files so if you're interested in loading translations from CSV, Yaml, HTTP, FILE or XML files, continue to the next section and if not then skip to the section after.

Loading translations from other file types

In order for us to read translations from different file types, we can use the easy_localization_loader package.

To install it, add the package to your pubspec.yaml file:

dependencies:
  easy_localization_loader: ^0.0.2

Then add the import statement to your dart code:

import 'package:easy_localization_loader/easy_localization_loader.dart';

Once you've installed the package, we can easily start using it by adding the assetLoader property to our EasyLocalization widget. If you're using CSV files, then set the assetLoader property to CsvAssetLoader(), if you're using Yaml files, set the assetLoader property to YamlAssetLoader(). The same goes for other file types as well, here as an example showing the available loaders that come with the package.

void main() {
    runApp(
        EasyLocalization(
            supportedLocales: [Locale('en', 'US'), Locale('es', 'ES')],
            path: 'i18n',
            fallbackLocale: Locale('en', 'US'),
            child: MyApp()
            // assetLoader: RootBundleAssetLoader() // This is the default loader used by Flutter
            // assetLoader: HttpAssetLoader()
            // assetLoader: FileAssetLoader()
            // assetLoader: CsvAssetLoader()
            // assetLoader: YamlAssetLoader() // Multiple files
            // assetLoader: YamlSingleAssetLoader() // Single file
            // assetLoader: XmlAssetLoader() // Multiple files
            // assetLoader: XmlSingleAssetLoader() // Single file
            // assetLoader: CodegenLoader()
        ),
    );
}  
            

Using the package to translate text

Now that we've set up the package to work with our project, we can use the tr() function to translate our text. The example below shows the usage of the tr() function. The string 'title' should be replace with your language keys from the translation files.

Text('title').tr() // Text widget

print('title'.tr()); // String

var title = tr('title') // Static function

Here is a more detailed example using arguments in our translated text:

en-US.json file:

{
    "welcome.title": "Welcome",
    "welcome.subtitle": "Hello {}, how are you?"      
}
            

Dart Code

Text('welcome.title').tr(), // Without args

Text('welcome.subtitle').tr(args: ['John']); // With args. This would return: "Hello John, how are you?"

This package also supports namedArgs, pluralization and linked translations. Visit the document for more info abotu these topics.

Getting and settings our locale

Getting locales is quite easy. We can use the widget's BuildContent to get information about our locale:

print(context.locale) // Current locale; output: en_US

print(context.supportedLocales); // List of supported locales; output: [en_US, ar_DZ, de_DE, ru_RU]

print(context.fallbackLocale); // Fallbak locale; output: en_US

Setting our locale is also quite easy and can be done using the setLocale method which takes in a variable of type Locale. An example of setting our locale to Spanish is shown below.

context.setLocale(Locale('es', 'ES));

Using our setLocale method we can create a language setting method quite easily. In the example below I used a String variable to keep track of the selected language. Then I made a list of buttons of all the supported locales the app has. Once one of the buttons was pressed, I set the locale to the appripriate language. To get the native name and the flag of each language, I used a helper method called getLanguages which is why I needed a FutureBuilder. If you're not planning on doing this then you can remove the FutureBuilder from your code. An example of the code is also shown below:

...
@override
Widget build(BuildContext context) {
    String _selectedLang = context.locale.languageCode;
    return Scaffold(
    appBar: appBarWidget('global.appbar.language'.tr(), context),
    body: SafeArea(
        child: FutureBuilder>(
        future: Language.getLanguages(context.supportedLocales),
        builder: (BuildContext context, AsyncSnapshot res) {
            if (res == null || !res.hasData) {
            return Container();
            } else {
            return ListView.builder(
                padding: const EdgeInsets.all(8),
                itemCount: res.data.length,
                itemBuilder: (BuildContext context, int index) {
                Language lang = res.data[index];
                return Card(
                    color: lang.languageCode == _selectedLang
                        ? Theme.of(context).focusColor
                        : Theme.of(context).cardColor,
                    child: InkWell(
                    onTap: () {
                        context.setLocale(
                            Locale(lang.languageCode, lang.countryCode));
                    },
                    child: Padding(
                        padding: const EdgeInsets.all(18.0),
                        child: Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                            Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                                Text(
                                lang.nativeName,
                                style: CustomTextStyle.nativeLanguageName(
                                    context),
                                ),
                                Padding(
                                    padding: const EdgeInsets.symmetric(
                                        vertical: 3.0)),
                                Text(
                                lang.languageName,
                                style: CustomTextStyle.languageName(context),
                                ),
                            ],
                            ),
                            RichText(
                            text: TextSpan(
                                children: [
                                TextSpan(
                                    text: lang.emojiFlag,
                                ),
                                ],
                                style: TextStyle(fontSize: 25),
                            ),
                            ),
                        ],
                        ),
                    ),
                    ),
                );
                },
            );
            }
        },
        ),
    ),
    );
}
            

Next Up

Congratulations! You should now be able to switch between languages in your app. Be sure to check out the package's documentation as it has a lot more details about the package. Check out Part 2 to learn about how to set up Crowdin with you Flutter project.