Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Localizer Laravel Package

codezero/laravel-localizer

Laravel Localizer adds URL-based localization to Laravel apps, with localized routes, automatic locale detection, middleware, and language-switcher helpers. Lightweight and configurable, it makes building and maintaining multilingual sites simple.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a thin middleware-and-helper layer around Laravel's App::setLocale() that picks a locale from the request. There is nothing to build, so the fastest path is install β†’ publish config β†’ register middleware β†’ set config/localizer.php.

composer require codezero/laravel-localizer
php artisan vendor:publish --tag=localizer

The published config/localizer.php is the source of truth for your version. At minimum set:

// config/localizer.php
'supported_locales'    => ['en', 'fr', 'nl'],
'use_accept_language'  => true,
'use_session'          => true,
'use_cookie'           => false,
'use_route'            => false,
'route_action'         => null,
'store_in_session'     => true,
'store_in_cookie'      => false,

Register the middleware on the web group after StartSession β€” the package reads/writes the session, so it must run inside a session. Laravel 11+ bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        \CodeZero\Localizer\Middleware\SetLocale::class,
    ]);
})

For older skeletons, add it at the tail of web in App\Http\Kernel.

First use case: a public marketing site that serves the same routes in several languages. Open config/localizer.php, put your canonical codes in supported_locales, keep use_accept_language on, and hit the site with a Accept-Language: fr-FR,fr;q=0.9 header. App::getLocale() will resolve to fr. From there __('messages.welcome') and trans_choice behave normally β€” the package only chooses the value; Laravel's own __() machinery does the rest.

Where to look first when something is wrong: the middleware class (setLocale β†’ session β†’ cookie β†’ route β†’ accept-language β†’ fallback), and the Localizer facade (Localizer::getLocale(), Localizer::getSupportedLocales(), Localizer::getBrowserLocale()).

Implementation Patterns

1. URL-prefix localization (/{locale}/...). This is the pattern the package is designed around. Wrap routes in a locale prefix and enable use_route:

Route::prefix('{locale}')
    ->middleware(\CodeZero\Localizer\Middleware\SetLocale::class)
    ->whereIn('locale', config('localizer.supported_locales'))
    ->group(function () {
        Route::get('/', HomeController::class);
        Route::get('/about', AboutController::class);
    });

The middleware strips the {locale} value from the route so controllers don't receive it, then sets the app locale. The whereIn constraint is important: without it, unmatched segments like /admin/... will be consumed as a locale attempt. You typically also want use_session = false in this mode, because the URL should be the single source of truth (otherwise a visitor's first session locale can override an explicit URL).

2. Session-remembered locale (language switcher). For session-only sites, keep use_session and store_in_session on and expose a one-line switcher:

Route::post('/locale/{locale}', function (string $locale) {
    abort_unless(in_array($locale, Localizer::getSupportedLocales(), true), 404);
    Localizer::setLocale($locale);
    return back();
})->name('locale.set');

Localizer::setLocale() writes to whatever the config says to store in (session and/or cookie) and calls App::setLocale() immediately, so the very next render is already in the new language. Use back() rather than hard-coding a redirect target.

3. Cookie-only (stateless / no session). Set use_session = false, use_cookie = true, store_in_cookie = true, and keep store_in_session = false. Good for full-page cached marketing pages where you don't want a server-side session write; the trade-off is that the cookie is only read after the first switch, so the initial render still falls back to Accept-Language.

4. Normalizing "which URL to land on." route_action (a controller action or route name) lets the middleware redirect when detection changes the locale. The typical pattern is: detect on /, and if the visitor speaks fr, redirect to /fr/. Keep route_action pointing at something with a stable name so URL generation stays cacheable.

5. Localized URL generation. With a route prefix you must feed {locale} into every route() call. Set it once per request instead of threading it through Blade:

// AppServiceProvider::boot() or a small middleware that runs after SetLocale
URL::defaults(['locale' => app()->getLocale()]);

Now route('about') yields /fr/about correctly from anywhere, including in queued mails if you re-resolve the locale.

6. Testing. The detection is header/session driven, so tests are trivial:

$this->withHeader('Accept-Language', 'fr-FR,fr;q=0.9')->get('/')
    ->assertSee('Bienvenue');

Assert app()->getLocale() for the unit-level check and remember to reset it in tearDown() β€” the locale is global mutable state on the container and will leak between tests in the same process.

Gotchas and Tips

  • Middleware order is the #1 cause of "it doesn't work." SetLocale must run after StartSession (and after any auth middleware that might need translated messages). Place it near the end of the web group, not in global.
  • Accept-Language is a preference, not a fact. The header is absent in many API clients and spoofable in browsers. Never treat the detected locale as authorization or as a hard locale lock β€” always allow an explicit override (session, cookie, or URL).
  • Regional codes and canonical form. Browsers send fr-CH, fr;q=0.9, en;q=0.8. Your supported_locales should be the canonical form you actually translate (fr, not fr_CH). If you support both region and language, decide the mapping before you build on the package and normalize in config, otherwise __() silently falls back to the fallback locale and you get mixed-language pages. The package's closest-match logic handles the common fr-FR β†’ fr case, but not arbitrary zh-Hant-TW ↔ zh_TW naming.
  • config:cache bites. supported_locales is read on every request. If you deploy with php artisan config:cache, changing the array at runtime (config(['localizer.supported_locales' => ...]) in a service provider) will appear to work locally and fail on a cached deploy. Treat the config as static.
  • Localizer::setLocale() vs App::setLocale(). Use the facade when you want persistence (session/cookie) and the current request. Use App::setLocale() only when you want a one-request, non-persisting change (e.g. sending a single localized email from a default-locale queue). Mixing the two in one code path is a frequent source of "the language switched back after a redirect."
  • Route parameter leakage. If you use the /{locale} prefix without the package's route stripping, your controller signature show(string $locale, ...) will get the raw prefix and you'll end up duplicating the locale logic. Confirm the middleware is actually in the route group, not just globally applied.
  • Fallback locale matters. Set config('app.fallback_locale') to the same canonical form and make sure it exists in supported_locales; otherwise a visitor with an unsupported locale gets Laravel's fallback, while a visitor with a supported-but-untranslated key gets a missing-translation fallback β€” two different symptoms that look like one bug.
  • Not covered by the package: validation messages, Carbon::setLocale(), PHP setlocale() (affects strftime and number formatting), and pluralization rules. If you display dates or numbers, wire Carbon::setLocale(app()->getLocale()) yourself in a middleware right after SetLocale, or you'll ship a French page with English month names.
  • Octane / long-running workers. app()->setLocale() is per-request in standard FPM, but any static cache you build keyed by translation strings (a common micro-optimization) will be stale across locales and across requests in Octane. Key such caches on app()->getLocale() explicitly.
  • Cache-busting translated pages. If you full-page-cache rendered HTML, the cache key must include the locale and Vary: Accept-Language. Without the Vary header, an upstream CDN will serve one language to everybody and it will look like the middleware "randomly stopped working."
  • Extension points. The middleware is final-ish in older releases; if you need custom detection (geo-IP, user preference column), don't subclass the middleware β€” write a tiny middleware that runs after SetLocale and overrides App::setLocale() based on Auth::user()?->locale. That keeps updates from the vendor package safe and makes the override explicit in bootstrap/app.php.
  • Version drift. Last tagged release is early 2024 and the config key names have shifted across releases (use_route vs. route-prefix detection, cookie semantics). Always read the published config/localizer.php from your installed version rather than a blog post β€” the middleware's behavior is driven almost entirely by that file, so a mismatched key means a silently ignored feature.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo