Discover Language-Market Fit and what’s new from Lokalise.
Watch now
Developer Guides & Tutorials

Flutter localization and internationalization (i18n) with examples

Ilya Krukowski,Updated on August 8, 2026·19 min read
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.

Managing Flutter translations at scale

ARB files work well when you are managing one or two languages, but keeping them synchronized becomes harder as your app supports more locales and ships more frequently. A software localization platform like Lokalise connects to GitHub, GitLab, Bitbucket, or Azure Repos and can automatically sync ARB files on every push.

When new strings are added, Lokalise can translate them with AI and send only flagged content for human review. This keeps translation work connected to the development workflow instead of requiring teams to manage ARB files manually between releases.

For live updates, the Lokalise Flutter SDK is a drop-in replacement for AppLocalizations generated by flutter gen-l10n. It enables over-the-air (OTA) delivery of translation updates across iOS, Android, web, and desktop without requiring a new app store release.

Flutter localization and internationalization

Prerequisites for Flutter localization

In this article, we'll explore how to add Flutter i18n and l10n to your application. This tutorial assumes you have basic knowledge of Flutter and Dart.

If you haven't installed Flutter yet, one of the easiest ways to get started is through Visual Studio Code. Install the Flutter extension, open the Command Palette (Ctrl+Shift+P on Windows or Linux, Cmd+Shift+P on macOS), and select Flutter: New Project. Follow the prompts to install the Flutter SDK if necessary, then create an empty application named flutter_i18n_demo. You can also follow the official Flutter installation guide for detailed setup instructions.

Alternatively, you can create the same minimal project from the command line:

flutter create flutter_i18n_demo --empty

Open the project in your editor. We also need some content to demonstrate the Flutter localization process, so let's make a few more preparations.

Source code can be found on GitHub.

Preparing the Flutter app

Most of the files we'll work with are located inside the lib directory. Unless stated otherwise, create the files in this directory.

First, replace the contents of lib/main.dart with the following:

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

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

Next, create an app directory inside lib, then add a my_app.dart file:

import 'package:flutter/material.dart';
import 'package:flutter_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),
      ),
      home: const MyHomePage(),
    );
  }
}

Inside the app directory, create a pages directory and add a my_home_page.dart file:

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(
        title: const Text('Welcome!'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            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 gives us a small app with several strings to localize and a counter that we'll later use to demonstrate dynamic translations and pluralization.

With the basic app ready, we can start implementing software localization.

Internationalization libraries

To set up localization, add the flutter_localizations and intl packages. flutter_localizations provides Flutter's built-in localization support, while intl provides locale-aware formatting for values such as dates and numbers.

Run the following commands from the project root:

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

Next, open pubspec.yaml and enable localization code generation in the flutter section:

flutter:
  generate: true

Now create an l10n.yaml file in the project root:

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

This configuration tells Flutter that our ARB files will live in lib/l10n, that app_en.arb is the template file containing the source messages, and that the generated localization class should be written to app_localizations.dart.

Flutter generates the localization code in the directory specified by arb-dir unless you configure a separate output-dir. You can find all available l10n.yaml options in the official Flutter documentation.

With the localization tooling configured, we can create our first ARB files.

Creating Flutter translation files

ARB files and Flutter localization

Flutter's localization workflow uses ARB files to store localizable strings and their metadata. ARB (Application Resource Bundle) files use a JSON-based format and can include descriptions, placeholders, and other information used during localization.

Create an l10n directory inside lib. Then create two files:

lib/l10n/app_en.arb
lib/l10n/app_ru.arb

We'll use app_en.arb as the template file. Add the following content:

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

Here's what these entries mean:

  • @@locale identifies the locale represented by the file.
  • appTitle and welcome are message keys. Flutter uses these keys to generate Dart getters that we can access from the app.
  • @appTitle contains metadata for the appTitle message. Metadata isn't required for every simple message, but descriptions give translators useful context.

Now add the Russian translations to app_ru.arb:

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

The translated ARB file doesn't need to repeat the metadata from the template file.

Generating Dart localization files

Flutter uses the ARB files to generate Dart classes that expose the localized messages to your application. To generate them manually, run this command from the project root:

flutter gen-l10n

With our l10n.yaml configuration, Flutter generates the localization files inside lib/l10n. You should see files including:

app_localizations.dart
app_localizations_en.dart
app_localizations_ru.dart

app_localizations.dart contains the main AppLocalizations class, information about the supported locales, and localization delegates. The locale-specific files contain the generated implementations for English and Russian.

You don't normally need to run flutter gen-l10n after every change. Flutter also runs localization code generation as part of commands such as flutter run and flutter pub get.

Don't edit the generated Dart files manually. Any changes will be overwritten the next time the localization code is generated.

Performing simple translations

Now that we have English and Russian ARB files, we can use the generated localization classes in the app.

Translating the app title

Open lib/app/my_app.dart and import the generated AppLocalizations class:

import 'package:flutter_i18n_demo/l10n/app_localizations.dart';

Next, configure MaterialApp with the localization delegates and locales generated from our ARB files:

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(),
    );
  }
}

