Flutter localization and internationalization (i18n) with examples

If you’re a mobile developer, you might already be familiar with Flutter, Google’s open-source UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.

Initially known as “Sky” and exclusive to Android, Flutter now supports iOS, Linux, Mac, Windows, and Google Fuchsia. An essential aspect of using Flutter is mastering Flutter localization to make your app accessible to a global audience.

Integrating software internationalization practices can enhance your Flutter localization process, ensuring your app meets the diverse needs of users across different regions.

A well-structured localization process ensures seamless integration of translations and cultural adaptations within the app, optimizing user experience across all markets.

In this article, you’ll learn how to easily translate your Flutter application into multiple languages, leveraging effective translation management techniques to streamline the process.

You might be also interested in checking out our Flutter SDK, which offers over-the-air support for your apps.

In this article, we’ll explore how to add Flutter i18n and l10n to your application. Before diving into the internationalization part, you’ll need to set up your working environment by following the official guide at docs.flutter.dev.

This tutorial assumes you have basic knowledge of Flutter. Once you have everything installed, create a new Flutter application by running:

EnlighterJS 3 Syntax Highlighter

flutter create i18n_demo

flutter create i18n_demo

We also need content to demonstrate the process of Flutter localization, so let’s do some more preparations.

Source code can be found on GitHub.

We’ll create most of the files inside the lib folder. Unless stated otherwise, you should work within that folder.

First, open lib and modify the main.dart file as follows:

EnlighterJS 3 Syntax Highlighter

import 'package:flutter/material.dart';

import 'package:i18n_demo/app/my_app.dart';

import 'package:flutter/material.dart'; import 'package:i18n_demo/app/my_app.dart'; void main() { runApp(const MyApp()); }

import 'package:flutter/material.dart'; import 'package:i18n_demo/app/my_app.dart';

void main() { runApp(const MyApp()); }

Next, inside, create an app folder with a my_app.dart file:

EnlighterJS 3 Syntax Highlighter

import 'package:flutter/material.dart';

import 'package:i18n_demo/app/pages/my_home_page.dart';

