Filament Arabic and English locale switch with RTL layout
Let Filament 4 users switch between English and Arabic from the user menu, remembered in the session and profile, with the layout flipping to RTL.
FilamentInternationalisationfilamenti18nrtlarabic
Code
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class SetLocale
{
public const SUPPORTED = ['en' => 'English', 'ar' => 'العربية'];
public function handle(Request $request, Closure $next): Response
{
$locale = $request->session()->get('locale')
?? $request->user()?->locale
?? config('app.locale');
// Whitelist: never pass raw input to setLocale().
if (array_key_exists($locale, self::SUPPORTED)) {
// Filament reads the direction from this locale's translation files,
// so "ar" flips the whole panel to right-to-left on its own.
app()->setLocale($locale);
}
return $next($request);
}
}
// routes/web.php (the switch endpoint used by the user menu items)
//
// Route::get('/locale/{locale}', function (string $locale) {
// abort_unless(array_key_exists($locale, SetLocale::SUPPORTED), 404);
// session(['locale' => $locale]);
// auth()->user()?->update(['locale' => $locale]); // remember across devices
//
// return redirect()->back();
// })->name('locale.switch');
<?php
namespace App\Providers\Filament;
use App\Http\Middleware\SetLocale;
use Filament\Actions\Action;
use Filament\Panel;
use Filament\PanelProvider;
class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->id('admin')
->path('admin')
->login()
// Must run before Filament renders, so register it as panel middleware.
->middleware([SetLocale::class])
->userMenuItems($this->localeItems());
}
/** One menu entry per language other than the active one. */
private function localeItems(): array
{
return collect(SetLocale::SUPPORTED)
->except(app()->getLocale())
->map(fn (string $label, string $code) => Action::make("locale-$code")
->label($label)
->icon('heroicon-o-language')
->url(fn () => route('locale.switch', $code)))
->values()
->all();
}
}
Needs: filament/filament@^4.0
How it works
What it solves
Panels for businesses in the Gulf, North Africa or any bilingual market need Arabic next to English. Translating strings is only half the job; the whole layout must also run right-to-left. Filament already ships Arabic translations, so the missing piece is a clean way for users to choose their language.
How it works
SetLocale is a middleware registered on the panel. On each request it takes the locale from the session first, then from the user's saved preference, then from the app default. The value is checked against the SUPPORTED whitelist before app()->setLocale() is called, so a tampered session or query string cannot select an arbitrary locale.
No custom CSS is needed for direction. Filament decides dir="rtl" or ltr from the active locale's translation files, so setting ar flips the sidebar, tables, icons and forms automatically.
The provider builds user menu entries from the same list, excluding the language that is already active, so the menu always offers the other choice. Each entry links to a small route (shown in the comment) that validates the code, stores it in the session and saves it on the user.
Adapting it
Add more languages by extending SUPPORTED; for each, check that Filament includes a translation. Put your own strings in lang/ar/*.php or JSON files. Use Number::useLocale() if you want Arabic-Indic digits.
Pitfalls
- The user menu is built when the panel boots; if you compute the list before the middleware runs it may show the wrong "other" language, hence the
exceptcall at render time. - Add a
localestring column tousersbefore saving to it. - Mixed-direction content (English product names inside Arabic text) may need
dir="auto"on custom Blade views.
How to use it
- Add a nullable
localecolumn tousersand migrate. - Put
SetLocale.phpinapp/Http/Middleware/and add thelocale.switchroute from the comment. - Register the middleware and user menu items in your panel provider.
- Add Arabic strings for your own labels in
lang/ar/. - Log in, open the user menu and choose the other language: the page reloads mirrored.
Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.