tomatophp/filament-language-switcher
A configurable language switcher for Filament panels that lets users change the app locale from the topbar or user menu. Supports custom language lists, flags, RTL layouts, and simple Laravel localization integration.
Architecture fit. The package is a thin, well-bounded surface: a Filament panel plugin that renders a header dropdown, a single config file as the locale allowlist, one nullable column on the users table, and a switch route that persists the signed-in user's preference. Architecturally it is a passenger, not a platform — it swaps Laravel's string locale and nothing else. It does not do tenant locale, session locale, Accept-Language/geo negotiation, content translation, or RTL layout direction. As a TPM, scope it as admin-panel infrastructure and explicitly rule those adjacent concerns out of the ticket, or the "small language switcher" work item will silently absorb a localization subsystem.
Integration feasibility. High, provided the stack gate is cleared. Install is composer require tomatophp/filament-language-switcher:^5.0, one artisan install command, and a ->plugin(...) call in the panel provider — realistically a half-day for a competent Laravel dev including reading the source end to end. The real feasibility question is not effort, it is the hard dependency: v5 requires Filament v5, Livewire 4, Laravel 12/13, and PHP 8.2+. If the admin panel is not on Filament 5, this package cannot be adopted as-is.
Technical risk. Three items belong on the risk register with owners and dates:
model/model_id ignored — is the headline reason to move.4.x branch is unconfirmed. Release notes state v4 code is preserved there; they do not state the security fix was backported. Until that is confirmed in writing, "we'll stay on Filament v4 and keep the old branch" is a documented risk acceptance requiring named sign-off, not a default.Key questions to close before sign-off.
4.x?language column on users, or will we need an override? (Confirm in a test right after install, before anything is built on top.)Stack fit. Adopt only on the far side of the Filament 5 / Livewire 4 / Laravel 12 upgrade. Pin the constraint to ^5.0 rather than a floating range — the meaningful change in this package is a security change, and a routine composer update should not get to decide when you cross that line. Treat the pin as a one-line product guardrail protecting against an unplanned replatform.
Migration path. Three tracks, chosen deliberately rather than by default:
4.x branch's patch status, and accept that you may be maintaining a fork.Compatibility and pre-upgrade audit. Before or during cutover, deliberately hunt for pre-v5 habits: guest-facing switch links, generated URLs carrying model/model_id, bookmarks and dashboards that rely on one user changing another's language. The fix makes those flows stop working — correctly — and the difference between "found in an audit" and "found in a bug report" is a support incident. Also confirm the published migration's column name, type, and nullability against the actual user model; a non-default column name is the single most likely reason to read the source or override.
Sequencing.
4.x patch status.^5.0, install, publish config, read the migration.app.locale. Without that guard, a stale row written before a config change bypasses the route-level rejection.php artisan migrate be its own observable step — the install command runs migrations in-process, which is convenient on a laptop and opaque in CI.Maintenance. Day-to-day work is 90% config: the locale array, translated display names, and show_flags. That is a good place to be — a non-engineer-adjacent change touching one file. Two standing obligations: refresh config:cache whenever the locale array changes, and periodically re-check the upstream repo. With one maintainer and zero dependents, assign a named internal owner for the integration and a lightweight quarterly review of upstream releases. Keep the integration thin so that taking ownership later is a fork of a dropdown, not of a subsystem.
Support. Expect the support queue to be config drift, not defects. Document a fixed debug order for on-call: route:list (route name and middleware) → distinguish 403 from 302 (403 means middleware is working; 302 means the user isn't authenticated, which is a different bug entirely) → check the user row → check the config array. Most tickets resolve there. Also publish the allowlist semantics plainly: a locale present in lang/ but absent from the config is not switchable by design, and support will otherwise field "the translation exists but nobody can pick it" as a bug.
Scaling. Negligible on the hot path — one column read per request and a dropdown render. The scaling risk is process state, not load. Under Octane or long-lived queue workers, locale is per-process; set it per request or job and reset at the boundary, or one user's language leaks into the next request. Any per-user caching keyed on rendered output must include locale. Do not build tenant, session, or Accept-Language negotiation inside this package — add those alongside if the roadmap calls for them.
Failure modes.
null over backfilling — null keeps the app.locale fallback live, so a future APP_LOCALE change propagates to users who never chose.show_flags on, a missing flag leaves a visible gap. Verify the switcher with flags both on and off and make sure a label, not a flag, is the primary affordance.Ramp-up. Essentially zero for anyone who knows Filament panel providers — the source is readable end to end in about ten minutes, and there is nothing to learn beyond one config file and one column. Plan two onboarding artifacts rather than training: a short README section covering the config allowlist, the fallback middleware, and the pin, plus the security test suite as living documentation of intended behavior. The real ramp-up cost is the Filament 5 migration underneath it, which is a separate and much larger program.
Where it earns its keep operationally. Localization QA and demos: a reviewer flips language in the topbar and validates translations on their own account — no shared demo user, no "changed the locale for everyone" incident, no juggling test accounts. That is a genuine, recurring saving, and it is the strongest operational argument for adopting rather than hand-rolling.
How can I help you explore Laravel packages today?