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

Laravel Multilingual Routes Laravel Package

chinleung/laravel-multilingual-routes

Register multilingual Laravel routes with a single multilingual() call. It maps paths and route names per configured locale, can detect the request locale via middleware, and supports Laravel 9 through 13 depending on package version. Ideal for localized URLs without duplicating route definitions.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Match the version to your framework before composer require. The package pins to Laravel majors: v6 → 13.x, v5 → 12.x, v4 → 10.x/11.x, v3 → 9.x. Installing without a constraint on the wrong major is the most common first-hour failure.

composer require chinleung/laravel-multilingual-routes

2. Register the locale detection middleware first in the web group. Order matters — it must run before anything else that touches locale, session, or URL generation:

// bootstrap/app.php
$middleware->web(prepend: [
    \ChinLeung\MultilingualRoutes\Middleware\DetectRequestLocale::class,
]);

3. Configure locales. By default only en is active and the default locale is unprefixed. To prefix it (e.g. /en/... alongside /fr/...), publish the config:

php artisan vendor:publish --provider="ChinLeung\MultilingualRoutes\MultilingualRoutesServiceProvider" --tag="config"

Then set app.locales (or adopt chinleung/laravel-locales, which the package reads from).

4. Write your first route and its translation. The URI comes from a per-locale translation file, not from the route argument:

// routes/web.php
Route::multilingual('test', 'TestController')->name('test');

// resources/lang/fr/routes.php
return ['test' => 'teste'];

5. Verify before writing more. php artisan route:list is the source of truth here — it shows the expanded en.test / fr.test pairs and their URIs. Do this after every route change until the mental model clicks.

First real use case: a marketing or content site with a handful of static pages and one resource section. Define Route::multilingual() per page, one multilingualResource() for the CRUD area, and a language switcher built on current_route(). That covers ~90% of what this package is for.

Implementation Patterns

Naming convention: everything is locale-suffixed. ->name('home') produces en.home and fr.home. This is the package's central invariant and drives every other pattern:

localized_route('home');            // current locale
localized_route('home', [], 'fr');  // explicit locale
current_route('fr');                // same page, other locale
current_route('fr', route('home')); // fallback if not translated
current_route_is('photos.show');    // locale-agnostic name match

Use localized_route() instead of route() in Blade, controllers, redirects, and mailables. Use current_route_is() (or Request::localizedRouteIs()) in middleware, navigation highlighting, and policies — it strips the locale prefix so you write the check once.

Language switcher. The idiomatic implementation is a loop over config('app.locales') with an "is this translation available?" guard:

@foreach (config('app.locales') as $locale)
    <a href="{{ current_route($locale) }}">{{ strtoupper($locale) }}</a>
@endforeach

Pass a fallback route as the second argument so pages that aren't translated (via ->except() / ->only()) link somewhere sane instead of 404-ing.

Translation file discipline. Route translations live in resources/lang/{locale}/routes.php as a flat key→URI map, where the key is the unprefixed literal URI string used in the route definition. Keep them flat, keep keys identical to the route argument, and treat that file as part of your route contract — it's reviewed and versioned like code.

Resources need parameter-segment translations too. This is the one that bites everyone:

// resources/lang/fr/routes.php
return [
    'test'       => 'teste',
    'test/{test}' => 'teste/{test}',   // required for show/edit/update/destroy
];

Without the second key the parameterized routes silently keep the English URI segment.

Signed URLs must be locale-aware. Password resets, unsubscribe links, and email verification need the locale baked into the signature:

URL::signedLocalizedRoute('unsubscribe', ['user' => 1]);
URL::temporarySignedLocalizedRoute('unsubscribe', now()->addMinutes(30), ['user' => 1]);

Generate these at send time using the recipient's stored locale, not the current request's.

SEO plumbing. Hreflang tags and sitemaps are both a loop over locales with localized_route():

@foreach (config('app.locales') as $locale)
    <link rel="alternate" hreflang="{{ $locale }}" href="{{ localized_route($base, $params, $locale) }}">
@endforeach

Emit x-default pointing at your configured default locale, and make sure a self-referencing hreflang exists.

Locale root redirect. If your default locale is unprefixed but users hit bare /, register the multilingual root first and add an explicit redirect for the locale-prefixed roots, otherwise /fr collides with a catch-all or 404s.

Testing. Assert against localized_route() rather than hardcoded URLs, and set the locale explicitly per test ($this->get(localized_route('home', [], 'fr'))). A route that passes in en and fails in fr almost always means a missing routes.php key.

Gotchas and Tips

  • route('home') does not exist. It's en.home. Any third-party package, notification, or Laravel internals that call route('home') will throw RouteNotFoundException. Audit these early — this is the single largest source of integration friction.
  • Missing translation keys fail silently. The URI just stays English. There's no exception, no warning. Add a CI check that every key in en/routes.php exists in every other locale file, and diff the key sets in a test.
  • Route caching freezes translations. route:cache compiles the expanded routes based on the routes.php contents at cache time. Editing a translation file on production without route:cache + config:cache (or optimize:clear) leaves stale URIs. Put this in your deploy script.
  • Default locale unprefixed means / is contested. Route::multilingual('/') claims the root, so nothing else can. Decide prefix-on-default before you build a sitemap or hreflang set, not after.
  • Two chainable naming styles. ->except() / ->only() on plain multilingual routes vs. ->exceptLocales() / ->onlyLocales() on resources — and on resources, ->except() / ->only() mean controller actions, not locales. Mixing them up produces routes you didn't intend and no error.
  • Per-locale names fragment your lookups. ->names(['en' => 'foo', 'fr' => 'bar']) yields en.foo and fr.bar, so helper calls must use the name for that specific locale. Treat it as an escape hatch for legacy URL parity, not a default — a single ->name() keeps localized_route('foo', [], $locale) working everywhere.
  • Route model binding uses the parameter name, not the slug. Localizing photos → photos gives you /fr/photos/{photo} with the same {photo} placeholder; only the literal prefix changes. If you need localized slugs, that's a separate concern (localized columns + getRouteKeyName), not something this package does.
  • multilingualResource with Laravel 12 features. ->withTrashed(['show']), ->whereParam('photos', '[0-9]+'), and ->missing(...) are supported but only on Laravel 12.x — they'll fatal on v4/Laravel 11. Guard accordingly if you support multiple majors.
  • Extension point: the package resolves URIs from the translator at route-registration time. If your translations live somewhere other than resources/lang/{locale}/routes.php (database, CMS), load them before routes/web.php is evaluated — a service provider's boot() is the right place, and a RouteServiceProvider override works if you need more control.
  • Debug quickly with: php artisan route:list --name=fr to see one locale in isolation, and php artisan route:list --path=fr to confirm the prefix is actually being applied.
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.
abbasudo/artisan-route
mirvan/ippanel
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
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package