translation_manager 2.0.5
translation_manager: ^2.0.5 copied to clipboard
A lightweight, GetX-inspired translation library for Flutter with automatic locale detection and Bloc integration.
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