Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,18 @@
*/

'name_prefix_before_locale' => env('MULTILINGUAL_ROUTES_NAME_PREFIX_BEFORE_LOCALE', false),

/*
|--------------------------------------------------------------------------
| Redirect Unprefixed Configuration
|--------------------------------------------------------------------------
|
| The configuration option that defines if routes without a locale prefix
| should be redirected to the default locale.
|
| Applies only if the default locale is prefixed.
|
*/

'redirect_unprefixed' => env('MULTILINGUAL_ROUTES_REDIRECT_UNPREFIXED', false),
];
40 changes: 40 additions & 0 deletions src/Controllers/RedirectController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace ChinLeung\MultilingualRoutes\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Illuminate\Routing\UrlGenerator;

class RedirectController
{
public const DESTINATION = 'multilingual_redirect_destination';

/**
* Invoke the controller method.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\UrlGenerator $url
* @return \Illuminate\Http\RedirectResponse
*/
public function __invoke(Request $request, UrlGenerator $url): RedirectResponse
{
$destination = $request->route()->getAction(self::DESTINATION);
$route = (new Route('GET', $destination, [
'as' => 'multilingual_route_redirect_destination',
]))->bind($request);

$parameters = collect($request->route()->parameters())->only(
$route->getCompiled()->getPathVariables()
)->all();

$destination = $url->toRoute($route, $parameters, false);

if ($query = $request->getQueryString()) {
$destination .= "?{$query}";
}

return new RedirectResponse($destination, 302);
}
}
40 changes: 40 additions & 0 deletions src/MultilingualRegistrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ChinLeung\MultilingualRoutes;

use ChinLeung\MultilingualRoutes\Controllers\RedirectController;
use Illuminate\Routing\Route;
use Illuminate\Routing\RouteCollection;
use Illuminate\Routing\Router;
Expand Down Expand Up @@ -46,6 +47,8 @@ public function register(string $key, $handle, array $locales, array $options):
$route->defaults($paramKey, $paramValue);
}
}

$this->registerUnprefixedRedirect($route, $locale);
}

return tap($this->router->getRoutes())->refreshNameLookups();
Expand All @@ -71,6 +74,8 @@ public function redirect(string $key, string $destination, int $status, array $l
$route->defaults($paramKey, $paramValue);
}
}

$this->registerUnprefixedRedirect($route, $locale);
}

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

/**
* Register a redirect to a prefixed default locale route.
*
* @param \Illuminate\Routing\Route $route
* @param string $locale
* @return void
*/
protected function registerUnprefixedRedirect(Route $route, string $locale): void
{
if (
! config('laravel-multilingual-routes.redirect_unprefixed')
|| $locale !== config('laravel-multilingual-routes.default')
|| ! config('laravel-multilingual-routes.prefix_default')
|| ! in_array('GET', $route->methods, true)
|| ($route->uri === $locale && ! config('laravel-multilingual-routes.prefix_default_home'))
|| ($route->uri !== $locale && ! str_starts_with($route->uri, "{$locale}/"))
) {
return;
}

$redirect = new Route(
['GET', 'HEAD'],
$route->uri === $locale ? '/' : substr($route->uri, strlen($locale) + 1),
array_merge(Arr::except($route->action, ['as', 'controller', 'prefix']), [
'uses' => RedirectController::class,
RedirectController::DESTINATION => "/{$route->uri}",
])
);

$redirect->wheres = $route->wheres;
$redirect->defaults = $route->defaults;

$this->router->getRoutes()->add($redirect);
}