class MyApp extends StatelessWidget {

const MyApp({super.key});

Widget build(BuildContext context) {

colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),

home: const MyHomePage(),

import 'package:flutter/material.dart'; import 'package:i18n_demo/app/pages/my_home_page.dart'; class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter i18n', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(), ); } }

import 'package:flutter/material.dart'; import 'package:i18n_demo/app/pages/my_home_page.dart';

class MyApp extends StatelessWidget { const MyApp({super.key});

@override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter i18n', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(), ); } }

Finally, in the app folder, create a new pages directory with a my_home_page.dart file. Add the following classes:

EnlighterJS 3 Syntax Highlighter

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {

const MyHomePage({super.key});

State<MyHomePage> createState() => _MyHomePageState();

class _MyHomePageState extends State<MyHomePage> {

void _incrementCounter() {

Widget build(BuildContext context) {

backgroundColor: Theme.of(context).colorScheme.inversePrimary,

title: const Text("Welcome!"),

mainAxisAlignment: MainAxisAlignment.center,

const Text("Press the button below"),

"Times pressed: $_counter",

style: Theme.of(context).textTheme.headlineMedium,

floatingActionButton: FloatingActionButton(

onPressed: _incrementCounter,

child: const Icon(Icons.add),

import 'package:flutter/material.dart'; class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @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( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text("Welcome!"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text("Press the button below"), Text( "Times pressed: $_counter", style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget { const MyHomePage({super.key});

@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( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text("Welcome!"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text("Press the button below"), Text( "Times pressed: $_counter", style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }

This sets up a simple app with a button that increments a counter when pressed.

Now we are ready to proceed to the main part and implement software localization!

To start with localization, we need to add two dependencies: flutter_localizations and intl. These libraries handle most of the heavy lifting.

Run these commands:

EnlighterJS 3 Syntax Highlighter

flutter pub add flutter_localizations --sdk=flutter

flutter pub add flutter_localizations --sdk=flutter flutter pub add intl:any

flutter pub add flutter_localizations --sdk=flutter flutter pub add intl:any

After installing the dependencies, open the pubspec.yaml file in the project root. Find the flutter section and add the generate setting:

EnlighterJS 3 Syntax Highlighter

# The following section is specific to Flutter packages.

# The following section is specific to Flutter packages. flutter: generate: true

# The following section is specific to Flutter packages. flutter: generate: true

Next, in the project root, create a file called l10n.yaml with the following contents:

EnlighterJS 3 Syntax Highlighter

template-arb-file: intl_en.arb

output-localization-file: app_localizations.dart

arb-dir: lib/l10n template-arb-file: intl_en.arb output-localization-file: app_localizations.dart

arb-dir: lib/l10n template-arb-file: intl_en.arb output-localization-file: app_localizations.dart

This file specifies where our translation files will be located, what the template file is, and where the compiled translations should be placed. For more options, refer to the official docs.

That’s it! Now let’s take care of the translation files.

Translations for Flutter apps are usually stored in ARB files, which are similar to JSON files. These simple key-value files will be compiled into special Dart files used by the app.

Start by creating an l10n directory within the lib folder. Inside, create two files: intl_en.arb and intl_ru.arb to store English and Russian translations. You can choose any other locale as Flutter supports over a hundred languages.

Let’s provide the English text first:

EnlighterJS 3 Syntax Highlighter

"appTitle": "Flutter i18n",

"description": "Main application title"

{ "@@locale": "en", "appTitle": "Flutter i18n", "@appTitle": { "description": "Main application title" }, "welcome": "Welcome!" }

{ "@@locale": "en", "appTitle": "Flutter i18n", "@appTitle": { "description": "Main application title" }, "welcome": "Welcome!" }

A few things to note:

  • @@locale specifies the language of this file.
  • appTitle and welcome are the translation keys used in the source code. Their values are the actual translations.
  • @appTitle is metadata for the appTitle translation keys. While not mandatory, metadata provides context for translators.

Now, let’s tweak the intl_ru.yaml file for Russian translations:

EnlighterJS 3 Syntax Highlighter

"appTitle": "Flutter i18n",

"welcome": "Добро пожаловать!"

{ "@@locale": "ru", "appTitle": "Flutter i18n", "welcome": "Добро пожаловать!" }

{ "@@locale": "ru", "appTitle": "Flutter i18n", "welcome": "Добро пожаловать!" }

In this file, we typically don’t provide metadata but we still have to provide the country code.

Although you and your translators will work with ARB files, Flutter requires translations in a different format. Convert the ARBs into Dart files by running:

EnlighterJS 3 Syntax Highlighter

flutter gen-l10n

flutter gen-l10n

Rerun this command whenever you update your translations.

You’ll notice a new .dart_tool/flutter_gen/gen_l10n folder in the project root. It contains three files: app_localizations.dart, app_localizations_en.dart, and app_localizations_ru.dart. The first file stores information about all supported locales and exposes useful methods, while the other two store the translations.

Avoid modifying these files manually as they will be recompiled every time you run the flutter gen-l10n command.

All right, so now that we have two translation keys it’s time to use them in the source code.

First, I’d like to display the app title. Open the app/my_app.dart file and import the app_localizations.dart:

EnlighterJS 3 Syntax Highlighter

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

Next, provide information about the supported locales and configure delegates. Modify the file as follows:

EnlighterJS 3 Syntax Highlighter

class MyApp extends StatelessWidget {

const MyApp({super.key});

Widget build(BuildContext context) {

localizationsDelegates: AppLocalizations.localizationsDelegates, // <--- add this

supportedLocales: AppLocalizations.supportedLocales, // <--- add this

colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),

home: const MyHomePage(),

class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter i18n', localizationsDelegates: AppLocalizations.localizationsDelegates, // <--- add this supportedLocales: AppLocalizations.supportedLocales, // <--- add this theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(), ); } }

class MyApp extends StatelessWidget { const MyApp({super.key});

@override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter i18n', localizationsDelegates: AppLocalizations.localizationsDelegates, // <--- add this supportedLocales: AppLocalizations.supportedLocales, // <--- add this theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(), ); } }

Finally, replace the title with the onGenerateTitle:

EnlighterJS 3 Syntax Highlighter

class MyApp extends StatelessWidget {

Widget build(BuildContext context) {

onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, // <---

class MyApp extends StatelessWidget { // ... Widget build(BuildContext context) { return MaterialApp( onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, // <--- // ... ); } }

class MyApp extends StatelessWidget { // ...

Widget build(BuildContext context) { return MaterialApp( onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, // <--- // ... ); } }

This code sample shows how to use translation keys in your app by referencing AppLocalizations class and calling the property named after your translation key (appTitle in this case).

For another example, open the app/pages/my_home_page.dart file and add this import:

EnlighterJS 3 Syntax Highlighter

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

Then, tweak your widget by adjusting its title and taking advantage of the AppLocalizations class:

EnlighterJS 3 Syntax Highlighter

class _MyHomePageState extends State<MyHomePage> {

void _incrementCounter() {

Widget build(BuildContext context) {

backgroundColor: Theme.of(context).colorScheme.inversePrimary,

title: Text(AppLocalizations.of(context)!.welcome), // <--- add this

class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { // ... } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(AppLocalizations.of(context)!.welcome), // <--- add this ), body: Center( // ... ), // ... ); } }

class _MyHomePageState extends State<MyHomePage> { int _counter = 0;

void _incrementCounter() { // ... }

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(AppLocalizations.of(context)!.welcome), // <--- add this ), body: Center( // ... ), // ... ); } }

In this case, we don’t need to use onGenerateTitle (it’s only needed for the global title).

That’s it! You can now run your Flutter app on a mobile phone or emulator to ensure everything runs smoothly. If you change the system locale in the phone settings, the app will be translated accordingly.

Inserting custom text

To enhance your Flutter localization, you might need to interpolate custom values within your translations.

Let’s say we want to display copyright information with the company name. Update the intl_en.arb file in the lib/l10n directory:

EnlighterJS 3 Syntax Highlighter

"createdBy": "Tutorial by {company}",

"description": "Copyright message",

{ "@@locale": "en", // ... "createdBy": "Tutorial by {company}", "@createdBy": { "description": "Copyright message", "placeholders": { "company": { "type": "String", "example": "Demo" } } } }

{ "@@locale": "en",

// ...

"createdBy": "Tutorial by {company}", "@createdBy": { "description": "Copyright message", "placeholders": { "company": { "type": "String", "example": "Demo" } } } }

Here, {company} is a placeholder for which we can provide a value in the source code. The metadata explains the expected type and gives an example.

Now, update the intl_ru.arb file for Russian:

EnlighterJS 3 Syntax Highlighter

"createdBy": "Руководство от {company}"

{ "@@locale": "ru", // ... "createdBy": "Руководство от {company}" }

{ "@@locale": "ru",

// ...

"createdBy": "Руководство от {company}" }

The placeholder name must remain the same, but the translation text can be modified as needed.

Remember to run the flutter gen-l10n command.

Next, open app/pages/my_home_page.dart and add a new text widget:

EnlighterJS 3 Syntax Highlighter

class _MyHomePageState extends State<MyHomePage> {

Widget build(BuildContext context) {

Text(AppLocalizations.of(context)!.createdBy('Lokalise')), // <--- add this

const Text("Press the button below"),

class _MyHomePageState extends State<MyHomePage> { // ... Widget build(BuildContext context) { return Scaffold( // ... body: Center( child: Column( // ... children: <Widget>[ Text(AppLocalizations.of(context)!.createdBy('Lokalise')), // <--- add this const Text("Press the button below"), // ... ], ), ), // ... ); } }

class _MyHomePageState extends State<MyHomePage> { // ...

Widget build(BuildContext context) { return Scaffold( // ... body: Center( child: Column( // ... children: <Widget>[ Text(AppLocalizations.of(context)!.createdBy('Lokalise')), // <--- add this const Text("Press the button below"), // ... ], ), ), // ... ); } }

Here, we provide the company name as an argument. The createdBy method looks like this:

EnlighterJS 3 Syntax Highlighter

String createdBy(String company) {

return 'Tutorial by $company';

String createdBy(String company) { return 'Tutorial by $company'; }

String createdBy(String company) { return 'Tutorial by $company'; }

So, it’s nothing too complex, really.

We can use interpolation to display the currently set locale.

First, create a new current_locale_widget.dart in the app/widgets folder. Add localizations widget:

EnlighterJS 3 Syntax Highlighter

import 'package:flutter/material.dart';

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class CurrentLocaleWidget extends StatelessWidget {

const CurrentLocaleWidget({super.key});

Widget build(BuildContext context) {

final locale = Localizations.localeOf(context);

AppLocalizations.of(context)!.currentLocale(locale.toString()),

style: Theme.of(context).textTheme.headlineMedium,

import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class CurrentLocaleWidget extends StatelessWidget { const CurrentLocaleWidget({super.key}); @override Widget build(BuildContext context) { final locale = Localizations.localeOf(context); return Center( child: Text( AppLocalizations.of(context)!.currentLocale(locale.toString()), style: Theme.of(context).textTheme.headlineMedium, )); } }

import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class CurrentLocaleWidget extends StatelessWidget { const CurrentLocaleWidget({super.key});

@override Widget build(BuildContext context) { final locale = Localizations.localeOf(context);

return Center(
    child: Text(
  AppLocalizations.of(context)!.currentLocale(locale.toString()),
  style: Theme.of(context).textTheme.headlineMedium,
));

} }

Fetch the currently set locale using the localeOf method and display it using the currentLocale method.

Next, import and display this widget in my_home_page.dart:

EnlighterJS 3 Syntax Highlighter

import 'package:i18n_demo/app/widgets/current_locale_widget.dart'; // <--- add this

class _MyHomePageState extends State<MyHomePage> {

Widget build(BuildContext context) {

mainAxisAlignment: MainAxisAlignment.center,

const CurrentLocaleWidget(), // <--- add this

Text(AppLocalizations.of(context)!.createdBy('Lokalise')),

// ... import 'package:i18n_demo/app/widgets/current_locale_widget.dart'; // <--- add this // ... class _MyHomePageState extends State<MyHomePage> { // ... Widget build(BuildContext context) { return Scaffold( // ... body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const CurrentLocaleWidget(), // <--- add this Text(AppLocalizations.of(context)!.createdBy('Lokalise')), // ... ], ), ), // ... ); } }

// ... import 'package:i18n_demo/app/widgets/current_locale_widget.dart'; // <--- add this

// ...

class _MyHomePageState extends State<MyHomePage> { // ...

Widget build(BuildContext context) { return Scaffold( // ... body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const CurrentLocaleWidget(), // <--- add this Text(AppLocalizations.of(context)!.createdBy('Lokalise')), // ... ], ), ), // ... ); } }

Finally, add English translation in the intl_en.arb file:

EnlighterJS 3 Syntax Highlighter

"currentLocale": "Current locale is {locale}"

{ // ... "currentLocale": "Current locale is {locale}" }

{ // ...

"currentLocale": "Current locale is {locale}" }

And the Russian one in the intl_ru.arb file:

EnlighterJS 3 Syntax Highlighter

"currentLocale": "Текущая локаль: {locale}"

{ // ... "currentLocale": "Текущая локаль: {locale}" }

{ // ...

"currentLocale": "Текущая локаль: {locale}" }

Great job! You’ve successfully used interpolation and placeholders in Flutter localization.

Let’s move on to localizing date and time in Flutter.

First, add a new Text widget before or after the const CurrentLocaleWidget() in the my_home_page.dart file:

EnlighterJS 3 Syntax Highlighter

Text(AppLocalizations.of(context)!.currentDate(DateTime.now())), // <--- add this

Text(AppLocalizations.of(context)!.pressButton),

// ... other text widgets here...

/ ... children: <Widget>[ const LanguageWidget(), Text(AppLocalizations.of(context)!.currentDate(DateTime.now())), // <--- add this Text(AppLocalizations.of(context)!.pressButton), // ... other text widgets here... ], // ...

/ ...

children: <Widget>[ const LanguageWidget(), Text(AppLocalizations.of(context)!.currentDate(DateTime.now())), // <--- add this Text(AppLocalizations.of(context)!.pressButton), // ... other text widgets here... ],

// ...

We are displaying the current date and time. Different formats can represent this information, and the DateFormat class from the intl package helps us choose the appropriate format.

To display the current year, month, weekday, and day, update the intl_en.arb file by adding localized string:

EnlighterJS 3 Syntax Highlighter

"currentDate": "Today is {date}",

"description": "Localized current date",

{ // ... "currentDate": "Today is {date}", "@currentDate": { "description": "Localized current date", "placeholders": { "date": { "type": "DateTime", "format": "yMMMMEEEEd" } } } }

{ // ...

"currentDate": "Today is {date}", "@currentDate": { "description": "Localized current date", "placeholders": { "date": { "type": "DateTime", "format": "yMMMMEEEEd" } } } }

The yMMMMEEEEd format is predefined in the DateFormat class. Note that it must be provided in the metadata.

For the intl_ru.arb file, you can omit the metadata:

EnlighterJS 3 Syntax Highlighter

"currentDate": "Сегодня {date}"

{ // ... "currentDate": "Сегодня {date}" }

{ // ...

"currentDate": "Сегодня {date}" }

That’s it!

Next, we’ll localize number and currency information.

Add another Text widget in the my_home_page.dart file:

EnlighterJS 3 Syntax Highlighter

Text(AppLocalizations.of(context)!.currentDate(DateTime.now())),

Text(AppLocalizations.of(context)!.currencyDemo(1234567)), // <--- add this

// ... other text widgets here...

// ... children: <Widget>[ const LanguageWidget(), Text(AppLocalizations.of(context)!.currentDate(DateTime.now())), Text(AppLocalizations.of(context)!.currencyDemo(1234567)), // <--- add this // ... other text widgets here... ], // ...

// ...

children: <Widget>[ const LanguageWidget(), Text(AppLocalizations.of(context)!.currentDate(DateTime.now())), Text(AppLocalizations.of(context)!.currencyDemo(1234567)), // <--- add this // ... other text widgets here... ],

// ...

Flutter provides predefined number formats, which you can find in the NumberFormat class documentation.

Update the intl_en.arb file:

EnlighterJS 3 Syntax Highlighter

"currencyDemo": "Here's a demo price: {value}",

"description": "A demo showing how to localize currency",

{ // ... "currencyDemo": "Here's a demo price: {value}", "@currencyDemo": { "description": "A demo showing how to localize currency", "placeholders": { "value": { "type": "int", "format": "currency", "optionalParameters": { "decimalDigits": 2 } } } } }

{ // ...

"currencyDemo": "Here's a demo price: {value}", "@currencyDemo": { "description": "A demo showing how to localize currency", "placeholders": { "value": { "type": "int", "format": "currency", "optionalParameters": { "decimalDigits": 2 } } } } }

This example uses the currency format, which follows the pattern <CURRENCY_NAME><FORMATTED_NUMBER>. For the English locale, the default currency is USD, while for Russian, it’s RUB. We also specify two decimal digits.

Update the intl_ru.arb file:

EnlighterJS 3 Syntax Highlighter

"currencyDemo": "Демонстрационная цена: {value}"

{ // ... "currencyDemo": "Демонстрационная цена: {value}" }

{ // ...

"currencyDemo": "Демонстрационная цена: {value}" }

Great — our currency information is properly localized!

Pluralization adjusts the text based on a count. We have a button that increments a counter, so let’s adjust the displayed text according to the counter’s value.

First, update the Text widgets in the my_home_page.dart file:

EnlighterJS 3 Syntax Highlighter

const CurrentLocaleWidget(),

Text(AppLocalizations.of(context)!.pressButton), // <-- change this

AppLocalizations.of(context)!.buttonPressed(_counter), // <-- change this

style: Theme.of(context).textTheme.headlineMedium,

children: <Widget>[ const CurrentLocaleWidget(), // ... Text(AppLocalizations.of(context)!.pressButton), // <-- change this Text( AppLocalizations.of(context)!.buttonPressed(_counter), // <-- change this style: Theme.of(context).textTheme.headlineMedium, ), ],

children: <Widget>[ const CurrentLocaleWidget(),

// ...

Text(AppLocalizations.of(context)!.pressButton), // <-- change this

Text( AppLocalizations.of(context)!.buttonPressed(_counter), // <-- change this style: Theme.of(context).textTheme.headlineMedium, ), ],

Then, update the intl_en.arb file:

EnlighterJS 3 Syntax Highlighter

"pressButton": "Press the button below",

"buttonPressed": "{count, plural, =0{Has not pressed yet} =1{Pressed 1 time} other{Pressed {count} times}}",

"description": "Shows how many times the button has been pressed (pluralized)",

{ "pressButton": "Press the button below", "buttonPressed": "{count, plural, =0{Has not pressed yet} =1{Pressed 1 time} other{Pressed {count} times}}", "@buttonPressed": { "description": "Shows how many times the button has been pressed (pluralized)", "placeholders": { "count": { "type": "num", "format": "compact" } } } }

{ "pressButton": "Press the button below", "buttonPressed": "{count, plural, =0{Has not pressed yet} =1{Pressed 1 time} other{Pressed {count} times}}", "@buttonPressed": { "description": "Shows how many times the button has been pressed (pluralized)", "placeholders": { "count": { "type": "num", "format": "compact" } } } }

The buttonPressed key uses a plural expression to display different texts based on the count value. There are three cases: when count equals zero, one, or any other value.

Flutter also supports the select expression for handling gender information:

EnlighterJS 3 Syntax Highlighter

"pronoun": "{gender, select, male{he} female{she} other{they}}",

"description": "A gendered message",

"pronoun": "{gender, select, male{he} female{she} other{they}}", "@pronoun": { "description": "A gendered message", "placeholders": { "gender": { "type": "String" } } }

"pronoun": "{gender, select, male{he} female{she} other{they}}", "@pronoun": { "description": "A gendered message", "placeholders": { "gender": { "type": "String" } } }

Finally, let’s add the translations within the intl_ru.arb file:

EnlighterJS 3 Syntax Highlighter

"pressButton": "Нажмите кнопку ниже",

"buttonPressed": "{count, plural, =0{Не было нажатий} =1{Нажата 1 раз} few{Нажата {count} раза} other{Нажата {count} раз}}"

{ // ... "pressButton": "Нажмите кнопку ниже", "buttonPressed": "{count, plural, =0{Не было нажатий} =1{Нажата 1 раз} few{Нажата {count} раза} other{Нажата {count} раз}}" }

{ // ...

"pressButton": "Нажмите кнопку ниже", "buttonPressed": "{count, plural, =0{Не было нажатий} =1{Нажата 1 раз} few{Нажата {count} раза} other{Нажата {count} раз}}" }

Different languages have different pluralization rules. In this case, Russian requires four options (plural forms).

That’s it! You’ve now translated your app, and you can rerun it to ensure everything works smoothly.

While the app language can be changed by switching the system locale, this isn’t always convenient. Let’s see a simple way to add a locale switcher to your Flutter app.

First, we’ll need to add a new dependency called provider that will help us manage state. Run the following command:

EnlighterJS 3 Syntax Highlighter

flutter pub add provider

flutter pub add provider

Next, create a locale_provider.dart file inside the app/providers directory:

EnlighterJS 3 Syntax Highlighter

import 'package:flutter/material.dart';

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LocaleProvider extends ChangeNotifier {

Locale _locale = const Locale("en");

Locale get locale => _locale;

void setLocale(Locale locale) {

if (!AppLocalizations.supportedLocales.contains(locale)) return;

import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class LocaleProvider extends ChangeNotifier { Locale _locale = const Locale("en"); Locale get locale => _locale; void setLocale(Locale locale) { if (!AppLocalizations.supportedLocales.contains(locale)) return; _locale = locale; notifyListeners(); } }

import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LocaleProvider extends ChangeNotifier { Locale _locale = const Locale("en");

Locale get locale => _locale;

void setLocale(Locale locale) { if (!AppLocalizations.supportedLocales.contains(locale)) return;

\_locale = locale;
notifyListeners();

} }

A few key points:

  • The default locale is set to en.
  • The setLocale method checks if the requested locale is supported, then switches it and notifies listeners.

Next, use this provider in the my_app.dart file. Add these imports:

EnlighterJS 3 Syntax Highlighter

import 'package:i18n_demo/app/providers/locale_provider.dart';

import 'package:provider/provider.dart';

import 'package:i18n_demo/app/providers/locale_provider.dart'; import 'package:provider/provider.dart';

import 'package:i18n_demo/app/providers/locale_provider.dart'; import 'package:provider/provider.dart';

Wrap the main part of the code in the ChangeNotifierProvider, updating your file to:

EnlighterJS 3 Syntax Highlighter

class MyApp extends StatelessWidget {

const MyApp({super.key});

Widget build(BuildContext context) => ChangeNotifierProvider( // <--- add this

create: (context) => LocaleProvider(), // <--- add this

builder: (context, child) { // <--- add this

final provider = Provider.of<LocaleProvider>(context); // <--- add this

localizationsDelegates: AppLocalizations.localizationsDelegates,

supportedLocales: AppLocalizations.supportedLocales,

locale: provider.locale, // <--- add this

onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,

colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),

home: const MyHomePage(),

class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) => ChangeNotifierProvider( // <--- add this create: (context) => LocaleProvider(), // <--- add this builder: (context, child) { // <--- add this final provider = Provider.of<LocaleProvider>(context); // <--- add this return MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, locale: provider.locale, // <--- add this onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(), ); }); }

class MyApp extends StatelessWidget { const MyApp({super.key});

@override Widget build(BuildContext context) => ChangeNotifierProvider( // <--- add this create: (context) => LocaleProvider(), // <--- add this builder: (context, child) { // <--- add this final provider = Provider.of<LocaleProvider>(context); // <--- add this

    return MaterialApp(
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      locale: provider.locale, // <--- add this
      onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  });

}

Here, we’re providing the locale and reading the current locale from the provider.

Now, create a locale_switcher_widget.dart file in the app/widgets folder. Add these imports:

EnlighterJS 3 Syntax Highlighter

import 'package:flutter/material.dart';

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:i18n_demo/app/providers/locale_provider.dart';

import 'package:provider/provider.dart';

import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:i18n_demo/app/providers/locale_provider.dart'; import 'package:provider/provider.dart';

import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:i18n_demo/app/providers/locale_provider.dart'; import 'package:provider/provider.dart';

Then, code the widget itself:

EnlighterJS 3 Syntax Highlighter

class LocaleSwitcherWidget extends StatelessWidget {

const LocaleSwitcherWidget({super.key});

Widget build(BuildContext context) {

final provider = Provider.of<LocaleProvider>(context);

final locale = provider.locale;

return DropdownButtonHideUnderline(

icon: Container(width: 12),

items: AppLocalizations.supportedLocales.map(

Provider.of<LocaleProvider>(context, listen: false);

provider.setLocale(nextLocale);

child: Text(nextLocale.toString()),

class LocaleSwitcherWidget extends StatelessWidget { const LocaleSwitcherWidget({super.key}); @override Widget build(BuildContext context) { final provider = Provider.of<LocaleProvider>(context); final locale = provider.locale; return DropdownButtonHideUnderline( child: DropdownButton( value: locale, icon: Container(width: 12), items: AppLocalizations.supportedLocales.map( (nextLocale) { return DropdownMenuItem( value: nextLocale, onTap: () { final provider = Provider.of<LocaleProvider>(context, listen: false); provider.setLocale(nextLocale); }, child: Center( child: Text(nextLocale.toString()), ), ); }, ).toList(), onChanged: (_) {}, ), ); } }

class LocaleSwitcherWidget extends StatelessWidget { const LocaleSwitcherWidget({super.key});

@override Widget build(BuildContext context) { final provider = Provider.of<LocaleProvider>(context); final locale = provider.locale;

return DropdownButtonHideUnderline(
  child: DropdownButton(
    value: locale,
    icon: Container(width: 12),
    items: AppLocalizations.supportedLocales.map(
      (nextLocale) {
        return DropdownMenuItem(
          value: nextLocale,
          onTap: () {
            final provider =
                Provider.of<LocaleProvider>(context, listen: false);

            provider.setLocale(nextLocale);
          },
          child: Center(
            child: Text(nextLocale.toString()),
          ),
        );
      },
    ).toList(),
    onChanged: (\_) {},
  ),
);

} }

This widget reads the currently set locale and displays a dropdown menu. It lists all supported locales and includes an onTap event that calls the setLocale method in our provider.

Import the locale_switcher_widget.dart in the my_home_page.dart file:

EnlighterJS 3 Syntax Highlighter

import 'package:i18n_demo/app/widgets/locale_switcher_widget.dart';

import 'package:i18n_demo/app/widgets/locale_switcher_widget.dart';

import 'package:i18n_demo/app/widgets/locale_switcher_widget.dart';

Then, add the locale switcher to the AppBar actions:

EnlighterJS 3 Syntax Highlighter

class _MyHomePageState extends State<MyHomePage> {

Widget build(BuildContext context) {

actions: const [ // <--- add this

class _MyHomePageState extends State<MyHomePage> { // ... Widget build(BuildContext context) { return Scaffold( appBar: AppBar( // ... actions: const [ // <--- add this LocaleSwitcherWidget(), SizedBox(width: 12), ], ), body: Center( // ... ), // ... ); } }

class _MyHomePageState extends State<MyHomePage> { // ...

Widget build(BuildContext context) { return Scaffold( appBar: AppBar( // ... actions: const [ // <--- add this LocaleSwitcherWidget(), SizedBox(width: 12), ], ), body: Center( // ... ), // ... ); } }

Awesome! Now reload the app and try switching locales using the dropdown in the top menu.

Managing translation files can be tedious, especially for larger applications. Additionally, you might need to support languages you don’t speak. You can hire a professional or use AI. Meet Lokalise, a translation management system that simplifies Flutter localization and supports ARB files fully. With features like:

  • Easy integration with services (GitHub, Figma, Asana, and more)
  • Collaborative translations
  • Translation quality assurance tools
  • Centralized dashboard for managing translations
  • Plus, loads of others

Lokalise makes it easy to expand your Flutter app to various locales and enhances your localization workflow.

To get started, sign up for a free trial (no credit card information required). In your projects dashboard, click the New project button:

In the dialog, choose Web and mobile:

Choose Web and mobile project type on Lokalise to get started with Flutter localization

Next, give your project a name, choose English (or another default language) as the Base language, and choose one or more Target languages (the ones to translate into):

During project creation, choose the languages for Flutter i18n

Let’s suppose that I would now like to translate into French in addition to Russian.

Then hit Create project.

On the next screen, click Upload files:

Upload translation files for Flutter i18n

Choose the ARB files from your project (don’t upload Dart files). You can upload intl_en.arb unless you need to adjust Russian translations.

Check the Detect ICU plurals option if you use pluralization in your ARB files (because the plural is actually an ICU expression):

Enable Detect ICU plurals if you're using ICU in your Flutter translations

The main part of the screen will look like this:

The chosen ARB files for Flutter internationalization will be displayed on the Upload page

To manage filenames better, add a %LANG_ISO% placeholder. Click the filename and provide this placeholder, which will be replaced with the locale code upon export.

Add language ISO placeholders into your Flutter ARB files to enhance localization workflow

Adding this placeholder is beneficial because it will be replaced with the actual locale code once the file is exported back to the project. Otherwise, all the translation files will have identical names.

Press “Import files” when ready.

Next, click “Editor” in the top menu to view your translations:

Manage your Flutter translation files in the project editor

You might notice that the buttonPressed key lacks the zero plural form because it’s not standard for English, which only has one and other.

For additional forms, click the languages dropdown in the top menu, select the desired locale, and go to Settings:

Adjust language settings on Lokalise easily

Enable the Custom plural forms switch and adjust as needed:

Choose custom plural forms for the project language

Re-upload the translation file, enabling Detect ICU plurals and Replace modified values options.

You can manually edit these texts or use Lokalise AI for translations.

So, let’s employ Lokalise AI to translate into French. Select all your translation keys by checking the box in the top left corner:

Choose all your Flutter translations on Lokalise

Select Create a task from the dialog and hit Proceed:

Choose Create task to translate your Flutter strings with the AI

Pick the Automatic translation task type, name it, and optionally provide additional instructions for the AI:

Choose Automatic translation task type and provide instructions for the AI

Click Next.

Choose the Source language (English in my case) and one or more Target languages to translate into:

Adjust task scope, choose the languages to translate your Flutter strings into

Press Create task and wait for the AI to complete the translations. You’ll receive a notification email once done.

Return to the editor to review the translations:

Checks your Flutter translations performed by the AI

Note that the AI correctly handles plural forms. However, double-check all AI-generated translations for accuracy.

Once ready, download your translations back to the Flutter project.

Click Download in the top menu and choose Flutter (.arb) from the File format dropdown:

Choose Flutter ARB file format to download

Avoid picking Flutter SDK as it relates to OTA, which we’ll briefly discuss soon.

Now pick one or more languages to include in the download bundle. Since I have only amended the French translations, I’ll choose one language:

Choose one or more languages to download into your Flutter i18n project

Then click Build and download:

Extract the archive into the l10n folder of the project and run the flutter gen-l10n command again.

Run the app to ensure it supports three languages. How cool is that?

Lokalise also offers a feature called over the air (OTA). It allows you to deliver updated translations to users without releasing a new app version. Sounds like magic? Well, it’s pretty close, and you can easily set it up in your Flutter project.

Check out the OTA tutorial for full details on utilizing this feature.

So, in this article we have learned how to translate Flutter apps into multiple languages, how to perform pluralization and localization, and how to programmatically set the locale. On top of that, we have utilized AI to translate our app into a new language. Great job!

You may also be interested in reading our article on Android localization.

Thank you for staying with me today and happy coding!

Author

Avatar photo

Ilya Krukowski

Lead of content, SDK/integrations dev

Главная - Вики-сайт
Copyright © 2011-2025 iteam. Current version is 2.139.2. UTC+08:00, 2025-01-22 12:23
浙ICP备14020137号-1 $Гость$