Mastering Flutter: Multilanguage Support
Tags: dart, tutorial, beginners, flutter
Published: 2021-06-23 15:03:46

One thing Flutter doesen't let you do out of the box is **multilanguage support**. There are a few ways around this issue, today we'll see one of those. It's not an hard way but it gives you a good control over your output.

 

## 1. Yaml file additions

First of all we'll need to add a few lines to our .yaml file. We'll need to add two packages, the intl package and flutter_localizations:

```dart
dependencies:
  flutter:
    sdk: flutter

  #This lines are new
  flutter_localizations:
    sdk: flutter

  cupertino_icons: ^1.0.0
  
  #This line is new
  intl: ^0.17.0
```

 

## 2. The localizationsDelegates and localeResolutionCallback

The two most important parts of our configuration are the **localizationsDelegates** and the **localeResolutionCallback**, let's check the first. The `LocalizationsDelegates` are classes which define a series of localized values by loading them. On our main class, as parameters for the MaterialApp we need to set our delegates:

```dart
localizationsDelegates: [
        AppLocalizationsDelegate(),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
```

**AppLocalizationsDelegate** is our custom class that will manage localized data, where we'll need to set our `supportedLanguages` and which language to load. So we'll need to create also an abstract class called **Languages**, that will extend our subclassed for all our languages:

```dart
abstract class Languages {

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

 

```dart
class AppLocalizationsDelegate extends LocalizationsDelegate<Languages> {

  const AppLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) =>
      ['es', 'en', 'de', 'it'].contains(locale.languageCode);

  @override
  Future<Languages> load(Locale locale) => _load(locale);

  static Future<Languages> _load(Locale locale) async {
    switch (locale.languageCode) {
      case 'it':
        return LanguageIt();
      case 'es':
        return LanguageEs();
      case 'de':
        return LanguageDe();
      case 'en':
        return LanguageEn();
      default:
        return LanguageEn();
    }
  }

  @override
  bool shouldReload(LocalizationsDelegate<Languages> old) => false;
}
```

**LanguageIt/Es/De/En** are our classes with localized strings, but first we'll have to add a string to our abstract Languages, which will need us to add it also to our other files:

```dart
abstract class Languages {

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

  String get aString;
}


class LanguageIt extends Languages {

  @override
  String get aString => "Sono un testo";
}

class LanguageEs extends Languages {

  @override
  String get aString => "Soy un texto";
}

class LanguageEn extends Languages {

  @override
  String get aString => "I am a text";
}

class LanguageDe extends Languages {

  @override
  String get aString => "Ich bin ein Text";
}
```

 

Next step is to add a `localeResolutionCallback` to our `MaterialApp`. This will determine which language is the one our app should be displayed. Also we'll need to add the supportedLocales property, to let our `MaterialApp` know which ones are supported.

```dart
supportedLocales: [
        const Locale('it', ''),
        const Locale('en', ''),
        const Locale('de', ''),
        const Locale('es', '')
      ],
localeResolutionCallback: (locale, supportedLocales) {
        for (var supportedLocale in supportedLocales) {
          if (supportedLocale.languageCode == locale?.languageCode) {
            return supportedLocale;
          }
        }
        return supportedLocales.first;
      },
```

 

We are ok for now. To use localized strings then we'll need to call Languages with the string we added:

```dart
Languages.of(context).aString
```

The result will be

- "**Io sono un testo**" for Italian language
- "**Ich bin ein Text**" for German language
- "**Soy un texto**" for Spanish language
- "**I am a text**" for english language or a not supported language.

 

Most apps will be ok this way, the phone language will determine which locale is the default for our app, but we can add a bit more spice by making our app **change language on the fly**, but we'll need to add a bit more code.

## 

## 3. Switch on the go

To achieve our next goal we'll need to add a **state management package**, I choose **Provider** as, in my opinion is pretty easy to implement and understand. We'll need to create a `ChangeNotifier` which will contain the current locale and initialize it as the system one:

```dart
class LocalizationManager extends ChangeNotifier {
  static Locale get defaultLocale => Locale('it', 'IT');

  Locale _currentLocale;

  final locales = [
    const Locale('it', ''),
    const Locale('en', ''),
    const Locale('de', ''),
    const Locale('es', '')
  ];


  Future<Locale> locale(BuildContext context) async {
    if (_currentLocale != null) {
      return _currentLocale;
    }
    String platformLocaleName = Platform.localeName;
    if (platformLocaleName.contains('_')) {
      platformLocaleName = platformLocaleName.split('_').first;
    }
    if (platformLocaleName.contains('-')) {
      platformLocaleName = platformLocaleName.split('-').first;
    }
    if (platformLocaleName != null) {
      for (Locale configurationLocale in locales) {
        if (platformLocaleName.toLowerCase() ==
            configurationLocale.languageCode.toLowerCase()) {
          _currentLocale = configurationLocale;
          break;
        }
      }
    }
    if (_currentLocale == null) {
      _currentLocale = locales.first ?? defaultLocale;
    }
    return _currentLocale;
  }

  Future<void> setLocale(Locale locale) async {
    _currentLocale = locale;
    notifyListeners();
  }
}
```

 

Next, we have to create a setter for our new locale and call `notifyListeners()` in order to let our app know when to redraw itself:

```dart
  Future<void> setLocale(Locale locale) async {
    _currentLocale = locale;
    notifyListeners();
  }
```

 

Then we'll have to configure Provider on our main and get the locale from the Provider, getting it as a consumer. Be aware that the locale getter is a future, so we'll have to add a **FutureBuilder**:

```dart
MultiProvider(
      providers:[
        ChangeNotifierProvider<LocalizationManager>(create: (_) => LocalizationManager()),
      ],
      child: Consumer<LocalizationManager>(
        builder: (context, localizationManager, child) {
          return FutureBuilder<Locale>(
            future: localizationManager.locale(context),
            builder: (context, snapshot) {
              return MaterialApp(
                localizationsDelegates: [
                  AppLocalizationsDelegate(),
                  GlobalMaterialLocalizations.delegate,
                  GlobalWidgetsLocalizations.delegate,
                  GlobalCupertinoLocalizations.delegate,
                ],
                supportedLocales: [
                  const Locale('it', ''),
                  const Locale('en', ''),
                  const Locale('de', ''),
                  const Locale('es', '')
                ],
                locale: snapshot.data,
                localeResolutionCallback: (locale, supportedLocales) {
                  return locale;
                },
                home: Scaffold(
                  backgroundColor: Colors.black,
                  body: DemoApp(),
                ),
              );
            },
          );
        },
      ),
    ),
```

 

Last, but not least, let's add some buttons that will change our language:

```dart
CupertinoButton(
              child: Text('Ita'),
              onPressed: () {
                Provider.of<LocalizationManager>(context, listen: false).setLocale(Locale('it', ''));
              },
            ),
            CupertinoButton(
              child: Text('En'),
              onPressed: () {
                Provider.of<LocalizationManager>(context, listen: false).setLocale(Locale('en', ''));
              },
            ),
            CupertinoButton(
              child: Text('Es'),
              onPressed: () {
                Provider.of<LocalizationManager>(context, listen: false).setLocale(Locale('es', ''));
              },
            ),
            CupertinoButton(
              child: Text('De'),
              onPressed: () {
                Provider.of<LocalizationManager>(context, listen: false).setLocale(Locale('de', ''));
              },
            ),
```

 

## Conclusions

This is a pretty easy implementation, we can also add the locale as a shared preference so it is saved and language change can be permanent, but i'll leave to you any other implementation you need. Good work, we did it also today!