Search code examples
scalaplayframeworkroutesplay-framework-2.7

Play Router: How to add a language-sensitive URL redirect rule?


I have an internationalized Scala Play 2.7.x WebApp and have the usual routes e.g.

GET /               controllers.ApplicationController.index
GET /page/somePage/ controllers.SomeController.somePage
GET /contact        controllers.ContactController.view

Now I'd like to add a new route that will basically change-language-redirect to any target route. I implement this use-case by adding an additional route on top of routes like this:

GET /$lang<(en|es)> controllers.ApplicationController.langRedirect(lang: String, target: String = "")

The idea is that every time you do e.g.

http://localhost:9000/en => will go to home page in english
http://localhost:9000/en/contact => will go to contact page in english
http://localhost:9000/es => will go to home page in spanish
http://localhost:9000/es/contact => will go to contact page in spanish

and so on. Unfortunately it doesn't always work e.g. the one included before /en/page/somePage/ it will not match it correctly to the first rule:

 GET /$lang<(en|es)> controllers.ApplicationController.langRedirect(lang: String, target: String = "")

presumably because of the intermediate / ... how can I fix that?

For completeness here is my ApplicationController.langRedirect(...) implementation:

def langRedirect(lang: String, target: String = "") = silhouette.UserAwareAction.async { implicit request =>
    Future.successful(Redirect("/" + target).withLang(Lang(lang)))
}

Solution

  • Using Router.withPrefix, you can add langage code prefix to all your routes.

    Here is an example.

    package handlers
    
    import javax.inject.Inject
    import play.api.http._
    import play.api.i18n.{ Langs, Lang }
    import play.api.mvc.{ Handler, RequestHeader }
    
    class I18nRequestHandler @Inject()(
        webCommands: play.core.WebCommands,
        optDevContext: play.api.OptionalDevContext,
        router: play.api.routing.Router,
        errorHandler: HttpErrorHandler,
        configuration: HttpConfiguration,
        filters: HttpFilters,
        langs: Langs)
      extends DefaultHttpRequestHandler(
        webCommands, optDevContext, router, errorHandler, configuration, filters) {
    
      def getLang(request: RequestHeader): Lang = {
        // Get the first path
        request.path.tail.split('/').headOption
          .flatMap(path => Lang.get(path))
          // language from the fist path, if it is in "play.i18n.langs (application.conf)"
          .filter(lang => langs.availables.exists(_ == lang))
          // Or preferred language, refereeing "Accept-Languages"
          .getOrElse(langs.preferred(request.acceptLanguages))
      }
    
      override def handlerForRequest(request: RequestHeader): (RequestHeader, Handler) = {
        // To use the language code from the path with MessagesApi,
        // Replace "Accept-Languages" to the language from the path.
        val requestWithLang = request.withHeaders(
          request.headers.replace(HeaderNames.ACCEPT_LANGUAGE -> getLang(request).code))
        super.handlerForRequest(requestWithLang)
      }
    
      override def routeRequest(request: RequestHeader): Option[Handler] = {
        val lang = getLang(request)
        request.path.tail.split('/').headOption
          // If the first path is right language code (if not, Not Found)
          .filter(_ == lang.code)
          // Route this request with language code prefix
          .flatMap(_ => router.withPrefix("/" + lang.code).handlerFor(request))
      }
    }
    

    To enable I18nRequestHandler, you have to add it to "application.conf".

    play.http.requestHandler = "handlers.I18nRequestHandler"
    

    Also add supported languages to "application.conf".

    play.i18n.langs = [ "en", "es" ]
    

    This code forces all routes to have the language code prefix. If you need a exceptional routes such as "/" to let users choose its language, create custom routes and add it on routeRequest method.

    Hope this is what you want ;)