Internacionalizar web por directorios según el país del usuario

En este articulo os voy a explicar como internacionalizar una aplicacion web redirigiendo al usuario al directorio segun el pais en el que se encuentre.

Os pongo el siguiente ejemplo: Imaginemos que queremos que los usuarios que viven en Mexico solo puedan ver los productos especificos de su pais, ya que estan segmentados por paises. Por lo que un usuario que acceda a la web española debera ser redirigido a la web de Mexico.

Para ello usaremos un bundle para las rutas muy util llamado : JMSi18nRoutingBundle:

1- Añadimos al composer.json

"jms/i18n-routing-bundle": "^2.0"

2- Inicializamos en AppKernel en bundle:

new JMS\I18nRoutingBundle\JMSI18nRoutingBundle(),

3- Configuramos el bundle(Existen mas opciones disponibles en el bundle pero en nuestro caso queremos esta)

jms_i18n_routing:
    default_locale: es
    locales: [es, mx]
    strategy: prefix

4- Instalamos el bundle para Geolocalizar por IP al usuario:

"maxmind/geoip": "~1.0.0",

5- Inicializamos en AppKernel

new \Maxmind\Bundle\GeoipBundle\MaxmindGeoipBundle(),

6- Este bundle necesita descargarse la base de datos interna para localizar la IP del usuario, para ello ejecutamos el siguiente comando:

php app/console maxmind:geoip:update-data 
http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz

Esto nos descargara un fichero .dat dentro del bundle pero si queremos moverlo para versionarlo o cualquier cosa, creamos esta configuracion de config.yml

maxmind_geoip:
    data_file_path: "%kernel.root_dir%/../app/GeoLiteCity.dat"

En mi caso lo he ubicado dentro de la carpeta app/

7- Creamos un Listener para capturar los eventos del Kernel y comprobar la IP, de la siguiente forma AppBundle/Listener/LocaleListener.php

<?php

namespace AppBundle\Listener;

use Maxmind\Bundle\GeoipBundle\Service\GeoipManager;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;

class LocaleListener implements EventSubscriberInterface
{
    const DEFAULT_LANGUAGE = 'es';

    private $avaliableLanguages;

    /** @var TokenStorageInterface */
    private $context;

    /** @var  Router $router */
    private $router;

    /** @var GeoipManager */
    private $geoIp;

    /** @var AuthorizationChecker */
    private $authChecker;

    /**
     * LocaleListener constructor.
     *
     * @param TokenStorageInterface $context
     * @param Router $router
     * @param GeoipManager $geoIp
     * @param AuthorizationChecker $authorizationChecker
     * @param $languages
     */
    public function __construct(TokenStorageInterface $context, Router $router, GeoipManager $geoIp, AuthorizationChecker $authorizationChecker, $languages)
    {
        $this->context = $context;
        $this->router = $router;
        $this->geoIp = $geoIp;
        $this->authChecker = $authorizationChecker;
        $this->avaliableLanguages = $languages;

    }

    /**
     * @param GetResponseEvent $event
     */
    public function onKernelRequest(GetResponseEvent $event)
    {
        /** @var Request $request */
        $request = $event->getRequest();
        $localeRequest = $request->getLocale();

        $clientIp = $request->getClientIp();
        $geoip = $this->geoIp->lookup($clientIp);

        if($geoip){
            $geoIpLocale = strtolower($geoip->getCountryCode());

            if(in_array($geoIpLocale, $this->avaliableLanguages)){
                $request->setLocale($geoIpLocale);
            }else{
                $request->setLocale(self::DEFAULT_LANGUAGE);
            }
        }elseif (!in_array($localeRequest, $this->avaliableLanguages)){
            $request->setLocale(self::DEFAULT_LANGUAGE);
        }else{
            $request->setLocale($localeRequest);
        }
    }

    public static function getSubscribedEvents()
    {
        return array(
            KernelEvents::REQUEST => array(array('onKernelRequest', 1))
        );
    }
}

8- Añadimos dentro del parameters.yml los idiomas disponibles:

languages:
    - es
    - mx

