Going Global: Internationalization & Localization in Laravel + Flutter

Hey everyone, Jamie here.

So, your app is humming along, features are shipping, and users are happy. But what if those users span different countries and speak different languages? Suddenly, “Your order has been placed!” needs to be “¡Tu pedido ha sido realizado!” or “Votre commande a été passée!”. This is where Internationalization (i18n) and Localization (l10n) come into play – crucial steps if you're aiming for a global audience with your Laravel + Flutter application.

It might seem daunting, but both Laravel and Flutter offer excellent tools to make this process manageable. Let's break down how to approach it.

Understanding the Terms

First, a quick refresher:

You do i18n first, so that l10n becomes easier.

Laravel: Handling Translations and Locale on the Backend

Our Laravel API plays a key role, especially if some content or messages originate from the server.

  1. Language Files: Laravel's localization features are primarily driven by language files stored in the lang directory (or resources/lang in older versions).

    • You'll create subdirectories for each supported language (e.g., en, es, fr).
    • Inside these, you'll have PHP files (e.g., messages.php) or JSON files (e.g., es.json) that return an array of keyed strings.

    Example (lang/es/messages.php):

    <?php
    
    return [
        'welcome' => '¡Bienvenido a nuestra aplicación!',
        'profile_updated' => 'Perfil actualizado con éxito.',
    ];
    

    Example (lang/fr.json):

    {
        "welcome": "Bienvenue sur notre application !",
        "profile_updated": "Profil mis à jour avec succès."
    }
    
  2. Retrieving Translated Strings:

    • You use the __('key') helper function or the @lang('key') Blade directive to retrieve translated strings. php echo __('messages.welcome'); // In PHP // {{ __('messages.welcome') }} or @lang('messages.welcome') in Blade
    • For JSON files, you just use the key: __('Welcome to our application!') if your default locale is en and you have an en.json with that key, and then a corresponding key in es.json.
  3. Pluralization: Laravel handles pluralization elegantly using a | character to separate singular and plural forms, and you can define more complex pluralization rules.

    // 'item_count' => 'There is one item|There are :count items'
    echo trans_choice('messages.item_count', 5); // Output: There are 5 items
    
  4. Setting the Locale:

    • The application's locale is set in config/app.php (locale and fallback_locale).
    • You can change the locale at runtime using App::setLocale('es');.
    • Commonly, you'd determine the user's preferred locale from:
      • A user profile setting stored in the database.
      • The Accept-Language HTTP header sent by the browser/client.
      • A segment in the URL (e.g., /es/dashboard).
    • A middleware is often used to set the locale for each request based on these factors.
  5. API Responses: If your API needs to return localized messages (e.g., validation errors, success messages), Laravel's default validation messages and notifications can also be translated by publishing their language files and adding your translations.

Flutter: Building a Multilingual UI

Flutter has excellent built-in support for i18n and l10n, primarily through the flutter_localizations package and code generation for message catalogs.

  1. Dependencies: Add flutter_localizations to your pubspec.yaml and potentially intl for more complex formatting.

    dependencies:
      flutter:
        sdk: flutter
      flutter_localizations: # Add this
        sdk: flutter         # Add this
      intl: ^0.18.0 # Or latest, for formatting and message extraction
        
    flutter:
      uses-material-design: true
      generate: true # Important for code generation
    
  2. Configuration:

    • In your MaterialApp (or CupertinoApp), specify localizationsDelegates and supportedLocales.

      import 'package:flutter_localizations/flutter_localizations.dart';
      // Import your generated AppLocalizations class (see below)
      // import 'generated/l10n.dart';
      
      MaterialApp(
        // ... other properties
        // localizationsDelegates: AppLocalizations.localizationsDelegates, // Generated
        // supportedLocales: AppLocalizations.supportedLocales, // Generated
        localizationsDelegates: [
          // AppLocalizations.delegate, // Your app's generated delegate
          GlobalMaterialLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
          GlobalCupertinoLocalizations.delegate,
        ],
        supportedLocales: [
          const Locale('en', ''), // English, no country code
          const Locale('es', ''), // Spanish, no country code
          const Locale('fr', ''), // French, no country code
          // ... other locales your app supports
        ],
        // locale: _userLocale, // Optionally set the initial locale
        // localeResolutionCallback: (locale, supportedLocales) { ... } // For custom logic
      );
      
  3. ARB Files (.arb): Application Resource Bundle files are used to store your translated strings. You'll typically have one per locale (e.g., app_en.arb, app_es.arb). These are usually placed in an l10n directory at the root of your project.

    Example (l10n/app_en.arb):

    {
      "helloWorld": "Hello World!",
      "welcomeMessage": "Welcome {userName} to our awesome app!",
      "@welcomeMessage": {
        "description": "A welcome message shown on the home screen",
        "placeholders": {
          "userName": {
            "type": "String",
            "example": "Jamie"
          }
        }
      },
      "itemCount": "{count,plural, =0{No items}=1{One item}other{{count} items}}",
      "@itemCount": {
        "description": "Indicates the number of items",
        "placeholders": {
          "count": {
            "type": "int"
          }
        }
      }
    }
    

    (And a corresponding app_es.arb, app_fr.arb etc.)

  4. Code Generation: Flutter tools use the .arb files to generate Dart code that provides access to your localized strings.

    • Ensure generate: true is in your pubspec.yaml under the flutter section.
    • Running flutter pub get (or building your app) will trigger code generation (usually into lib/generated/l10n.dart).
  5. Using Localized Strings in Widgets:

    • Import the generated localizations class (often AppLocalizations).
    • Access strings via AppLocalizations.of(context)!.yourStringKey.
    // import 'generated/l10n.dart'; // Your generated file
    
    class MyWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // final l10n = AppLocalizations.of(context)!; // Get the localizations instance
        return Scaffold(
          // appBar: AppBar(title: Text(l10n.helloWorld)),
          // body: Center(child: Text(l10n.welcomeMessage('Jamie'))),
          appBar: AppBar(title: Text("Example Title")), // Placeholder until l10n is fully set up
          body: Center(child: Text("Welcome Jamie")), // Placeholder
        );
      }
    }
    
  6. Formatting Dates, Numbers, Currencies: Use the intl package for locale-aware formatting.

    // import 'package:intl/intl.dart';
    // DateFormat.yMMMd(AppLocalizations.of(context)!.localeName).format(DateTime.now());
    // NumberFormat.currency(locale: AppLocalizations.of(context)!.localeName, symbol: '€').format(123.45);
    
  7. Changing Locale Dynamically: You'll need a way for users to select their language, or detect it. This usually involves a state management solution (Provider, Riverpod, Bloc) to hold the current Locale and rebuild MaterialApp when it changes.

Syncing Backend and Frontend Locales

Key Considerations

The Pragmatic Path

Going global is an investment, but it opens your app to a much wider audience.

  1. Internationalize Early: Design with i18n in mind from the start (externalize strings, think about layout).
  2. Start with Key Languages: You don't need to support every language on day one. Begin with your primary target markets.
  3. Leverage Framework Tools: Both Laravel and Flutter provide robust localization systems. Learn and use them.
  4. Automate Where Possible: Use code generation in Flutter and consider translation management tools for larger projects.

Taking your application multilingual can seem like a big step, but by breaking it down and utilizing the powerful features within Laravel and Flutter, you can create a truly global experience for your users.

Have you tackled i18n/l10n in your projects? Any tips or pitfalls to share? Let's discuss!

Cheers,

Jamie C