/**
* Apply the constraints of a route.
*
Expand Down
16 changes: 11 additions & 5 deletions tests/RouteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ public function it_can_register_a_post_route(): void
->method('post')
->register();

foreach ($routes as $route) {
foreach (locales() as $locale) {
$this->assertContains(
'POST',
$route->methods
$routes->getByName("{$locale}.test")->methods
);
}
}
Expand Down Expand Up @@ -532,10 +532,16 @@ public function the_default_home_page_can_be_registered_without_prefix(): void
/** @test **/
public function a_route_can_be_registered_with_a_middleware(): void
{
Route::multilingual('/')->middleware('web');
$routes = Route::multilingual('/')
->middleware('web')
->name('home')
->register();

foreach (Route::getRoutes() as $route) {
$this->assertContains('web', data_get($route, 'action.middleware'));
foreach (locales() as $locale) {
$this->assertContains(
'web',
data_get($routes->getByName("{$locale}.home"), 'action.middleware')
);
}
}

Expand Down
119 changes: 119 additions & 0 deletions tests/UnprefixedRedirectTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

namespace ChinLeung\MultilingualRoutes\Tests;

use ChinLeung\LaravelLocales\LaravelLocalesServiceProvider;
use ChinLeung\MultilingualRoutes\MultilingualRoutesServiceProvider;
use Illuminate\Support\Facades\Route;
use Orchestra\Testbench\TestCase;

class UnprefixedRedirectTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

config([
'locales.supported' => ['en', 'fr'],
'laravel-multilingual-routes.default' => 'en',
'laravel-multilingual-routes.prefix_default' => true,
'laravel-multilingual-routes.redirect_unprefixed' => true,
]);
}

/** @test */
public function an_unprefixed_localized_route_redirects_to_the_default_locale(): void
{
Route::multilingual('posts/{post}', static function () {
//
})->where('post', '[0-9]+');

$this->get('/posts/123')->assertRedirect('/en/posts/123');
$this->get('/posts/not-a-number')->assertNotFound();
}

/** @test */
public function get_parameters_are_maintained_on_redirect(): void
{
Route::multilingual('search');

$response = $this->get('/search?query=multilingual%20routes&page=2');
$location = $response->headers->get('Location');
parse_str(parse_url($location, PHP_URL_QUERY), $query);

$this->assertSame('/en/search', parse_url($location, PHP_URL_PATH));
$this->assertSame('multilingual routes', $query['query']);
$this->assertSame('2', $query['page']);
}

/** @test */
public function a_url_without_an_applicable_default_locale_route_is_not_redirected(): void
{
Route::multilingual('french-only')->only('fr');

$this->get('/french-only')->assertNotFound();
$this->get('/not-localized')->assertNotFound();
}

/** @test */
public function an_unprefixed_default_locale_does_not_create_a_redirect(): void
{
config(['laravel-multilingual-routes.prefix_default' => false]);

Route::multilingual('posts', static fn () => 'posts');

$this->get('/posts')->assertOk()->assertSee('posts');
}

/** @test */
public function a_non_get_route_does_not_create_an_unprefixed_redirect(): void
{
Route::multilingual('posts', static function () {
//
})->method('post');

$this->post('/posts')->assertNotFound();
$this->post('/en/posts')->assertOk();
}

/** @test */
public function the_home_route_redirects_when_the_default_home_is_prefixed(): void
{
config(['laravel-multilingual-routes.prefix_default_home' => true]);

Route::multilingual('/');

$this->get('/')->assertRedirect('/en');
}

/** @test */
public function an_unprefixed_default_home_route_is_left_untouched(): void
{
config(['laravel-multilingual-routes.prefix_default_home' => false]);

Route::multilingual('/', static fn () => 'home');

$this->get('/')->assertOk()->assertSee('home');
}

/** @test */
public function an_unprefixed_redirect_respects_route_groups(): void
{
Route::prefix('admin')->group(static function () {
Route::multilingual('posts');
});

$response = $this->get('/admin/posts');

$response->assertRedirect('/en/admin/posts');
$this->assertSame('/en/admin/posts', $response->headers->get('Location'));
}

protected function getPackageProviders($app)
{
return [
LaravelLocalesServiceProvider::class,
MultilingualRoutesServiceProvider::class,
];
}
}
Loading