Skip to content

Commit 0baced3

Browse files
committed
feat: add redirect_unprefixed config option
1 parent bf45dc4 commit 0baced3

4 files changed

Lines changed: 213 additions & 0 deletions

File tree

config/config.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,18 @@
4747
*/
4848

4949
'name_prefix_before_locale' => env('MULTILINGUAL_ROUTES_NAME_PREFIX_BEFORE_LOCALE', false),
50+
51+
/*
52+
|--------------------------------------------------------------------------
53+
| Redirect Unprefixed Configuration
54+
|--------------------------------------------------------------------------
55+
|
56+
| The configuration option that defines if routes without a locale prefix
57+
| should be redirected to the default locale.
58+
|
59+
| Applies only if the default locale is prefixed.
60+
|
61+
*/
62+
63+
'redirect_unprefixed' => env('MULTILINGUAL_ROUTES_REDIRECT_UNPREFIXED', false),
5064
];
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace ChinLeung\MultilingualRoutes\Controllers;
4+
5+
use Illuminate\Http\RedirectResponse;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Routing\Route;
8+
use Illuminate\Routing\UrlGenerator;
9+
10+
class RedirectController
11+
{
12+
public const DESTINATION = 'multilingual_redirect_destination';
13+
14+
/**
15+
* Invoke the controller method.
16+
*
17+
* @param \Illuminate\Http\Request $request
18+
* @param \Illuminate\Routing\UrlGenerator $url
19+
* @return \Illuminate\Http\RedirectResponse
20+
*/
21+
public function __invoke(Request $request, UrlGenerator $url): RedirectResponse
22+
{
23+
$destination = $request->route()->getAction(self::DESTINATION);
24+
$route = (new Route('GET', $destination, [
25+
'as' => 'multilingual_route_redirect_destination',
26+
]))->bind($request);
27+
28+
$parameters = collect($request->route()->parameters())->only(
29+
$route->getCompiled()->getPathVariables()
30+
)->all();
31+
32+
$destination = $url->toRoute($route, $parameters, false);
33+
34+
if ($query = $request->getQueryString()) {
35+
$destination .= "?{$query}";
36+
}
37+
38+
return new RedirectResponse($destination, 302);
39+
}
40+
}

src/MultilingualRegistrar.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace ChinLeung\MultilingualRoutes;
44

5+
use ChinLeung\MultilingualRoutes\Controllers\RedirectController;
56
use Illuminate\Routing\Route;
67
use Illuminate\Routing\RouteCollection;
78
use Illuminate\Routing\Router;
@@ -46,6 +47,8 @@ public function register(string $key, $handle, array $locales, array $options):
4647
$route->defaults($paramKey, $paramValue);
4748
}
4849
}
50+
51+
$this->registerUnprefixedRedirect($route, $locale);
4952
}
5053

5154
return tap($this->router->getRoutes())->refreshNameLookups();
@@ -71,6 +74,8 @@ public function redirect(string $key, string $destination, int $status, array $l
7174
$route->defaults($paramKey, $paramValue);
7275
}
7376
}
77+
78+
$this->registerUnprefixedRedirect($route, $locale);
7479
}
7580

7681
return tap($this->router->getRoutes())->refreshNameLookups();
@@ -383,6 +388,41 @@ protected function shouldNotPrefixDefaultHome(string $locale): bool
383388
&& ! config('laravel-multilingual-routes.prefix_default_home');
384389
}
385390