9- Creamos el servicio para que se enganche al subscribe del Kernel dentro del archivo /app/services.yml

service:
//....
myapp.locale_listener:
    class: AppBundle\Listener\LocaleListener
    arguments: ["@security.token_storage", "@router", "@maxmind.geoip", "@security.authorization_checker" ,"%languages%"]
    tags:
        - { name: kernel.event_subscriber }

Con estos cambios ya tendremos practicamente todo montado, ahora si accedemos a cualquier ruta de la web, debera setear el idioma que le toque o si no existe poner el idioma por defecto.

Tambien podemos añadir en el footer de nuestra web, varios enlaces a los idiomas posibles de la siguiente forma:

  • <a href=”{{ path(‘homepage’, {‘_locale’: ‘mx’}) }}”>Mexico</a>
  • <a href=”{{ path(‘homepage’, {‘_locale’: ‘es’}) }}”>España</a>

Espero que os haya gustado y recordar compartir este articulo.

Saludos ¡¡

Anuncios

Internacionalización de nuestra aplicación web o i18N en symfony2 con JMSTranslationBundle

El post de hoy tratará sobre como traducir nuestra aplicacion web hecha con Symfony 2 a distintos idiomas y no morir en el intento. (Que va, es mucho mas sencillo ya vereis…)

Lo primero, voy a usar un bundle muy famoso de los que más soporte tienen y que me gusta mucho porque aparte de funcionar muy bien, nos ofrece un panel donde poder añadir o editar traducciones que la verdad es que es muy cómodo y da mucha libertad si en nuestro equipo tuviéramos a personal dedicado a las traducciones, ya que les podríamos asignar un determinado ROL para que accedan al panel a trabajar todas las traducciones de nuestra aplicacion web.

Primeros pasos

1- Instalar bundle/s

>> composer require “jms/translation-bundle”

> composer require “jms/i18n-routing-bundle”

2- Configurar los bundles

2.1 Creamos un nuevo fichero jms_translations.yml, lo incluimos en nuestro config.yml y dentro añadimos todos los bundles que contendrán traducciones:

jms_translation:
    configs:
        app:
            dirs: ["%kernel.root_dir%", "%kernel.root_dir%/../src"]
            output_dir: "%kernel.root_dir%/Resources/translations"
            ignored_domains: ["routes"]
            output_format:  yml
            excluded_dirs: ["cache, data, logs"]
            extractors: ["jms_i18n_routing"]
        app_bundle:
            dirs: ["%kernel.root_dir%", "%kernel.root_dir%/../src"]
            output_dir: "%kernel.root_dir%/Resources/FOSUserBundle/translations/translations"
            extractors: ["jms_i18n_routing"]
        ...etc.

2.2 Creamos otro fichero llamado jms_i18n_routing.yml, lo incluimos en el config.yml y añadimos:

jms_i18n_routing:
    default_locale: "%locale%"
    locales: "%languages%"
    strategy: prefix_except_default

* Tendremos que definir en nuestro parameters.yml el ‘local’ y los lenguages que ofreceremos disponibles.

3- Habilitar dichos bundles en nuestro AppKernel.php

new JMS\TranslationBundle\JMSTranslationBundle(),
new JMS\I18nRoutingBundle\JMSI18nRoutingBundle(),

4- Crear una clase LocaleListener dentro de AppBundle/Listener/ que en cada petición revise si llega un parámetro llamado ‘_locale’ para que ajuste el locale en la session, tal como dice la documentación oficial de Symfony.

<?php
namespace efor\CoreBundle\Listener;

use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class LocaleListener implements EventSubscriberInterface
{
    private $defaultLocale;

    public function __construct($defaultLocale = 'es')
    {
        $this->defaultLocale = $defaultLocale;
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();
        if (!$request->hasPreviousSession()) {
            return;
        }

        // try to see if the locale has been set as a _locale routing parameter
        if ($locale = $request->attributes->get('_locale')) {
            $request->getSession()->set('_locale', $locale);
        } else {
            // if no explicit locale has been set on this request, use one from the session
            $request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
        }
    }

    public static function getSubscribedEvents()
    {
        return array(
            // must be registered after the default Locale listener
            KernelEvents::REQUEST => array(array('onKernelRequest', 15)),
        );
    }
}

