modera/backend-languages-bundle
Modera Backend Languages Bundle provides backend language management for PHP applications, making it easy to define, organize, and expose supported locales for admin interfaces. It streamlines multilingual configuration and integration with Modera backend tooling.
First, a reality check that will save you a day: modera/backend-languages-bundle is a Symfony bundle, not a Laravel package. It has no service provider, no facade, no config/*.php, and it will not autoload usefully in a Laravel app. It is also unmaintained since June 2016 with 0 stars and 0 dependents. Treat it as a reference implementation of a feature, not a dependency you install.
Minimal steps if you are actually evaluating the concept:
composer.json first — confirm the Symfony version constraint. It will pin you to Symfony 2.x/3.x era APIs (kernel.event_listener, YAML/XML service definitions), which is the single fastest way to decide "no."DependencyInjection/Configuration.php and Resources/config/services.* to see how the preferred language is wired in — that's the whole feature surface.Your first use case, as a Laravel dev, is not "install this." It's: "Let an admin user pick their own backend UI language, persisted per user, applied on every subsequent request." That is a ~40-line middleware + column in Laravel and you should just build it:
// migration
$table->string('locale', 10)->nullable()->after('email');
// app/Http/Middleware/SetBackendLocale.php
public function handle($request, Closure $next)
{
if ($user = $request->user()) {
app()->setLocale($user->locale ?? config('app.locale'));
}
return $next($request);
}
// controller
public function updateLocale(Request $request)
{
$request->validate(['locale' => ['required', Rule::in(config('app.locales'))]]);
$request->user()->update(['locale' => $request->locale]);
return back();
}
The bundle embodies four patterns worth stealing, all portable:
1. Preference persistence on the user, not the session. Backend language is a user attribute — it should follow the user across browsers and devices. Store it on the user model (or a user_preferences key/value table if you expect many such settings). Never store it solely in the session, or a user who logs in on a new machine silently gets the wrong language.
2. Request-scoped locale application via a listener/middleware. The bundle hooks the request lifecycle to set the locale before controllers render. In Laravel the equivalent is middleware, registered in bootstrap/app.php (L11+) or Kernel::$middleware. Order matters: it must run after Authenticate so $request->user() is populated.
3. Explicit "change language" endpoint. The switcher is a form POST to a dedicated route, then a redirect back. This avoids GET-based state mutation and makes the CSRF protection free. Replicate exactly:
<form method="POST" action="{{ route('locale.update') }}">
@csrf
<select name="locale" onchange="this.form.submit()">
@foreach (config('app.locales') as $code => $label)
<option value="{{ $code }}" @selected(app()->getLocale() === $code)>{{ $label }}</option>
@endforeach
</select>
</form>
4. Locale allow-listing. The bundle validates against a configured set of enabled languages rather than accepting arbitrary locales — important because setLocale() with an attacker-supplied string can leak into trans() file paths. Always Rule::in(...) against a config array.
Integration tips:
array_diff(scandir(lang_path()), ['.', '..']), or just hardcode in config/app.php as 'locales' => ['en' => 'English', 'de' => 'Deutsch'].?lang=de on public pages), decide precedence explicitly and document it. The bundle's implicit precedence is a common source of confusion.back() rather than rendering inline, so any locale-dependent cached fragments (Blade component caches, view caches keyed by locale) rebuild cleanly.Accept-Language in a separate middleware and keep admin UI language as an independent concern.The bundle will not install into Laravel, and probably not into modern Symfony either. The 2016 release predates Symfony 4's services.yaml conventions, autowiring defaults, and AbstractController. Even on Symfony you are likely to hit signature mismatches in EventSubscriberInterface, deprecated ContainerAware usage, and PSR-4 vs PSR-0 assumptions. Do not add it to composer.json expecting it to work.
Hard coupling to the Modera ecosystem. Functionality almost certainly depends on sibling bundles (ModeraBackendBundle, the translations bundle) for its menu entry, toolbar widget, and routing. Extracting the feature means rewriting both the persistence and the UI hook. There is no meaningful "standalone" mode.
Pitfalls of the pattern itself (these will bite you regardless of framework):
setLocale() in a service constructor. Services are often resolved once and reused; setting locale at construction time freezes the locale for the worker. Do it in middleware, per request — especially under Octane, where a leaked locale will persist across requests.app()->setLocale() is not reset between Octane requests unless you reset it in a terminating callback or at the top of the middleware unconditionally.setLocale() changes trans() lookups, not your route names, validation rule messages you hardcoded, or Carbon's format defaults — set Carbon::setLocale() separately if you display dates.lang/ directory must exist for the locale. Laravel's translator silently falls back; you won't get an error, just English strings. Add a test asserting a known key differs per locale.ext-intl and a separate locale-formatting concern; don't overload one column for both.Extension points worth knowing: the persistence layer is the only part you'd genuinely want to swap — if you adopt the concept on Symfony, override the user-preference repository rather than the listener. In Laravel terms, put the read/write behind a small LocalePreference service so tests and future multi-tenant scoping don't require touching middleware.
Debugging the concept: if the language "doesn't stick," check in this order — (1) middleware registered and ordered after auth; (2) column actually saved (dump $user->fresh()->locale); (3) setLocale() called before the response is rendered; (4) the translation file for that locale exists; (5) config('app.fallback_locale') isn't silently masking a missing key. Nine times out of ten it's step 1 or 2.
Verdict: the bundle is a 2016 Symfony artifact with no Laravel applicability and no maintenance. Its pattern — user-scoped locale persisted on the user, applied by middleware, changed via a POST endpoint — is correct and takes under an hour to reimplement natively. Do that instead.
How can I help you explore Laravel packages today?