Update translations in your Flutter apps without shipping a new build.
Localizely lets you update translations in your Flutter applications over the air. Fix a typo, refine a phrase, or add a missing translation, and your users see the change on their next app start, with no new release to the App Store or Google Play and no waiting for review.
It is ideal for correcting mistakes that slipped into a release, polishing copy after launch, and keeping translations current between app versions.
Most Flutter apps are localized with the intl package and a tool that generates the localization code. The sections below show how to add Over-the-Air (OTA) translation updates to such a project. Pick the tab that matches your setup.
The gen_l10n tool is the standard way of localizing Flutter applications. It ships with the Flutter SDK, so there is nothing extra to install. This guide assumes your app is already localized with it. If not, start with our guide to setting up and localizing apps using the gen_l10n tool, then come back here to enable Over-the-Air updates.
A complete sample app with Over-the-Air integration is available on GitHub.
Open the Over-the-Air page of your project in Localizely and generate an SDK token. You will need it when you configure the SDK in your Flutter project.
On the same page, create a distribution. A project can have several distributions, but one is enough for most apps. Note the generated Distribution ID, which you will also need for the SDK configuration.
A release is a snapshot of your project's translations, made available to the apps connected to the distribution. Whenever you want to push updated translations, create a new release. Each release can carry the following settings:
New releases are visible only to app builds that initialized the SDK with the prerelease flag set to true. This lets your team verify translation changes internally before you publish the release to all users.
Add localizely_sdk to your Flutter project by running the following command:
flutter pub add localizely_sdkThis adds a dependency like the following to your pubspec.yaml file:
dependencies:
localizely_sdk: ^2.7.7Recent Flutter versions no longer generate localization code as a synthetic package, so make sure your l10n.yaml file writes the generated code into your lib directory. The sample app uses the following configuration:
arb-dir: lib/l10n
template-arb-file: intl_en.arb
output-localization-file: app_localizations.dart
output-dir: lib/l10n/generatedNext, generate the localization file the SDK needs by running:
dart run localizely_sdk:generateNote: In Flutter 3.22.0, running the command dart run localizely_sdk:generate may produce false analyzer errors. This issue has been resolved in Flutter 3.22.1. If you need to use 3.22.0 and encounter these errors, running flutter pub get again should fix the problem.
To regenerate the code automatically whenever your .arb files change, add build_runner to your dev_dependencies and run it in watch mode:
flutter pub add build_runner -d
dart run build_runner watchOnce the localization file is generated, point the localizationsDelegates and supportedLocales properties of your MaterialApp widget at it.
...
import 'l10n/generated/localizely_localizations.dart';
class MyApp extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
return MaterialApp(
...
localizationsDelegates: LocalizelyLocalizations.localizationsDelegates,
supportedLocales: LocalizelyLocalizations.supportedLocales,
...
);
}
}You will need the SDK token from Step 1 and the Distribution ID from Step 2. In your main.dart file, initialize the SDK before running the app and call updateTranslations once the localization delegates are in place:
import 'package:flutter/material.dart';
import 'l10n/generated/app_localizations.dart';
import 'l10n/generated/localizely_localizations.dart';
import 'package:localizely_sdk/localizely_sdk.dart'; // Import sdk package
void main() {
Localizely.init('<SDK_TOKEN>', '<DISTRIBUTION_ID>'); // Init sdk
Localizely.setPreRelease(true); // Add this only if you want to use prereleases
Localizely.setAppVersion('<APP_VERSION>'); // Add this only if you want to explicitly set the application version, or in cases when automatic detection is not possible (e.g. Flutter web apps)
runApp(MaterialApp(
onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,
localizationsDelegates: LocalizelyLocalizations.localizationsDelegates,
supportedLocales: LocalizelyLocalizations.supportedLocales,
home: HomePage()));
}
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool _isLoading = true;
@override
void initState() {
super.initState();
// Call 'updateTranslations' after localization delegates initialization
Localizely.updateTranslations().then(
(response) => setState(() {
_isLoading = false;
}),
onError: (error) => setState(() {
_isLoading = false;
}));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(AppLocalizations.of(context)!.pageHomeTitle)),
body: Center(
child: _isLoading ? CircularProgressIndicator() : Column(children: <Widget>[Text(AppLocalizations.of(context)!.welcome)])));
}
}Avoid committing secrets such as the SDK token and Distribution ID to version control. In real-world projects, keep them in environment variables and load them at runtime with a package like flutter_dotenv.
Note: Ensure you have added INTERNET permission for Android devices.
Note: Localizely.updateTranslations() should be called just once on app start, as shown in the given example. Please do not call it frequently after the app starts.
Note: The Over-the-Air support for Flutter apps that use gen_l10n tool for localization is added in the localizely_sdk 2.5.0.
Nothing changes in the rest of your code. Refer to your string keys exactly as before:
AppLocalizations.of(context)!.titleHint: In case you can't see updated translations in the app after successful OTA retrieval, please check if you have missed some of the steps.
Some tips that can help you: check if you have created a new release with the latest changes on Localizely, check if you are working with enabled or disabled prereleases, check if you are using the same languages on the Localizely and within the app, and check if you need to rebuild widgets tree after OTA update.
Switching locales works exactly as it did before you added the SDK.
The Flutter Intl is an IDE plugin that generates the boilerplate code for the intl package. This guide assumes your app is already localized with it. If not, start with our guide to setting up and localizing apps using the Flutter Intl plugin, then come back here to enable Over-the-Air updates.
A complete sample app with Over-the-Air integration is available on GitHub.
Open the Over-the-Air page of your project in Localizely and generate an SDK token. You will need it when you configure the SDK in your Flutter project.
On the same page, create a distribution. A project can have several distributions, but one is enough for most apps. Note the generated Distribution ID, which you will also need for the SDK configuration.
A release is a snapshot of your project's translations, made available to the apps connected to the distribution. Whenever you want to push updated translations, create a new release. Each release can carry the following settings:
New releases are visible only to app builds that initialized the SDK with the prerelease flag set to true. This lets your team verify translation changes internally before you publish the release to all users.
Add localizely_sdk to your Flutter project by running the following command:
flutter pub add localizely_sdkThis adds a dependency like the following to your pubspec.yaml file:
dependencies:
localizely_sdk: ^2.7.7Next, enable Over-the-Air support in the flutter_intl section of your pubspec.yaml file and regenerate the localization code:
...
flutter_intl:
enabled: true
localizely:
ota_enabled: trueNote: It is required to use Flutter Intl plugin for Android Studio / VS Code or intl_utils (1.5.0 or newer) in order to generate relevant localization dart code.
You will need the SDK token from Step 1 and the Distribution ID from Step 2. In your main.dart file, initialize the SDK before running the app and call updateTranslations once the localization delegates are in place:
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:localizely_sdk/localizely_sdk.dart'; // Import sdk package
import 'generated/l10n.dart';
void main() {
Localizely.init('<SDK_TOKEN>', '<DISTRIBUTION_ID>'); // Init sdk
Localizely.setPreRelease(true); // Add this only if you want to use prereleases
Localizely.setAppVersion('<APP_VERSION>'); // Add this only if you want to explicitly set the application version, or in cases when automatic detection is not possible (e.g. Flutter web apps)
runApp(MaterialApp(
onGenerateTitle: (context) => S.of(context).appTitle,
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
home: HomePage()));
}
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool _isLoading = true;
@override
void initState() {
super.initState();
// Call 'updateTranslations' after localization delegates initialization
Localizely.updateTranslations().then(
(response) => setState(() {
_isLoading = false;
}),
onError: (error) => setState(() {
_isLoading = false;
}));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(S.of(context).pageHomeTitle)),
body: Center(
child: _isLoading ? CircularProgressIndicator() : Column(children: <Widget>[Text(S.of(context).welcome)])));
}
}Avoid committing secrets such as the SDK token and Distribution ID to version control. In real-world projects, keep them in environment variables and load them at runtime with a package like flutter_dotenv.
Note: Ensure you have added INTERNET permission for Android devices.
Note: Localizely.updateTranslations() should be called just once on app start, as shown in the given example. Please do not call it frequently after the app starts.
Note: The localizely_sdk >=2.4.0 <2.5.0 requires an update of min platform versions:
- Android: Required Android SDK 21 or newer (update minSdkVersion to 21 in the android/app/build.gradle file).
- iOS: Required iOS 11 or newer.
As of version 2.5.0, these updates are no longer required due to changes in implementation.
Nothing changes in the rest of your code. Refer to your string keys exactly as before:
S.of(context).titleHint: In case you can't see updated translations in the app after successful OTA retrieval, please check if you have missed some of the steps.
Some tips that can help you: check if you have created a new release with the latest changes on Localizely, check if you are working with enabled or disabled prereleases, check if you are using the same languages on the Localizely and within the app, and check if you need to rebuild widgets tree after OTA update.
Switching locales works exactly as it did before you added the SDK:
setState(() {
S.load(Locale('en', 'US'));
});When an app that uses the Localizely SDK starts for the first time on a device, the SDK generates a unique, random device identifier. Its sole purpose is to count active users over a given period. It is not used for any other form of tracking and contains no user or device information.
Over-the-Air updates are not switched off automatically when you exceed your MAU quota. We will let you know, and you will have enough time to upgrade your OTA add-on to match your needs.
The app falls back to the translations it last fetched from Localizely, or, failing that, to the translations bundled at build time. As soon as the app is back online, it fetches the latest release again.
Previous: Translation editor
Read next: Flutter In-Context Editing
Tired of manually editing translation files?
Our platform streamlines software localization for you.