language_extract 1.0.4
language_extract: ^1.0.4 copied to clipboard
CLI tool that scans Flutter projects for hardcoded strings and generates localization files for GetX, easy_localization, and intl. Supports --replace to rewrite source files with .tr calls automatically.
language_extract #
Stop translating by hand. Extract, generate, replace — done.
language_extract is a Dart CLI tool that scans a Flutter project's source code for hardcoded UI strings, generates localization files for your chosen i18n package, and optionally rewrites every string literal with the correct localized call — including dynamic strings with $variables.
Features #
- AST-based scanning — uses the Dart analyzer, not regex. Understands context: only extracts strings inside UI widgets (
Text,AppBar,SnackBar,AlertDialog, …), not route names, asset paths, or code identifiers. - Three i18n packages supported — GetX, easy_localization, intl (ARB).
- Handles interpolation —
'Hello $name'becomes'hello_name'.trParams({'name': name.toString()})(GetX) or'hello_name'.tr(namedArgs: {'name': name.toString()})(easy_localization). - Idempotent — safe to re-run. Source locale files are always regenerated; non-source locale files merge new keys while preserving existing human translations.
- Auto
constremoval — when a string inside aconstwidget is replaced, theconstkeyword is removed automatically. - Auto import insertion — the required package import is added to every modified file.
- One-time setup hints — prints setup instructions on first run; silently skips them once your project is already wired up.
Installation #
Installation #
Install once, run from any directory:
dart pub global activate language_extract
If the language_extract command is not found after activation, you need to add the pub cache bin directory to your PATH. See the Dart guide for all platforms.
Then run from anywhere in your terminal:
language_extract getx /path/to/my_flutter_app --locales=en_US,ar_AR
Quick start #
Important:
<project_path>must be the Flutter project root — the folder that containspubspec.yamlandlib/. Do not point it at thelib/folder itself, or the tool will fail looking forlib/lib/.
# Correct — project root
language_extract getx ~/projects/myapp --locales=en_US,ar_AR
# Wrong — do not point at lib/
language_extract getx ~/projects/myapp/lib --locales=en_US,ar_AR
# 1. Scan and generate locale files (dry run — source untouched)
language_extract getx /path/to/app --locales=en_US,ar_AR
# 2. Also rewrite source files (adds .tr calls, removes const, adds imports)
language_extract getx /path/to/app --locales=en_US,ar_AR --replace
# 3. Use easy_localization instead
language_extract easy_localization /path/to/app --locales=en,ar --replace
# 4. Use the intl package
language_extract intl /path/to/app --locales=en_US,ar_AR --replace
Usage #
language_extract <package> <project_path> [options]
Packages:
getx GetX — generates lib/translations/*.dart
easy_localization easy_localization — generates assets/translations/*.json
intl intl / flutter gen-l10n — generates lib/l10n/app_*.arb
Options:
-l, --locales=<locales> Comma-separated list of locales [required]
e.g. --locales=en_US,ar_AR,fr_FR
-s, --source-locale=<locale> Which locale to use as the source (values from code).
Must be one of the --locales values.
Defaults to the first locale in the list.
-r, --replace Rewrite source files with localized calls.
-h, --help Show this help message.
Examples #
# GetX — English source, add Arabic
language_extract getx ~/projects/myapp --locales=en_US,ar_AR --source-locale=en_US
# easy_localization — three locales, rewrite source
language_extract easy_localization ~/projects/myapp \
--locales=en,ar,fr \
--source-locale=en \
--replace
# intl — default source locale (first in list)
language_extract intl ~/projects/myapp --locales=en_US,ar_AR --replace
What gets generated #
GetX #
lib/
translations/
en_US.dart ← source locale (values from code, always regenerated)
ar_AR.dart ← other locales (existing translations preserved,
new keys appended as empty strings)
app_translations.dart ← Translations subclass, register in GetMaterialApp
en_US.dart
const Map<String, String> enUS = {
'home_screen_welcome': 'Welcome',
'home_screen_hello_name': 'Hello @name',
};
ar_AR.dart (fill in your translations)
const Map<String, String> arAR = {
'home_screen_welcome': '', // ← translate here
'home_screen_hello_name': '',
};
app_translations.dart
import 'package:get/get.dart';
import 'en_US.dart';
import 'ar_AR.dart';
class AppTranslations extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': enUS,
'ar_AR': arAR,
};
}
One-time main.dart setup (printed on first run):
import 'translations/app_translations.dart';
GetMaterialApp(
translations: AppTranslations(),
locale: const Locale('en', 'US'),
fallbackLocale: const Locale('en', 'US'),
// ... rest of your config
)
easy_localization #
assets/
translations/
en.json ← source locale
ar.json ← other locales (merged)
en.json
{
"home_screen_welcome": "Welcome",
"home_screen_hello_name": "Hello {name}"
}
One-time setup (printed on first run):
-
pubspec.yaml:flutter: assets: - assets/translations/ -
main.dart:void main() async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); runApp( EasyLocalization( supportedLocales: [Locale('en'), Locale('ar')], path: 'assets/translations', fallbackLocale: Locale('en'), child: MyApp(), ), ); } -
In
MaterialApp:localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales,
intl (flutter gen-l10n) #
lib/
l10n/
app_en.arb ← source locale (values + metadata for gen-l10n)
app_ar.arb ← other locales (merged)
app_en.arb
{
"@@locale": "en",
"homeScreenWelcome": "Welcome",
"@homeScreenWelcome": { "description": "home_screen_welcome" },
"homeScreenHelloName": "Hello {name}",
"@homeScreenHelloName": {
"description": "home_screen_hello_name",
"placeholders": { "name": { "type": "String" } }
}
}
One-time setup (printed on first run):
-
Create
l10n.yamlin your project root:arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart -
Add to
pubspec.yaml:flutter: generate: true -
Run
flutter gen-l10nto generateAppLocalizations.
The --replace flag #
Without --replace, only locale files are written — your source code stays untouched. With --replace, every detected string literal is rewritten in place.
Before #
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final user = 'Alice';
return Scaffold(
appBar: AppBar(title: const Text('My Dashboard')),
body: Column(
children: [
Text('Welcome back, $user'),
const Text('Settings'),
],
),
);
}
}
After (GetX) #
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final user = 'Alice';
return Scaffold(
appBar: AppBar(title: Text('home_screen_my_dashboard'.tr)),
body: Column(
children: [
Text('home_screen_welcome_back_user'.trParams({'user': (user).toString()})),
Text('home_screen_settings'.tr),
],
),
);
}
}
Notice:
constremoved fromTextwidgets (.tris a runtime call, soconstis invalid)import 'package:get/get.dart';added automatically- Dynamic
$usermapped totrParamswith.toString()conversion (safe for int, double, etc.)
After (easy_localization) #
Text('home_screen_my_dashboard'.tr())
Text('home_screen_welcome_back_user'.tr(namedArgs: {'user': (user).toString()}))
After (intl) #
Text(AppLocalizations.of(context)!.homeScreenMyDashboard)
Text(AppLocalizations.of(context)!.homeScreenWelcomeBackUser((user).toString()))
Key format #
Keys are derived from the file name and string value:
| File | String | Key |
|---|---|---|
home_screen.dart |
"Welcome" |
home_screen_welcome |
home_screen.dart |
"Hello $name" |
home_screen_hello_name |
signup_screen.dart |
"Create your account today" |
signup_screen_create_your_account |
Rules:
- Prefix — file name without
.dart, converted tosnake_case - Suffix — up to 4 words from the string value, lowercased, joined by
_ - Deduplication — same string in the same file → same key (no duplicates in output)
- Collision resolution — different strings that map to the same key get
_2,_3suffixes
What gets skipped #
The scanner ignores strings that are clearly not UI text:
| Pattern | Example |
|---|---|
| Route strings | /home, /auth/login |
| URLs | https://api.example.com |
| Asset paths | assets/images/logo.png |
| Pure numbers | '42', '3.14' |
| Reverse-domain IDs | com.example.app |
camelCase identifiers |
myVariableName |
snake_case identifiers |
my_variable_name |
| Hex colors | #FF5733 |
| Single characters | '/', ':' |
| Strings not in UI context | Log messages, map keys, constants |
Idempotency & merge behavior #
| Locale type | Behavior on re-run |
|---|---|
| Source locale | Always regenerated from current source code |
| Other locales | Existing translations preserved; new keys appended as empty strings |
Re-run the tool at any time as your codebase grows. Translators can fill in non-source locale files between runs without losing their work.
Interpolation handling #
| Input | GetX output |
|---|---|
'Hello $name' |
'hello_name'.trParams({'name': (name).toString()}) |
'Order #${order.id}' |
'order_id'.trParams({'orderId': (order.id).toString()}) |
'Hi ${user.name ?? "Guest"}' |
'hi_user_name_guest'.trParams({'userNameGuest': (user.name ?? "Guest").toString()}) |
Locale file values use package-specific placeholder syntax:
| Package | Placeholder syntax |
|---|---|
| GetX | Hello @name |
| easy_localization | Hello {name} |
| intl | Hello {name} |
All dynamic values are wrapped in (expr).toString() — this ensures safety when the expression is an int, double, or uses null-coalescing (??) where operator precedence would otherwise cause issues.
Supported UI widgets #
The scanner detects strings in these built-in Flutter widgets:
Text · AppBar · SnackBar · AlertDialog · CupertinoAlertDialog · TextButton · ElevatedButton · OutlinedButton · FloatingActionButton · ListTile · DropdownMenuItem · InputDecoration · PopupMenuEntry · Tab · Tooltip · Card · Chip · Badge · NavigationDestination · BottomNavigationBarItem · DrawerHeader · ExpansionTile · DataColumn · DataCell · SelectableText
Custom widgets are also supported — any string passed to a recognized named parameter is extracted:
title: · label: · text: · message: · hintText: · labelText: · helperText: · errorText: · prefixText: · suffixText: · counterText: · tooltip: · semanticLabel: · buttonText: · confirmText: · cancelText: · placeholderText:
Requirements #
- Dart SDK
>=3.0.0 - A Flutter project with the standard structure (
pubspec.yaml+lib/at the root) - Pass the project root as
<project_path>— the tool automatically scanslib/and writes output relative to that root
Contributing #
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
Run from source #
git clone https://github.com/usamaahsan/flutter_language_extract.git
cd flutter_language_extract
dart pub get
dart run bin/language_extract.dart getx /path/to/my_flutter_app --locales en_US,ar_AR
Run tests #
dart test
Analyze #
dart analyze
License #
MIT