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

Filament Language Switch Laravel Package

bezhansalleh/filament-language-switch

Zero-config language switching for Filament Panels. Auto-detects your panel layout and renders in the right place: topbar icon, sidebar nav item, or user menu. Offers flags, labels, appearance and placement options, plus user-preferred locale support.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the package

composer require bezhansalleh/filament-language-switch

2. Wire up your theme (this is not optional)

The plugin ships its own views/CSS and follows Filament's Tailwind source-scanning rules. If you don't already have a custom theme, create one, then add the vendor path to resources/css/filament/{panel}/theme.css:

@source '../../../../vendor/bezhansalleh/filament-language-switch/**';

Adjust the ../ depth to match your theme file's real location, then rebuild:

npm run build

Skipping this step is the #1 cause of "the switcher renders but looks broken/unstyled." Always do it before you start tweaking appearance.

3. Configure it in a service provider boot()

use BezhanSalleh\LanguageSwitch\LanguageSwitch;

public function boot(): void
{
    LanguageSwitch::configureUsing(function (LanguageSwitch $switch) {
        $switch->locales(['en', 'fr', 'ar']);
    });
}

That's the whole first use case: the switch auto-detects your panel layout and lands in the topbar (or the sidebar if the topbar is disabled). No render hook, no Blade include, no panel registration.

Where to look first

  • README Table of Contents — the options are grouped by concern (Display Modes, Trigger, Flags, Labels, Placement, Outside Panels, Customization, Event, Control Panel), which maps cleanly to how you'll configure it.
  • The Enums namespace (DisplayMode, TriggerStyle, ItemStyle, Placement, PlacementMode) — everything is strongly typed, so your IDE autocompletes the valid surface area.
  • The Event and User Preferred Locale sections if the switcher needs to persist a choice across sessions rather than just for the current request.

Implementation Patterns

Pick display mode by locale count. Dropdown is fine up to ~6 locales. Past that, switch to the modal — it's scrollable and purpose-built:

use BezhanSalleh\LanguageSwitch\Enums\DisplayMode;

$switch
    ->locales(['en', 'fr', 'ar', 'de', 'es', 'pt', 'ja', 'ko', 'zh'])
    ->displayMode(DisplayMode::Modal)
    ->columns(3)
    ->modalWidth('lg')
    ->modalHeading('Choose Language')
    ->modalIcon('heroicon-o-language');

For 15+ locales, ->modalSlideOver() keeps the list readable without a cramped grid.

Flags are a content decision, not just decoration. Once you supply ->flags([...]), the item style auto-switches to FlagWithLabel and the trigger defaults to a flag. Be explicit when flags are ambiguous (e.g. en → US vs UK):

$switch
    ->flags([
        'en' => asset('flags/us.svg'),
        'fr' => asset('flags/fr.svg'),
        'ar' => asset('flags/sa.svg'),
    ])
    ->itemStyle(ItemStyle::FlagOnly)   // compact grid, tooltips on hover
    ->circular();

ItemStyle::FlagOnly + DisplayMode::Modal + columns(3) is the nicest "showcase" pattern for travel/i18n-heavy admin panels.

Combine nativeLabel() with displayLocale() correctly. They solve different problems:

$switch->nativeLabel();          // "Français", "العربية" — best default UX
// or, for labels generated in a fixed language:
$switch->displayLocale('fr');
// or hard overrides:
$switch->labels(['pt_BR' => 'Brasileiro', 'pt_PT' => 'Europeu']);

nativeLabel() is what you want for a language picker — users recognize their own language name faster than a translated one.

Move it into the user menu when the topbar is busy. Explicit render hook wins over the smart default:

use Filament\View\PanelsRenderHook;

$switch->renderHook(PanelsRenderHook::USER_MENU_PROFILE_AFTER);

The trigger design adapts automatically (icon button in topbar hooks, nav item in sidebar hooks, dropdown item in profile hooks). You only need ->trigger(style: ...) when you want to override that adaptation.

Show it on auth pages via visible() + placement. Keep the insidePanels/outsidePanels toggles independent so you can scope it per context:

use BezhanSalleh\LanguageSwitch\Enums\{Placement, PlacementMode};