localizationsDelegates provides the localized resources used by the app, while supportedLocales tells Flutter which locales are available. Both values are generated automatically from our localization setup.

To localize the application title as well, add onGenerateTitle:

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      onGenerateTitle: (context) =>
          AppLocalizations.of(context)!.appTitle,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(),
    );
  }
}

The appTitle property comes from the appTitle key in our ARB files. Flutter generates getters and methods from these keys, giving us type-safe access to localized messages in Dart.

Displaying the welcome message

Now open lib/app/pages/my_home_page.dart and import AppLocalizations:

import 'package:flutter_i18n_demo/l10n/app_localizations.dart';

Replace the hardcoded Welcome! text in the AppBar with the localized welcome message:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(AppLocalizations.of(context)!.welcome),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          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),
    ),
  );
}

Flutter selects a locale from AppLocalizations.supportedLocales based on the locale of the platform running the app. If the platform uses Russian, welcome resolves to Добро пожаловать!; otherwise, our English translation is used as the fallback.

The remaining hardcoded strings will be localized in the following sections as we introduce placeholders, formatting, and pluralization.

Interpolation and placeholders in Flutter translations

Inserting dynamic values

Localized messages often need to include values that are only known at runtime. Flutter handles these values with placeholders defined in ARB files.

Suppose we want to display a message containing a company name. Update lib/l10n/app_en.arb:

{
  "@@locale": "en",
  "appTitle": "Flutter i18n",
  "@appTitle": {
    "description": "Main application title"
  },
  "welcome": "Welcome!",
  "createdBy": "Tutorial by {company}",
  "@createdBy": {
    "description": "Tutorial attribution",
    "placeholders": {
      "company": {
        "type": "String",
        "example": "Lokalise"
      }
    }
  }
}

The {company} value is a placeholder. Its metadata tells Flutter that the generated localization method should accept a String value and provides an example that can also help translators understand the message.

Add the corresponding translation to lib/l10n/app_ru.arb:

{
  "@@locale": "ru",
  "appTitle": "Flutter i18n",
  "welcome": "Добро пожаловать!",
  "createdBy": "Руководство от {company}"
}

Placeholder names must remain consistent across translations, although their position within the translated message can change.

Regenerate the localization classes if necessary:

flutter gen-l10n

Now open lib/app/pages/my_home_page.dart and add the new message to the Column:

children: [
Text(AppLocalizations.of(context)!.createdBy('Lokalise')),
  const Text('Press the button below'),
  Text(
    'Times pressed: $_counter',
    style: Theme.of(context).textTheme.headlineMedium,
  ),
],

Flutter generates createdBy as a method because the message contains a placeholder. Passing 'Lokalise' produces Tutorial by Lokalise in English and Руководство от Lokalise in Russian.

Displaying the current locale

Placeholders can also be used with values obtained from the app itself. Let's display the locale Flutter is currently using.

First, add another message to app_en.arb:

"currentLocale": "Current locale: {locale}",
"@currentLocale": {
  "description": "Currently active app locale",
  "placeholders": {
    "locale": {
      "type": "String",
      "example": "en-US"
    }
  }
}

Add its Russian translation to app_ru.arb:

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

Remember to place these entries inside the existing JSON objects and add commas where necessary.

Next, create lib/app/widgets/current_locale_widget.dart:

import 'package:flutter/material.dart';
import 'package:flutter_i18n_demo/l10n/app_localizations.dart';

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

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

    return Text(
      AppLocalizations.of(context)!.currentLocale(locale.toLanguageTag()),
      style: Theme.of(context).textTheme.headlineMedium,
    );
  }
}

Localizations.localeOf(context) returns the locale currently used by the app. Calling toLanguageTag() converts it to a standard locale identifier such as en, en-US, or ru.

Finally, import the widget in lib/app/pages/my_home_page.dart:

import 'package:flutter_i18n_demo/app/widgets/current_locale_widget.dart';

Then add it to the Column:

children: [
  const CurrentLocaleWidget(),
  Text(AppLocalizations.of(context)!.createdBy('Lokalise')),
  const Text('Press the button below'),
  Text(
    'Times pressed: $_counter',
    style: Theme.of(context).textTheme.headlineMedium,
  ),
],

The app now uses placeholders both for values supplied directly in Dart code and for values obtained dynamically at runtime.

More Flutter localization and i18n features

Date localization

Flutter can also format dates according to the active locale. Let's add the current date to our app.

Open lib/app/pages/my_home_page.dart and add another Text widget to the existing Column:

children: [
  const CurrentLocaleWidget(),
  Text(
    AppLocalizations.of(context)!.currentDate(DateTime.now()),
  ),
  Text(AppLocalizations.of(context)!.createdBy('Lokalise')),
  const Text('Press the button below'),
  Text(
    'Times pressed: $_counter',
    style: Theme.of(context).textTheme.headlineMedium,
  ),
],

Next, add the following message to lib/l10n/app_en.arb:

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

The DateTime placeholder tells Flutter to format the value using the intl package. The yMMMMEEEEd format produces a locale-aware date containing the year, month, weekday, and day.

Add the corresponding translation to app_ru.arb:

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

Regenerate the localization classes:

flutter gen-l10n

The same DateTime value can now be displayed differently depending on the active locale.

Localizing numbers and currencies

Numbers and currency values also have locale-specific formatting rules. Flutter's localization generator uses the NumberFormat class from the intl package to format numeric placeholders.

Add another message to the Column in my_home_page.dart:

Text(
  AppLocalizations.of(context)!.currencyDemo(1234567.89),
),

Then add the following entry to app_en.arb:

"currencyDemo": "Demo price: {value}",
"@currencyDemo": {
  "description": "Displays a localized currency value",
  "placeholders": {
    "value": {
      "type": "double",
      "format": "currency",
      "optionalParameters": {
        "name": "USD",
        "decimalDigits": 2
      }
    }
  }
}

Here, format: "currency" tells Flutter to use locale-aware currency formatting. We explicitly set the currency to USD and request two digits after the decimal separator, while the active locale determines how the resulting value is formatted.

Add the Russian translation to app_ru.arb:

"currencyDemo": "Пример цены: {value}"

Run the localization generator again:

flutter gen-l10n

The numeric value and currency remain the same, but their presentation follows the formatting conventions of the active locale.

Pluralization

Pluralization lets a localized message change according to a numeric value. Our app already has a counter, so we can use it to display different messages depending on how many times the button has been pressed.

First, replace the remaining hardcoded counter strings in lib/app/pages/my_home_page.dart:

children: [
  const CurrentLocaleWidget(),
  Text(
    AppLocalizations.of(context)!.currentDate(DateTime.now()),
  ),
  Text(
    AppLocalizations.of(context)!.currencyDemo(1234567.89),
  ),
  Text(AppLocalizations.of(context)!.createdBy('Lokalise')),
  Text(AppLocalizations.of(context)!.pressButton),
  Text(
    AppLocalizations.of(context)!.buttonPressed(_counter),
    style: Theme.of(context).textTheme.headlineMedium,
  ),
],

Next, add the following messages to lib/l10n/app_en.arb:

"pressButton": "Press the button below",
"buttonPressed": "{count, plural, =0{Not pressed yet} one{Pressed {count} time} other{Pressed {count} times}}",
"@buttonPressed": {
  "description": "Shows how many times the button has been pressed",
  "placeholders": {
    "count": {
      "type": "num"
    }
  }
}

The plural expression selects a message based on the value of count. Here, =0 handles zero explicitly, one handles the singular form, and other provides the remaining English plural form.

Now add the Russian translations to app_ru.arb:

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

Russian uses more plural categories than English, so the same numeric value can require different word forms depending on the count. Flutter selects the appropriate plural category according to the active locale.

Regenerate the localization classes:

flutter gen-l10n

The buttonPressed message is generated as a method that accepts the count:

AppLocalizations.of(context)!.buttonPressed(_counter)

As the counter changes, Flutter selects the appropriate localized plural form automatically.

Switching the locale programmatically

By default, Flutter chooses the app locale based on the user's system settings and the locales listed in supportedLocales. We can also let users select a language directly inside the app.

For this simple example, we don't need an additional state management package. We'll store the selected locale in the root widget and rebuild MaterialApp when it changes.

Storing the selected locale

Open lib/app/my_app.dart and convert MyApp from a StatelessWidget to a StatefulWidget:

import 'package:flutter/material.dart';
import 'package:flutter_i18n_demo/app/pages/my_home_page.dart';
import 'package:flutter_i18n_demo/l10n/app_localizations.dart';

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Locale? _locale;

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

    setState(() {
      _locale = locale;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      locale: _locale,
      onGenerateTitle: (context) =>
          AppLocalizations.of(context)!.appTitle,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: MyHomePage(
        onLocaleChanged: _setLocale,
      ),
    );
  }
}

The _locale value starts as null, so Flutter initially uses the locale selected from the user's system settings. When _setLocale receives one of our supported locales, setState rebuilds MaterialApp with the selected locale.

Creating a locale switcher

Create lib/app/widgets/locale_switcher_widget.dart:

import 'package:flutter/material.dart';
import 'package:flutter_i18n_demo/l10n/app_localizations.dart';

class LocaleSwitcherWidget extends StatelessWidget {
  const LocaleSwitcherWidget({
    super.key,
    required this.onLocaleChanged,
  });

  final ValueChanged<Locale> onLocaleChanged;

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

    return DropdownButtonHideUnderline(
      child: DropdownButton<Locale>(
        value: currentLocale,
        items: AppLocalizations.supportedLocales.map((locale) {
          return DropdownMenuItem<Locale>(
            value: locale,
            child: Text(locale.toLanguageTag()),
          );
        }).toList(),
        onChanged: (locale) {
          if (locale != null) {
            onLocaleChanged(locale);
          }
        },
      ),
    );
  }
}

Localizations.localeOf(context) gives us the locale currently used by the app. The dropdown is built from AppLocalizations.supportedLocales, so it automatically reflects the locales generated from our ARB files.

When a user selects an item, the widget passes the selected Locale back to the root widget, which updates MaterialApp.locale.

Displaying the language switcher

Next, update MyHomePage so it can receive the locale change callback.

In lib/app/pages/my_home_page.dart, update the widget class:

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    super.key,
    required this.onLocaleChanged,
  });

  final ValueChanged<Locale> onLocaleChanged;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

Import the locale switcher:

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

Then add it to the AppBar:

appBar: AppBar(
  title: Text(AppLocalizations.of(context)!.welcome),
  actions: [
    LocaleSwitcherWidget(
      onLocaleChanged: widget.onLocaleChanged,
    ),
    const SizedBox(width: 12),
  ],
),

Run the app and select en or ru from the menu. MaterialApp rebuilds with the selected locale, and the localized strings, date formatting, number formatting, and plural rules update accordingly.

Use Lokalise for Flutter localization

Managing ARB files manually is manageable for a small app, but the process becomes harder as you add languages, contributors, and more frequent releases. Lokalise is a translation management platform that supports Flutter ARB files and brings translation, review, collaboration, and file management into one place.

Lokalise can also connect to your code repository through integrations with services such as GitHub, GitLab, Bitbucket, and Azure Repos. This can reduce manual file exchange in a production workflow. For this tutorial, however, we'll upload and download the ARB files manually so you can see each step.

Uploading ARB translation files

Create a Lokalise account or sign in, then click New project from the projects dashboard.

What would you like to localize

Choose the Web and mobile project type. Give the project a name, set English as the Base language, and add the languages you want to support as Target languages.

Web and Mobile app

Our Flutter app already contains English and Russian translations, and we'll add French in this example. Therefore, select Russian and French as target languages and create the project.

Open the Upload page.

Upload Language file in any format

Select the ARB files from lib/l10n:

app_en.arb
app_ru.arb

Upload the ARB source files only; the generated app_localizations*.dart files are not translation resources and should not be uploaded.

Because our buttonPressed message uses an ICU plural expression, enable Detect ICU plurals during the import. This tells Lokalise to recognize the message as a plural key rather than ordinary text.

Lokalise detects language codes in filenames such as app_en.arb and app_ru.arb and replaces them with the %LANG_ISO% placeholder for the project filename. Verify that the resulting filename follows a pattern such as:

app_%LANG_ISO%.arb
null

When translations are exported later, %LANG_ISO% is replaced with the corresponding language code.

Click Import files, then open the project editor to review the imported keys and translations.

null

Lokalise handles plural forms according to the rules of each language. If you need a plural form that isn't enabled for a particular language, open that language's settings and configure Custom plural forms.

3 languages
null

Translating Flutter strings with Lokalise AI

You can translate strings manually in the editor or create an AI translation task. For this example, we'll use Lokalise AI to generate the French translations.

In the editor, select the keys you want to translate.

null

Choose Create task from the bulk actions menu.

null

Select Automatic translation as the task type, give the task a name, and optionally provide instructions describing the content and where it is used.

null

Next, choose English as the Source language and French as the Target language. Review the task scope and create the task.

Task scope

Lokalise AI processes the selected strings and writes the generated translations back to the project. Once the task is complete, return to the editor and review the French translations before exporting them.

2023-10-11-19_03_24-Flutter-_-Lokalise-—-Mozilla-Firefox.png

Lokalise AI supports plural translation keys and can generate the plural forms required by the target language.

Downloading the translated ARB files

Once the translations are ready, open Download from the project menu and choose Flutter (.arb) as the file format.

null

Select the languages you want to export. If you only need the newly added French translation, select French.

null

Make sure the exported file structure preserves the locale placeholder so that the French file is generated as:

app_fr.arb

Then click Build and download.

null

Extract the downloaded ARB file into your project's lib/l10n directory. The directory should now contain:

app_en.arb
app_ru.arb
app_fr.arb

Regenerate the Flutter localization classes:

flutter gen-l10n

Run the app again. Because AppLocalizations.supportedLocales is generated from the available ARB files, French is now included automatically and appears in the language switcher we created earlier.

Over-the-air (OTA) translation updates in Flutter

Downloading ARB files works well when translations should ship with a regular application release. For smaller copy changes, Lokalise also provides a Flutter SDK that can retrieve updated translations over the air (OTA).

The SDK uses its own generated localization class as a replacement for the AppLocalizations class we've used so far. Once configured, the app can download a published Lokalise OTA bundle when it starts instead of requiring every translation change to be bundled into a new application release.

Generate a Flutter SDK bundle

Before changing the Flutter code, prepare an OTA bundle in Lokalise.

First, make sure that the translation keys you want to include in the bundle are assigned to the Other platform. Flutter SDK bundles only include keys assigned to this platform.

2026-08-09 17_43_33-Flutter _ Lokalise — LibreWolf.webp

Then open Download in your Lokalise project and select Flutter SDK as the file format. This is different from the Flutter (.arb) format we used earlier: .arb downloads are intended for files stored in the project, while the Flutter SDK format creates an OTA bundle.

2026-08-09 17_43_54-Flutter _ Lokalise — LibreWolf.webp

Click Build only.

Lokalise creates the bundle and registers it with the OTA service. You can manage generated bundles under: Project settings → OTA Bundles → Flutter SDK.

2026-08-09 17_44_29-Flutter _ Lokalise — LibreWolf.webp

The first generated bundle is assigned both the Production and Prerelease tags. New bundles become Prerelease by default, allowing you to test them before moving the Production tag to the new version.

When you change a bundle tag, click Save changes.

Generate an SDK token

The Flutter SDK needs an SDK token to connect your app to the Lokalise OTA service.

Open your Lokalise project settings and find the SDK token section. Click Generate new token, then copy the generated value.

2026-08-09 17_44_54-Flutter _ Lokalise — LibreWolf.webp

The SDK token belongs to a single Lokalise project and is different from a Lokalise API token. Make sure you use the SDK token when initializing the Flutter SDK.

You'll also need your Lokalise Project ID, which you can find in the same project's settings.

We'll use placeholders for both values in the code:

YOUR_PROJECT_ID
YOUR_SDK_TOKEN

Install the Lokalise Flutter SDK

From the Flutter project root, install the SDK:

flutter pub add lokalise_flutter_sdk

The SDK uses its own localization code generator. Because our template file is named app_en.arb rather than the SDK's default intl_en.arb, create a lok-l10n.yaml file in the project root:

arb-dir: lib/l10n
template-arb-file: app_en.arb
output-dir: lib/l10n/generated
output-localization-file: l10n.dart
output-class: Lt

Now generate the SDK localization classes:

dart run lokalise_flutter_sdk:gen-lok-l10n

The generated files are placed in:

lib/l10n/generated/

The main generated class is Lt. It serves the same purpose as the AppLocalizations class generated earlier by flutter gen-l10n, while also integrating with Lokalise OTA.

From this point, when you change the local ARB files, regenerate these classes with:

dart run lokalise_flutter_sdk:gen-lok-l10n

Replace AppLocalizations with the Lokalise SDK class

Our application currently imports:

import 'package:flutter_i18n_demo/l10n/app_localizations.dart';

Replace that import wherever it appears with:

import 'package:flutter_i18n_demo/l10n/generated/l10n.dart';

Then replace references to AppLocalizations with Lt.

For example:

AppLocalizations.of(context)!.welcome

becomes:

Lt.of(context).welcome

The same applies to the generated locale configuration:

localizationsDelegates: Lt.localizationsDelegates,
supportedLocales: Lt.supportedLocales,

Our locale validation also changes accordingly:

void _setLocale(Locale locale) {
  if (!Lt.supportedLocales.contains(locale)) {
    return;
  }

  setState(() {
    _locale = locale;
  });
}

The updated MaterialApp configuration in my_app.dart should look like this:

return MaterialApp(
  locale: _locale,
  onGenerateTitle: (context) => Lt.of(context).appTitle,
  localizationsDelegates: Lt.localizationsDelegates,
  supportedLocales: Lt.supportedLocales,
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
  ),
  home: MyHomePage(
    onLocaleChanged: _setLocale,
  ),
);

Initialize the Lokalise Flutter SDK

Next, update lib/main.dart.

Import the SDK:

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

Then initialize Lokalise before starting the application:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Lokalise.init(
    projectId: 'YOUR_PROJECT_ID',
    sdkToken: 'YOUR_SDK_TOKEN',
    preRelease: true,
  );

  runApp(const MyApp());
}

Replace YOUR_PROJECT_ID and YOUR_SDK_TOKEN with the values from your Lokalise project.

The preRelease: true option tells the SDK to use the bundle tagged as Prerelease. This is useful while testing OTA updates. For a production release, use the production bundle instead and manage which bundle is served through the Production tag or bundle freezes.

Download updated translations

Initializing the SDK connects the app to the OTA service, but we also need to request the latest bundle.

Our MyHomePage is already a StatefulWidget, so we can request an update when the page starts.

Import the SDK in my_home_page.dart:

import 'package:lokalise_flutter_sdk/lokalise_flutter_sdk.dart';

Add a loading state and update method:

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

  @override
  void initState() {
    super.initState();
    _updateTranslations();
  }

  Future<void> _updateTranslations() async {
    try {
      await Lokalise.instance.update();
    } finally {
      if (mounted) {
        setState(() {
          _isLoadingTranslations = false;
        });
      }
    }
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  // ...
}

Then show a loading indicator while the SDK checks for the latest translations:

body: _isLoadingTranslations
    ? const Center(
        child: CircularProgressIndicator(),
      )
    : Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Existing localized widgets...
          ],
        ),
      ),

Lokalise.instance.update() checks the OTA service for the appropriate bundle and downloads new translations when an update is available. Calling it when the app starts also provides a simple retry mechanism if a previous request failed.

