Webs multi-idioma en CodeIgniter con gettext

Hace un tiempo decidimos empezar a utilizar el framework CodeIgniter para algunos de nuestros proyectos de PHP.

¿Qué es un framework? Una herramienta para desarrollar aplicaciones, webs, etc., que proporciona una serie de piezas de código ya programadas y suficientemente testadas que pueden ser reutilizadas, facilitando así el trabajo, ya que te evita tener que crear toda la estructura de un proyecto desde cero.

CodeIgniter ya ofrece una serie de clases que permiten crear una web multi-idioma, pero en nuestro caso queríamos hacer uso de gettext para generar nuestras webs en varios idiomas y para ello hemos tenido que adaptar CodeIgniter.

Y la siguiente pregunta es… ¿qué es gettext? Pues tal y como dice la wikipedia, se trata de “una biblioteca GNU de internacionalización” que se utiliza para escribir programas con interfaces en varios idiomas.

Para trabajar con gettext necesitamos unos archivos, los “.po”, en los que se almacenan una serie de claves y traducciones. Nuestra aplicación debe tener, por cada idioma en la deseemos que esté, dos archivos: uno con extensión “.po” y otro con extensión “.mo”. En realidad los archivos “.po” son compilados en un fichero binario “.mo” que son los que realmente leen las aplicaciones.

¿Y quién se encarga de dicha compilación? En nuestro caso concreto utilizamos poedit. Se trata de un editor de catálogos de gettext. Permite editar archivos “.po” para gestionar las traducciones y se encarga de compilar dichos archivos a formato binario generando así los archivos “.mo”.

poedit

Bueno, una vez que ya tenemos claro quienes son CodeIgniter, gettext, poedit y nuestros amigos los “.po” y los “.mo”, avancemos en nuestro objetivo: cómo generar una web multi-idioma con CodeIgniter y gettext.

Lo primero que haremos será generar los archivos con las traducciones. Por convenio, estos archivos deben almacenarse bajo la siguiente ruta “application\language\locales”. Bajo dicho directorio se debe crear un subdirectorio por cada idioma, cuyo nombre debe ser el código del idioma: “es_ES”, “eu_ES”, “en_EN”, etc…

En cada directorio del idioma correspondiente, crearemos un nuevo subdirectorio “LC_MESSAGES” dentro del cual añadiremos dos archivos, nuestro amigos, ¿os acordáis? Sí, sí,  el “.po” y el “.mo”, que son los que contienen las traducciones, y denominaremos con la siguiente nomenclatura “xx_XX_lang”, siendo xx_XX el código del idioma correspondiente.

Resumiendo, debemos tener la siguiente estructura:

estructura_archivos_idiomas

Para no hacer este post excesivamente largo, dejaremos la generación de archivos desde el Poedit para otra ocasión.

Para leer los archivos con las traducciones lo que hacemos es utilizar una serie de librerías que guardaremos en “application\libraries”, concretamente las siguientes:

Tras esto debemos reemplazar la clase “Lang.php” del CodeIgniter. La clase original se encuentra en “system\core”. Para reemplazarla, creamos en el directorio “application\core” un nuevo archivo “Lang.php” que contendrá la nueva clase “CI_Lang” que será la que se ejecute en lugar de la clase original.

La clase “Lang” original, además del contructor, sólo tiene dos métodos:

  • Load, encargado de cargar los archivos con las traducciones, los “.mo”.
  • Line, encargado de obtener la traducción que se solicite en base al parámetro que recibe.

Con el nuevo sistema de traducciones debemos redefinir ambos métodos para que carguen los nuevos archivos de traducciones y obtengan los datos de dichos archivos.

Para cargar los archivos usamos la clase POMO, implementada en el archivo “POMO.php” que hemos mencionado anteriormente.

Y para obtener las traducciones utilizaremos la función “gettext” definida en dicha clase.

En la clase “Lang” (Lang.php), además de la modificación de los métodos indicados, hay código añadido para realizar el control de idiomas por url. Para realizar este control, hay que realizar además de las modificaciones indicadas otra serie de cambios. En la siguiente url se indica los cambios a realizar: http://maestric.com/en/doc/php/codeigniter_i18n

Hay que tener en cuenta que las librerías y helpers que trae CodeIgniter por defecto también utilizan la clase “Lang” cuando requieren mostrar textos. Los textos a los que se puede hacer referencia desde estas librerías se encuentran definidos en una serie de archivos localizados en “system\language\XXX“, donde XXX es el nombre del idioma en el que están los textos, por defecto “english”:

estructura_system_language

Debemos seguir manteniendo los “códigos” asociados a los textos que se especifican en estos archivos, para que cuando se use una de estas librerías aparezcan los textos que tengan asociados correctamente. Para mantenerlos y poderlos gestionar también desde el poedit, hemos implementado la siguiente solución:

  • Generamos un nuevo directorio en “application\language” denominado “textos_sistema”. Dentro de dicho directorio cargamos todos los archivos “.php” que contienen las traducciones, los definidos en “system\language\xxx” (siendo xxx normalmente “english”): about_lang.php, calendar_lang.php, date_lang.php, db_lang.php, etc.

estructura_archivos_idiomas_textos_sistema

  • Tras copiarlos editamos cada uno de ellos y realizamos las siguientes sustituciones:
    • $lang[ ? $lang(
    • ‘] ? ‘)

ejemplo_archivo_textos_sistema