391+
/**
392+
* Register a redirect to a prefixed default locale route.
393+
*
394+
* @param \Illuminate\Routing\Route $route
395+
* @param string $locale
396+
* @return void
397+
*/
398+
protected function registerUnprefixedRedirect(Route $route, string $locale): void
399+
{
400+
if (
401+
! config('laravel-multilingual-routes.redirect_unprefixed')
402+
|| $locale !== config('laravel-multilingual-routes.default')
403+
|| ! config('laravel-multilingual-routes.prefix_default')
404+
|| ! in_array('GET', $route->methods, true)
405+
|| ($route->uri === $locale && ! config('laravel-multilingual-routes.prefix_default_home'))
406+
|| ($route->uri !== $locale && ! str_starts_with($route->uri, "{$locale}/"))
407+
) {
408+
return;
409+
}
410+
411+
$redirect = new Route(
412+
['GET', 'HEAD'],
413+
$route->uri === $locale ? '/' : substr($route->uri, strlen($locale) + 1),
414+
array_merge(Arr::except($route->action, ['as', 'controller', 'prefix']), [
415+
'uses' => RedirectController::class,
416+
RedirectController::DESTINATION => "/{$route->uri}",
417+
])
418+
);
419+
420+
$redirect->wheres = $route->wheres;
421+
$redirect->defaults = $route->defaults;
422+
423+
$this->router->getRoutes()->add($redirect);
424+
}
425+
386426
/**
387427
* Apply the constraints of a route.
388428
*

tests/UnprefixedRedirectTest.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
namespace ChinLeung\MultilingualRoutes\Tests;
4+
5+
use ChinLeung\LaravelLocales\LaravelLocalesServiceProvider;
6+
use ChinLeung\MultilingualRoutes\MultilingualRoutesServiceProvider;
7+
use Illuminate\Support\Facades\Route;
8+
use Orchestra\Testbench\TestCase;
9+
10+
class UnprefixedRedirectTest extends TestCase
11+
{
12+
protected function setUp(): void
13+
{
14+
parent::setUp();
15+
16+
config([
17+
'locales.supported' => ['en', 'fr'],
18+
'laravel-multilingual-routes.default' => 'en',
19+
'laravel-multilingual-routes.prefix_default' => true,
20+
'laravel-multilingual-routes.redirect_unprefixed' => true,
21+
]);
22+
}
23+
24+
/** @test */
25+
public function an_unprefixed_localized_route_redirects_to_the_default_locale(): void
26+
{
27+
Route::multilingual('posts/{post}', static function () {
28+
//
29+
})->where('post', '[0-9]+');
30+
31+
$this->get('/posts/123')->assertRedirect('/en/posts/123');
32+
$this->get('/posts/not-a-number')->assertNotFound();
33+
}
34+
35+
/** @test */
36+
public function get_parameters_are_maintained_on_redirect(): void
37+
{
38+
Route::multilingual('search');
39+
40+
$response = $this->get('/search?query=multilingual%20routes&page=2');
41+
$location = $response->headers->get('Location');
42+
parse_str(parse_url($location, PHP_URL_QUERY), $query);
43+
44+
$this->assertSame('/en/search', parse_url($location, PHP_URL_PATH));
45+
$this->assertSame('multilingual routes', $query['query']);
46+
$this->assertSame('2', $query['page']);
47+
}
48+
49+
/** @test */
50+
public function a_url_without_an_applicable_default_locale_route_is_not_redirected(): void
51+
{
52+
Route::multilingual('french-only')->only('fr');
53+
54+
$this->get('/french-only')->assertNotFound();
55+
$this->get('/not-localized')->assertNotFound();
56+
}
57+
58+
/** @test */
59+
public function an_unprefixed_default_locale_does_not_create_a_redirect(): void
60+
{
61+
config(['laravel-multilingual-routes.prefix_default' => false]);
62+
63+
Route::multilingual('posts', static fn () => 'posts');
64+
65+
$this->get('/posts')->assertOk()->assertSee('posts');
66+
}
67+
68+
/** @test */
69+
public function a_non_get_route_does_not_create_an_unprefixed_redirect(): void
70+
{
71+
Route::multilingual('posts', static function () {
72+
//
73+
})->method('post');
74+
75+
$this->post('/posts')->assertNotFound();
76+
$this->post('/en/posts')->assertOk();
77+
}
78+
79+
/** @test */
80+
public function the_home_route_redirects_when_the_default_home_is_prefixed(): void
81+
{
82+
config(['laravel-multilingual-routes.prefix_default_home' => true]);
83+
84+
Route::multilingual('/');
85+
86+
$this->get('/')->assertRedirect('/en');
87+
}
88+
89+
/** @test */
90+
public function an_unprefixed_default_home_route_is_left_untouched(): void
91+
{
92+
config(['laravel-multilingual-routes.prefix_default_home' => false]);
93+
94+
Route::multilingual('/', static fn () => 'home');
95+
96+
$this->get('/')->assertOk()->assertSee('home');
97+
}
98+
99+
/** @test */
100+
public function an_unprefixed_redirect_respects_route_groups(): void
101+
{
102+
Route::prefix('admin')->group(static function () {
103+
Route::multilingual('posts');
104+
});
105+
106+
$response = $this->get('/admin/posts');
107+
108+
$response->assertRedirect('/en/admin/posts');
109+
$this->assertSame('/en/admin/posts', $response->headers->get('Location'));
110+
}
111+
112+
protected function getPackageProviders($app)
113+
{
114+
return [
115+
LaravelLocalesServiceProvider::class,
116+
MultilingualRoutesServiceProvider::class,
117+
];
118+
}
119+
}

0 commit comments

Comments
 (0)