language_helper 0.13.0-rc.7 
language_helper: ^0.13.0-rc.7 copied to clipboard
A Flutter package for easy multi-language app localization with automatic text extraction and translation support.
Language Helper #
A Flutter package for easy multi-language app localization with automatic text extraction and translation support.
Features #
- π Easy Setup: Add 
.trand.trPto any string for instant translation - π Auto Extraction: Automatically extract all translatable text from your Dart files
 - π― Smart Translation: Control translations with conditions and parameters
 - π Multiple Sources: Support for Dart maps, JSON files, assets, and network data
 - π± Device Locale: Automatically uses device locale on first launch
 - π§ AI Integration: Custom translator for easy language conversion
 - π¨ LanguageScope: Provide scoped 
LanguageHelperinstances to specific widget trees - βοΈ LanguageImprover: Visual translation editor for on-device translation improvement
 
Quick Start #
1. Add to your project #
flutter pub add language_helper
2. Initialize (while developing) #
final languageHelper = LanguageHelper.instance;
main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await languageHelper.initial(data: []);
  runApp(const MyApp());
}
3. Add translations to your strings #
// Simple translation
Text('Hello World'.tr)
// With parameters
Text('Hello @{name}'.trP({'name': 'John'}))
// With conditions
Text('You have @{count} item'.trP({'count': itemCount}))
4. Wrap your app #
class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LanguageBuilder(
      builder: (context) {
        return MaterialApp(
          localizationsDelegates: languageHelper.delegates,
          supportedLocales: languageHelper.locales,
          locale: languageHelper.locale,
          home: const HomePage(),
        );
      }
    );
  }
}
Generate Translations #
The generator automatically scans your project for text using language_helper extensions (tr, trP, trT, trF) and translate method, then creates organized translation files with your existing translations preserved.
Note: The generator is smart about managing translations:
- β Keeps existing translations - Your current translated texts are preserved
 - π Marks new texts with TODO - Only untranslated texts get TODO markers
 - ποΈ Removes unused texts - Automatically cleans up translations no longer used in your code
 
Add Generator Dependency #
First, add the generator to your pubspec.yaml:
dev_dependencies:
  language_helper_generator: ^0.7.0
Then run:
flutter pub get
Basic Generation #
Extract all translatable text and generate language files:
dart run language_helper:generate --languages=en,vi,fr --ignore-todo=en
This creates:
lib/languages/
βββ codes.dart
βββ data/
    βββ en.dart // without TODO markers for missing translations
    βββ vi.dart // with TODO markers for missing translations
    βββ fr.dart // with TODO markers for missing translations
JSON Generation #
For assets or network-based translations:
dart run language_helper:generate --languages=en,vi,fr --json
Creates:
assets/languages/
βββ codes.json
βββ data/
    βββ en.json
    βββ vi.json
    βββ fr.json
JSON files do not support TODO markers. To identify untranslated or new strings, look for entries where the key and value are identical.
Generator Options #
| Option | Description | Example | 
|---|---|---|
--languages | 
Language codes to generate | --languages=en,vi,es | 
--ignore-todo | 
Skip TODO markers for specific languages | --ignore-todo=en | 
--path | 
Custom output directory | --path=./lib/languages | 
--json | 
Generate JSON files instead of Dart | --json | 
Common Examples #
Skip TODOs in English (your base language):
dart run language_helper:generate --languages=en,vi --ignore-todo=en
Custom output path:
dart run language_helper:generate --path=./lib/languages --languages=en,vi
Generate for multiple languages:
dart run language_helper:generate --languages=en,vi,es,fr --ignore-todo=en
Using Generated Data #
Dart Map #
final languageDataProvider = LanguageDataProvider.lazyData(languageData);
main() async {
  await languageHelper.initial(data: [languageDataProvider]);
  runApp(const MyApp());
}
JSON Assets #
final languageDataProvider = LanguageDataProvider.asset('assets/languages');
main() async {
  await languageHelper.initial(data: [languageDataProvider]);
  runApp(const MyApp());
}
Network Data #
final languageDataProvider = LanguageDataProvider.network('https://api.example.com/translations');
main() async {
  await languageHelper.initial(data: [languageDataProvider]);
  runApp(const MyApp());
}
Manual Translation Setup #
Dart Map Example #
final en = {
  'Hello World': 'Hello World',
  'Hello @{name}': 'Hello @{name}',
  'You have @{count} item': LanguageConditions(
    param: 'count',
    conditions: {
      '1': 'You have one item',
      '_': 'You have @{count} items', // Default
    }
  ),
};
final vi = {
  'Hello World': 'Xin chΓ o thαΊΏ giα»i',
  'Hello @{name}': 'Xin chΓ o @{name}',
  'You have @{count} item': 'BαΊ‘n cΓ³ @{count} mα»₯c',
};
LazyLanguageData languageData = {
  LanguageCodes.en: () => en,
  LanguageCodes.vi: () => vi,
};
JSON Example #
assets/languages/codes.json:
["en", "vi"]
assets/languages/data/en.json:
{
  "Hello World": "Hello World",
  "Hello @{name}": "Hello @{name}",
  "You have @{count} item": {
    "param": "count",
    "conditions": {
      "1": "You have one item",
      "_": "You have @{count} items"
    }
  }
}
Don't forget to add to pubspec.yaml:
flutter:
  assets:
    - assets/languages/codes.json
    - assets/languages/data/
Language Control #
Change Language #
languageHelper.change(LanguageCodes.vi);
Add New Language Data #
languageHelper.addData(LanguageDataProvider.lazyData(newLanguageData));
Listen to Changes #
final sub = languageHelper.stream.listen((code) => print('Language changed to: $code'));
// Remember to cancel: sub.cancel()
Get Supported Languages #
final codes = languageHelper.codes; // All supported languages
final overrides = languageHelper.codesOverrides; // Override languages
Advanced Usage #
Generator Features #
- Fast: Uses Dart Analyzer, no build_runner dependency
 - Smart: Preserves existing translations
 - Organized: Groups translations by file path
 - Helpful: Adds TODO markers for missing translations
 - Clean: Removes unused translation keys automatically
 
Custom Paths #
# Custom output path
dart run language_helper:generate --path=./lib/languages --languages=en,vi
# Generate JSON to assets folder
dart run language_helper:generate --path=./assets/languages --languages=en,vi --json
Multiple Data Sources #
main() async {
  await languageHelper.initial(
    data: [
      // Assume that our `code.json` in `https://api.example.com/translations/code.json`
      // So our data will be in `https://api.example.com/translations/data/en.json`
      LanguageDataProvider.network('https://api.example.com/translations'),
      // Assume that our `code.json` in `assets/languages/code.json`
      // So our data will be in `assets/languages/en.json`
      LanguageDataProvider.asset('assets/languages'),
      
      LanguageDataProvider.lazyData(localLanguageData),
    ],
  );
  runApp(const MyApp());
}
Data Priority: When multiple sources contain the same translation:
- First source wins - Data sources are processed in order (top to bottom) for the entire source
 - Specific overrides - To override individual translations, use
 dataOverridesinstead of adding todata- AddData behavior - New data can overwrite existing translations (controlled by
 overwriteparameter)
Widget Rebuilding #
LanguageBuilder(
  builder: (context) => Text('Hello'.tr),
)
Tr((_) => Text('Hello'.tr))
Force Rebuild and Tree Refresh #
forceRebuild Parameter
By default, only the root LanguageBuilder widget rebuilds when the language changes for better performance. Use forceRebuild: true to force a specific widget to always rebuild:
LanguageBuilder(
  forceRebuild: true, // This widget will always rebuild on language change
  builder: (context) => Text('Hello'.tr),
)
trueβ Always rebuild this widget when language changesfalseβ Only rebuild the root widget (default behavior)nullβ Fallback toLanguageHelper.forceRebuilddefault
refreshTree Parameter
Use refreshTree: true to completely refresh the widget tree using KeyedSubtree. This changes the key of the current tree so the entire tree is removed and recreated:
LanguageBuilder(
  refreshTree: true, // Uses KeyedSubtree to refresh entire tree
  builder: (context) => MyComplexWidget(),
)
β οΈ Performance Warning:
refreshTreecauses the entire widget tree to be destroyed and recreated, which can be expensive for complex widgets. This may lead to:
- Loss of widget state and animations
 - Poor performance with large widget trees
 - Unnecessary rebuilds of child widgets
 π‘ Note: If you use
constwidgets nested inside aLanguageBuilder, they may not rebuild automatically when the root rebuilds. To ensure these widgets update on language change (without usingrefreshTree), wrap them in their ownLanguageBuilderwithforceRebuild: true.
Use refreshTree only when you specifically need to reset widget state or when dealing with widgets that don't properly handle language changes.
LanguageScope - Scoped LanguageHelper #
LanguageScope allows you to provide a custom LanguageHelper instance to a specific part of your widget tree. When you wrap a widget tree with LanguageScope, all tr, trP, LanguageBuilder, and Tr widgets within that scope will automatically use the scoped helper instead of LanguageHelper.instance.
Basic Usage
final customHelper = LanguageHelper('CustomHelper');
await customHelper.initial(
  data: customLanguageData,
  initialCode: LanguageCodes.es,
);
LanguageScope(
  languageHelper: customHelper,
  child: MyWidget(),
)
How It Works
When LanguageScope is present in the widget tree:
- LanguageBuilder and Tr - Automatically inherit the scoped helper (unless an explicit 
languageHelperis provided) - Extension methods (
tr,trP,trT,trF) - Use the scoped helper when called within aLanguageBuilder. When called outside aLanguageBuilder, they fall back toLanguageHelper.instance(which is always available) - Priority order: Explicit 
languageHelperparameter >LanguageScope>LanguageHelper.instance 
Extension Methods Behavior
Extension methods (tr, trP, trT, trF) always use a valid LanguageHelper instance:
- Inside 
LanguageBuilder: Use the helper associated with that builder (fromLanguageScope, explicit parameter, orLanguageHelper.instance) - Outside 
LanguageBuilder: Fall back toLanguageHelper.instance(always available) 
This ensures that extension methods never fail and always have a helper to work with, making them safe to use anywhere in your code.
Example: Scoped Translation
final adminHelper = LanguageHelper('AdminHelper');
final userHelper = LanguageHelper('UserHelper');
await adminHelper.initial(data: adminTranslations, initialCode: LanguageCodes.en);
await userHelper.initial(data: userTranslations, initialCode: LanguageCodes.vi);
// Admin section uses admin translations
LanguageScope(
  languageHelper: adminHelper,
  child: LanguageBuilder(
    builder: (context) => Column(
      children: [
        Text('Admin Panel'.tr), // Uses adminHelper
        Text('Manage Users'.trP({'count': 5})), // Uses adminHelper
      ],
    ),
  ),
)
// User section uses user translations
LanguageScope(
  languageHelper: userHelper,
  child: LanguageBuilder(
    builder: (context) => Column(
      children: [
        Text('Dashboard'.tr), // Uses userHelper
        Text('Welcome @{name}'.trP({'name': 'John'})), // Uses userHelper
      ],
    ),
  ),
)
Accessing Scoped Helper
You can access the scoped helper directly from context:
// Gets the scoped helper or falls back to LanguageHelper.instance
// Always returns a valid helper since LanguageHelper.instance is always available
final helper = LanguageHelper.of(context);
final translated = helper.translate('Hello');
Priority with Explicit Helper
If you provide an explicit languageHelper parameter, it takes priority over the scope:
LanguageScope(
  languageHelper: scopedHelper, // This will be ignored
  child: LanguageBuilder(
    languageHelper: explicitHelper, // This takes priority
    builder: (_) => Text('Hello'.tr), // Uses explicitHelper
  ),
)
Nested Scopes
Child scopes override parent scopes for their subtree:
LanguageScope(
  languageHelper: parentHelper, // Parent scope
  child: LanguageBuilder(
    builder: (_) => Column(
      children: [
        Text('Hello'.tr), // Uses parentHelper
        LanguageScope(
          languageHelper: childHelper, // Child scope overrides parent
          child: LanguageBuilder(
            builder: (_) => Text('Hello'.tr), // Uses childHelper
          ),
        ),
      ],
    ),
  ),
)
Tr Widget with Scope
The Tr widget also inherits from LanguageScope:
LanguageScope(
  languageHelper: scopedHelper,
  child: Tr((_) => Text('Hello'.tr)), // Uses scopedHelper
)
Use Cases
- Multi-tenant apps: Different translation sets for different user types
 - Feature modules: Separate translations for different app modules
 - A/B testing: Different translations for different user groups
 - Admin panels: Specialized translations for admin interfaces
 - Overrides: Temporarily override translations in specific sections
 
LanguageImprover - Visual Translation Editor #
LanguageImprover is a widget that provides a user-friendly interface for viewing, comparing, and editing translations. It's perfect for translators, QA teams, or anyone who needs to improve translations directly in the app.
Features
- π Side-by-side comparison: View reference and target translations together
 - π Search functionality: Quickly find translations by key or content
 - βοΈ Inline editing: Edit translations directly in the interface
 - π Auto-scroll: Automatically scroll to specific translation keys
 - πΎ Update callback: Receive updated translations via callback
 - π― Flash animation: Visual highlight for keys being focused
 
Basic Usage
dart pub add language_improver
LanguageImprover(
  languageHelper: LanguageHelper.instance,
  onTranslationsUpdated: (updatedTranslations) {
    // Handle the improved translations
    // updatedTranslations: Map<LanguageCodes, Map<String, dynamic>>
    for (final entry in updatedTranslations.entries) {
      final code = entry.key;
      final translations = entry.value;
      
      // Create a LanguageDataProvider from updated translations
      final provider = LanguageDataProvider.data({
        code: translations,
      });
      
      // Add translations as overrides
      LanguageHelper.instance.addDataOverrides(provider);
    }
  },
)
Parameters
| Parameter | Type | Description | 
|---|---|---|
languageHelper | 
LanguageHelper? | 
The LanguageHelper instance to use. Defaults to LanguageHelper.instance | 
onTranslationsUpdated | 
FutureOr<void> Function(Map<LanguageCodes, Map<String, dynamic>>)? | 
Callback called when translations are saved. Receives a map of language codes to updated translations | 
onCancel | 
VoidCallback? | 
Callback called when the user cancels editing | 
initialDefaultLanguage | 
LanguageCodes? | 
Initial reference language. Defaults to first available language | 
initialTargetLanguage | 
LanguageCodes? | 
Initial target language to improve. Defaults to current language | 
scrollToKey | 
String? | 
Key to automatically scroll to and focus on | 
autoSearchOnScroll | 
bool | 
Whether to automatically search for scrollToKey. Defaults to true | 
showKey | 
bool | 
Whether to show the translation key. Defaults to true | 
Example: Navigate and Open
ElevatedButton(
  onPressed: () {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => LanguageImprover(
          languageHelper: LanguageHelper.instance,
          initialDefaultLanguage: LanguageCodes.en,
          initialTargetLanguage: LanguageCodes.vi,
          scrollToKey: 'Hello World', // Automatically scroll to this key
          onTranslationsUpdated: (updatedTranslations) {
            // Apply updated translations
            for (final entry in updatedTranslations.entries) {
              final provider = LanguageDataProvider.data({
                entry.key: entry.value,
              });
              LanguageHelper.instance.addDataOverrides(provider);
            }
            
            // Show success message
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text('Translations updated!'.tr),
                backgroundColor: Colors.green,
              ),
            );
          },
          onCancel: () {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text('Translation editing cancelled.'.tr),
              ),
            );
          },
        ),
      ),
    );
  },
  child: Text('Improve Translations'.tr),
)
Example: Scroll to Specific Key
LanguageImprover(
  languageHelper: LanguageHelper.instance,
  scrollToKey: 'Welcome Message', // Scrolls to this key on load
  autoSearchOnScroll: true, // Automatically filters to this key
  onTranslationsUpdated: (translations) {
    // Handle updates
  },
)
Example: Hide Translation Keys
LanguageImprover(
  languageHelper: LanguageHelper.instance,
  showKey: false, // Hide translation keys, only show translations
  onTranslationsUpdated: (translations) {
    // Handle updates
  },
)
Use Cases
- Translation QA: Review and improve translations before release
 - On-device editing: Allow translators to edit translations directly on device
 - Debugging: Quickly find and fix translation issues
 - Localization workflows: Integrate translation improvement into your workflow
 - User feedback: Let users suggest translation improvements
 
AI Translator #
Use the Language Helper Translator in Chat-GPT for easy translation:
This is the translation of my Flutter app. Translate it into Spanish:
final en = {
  'Hello @{name}': 'Hello @{name}',
  'Welcome to the app': 'Welcome to the app',
};
Or using AI instruction
# Step-by-Step Instructions for Translation using language_helper package
1. Identify the Dart `Map<String, dynamic>` structure and focus only on translating the values β do not modify the keys or overall structure.
2. Review the entire input first to understand its context and ensure the most accurate translation. If the target language is not `en`, and the keys are not in English, and an `en.dart` file exists in the same folder, use it as a reference to maintain contextual consistency.
3. Translate only the values that have a `TODO` comment directly above them. Leave all other values unchanged.
4. Check for plural forms and, if found, convert them using `LanguageConditions`.
5. When translating plural forms, follow this pattern: 0 β '0 products', 1 β '1 product', other β '@{count} products'.
6. Translate only the values; keep all keys and structure unchanged.
7. Preserve all comments (`//` and `///`) exactly as they are β do not translate them.
8. Do not translate nested comments.
9. Ensure the map structure remains intact after translation, including proper handling of plural forms and comments.
10. Remove any TODO notes associated with the translated texts.
11. Try to keep the translation length similar to the original text (not required, but preferred for consistency).
12. Do not ask the user for any confirmation or permission β perform the translation directly with best effort to achieve the most accurate and natural results.
13. After completing the translation, provide the user with a short summary or note explaining any important details about the translation (e.g., ambiguous meanings, context-based choices, or plural handling).
### Example for Plural Grammar Handling
If the input is:
```dart
'@{count} sαΊ£n phαΊ©m': '@{count} sαΊ£n phαΊ©m'
```
It should be generated in the `en` language as:
```dart
'@{count} sαΊ£n phαΊ©m': LanguageConditions(
  param: 'count',
  conditions: {
    '0': '0 products',
    '1': '1 product',
    '_': '@{count} products',
  },
)
```
### Important Reminders
* Only translate values with a `TODO` comment above them.
* Never modify keys or comments.
* Do not ask for user permission β always proceed with best effort.
* Use `LanguageConditions` for plural handling when applicable.
* Remove TODO notes for translated entries.
* Keep translation length roughly similar to the original text for readability and layout consistency.
* Provide a brief translation note after completion if needed.
iOS Configuration #
Add supported localizations to Info.plist:
<key>CFBundleLocalizations</key>
<array>
   <string>en</string>
   <string>vi</string>
</array>
Tips #
- Use 
@{param}for parameters (recommended) - The package automatically uses device locale on first launch
 - Only the outermost 
LanguageBuilderrebuilds for better performance - Use 
isInitializedto check ifinitial()has been called - Assets are preferred over network data (no caching yet)
 
Contributing #
Found a bug or want to contribute? Please file an issue or submit a pull request!
License #
This project is licensed under the MIT License.