19
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.
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
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,
],
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);
}
}
class AppLocalizationsDelegate extends LocalizationsDelegate<Languages> {
const AppLocalizationsDelegate();
bool isSupported(Locale locale) =>
['es', 'en', 'de', 'it'].contains(locale.languageCode);
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();
}
}
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:
abstract class Languages {
static Languages of(BuildContext context) {
return Localizations.of<Languages>(context, Languages);
}
String get aString;
}
class LanguageIt extends Languages {
String get aString => "Sono un testo";
}
class LanguageEs extends Languages {
String get aString => "Soy un texto";
}
class LanguageEn extends Languages {
String get aString => "I am a text";
}
class LanguageDe extends Languages {
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.
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:
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.
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();
}
}
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();
}
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(),
),
);
},
);
},
),
),
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', ''));
},
),
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!
19