DEV Community

Cover image for Mastering Flutter: Multilanguage Support
TheOtherDev/s
TheOtherDev/s

Posted on

Mastering Flutter: Multilanguage Support

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

localizationsDelegates: [
        AppLocalizationsDelegate(),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
Enter fullscreen mode Exit fullscreen mode

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:

abstract class Languages {

  static Languages of(BuildContext context) {
    return Localizations.of<Languages>(context, Languages);
  }
}
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode

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:

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";
}
Enter fullscreen mode Exit fullscreen mode

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.

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;
      },
Enter fullscreen mode Exit fullscreen mode

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

Languages.of(context).aString
Enter fullscreen mode Exit fullscreen mode

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:

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

  Future<void> setLocale(Locale locale) async {
    _currentLocale = locale;
    notifyListeners();
  }
Enter fullscreen mode Exit fullscreen mode

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:

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(),
                ),
              );
            },
          );
        },
      ),
    ),
Enter fullscreen mode Exit fullscreen mode

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

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', ''));
              },
            ),
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)