l10n_extractor

A command line tool that walks every Dart file in your Flutter project, pulls out the hardcoded strings, turns them into translation keys, and writes one translation file per locale you pick — with the translations left as "" so you (or a translator) can fill them in.

$ dart run l10n_extractor extract

? Which locales do you need? (comma separated) [en, ar]: en, ar, fr
? Output format (json / arb) [json]:
? Where should the locale files go? [assets/translations]:
? Which locale holds the original text? (empty = write "" everywhere) [en]:

About to write 3 locale file(s) into assets/translations:
  • assets/translations/en.json (source, keeps original text)
  • assets/translations/ar.json
  • assets/translations/fr.json

? Continue? (Y/n)

Scan
  Files scanned      3
  Strings found      21
  Unique keys        20
  Skipped literals   11

Locales
  en     assets/translations/en.json  +20 new, 0 kept, created
  ar     assets/translations/ar.json  +20 new, 0 kept, created
  fr     assets/translations/fr.json  +20 new, 0 kept, created

✓ Done in 96ms.

assets/translations/en.json

{
  "email_address": "Email address",
  "hello_you_have_3_new_messages": "Hello {userName}, you have 3 new messages",
  "sign_in": "Sign in"
}

assets/translations/ar.json

{
  "email_address": "",
  "hello_you_have_3_new_messages": "",
  "sign_in": ""
}

Install

Add it as a dev dependency in the project you want to scan:

dev_dependencies:
  l10n_extractor: ^0.1.0
dart pub get
dart run l10n_extractor extract

Or install it once and use it in any project:

dart pub global activate l10n_extractor
l10n_extractor extract

Usage

# Interactive: asks for locales, format, output folder and the source locale.
dart run l10n_extractor extract

# Fully scripted (no prompts).
dart run l10n_extractor extract -l en,ar,fr --source en -y

# ARB files for flutter_localizations / intl codegen.
dart run l10n_extractor extract -l en,ar -f arb -o lib/l10n

# See what would change without writing anything. The report is still written,
# so you can review every key before the first real run.
dart run l10n_extractor extract --dry-run --report l10n_report.json

# Save the answers to a config file, then just run `extract` from then on.
dart run l10n_extractor init

# Piping the answers in from a script.
printf 'en, ar, fr\n\n\nen\ny\n' | dart run l10n_extractor extract --interactive

Commands

Command What it does
extract [path] Scans the project and writes/merges the locale files. This is the default.
init Creates l10n_extractor.yaml with your answers.
help Shows the usage screen.

Options

Option Description
-l, --locale Locale codes, e.g. -l en -l ar or -l en,ar.
-s, --source Locale that keeps the original text. Every other locale gets "". Omit it to write "" everywhere.
-f, --format json (default) or arb.
-o, --output Output folder. Defaults to assets/translations (json) or lib/l10n (arb).
--path Project root to scan. Can also be passed positionally.
--include Folders/files to scan. Defaults to lib.
--exclude Extra path fragments to skip.
--key-style snake (default) or camel.
--group-by-file Nest keys under the file they came from: login_page.sign_in.
--no-interpolations Skip 'Hello $name' strings instead of converting them.
--keys-class Path of a generated Dart class of key constants.
--prune Drop keys that no longer exist anywhere in the code.
-n, --dry-run Report what would change, write nothing.
--report Write a JSON report with every key and the file:line it came from. Works with --dry-run.
-y, --yes Never prompt; fail instead of asking.
--interactive Ask the questions even when stdin is not a terminal, so answers can be piped in.

Config file

l10n_extractor init writes l10n_extractor.yaml next to your pubspec.yaml. CLI flags always override it.

include:
  - lib
exclude:
  - .g.dart
  - generated/
locales:
  - en
  - ar
source_locale: en
format: json
output_dir: assets/translations
key_style: snake
group_by_file: false
include_interpolations: true
keys_class: lib/generated/locale_keys.g.dart
min_length: 2
max_key_words: 6
ignore_map_keys: true
ignore_patterns:
  - '^[A-Z0-9_]+$'
