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

Localeswitcher Widget Laravel Package

friendsofvictoire/localeswitcher-widget

A Blade widget for Laravel that renders a ready-made language switcher for your application, letting visitors change the active locale without extra markup or wiring. Drop the component into any view, configure the supported locales, and the package handles the selection UI and locale persistence.

View on GitHub
Deep Wiki
Context7

Getting Started

First finding, before any install command: this is not a Laravel package. The repository is FriendsOfVictoire/WidgetLocaleswitcherBundle, a Symfony bundle for Victoire DCMS. There is no service provider, no facade, no publishable config, and no Blade views. composer require friendsofvictoire/localeswitcher-widget in a Laravel app will either fail on symfony/* constraints or succeed and do absolutely nothing — its classes register against a Symfony kernel and Victoire's WidgetBundle registry, neither of which your app runs.

So "getting started" on Laravel means reading it as prior art and shipping the equivalent yourself. It is a template partial plus a bit of URL logic — roughly 40–80 lines. Do not add it as a dependency.

If you want to study the bundle's shape (in this order):

  • Widget/LocaleSwitcherWidget.php — the BaseWidget subclass; tells you what per-instance config keys exist (display mode, flags vs. text)
  • Resources/views/widget.html.twig — the actual rendering loop over locales and how target URLs are built
  • DependencyInjection/ and Resources/config/services.yml — what the container wires up
  • Resources/translations/ — label strings

What you should copy from it is the pattern, not the code: a self-contained component that reads the current route, substitutes the locale segment, preserves the query string, and renders one link per configured locale with an hreflang attribute.

Your first Laravel use case: a language switcher in the header or footer of a multi-locale site where URLs carry the locale (/en/pricing, /fr/pricing) and the user should land on the same page in the other language.

Implementation Patterns

Config first. Declare supported locales once and read them everywhere:

// config/app.php
'supported_locales' => [
    'en' => 'English',
    'fr' => 'Français',
    'de' => 'Deutsch',
],

Route model: prefix + middleware. This is the pattern the Victoire bundle assumes (locale is a routing concern, not a session concern), and it is the one that behaves well with caches and SEO:

// routes/web.php
Route::group(['prefix' => '{locale}', 'middleware' => SetLocale::class], function () {
    Route::get('/', HomeController::class)->name('home');
    Route::get('/pricing', PricingController::class)->name('pricing');
});
// app/Http/Middleware/SetLocale.php
public function handle(Request $request, Closure $next): Response
{
    $locale = $request->route('locale') ?? config('app.locale');
    $supported = array_keys(config('app.supported_locales'));

    abort_unless(in_array($locale, $supported, true), 404);

    app()->setLocale($locale);
    \Carbon\Carbon::setLocale($locale);

    return $next($request);
}

The switcher component. This is your stand-in for the bundle's widget — same job, Laravel idiom:

// app/View/Components/LocaleSwitcher.php
namespace App\View\Components;

use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Route;
use Illuminate\View\Component;

class LocaleSwitcher extends Component
{
    public array $locales;
    public string $current;

    public function __construct()
    {
        $this->locales = config('app.supported_locales', []);
        $this->current = app()->getLocale();
    }

    public function urlFor(string $locale): string
    {
        $route = Route::current();
        $name  = $route?->getName();

        // No named route (404 page, error handler): fall back to the locale root.
        if ($name === null) {
            return url("/{$locale}");
        }

        $params = array_merge($route->parameters(), ['locale' => $locale]);
        $url    = route($name, $params);

        $query = $request ?? null;
        $query = request()->query();

        return $query ? $url.'?'.http_build_query($query) : $url;
    }

    public function render(): View
    {
        return view('components.locale-switcher');
    }
}
{{-- resources/views/components/locale-switcher.blade.php --}}
<nav aria-label="{{ __('Language') }}">
    <ul class="flex items-center gap-3">
        @foreach ($locales as $code => $label)
            <li>
                @if ($code === $current)
                    <span lang="{{ $code }}" aria-current="true" class="font-semibold">
                        {{ $label }}
                    </span>
                @else
                    <a href="{{ $this->urlFor($code) }}"
                       hreflang="{{ $code }}"
                       lang="{{ $code }}"
                       rel="alternate">
                        {{ $label }}
                    </a>
                @endif
            </li>
        @endforeach
    </ul>
</nav>

Render it with <x-locale-switcher /> in your layout.

Preserving the query string matters more than it looks. Filters, pagination, and search params are where naive switchers break. request()->query() merged onto the new URL keeps /en/products?page=3&sort=price intact when the user jumps to /fr/products?page=3&sort=price.

Emit hreflang in <head>, not just on the switcher. The bundle renders links only; hreflang for SEO is a separate concern and should be a companion component that emits one <link rel="alternate" hreflang="…"> per locale plus x-default, all pointing at absolute canonical URLs:

@foreach (config('app.supported_locales') as $code => $label)
    <link rel="alternate" hreflang="{{ $code }}" href="{{ $this->urlFor($code) }}">
@endforeach
<link rel="alternate" hreflang="x-default" href="{{ $this->urlFor(config('app.locale')) }}">

If you genuinely need editor-placeable widget semantics (drag-and-drop into a page zone, per-instance display mode config), model it against the page builder you actually run — a generic renderable block or a translatable partial — not by importing Victoire's Doctrine entity contract. The editor-configurability is the only thing the bundle offers beyond markup, and it is worth maybe an extra half-day on top of the component above.

Persistence of choice. If you want a returning visitor to be remembered, set a cookie in SetLocale and add a small redirect middleware that reads it when no {locale} segment is present. Keep the URL authoritative once the user is on a locale-prefixed page — see the gotchas below.

Analytics. Because you own the markup, add a data-locale attribute and wire it to your analytics listener. The bundle provides no hooks here; this is a concrete reason to build rather than adopt.

Gotchas and Tips

  • Composer will fail, not warn. victoire/* and symfony/framework-bundle at 2.7/3.x generations conflict with Laravel 9+ (symfony/* ^6). --ignore-platform-reqs gets you a green install and a bundle that cannot register — a worse failure because it looks installed.
  • Silent non-registration in Victoire. If you ever do work in Victoire, the bundle not being listed in AppKernel produces no error: the widget simply never appears in the picker. Always run bin/console cache:clear after registering.
  • Route::current() is null outside a routed request. Queue jobs, console commands, and some error pages will blow up your component. The ?->getName() guard above handles it; keep it.
  • route() with a locale param requires the route to be defined under the prefix. Calling route('pricing', ['locale' => 'fr']) when pricing was registered without the prefix throws UrlGenerationException. Register every localizable route inside the group, or you will get 500s the moment the component renders on that page.
  • array_merge($route->parameters(), ['locale' => $locale]) ordering is deliberate. parameters() already contains the current locale; the merge must override it. Reversing the arguments silently links every language back to the current one — a bug that looks like the switcher "doing nothing."
  • hreflang must be reciprocal and absolute. Each localized page must link to all others (including itself) with fully qualified URLs. Relative hrefs and one-directional links are ignored by Google. Also add x-default for the fallback locale.
  • Cookie vs. URL locale conflicts cause flip-flopping. If a cookie says fr but the URL says /en/..., and a redirect middleware "fixes" it, users bounce between locales and crawlers index contradictory content. Rule: URL wins whenever a locale prefix is present; the cookie only seeds the first visit.
  • Shared caches key on the URL, and that's the point. URL-prefixed locales give you clean per-locale cache keys for free. If you switch to Accept-Language detection or a session-based locale, you must add Vary: Accept-Language (or Vary: Cookie) or you will serve French pages to English visitors behind any CDN or reverse proxy.
  • Guard locale input. {locale} is user-controlled; validate against config('app.supported_locales') in middleware and abort(404) otherwise. Without it you will generate hreflang tags and canonical URLs for arbitrary junk strings and invite soft-404 spam.
  • Accessibility is yours to own. The bundle gives no guarantees. Minimum bar: <nav aria-label>, lang attribute on each link (so a screen reader pronounces "Français" correctly), aria-current="true" on the active locale, and a real <a href> rather than a JS click handler — the switcher must work with JS disabled and be crawlable.
  • Don't bury the switcher inside a cached fragment without adding the locale to the fragment key (e.g. @cache('locale-switcher-'.app()->getLocale())). It renders identically in every locale except the active-state styling and the link targets, which is exactly the kind of thing a shared cache gets wrong.
  • Extension points are all in the fork. If you inherit a Victoire install, your customization surface is overriding Resources/views/widget.html.twig and subclassing the widget for config options — and you are now the maintainer, since this bundle has zero stars and zero dependents. Budget for it as a fork, not a dependency.
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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle