Flutter Translation Manager
A lightweight, GetX-inspired translation library for Flutter that provides simple internationalization with automatic locale detection and Bloc integration.
Features
✨ Simple API - Clean, intuitive syntax inspired by GetX
🌍 Locale Detection - Automatically detects user's device language
🔄 Dynamic Switching - Change languages on the fly
💾 Persistent Storage - Optional locale preference saving
🎯 Type Safe - Full Dart type safety
🔌 Bloc Integration - Works seamlessly with flutter_bloc
Installation
Add this to your package's pubspec.yaml file:
dependencies:
translation_manager: ^2.0.4
Optional: For state management example with Bloc:
dependencies:
flutter_bloc: ^8.1.3
equatable: ^2.0.5
Then run:
flutter pub get
Quick Start
1. Define Your Translations
Create a translations file (e.g., lib/translations/app_translations.dart):
import 'package:translation_manager/translation_manager.dart';
class AppTranslations extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'hello': 'Hello',
'welcome': 'Welcome @name',
'items': 'item',
'items_plural': 'items',
'greeting': 'Hello, @name! You have @count messages.',
},
'es_ES': {
'hello': 'Hola',
'welcome': 'Bienvenido @name',
'items': 'artículo',
'items_plural': 'artículos',
'greeting': 'Hola, @name! Tienes @count mensajes.',
},
'fr_FR': {
'hello': 'Bonjour',
'welcome': 'Bienvenue @name',
'items': 'article',
'items_plural': 'articles',
'greeting': 'Bonjour, @name! Vous avez @count messages.',
},
'de_DE': {
'hello': 'Hallo',
'welcome': 'Willkommen @name',
'items': 'Artikel',
'items_plural': 'Artikel',
'greeting': 'Hallo, @name! Du hast @count Nachrichten.',
},
'pt_BR': {
'hello': 'Olá',
'welcome': 'Bem-vindo @name',
'items': 'item',
'items_plural': 'itens',
'greeting': 'Olá, @name! Você tem @count mensagens.',
},
'ar_AE': {
'hello': 'مرحبا',
'welcome': 'مرحبا @name',
'items': 'عنصر',
'items_plural': 'عناصر',
'greeting': 'مرحبا، @name! لديك @count رسائل.',
},
};
}
2. Initialize in main.dart
import 'package:flutter/material.dart';
import 'package:translation_manager/translation_manager.dart';
import 'translations/app_translations.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize translations
final translations = AppTranslations();
await TranslationManager.init(
translations: translations.keys,
fallbackLocale: const Locale('en', 'US'),
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Translation Manager Demo',
locale: state.locale,
supportedLocales: context.read<LocaleCubit>().supportedLocales,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const HomePage(),
);
}
}
3. Use Translations in Your UI
import 'package:flutter/material.dart';
import 'package:translation_manager/translation_manager.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('hello'.tr), // Simple translation
),
body: Column(
children: [
// Translation with parameters
Text('welcome'.trParams({'name': 'John'})),
// Plural translation
Text('You have 5 ${'items'.trPlural('items_plural', 5)}'),
// Multiple parameters
Text('greeting'.trParams({
'name': 'Alice',
'count': '3',
})),
],
),
);
}
}
Usage
Basic Translation
'hello'.tr // Returns: "Hello" (or translated text)
Translation with Parameters
Use @paramName in your translation strings:
// In translations:
'welcome': 'Welcome @name'
// In code:
'welcome'.trParams({'name': 'John'}) // Returns: "Welcome John"
Plural Translation
// Define both singular and plural keys in your translations:
'item': 'item',
'item_plural': 'items',
// Usage:
'item'.trPlural('item_plural', 1) // Returns: "item"
'item'.trPlural('item_plural', 5) // Returns: "items"
// Real-world example:
int count = 3;
Text('You have $count ${'item'.trPlural('item_plural', count)}')
// Output: "You have 3 items"
Change Language
// Direct access to TranslationManager (works with any state management)
TranslationManager().changeLocale(const Locale('es', 'ES'));
// In a StatefulWidget
setState(() {
TranslationManager().changeLocale(const Locale('fr', 'FR'));
});
Note: See the State Management Integration section for examples with Bloc, Provider, Riverpod, etc.
Get Current Locale
final currentLocale = TranslationManager().locale;
print('Current language: ${currentLocale.languageCode}');
Configuration
Define Supported Locales
You can create a wrapper to manage supported locales:
class AppLocale {
static final supportedLocales = [
const Locale('en', 'US'),
const Locale('es', 'ES'),
const Locale('fr', 'FR'),
];
static void changeLocale(Locale locale) {
if (supportedLocales.contains(locale)) {
TranslationManager().changeLocale(locale);
}
}
}
Fallback Locale
Set a fallback locale for missing translations:
TranslationManager().setFallbackLocale(const Locale('en', 'US'));
If a translation key is missing in the current locale, it will use the fallback locale.
Advanced Features
State Management Integration
The library works with any state management solution. Here's an example with Bloc:
See the /example folder for a complete Bloc implementation with:
- Automatic device locale detection
- Locale state management with Cubit
- Language picker dialog
- Persistent storage with SharedPreferences
Other state management examples:
Provider
class LocaleProvider extends ChangeNotifier {
Locale _currentLocale = const Locale('en', 'US');
Locale get currentLocale => _currentLocale;
void changeLocale(Locale locale) {
_currentLocale = locale;
TranslationManager().changeLocale(locale);
notifyListeners();
}
}
// Usage
Provider.of<LocaleProvider>(context, listen: false).changeLocale(locale);
Riverpod
final localeProvider = StateNotifierProvider<LocaleNotifier, Locale>((ref) {
return LocaleNotifier();
});
class LocaleNotifier extends StateNotifier<Locale> {
LocaleNotifier() : super(const Locale('en', 'US'));
void changeLocale(Locale locale) {
state = locale;
TranslationManager().changeLocale(locale);
}
}
// Usage
ref.read(localeProvider.notifier).changeLocale(locale);
GetX
class LocaleController extends GetxController {
final _locale = const Locale('en', 'US').obs;
Locale get locale => _locale.value;
void changeLocale(Locale locale) {
_locale.value = locale;
TranslationManager().changeLocale(locale);
}
}
// Usage
Get.find<LocaleController>().changeLocale(locale);
Language Picker Dialog
import 'package:flutter/material.dart';
import 'package:translation_manager/translation_manager.dart';
void showLanguageDialog(BuildContext context) {
final supportedLocales = [
const Locale('en', 'US'),
const Locale('es', 'ES'),
const Locale('fr', 'FR'),
];
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Choose Language'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: supportedLocales.map((locale) {
return ListTile(
title: Text(_getLanguageName(locale)),
trailing: TranslationManager().locale == locale
? const Icon(Icons.check, color: Colors.green)
: null,
onTap: () {
TranslationManager().changeLocale(locale);
Navigator.pop(context);
// Trigger rebuild if using setState
(context as Element).markNeedsBuild();
},
);
}).toList(),
),
),
);
}
String _getLanguageName(Locale locale) {
const names = {
'en': 'English',
'es': 'Español',
'fr': 'Français',
};
return names[locale.languageCode] ?? locale.languageCode;
}
Persistent Locale Storage
To remember user's language preference between app sessions, add shared_preferences:
dependencies:
shared_preferences: ^2.2.0
Create a custom cubit with persistence:
import 'dart:ui';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:translation_manager/translation_manager.dart';
class PersistentLocaleCubit extends LocaleCubit {
final SharedPreferences _prefs;
static const String _localeKey = 'app_locale';
PersistentLocaleCubit(this._prefs) : super() {
_loadSavedLocale();
}
Future<void> _loadSavedLocale() async {
final savedLocaleCode = _prefs.getString(_localeKey);
if (savedLocaleCode != null) {
final parts = savedLocaleCode.split('_');
final locale = Locale(parts[0], parts.length > 1 ? parts[1] : null);
if (supportedLocales.contains(locale)) {
changeLocale(locale);
}
}
}
@override
void changeLocale(Locale locale) {
if (supportedLocales.contains(locale)) {
final localeCode = locale.countryCode != null
? '${locale.languageCode}_${locale.countryCode}'
: locale.languageCode;
_prefs.setString(_localeKey, localeCode);
super.changeLocale(locale);
}
}
}
Update main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final prefs = await SharedPreferences.getInstance();
final translations = AppTranslations();
await TranslationManager.init(
translations: translations.keys,
fallbackLocale: const Locale('en', 'US'),
);
runApp(MyApp(prefs: prefs));
}
class MyApp extends StatelessWidget {
final SharedPreferences prefs;
const MyApp({super.key, required this.prefs});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => PersistentLocaleCubit(prefs),
child: BlocBuilder<LocaleCubit, LocaleState>(
builder: (context, state) {
return MaterialApp(
locale: state.locale,
supportedLocales: context.read<LocaleCubit>().supportedLocales,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: const HomePage(),
);
},
),
);
}
}
Organize Large Translation Files
For apps with many translations, split them into multiple classes:
// lib/translations/home_translations.dart
class HomeTranslations extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'home.title': 'Home',
'home.welcome': 'Welcome back!',
},
'es_ES': {
'home.title': 'Inicio',
'home.welcome': '¡Bienvenido de nuevo!',
},
};
}
// lib/translations/settings_translations.dart
class SettingsTranslations extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'settings.title': 'Settings',
'settings.language': 'Language',
},
'es_ES': {
'settings.title': 'Configuración',
'settings.language': 'Idioma',
},
};
}
// lib/translations/app_translations.dart
class AppTranslations extends Translations {
@override
Map<String, Map<String, String>> get keys {
final home = HomeTranslations().keys;
final settings = SettingsTranslations().keys;
// Merge all translations
final merged = <String, Map<String, String>>{};
for (var locale in home.keys) {
merged[locale] = {
...?home[locale],
...?settings[locale],
};
}
return merged;
}
}
API Reference
TranslationManager
| Method | Description |
|---|---|
addTranslations(Map) |
Initialize translations with locale map |
changeLocale(Locale) |
Change current locale |
setFallbackLocale(Locale) |
Set fallback for missing translations |
translate(String, {params}) |
Get translation for key with optional params |
locale |
Get current locale |
fallbackLocale |
Get fallback locale |
String Extensions
| Extension | Description | Example |
|---|---|---|
.tr |
Basic translation | 'hello'.tr |
.trParams(Map) |
Translation with parameters | 'welcome'.trParams({'name': 'John'}) |
.trPlural(String, int) |
Plural translation | 'item'.trPlural('item_plural', 5) |
LocaleCubit
| Property/Method | Description |
|---|---|
state.locale |
Current locale |
supportedLocales |
List of supported locales |
changeLocale(Locale) |
Change app locale |
changeLocaleByCode(String, [String?]) |
Change locale by language code |
LocaleState
| State | Description |
|---|---|
LocaleInitial |
Initial state before locale detection |
LocaleLoaded(Locale) |
Locale has been loaded and set |
Best Practices
1. Key Naming Convention
Use hierarchical, dot-separated keys for better organization:
'home.title': 'Home'
'home.welcome': 'Welcome'
'settings.title': 'Settings'
'settings.language': 'Language'
'profile.edit': 'Edit Profile'
2. Parameter Naming
Use clear, descriptive parameter names:
// Good
'greeting': 'Hello, @userName! You have @messageCount new messages.'
// Avoid
'greeting': 'Hello, @a! You have @b new messages.'
3. Always Define Plurals
Define both singular and plural forms:
'message': 'message',
'message_plural': 'messages',
'notification': 'notification',
'notification_plural': 'notifications',
4. Set Fallback Locale
Always configure a fallback locale in your main.dart:
TranslationManager().setFallbackLocale(const Locale('en', 'US'));
5. Locale Code Format
Use the correct format: languageCode_COUNTRYCODE
// Correct
'en_US', 'es_ES', 'fr_FR', 'pt_BR'
// Incorrect
'en-US', 'en', 'EN_US'
Troubleshooting
Translation not showing
Problem: Translations return the key instead of translated text
Solutions:
- Verify the key exists in your translations map
- Check locale code format (must be
en_US, noten-US) - Ensure
TranslationManager().addTranslations()was called inmain() - Set a fallback locale:
TranslationManager().setFallbackLocale()
Locale not changing
Problem: Language doesn't change when calling changeLocale()
Solutions:
- Verify the locale exists in
supportedLocaleslist - Ensure
BlocBuilder<LocaleCubit, LocaleState>wraps yourMaterialApp - Check that
localeproperty is bound tostate.locale - Verify
localizationsDelegatesare added toMaterialApp
Parameters not replacing
Problem: Parameters like @name appear literally in translated text
Solutions:
- Verify parameter uses
@prefix:@namenot{name}or$name - Check parameter key in
trParams()matches the translation:'name'for@name - Ensure parameter values are strings:
{'count': '5'}not{'count': 5}
MaterialLocalizations Error
Problem: Error: "No MaterialLocalizations found"
Solution: Add localization delegates to MaterialApp:
MaterialApp(
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
// ...
)
Examples
Check out the /example folder for complete working examples:
- Basic Setup - Simple app with translations
- Language Picker - App with language selection dialog
- Persistent Storage - App that remembers language preference
- Large App - Example with organized translation files
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
License
MIT License - see the LICENSE file for details.
Support
- 📫 Report issues on GitHub Issues
- 💬 Discuss on GitHub Discussions
- ⭐ Star the repo if you find it useful!
Made with ❤️ for the Flutter community
Libraries
- translation_manager
- A lightweight translation library for Flutter inspired by GetX.