Laravel localization: the practical guide

How Laravel i18n works, from language files to plural rules, and what changes for each language. Start with the framework guide, then open the language you are shipping.

Language files: where translations live

Laravel reads translations from the lang directory. A new application does not include it; running the publish command creates it with the framework's own English strings for validation, authentication, pagination and passwords.

There are two formats and you can use both. PHP files return nested arrays and are addressed as file.key, which suits strings you want grouped. JSON files, one per locale, are keyed by the source sentence itself, which suits apps with a lot of copy where naming every string would slow you down.

php artisan lang:publish

lang/
├── en/
│   ├── messages.php      // __('messages.welcome')
│   └── validation.php
├── es/
│   ├── messages.php
│   └── validation.php
└── es.json               // __('Save changes')

Retrieving strings: __(), @lang and placeholders

The __() helper looks a key up in the active locale and falls back to the fallback locale when it is missing. If neither has it, you get the key back, which is why a missing translation shows up on the page as messages.welcome instead of an error.

Placeholders start with a colon and are replaced from the second argument. Writing the placeholder capitalized or in upper case changes the case of the value to match. In Blade, prefer {{ __() }} over @lang: the directive prints the string without escaping it.

// lang/en/messages.php
return [
    'welcome' => 'Welcome back, :name',
];

// Anywhere in PHP
__('messages.welcome', ['name' => $user->name]);

{{-- Blade --}}
<h1>{{ __('messages.welcome', ['name' => $user->name]) }}</h1>

Setting the locale and building a language switcher

The default and fallback locales come from APP_LOCALE and APP_FALLBACK_LOCALE. To serve a different language per request, call App::setLocale() early, in middleware, from something stable: a URL segment, the authenticated user's preference or a subdomain.

A URL segment is the choice that search engines can index, because every language gets its own address. A language switcher is then a list of ordinary links to the same page under each locale prefix. Storing the locale only in the session works for a logged-in dashboard and hides every language but the default from crawlers.

// app/Http/Middleware/SetLocale.php
public function handle(Request $request, Closure $next): Response
{
    $locale = $request->route('locale');

    if (in_array($locale, config('app.available_locales'), true)) {
        App::setLocale($locale);
        URL::defaults(['locale' => $locale]);
    }

    return $next($request);
}

// routes/web.php
Route::prefix('{locale}')
    ->whereIn('locale', config('app.available_locales'))
    ->middleware(SetLocale::class)
    ->group(function () {
        Route::get('/pricing', PricingController::class)->name('pricing');
    });

Pluralization with trans_choice()

English has two plural forms. Polish and Ukrainian have three in everyday use, Arabic has six, and Chinese, Japanese and Vietnamese have one. trans_choice() picks the right segment of a pipe-separated string using the plural rules of the active locale, so each language file must supply as many forms as that language needs.

This is the part of localization most often wrong in production, because a two-form string in a three-form language still renders something. The per-language guides below list the exact forms Laravel expects for each language.

// lang/en/messages.php
'apples' => '{0} No apples|{1} One apple|[2,*] :count apples',

// lang/pl/messages.php (one, few, many)
'apples' => ':count jabłko|:count jabłka|:count jabłek',

trans_choice('messages.apples', 5); // "5 jabłek" when the locale is pl

Dates, numbers and validation messages

Translated strings are half of localization. Dates follow the application locale through Carbon: isoFormat() and translatedFormat() produce month and day names in the active language, while format() never does. Numbers and currency go through the Number helper, which takes a locale and applies the right separators and symbol position.

Validation messages live in lang/{locale}/validation.php, including an attributes array that replaces field names such as email_address with readable labels. Leaving that array empty is the reason translated forms show English field names inside otherwise translated sentences.

now()->isoFormat('LL');                                // "9 de octubre de 2026" in es
Number::currency(1234.5, in: 'EUR', locale: 'de');     // "1.234,50 €"

// lang/es/validation.php
'attributes' => [
    'email_address' => 'correo electrónico',
],

Localization practices that hold up

Never build a sentence by concatenating translated fragments: word order differs between languages, so pass values in as placeholders and translate the whole sentence. Keep keys stable and descriptive, and do not reuse one key in two contexts just because the English text happens to match.

Find hardcoded text before translating, not after: every string left in a Blade view is a string no translator will ever see. Test with a locale that stresses the layout, such as German for length or Arabic for right-to-left. And decide early who changes copy after launch, because if the answer is a developer and a deploy, every typo becomes a release.

Laravel localization, language by language

Every language pluralizes, formats dates and writes differently. Each guide gives you the locale codes, the exact trans_choice plural forms Laravel expects, localized Carbon output and script-specific tips, generated from ICU and CLDR data.

Arabic

ar

العربية

Arabic script · 6 plural forms · RTL

Catalan

ca

Català

Latin script · 2 plural forms

Chinese (Simplified)

zh

简体中文

Han (Simplified) script · 1 plural form

Czech

cs

Čeština

Latin script · 3 plural forms

Danish

da

Dansk

Latin script · 2 plural forms

Dutch

nl

Nederlands

Latin script · 2 plural forms

English

en

English

Latin script · 2 plural forms

Finnish

fi

Suomi

Latin script · 2 plural forms

French

fr

Français

Latin script · 2 plural forms

German

de

Deutsch

Latin script · 2 plural forms

Greek

el

Ελληνικά

Greek script · 2 plural forms

Hebrew

he

עברית

Hebrew script · 2 plural forms · RTL

Hindi

hi

हिन्दी

Devanagari script · 2 plural forms

Hungarian

hu

Magyar

Latin script · 2 plural forms

Indonesian

id

Bahasa Indonesia

Latin script · 1 plural form

Italian

it

Italiano

Latin script · 2 plural forms

Japanese

ja

日本語

Japanese (Kanji/Kana) script · 1 plural form

Korean

ko

한국어

Hangul script · 1 plural form

Malay

ms

Bahasa Melayu

Latin script · 1 plural form

Norwegian

no

Norsk

Latin script · 2 plural forms

Polish

pl

Polski

Latin script · 3 plural forms

Portuguese

pt

Português

Latin script · 2 plural forms

Romanian

ro

Română

Latin script · 3 plural forms

Russian

ru

Русский

Cyrillic script · 3 plural forms

Spanish

es

Español

Latin script · 2 plural forms

Swedish

sv

Svenska

Latin script · 2 plural forms

Thai

th

ไทย

Thai script · 1 plural form

Turkish

tr

Türkçe

Latin script · 1 plural form

Ukrainian

uk

Українська

Cyrillic script · 3 plural forms

Vietnamese

vi

Tiếng Việt

Latin script · 1 plural form

Frequently asked questions

What is localization in Laravel?

Localization in Laravel is the set of features for serving an application in several languages: language files in the lang directory, the __() and trans_choice() helpers to retrieve strings, and a per-request locale that also drives date formatting and validation messages.

What is the difference between i18n and localization in Laravel?

Internationalization (i18n) is the engineering work of making the app translatable: externalizing strings, handling plurals and formats. Localization (l10n) is supplying the content for a specific language and region. You internationalize once and localize for each language.

How do I add a new language to a Laravel app?

Create a directory for the locale under lang with the same files as your source language, or a JSON file named after the locale, translate the values, and add the locale to the list your middleware accepts. No code changes are needed if strings already go through __().

How do I change the language at runtime in Laravel?

Call App::setLocale() with the locale code, normally in middleware so it runs before controllers and views. The value applies to the current request only, so it has to be derived on every request from the URL, the user or the session.

What happens when a translation is missing?

Laravel tries the active locale, then the fallback locale, and finally returns the key itself. The page keeps working and shows either the fallback language or the raw key, so missing translations are easy to ship unnoticed unless you test for them.

Can I change translations without redeploying a Laravel app?

Not with language files alone, since they ship with the release. Loading published translations from a service or CDN at runtime lets non-developers edit text and have it go live without a commit or a deploy, which is what LangSyncer does.

We'd like to use analytics and marketing cookies to understand how the site is used. They stay off unless you accept them. Cookie Policy