Let's face it. When you're building an app which you plan to release to the public, having it in just one language isn't going to cut it. While English is definitely one of the most spoken languages throughout the world, internationalizing your apps is really a must.
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.
Prepare your 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 file.
Create the language files
Create a new folder lang in the project root. This folder will hold all of the language files. Because I'm Slovak, I've created two files - en.json and sk.json. If you want to follow along in a different language, feel free to do so, just remember to change any reference to 'sk' to your preferred language.
These files will contain only simple key-value pairs of strings. Using JSON is beneficial, because similar to XML, you can just give it to a translator without them needing to access your code.
en.json
{
"first_string": "Hello! This is the first message.",
"second_string": "If this tutorial helps you, give it a like and subscribe to Reso Coder "
}
sk.json
{
"first_string": "Ahoj! Toto je prvá správa.",
"second_string": "Ak ti tento tutoriál pomáha, daj mu like a prihlás sa na odber u Reso Codera "
}
Update pubspec.yaml
Localization requires a dependency which comes directly from the Flutter SDK. Also, since you've just added new language JSON files, you need to specify them as assets in order to access them in code.
pubspec.yaml
...
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
...
flutter:
...
assets:
- lang/en.json
- lang/sk.json
Make the MaterialApp localized
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. You also initialize localization objects (more on them later) here.
main.dart
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,
),
// List all of the app's supported locales here
supportedLocales: [
Locale('en', 'US'),
Locale('sk', 'SK'),
],
// 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(),
);
}
}
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 a 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.
Working with the custom localization 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 translate. You can then call 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.
app_localizations.dart
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 "lang" folder
String jsonString =
await rootBundle.loadString('lang/${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>. Its 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.
app_localizations.dart
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;
}
Translating text in the UI
Once you have all of this setup done, how do you actually use this translating contraption? It's simple! Just call the of() helper static method on AppLocalizations() to get the instance properly set up by Flutter and then translate to your heart's content using the keys you've specified in the JSON files!
main.dart
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,
),
],
),
),
),
);
}
}
Conclusion
With this approach, translating your Flutter apps will go like a breeze. Simply create a JSON file for every supported language and use AppLocalizations throughout your UI.
To test whether this internationalizing works on the device/emulator, try changing the device language from the settings.
Can you remake this with flutter localizations package and intl package (not Json file) by using flutter_bloc & Equatable for state management?
Certainly. I actually recently discovered a very cool way of translating apps. I’m not sure about the flutter_bloc part though… Do you really need a Bloc to manage the state of a simple translation demo app?
How can i acces AppLocalization iniside a Bloc without a context? For showing a translated Toast for example?
Blocs shouldn’t access anything UI-specific, including localization. Emit a state from the Bloc and react to it from the UI where you’ll also access the localizations.
Care to share your other “recently discovered cool way of translating apps”? is it better than this example of yours?
Check out this VS Code extension: https://github.com/esskar/vscode-flutter-i18n-json
hi, Matt! i decided to use vscode-flutter-i18n-json plugin in VSCode and i’m wondering how can i avoid using exact locale json files(e.g., en-US.json) and just have a single json for English language and ignore the country code?
It is really useful. Thanks.
How can I use the above approach to change the language on the profile screen on my app?
Thank you very much for this simple yet effective way to localize an application! Is there an easy way using your solution to translate the error texts in a TextFormField validator? As those methods are static and their signatures defined in Flutter classes, I don’t have a hunch on how to do this unless I define my own extensions of TextFormField, FormFieldState and FormState at least. This would let me add an argument to the validator method such as the context for example so that localization becomes possible. Do you have an idea to achieve this in an easier way?
Hello Matej,
I like your tutorials. I have been watching them since a couple of weeks.
In the isSupported() you should check the supportedLocales.keyset list to have the single-source-of-truth. What do you think?
Cheers,
Viktor
You are correct. I’d now use this VS Code extension which will do all this work for you.https://marketplace.visualstudio.com/items?itemName=esskar.vscode-flutter-i18n-json
Hi! Interesting complement, but how is it used from a service, for example, where it has no context?
Hi Ferando,
Did you get the answer for this
did you get the answer for that?
Avoid to call manually to:
initializeDateFormatting (from intl/date_symbol_data_local.dart)
It throws an weird error related to unsuported map operation..
Hi Alexis,
i am facing this issue when accessing the date
The getter ‘ZERODIGIT’ was called on null.
I/flutter (15356): Receiver: null
I/flutter (15356): Tried calling: ZERODIGIT
Can you help to sort it out
nice tutorial. i do have a question. can we use another language not listed among the 52 in flutter_localizations.
Can you share your sample?
I followed these steps but got this error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: Unable to load asset: lang/en.json
hi. i followed your tutorial but when i call AppLocalizations.of(context), it is always null. any idea?
Hello Reso,
Thank you for the wonderful content.
I am using bloc library in my project and I want to do in-app localization.
(the language change based on a click of a button)
How to accomplish this? I tried calling load() method from the delegate and it loads the json file but the ui doesn’t change. What I should do to make the ui change?
Hello @Matej Rešetár, In case I want to put some parameters in the localized string, how to do it ? For example:
{“hello”:”hello %@”}
did you y=got solution for this
Hello @Matej, thank you for making these amazing tuts. I’ve implemented it to my existing flutter_bloc app and for some reason the AppLocalizations.delegate is not working with my BlocListeners across the app… Any idea why it isn’t working?
Sorry for the dumb question.
I fixed it, had to update my flutter_bloc to ^3.0.0
Hi,
I’m trying to use nested keys but that’s not working. This is how I try to get:
AppLocalizations.of(context).translate(‘nested.key1.key2’)
Any ideas?
Thanks for your post.
How can I switch language manually?
Did you got solution for this
How Can I force a locale into it?
not working on ios
did you work it out?
you need to add this to info.plist in order to work in IOS
CFBundleLocalizations
en
ru
how would you add country code support. (and also using supportedLocales.keyset as single source of truth) ?
sorry never mind. wasnt that hard 🙂
The method ‘translate’ was called on null
Hi Matt, Can yo please let me know about getting a AppLocalizations without a context. also I am getting The getter ‘ZERODIGIT’ was called on null.
I/flutter (15356): Receiver: null
I/flutter (15356): Tried calling: ZERODIGIT
when accessing the date format
does anybody achieve localizations for enum
Hey Matt,
Can you apply localization for RichText widget as well? If yes.. How?
Rich Text widget contains TextSpan in it.
A quick reply is appreciated.
Thanks.
Hi Matt,
great tutorial. One part I find missing. On multi lingual systems, it is good to allow the user to change the language inside the app.
I found the missing piece here: https://stackoverflow.com/a/59263272/6805478
Cheers,
Adam
Hi Matt,
What’re your thoughts on https://github.com/localizely/flutter-intl-vscode ?
It’s closed source but looks on the right path for supporting the official intl package & l10i implementation.
Hi i follow this tutorial but after i have only a white screen on my app 🙁
Hello Matt, thank you for this tutorial, but I closely followed on every steps with the exact the same language en and sk, but somehow it’s not working. There is no error showing, but when I changed the phone language setting, the screen does not change. I have tested on Android and iOS phone, both are not working, Why? I have examined the code, it’s the same as yours, did you miss any steps? Is this method still working? Please update with me, thank you!
Hello! I am using a viewmodel implementation packages called stacked and stacked_services. Under the hood it provides a service to display dialogs and I was wondering how can I get a translation in the viewmodel (obviously I shouldn’t be using context here).
Did you found the solution?
Discover what Flutter localization and internationalization are, its purpose, differences, and examples.
Check https://axisbits.com/blog/introduction-flutter-localization-and-flutter-internationalization
Your article helped me a lot, is there any more related content? Thanks!
Can you be more specific about the content of your article? After reading it, I still have some doubts. Hope you can help me.
Your point of view caught my eye and was very interesting. Thanks. I have a question for you.