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

Lingua Laravel Package

alessandrobelli/lingua

Lingua is a Laravel package that simplifies multilingual support and localization. It helps organize language files, manage translations, and streamline workflows so your application can serve users in multiple languages with less setup and cleaner configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Lingua is a small, pre-1.0 Laravel package that centralizes translation handling: it holds the list of locales your app supports, resolves/switches the active locale, and exposes helpers for pulling localized strings in views and controllers. The 0.3.1 release is a patch on 0.3 — assume the 0.3 README is current and everything else is thin. There is no meaningful changelog, so plan to read the source.

Minimal setup:

  1. composer require alessandrobelli/lingua:^0.3 — pin the minor. Pre-1.0 means 0.4 can break you with no notice.
  2. php artisan vendor:publish and inspect the lingua config tag. This file is the contract: supported locales, fallback locale, and any detection/switching behavior live here. Read it before you write a line of app code — it tells you what the package actually does in your version.
  3. Read the package source once (vendor/alessandrobelli/lingua/src). It's tiny (2 stars, single maintainer, no published API docs). Ten minutes here will save you two hours of guessing. Specifically look for: the service provider bindings, the config keys it reads, and how it resolves a locale (session? header? explicit call?).
  4. Confirm config('lingua.*') locales and fallback line up with config('app.fallback_locale'). If they don't, decide deliberately which wins rather than inheriting whatever the package does.
  5. Register middleware if the package ships one. Order matters: after StartSession, before anything that renders translated output.
  6. Hit any route and dump app()->getLocale() to confirm it's applied.

First use case — a language switcher. Add a route that validates the value against the configured locale list, sets the locale, and redirects back:

Route::post('/locale/{locale}', function (string $locale) {
    abort_unless(in_array($locale, config('lingua.locales'), true), 404);
    app()->setLocale($locale);                 // or the package's setter, if it has one
    session(['locale' => $locale]);
    return back();
})->name('locale.switch');

Then render the switcher from config('lingua.locales'), never a hardcoded array. That single route plus the config file gets you a working switcher and a clean place to add persistence later.

Implementation Patterns

Decide the negotiation order explicitly. Whether the stored session value beats Accept-Language, or vice versa, is a product decision — most apps want: explicit user choice → session → header → fallback. If Lingua's default order differs, wrap the call in your own middleware rather than fighting the config or the package internals. Your middleware should be the only thing your app depends on; the package stays behind it.

Two sources of truth are a bug. If you also localize URLs (/it/pricing, /fr/pricing), the route parameter and the session value will drift. A visitor who bookmarks /fr/pricing should have their session updated on that request. Add a thin middleware after Lingua's that reads the route locale and writes it back to the session — one-directional sync, one place.

Persist the choice per user. For authenticated users, store the locale on the users table and re-apply it early (a Login event listener, or a middleware that prefers the column over the session). This is what makes the choice survive cookie clearing, incognito, and a second device — and it's the difference between a demo feature and a real one.

Route groups. Register the middleware on web only. API and webhook routes are stateless; session-backed detection either throws or silently no-ops there. If you want header-based locale on API routes, you need Vary: Accept-Language on responses and cache-aware clients — document it.

Wrap the package in a facade-of-your-own. A tiny Localizer service, one or two methods, bound in the container. Everything — Blade directives, controllers, middleware, jobs — calls yours. When a 0.4 rename lands, it's a one-file fix. Given pre-1.0 status and a solo maintainer, this is not optional discipline.

Queue jobs and mailables need the locale set explicitly. Locale is request-scoped and does not follow a job onto the queue. Set it at the top of handle() (App::setLocale($locale)) and use ->locale($user->locale) on mailables and notifications. Otherwise every queued email goes out in the fallback language and nobody notices until a customer complains.

Blade and views. Use the package's helper/directive for localized output, but keep the switcher and the locale list driven by config('lingua.locales'). Don't scatter __() calls with hardcoded locale math inside them if the package gives you a resolving helper — pick one mechanism per view and stick to it, or you'll end up with half the page in the session locale and half in the app locale.

Testing. Assert negotiation with withHeader('Accept-Language', 'it-CH,it;q=0.9,en;q=0.8') and persistence with withSession(['locale' => 'fr']). Crucially, reset the locale in tearDown() — app()->setLocale() leaks across tests and produces maddening order-dependent failures where test 12 only fails when test 11 ran first.

Admin surface. If editors manage translated copy, keep the editing UI thin and let the package own storage/fallback. Don't reinvent a translations table next to it; you'll have two systems disagreeing about which value is authoritative.

Gotchas and Tips

  • Middleware order is the classic failure. Register before StartSession and session-backed detection silently falls back to the header or the default. Symptom: the switcher "works" on the current request but resets on the next one. If switching appears to be ignored, check kernel order first, config second, package source third.
  • The docs are the source. With ~2 stars, no published API reference, and a release note that is literally just a compare link, do not trust blog posts or your memory of another package. vendor/alessandrobelli/lingua/src + the published config are the authoritative documents for your pinned version.
  • 0.3.1 is an opaque patch. Read .../compare/0.3...0.3.1 on GitHub before upgrading. For a 0.x package, confirm nothing touched config keys, middleware signature, or helper names. Keep a note of the diff in your PR description so the next dev has a trail.
  • Region variants bite immediately. Accept-Language yields it-CH, pt-BR, zh-Hans-CN, with q-values attached. If your list is ['en','it'], decide whether it-CH maps to it or to en, and do the alias mapping in one place — a config map or a custom detector — not with substr() calls sprinkled across controllers.
  • Negotiating a locale ≠ having translations. Lingua resolves which language to use; it does not create lang/it/*.php. A perfectly negotiated locale with missing files renders raw keys. Ship files for every entry in your supported list, or shrink the list to what you actually translate. This is the most common "it's broken" that isn't the package's fault.
  • Config caching. After changing the locale list in production, run php artisan config:cache (or config:clear locally). A stale cached config is the near-universal cause of "the new language doesn't show up anywhere."
  • Response caching must key on locale. cache()->remember("page.{$slug}.{$locale}", ...). Full-page caches that don't include locale will serve the first visitor's language to everyone — and it will look like a random, intermittent bug.
  • Emit Vary: Accept-Language on any cacheable response where locale depends on the header, or your CDN will cross-serve languages between users.
  • Validate user input against the locale list. Never pass a raw request parameter into App::setLocale() or the package setter — it's a trivially reachable footgun for garbage config and confusing 500s.
  • Extension points. Detectors (or the resolver the provider binds) are the natural seam: swap the configured class or bind your own implementation in the container to add geo-IP, subdomain, or ?lang= negotiation without forking. Publish the config before overriding anything, so a composer update doesn't silently reset your settings.
  • Pre-1.0 posture. Treat 0.4 as breaking regardless of what the notes say. Keep the integration behind your own thin service/middleware layer, pin ^0.3, and watch the repo's commit activity — a single-maintainer package with 2 stars is a bus-factor risk, so know your exit path (everything is standard Laravel lang files and app()->getLocale(), so removal is tractable).
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
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