search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs

Localization

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_locale if not found
  • Returns the key itself if no translation exists
  • Replaces :param placeholders 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] LocalizationInterceptor is registered automatically by LocalizationServiceProvider. 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:

  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:

{
  "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.