Laravel's built-in localization system handles a multi-language site through simple PHP array files, one per language, with a straightforward mechanism for switching the active locale per request.
Creating language files
// lang/en/messages.php
return [
'welcome' => 'Welcome to our store',
'add_to_cart' => 'Add to Cart',
];
// lang/es/messages.php
return [
'welcome' => 'Bienvenido a nuestra tienda',
'add_to_cart' => 'Añadir al Carrito',
];
Using translations in a Blade view
{{ __('messages.welcome') }}
{{ __('messages.add_to_cart') }}
@lang('messages.welcome')
__() and the @lang Blade directive are functionally equivalent — __() is more commonly used since it also works directly in PHP code (controllers, form requests), not just inside a Blade template.
Setting the active locale
App::setLocale('es');
Setting the locale per request, based on a URL segment
Route::group(['prefix' => '{locale}'], function () {
Route::get('/', [HomeController::class, 'index']);
});
// A middleware that runs on every request in this group
public function handle(Request $request, Closure $next)
{
$locale = $request->route('locale');
if (in_array($locale, ['en', 'es', 'fr'])) {
App::setLocale($locale);
}
return $next($request);
}
Validating the locale against a known allowed list before calling setLocale() is important — passing an unrecognized or malicious value directly through without this check could, depending on the Laravel version and configuration, be used to probe for unintended file paths.
Persisting the user's chosen locale across requests
public function switchLanguage(string $locale)
{
session(['locale' => $locale]);
return back();
}
// In a middleware
if (session()->has('locale')) {
App::setLocale(session('locale'));
}
Translation strings with placeholders
// lang/en/messages.php
'items_in_cart' => 'You have :count items in your cart',
{{ __('messages.items_in_cart', ['count' => $cartCount]) }}
Pluralization
// lang/en/messages.php
'items_in_cart' => '{0} Your cart is empty|{1} You have one item|[2,*] You have :count items',
{{ trans_choice('messages.items_in_cart', $cartCount, ['count' => $cartCount]) }}
trans_choice() selects the correct string variant based on the given count using this range-based syntax — genuinely necessary since simple string concatenation ("You have {$count} items") produces grammatically awkward results for a count of exactly one in English, and pluralization rules vary considerably in other languages beyond just singular/plural.
What happens when a translation key is missing
If a key doesn't exist in the current locale's file, Laravel falls back to displaying the key string itself (like messages.welcome) rather than blank text or an error — this is actually a useful debugging signal, since seeing a raw key on the live page is an immediately obvious sign that a translation is missing for that specific locale.
Setting a fallback locale
// config/app.php
'fallback_locale' => 'en',
If a key exists in the fallback locale's file but not the currently active one, Laravel uses the fallback's value automatically rather than showing the raw key — worth setting to whichever language has the most complete translation coverage, typically the site's original or primary language.