ignore_functions:
  - myLogger
ignore_named_arguments:
  - analyticsName

How keys are generated

The key is derived from the text itself, so it stays the same on every run:

String in code Key
'Sign in' sign_in
'Sign in to your account' sign_in_to_your_account
'Hello $userName, you have 3 messages' hello_you_have_3_messages
'حساب جديد' text_dff2b9 (hash fallback for non-Latin text)
  • Keys are capped at max_key_words words.
  • Two different strings that slugify to the same key get _2, _3, ... appended.
  • If a key already exists in the source locale file with the same text, that key is reused — so renaming a key by hand survives the next run.
  • Interpolations become named placeholders: 'Hi $name'"Hi {name}". In ARB output the matching @key.placeholders metadata is written too.

What is skipped

The scanner parses real Dart AST (via package:analyzer), not regexes, so it can tell what a literal is actually used for. It skips:

  • import / export / part URIs and annotations
  • asset paths, URLs, emails, hex colors, mime types, date patterns
  • identifier-like strings: user_name, app.title, /home
  • map keys ({'user_id': ...}) and index keys (json['name']) — values are still extracted
  • equality comparisons: if (status == 'pending')
  • strings that are only a placeholder ('$value') or a URL template ('$baseUrl/cars/$id')
  • named arguments like key:, fontFamily:, restorationId:
  • arguments of calls that never take copy: debugPrint, RegExp, ValueKey, pushNamed, Image.asset, ...
  • case 'value': labels and assert(..., 'message') messages
  • strings that are already localized: 'key'.tr()
  • generated files: *.g.dart, *.freezed.dart, generated/, build/
  • the output folder itself

Everything on that list is configurable through l10n_extractor.yaml.

Re-running is safe

  • Existing translations are never overwritten — only missing keys are appended.
  • An empty value in the source locale is backfilled with the original text.
  • Nothing is deleted unless you pass --prune.
  • Output is sorted, so diffs stay small.

Generated keys class

With --keys-class lib/generated/locale_keys.g.dart you also get:

abstract final class LocaleKeys {
  const LocaleKeys._();

  static const String emailAddress = 'email_address';
  /// Placeholders: {userName}
  static const String helloYouHave3NewMessages = 'hello_you_have_3_new_messages';
  static const String signIn = 'sign_in';
}

So call sites read Text(LocaleKeys.signIn.tr()) instead of a raw string.

Wiring the files into your app

The generated JSON is the layout easy_localization expects:

# pubspec.yaml
flutter:
  assets:
    - assets/translations/
await EasyLocalization.ensureInitialized();
runApp(EasyLocalization(
  supportedLocales: const [Locale('en'), Locale('ar')],
  path: 'assets/translations',
  fallbackLocale: const Locale('en'),
  child: const MyApp(),
));

With --format arb the files feed flutter gen-l10n / package:intl instead.

Using it as a library

Every piece is exported, so you can embed the scan in your own tooling:

import 'package:l10n_extractor/l10n_extractor.dart';

final result = Extractor(
  ExtractorConfig(
    projectRoot: Directory.current.path,
    include: const ['lib'],
    exclude: ExtractorConfig.defaultExclude,
    locales: const ['en', 'ar'],
    sourceLocale: 'en',
    outputDir: 'assets/translations',
    dryRun: true,
  ),
).run();

for (final entry in result.keyed) {
  print('${entry.key} -> "${entry.value}" (${entry.occurrences.length} places)');
}

Example

example/ is a small app with the typical mess — asset paths, route names, map literals, interpolation, Arabic text — plus the config and the locale files the tool produced from it.

dart run l10n_extractor extract example -y

Limitations

  • The tool writes translation files; it does not rewrite your widgets. Use --report (or the generated keys class) to migrate the call sites.
  • Plurals and genders are not detected — an interpolated count becomes a plain {count} placeholder.
  • A string built at runtime ('Total: ' + price) is extracted as Total: only.

Libraries

l10n_extractor
Extract hardcoded strings from a Dart/Flutter project into translation files.