Package Data | |
---|---|
Maintainer Username: | codezero |
Maintainer Contact: | ivan@codezero.be (Ivan Vermeyen) |
Package Create Date: | 2018-03-25 |
Package Last Update: | 2024-03-17 |
Home Page: | |
Language: | PHP |
License: | MIT |
Last Refreshed: | 2025-01-23 15:10:44 |
Package Statistics | |
---|---|
Total Downloads: | 470,913 |
Monthly Downloads: | 13,443 |
Daily Downloads: | 717 |
Total Stars: | 517 |
Total Watchers: | 16 |
Total Forks: | 47 |
Total Open Issues: | 11 |
route()
helper.redirect()->route()
helper.Route::localizedUrl()
macro.404
pages.composer require codezero/laravel-localized-routes
Laravel will automatically register the ServiceProvider.
php artisan vendor:publish --provider="CodeZero\LocalizedRoutes\LocalizedRoutesServiceProvider" --tag="config"
You will now find a localized-routes.php
file in the config
folder.
Add any locales you wish to support to your published config/localized-routes.php
file:
'supported-locales' => ['en', 'nl', 'fr'],
This will automatically prepend a slug to your localized routes. More on this below.
Alternatively, you can use a different domain or subdomain for each locale by configuring the supported-locales
like this:
'supported-locales' => [
'en' => 'example.com',
'nl' => 'nl.example.com',
'fr' => 'fr.example.com',
],
Specify your main locale if you want to omit its slug from the URL:
'omit_url_prefix_for_locale' => null
Setting this option to 'en'
will result, for example, in URL's like this:
/some-url
instead of the default /en/some-url
/nl/some-url
as usual/fr/some-url
as usualThis option has no effect if you use domains instead of slugs.
By default, the app locale will always be what you configured in config/app.php
.
To automatically update the app locale when a localized route is accessed, you need to use a middleware.
⚠️ Important note for Laravel 6+
To make route model binding work in Laravel 6+ you always also need to add the middleware
to the $middlewarePriority
array in app/Http/Kernel.php
so it runs before SubstituteBindings
:
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class, // <= after this
//...
\CodeZero\LocalizedRoutes\Middleware\SetLocale::class,
//...
\Illuminate\Routing\Middleware\SubstituteBindings::class, // <= before this
];
You can then enable the middleware in a few ways:
For every localized route, via our config file
Simply set the option to true
to add the middleware to every localized route:
'use_locale_middleware' => true
This will not add the middleware to non-localized routes!
OR, for every route using the web
middleware group
You can manually add the middleware to the $middlewareGroups
array in app/Http/Kernel.php
:
protected $middlewareGroups = [
'web' => [
\Illuminate\Session\Middleware\StartSession::class, // <= after this
//...
\CodeZero\LocalizedRoutes\Middleware\SetLocale::class,
//...
\Illuminate\Routing\Middleware\SubstituteBindings::class, // <= before this
],
];
OR, for specific routes
Alternatively, you can add the middleware to a specific route or route group:
Route::localized(function () {
Route::get('about', AboutController::class.'@index')
->name('about')
->middleware(\CodeZero\LocalizedRoutes\Middleware\SetLocale::class);
Route::group([
'as' => 'admin.',
'middleware' => [\CodeZero\LocalizedRoutes\Middleware\SetLocale::class],
], function () {
Route::get('admin/reports', ReportsController::class.'@index')
->name('reports.index');
});
});
This package can use codezero/laravel-localizer to automatically detect and set the locale.
With this option disabled, the app locale will only be updated when accessing localized routes.
With this option enabled, the app locale can also be updated when accessing non-localized routes. For non-localized routes it will look for a preferred locale in the session, in a cookie or in the browser. Additionally, it will also store the app locale in the session and in a cookie.
Enabling this option can be handy if you have, for example, a generic homepage and you want to know the preferred locale.
To enable this option, set it to true
in the published config file.
'use_localizer' => true
This option only has effect on routes that use our SetLocale
middleware.
You can review codezero/laravel-localizer, publish its config file and tweak it as needed. The only option we will override is
supported-locales
, to match the option in our own config file.
To set an option for one localized route group only, you can specify it as the second parameter of the localized route macro. This will override the config file settings.
Route::localized(function () {
Route::get('about', AboutController::class.'@index')
->name('about');
}, [
'supported-locales' => ['en', 'nl', 'fr'],
'omit_url_prefix_for_locale' => null,
'use_locale_middleware' => false,
]);
Example:
// Not localized
Route::get('home', HomeController::class.'@index')
->name('home');
// Localized
Route::localized(function () {
Route::get('about', AboutController::class.'@index')
->name('about');
Route::name('admin.')->group(function () {
Route::get('admin/reports', ReportsController::class.'@index')
->name('reports.index');
});
});
In the above example there are 5 routes being registered.
The routes defined in the Route::localized
closure are automatically registered for each configured locale.
This will prepend the locale to the route's URI and name. If you configured custom domains, it will use those instead of the slugs.
| URI | Name | | ----------------- | ---------------------- | | /home | home | | /en/about | en.about | | /nl/about | nl.about | | /en/admin/reports | en.admin.reports.index | | /nl/admin/reports | nl.admin.reports.index |
If you set omit_url_prefix_for_locale
to 'en'
in the configuration file, the resulting routes look like this:
| URI | Name | | ----------------- | ---------------------- | | /home | home | | /about | en.about | | /nl/about | nl.about | | /admin/reports | en.admin.reports.index | | /nl/admin/reports | nl.admin.reports.index |
⚠️ Beware that you don't register the same URL twice when omitting the locale.
You can't have a localized /about
route and also register a non-localized /about
route in this case.
The same idea applies to the /
(root) route! Also note that the route names still have the locale prefix.
404
Pages and Redirecting to Localized URL'sTo use these 2 features, you need to register the fallback route at the end of your routes/web.php
file:
Route::fallback(\CodeZero\LocalizedRoutes\Controller\FallbackController::class)
->middleware(\CodeZero\LocalizedRoutes\Middleware\SetLocale::class);
After registering the fallback route the provided controller will look for a 404 error view at resources/views/errors/404.blade.php
.
If this view does not exist, a normal NotFoundHttpException
will be thrown.
You can configure which view to use by changing the 404_view
entry in the config file.
By default, Laravel's 404
pages don't go trough the middleware and have no Route::current()
associated with it.
Not even when you create your custom errors.404
view.
Therefor, the locale can't be set to match the requested URL automatically via middleware.
To enable localized 404
pages, you need to register a fallback
route
and make sure it has the SetLocale
middleware.
This is basically a catch all route that will trigger for all non existing URL's.
Another thing to keep in mind is that a fallback
route returns a 200
status by default.
So to make it a real 404
you need to return a 404
response yourself.
Fallback routes will not be triggered when:
404
error (as in abort(404)
)ModelNotFoundException
(like with route model binding)Because those routes are in fact registered, the 404
page will have the correct App::getLocale()
set.
Here is a good read about fallback routes.
After registering the fallback route, you can update the config option redirect_to_localized_urls
to true
.
The provided FallbackController
will then try to redirect routes that have not been registered, to their localized version.
If it does not have a localized version, a 404
will be thrown.
So the home page
/
would be redirected to/en
if the active locale isen
and the/en
route exists. And/about
would redirect to/en/about
.
If you configured the app to omit the main locale slug, then the former redirection will obviously not happen, because the unprefixed routes exist. Instead, accessing a prefixed route with the main locale will redirect to an unprefixed URL.
So if the main locale is
en
, visiting/en
would redirect to the home page/
(unless/
is a registered route). Also/en/about
would redirect to/about
.
You can get the URL of your named routes as usual, using the route()
helper.
Normally you would have to include the locale whenever you want to generate a URL:
$url = route(app()->getLocale().'.admin.reports.index');
Because the former is rather ugly, this package overwrites the route()
function
and the underlying UrlGenerator
class with an additional, optional $locale
argument
and takes care of the locale prefix for you. If you don't specify a locale, either a normal,
non-localized route or a route in the current locale is returned.
$url = route('admin.reports.index'); // current locale
$url = route('admin.reports.index', [], true, 'nl'); // dutch URL
This is the new route helper signature:
route($name, $parameters = [], $absolute = true, $locale = null)
A few examples (given the example routes we registered above):
app()->setLocale('en');
app()->getLocale(); // 'en'
$url = route('home'); // /home (normal routes have priority)
$url = route('about'); // /en/about (current locale)
// Get specific locales...
// This is most useful if you want to generate a URL to switch language.
$url = route('about', [], true, 'en'); // /en/about
$url = route('about', [], true, 'nl'); // /nl/about
// You could also do this, but it kinda defeats the purpose...
$url = route('en.about'); // /en/about
$url = route('en.about', [], true, 'nl'); // /nl/about
Note: in a most practical scenario you would register a route either localized or non-localized, but not both. If you do, you will always need to specify a locale to get the URL, because non-localized routes always have priority when using the
route()
function.
Laravel's Redirector
uses the same UrlGenerator
as the route()
function behind the scenes.
Because we are overriding this class, you can easily redirect to your routes.
return redirect()->route('home'); // non-localized route, redirects to /home
return redirect()->route('about'); // localized route, redirects to /en/about (current locale)
You can't redirect to URL's in a specific locale this way, but if you need to, you can of course just use the route()
function.
return redirect(route('about', [], true, 'nl')); // localized route, redirects to /nl/about
To generate a URL for the current route in any locale, you can use this Route
macro:
If your route uses a localized key (like a slug) and you are using route model binding, then the key will automatically be localized.
$current = \Route::localizedUrl(); // /en/posts/en-slug
$en = \Route::localizedUrl('en'); // /en/posts/en-slug
$nl = \Route::localizedUrl('nl'); // /nl/posts/nl-slug
If you have a route with multiple keys, like /en/posts/{id}/{slug}
, then you can pass the parameters yourself
(like in the example without route model binding below) or you can implement this interface in your model:
use CodeZero\LocalizedRoutes\ProvidesRouteParameters;
use Illuminate\Database\Eloquent\Model;
class Post extends Model implements ProvidesRouteParameters
{
public function getRouteParameters($locale = null)
{
return [
$this->id,
$this->getSlug($locale) // Add this method yourself of course :)
];
}
}
Now, as long as you use route model binding, you can still just do:
$current = \Route::localizedUrl(); // /en/posts/en-slug
$en = \Route::localizedUrl('en'); // /en/posts/en-slug
$nl = \Route::localizedUrl('nl'); // /nl/posts/nl-slug
If you don't use route model binding and you need a localized slug in the URL, then you will have to pass it manually.
For example:
$nl = \Route::localizedUrl('nl'); // Wrong: /nl/posts/en-slug
$nl = \Route::localizedUrl('nl', [$post->getSlug('nl')]); // Right: /nl/posts/nl-slug
The getSlug()
method is just for illustration, so you will need to implement that yourself of course.
Generating a signed route URL is just as easy.
Pass it the route name, the necessary parameters and you will get the URL for the current locale.
$signedUrl = URL::signedRoute('reset.password', ['user' => $id], now()->addMinutes(30));
You can also generate a signed URL for a specific locale:
$signedUrl = URL::signedRoute($name, $parameters, $expiration, true, 'nl');
Check out the Laravel docs for more info on signed routes.
If you want to translate the segments of your URI's, create a routes.php
language file for each locale you configured:
resources
└── lang
├── en
│ └── routes.php
└── nl
└── routes.php
In these files, add a translation for each segment.
// lang/nl/routes.php
return [
'about' => 'over',
'us' => 'ons',
];
Now you can use our Lang::uri()
macro during route registration:
Route::localized(function () {
Route::get(Lang::uri('about/us'), AboutController::class.'@index')
->name('about.us');
});
Note that in order to find a translated version of a route, you will need to give your routes a name. If you don't name your routes, only the parameters (model route keys) will be translated, not the "hard-coded" slugs.
The above will generate:
If a translation is not found, the original segment is used.
Parameter placeholders are not translated via language files. These are values you would provide via the route()
function.
The Lang::uri()
macro will skip any parameter placeholder segment.
If you have a model that uses a route key that is translated in the current locale,
then you can still simply pass the model to the route()
function to get translated URL's.
An example...
Given we have a model like this:
class Post extends \Illuminate\Database\Eloquent\Model
{
public function getRouteKey()
{
$slugs = [
'en' => 'en-slug',
'nl' => 'nl-slug',
];
return $slugs[app()->getLocale()];
}
}
TIP: checkout spatie/laravel-translatable for translatable models.
If we have a localized route like this:
Route::localized(function () {
Route::get('posts/{post}', PostsController::class.'@show')
->name('posts.show');
});
We can now get the URL with the appropriate slug:
app()->setLocale('en');
app()->getLocale(); // 'en'
$post = new Post;
$url = route('posts.show', [$post]); // /en/posts/en-slug
$url = route('posts.show', [$post], true, 'nl'); // /nl/posts/nl-slug
If you enable the middleware included in this package, you can use Laravel's route model binding to automatically inject models with localized route keys in your controllers.
All you need to do is add a resolveRouteBinding()
method to your model.
Check Laravel's documentation
for alternative ways to enable route model binding.
public function resolveRouteBinding($value)
{
// Perform the logic to find the given slug in the database...
return $this->where($this->getRouteKeyName().'->'.app()->getLocale(), $value)->firstOrFail();
}
Laravel 7 and newer has an optional $field
parameter that allows you to override the route key on specific routes:
// Use the post slug as the route parameter instead of the default ID
Route::get('posts/{post:slug}', ...);
The new method would then look like this:
public function resolveRouteBinding($value, $field = null)
{
// Perform the logic to find the given slug in the database...
return $this->where($field ?? $this->getRouteKeyName().'->'.app()->getLocale(), $value)->firstOrFail();
}
TIP: checkout spatie/laravel-translatable for translatable models.
In production you can safely cache your routes per usual.
php artisan route:cache
composer test
If you discover any security related issues, please e-mail me instead of using the issue tracker.
A complete list of all notable changes to this package can be found on the releases page.
The MIT License (MIT). Please see License File for more information.