Localization
- Introduction
- Configuration
- Defining Translation Strings
- Retrieving Translations
- Pluralization
- Automatic HTTP Headers
- Changing Locale
- CLI Commands
- Development: Hot Restart
Introduction
Magic provides a Laravel-style localization system that allows you to easily retrieve strings in various languages without needing BuildContext. This is a game-changer for Flutter developers who are tired of passing context everywhere.
// Anywhere in your code - no context needed!
Text(trans('welcome', {'name': 'Magic'})) // "Welcome, Magic!"
Text(trans('auth.failed')) // "Authentication failed."
Configuration
Enabling Localization
Add LocalizationServiceProvider to your providers in config/app.dart:
'providers': [
(app) => LocalizationServiceProvider(app),
// ... other providers
],
Localization Configuration
Create lib/config/localization.dart:
Map get localizationConfig => {
'localization': {
'locale': 'en',
'fallback_locale': 'en',
'supported_locales': ['en', 'tr', 'es', 'de'],
'auto_detect_locale': true,
'path': 'assets/lang',
},
};
Configuration Options
| Key | Default | Description |
|---|---|---|
locale |
'en' |
Default locale |
fallback_locale |
'en' |
Fallback when translation is missing |
supported_locales |
['en'] |
Supported locale codes |
auto_detect_locale |
false |
Auto-detect from device on boot |
path |
'assets/lang' |
Translation JSON files path |
Register Assets
Add translation files to your pubspec.yaml:
flutter:
assets:
- assets/lang/en.json
- assets/lang/tr.json
- assets/lang/es.json
Defining Translation Strings
Translation strings are stored as JSON files in assets/lang/:
// assets/lang/en.json
{
"welcome": "Welcome, :name!",
"auth": {
"login": "Login",
"register": "Register",
"logout": "Logout",
"failed": "These credentials do not match our records.",
"throttle": "Too many login attempts. Please try again in :seconds seconds."
},
"validation": {
"required": "The :attribute field is required.",
"email": "The :attribute must be a valid email address.",
"min": {
"string": "The :attribute must be at least :min characters."
}
},
"attributes": {
"email": "email address",
"password": "password",
"password_confirmation": "password confirmation"
}
}
Nested Keys
Use dot notation to access nested translations:
trans('auth.failed') // "These credentials do not match..."
trans('validation.required') // "The :attribute field is required."
Retrieving Translations
Basic Usage
// Simple translation
WText(trans('auth.login'))
// With parameters
WText(trans('welcome', {'name': user.name}))
// Output: "Welcome, John!"
// Nested keys
WText(trans('auth.throttle', {'seconds': '60'}))
// Output: "Too many login attempts. Please try again in 60 seconds."
The trans() Helper
The trans() function is globally available:
String trans(String key, [Map? params])
- Returns the translation for the current locale
- Falls back to
fallback_localeif not found - Returns the key itself if no translation exists
- Replaces
:paramplaceholders with provided values
Using in Controllers
class AuthController extends MagicController {
Future login(Map data) async {
final response = await Http.post('/login', data: data);
if (response.successful) {
Magic.success(trans('common.success'), trans('auth.logged_in'));
} else {
Magic.error(trans('common.error'), trans('auth.failed'));
}
}
}
Automatic HTTP Headers
When LocalizationServiceProvider is registered, Magic automatically attaches two headers to every outgoing HTTP request via LocalizationInterceptor. No manual setup is required.
| Header | Value | Example |
|---|---|---|
Accept-Language |
Current locale language code | en, tr, es |
X-Timezone |
Current IANA timezone identifier | Europe/Istanbul, America/New_York |
Both values are resolved at request-time, so they always reflect the locale and timezone that are active when the request is dispatched.
// When the current locale is 'tr' and timezone is 'Europe/Istanbul',
// every Http.get/post/put/delete call automatically includes:
//
// Accept-Language: tr
// X-Timezone: Europe/Istanbul
//
// This works transparently — no configuration needed.
final response = await Http.get('/api/profile');
[!NOTE]
LocalizationInterceptoris registered automatically byLocalizationServiceProvider. You do not need to add it to your provider list or network configuration manually.
Changing Locale
Programmatically
// Change locale at runtime
await Lang.setLocale(Locale('tr'));
// Get current locale
final currentLocale = Lang.locale; // Locale('tr')
// Check if locale is supported
if (Lang.isSupported(Locale('fr'))) {
await Lang.setLocale(Locale('fr'));
}
Language Picker Example
WFormSelect(
value: Lang.locale.languageCode,
options: [
SelectOption(value: 'en', label: 'English'),
SelectOption(value: 'tr', label: 'Türkçe'),
SelectOption(value: 'es', label: 'Español'),
],
onChange: (code) async {
await Lang.setLocale(Locale(code!));
Magic.toast(trans('common.language_changed'));
},
label: trans('settings.language'),
)
CLI Commands
Create Translation File
dart run magic:magic make:lang fr
dart run magic:magic make:lang de
dart run magic:magic make:lang tr
This command:
- Creates
assets/lang/.jsonwith a starter template - Automatically registers the asset in
pubspec.yaml
Starter Template
The generated file includes common translation keys:
{
"welcome": "Welcome, :name!",
"common": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"success": "Success",
"error": "Error"
},
"auth": {
"login": "Login",
"register": "Register",
"logout": "Logout",
"failed": "Authentication failed.",
"throttle": "Too many attempts. Please try again in :seconds seconds."
},
"validation": {
"required": "The :attribute field is required.",
"email": "The :attribute must be a valid email address."
},
"attributes": {
"email": "email",
"password": "password"
}
}
[!TIP] Keep your translation keys organized by feature (auth, validation, users, etc.) for easier maintenance.
Development: Hot Restart
During development, Magic attempts to bypass Flutter's asset bundle cache so that translation JSON changes can be picked up on hot restart (Shift+R) without a full rebuild.
| Platform | Mechanism | Reliability |
|---|---|---|
| Web (Chrome) | Fetches JSON via HTTP with cache-busting query parameter | Verified |
| macOS / Linux / Windows | Reads JSON from disk via dart:io |
Best-effort (works when flutter run sets the working directory to the project root) |
| iOS / Android | Attempts disk read via dart:io, falls back to rootBundle |
Limited (asset files are typically not on disk) |
This behavior is debug-mode only (kDebugMode). Release builds use Flutter's standard rootBundle with full caching for optimal performance.
[!NOTE] Hot reload (lowercase
r) only reloads Dart code — it cannot pick up asset changes. Use hot restart (Shift+R) to see translation updates.