8000 [Routing] Add locale fallback to internationalized routes · symfony/symfony@3ae6eaa · GitHub
[go: up one dir, main page]

Skip to content

Commit 3ae6eaa

Browse files
committed
[Routing] Add locale fallback to internationalized routes
1 parent eb112a5 commit 3ae6eaa

File tree

3 files changed

+116
-22
lines changed

3 files changed

+116
-22
lines changed

src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,19 +108,23 @@ private function generateGenerateMethod()
108108
{
109109
return <<<'EOF'
110110
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
111-
{
112-
$locale = $parameters['_locale']
113-
?? $this->context->getParameter('_locale')
114-
?: $this->defaultLocale;
115-
116-
if (null !== $locale && (self::$declaredRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) {
117-
unset($parameters['_locale']);
118-
$name .= '.'.$locale;
119-
} elseif (!isset(self::$declaredRoutes[$name])) {
120-
throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
111+
{
112+
foreach ($this->getLocalesToCheck($parameters, $this->defaultLocale) as $localeToCheck) {
113+
$localizedName = $name . '.' . $localeToCheck;
114+
if ((self::$declaredRoutes[$localizedName][1]['_canonical_route'] ?? null) === $name) {
115+
unset($parameters['_locale']);
116+
117+
$declaredRoute = self::$declaredRoutes[$localizedName];
118+
119+
break;
120+
}
121+
}
122+
123+
if (!isset($declaredRoute) && null === ($declaredRoute = self::$declaredRoutes[$name] ?? null)) {
124+
throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
121125
}
122126
123-
list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = self::$declaredRoutes[$name];
127+
list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = $declaredRoute;
124128
125129
return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes);
126130
}

src/Symfony/Component/Routing/Generator/UrlGenerator.php

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
6767
'%7C' => '|',
6868
);
6969

70+
/**
71+
* @var array the locales to check cache
72+
*/
73+
private static $localesToCheck = array();
74+
7075
public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null)
7176
{
7277
$this->routes = $routes;
@@ -112,13 +117,17 @@ public function isStrictRequirements()
112117
*/
113118
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
114119
{
115-
$locale = $parameters['_locale']
116-
?? $this->context->getParameter('_locale')
117-
?: $this->defaultLocale;
120+
foreach ($this->getLocalesToCheck($parameters, $this->defaultLocale) as $localeToCheck) {
121+
if (null !== ($localizedRoute = $this->routes->get($name.'.'.$localeToCheck)) && $localizedRoute->getDefault('_canonical_route') === $name) {
122+
unset($parameters['_locale']);
123+
124+
$route = $localizedRoute;
125+
126+
break;
127+
}
128+
}
118129

119-
if (null !== $locale && null !== ($route = $this->routes->get($name.'.'.$locale)) && $route->getDefault('_canonical_route') === $name) {
120-
unset($parameters['_locale']);
121-
} elseif (null === $route = $this->routes->get($name)) {
130+
if (!isset($route) && null === ($route = $this->routes->get($name))) {
122131
throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
123132
}
124133

@@ -277,6 +286,35 @@ protected function doGenerate($variables, $defaults, $requirements, $tokens, $pa
277286
return $url;
278287
}
279288

289+
/**
290+
* Get the locales to check from the array of parameters and the default locale.
291+
*
292+
* @param array $parameters the parameters
293+
* @param string|null $defaultLocale the default locale
294+
*
295+
* @return array the locales to check
296+
*/
297+
protected function getLocalesToCheck(array $parameters, ?string $defaultLocale): array
298+
{
299+
$locale = $parameters['_locale']
300+
?? $this->context->getParameter('_locale')
301+
?: $defaultLocale;
302+
303+
if (!is_string($locale)) {
304+
return array();
305+
}
306+
307+
if (!isset($this->localesToCheck[$locale])) {
308+
self::$localesToCheck[$locale] = array($locale);
309+
310+
if (false !== ($lastUnderscoreOccurrence = strrchr($locale, '_'))) {
311+
self::$localesToCheck[$locale][] = substr($locale, 0, -strlen($lastUnderscoreOccurrence));
312+
}
313+
}
314+
315+
return self::$localesToCheck[$locale];
316+
}
317+
280318
/**
281319
* Returns the target path as relative reference from the base path.
282320
*

src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ protected function setUp()
4646

4747
$this->routeCollection = new RouteCollection();
4848
$this->generatorDumper = new PhpGeneratorDumper($this->routeCollection);
49-
$this->testTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.php';
50-
$this->largeTestTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.large.php';
49+
$this->testTmpFilepath = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.php';
50+
$this->largeTestTmpFilepath = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.large.php';
5151
@unlink($this->testTmpFilepath);
5252
@unlink($this->largeTestTmpFilepath);
5353
}
@@ -84,19 +84,20 @@ public function testDumpWithRoutes()
8484
$this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter);
8585
}
8686

87-
public function testDumpWithLocalizedRoutes()
87+
public function testDumpWithSimpleLocalizedRoutes()
8888
{
89+
$this->routeCollection->add('test', (new Route('/foo')));
8990
$this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'test'));
9091
$this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_locale', 'nl')->setDefault('_canonical_route', 'test'));
9192

9293
$code = $this->generatorDumper->dump(array(
93-
'class' => 'LocalizedProjectUrlGenerator',
94+
'class' => 'SimpleLocalizedProjectUrlGenerator',
9495
));
9596
file_put_contents($this->testTmpFilepath, $code);
9697
include $this->testTmpFilepath;
9798

9899
$context = new RequestContext('/app.php');
99-
$projectUrlGenerator = new \LocalizedProjectUrlGenerator($context, null, 'en');
100+
$projectUrlGenerator = new \SimpleLocalizedProjectUrlGenerator($context, null, 'en');
100101

101102
$urlWithDefaultLocale = $projectUrlGenerator->generate('test');
102103
$urlWithSpecifiedLocale = $projectUrlGenerator->generate('test', array('_locale' => 'nl'));
@@ -109,6 +110,57 @@ public function testDumpWithLocalizedRoutes()
109110
$this->assertEquals('/app.php/testen/is/leuk', $urlWithSpecifiedLocale);
110111
$this->assertEquals('/app.php/testing/is/fun', $urlWithEnglishContext);
111112
$this->assertEquals('/app.php/testen/is/leuk', $urlWithDutchContext);
113+
114+
// test with full route name
115+
$this->assertEquals('/app.php/testing/is/fun', $projectUrlGenerator->generate('test.en'));
116+
117+
$context->setParameter('_locale', 'de_DE');
118+
// test that it fall backs to another route when there is no matching localized route
119+
$this->assertEquals('/app.php/foo', $projectUrlGenerator->generate('test'));
120+
}
121+
122+
/**
123+
* @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException
124+
* @expectedExceptionMessage Unable to generate a URL for the named route "test" as such route does not exist.
125+
*/
126+
public function testDumpWithRouteNotFoundLocalizedRoutes()
127+
{
128+
$this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'test'));
129+
130+
$code = $this->generatorDumper->dump(array(
131+
'class' => 'RouteNotFoundLocalizedProjectUrlGenerator',
132+
));
133+
file_put_contents($this->testTmpFilepath, $code);
134+
include $this->testTmpFilepath;
135+
136+
$projectUrlGenerator = new \RouteNotFoundLocalizedProjectUrlGenerator(new RequestContext('/app.php'), null, 'pl_PL');
137+
$projectUrlGenerator->generate('test');
138+
}
139+
140+
public function testDumpWithFallbackLocaleLocalizedRoutes()
141+
{
142+
$this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_canonical_route', 'test'));
143+
$this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_canonical_route', 'test'));
144+
$this->routeCollection->add('test.fr', (new Route('/tester/est/amusant'))->setDefault('_canonical_route', 'test'));
145+
146+
$code = $this->generatorDumper->dump(array(
147+
'class' => 'FallbackLocaleLocalizedProjectUrlGenerator',
148+
));
149+
file_put_contents($this->testTmpFilepath, $code);
150+
include $this->testTmpFilepath;
151+
152+
$context = new RequestContext('/app.php');
153+
$context->setParameter('_locale', 'en_GB');
154+
$projectUrlGenerator = new \FallbackLocaleLocalizedProjectUrlGenerator($context, null, null);
155+
156+
// test with context _locale
157+
$this->assertEquals('/app.php/testing/is/fun', $projectUrlGenerator->generate('test'));
158+
// test with parameters _locale
159+
$this->assertEquals('/app.php/testen/is/leuk', $projectUrlGenerator->generate('test', array('_locale' => 'nl_BE')));
160+
161+
$projectUrlGenerator = new \FallbackLocaleLocalizedProjectUrlGenerator(new RequestContext('/app.php'), null, 'fr_CA');
162+
// test with default locale
163+
$this->assertEquals('/app.php/tester/est/amusant', $projectUrlGenerator->generate('test'));
112164
}
113165

114166
public function testDumpWithTooManyRoutes()

0 commit comments

Comments
 (0)
0