flutter_string_extractor 2.0.0
flutter_string_extractor: ^2.0.0 copied to clipboard
Dart CLI to extract Flutter localization strings into JSON. Supports GetX, Easy Localization, Intl and ARB import, preserving existing translations.
Flutter String Extractor — Flutter localization strings to JSON #
Extract static translation strings and keys from Flutter/Dart source into a JSON catalog. Repeated runs preserve your translations and old keys by default. Dart syntax parsing handles quotes, escapes, raw strings, multiline strings, and adjacent literals, while ignoring comments.
Requires Dart 3.9 or later. No Flutter SDK is needed to run the extractor. Source files are read only; extraction does not rewrite widgets or insert translation calls.
Use it to collect GetX or Easy Localization keys, discover hardcoded Flutter text before localization, or export an ARB catalog to a JSON dictionary. It runs locally without a translation service, AI model, or API key.
Package: flutter_string_extractor on pub.dev.
Source, issues, and releases: Reshail1/flutter_string_extractor on GitHub.
See the support table before choosing a
localization workflow.
Quick start: extract Flutter strings to JSON #
Install the published CLI and run it from your Flutter application root:
dart pub global activate flutter_string_extractor
flutter_string_extractor -s lib -o assets/translations/en.json
For this source:
Text('Welcome'.tr);
Text(tr('settings.title'));
the JSON dictionary is:
{
"Welcome": "Welcome",
"settings.title": "settings.title"
}
Edit the values to add translations. Subsequent runs keep translated values and
add missing keys. Preview first with --dry-run.
The expanded APIs and options below describe 2.0.0. If pub.dev does not yet list that version, install a repository checkout using the instructions below.
Install from source #
From this package directory:
dart pub get
dart run bin/main.dart -s /path/to/flutter_app/lib -o /path/to/flutter_app/assets/translations/en.json
For a command usable from any Flutter project:
dart pub global activate --source path .
# Run from your Flutter application's root:
flutter_string_extractor -s lib -o assets/translations/en.json
Add Dart's pub cache bin directory to your PATH if the activated command is
not found. The original library entry point and -s / -o options still work.
Supported translation patterns #
Matching uses syntax and exact method names, without resolving package imports. Calls with the same names in custom libraries are candidates too.
| Approach | Recognized examples |
|---|---|
| GetX | 'title'.tr, .trArgs([...]), .trParams({...}), 'one'.trPlural('many', count), .trPluralParams(...) |
| Easy Localization | 'title'.tr(), tr('title'), context.tr('title'), Text('title').tr(), const Text('title').tr() |
| Easy Localization plurals | 'items'.plural(count), plural('items', count), context.plural('items', count), Text('items').plural(count) |
| Flutter Translate | translate('title'), translatePlural('items', count) |
| FlutterI18n | FlutterI18n.translate(context, 'title'), .plural(context, 'items', count); literal fallbackKey values are included |
| i18n_extension | 'Hello'.i18n, 'One item'.plural(count) |
| Intl | Intl.message('Hello', name: 'hello') exports "hello": "Hello"; without a literal name, an unnamed static message uses its text as the key |
| gen-l10n / Flutter Intl | Import an ARB catalog with --from-arb for AppLocalizations, S.of(context), and similar generated APIs |
| Custom wrappers | --function localize for localize('title') / service.localize('title'); --getter localized for 'title'.localized or 'title'.localized() |
GetX plural calls include both literal singular and plural keys. Translation argument values, parameter names, and descriptions are not extracted in the default mode. Duplicate keys are combined, and conflicting named Intl messages abort the run before changing output.
References: GetX, Easy Localization, Flutter Translate, FlutterI18n, i18n_extension, Flutter internationalization.
Preview, discover, and filter #
# Preview the merged catalog; stdout contains JSON, stderr contains the summary.
flutter_string_extractor -s lib -o strings.json --dry-run
# Discover unmarked static literals such as Text('Welcome').
flutter_string_extractor -s lib -o strings.json --all-strings --dry-run
# Exclude files or directories using paths relative to the source directory.
flutter_string_extractor -s lib -o strings.json --exclude 'models/**' --exclude '**/debug_*.dart'
# Add your translation wrapper's exact method name.
flutter_string_extractor -s lib -o strings.json --function localize --getter localized
--all-strings includes static literals beyond UI text, such as URLs, test data,
and translation arguments. Review the preview before using them as translations.
Imports, exports, other directives, and runtime interpolation are excluded.
Marked calls inside interpolated expressions are still extracted.
Build/cache directories and source symlinks are never traversed. Generated
.g.dart, .freezed.dart, app_localizations*.dart, and messages_*.dart files
are skipped unless you pass --include-generated.
Generated localizations and ICU messages #
Generated getters do not contain the original message text. Use the source catalog instead:
flutter_string_extractor -s lib -o strings.json --from-arb lib/l10n/app_en.arb
All message entries from that one source-locale catalog are imported,
including messages not referenced in the scanned code. ARB metadata (@... and
@@...) is omitted; ICU plural/select/placeholder strings are copied intact.
Catalog values take precedence over identity placeholders extracted from code;
existing output translations still win unless you choose --replace.
This exports a JSON dictionary, not a gen-l10n ARB file. Keep your original ARB
files for their metadata and Flutter code generation. Use this same catalog
route for Intl.plural, Intl.gender, and Intl.select: the extractor reports
these calls rather than exporting incomplete plural or gender branches.
Computed keys, variables such as key.tr, constant references such as
LocaleKeys.title.tr, concatenated expressions, and interpolated messages
cannot be resolved by this syntax-only scan. They produce location diagnostics
when used in recognized translation calls. Supply literals or import the source
catalog; the extractor does not evaluate Dart code or guess their values.
Output and translation preservation #
For 'Hello'.tr and tr('Settings'), the initial catalog contains:
{
"Hello": "Hello",
"Settings": "Settings"
}
After translating Hello to Hallo, running extraction again keeps Hallo.
Removed source keys remain in the output. Existing nested catalogs and plural /
gender objects are preserved; newly discovered dotted keys are added inside an
existing nested namespace. New catalogs use flat keys. Missing translations
start as key-to-key placeholders; plural/gender placeholders need the correct
structure for your localization package before use.
To explicitly reset the catalog to current extraction results:
flutter_string_extractor -s lib -o strings.json --replace
Only .json output destinations are accepted. Missing output directories are
created, except in a dry run. Missing source, syntax errors, conflicting message
IDs, malformed catalogs, and unsafe destinations abort before writing. Existing
JSON must be an object even in replacement mode. Top-level keys are sorted.
Writes use a private temporary directory beside the destination and a
same-filesystem rename, with cleanup afterward. Output symlinks, symlinked parent
directories, and directory destinations are rejected. On macOS/Linux, existing
POSIX access permissions are retained and new files use owner-only permissions
(0600); /bin/chmod must be available. Windows uses filesystem permissions
inherited from the destination directory. Replacement can change file ownership
or extended ACLs; review those separately when sharing catalogs. Portable file
checks do not fully prevent races with another process changing paths.
Errors and warnings go to stderr. Exit codes: 0 for a successful extraction or
preview, 1 for extraction/file errors, 64 for incorrect CLI arguments.
Warnings about unresolved messages do not change the successful exit code.
Options #
| Option | Default / behavior |
|---|---|
-s, --src-dir |
lib |
-o, --output-file |
strings.json |
--all-strings |
Discover all static literals except directives |
--dry-run |
Print merged JSON without modifying files |
--replace |
Discard existing translations and old keys explicitly |
--exclude |
Repeatable source-relative glob; quote patterns in the shell |
--include-generated |
Include generated files; build/cache exclusions still apply |
--function |
Repeatable translator method name; key is the first positional argument |
--getter |
Repeatable String getter/extension method name |
--from-arb |
Source ARB catalog to import |
-h, --help |
Display CLI help |
Library usage #
import 'package:flutter_string_extractor/flutter_string_extractor.dart';
void main() {
final result = FlutterStringExtractor.extractStrings(
'lib',
'strings.json',
dryRun: true,
exclude: ['models/**'],
functions: ['localize'],
// sourceArb: 'lib/l10n/app_en.arb',
);
print(result.json);
for (final warning in result.warnings) {
print(warning);
}
}
The result includes translations, filesScanned, extractedCount,
addedCount, and warnings. Existing two-argument calls still write output.
Frequently asked questions #
How do I extract hardcoded Flutter text for localization? #
Run flutter_string_extractor -s lib -o strings.json --all-strings --dry-run.
Review the JSON preview: discovery also includes URLs and other non-UI literals.
Remove --dry-run when ready to write the catalog. Add translation calls to
your widgets separately; the extractor keeps source files unchanged.
Can I use it with AppLocalizations or S.of(context)? #
Yes, import the source ARB file with
--from-arb lib/l10n/app_en.arb. Generated getters cannot supply the original
messages to a syntax-only scan. The import includes every message in that
catalog and keeps ICU strings intact; it does not create a new ARB file.
Does it translate strings using AI or send my source to a server? #
No. Extraction parses local Dart files and reads local JSON/ARB catalogs. It creates translation placeholders; you provide the translated values. You do not need an AI subscription or translation API key to run it.
Does rerunning extraction overwrite my translations? #
Existing values, nested catalogs, and old keys remain by default. New keys are
added. --replace explicitly discards the previous catalog after validating it.
Why was a translation key skipped? #
Check stderr for the file and line. Variables, constant references, and
interpolation cannot be evaluated by this syntax-only extractor. Use literal
keys or import the source catalog. Custom wrapper names can be registered with
--function and --getter.
Which installed CLI version am I using? #
Run dart pub global list. To update a pub.dev installation, run
dart pub global activate flutter_string_extractor again.
Development #
dart pub get
dart analyze
dart test
dart test --test-randomize-ordering-seed=421
dart format --output=none --set-exit-if-changed lib bin/main.dart test example/main.dart
Tests exercise translation APIs, Unicode/escaped literals, ARB imports, nested catalogs, CLI errors, permissions, and unsafe destinations. A seeded stress case extracts 500 messages across 20 files and checks ten subsequent writes for identical output. CI runs the suite on Linux, macOS, and Windows with Dart 3.9.0 and stable. Formatting is checked with Dart 3.9.0 to keep the formatter consistent across SDK updates. Fork pull requests receive read-only CI permissions.
Publishing releases #
The publishing workflow runs when a version tag
such as v2.0.0 is pushed. It uses the official Dart reusable workflow and
short-lived OIDC authentication; no pub.dev credentials belong in this public
repository. Regular pushes to main run CI; publishing requires a version tag.
In the package's pub.dev admin settings,
enable automated publishing from GitHub for repository
Reshail1/flutter_string_extractor and tag pattern v{{version}}. This
publisher setting is required separately from committing the workflow.
See Dart's automated publishing guide.
After updating pubspec.yaml and CHANGELOG.md, wait for the main commit's CI
checks to pass. Confirm the tag version equals the package version and has not
already been published, then release it:
git tag v2.0.0
git push origin v2.0.0
For later releases, use the new package version in both commands. Restrict creation and modification of release tags to trusted maintainers in GitHub repository rulesets: the pub.dev publisher grants that repository permission to release the package. Never commit access tokens, pub credentials, or private application catalogs.
License #
MIT.