As of October 2026, the intl_utils package is no longer maintained. The same applies to Flutter Intl, the plugin for Android Studio and the extension for VS Code built on top of it. Your projects keep working, and nothing changes on your side today. We do recommend migrating to Flutter's built-in gen-l10n, though, and this guide shows how to do it with minimal changes to your project.
gen-l10n has matured over the years and does the job Flutter Intl and intl_utils were created for. Below, we explain why we built them, what has changed since then, and how to migrate.
At the beginning of 2020, the usual way to localize a Flutter app was the intl package together with intl_translation. It worked, but it asked a lot of the developer. Every string needed a hand-written Intl.message wrapper in Dart. Every time a string was added or changed, you ran the extract_to_arb command to pull the strings out of the code, and generate_from_arb to turn the translations back into Dart. The extraction itself is nothing unusual. Separating translatable strings from code is a practice that software localization established long before Flutter, for a simple reason: translators translate, they do not code. The problem was the amount of manual work around it.
The idea behind Flutter Intl and intl_utils was to generate that boilerplate for you, so that the repetitive wiring took no time at all. Both treated the ARB files as the single source of truth: every string in the app lived in an ARB file. Whenever one of those files changed, Flutter Intl noticed it in the IDE and ran intl_utils under the hood, which generated a Dart getter or method for every key. Developers got autocompletion for keys and a compile error instead of a runtime surprise when a key went missing, and translators got plain ARB files to work with.
Over time, Flutter Intl grew into a small localization workflow of its own: adding and removing languages in the project, extracting hard-coded strings from Dart code into ARB files, and exchanging ARB files with Localizely to streamline translation even further.
The tools reached far more developers than we ever expected, and we are grateful for every one of them. Many of the projects built with them are still around today and deserve a proper way forward, not just a notice. The rest of this post is exactly that.
Not long after Flutter Intl and intl_utils came out, Flutter shipped the first version of gen-l10n, a tool built into the Flutter SDK with the same goal: generating the localization boilerplate for you. It has improved a lot since then. The early rough edges are gone, and it gained options such as deferred loading, escaping, relaxed syntax, and reports of untranslated messages. Google's ARB Editor extension for VS Code added syntax highlighting, diagnostics, and quick fixes for the ARB files themselves.
Localizely adapted every Flutter-related feature to work with gen-l10n as well: Over-the-Air updates, In-Context Editing, the CLI, and the GitHub, GitLab, and Bitbucket integrations for syncing ARB files.
Being part of the Flutter SDK gives gen-l10n a level of integration that a side tool can never match. There is nothing to install, no plugin or extension to set up, and the code is generated as part of flutter pub get, flutter run, and flutter build rather than by something watching your files from the outside. Keeping a second tool alive for something the Flutter SDK already covers no longer makes sense. Flutter Intl and intl_utils solved a problem that existed at the time. That problem is now solved better, at the source.
Our Flutter localization guide explains gen-l10n from scratch. This section covers only what changes when you move an existing Flutter Intl project to it.
Flutter Intl kept its settings in the flutter_intl section of pubspec.yaml. gen-l10n reads its settings from an l10n.yaml file next to it.
In pubspec.yaml, remove the flutter_intl section and the intl_utils dev dependency, and add generate: true to the flutter section. Keep flutter_localizations and intl:
dependencies:
flutter_localizations:
sdk: flutter
intl: any
flutter:
generate: true
uses-material-design: true
Then create l10n.yaml in the project root with the values from your old flutter_intl section. With the Flutter Intl defaults, it looks like this:
arb-dir: lib/l10n # flutter_intl: arb_dir
template-arb-file: intl_en.arb # flutter_intl: main_locale
output-dir: lib/generated # flutter_intl: output_dir
output-localization-file: l10n.dart
output-class: S # flutter_intl: class_name
nullable-getter: false # keeps S.of(context) non-nullable, as before
Your ARB files need no changes.
Note: On Flutter versions older than 3.32, also add synthetic-package: false to l10n.yaml. Generating into a synthetic package was removed in Flutter 3.35, so there is no point in starting with it.
Delete the old generated files first. intl_utils kept message tables in lib/generated/intl/, which gen-l10n does not use. Then generate the new ones:
rm -rf lib/generated
flutter pub get
flutter gen-l10n
You get lib/generated/l10n.dart with the S class and one l10n_<locale>.dart file per locale. From now on, Flutter regenerates them on every flutter pub get, flutter run, and flutter build.
gen-l10n reads the same ARB files, so this step usually goes through without errors. If it reports a problem, the error names the key and what is wrong with it. One difference the compiler cannot catch for you: when a message has several placeholders of the same type, the two tools may order the generated parameters differently, so check those call sites.
The generated/l10n.dart import and every S.of(context).someKey call stay as they are. Only the static API around the class changes:
S.delegate.supportedLocales becomes S.supportedLocales. In most apps, that is the only change in MaterialApp.S.current and S.load() have no equivalent in gen-l10n, so code that relies on them needs rework.Run flutter analyze to find anything else.
Note: This guide keeps the old S class and l10n.dart file names on purpose. If you prefer the gen-l10n defaults, AppLocalizations and app_localizations.dart, remove output-class and output-localization-file from l10n.yaml and rename the usages in your code.
dart run intl_utils:generate with flutter gen-l10n, or drop it, since flutter build generates the code anyway.Skip this step if you do not use Localizely. The localizely section of flutter_intl did two things, and both have a gen-l10n replacement.
Syncing ARB files. The upload and download commands and IDE actions are replaced by the Localizely CLI. Run localizely-cli init once to connect the project; localizely-cli push and localizely-cli pull then do what the old commands did. The configuration file it creates also drives the GitHub, GitLab, and Bitbucket integrations, if you prefer pull requests with fresh translations over running a command.
Over-the-Air updates and In-Context Editing. With Flutter Intl, ota_enabled: true made intl_utils generate the metadata the Localizely SDK needs. With gen-l10n, the SDK generates it itself. The Over-the-Air and In-Context Editing guides cover the setup.
Thank you to everyone who used these tools, reported bugs, suggested features, and sent pull requests over the years. We hope the migration goes smoothly.
Like this article? Share it!
Zoran is a Software Engineer at Localizely. His primary interest is web development, but he also has a solid background in other technologies. For the last few years, he has been involved in the development of software localization tools.
Subscribe to the Localizely blog newsletter for quality product content in your inbox.