For applications that can remain open for long periods, you can additionally call update() when the application resumes from the background.

Test an OTA translation update

Now we can test the complete OTA flow.

Open your Lokalise project and change an existing translation—for example, modify the welcome message.

Then:

  1. Open Download.
  2. Select Flutter SDK.
  3. Click Build only.
  4. Open Project settings → OTA Bundles → Flutter SDK.
  5. Confirm that the new bundle has the Prerelease tag.

Because our test app uses:

preRelease: true

restart the application. When Lokalise.instance.update() runs, the SDK retrieves the latest prerelease bundle and the updated translation appears in the app without changing or downloading the local ARB files.

Once the update is ready for production, move the Production tag to the desired bundle and save the changes.

For more control, Lokalise also supports bundle freezes, which let you restrict an OTA bundle to particular application versions. The Flutter SDK reads the application version from the version field in pubspec.yaml.

Important: Lt.supportedLocales is generated from the ARB locales included in the Flutter project. OTA is therefore best suited to updating translations for locales already included in the application. Adding a completely new supported locale requires updating the local ARB files and regenerating the localization code before that locale can appear in Lt.supportedLocales.

Conclusion

In this tutorial, you learned how to localize a Flutter app with ARB files, generate localization code, handle pluralization and formatting, and switch locales programmatically. You also saw how to add another language with AI-assisted translation.

As your Flutter project grows beyond one or two languages, managing ARB files manually can become harder to maintain across releases. Lokalise can connect to your repository, automate translation with AI, and deliver translation updates to live Flutter apps via OTA across iOS, Android, web, and desktop.

You may also be interested in our Android app localization guide.

Frequently asked questions

What is Flutter localization?

How does Lokalise help with Flutter app localization?

What are ARB files in Flutter?

Can Lokalise deliver OTA translation updates to Flutter apps?

Developer Guides & Tutorials

Author

1517544791599.jpg

Lead of content, SDK/integrations dev

Ilya is the lead for content, documentation, and onboarding at Lokalise, where he focuses on helping engineering teams build reliable internationalization workflows. With a background at Microsoft and Cisco, he combines practical development experience with a deep understanding of global product delivery, localization systems, and developer education.

He specializes in i18n architectures across modern frameworks — including Vue, Angular, Rails, and custom localization pipelines — and has hands-on experience with Ruby, JavaScript, Python, Elixir, Go, Rust, and Solidity. His work often centers on improving translation workflows, automation, and cross-team collaboration between engineering, product, and localization teams.

Beyond his role at Lokalise, Ilya is an IT educator and author who publishes technical guides, best-practice breakdowns, and hands-on tutorials. He regularly contributes to open-source projects and maintains a long-standing passion for teaching, making complex internationalization topics accessible to developers of all backgrounds.

Outside of work, he keeps learning new technologies, writes educational content, stays active through sports, and plays music. His goal is simple: help developers ship globally-ready software without unnecessary complexity.

SRT scaled

What is an SRT file? Subtitle format explained

An SRT file is a plain text file used to add subtitles to videos. It’s one of the simplest and most common formats out there. If you’ve ever turned on captions on a YouTube video, there’s a good chance it was using an SRT file behind the scenes. People use SRT files for all kinds of things: social media clips, online courses, interviews, films, you name it. They’re easy to make, easy to edit, and they work pretty much everywhere without hassle. In this post, we’ll

Updated on June 19, 2025·Ilya Krukowski
Libraries for translating JavaScript apps

Libraries and frameworks to translate JavaScript apps

In our previous discussions, we explored localization strategies for backend frameworks like Rails and Phoenix. Today, we shift our focus to the front-end and talk about JavaScript translation and localization. The landscape here is packed with options, which makes many developers a

Updated on April 28, 2025·Ilya Krukowski

Stop wasting time with manual localization tasks.

Launch global products days from now.

  • Lokalise_Arduino_logo_28732514bb (1).svg
  • mastercard_logo2.svg
  • 1273-Starbucks_logo.svg
  • 1277_Withings_logo_826d84320d (1).svg
  • Revolut_logo2.svg
  • hyuindai_logo2.svg