language_extract 1.0.4 copy "language_extract: ^1.0.4" to clipboard
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 const removal — when a string inside a const widget is replaced, the const keyword 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 contains pubspec.yaml and lib/. Do not point it at the lib/ folder itself, or the tool will fail looking for lib/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):

  1. pubspec.yaml:

    flutter:
      assets:
        - assets/translations/
    
  2. 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(),
        ),
      );
    }
    
  3. 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):

  1. Create l10n.yaml in your project root:

    arb-dir: lib/l10n
    template-arb-file: app_en.arb
    output-localization-file: app_localizations.dart
    
  2. Add to pubspec.yaml:

    flutter:
      generate: true
    
  3. Run flutter gen-l10n to generate AppLocalizations.


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:

  • const removed from Text widgets (.tr is a runtime call, so const is invalid)
  • import 'package:get/get.dart'; added automatically
  • Dynamic $user mapped to trParams with .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 to snake_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, _3 suffixes

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 scans lib/ 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

1
likes
140
points
12
downloads

Documentation

API reference

Publisher

verified publisherusamaahsan.dev

Weekly Downloads

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.

Repository (GitHub)
View/report issues

Topics

#flutter #localization #i18n #getx #cli

License

MIT (license)

Dependencies

analyzer, args, path

More

Packages that depend on language_extract