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:
- Internationalization (i18n): Designing and developing your application so it can be adapted to various languages and regions without engineering changes. Think of it as building a “language-agnostic” foundation. This includes things like using Unicode, supporting right-to-left (RTL) text, and externalizing strings.
- Localization (l10n): The process of actually adapting your internationalized application for a specific region or language by adding locale-specific components and translating text. This includes translating UI strings, formatting dates, times, numbers, and currencies according to local conventions.
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.
Language Files: Laravel's localization features are primarily driven by language files stored in the
lang
directory (orresources/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." }
- You'll create subdirectories for each supported language (e.g.,
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 isen
and you have anen.json
with that key, and then a corresponding key ines.json
.
- You use the
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
Setting the Locale:
- The application's locale is set in
config/app.php
(locale
andfallback_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.
- The application's locale is set in
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.
Dependencies: Add
flutter_localizations
to yourpubspec.yaml
and potentiallyintl
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
Configuration:
In your
MaterialApp
(orCupertinoApp
), specifylocalizationsDelegates
andsupportedLocales
.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 );
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 anl10n
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.)Code Generation: Flutter tools use the
.arb
files to generate Dart code that provides access to your localized strings.- Ensure
generate: true
is in yourpubspec.yaml
under theflutter
section. - Running
flutter pub get
(or building your app) will trigger code generation (usually intolib/generated/l10n.dart
).
- Ensure
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 ); } }
- Import the generated localizations class (often
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);
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 rebuildMaterialApp
when it changes.
Syncing Backend and Frontend Locales
- When your Flutter app makes API calls to Laravel, you might want to include the current app locale in a header (e.g.,
X-App-Locale: es
). - Your Laravel middleware can then use this header to set the backend locale for that request, ensuring any API responses (like validation messages) are also localized.
Key Considerations
- Translation Management: For larger apps, managing
.arb
or PHP language files manually can be cumbersome. Consider using translation management platforms (e.g., Lokalise, Phrase, Crowdin) that can often export in the required formats. - Right-to-Left (RTL) Support: If you support languages like Arabic or Hebrew, ensure your UI correctly handles RTL layouts. Flutter's
Directionality
widget and Material/Cupertino widgets often handle this well if the locale indicates an RTL language. - Testing: Test all supported languages and regions thoroughly. Pay attention to UI overflows due to varying string lengths.
- Context is Key for Translators: Provide context (screenshots, descriptions like in
@key
in ARB files) to translators so they understand where and how strings are used.
The Pragmatic Path
Going global is an investment, but it opens your app to a much wider audience.
- Internationalize Early: Design with i18n in mind from the start (externalize strings, think about layout).
- Start with Key Languages: You don't need to support every language on day one. Begin with your primary target markets.
- Leverage Framework Tools: Both Laravel and Flutter provide robust localization systems. Learn and use them.
- 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