I recently added the Laravel-Localization package (with Laravel 5.2) to a project I’m working on. Initial setup went smoothly, and I was impressed with how well things worked essentially out of the box.
Then I tried to test my routes.
I had previously made a test case that ran through all of my public routes and asserted that each one returned HTTP status code 200 OK. Suddenly this was failing on every route I tried, saying that 404 Not Found was being returned instead.
There was 1 failure:
A request to [http://localhost/en] failed. Received status code .
The route worked in the browser. It returned the correct result using
curl on the vagrant box. But it refused to work in the test. I had the application’s default locale set to ‘en’, and every indication was that it worked perfectly everywhere except in the tests. I pared the test right down:
public function testMinimalExample()
The error was the same. The request was correctly updated from
/en based on the default locale, but apparently Laravel insisted on returning 404 to the test. I would have understood if the status code were 301 or 302, since I’m using the LaravelLocalizationRedirectFilter middleware and expect there to be a redirect. But why would the route simply disappear?
To make a long debugging session short, it turns out that while LaravelLocalizationRedirectFilter middleware correctly leads any browser to make a second request for the new URL, the testing framework tries to follow redirects within the routes available to the same initial request object.
Standard operation of Laravel-Localization is to set up a route group with a prefix value of
LaravelLocalization::setLocale(). When the browser visits the homepage initially, this function returns null so
Route::get('/', ...) is matched, the middleware is invoked, and the browser is redirected to
/en. The browser then invokes a second request, this time to
/en as directed, which means
setLocale() is called again, this time returning
Route::get('/', ...) is now prefixed by
'en', the requested route is matched, and everything works correctly.
In the test however, there is only ever a single request object, in which the prefix defined by
setLocale() is simply
''. This matches the first time, the server runs the middleware and sends a redirect, but instead of making a second request, the testing framework tries to follow the redirect by resolving the new route against the
RouteCollection in the original
Request object. Since it was created with a prefix of
'', any route prefixed by
'en' won’t exist.
So, how do we solve this? I’ve found two interim solutions so far, though I plan to keep looking another day.
Option 1 is to disable the LaravelLocalizationRedirectFilter middleware. This means that requests to un-locale-prefixed routes will work just fine, but won’t be redirected to their prefixed versions, resulting in two different URLs for each resource in the default language.
Option 2 is to set
'hideDefaultLocaleInURL' => true in the
laravellocalization.php config file. This results in the redirection of all URLs prefixed with the default locale to unprefixed ones—
/users, and so on. This is perhaps preferable to Option 1 from an SEO standpoint, but comes with the caveat that the Accept-Language header of any user’s browser will now be ignored.
Likely some other options exist that involve changing the requested routes in the test cases instead.