Skip to content

Commit 62ace8b

Browse files
committed
Localize 404 and fallback URL's
1 parent cd14605 commit 62ace8b

File tree

2 files changed

+230
-7
lines changed

2 files changed

+230
-7
lines changed

src/LocalizedUrlGenerator.php

Lines changed: 134 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace CodeZero\LocalizedRoutes;
44

55
use Illuminate\Support\Collection;
6+
use Illuminate\Support\Facades\App;
7+
use Illuminate\Support\Facades\Config;
8+
use Illuminate\Support\Facades\Request;
69
use Illuminate\Support\Facades\Route;
710
use Illuminate\Support\Facades\URL;
811

@@ -23,33 +26,106 @@ public function __construct()
2326
$this->route = Route::current();
2427
}
2528

29+
/**
30+
* Generate a localized URL for the current request.
31+
*
32+
* @param string|null $locale
33+
* @param mixed $parameters
34+
* @param bool $absolute
35+
*
36+
* @return string
37+
*/
38+
public function generateFromRequest($locale = null, $parameters = null, $absolute = true)
39+
{
40+
return ($this->isDefault404() || $this->isNonLocalizedFallback404())
41+
? $this->generateFromUrl($locale, $parameters, $absolute)
42+
: $this->generateFromRoute($locale, $parameters, $absolute);
43+
}
44+
2645
/**
2746
* Check if the current route is localized.
2847
*
2948
* @return bool
3049
*/
3150
public function isLocalized()
3251
{
33-
return $this->route && $this->route->getAction('localized-routes-locale');
52+
return $this->routeExists() && $this->route->getAction('localized-routes-locale') !== null;
3453
}
3554

3655
/**
37-
* Generate a localized URL for the current request.
56+
* Check if the current request is a default 404.
57+
* Default 404 requests will not have a Route.
58+
*
59+
* @return bool
60+
*/
61+
protected function isDefault404()
62+
{
63+
return ! $this->routeExists();
64+
}
65+
66+
/**
67+
* Check if the current request is a non localized fallback 404 route.
68+
* If a fallback route is used as a 404, we expect it to be named '404'.
69+
*
70+
* @return bool
71+
*/
72+
protected function isNonLocalizedFallback404()
73+
{
74+
return $this->routeExists() && $this->route->isFallback && $this->route->getName() === '404';
75+
}
76+
77+
/**
78+
* Check if the current Route exists.
79+
* Default 404 requests will not have a Route.
80+
*
81+
* @return bool
82+
*/
83+
protected function routeExists()
84+
{
85+
return $this->route !== null;
86+
}
87+
88+
/**
89+
* Generate a localized version of a URL.
3890
*
3991
* @param string|null $locale
4092
* @param mixed $parameters
4193
* @param bool $absolute
4294
*
4395
* @return string
4496
*/
45-
public function generateFromRequest($locale = null, $parameters = null, $absolute = true)
97+
protected function generateFromUrl($locale = null, $parameters = null, $absolute = true)
4698
{
47-
// Route does not exist (default 404)
48-
if ( ! $this->route) {
49-
return URL::current();
99+
$locale = $locale ?: App::getLocale();
100+
$supportedLocales = $this->getSupportedLocales();
101+
$locales = $this->getLocaleKeys($supportedLocales);
102+
$domains = $this->getCustomDomains($supportedLocales);
103+
$currentUrl = Request::fullUrl();
104+
$urlParts = parse_url($currentUrl);
105+
106+
if ($domains !== null) {
107+
$urlParts['host'] = $domains[$locale] ?? $urlParts['host'];
108+
}
109+
110+
if ($domains === null) {
111+
$currentPath = $urlParts['path'] ?? '';
112+
$slugs = explode('/', trim($currentPath, '/'));
113+
$localeSlug = $slugs[0] ?? '';
114+
115+
if (in_array($localeSlug, $locales)) {
116+
$slugs[0] = $locale;
117+
} else {
118+
array_unshift($slugs, $locale);
119+
}
120+
121+
if ($slugs[0] === Config::get('localized-routes.omit_url_prefix_for_locale')) {
122+
$urlParts[0] = '';
123+
} else {
124+
$urlParts['path'] = '/' . join('/', $slugs);
125+
}
50126
}
51127

52-
return $this->generateFromRoute($locale, $parameters, $absolute);
128+
return $urlParts['scheme'] . '://' . $urlParts['host'] . ($urlParts['port'] ?? '') . ($urlParts['path'] ?? '');
53129
}
54130

55131
/**
@@ -80,4 +156,55 @@ protected function generateFromRoute($locale = null, $parameters = null, $absolu
80156

81157
return route($this->route->getName(), $parameters, $absolute, $locale);
82158
}
159+
160+
/**
161+
* Get the custom domains from the supported locales configuration.
162+
*
163+
* @return array|null
164+
*/
165+
protected function getCustomDomains(array $locales)
166+
{
167+
return $this->hasCustomDomains($locales) ? $locales : null;
168+
}
169+
170+
/**
171+
* Get the locale keys from the supported locales configuration.
172+
*
173+
* @param array $locales
174+
*
175+
* @return array
176+
*/
177+
protected function getLocaleKeys(array $locales)
178+
{
179+
return $this->hasCustomDomains($locales) ? array_keys($locales) : $locales;
180+
}
181+
182+
/**
183+
* Check if custom domains are configured.
184+
*
185+
* @param array $locales
186+
*
187+
* @return bool
188+
*/
189+
protected function hasCustomDomains(array $locales)
190+
{
191+
$keys = array_keys($locales);
192+
193+
if (empty($locales) || is_numeric($keys[0])) {
194+
return false;
195+
}
196+
197+
return true;
198+
}
199+
200+
/**
201+
* Get the supported locales and not the custom domains.
202+
*
203+
* @return array
204+
*/
205+
protected function getSupportedLocales()
206+
{
207+
return Config::get('localized-routes.supported-locales', []);
208+
209+
}
83210
}

