DEV Community

Cover image for A health check for your Flutter translations
Noa Tubić
Noa Tubić

Posted on Originally published at Medium

A health check for your Flutter translations

Translation files only grow. Features get removed, screens get rewritten, but nobody deletes the old keys, because nobody is sure they are unused.

I ran into this on a production Flutter app with about 2,900 Dart files and 2,400 translation keys. 365 keys were never used. 38 were missing in one of the languages. 247 strings in Dart code never went through localization at all. And 10 translations had invisible zero-width characters hiding in them.

The analyzer doesn't report any of that, and neither does the code generator. So I wrote a small tool that does.

What it does

l10n_health reads your .arb files and your Dart code together and reports:

  • keys that no code uses
  • keys missing in one language, or identical to the template (likely never translated)
  • placeholders that differ between languages, like {name} vs {userName}
  • broken ICU messages and plural forms a language needs, like Croatian few
  • hardcoded text that should be a translation, and the existing key to use when there is one
  • invisible Unicode: zero-width spaces, bidi controls, look-alike spaces

It works with Flutter's gen_l10n and with flutter_intl, and it finds your setup on its own. A full run on that big app takes about a quarter of a second.

Try it

dart pub global activate l10n_health
l10n_health
Enter fullscreen mode Exit fullscreen mode

Or as a dev dependency, so the whole team runs the same version:

dart pub add --dev l10n_health
dart run l10n_health
Enter fullscreen mode Exit fullscreen mode

This is the output on the small example project in the repo:

Placeholder mismatches (placeholder_mismatch)  1
  error lib/l10n/app_de.arb:4:3  Placeholders in "greeting" differ from en: missing {name}, unexpected {userName}

Hardcoded strings (hardcoded_string)  2
  warn  lib/main.dart:22:9  Hardcoded text "Please select a pharmacy" bypasses localization (prose, en)
  warn  lib/main.dart:24:9  Hardcoded "Close"; use the existing key "close"

Unused keys (unused_key)  2
  warn  lib/l10n/app_en.arb:11:3  Key "legacyBanner" is never used in Dart code
  warn  lib/l10n/app_en.arb:12:3  Key "close" is never used in Dart code
Enter fullscreen mode Exit fullscreen mode

For a report you can open in the browser or attach in CI:

l10n_health --format html -o l10n.html
Enter fullscreen mode Exit fullscreen mode

Deleting keys without fear

Finding dead keys is half the job. Removing them is where people get nervous, so prune is careful:

l10n_health prune          # dry run, writes nothing
l10n_health prune --apply  # removes the keys from every language
Enter fullscreen mode Exit fullscreen mode

It edits the files as text, so your diff is only the removed lines. Every file is checked to be valid JSON before anything is written. If a key also appears as a plain string somewhere, it might be looked up dynamically, so it is held back and listed for you to review instead of deleted.

After pruning, regenerate and build. If a key was still used, you find out at compile time, not from a user.

In CI

Record the current state once, then fail only on new problems:

l10n_health --update-baseline
l10n_health --fail-on warning
Enter fullscreen mode Exit fullscreen mode

There are also --format gitlab and --format github outputs, so findings show up inline in merge requests and pull requests.

Try it on your project

Run l10n_health once in your app and look at the unused keys first. They're usually the quickest win. Everything else is documented here:

GitHub logo NoaTubic / l10n_health

Health check for the translation layer of Dart and Flutter apps: unused and missing ARB keys, placeholder mismatches, hardcoded strings, invisible Unicode, safe prune.

l10n_health

A health check for the translation layer of Dart and Flutter apps.

l10n_health reads your .arb files and your Dart code together, and reports what neither the analyzer nor the code generator will tell you keys nobody uses, translations that are missing or were never translated placeholders that differ between languages, text that bypasses localization entirely, and invisible characters hiding in your strings. It can then prune the dead keys for you, safely.

It works with Flutter's built-in gen_l10n, with flutter_intl / intl_utils, and with any other ARB-based setup. Configuration is optional; the setup is detected from l10n.yaml or pubspec.yaml.

$ l10n_health
l10n_health  flutter_intl · template de · 2439 keys · 2931 Dart files · strict usage · 235 ms
  Locales   de 2434  en 2406

Placeholder mismatches (placeholder_mismatch)  2
  error lib/l10n/intl_en.arb:313:3  Placeholders in "hint1" differ from de: missing {day} {month} {year}, unexpected {DateTime}
  ...
Malformed ICU
…

https://pub.dev/packages/l10n_health

Feedback and issues are welcome on GitHub.

Top comments (0)