# Localization
- [Introduction](#introduction)
- [Configuration](#configuration)
- [Defining Translation Strings](#defining-translation-strings)
- [Retrieving Translations](#retrieving-translations)
- [Pluralization](#pluralization)
- [Automatic HTTP Headers](#automatic-http-headers)
- [Changing Locale](#changing-locale)
- [CLI Commands](#cli-commands)
- [Development: Hot Restart](#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.
```dart
// 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`:
```dart
'providers': [
(app) => LocalizationServiceProvider(app),
// ... other providers
],
```
### Localization Configuration
Create `lib/config/localization.dart`:
```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`:
```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/`:
```json
// 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:
```dart
trans('auth.failed') // "These credentials do not match..."
trans('validation.required') // "The :attribute field is required."
```
## Retrieving Translations
### Basic Usage
```dart
// 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:
```dart
String trans(String key, [Map? params])
```
- Returns the translation for the current locale
- Falls back to `fallback_locale` if not found
- Returns the key itself if no translation exists
- Replaces `:param` placeholders with provided values
### Using in Controllers
```dart
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.
```dart
// 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]
> `LocalizationInterceptor` is registered automatically by `LocalizationServiceProvider`. You do not need to add it to your provider list or network configuration manually.
## Changing Locale
### Programmatically
```dart
// 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
```dart
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
```bash
dart run magic:magic make:lang fr
dart run magic:magic make:lang de
dart run magic:magic make:lang tr
```
This command:
1. Creates `assets/lang/.json` with a starter template
2. Automatically registers the asset in `pubspec.yaml`
### Starter Template
The generated file includes common translation keys:
```json
{
"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.