Monday, January 13, 2014

Symfony, internalization/localization, and 404 pages

Background

Given 1) the Symfony framework and 2) the need for localization, one shouldn't be surprised that there's a package that takes care of that already (somewhat): JMSI18nRoutingBundle. It works so that in your config.yml, you can specify the default locale, the locales that your site works with, and what the domain names are for each locale. I guess I'm not using this bundle correctly, though, because although my config.yml file looks like this:

jms_i18n_routing:
  default_locale: %locale%
  locales: [en, fr]
  strategy: custom
  hosts:
    en: www.englishVersion.com
    fr: www.frenchVersion.com
  redirect_to_host: true

typing in the French locale into the address bar redirects to the English site. (And if you happen to have an inkling of what I'm doing wrong, do let me know!)

This necessitates the extra steps of 1) creating a separate /fr path that serves up French content and 2) configuring .htaccess to redirect www.frenchVersion.com to that path.

The Problem

Given that 1) www.frenchVersion.com originally serves up English content and 2) needs coaxing in .htaccess to redirect, I hit the problem of English 404 pages appearing under the French domain. Ie., www.frenchVersion.com/path-does-not-exist will serve up the English 404 message.

The Solutions

The halfway solution - detect the language in the template

In my custom 404 page (in project_root\app\Resources\TwigBundle\views\Exception\error404.html.twig), I included an if-statement that checked the domain name and then set the variable lang appropriately.

{% set lang = ('frenchVersion' in app.request.getHost()) ? 'fr' : 'en' %}

{{ 'projname.error404.copy' | trans({}, "messages", lang) }}

This solution only worked halfway, though. While the 404 message was in French, the surrounding layout.html.twig was still in English.

Full solution - create a listener for any exceptions thrown

With the guidance of this Stackoverflow answer, I was able to check for the domain name before anything was rendered when an exception is thrown. This is a two-file (or two-part) solution. First, you create a LanguageListener in projectroot\src\GenericName\SpecificNameBundle\EventListener. Code it like:


namespace GenericName\SpecificNameBundle\EventListener;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;


class LanguageListener
{
  public function setLocale(GetResponseEvent $event)
  {
    if (strstr($_SERVER['HTTP_HOST'], 'frenchVersion'))
    {
      $request = $event->getRequest();
      $request->setLocale('fr');
    }
  }
}

And then in projectroot\src\GenericName\SpecificNameBundle\Resources\config\services.yml:

services:
  # ...
  genericname.language.kernel_request_listener:
    class: GenericName\SpecificNameBundle\EventListener\LanguageListener
    tags:
      - { name: kernel.event_listener, event: kernel.exception, method: setLocale }

And there you go! Even your layout should be rendered using the correct locale. Go try it out! Happy coding!