Motrgem - Flutter L10n Text Extractor
A powerful command-line tool that automatically extracts hardcoded text strings from Flutter widgets and converts them to l10n (localization) format.
Installation
Option 1: Quick Install (Recommended for Dev Dependency)
Run this command in your Flutter project:
flutter pub add dev:motrgem
This automatically adds motrgem to your dev_dependencies and runs flutter pub get.
Option 2: Global Installation (Recommended for CLI Tool)
Install globally to use the motrgem command anywhere:
dart pub global activate motrgem
Verify installation:
motrgem --help
Option 3: Manual Installation
Add to your pubspec.yaml:
dev_dependencies:
motrgem: ^1.1.1
Then run:
flutter pub get
Command Usage:
- Global install:
motrgem start,motrgem --replace - Dev dependency:
dart run motrgem start,dart run motrgem --replace
π€ AI Prompt
Using an AI coding assistant (Claude Code, Cursor, Copilot Chat, etc.) in your Flutter project? Paste this prompt in and it will drive motrgem for you correctly, including the manual review steps the CLI can't do on its own:
This Flutter project should use the `motrgem` package to extract hardcoded
text into Flutter's l10n system. Please:
1. Add it as a dev dependency: `flutter pub add dev:motrgem`
2. If the project has no `l10n.yaml` / `lib/l10n` yet, run:
`dart run motrgem start`
3. Run `dart run motrgem --dry-run` and show me what would be extracted
before changing anything.
4. If I approve, run `dart run motrgem --replace` to extract the strings,
update `lib/l10n/app_en.arb`, and rewrite the call sites.
5. After that finishes, run `flutter analyze` and fix anything it flags,
in particular:
- any remaining `const` on a widget that now calls
`AppLocalizations.of(context)!.xxx` (remove the `const`)
- any file missing the
`import 'package:<package_name>/l10n/app_localizations.dart';` import
- any ARB entry with `{value1}`/`{value2}`-style placeholders that needs
its generated method call reviewed for correct argument order
6. Run `flutter gen-l10n` (or `flutter pub get`) to regenerate
`AppLocalizations`, and confirm the app still builds.
7. Check `lib/l10n/possible_hardcoded_texts.txt` (motrgem writes it whenever
it finds text it couldn't safely auto-fix: custom-widget/exception
constructor args, `validator:` closures, const string lists). For each
entry, either localize it by hand or add it to `motrgem.yaml`'s
`extra_widgets`/`extra_text_params` so future runs handle it
automatically, then re-run `dart run motrgem --replace`.
8. Ask me which locales to add, then run
`dart run motrgem --add-locale <code>` for each one and tell me to
review the machine translations in `lib/l10n/app_<code>.arb` before
shipping.
Do not hand-write translations yourself β only use motrgem's ARB output.
Features
This project includes a powerful L10n Text Extractor library that automatically:
- π Analyzes your Flutter code using the Dart analyzer to find hardcoded text strings in widgets
- π·οΈ Generates unique IDs for each text string in camelCase format
- π Updates ARB files with extracted texts and metadata
- π Replaces hardcoded strings with
AppLocalizationscalls - π¦ Automatically adds imports for localization files
- π Supports multiple locales with easy locale file generation
Supported Widgets
The library extracts text from these common Flutter widgets:
TextAppBarTextButton,ElevatedButton,OutlinedButtonFloatingActionButtonTooltipSnackBar,AlertDialogListTile,ChipInputDecoration(with parameters likehintText,labelText, etc.)
Beyond SDK Widgets: Possible Hardcoded Text & Custom Sinks
Auto-extraction only ever touches the SDK widgets/parameters above β it never rewrites your own
reusable widgets, since it can't safely assume every call site has the same BuildContext
availability an SDK widget does. Real apps route a lot of copy through those custom widgets
anyway (e.g. MyStatCard(label: 'Bookings') that internally does Text(label)), so instead of
staying silent about it, every --dry-run/--replace run also scans and reports (without
auto-fixing) three common patterns:
- String arguments passed to a locally-declared class constructor motrgem doesn't recognize
(covers custom widgets and custom exception/error classes, e.g.
throw AuthError(message: 'Your session has expired.')) - String literals inside a
validator:closure (form-field validators) - Elements of a
const/plainList<String>variable (e.g.const kDaysOfWeek = ['Monday', ...])
If anything is found, you'll see a console summary and a full list written to
lib/l10n/possible_hardcoded_texts.txt β review it and either localize those spots by hand, or
configure motrgem to auto-handle them (next section).
Configuring extra sinks (motrgem.yaml)
To make motrgem treat your own design-system components exactly like Text/AppBar (full
extraction and auto-replacement, not just a report), add an optional motrgem.yaml at your
project root:
extra_widgets:
MyStatCard:
- label
- subtitle
extra_text_params:
- placeholder
extra_widgetsmaps a class name to the parameter names on it that hold display text.extra_text_paramsadds parameter names recognized on any widget (built-in or custom).
Widgets configured this way are also excluded from the possible-hardcoded-text report, since they're now fully handled instead of merely flagged.
Usage
Initialize Project (First Time Setup)
Initialize a Flutter project with l10n support:
dart run motrgem start
This command will:
- Add necessary dependencies to
pubspec.yaml(flutter_localizations, intl, analyzer, path, args) - Create
l10n.yamlconfiguration file - Create
lib/l10ndirectory - Create initial
app_en.arbfile - Enable
generate: truein pubspec.yaml
Extract texts (Dry Run)
See what would be extracted without making any changes:
dart run motrgem --dry-run
Extract and replace
Extract texts, update ARB file, and replace hardcoded strings in your code:
dart run motrgem --replace
flutter clean
flutter pub get
Const Keyword remove
You should delete all const widgets which use AppLocalizations:
Before:
const Text(AppLocalizations.of(context)!.helloWorld)
Should be After:
Text(AppLocalizations.of(context)!.helloWorld)
Add a new locale
Create a new locale file (e.g., Spanish, French, Arabic):
dart run motrgem --add-locale es
dart run motrgem --add-locale fr
dart run motrgem --add-locale ar
flutter clean
flutter pub get
Note: If using as a dev dependency, prefix all commands with
dart run, e.g.,dart run motrgem start
Changing Language at Runtime
After setting up localization, you can allow users to change the app language dynamically:
1. Make your app stateful with locale management:
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:your_app/l10n/app_localizations.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
// Static method to change locale from anywhere in the app
static void setLocale(BuildContext context, Locale newLocale) {
_MyAppState? state = context.findAncestorStateOfType<_MyAppState>();
state?.setLocale(newLocale);
}
}
class _MyAppState extends State<MyApp> {
Locale? _locale;
void setLocale(Locale locale) {
setState(() {
_locale = locale;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
locale: _locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const HomePage(),
);
}
}
2. Change language from anywhere in your app:
// Switch to Spanish
MyApp.setLocale(context, const Locale('es'));
// Switch to Arabic
MyApp.setLocale(context, const Locale('ar'));
// Switch to English
MyApp.setLocale(context, const Locale('en'));
3. Example: Language selector dropdown:
class LanguageSelector extends StatelessWidget {
const LanguageSelector({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: Localizations.localeOf(context).languageCode,
items: const [
DropdownMenuItem(value: 'en', child: Text('English')),
DropdownMenuItem(value: 'es', child: Text('EspaΓ±ol')),
DropdownMenuItem(value: 'ar', child: Text('Ψ§ΩΨΉΨ±Ψ¨ΩΨ©')),
DropdownMenuItem(value: 'fr', child: Text('FranΓ§ais')),
],
onChanged: (String? languageCode) {
if (languageCode != null) {
MyApp.setLocale(context, Locale(languageCode));
}
},
);
}
}
Getting Started
Quick Start (New Project)
- Create or navigate to your Flutter project
- Initialize l10n support:
dart run motrgem start
- Install dependencies:
flutter pub get
- Extract and replace texts:
dart run motrgem --replace
Prerequisites
- Flutter SDK installed
Setup Localization
- The project already includes
l10n.yamlconfiguration:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
- In your
pubspec.yaml, ensure you have:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.20.2
flutter:
generate: true
- After running the extractor with
--replace, add localization delegates to yourMaterialApp:
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
// ... other properties
)
How It Works
1. Text Extraction
The library uses the Dart analyzer package to parse your Flutter code's Abstract Syntax Tree (AST). It identifies:
- Direct string literals in Text widgets
- Named parameters containing text (like
title:,tooltip:,label:) - Strings in various widget constructors
2. ID Generation
Text strings are converted to camelCase IDs:
- "Hello World" β
helloWorld - "You have pushed the button" β
youHavePushedTheButton - "Sign In" β
signIn
The generator:
- Removes special characters
- Handles duplicates by appending numbers
- Ensures valid Dart identifiers
3. ARB File Management
Extracted texts are added to lib/l10n/app_en.arb:
{
"@@locale": "en",
"youHavePushedTheButton": "You have pushed the button this many times:",
"@youHavePushedTheButton": {
"description": "Text from Text in main.dart"
}
}
4. Code Replacement
Original code:
Text('You have pushed the button this many times:')
Becomes:
Text(AppLocalizations.of(context)!.youHavePushedTheButton)
Project Structure
lib/
βββ main.dart # Main app file
βββ l10n/
β βββ app_en.arb # English translations
βββ src/
βββ utils/
βββ Text_extractor.dart # Core analyzer and extractor
βββ arb_manager.dart # ARB file operations
βββ l10n_manager.dart # Workflow orchestration
bin/
βββ motrgem.dart # CLI tool
l10n.yaml # L10n configuration
Library Components
TextExtractor
Analyzes Dart files using the analyzer package to find hardcoded strings.
final extractor = TextExtractor();
final texts = await extractor.extractTextFromProject(projectPath);
ArbManager
Manages ARB file operations (reading, writing, adding locales).
final arbManager = ArbManager(projectPath: projectPath);
await arbManager.addTextsToArb(texts);
L10nManager
Orchestrates the complete workflow.
final manager = L10nManager(projectPath);
final result = await manager.processProject(replaceInCode: true);
Example Output
Initialize Command
π Initializing Flutter L10n in project...
π Setup Results:
β
Updated pubspec.yaml with dependencies
β
Created l10n.yaml configuration
β
Created lib/l10n directory
β
Created initial ARB file (app_en.arb)
β
Project initialized successfully!
π Next steps:
1. Run: flutter pub get
2. Run: dart run motrgem --dry-run
3. Run: dart run motrgem --replace
Extract Command
π Flutter L10n Text Extractor
ββββββββββββββββββββββββββββββββββββββββββββββββββ
π Extracting texts from project: .
π Found 2 hardcoded text(s)
π Extracted texts:
lib/main.dart:107:24 - [Text] "You have pushed the button..." -> youHavePushedTheButton
lib/main.dart:117:18 - [FloatingActionButton.tooltip] "Increment" -> increment
π Updating ARB file...
ARB file updated: lib/l10n/app_en.arb
Added 2 text entries
π Replacing texts in code...
β
Replaced in main.dart: "You have pushed the button..."
β
Replaced in main.dart: "Increment"
π¦ Added import to main.dart
β¨ Summary:
- Texts extracted: 2
- Texts replaced: 2
π ARB Statistics:
- Total entries: 2
- With metadata: 2
ββββββββββββββββββββββββββββββββββββββββββββββββββ
β
Process completed successfully!
Advanced Usage
Filtering Technical Strings
The library automatically skips:
- URLs (
http://,https://,www.) - File paths
- Numbers-only strings
- ALL_CAPS constants
- Format strings (
%s,%d) - Template strings with
${}
Handling Duplicates
When the same base ID would be generated multiple times, the library automatically appends numbers:
- First occurrence:
buttonText - Second occurrence:
buttonText2 - Third occurrence:
buttonText3
Development
Running Tests
dart test
Adding New Widget Support
Edit lib/src/utils/Text_extractor.dart and add to the textWidgets set:
static const textWidgets = {
'Text',
'YourCustomWidget',
// ... more widgets
};
Adding New Text Parameters
Add to the textParams set in _isTextParameter():
const textParams = {
'title',
'yourCustomParam',
// ... more parameters
};
Contributing
Feel free to submit issues and enhancement requests!
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Built with the Dart
analyzerpackage - Uses Flutter's official
intlpackage for localization - Follows Flutter localization best practices