Skip to content

Commit 4daffcb

Browse files
committed
Support redirects to localized routes (#35) (#18)
1 parent 5638495 commit 4daffcb

File tree

5 files changed

+211
-7
lines changed

5 files changed

+211
-7
lines changed

README.md

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,23 @@ If you set `omit_url_prefix_for_locale` to `'en'` in the configuration file, the
254254
You can't have a localized `/about` route and also register a non-localized `/about` route in this case.
255255
The same idea applies to the `/` (root) route! Also note that the route names still have the locale prefix.
256256

257-
### 🔦 Localized `404` Pages
257+
### 🔦 Localized `404` Pages and Redirecting to Localized URL's
258+
259+
To use these 2 features, you need to register the fallback route **at the end** of your `routes/web.php` file:
260+
261+
```php
262+
Route::fallback(\CodeZero\LocalizedRoutes\Controller\FallbackController::class)
263+
->middleware(\CodeZero\LocalizedRoutes\Middleware\SetLocale::class);
264+
```
265+
266+
##### 404 - Not Found
267+
268+
After registering the fallback route the provided controller will look for a 404 error view at `resources/views/errors/404.blade.php`.
269+
If this view does not exist, a normal `NotFoundHttpException` will be thrown.
270+
You can configure which view to use by changing the `404_view` entry in the config file.
271+
272+
<details>
273+
<summary>**Some background info...**</summary>
258274

259275
By default, Laravel's `404` pages don't go trough the middleware and have no `Route::current()` associated with it.
260276
Not even when you create your custom `errors.404` view.
@@ -264,12 +280,6 @@ To enable localized `404` pages, you need to register a `fallback` route
264280
and make sure it has the `SetLocale` middleware.
265281
This is basically a catch all route that will trigger for all non existing URL's.
266282

267-
```php
268-
Route::fallback(function () {
269-
return response()->view('errors.404', [], 404);
270-
})->middleware(\CodeZero\LocalizedRoutes\Middleware\SetLocale::class);
271-
```
272-
273283
Another thing to keep in mind is that a `fallback` route returns a `200` status by default.
274284
So to make it a real `404` you need to return a `404` response yourself.
275285

@@ -283,6 +293,23 @@ Because those routes are in fact registered, the `404` page will have the correc
283293

284294
[Here is a good read about fallback routes](https://themsaid.com/laravel-55-better-404-response-20170921).
285295

296+
</details>
297+
298+
##### Redirecting to Localized URL's
299+
300+
After registering the fallback route, you can update the config option `redirect_to_localized_urls` to `true`.
301+
The provided `FallbackController` will then try to redirect routes that have not been registered, to their localized version.
302+
If it does not have a localized version, a `404` will be thrown.
303+
304+
> So the home page `/` would be redirected to `/en` if the active locale is `en` and the `/en` route exists.
305+
And `/about` would redirect to `/en/about`.
306+
307+
If you configured the app to omit the main locale slug, then the former redirection will obviously not happen, because the unprefixed routes exist.
308+
Instead, accessing a prefixed route with the main locale will redirect to an unprefixed URL.
309+
310+
> So if the main locale is `en`, visiting `/en` would redirect to the home page `/` (unless `/` is a registered route).
311+
Also `/en/about` would redirect to `/about`.
312+
286313
### 🚕 Generate Route URL's
287314

288315
You can get the URL of your named routes as usual, using the `route()` helper.

config/localized-routes.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,28 @@
1515
*/
1616
'omit_url_prefix_for_locale' => null,
1717

18+
/**
19+
* Set this option to true if you want to redirect
20+
* unlocalized URL's to their localized version.
21+
* You need to register the fallback route for this to work.
22+
*/
23+
'redirect_to_localized_urls' => false,
24+
25+
/**
26+
* The status code when redirecting to localized URL's.
27+
* 301 - permanently
28+
* 302 - temporary
29+
*/
30+
'redirect_status_code' => 301,
31+
32+
/**
33+
* Set your custom 404 view.
34+
* This view is localized.
35+
* If the view does not exist, a normal 404 will be thrown.
36+
* You need to register the fallback route for this to work.
37+
*/
38+
'404_view' => 'errors.404',
39+
1840
/**
1941
* If you want to automatically set the locale
2042
* for localized routes set this to true.

src/Controller/FallbackController.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace CodeZero\LocalizedRoutes\Controller;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Routing\Controller;
7+
use Illuminate\Support\Collection;
8+
use Illuminate\Support\Facades\Config;
9+
use Illuminate\Support\Facades\Redirect;
10+
use Illuminate\Support\Facades\Response;
11+
use Illuminate\Support\Facades\Route;
12+
use Illuminate\Support\Facades\View;
13+
14+
class FallbackController extends Controller
15+
{
16+
/**
17+
* Handle the fallback route.
18+
*
19+
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
20+
*/
21+
public function __invoke()
22+
{
23+
$shouldRedirect = Config::get('localized-routes.redirect_to_localized_urls', false);
24+
25+
if ($shouldRedirect) {
26+
$localizedUrl = Route::localizedUrl();
27+
$route = $this->findRouteByUrl($localizedUrl);
28+
29+
if ( ! $route->isFallback) {
30+
return Redirect::to($localizedUrl, Config::get('localized-routes.redirect_status_code', 301));
31+
}
32+
}
33+
34+
return $this->NotFoundResponse();
35+
}
36+
37+
/**
38+
* Find a Route by its URL.
39+
*
40+
* @param string $url
41+
*
42+
* @return \Illuminate\Routing\Route
43+
*/
44+
protected function findRouteByUrl($url)
45+
{
46+
return Collection::make(Route::getRoutes())->first(function ($route) use ($url) {
47+
return $route->matches(Request::create($url));
48+
});
49+
}
50+
51+
/**
52+
* Return a 404 view or throw a 404 error if the view doesn't exist.
53+
*
54+
* @return \Illuminate\Http\Response
55+
*/
56+
protected function NotFoundResponse()
57+
{
58+
$view = Config::get('localized-routes.404_view');
59+
60+
if (View::exists($view)) {
61+
return Response::view($view, [], 404);
62+
}
63+
64+
abort(404);
65+
}
66+
}

tests/TestCase.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ protected function setOmitUrlPrefixForLocale($value)
7979
Config::set('localized-routes.omit_url_prefix_for_locale', $value);
8080
}
8181

82+
/**
83+
* Set the 'redirect_to_localized_urls' config option.
84+
*
85+
* @param bool $value
86+
*
87+
* @return void
88+
*/
89+
protected function setRedirectToLocalizedUrls($value)
90+
{
91+
Config::set('localized-routes.redirect_to_localized_urls', $value);
92+
}
93+
8294
/**
8395
* Set the use_locale_middleware config option
8496
*
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace CodeZero\LocalizedRoutes\Tests\Unit;
4+
5+
use CodeZero\LocalizedRoutes\Tests\TestCase;
6+
use Illuminate\Support\Facades\Route;
7+
8+
class RedirectToLocalizedTest extends TestCase
9+
{
10+
/** @test */
11+
public function it_redirects_to_the_localized_url()
12+
{
13+
$this->withoutExceptionHandling();
14+
$this->setSupportedLocales(['en', 'nl']);
15+
$this->setUseLocaleMiddleware(false);
16+
$this->setRedirectToLocalizedUrls(true);
17+
18+
Route::localized(function () {
19+
Route::get('/', function () {});
20+
Route::get('about', function () {});
21+
});
22+
23+
Route::fallback(\CodeZero\LocalizedRoutes\Controller\FallbackController::class);
24+
25+
$this->setAppLocale('en');
26+
$this->get('/')->assertRedirect('en');
27+
$this->get('en')->assertOk();
28+
$this->get('about')->assertRedirect('en/about');
29+
$this->get('en/about')->assertOk();
30+
31+
$this->setAppLocale('nl');
32+
$this->get('/')->assertRedirect('nl');
33+
$this->get('nl')->assertOk();
34+
$this->get('about')->assertRedirect('nl/about');
35+
$this->get('nl/about')->assertOk();
36+
}
37+
38+
/** @test */
39+
public function it_redirects_when_default_locale_slug_is_omitted()
40+
{
41+
$this->withoutExceptionHandling();
42+
$this->setSupportedLocales(['en', 'nl']);
43+
$this->setUseLocaleMiddleware(false);
44+
$this->setOmitUrlPrefixForLocale('en');
45+
$this->setRedirectToLocalizedUrls(true);
46+
47+
Route::localized(function () {
48+
Route::get('/', function () {});
49+
Route::get('about', function () {});
50+
});
51+
52+
Route::fallback(\CodeZero\LocalizedRoutes\Controller\FallbackController::class);
53+
54+
$this->setAppLocale('en');
55+
$this->get('en')->assertRedirect('/');
56+
$this->get('/')->assertOk();
57+
$this->get('en/about')->assertRedirect('about');
58+
$this->get('about')->assertOk();
59+
60+
$this->setAppLocale('nl');
61+
$this->get('nl')->assertOk();
62+
$this->get('nl/about')->assertOk();
63+
}
64+
65+
/** @test */
66+
public function it_throws_404_and_does_not_redirect_if_no_localized_route_is_registered()
67+
{
68+
$this->setSupportedLocales(['en', 'nl']);
69+
$this->setUseLocaleMiddleware(false);
70+
$this->setRedirectToLocalizedUrls(true);
71+
72+
Route::fallback(\CodeZero\LocalizedRoutes\Controller\FallbackController::class);
73+
74+
$this->setAppLocale('en');
75+
$this->get('missing')->assertNotFound();
76+
}
77+
}

0 commit comments

Comments
 (0)