liberusoftware/localization-core
Localization Core is a Laravel package offering foundational localization infrastructure for multi-language applications. It supports locale management and translation organization, giving developers a base for consistent localization across PHP projects.
composer require liberusoftware/localization-core
php artisan vendor:publish --tag=localization-core-config
php artisan migrate # only if you opt into per-user preference storage
First look at config/localization-core.php — this is where supported locales, the default fallback, cookie/session keys, and UI-direction mappings live. If the package exposes a service provider under liberusoftware\LocalizationCore\LocalizationCoreServiceProvider, that file also tells you which middleware and Blade directives are registered.
First use case: resolving the active locale for a request. In bootstrap/app.php (Laravel 11+) or Kernel.php (Laravel 10), append the package middleware to the web group:
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\LiberuSoftware\LocalizationCore\Http\Middleware\ResolveLocale::class,
]);
})
Then confirm resolution works from the tinker shell before touching views:
app()->getLocale(); // should reflect the resolved request locale
localization()->supported(); // inspect the configured locale list
localization()->direction(app()->getLocale()); // "ltr" / "rtl"
If app()->getLocale() returns the config default rather than your browser/cookie locale, the middleware isn't running on that route — check the group before debugging anything else.
Route-level locale propagation. The dominant pattern for this kind of package is a locale segment in the URL. Register a parameterised group and let the middleware bind it:
Route::prefix('{locale}')
->middleware(['web', 'localization.resolve'])
->whereIn('locale', config('localization-core.supported'))
->group(function () {
Route::get('/', HomeController::class);
});
Keep the canonical locale in the URL for SEO and shareability, and treat cookie/session/browser negotiation as a fallback only. Mixing both without a redirect strategy is where stale URLs creep in — pick one as authoritative.
Persisted user preference. When users change language in the profile page, update the preference record and reset the session key in the same request:
public function update(Request $request, UpdateLocaleAction $action)
{
$action->handle($request->user(), $request->validated('locale'));
return redirect()
->back()
->withCookie(cookie()->forever(config('localization-core.cookie')));
}
Preference should win over Accept-Language but lose to an explicit route segment. Encode that precedence in one place — usually the resolver's ordered strategies — rather than sprinkling if statements through controllers.
Propagation across jobs and notifications. Anything queued loses request context. Use the package's locale-aware job trait/interface (or wrap manually) so the worker rehydrates the right locale:
final class SendInvoice implements ShouldQueue
{
use \LiberuSoftware\LocalizationCore\Queue\LocalizesJobs;
public function __construct(public Invoice $invoice)
{
$this->captureLocale(); // stores app()->getLocale() on the payload
}
public function handle(): void
{
// app()->getLocale() is restored here
Mail::to($this->invoice->user)->send(new InvoiceMail($this->invoice));
}
}
For mails specifically, set the locale on the mailable or call App::setLocale($user->locale) in the notification's via/constructor — mailables are serialized, so the locale must travel with them.
UI direction. Drive dir and lang from a single view composer or layout variable rather than calling the resolver in every component:
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"
dir="{{ localization()->direction(app()->getLocale()) }}">
If you use a component library (Tailwind, Flux, Filament), ensure logical properties (ms-*, me-*, ps-*, pe-*) are used in place of ml-*/mr-*; the package can only tell you the direction, not retrofit your CSS.
Testing. Assert locale resolution as a first-class behaviour — it's the easiest thing to break silently:
it('resolves the locale from the route segment', function () {
$this->get('/fr/dashboard')->assertOk();
expect(app()->getLocale())->toBe('fr');
});
/fr/... while holding an en cookie will see inconsistent behaviour if the middleware writes the cookie on every request. Decide whether an explicit route segment overwrites the stored preference — and document it.responsecache, you must vary on locale. Add the locale to the cache key or the Vary header, otherwise the first visitor's language is served to everyone.Accept-Language parsing is not trivial. de-AT,de;q=0.9 must map to a configured de if de-AT is unsupported. Verify the package falls back correctly rather than assuming; test with a two-tier header explicitly.Carbon::setLocale() and NumberFormatter/Intl use a different source of truth than Laravel's translator. Wire them in the same middleware or a small LocaleContext service; forgetting this leaves English date formats inside a French page.@direction, clear the view cache (php artisan view:clear) — a stale compiled view is a classic five-minute red herring.php artisan config:cache must be re-run after editing supported. In CI this frequently makes locale tests fail locally-pass/CI-fail or vice versa.dir="rtl". Icons with directional meaning (arrows, chevrons, progress bars) need explicit mirroring; the package's direction helper is a signal, not a solution.composer.json and run your locale test suite after every update.How can I help you explore Laravel packages today?