Default internationalization support in Play works with cookies which is not SEO-friendly. It would be very nice if it was possible to use route parameters instead of cookies but as Play routes are (nicely, truly and correctly) type-safe, this will come at some cost; verbosity. Albeit it’s not true for smart people who code in Scala :D
I’m using the Scala version of the Play framework.
Play selects the preferred user language through
Accept-Language HTTP header out of the box, but we want the user to be able to select the language which is also supported by Play as well.
How it works by default
Controller has an implicit method
request2lang which takes an implicit
Request[A] and provides the corresponding
All of the internationalizing APIs take an implicit
Lang object (which is now replaced by
Messages in Play 2.4, while the concept is the same) that would be provided by
request2lang tries to get the language from cookies if available, otherwise it checks
accept-language header, and then selects the preferred language specified in the application configuration.
There are two major approach for changing the current language in Play:
- Explicit route parameters
Change the language using cookie
It’s simply possible to set/change the current language by calling
withLang method on a
Or you can clear the selected language:
It sets/clears the cookie (named
PLAY_LANG) which stores the language code behind the scene.
- Easy to use
- Not SEO friendly (I used the same approach and Google never indexed other languages)
Use type-safe explicit route parameters
Using cookies to store the language is not a SEO friendly approach. It seems that Google crawler ignores cookies, which makes sense.
The best approach is to use route parameters as language indicators. For example
http://www.mysite.com/fa points to the Persian version of the site and the
http://www.mysite.com/en points to the English one.
This way we have to add language parameter to each action and its corresponding route:
Read more information about this approach here.
- SEO friendly
- Pollute all action methods with extra language parameters
Explicit route parameters are too verbose and cookies are not SEO friendly. Another interesting approach is to intercept the routing process. We can fetch and remove the current language from the route and create a new path to be used for routing.
http://www.mysite.com/en/page/1 would be transformed to
http://www.mysite.com/page/1 and the route file would be like this:
But where can we store the parsed and removed language code? The best place I’ve found is the headers.
Here is the filter which parses the language code and stores it to the headers:
I also add the custom header
x-Route-Lang which will come handy to distinguish processed URLs from the originals.
As its not possible to transform the route by the filters, we have to override
routeRequest method of
Global object in Play 2.3 or
DefaultHttpRequestHandler in Play 2.4 to do so.
Language code will be extracted by the
Filter and route transformation will be done by
As we update the cookies according to the route parameter,
request2lang method of
Controller works as expected.
The only thing we have to worry about is the reverse routing. There is two approach:
- Redirecting to SEO friendly URLs
Callhelpers (ex. by adding
withLangmethod) which adds the language code to the original URL
I went for the first one because I’ll forgot to call the specified method in second approach for all hyper links. More importantly it’s not type-safe nor automated.
All of my controllers inherit from a special controller trait which provides them a centralized action builder. So it’s super easy for me to intercept all actions of a specific controller by implementing a trait.
Supposing the base controller trait has a simple action method (mine is more complicated) which is used by child controllers:
This would be the trait which redirects all non-SEO friendly URLs of the child controller to SEO friendly ones:
As you can see
x-Route-Lang header (
RouteLangFilter.routeLangKey) is used to find out whether we should redirect to SEO-friendly URLs or not.
Now its just enough to implement
WithRouteLang trait for each controller you want to be SEO-friendly internationalized.