tests/Unit/Macros/LocalizedUrlMacroTest.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,100 @@ public function it_returns_a_localized_url_for_a_localized_fallback_route()
242242
'nl' => url('/nl/non/existing/route'),
243243
], $response->original);
244244
}
245+
246+
/** @test */
247+
public function it_returns_a_localized_url_for_a_non_localized_fallback_route_if_the_url_contains_a_supported_locale()
248+
{
249+
$this->setSupportedLocales(['en', 'nl']);
250+
$this->setAppLocale('nl');
251+
252+
Route::fallback(function () {
253+
return response([
254+
'current' => Route::localizedUrl(),
255+
'en' => Route::localizedUrl('en'),
256+
'nl' => Route::localizedUrl('nl'),
257+
], 404);
258+
})->name('404');
259+
260+
$response = $this->call('GET', '/nl/non/existing/route');
261+
$response->assertNotFound();
262+
$this->assertEquals([
263+
'current' => url('/nl/non/existing/route'),
264+
'en' => url('/en/non/existing/route'),
265+
'nl' => url('/nl/non/existing/route'),
266+
], $response->original);
267+
}
268+
269+
/** @test */
270+
public function it_returns_a_localized_url_for_a_non_localized_fallback_route_if_the_url_does_not_contain_a_supported_locale()
271+
{
272+
$this->setSupportedLocales(['en', 'nl']);
273+
$this->setAppLocale('nl');
274+
275+
Route::fallback(function () {
276+
return response([
277+
'current' => Route::localizedUrl(),
278+
'en' => Route::localizedUrl('en'),
279+
'nl' => Route::localizedUrl('nl'),
280+
], 404);
281+
})->name('404');
282+
283+
$response = $this->call('GET', '/non/existing/route');
284+
$response->assertNotFound();
285+
$this->assertEquals([
286+
'current' => url('/nl/non/existing/route'),
287+
'en' => url('/en/non/existing/route'),
288+
'nl' => url('/nl/non/existing/route'),
289+
], $response->original);
290+
}
291+
292+
/** @test */
293+
public function it_returns_a_localized_url_for_a_non_localized_fallback_route_when_omitting_the_main_locale()
294+
{
295+
$this->setSupportedLocales(['en', 'nl']);
296+
$this->setOmitUrlPrefixForLocale('nl');
297+
$this->setAppLocale('nl');
298+
299+
Route::fallback(function () {
300+
return response([
301+
'current' => Route::localizedUrl(),
302+
'en' => Route::localizedUrl('en'),
303+
'nl' => Route::localizedUrl('nl'),
304+
], 404);
305+
})->name('404');
306+
307+
$response = $this->call('GET', '/non/existing/route');
308+
$response->assertNotFound();
309+
$this->assertEquals([
310+
'current' => url('/non/existing/route'),
311+
'en' => url('/en/non/existing/route'),
312+
'nl' => url('/non/existing/route'),
313+
], $response->original);
314+
}
315+
316+
/** @test */
317+
public function it_returns_a_localized_url_for_a_non_localized_fallback_route_when_using_custom_domains()
318+
{
319+
$this->setSupportedLocales([
320+
'en' => 'en.domain.test',
321+
'nl' => 'nl.domain.test',
322+
]);
323+
$this->setAppLocale('nl');
324+
325+
Route::fallback(function () {
326+
return response([
327+
'current' => Route::localizedUrl(),
328+
'en' => Route::localizedUrl('en'),
329+
'nl' => Route::localizedUrl('nl'),
330+
], 404);
331+
})->name('404');
332+
333+
$response = $this->call('GET', '/en/non/existing/route');
334+
$response->assertNotFound();
335+
$this->assertEquals([
336+
'current' => 'http://nl.domain.test/en/non/existing/route',
337+
'en' => 'http://en.domain.test/en/non/existing/route',
338+
'nl' => 'http://nl.domain.test/en/non/existing/route',
339+
], $response->original);
340+
}
245341
}

0 commit comments

Comments
 (0)