5- Declaramos el servicio en nuestro app/config/services.yml

services:
    ...
    app.locale_listener:
        class: efor\CoreBundle\Listener\LocaleListener
        arguments: ['%kernel.default_locale%']
        tags:
            - { name: kernel.event_subscriber }

6- Ahora tendremos que crear las carpetas de traducciones dentro de cada bundle para añadir las traducciones de cada idioma tal que así(en mi caso tengo 3 idiomas, entonces un fichero por cada idioma):

estructura de carpetas traducciones

7- Dentro de cada fichero ‘.yml’ añadiremos las traducciones en el siguiente formato, agrupando las cosas comunes y que tengan sentido:

organization:
    title: 'Organizaciones'
    create: 'Crear organización'
    list: 'Lista de organizaciones'
    add_course: 'Añadir curso a organización'
logout: 'Cerrar sesión'

8- Ahora solo nos faltaría añadir el típico selector de idiomas en nuestro ‘header’ o ‘footer’ para que pueda cambiar el ‘locale’ de la aplicacion y listo(aun no del todo):

<a href="{{ url('dashboard_homepage', {'_locale': 'es'}) }}">Español</a> |
<a href="{{ url('dashboard_homepage', {'_locale': 'va'}) }}">Valenciano</a> |
<a href="{{ url('dashboard_homepage', {'_locale': 'en'}) }}">Ingles</a>

Añadimos un parámetro a la URL para que el LocaleListener lo recoja y lo setee en la session del usuario y asi, puedan funcionar las traducciones por arte de magia.

Y listo… ¿¡Espera no, aún no deberia funcionar…¡? Y de esto me dado cuenta después de 10 minutos de revisarlo todo de arriba a abajo¡¡¡

>> sudo rm -rf app/cache/* app/sessions/*

Si no borras la cache y cierras la session que pudieras tener abierta, no funcionaran las traducciones ni se añadirá el locale en la URL indicando que locale tienes ¡

Documentacion:

-http://jmsyst.com/bundles/JMSTranslationBundle

-http://jmsyst.com/bundles/JMSI18nRoutingBundle

Configurar el panel de traducciones

Como os he comentado antes, este magnifico bundle viene con un panel para administrar las traducciones que tenemos metidas en los ficheros. Para configurar el acceso a este panel, lo haremos de la siguiente forma:

1-  Instalamos el bundle que requiere dicho panel:

> composer require “jms/di-extra-bundle”

2- Activamos el bundle en AppKernel.php como nos dicen en la documentación del bundle:

new JMS\DiExtraBundle\JMSDiExtraBundle($this),
new JMS\AopBundle\JMSAopBundle(),

3- Importamos en el routing_dev.yml el bloque(solo se podrá acceder desde el entorno de desarrollo):

JMSTranslationBundle_ui:
    resource: "@JMSTranslationBundle/Controller/"
    type:     annotation
    prefix:   /_trans

*Si queremos añadirle seguridad de acceso, podemos añadir la siguiente configuración en el security.yml

4- Ahora podemos acceder a la ruta /_trans y veremos el panel que de forma automática nos ofrece el bundle JMS. Puede que nos aparezca un mensaje de error diciéndonos que los archivos no tienen permisos de escritura, por lo que tendremos que darles esos permisos a mano, o crearnos este pequeño shell-script para darle permisos a TODOS a la vez.

#!/bin/bash

find ../ -type f -name "*.es.yml" -exec chmod 777 {} \;
find ../ -type f -name "*.en.yml" -exec chmod 777 {} \;
find ../ -type f -name "*.va.yml" -exec chmod 777 {} \;

Si queréis mas informacion, podéis visitar los enlaces que he puesto arriba de documentación.

Si os ha gustado el post, no dudéis en compartirlo en redes sociales.

Hasta la próxima SymfonyDevs ¡