Internationalization Example In Flutter

In this tutorial, you will learn how to internationalize your app. This is necessary if you are planning to make your app available across multiple countries. Contrary to frameworks like native Android, Flutter doesn’t have an absolutely set way of how localization is done. It gives you a lot of freedom which can be good if you know what you’re doing, but also extremely confusing if you’re just starting out. In this tutorial, you’re going to learn how to localize your apps the simple way by using JSON files to store your strings in multiple languages.  For this purpose we will follow the next steps:

  1. Create a new Flutter App,
  2. Prepare the project,
  3. Update “pubspec.yaml” file,
  4. Localize material app,
  5. Working with the custom strings,
  6. Translate the UI.

1. Create a New Flutter Project

Go ahead and create a new Flutter app. If you don’t know how to create a project you can refer to the “Hello World App in Flutter” tutorial. For this tutorial, we will use the initial app that Flutter creates by default.

2. Prepare the project

Before writing Dart code to access the JSON files containing translated strings, you actually need to create the files first and update the “pubspec.yaml” file. We will create a new folder “language” in the project root. This folder will hold all of the language files. We will create two files – “en.json” for English and “es.json” for Spanish.

These files will contain only simple key-value pairs of strings. Using JSON is beneficial because it’s similar to XML, you can just give it to a translator without them needing to access your code.

Here is the code for en.json:

{
  "first_string": "Hello World!",
  "second_string": "I hope this tutorial helps you"
}

Here is the code for es.json:

{
 "first_string": "Hola Mundo!", 
"second_string": "Espero que este tutorial te ayude." 
}

3. Update “pubspec.yaml” file

Localization requires a dependency that comes directly from the Flutter SDK. Also, since we’ve just added new language JSON files, you need to specify them as assets in order to access them in code.

dependencies:
  flutter:
    sdk: flutter

  flutter_localizations:
    sdk: flutter

  assets:
   - language/en.json
   - language/es.json

4. Localize material app

Setup of the localization happens inside the “MaterialApp” widget, which is the root of a Flutter app. Here you specify which languages are supported and what happens if the current language of a device is not supported.

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

import 'app_localizations.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      
      supportedLocales: [
        Locale('en', 'US'),
        Locale('es', 'ES'),
      ],
     
      localizationsDelegates: [       
        AppLocalizations.delegate,
        // Built-in localization of basic text for Material widgets
        GlobalMaterialLocalizations.delegate,
        // Built-in localization for text direction LTR/RTL
        GlobalWidgetsLocalizations.delegate,
      ],
      // Returns a locale which will be used by the app
      localeResolutionCallback: (locale, supportedLocales) {
        // Check if the current device locale is supported
        for (var supportedLocale in supportedLocales) {
          if (supportedLocale.languageCode == locale.languageCode &&
              supportedLocale.countryCode == locale.countryCode) {
            return supportedLocale;
          }
        }
        // If the locale of the device is not supported, use the first one
        // from the list (English, in this case).
        return supportedLocales.first;
      },
      home: MyHomePage(),
    );
  }
}

One thing that may be difficult to grasp in the code above is the “localizationDelegates” list. A “LocalizationsDelegate” is an object which knows what to do when it’s time to load particular language.

Apart from using “AppLocalizations” which you haven’t yet created, you also specify some predefined material and widgets localizations.

Many widgets from the material package contain some text. For example, a material alert dialog can contain a “cancel” button. This button is there by default, you don’t need to write the text yourself. Flutter has these kinds of standard strings already localized, all you need to do is to specify the delegate.

5. Working with the custom strings

Similar to how there are “GlobalMaterialLocalizations”, you want to have localizations specific to your app, hence the class name “AppLocalizations”.  Its most important method will be the translate() method. You can then call the translate() method throughout your UI widgets to specify localized strings.

Create a new file “app_localizations.dart” under the lib folder. First, create a class “AppLocalizations” which will have two methods – load() for loading the JSON into an in-memory Map<String, String>,  and translate() for accessing the map of translated strings.

Here is the code for the file:

import 'dart:async';
import 'dart:convert';

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

class AppLocalizations {
  final Locale locale;

  AppLocalizations(this.locale);

  // Helper method to keep the code in the widgets concise
  // Localizations are accessed using an InheritedWidget "of" syntax
  static AppLocalizations of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations);
  }

  Map<String, String> _localizedStrings;

  Future<bool> load() async {
    // Load the language JSON file from the "language" folder
    String jsonString =
        await rootBundle.loadString('language/${locale.languageCode}.json');
    Map<String, dynamic> jsonMap = json.decode(jsonString);

    _localizedStrings = jsonMap.map((key, value) {
      return MapEntry(key, value.toString());
    });

    return true;
  }

  // This method will be called from every widget which needs a localized text
  String translate(String key) {
    return _localizedStrings[key];
  }
}

The class above is where the actual logic is located. However, you still need to provide a way for Flutter to step in and decide when the “load()” method will be called and which locale will be used.

Flutter localization uses delegates for this initialization. Create a new package-private class “_AppLocalizationsDelegate” which extends a “LocalizationsDelegate<AppLocalizations>”. It’s private because you don’t need to instantiate it inside any other file.

Instead, to access this delegate, make a new static field on “AppLocalizations” class:

import 'dart:async';
import 'dart:convert';

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

class AppLocalizations {
  ...
  // Static member to have a simple access to the delegate from the MaterialApp
  static const LocalizationsDelegate<AppLocalizations> delegate =
      _AppLocalizationsDelegate();
  ...
}

// LocalizationsDelegate is a factory for a set of localized resources
// In this case, the localized strings will be gotten in an AppLocalizations object
class _AppLocalizationsDelegate
    extends LocalizationsDelegate<AppLocalizations> {
  // This delegate instance will never change (it doesn't even have fields!)
  // It can provide a constant constructor.
  const _AppLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) {
    // Include all of your supported language codes here
    return ['en', 'sk'].contains(locale.languageCode);
  }

  @override
  Future<AppLocalizations> load(Locale locale) async {
    // AppLocalizations class is where the JSON loading actually runs
    AppLocalizations localizations = new AppLocalizations(locale);
    await localizations.load();
    return localizations;
  }

  @override
  bool shouldReload(_AppLocalizationsDelegate old) => false;
}

6. Translate the UI

Once you have all of this setup done just call the “of()” helper static method on “AppLocalizations()” to get the instance properly set up by Flutter and then translate your content using the keys you’ve specified in the JSON files.

Here is the “main.dart” code:

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // List all of the app's supported locales here
      supportedLocales: [
        Locale('en', 'US'),
        Locale('es', 'ES'),
      ],
      // These delegates make sure that the localization data for the proper language is loaded
      localizationsDelegates: [
        // THIS CLASS WILL BE ADDED LATER
        // A class which loads the translations from JSON files
        AppLocalizations.delegate,
        // Built-in localization of basic text for Material widgets
        GlobalMaterialLocalizations.delegate,
        // Built-in localization for text direction LTR/RTL
        GlobalWidgetsLocalizations.delegate,
      ],
      // Returns a locale which will be used by the app
      localeResolutionCallback: (locale, supportedLocales) {
        // Check if the current device locale is supported
        for (var supportedLocale in supportedLocales) {
          if (supportedLocale.languageCode == locale.languageCode &&
              supportedLocale.countryCode == locale.countryCode) {
            return supportedLocale;
          }
        }
        // If the locale of the device is not supported, use the first one
        // from the list (English, in this case).
        return supportedLocales.first;
      },
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Text(
                AppLocalizations.of(context).translate('first_string'),
                style: TextStyle(fontSize: 25),
                textAlign: TextAlign.center,
              ),
              SizedBox(height: 10),
              Text(
                AppLocalizations.of(context).translate('second_string'),
                style: TextStyle(fontSize: 25),
                textAlign: TextAlign.center,
              ),
              SizedBox(height: 10),
              Text(
                'This will not be translated.',
                style: TextStyle(fontSize: 25),
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

To test whether this internationalizing works on the device/emulator, try changing the device language from the settings.

Here is the output:

And here is what happens after changing the settings:

 

 

I hope this Flutter tutorial was helpful for you. If you are interested in learning Flutter, please check other Flutter tutorials on this site. Some of them have video tutorials included.

Happy learning Flutter!


Leave a Reply

Your email address will not be published. Required fields are marked *