Con estos cambios conseguimos que el poedit pueda detectar estos textos (habiendo establecido previamente en el catálogo correspondiente del poedit la etiqueta “$lang”), y por tanto podemos gestionar las traducciones de dichos textos desde el poedit y guardarlas en el archivo “.mo” que se genera por cada idioma. De esta forma, si una librería solicita un texto concreto, la clase “Lang” se lo podrá proporcionar ya que lo encontrará en el archivo “lang.mo” del idioma correspondiente.

¿Qué más cambios hay que realizar?

Además de los cambios ejecutados hasta ahora debemos modificar algunos archivos más:

  • application\config\config.php
    • En el apartado $config[‘language’] se debe indicar el código de un idioma existente, el idioma que establezcamos por defecto: $config[‘language’] = ‘eu_ES’;
    • NOTA: Hay que asegurarse de que todos los archivos de traducciones estén en el idioma que establezcamos en dicha variable.
  • application\config\routes.php
    • Debemos modificar las URL de acceso a los controladores para añadir en ellas el código de idioma, ya que nuestra clase “Lang” buscará el idioma en la url de la web. Por ejemplo:
      • $route[‘^es/login’] = “login/acceso”;
      • $route[‘^eu/sartu’] = “login/acceso”;
  • En los controladores de los páginas (application\controllers\xxx), se deben cargar dos helpers: ‘language’ y ‘url’
    • Una alternativa para evitar cargar en todos los controladores ambos helpers, es cargarlos directamente en el archivo “application\config\autoload.php“. Concretamente en la sección “Helpers”: $autoload[‘helper’] = array(‘language’,’url’);

Tras dichos cambios, para hacer referencia a cualquier texto basta con añadir el siguiente código: <?php echo lang(‘el_texto_que_se_desee’);?>

Esto hará que se imprima el texto correspondiente en la vista o clase donde metamos dicho código (gracias al helper “language” que contiene la función “lang”),  y por otro lado, permitirá que al abrir el catálogo del poedit asociado al proyecto, “el_texto_que_se_desee” sea cargado automáticamente en dicho catálogo para poder asociarle allí su traducción.

Con estos cambios ya estamos listos para utilizar CodeIgniter con gettext.

Como apunte final, debemos decir que esta solución que os hemos presentado es aplicable a webs de compañías en pleno proceso de internacionalización, que requieren versiones webs en idiomas no latinos como el chino, árabe o ruso.

8 respuestas

  1. bernadette
    Jul 09, 2014 - 03:28 PM

    Si usted está interesado en crear sitios web multi-idioma con gettext, yo recomiendo altamente esta rápida y intuitiva herramienta en línea para la localización de software: https://poeditor.com/

  2. cristian
    Sep 10, 2015 - 12:40 AM

    buenas.
    broo eh hecho el procedimiento tal cual describes pero al momento de poner localhost, para que me cargue por defecto el login
    me carga por defecto http://localhost/index.php/eu/en , no se por que, me podrian ayudar porfa.

    • Ana
      Sep 10, 2015 - 07:03 AM

      Buenas Cristian. No sé a qué pude deberse que te cargue en la url los dos idiomas. Repasa el archivo Lang (“application\core\Lang.php”)) y vete debugeando a ver dónde está el problema.
      Quizá puede estar a nivel de servidor, que no está detectando bien los parámetros por url y por eso no encuentra el primer idioma y añade el siguiente por detras que seguramente sea el idioma que tienes definido por defecto. Comprueba cuando se llama a
      $segment = $URI->segment(1); –> si está devolviendo el idioma de la URL. Esto debería devolver el primer parámetro que tienes después del “index.php”. Si la url es “http://xxxx/index.php/es/login” debería devolverte “es”. Si esto no lo está haciendo debes echar un vistazo al servidor.

      Por otro lado, en el archivo “application\config\routes.php” puedes establecer como el controlador por defecto ($route[‘default_controller’]) la ruta asociada al login, que no sé cómo la tendrás definida. Por ejemplo, si tienes dos idiomas, es y eu y en routes tienes:
      $route[‘^es/login’] = “login/acceso”;
      $route[‘^eu/sartu’] = “login/acceso”;

      En el default controller podrías poner dicha ruta:

      $route[‘default_controller’] = = “login/acceso”;

      Así tanto si por url accedes a “http://xxxx/index.php/es/login” como si accedes a “http://xxxx/index.php/eu/login” o si no especificas nada y accedes como “”http://xxxx/index.php” (con o sin idioma) siempre debería mostrarte la página de login.

      • cristian
        Sep 10, 2015 - 09:06 PM

        tienes razon, $segment = $URI->segment(1); devuelve null.
        entonces es cosa de mi servidor ?
        uso xampp, que podria modificar para que me funcione .. ?
        de todas maneras seguire revisando el codigo.
        podrias dejarme tu mail para comunicarnos. porfavor. 🙂

        • cristian
          Sep 10, 2015 - 09:26 PM

          agrego a esto.. es asi como se obtiene el indice de de segment ?
          es asi $segment = $URI->segment(1); por que eh visto la clase $UR y no tiene “segment” si no “segments” y su indice se obtiene con “[1]” no con “(1)” .

          lo he puesto $URI->segments[1]; y me devuelve el valor que tu me dices, pero aun sigue sin funcionar , o yo me estoy equicando ?
          te comento tambien que estoy trabajando con codeinater 2.2.0 y version de php 5.1.6, espero me respondas gracias.

  3. Felipe
    Dic 15, 2015 - 04:46 PM

    Esto no funciona ni de coña, tenéis que ponerlo mas claro.

  4. Thom
    Ene 16, 2017 - 01:07 AM

    Instrucciones no explicitas, objetivo de aprendizaje no alcanzado.

Deja un comentario