Skip to main content

Laravel i18n: Complete Internationalization & Localization Guide

From first locale file to production: configure Laravel's translation system, handle pluralization, fix the JSON fallback bug, and add smart locale fallback chains for regional variants.

1

Understand Laravel's Translation System

Laravel ships with a built-in translation system that supports two file formats: PHP arrays and JSON. PHP files use nested keys organized by feature (auth.failed, validation.required). JSON files use the source string as the key, which is simpler but doesn't support nesting.

Terminal
composer create-project laravel/laravel my-app
cd my-app

# Laravel includes i18n out of the box
# No extra packages needed for basic usage
Laravel's translation system lives in the lang/ directory (Laravel 9+) or resources/lang/ (Laravel 8 and earlier). The framework auto-detects the directory. If both exist, lang/ takes precedence.
2

Configure Locale Settings

Set your application's default locale and fallback locale in config/app.php. The fallback locale is used when a translation key is missing from the active locale. Configure supported locales for your application and add middleware to detect and set the user's preferred locale.

config/app.php
// config/app.php
return [
    'locale' => 'en',            // Default locale
    'fallback_locale' => 'en',   // Fallback when key is missing
    'faker_locale' => 'en_US',

    // Available locales (for your language switcher)
    'available_locales' => ['en', 'de', 'ja', 'es', 'fr', 'pt-BR'],
];
app/Http/Middleware/SetLocale.php
// app/Http/Middleware/SetLocale.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class SetLocale
{
    public function handle(Request $request, Closure $next)
    {
        $locale = $request->segment(1); // e.g. /de/about

        if (in_array($locale, config('app.available_locales'))) {
            app()->setLocale($locale);
        }

        return $next($request);
    }
}
3

Use Translation Functions

Laravel provides three ways to translate strings: the __() helper function (recommended), the trans() function, and the @lang Blade directive. All three accept the translation key and optional replacement parameters. Use __() in PHP code and Blade templates, and @lang in Blade when you don't need to escape HTML.

lang/en/messages.php
// lang/en/messages.php
return [
    'welcome' => 'Welcome to our application',
    'greeting' => 'Hello, :name!',
    'nav' => [
        'home' => 'Home',
        'about' => 'About',
        'settings' => 'Settings',
    ],
];

// lang/de/messages.php
return [
    'welcome' => 'Willkommen in unserer Anwendung',
    'greeting' => 'Hallo, :name!',
    'nav' => [
        'home' => 'Startseite',
        'about' => 'Über uns',
        'settings' => 'Einstellungen',
    ],
];
Prefer __() over trans() in new code. __() works with both PHP and JSON translation files, while trans() only works with PHP files. The @lang directive is equivalent to {'{ __() }'} in Blade templates but with slightly cleaner syntax.
4

Handle Pluralization

Laravel uses a pipe-separated syntax for plural forms. The simplest form is 'apples' => 'There is one apple|There are many apples'. For explicit ranges, use 'apples' => '{0} No apples|{1} One apple|[2,*] :count apples'. The trans_choice() helper or Str::plural() selects the correct form based on the count.

lang/en.json
// lang/en.json
{
    "Welcome back!": "Welcome back!",
    "You have :count new notifications": "You have :count new notifications",
    "Copyright :year :company": "Copyright :year :company"
}

// lang/de.json
{
    "Welcome back!": "Willkommen zurück!",
    "You have :count new notifications": "Sie haben :count neue Benachrichtigungen",
    "Copyright :year :company": "Copyright :year :company"
}
Laravel's built-in pluralization only handles simple one/other rules correctly for most languages. For languages with complex plural forms (Arabic has 6, Russian has 4, Polish has 3), you need to define all required CLDR plural categories. Without them, users see grammatically incorrect text.
5

Add Locale Fallback Chains

Laravel's built-in fallback only goes from the active locale to fallback_locale — there is no intermediate step. A pt-BR user with a missing key sees English instead of the perfectly good pt-PT translation. laravel-locale-chain fixes this by deep-merging translations from a configurable fallback chain at load time.

resources/views/example.blade.php
{{-- Simple translation --}}
<h1>{{ __('messages.welcome') }}</h1>

{{-- With variables --}}
<p>{{ __('messages.greeting', ['name' => $user->name]) }}</p>

{{-- Using @lang directive --}}
<h2>@lang('messages.nav.home')</h2>

{{-- JSON translations (use the string itself as key) --}}
<p>{{ __('Welcome back!') }}</p>

{{-- Pluralization --}}
{{ trans_choice('{0} No items|{1} One item|[2,*] :count items', $count) }}

{{-- Inside Blade components --}}
<x-button>{{ __('messages.nav.settings') }}</x-button>
6

Automate Translations

With your Laravel i18n setup complete, translate your locale files using AI. Point your AI assistant at the source locale file, or use the i18n Agent CLI in your CI/CD pipeline. Both PHP and JSON translation files are supported.

config/locale-chain.php
// Install locale chain package
composer require i18n-agent/laravel-locale-chain

// config/locale-chain.php
return [
    'chains' => [
        'pt-BR' => ['pt-BR', 'pt', 'en'],
        'zh-Hant-TW' => ['zh-Hant-TW', 'zh-Hant', 'zh', 'en'],
        'es-419' => ['es-419', 'es', 'en'],
    ],
];

// In AppServiceProvider::boot()
use I18nAgent\LocaleChain\LocaleChainServiceProvider;

// The package automatically deep-merges translations
// across the chain: pt-BR -> pt -> en
Translate incrementally — when you add new keys, translate just the diff rather than regenerating all files. This preserves human-reviewed translations and avoids unnecessary churn.

Common Pitfalls

Hardcoded Singular/Plural Logic

Writing $count == 1 ? 'item' : 'items' instead of using trans_choice() breaks for languages where 0 is singular (French), where there are 3+ plural forms (Russian, Polish), or where there are 6 forms (Arabic). Always use Laravel's plural syntax and define all required forms.

JSON Translations Don't Fall Back

Laravel's fallback_locale only works for PHP translation files. JSON translations use the source string as the key, so a missing translation returns the key itself (the English text) rather than looking in the fallback locale. This means JSON translations have no true locale fallback. Use laravel-locale-chain to fix this.

Mixing PHP and JSON Without Understanding Precedence

When the same key exists in both PHP and JSON files, PHP takes precedence. This can cause confusing behavior where updating the JSON file has no effect because the PHP file shadows it. Pick one format per feature namespace and stick with it.

Try i18n Agent Now

Drop your translation file here

JSON, YAML, PO, XML, CSV, Markdown, Properties

or click to browse

Target languages

No signup requiredInstant estimate

Laravel i18n FAQ