$switch
    ->visible(outsidePanels: true)
    ->outsidePanelPlacement(Placement::TopEnd, PlacementMode::Static)
    ->maxHeight('30rem');

Use PlacementMode::Pinned for long registration forms where the switcher must stay reachable, and PlacementMode::Relative when you want to nudge it with custom CSS (top: 1rem) without publishing the view.

Multi-panel setups. Configure globally in one provider using configureUsing once; use Panel Exclusion for panels that should not show it, rather than duplicating the closure per panel. Inside the closure you can branch on Filament::getCurrentPanel() if two panels genuinely need different locale lists — just remember the closure runs on render, so keep it cheap.

Global icon override instead of per-call. If you want a consistent custom icon across every trigger:

use Filament\Support\Facades\FilamentIcon;

FilamentIcon::register([
    'language-switch::trigger' => 'phosphor-translate',
]);

Inline embedding and the Control Panel are both documented — read those sections when you need the switcher inside a custom page or want per-panel controls exposed through Filament's UI. They're the extension points beyond the fluent config.

Gotchas and Tips

  • The @source path is the most common failure. Four levels of ../ assumes resources/css/filament/<panel>/theme.css. If you put the theme elsewhere (or use a root-level theme.css), recount. Symptom of a wrong path: markup renders, styles don't. Fix by checking the built CSS for fi-ls class rules.
  • You must rebuild after install. npm run build (the README shows it twice — a docs duplication, not two steps). The plugin's classes won't be in a stale build.
  • TriggerStyle::Flag / FlagLabel requires ->flags(). Without a flags map, the trigger has nothing to render. Either supply flags or stick to Icon, Avatar, or Label. The default auto-detects this, so only override when you know flags exist.
  • ItemStyle::AvatarOnly degrades in modal mode — the abbreviation is too small for the showcase card, so it silently falls back to label-only cards. If you specifically want the compact look, use FlagOnly in modal.
  • maxHeight('max-content') disables scrolling. Great for 3 locales on desktop; painful for 20 locales on a laptop. Prefer the default 18rem or an explicit 30rem unless you've tested the worst case.
  • Locale keys must match config/app.php and your lang/ directory exactly. pt_BR (underscore) and pt-BR (hyphen) are different strings; if the switcher renders a label but the app doesn't translate, that mismatch is usually the cause. Also check that labels() keys match locales() verbatim.
  • outsidePanelRoutes() excludes the profile route by default unless the panel's profile page is simple (->profile(isSimple: true)). A full-layout profile page already has the switcher inside the panel, so this avoids double rendering — but it surprises people when they explicitly add auth.profile and it doesn't show.
  • RTL is handled for you — test it. All six Placement values flip start/end automatically. Add ar (or he) to your locales during development and eyeball the topbar and simple-layout placements; the flip is where layout bugs surface.
  • configureUsing is global, not per-panel. If you call it in two providers expecting isolation, the last registration wins. Centralize it, or branch on the current panel inside the single closure.
  • Version support needs verifying in composer.json. The README badges claim Filament 4.x and 5.x, but the API shown (Heroicon enum in trigger(icon: ...), Enums\*, PanelsRenderHook) is the newer surface. Before upgrading Filament, check the package's release notes — with a small dependent base and a recent release cadence, pinning a minor version and reading the CHANGELOG before bumping is the safe move. heroicon-o-globe-alt as a plain string is the most version-portable icon argument.
  • Debugging checklist when it doesn't appear: (1) is locales() non-empty? (2) is the panel excluded? (3) does the resolved render hook exist in your panel layout (e.g. a USER_MENU_* hook on a panel with no user menu)? (4) inspect DOM for the fi-ls / fi-ls-trigger classes — present means styling/@source problem, absent means config/hook problem.
  • Custom hooks fall back to a plain icon button. The plugin only classifies the known topbar/sidebar/user-menu/simple-layout hooks. Anything else "renders, but the visual fit is on you" — style it against the stable fi-ls* classes in your theme, or publish the views for structural changes. That's the intended extension point, so don't fight the defaults.
  • Persisting the choice is your job beyond the request. The switcher sets the locale for the session/request; if you need it stored on users.preferred_locale, listen to the documented Event, and make sure a SetLocale middleware applies the stored value on subsequent requests.
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.
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
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo