The recent release of Flutter 3, with its wonderful development experience and promise of a single codebase that works on many platforms, has become a Swiss Army knife for many iOS and Android apps, along with desktop and web apps slowly picking up the pace.
When you’re publishing an app that will be used all over the globe, having it in only one language doesn’t provide the same experience to every end user. While English is one of the most widely spoken languages, translating the app makes it more accessible and understandable to all users. For that reason, we’ll learn about localizing our Flutter apps in this article.
We’ll try to understand localization by using a counter app that comes in handy whenever you create a Flutter project, with some changes in the counterexample to demonstrate localization.
We’ll use the Flutter localizations package to successfully translate our app into another language, including interpolation for words and phrases and correct translation of singles and plurals.
pubspec.yaml
:
environment: sdk: ">=2.16.1 <3.0.0" dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter intl: ^0.17.0 cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^1.0.0 flutter: generate: true uses-material-design: true
Change your pubspec.yaml
file as indicated above. flutter localizations
includes a native localization package and intl
that allows for internationalization and localization, including message translation, plurals, and genders. The generate: true
line is essential for the localization packages’ automatic code generation and saves a lot of time.
Create a l10n.yaml
file to the root of our project; i.e., lib/l10n.yaml
. This file specifies the location of our translation files as well as the names of autogenerated Dart files:
arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart
By default, Flutter localization stores its translations in ARB (Application Resource Bundle) files. These are simply key-value paired files like JSON. Let’s start by creating an arb
file for our default English called app_en.arb
and put this in lib/l10n/app_en.arb
.
Since we’re supporting multiple locales, we’ll have to create a separate file for each of them that our app supports. In this article, we’ll only support English and Hindi as an example.
You can add as many arb
files as the number of locales you want to support. For the time being, let’s make two separate arb
files because we only support two locales in this example :
lib/l10n/app_en.arb
{ "appTitle": "Demo App" }
lib/l10n/app_hi.arb
{ "appTitle": "डेमो ऐप" }
Next, let’s add localization to MaterialApp
:
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo'), ); } }
AppLocalizations.localizationsDelegates
is responsible for localizing our app. Localizations for Flutter widgets, Material and Cupertino, have already been provided by the Flutter team. For example, if you open DatePicker
using showDatePicker()
, you’ll find that the dialogue is already translated to the device’s locale without the need for a localization file. With AppLocalizations.supportedLocales
, Flutter only rebuilds our app’s UI when a new locale is detected and added to the MaterialApp
‘s supportedLocales
.
To support localization in iOS, we’ll need to make the following changes to Info.plist
:
<key>CFBundleLocalizations</key> <array> <string>en</string> <string>hi</string> </array>
To use the translations from the ARB files that we added earlier, we need to generate Dart files alternate to the ARB files that can be imported wherever we want to use localization values. To generate these files, just start the app once you’re done with the configuration changes stated above. Once the code generation is finished, you should see the following files:
.dart_tool/flutter_gen/gen_l10n/app_localizations.dart
.dart_tool/flutter_gen/gen_l10n/app_localizations_en.dart
.dart_tool/flutter_gen/gen_l10n/app_localizations_hi.dart
Note: If localization code generation doesn’t happen automatically, running
flutter gen-l10n
will do the trick.
If you open app_localizations.dart
, you’ll see that the file contains an abstract class AppLocalizations
having localizationsDelegates
and supportedLocales
along with the locale values that you added in the ARB files. The other files app_localizations_en.dart
and app_localizations_hi.dart
contain classes that extend AppLocalizations
with locale-specific variables and methods.
In order to use the AppLocalizaions
class, you’ll have to first import this class:
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
After that, you can access the locale value using AppLocalizations.of(context).appTitle
. Our MaterialApp
now looks like this:
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: AppLocalizations.of(context).appTitle), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Padding( padding: EdgeInsets.only(top: 32), child: Text( 'Flutter is Awesome', style: TextStyle(fontSize: 24), ), ), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('You have pushed this button :', style: Theme.of(context).textTheme.headline6), Text( '$_counter times', style: Theme.of(context).textTheme.headline4, ) ], ), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
Congratulations, our app’s title has now been successfully localized. We just need to update the other String
values.
Interpolation is just the insertion of something into something else of a different kind. Interpolation is useful in localization when you want a word or text to be in the default language (e.g., English) for all supported locale. For example, in our demo app, we want to show a text saying “Flutter is awesome,” but the catch is that we want the word Flutter to be in English no matter what locale is being used on the user’s device.
Let’s add the interpolated sentence to our locale ARB files:
lib/l10n/app_en.arb
:
{ "appTitle": "Demo App", "appDescription": "{flutter} is Awesome", "@appDescription": { "placeholders": { "flutter": { "type": "String", "example": "Flutter" } } }, }
lib/l10n/app_hi.arb
:
{ "appTitle": "डेमो ऐप", "appDescription": "{flutter} बहुत बढ़िया है", }
Now, we’ll have to consume the interpolated text description that we added to the ARB files. Since we’re going access AppLocalizations
at multiple places, we’ll make an instance variable _locale
and initialize it in didChangeDependencies()
:
class _MyHomePageState extends State<MyHomePage> { int _counter = 0; late AppLocalizations _local; @override void didChangeDependencies() { _local = AppLocalizations.of(context); super.didChangeDependencies(); } void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_local.appTitle), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Padding( padding: const EdgeInsets.only(top: 32), child: Text( _local.appDescription('Flutter'), style: const TextStyle(fontSize: 24), ), ), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("You have pushed this button :", style: Theme.of(context).textTheme.headline6), Text( "$_counter", style: Theme.of(context).textTheme.headline4, ) ], ), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: "Increment", child: const Icon(Icons.add), ), ); } }
Run the app and you’ll see that no matter what language your device is set to, the word “Flutter” will always be in English.
In localization, we often have to deal with singular and plurals. For example – “Congrats 🎉, you won a coupon,” or, “Congrats 🎉, you won two coupons.” It’s important to note that different languages handle plurals differently, and you’ll need to be cautious when working with plurals because you’ll need some understanding of the language you’re translating to.
Let’s make the required changes to support plurals in our localization files:
lib/l10n/app_en.arb
:
{ "appTitle": "Demo App", "appDescription": "{flutter} is Awesome", "@appDescription": { "placeholders": { "flutter": { "type": "String", "example": "Flutter" } } }, "counterText": "You have pushed this button :", "counter": "{count,plural, =0{0} =1{1 time} other{{count} times}}", "@counter": { "placeholders": { "count": { "type": "int", "example": "count" } } }, "counterButtonText": "Increment" }
lib/l10n/app_hi.arb
:
{ "appTitle": "डेमो ऐप", "appDescription": "{flutter} बहुत बढ़िया है", "counterText": "आपने यह बटन दबा दिया है :", "counter": "{count,plural, =0{0} =1{1 बार} other{{count} बार}}", "counterButtonText": "जोड़ें" }
Now that we’ve added plurals in the key counter
for both of our ARB files and also added counterButtonText
that will be used as tooltip for the increment button, we can write this:
class MyHomePage extends StatefulWidget { const MyHomePage({Key? key}) : super(key: key); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; late AppLocalizations _local; @override void didChangeDependencies() { _local = AppLocalizations.of(context); super.didChangeDependencies(); } void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_local.appTitle), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Padding( padding: const EdgeInsets.only(top: 32), child: Text( _local.appDescription('Flutter'), style: const TextStyle(fontSize: 24), ), ), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(_local.counterText, style: Theme.of(context).textTheme.headline6), Text( _local.counter(_counter), style: Theme.of(context).textTheme.headline4, ) ], ), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: _local.counterButtonText, child: const Icon(Icons.add), ), ); } }
Notice how we’re passing the _counter
value to the AppLocalizations.counter()
, which will eventually check whether the value is singular or plural, and it will return a String
value based on that.
In this tutorial, you learned how to localize your flutter app and make it more accessible to users. I hope you keep trying new things!
Now that we have everything cooked and ready, all you have to do is run the application and enjoy.
Good luck! Happy Fluttering!
If you have any questions, feel free to post them. Any feedback is welcome.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
2 Replies to "Internationalization for Flutter apps"
Hi!
I liked the article.
Could you explain how to set the default locale programatically?
Could you explain how I change locale programatically?
can user change the language from app?