Caution: You are browsing the legacy symfony 1.x part of this website.
Cover of the book Symfony 5: The Fast Track

Symfony 5: The Fast Track is the best book to learn modern Symfony development, from zero to production. +300 pages showcasing Symfony with Docker, APIs, queues & async tasks, Webpack, SPAs, etc.

calendario de symfony día veintitrés: Internacionalización

1.0
Language

Anteriormente en symfony

Ahora que ya has aprendido como transferir una aplicación symfony al host de producción, la aplicación de askeet puede correr en cualquier sitio. Pero que pasa si alguien decide usarla en un país de habla no inglesa, digamos Francia?

Siendo askeet un proyecto de código abierto esperamos que gente de todo el mundo lo use pronto. Esto no sólo significa que todos los archivos del proyecto se tienen que codificar en utf-8, la aplicación también tiene que proponer una interface y un contenido localizado.

Piensa acerca de las compañías multinacionales que se instalarán askeet en sus Intranets para manejar la base de conocimiento. Definitivamente requerirán que los usuarios puedan cambiar el lenguaje de la interfície o el contenido en vez de instalar un askeet por idioma... Afortunadamente, las decisiones hechas durante el día dieciocho de implementar universos, harán mucho más fácil nuestro trabajo, y además symfony tiene soporte nativo para las interfícies internacionalizadas.

Localización

Qué pasa si se llama a una dirección como:

    http://fr.askeet.com/      ...mostramos sólo preguntas en francés? Bien esto es muy fácil porque desde el día dieciocho, un URI como este se entiendo como un universo.

Contenido

Creando preguntas en un idioma universo lo etiquetará automáticamente con la etiqueta del idioma (aquí: 'fr'). Y, si navegas el universo 'fr', sólo verás las preguntas donde aparezca la etiqueta 'fr'.

Por lo tanto el filtro del universo ya se preocupa del contenido localizado. Esto ha sido un paso sencillo.

Apariencia

Los universos puede tener sus propias hojas de estilo. Esto significa que la apariencia de un askeet localizado puede ser fácilmente adaptada con el mismo mecanismo. Siguiente, por favor.

Funciones que dependen del idioma

El sistema de indexación construido durante el día veinte se basa en un algoritmo de división que es dependiente del idioma. En una versión localizada, se tiene que adaptar.

Por ahora no hay ninguna biblioteca de división para otros lenguajes que no sean el Inglés en PHP, pero que pasaría si hubiera alguno, o que pasaría si alguien decide portar uno de las bibliotecas de división de Perl a PHP?

Luego, en el método myTools::stemPhrase(), deberíamos llamar a el método factory en lugar de a un simple PorterStemmer (se deja como ejercicio por ahora)

Contenido de la base de datos

Imagina un sitio web internacional que propone una lista de hoteles alrededor del mundo. Cada hotel se muestra con un texto descriptivo de las habitaciones, el servicio y el horario. Hay miles de hoteles, por lo tanto el contenido se debe de guardar en una base de datos. El problema es que debe haber tantas versiones de las descripciones como traducciones del sitio.

Symfony provee una forma de estructurar los datos para manejar estas situaciones. Con el ejemplo anterior, habría una clase Hotel para las tasas, dirección y el  contenido sin traducir,  y  una  clase  HotelI18n para el contenido que se deba traducir. Como los accessores de Propel abstraen esta separación, incluso si la descripción se encuentra en la tabla HotelI18n, todavía podrías acceder al contenido con un simple:

    [php]     $description = $hotel->getDescription();

Para entender mejor como funciona esto, mira el capítulo i18n del libro de symfony.

Afortunadamente, el sistema de filtrado de los universos de askeet reemplazan la necesidad de adaptar el contenido, por lo tanto no lo usaremos aquí.

Internacionalización

Como es una palabra larga, los desarrolladores, a menudo, se refieren a internacionalización como 'i18n'. Para aquellos que no sepan porqué, sólo tenéis que contar las letras de la palabra 'internationalitzation', y también entenderéis porqué 'localización' ('localitzation') es referido como 'l10n'. En el desarrollo de aplicaciones web, i18n mayormente concierne la traducción de los contenidos del texto y el uso de formatos locales para la interfície.

Establecer la cultura

Muchas de las características de la i18n dentro de symfony se basan en el parámetro de sesión de usuario llamado la cultura. La cultura es la combinación del país y del idioma del usuario, y determina como el texto y la información dependiente de la cultura se mostrará.

Cuando la aplicación askeet reconoce un universo como una localización, tiene que establecer la cultura correspondiente. ¿Cuándo una etiqueta permanente debe de ser reconocida cómo localización? Escogemos permitir sólo aquellas para las que la interfície está traducida (ver más abajo), por lo tanto el hecho de que un universo es una localización es determinado por la existencia de un fichero XML de traducción en el directorio i18n/ del proyecto.

Los universos se encuentran en el filtro askeet/apps/frontend/lib/myTagFilter.class.php, por lo tanto sólo tenemos que modificar un poco:

    [php]     public function execute ($filterChain)     {       ...       // is there a tag in the hostname?       $request  = $this->getContext()->getRequest();       $hostname = $request->getHost();       if (!preg_match($this->getParameter('host_exclude_regex'), $hostname) && $pos = strpos($hostname, '.'))       {         $tag = Tag::normalize(substr($hostname, 0, $pos));             // add a permanent tag constant         sfConfig::set('app_permanent_tag', $tag);             // add a custom stylesheet         $request->setAttribute('app/tag_filter', $tag, 'helper/asset/auto/stylesheet');               // is the tag a culture?         if (is_readable(sfConfig::get('sf_app_i18n_dir').'/global/messages.'.strtolower($tag).'.xml'))         {           $this->getContext()->getUser()->setCulture(strtolower($tag));         }         else         {           $this->getContext()->getUser()->setCulture('en');         }       }       ...     }

note

Las etiquetas de idioma que serán reconocidas se tienen que codificar en dos caracteres en minúscula, como se describe en la norma ISO 639-1 (por ejemplo fr para el francés). Cuando se trabaja con la internacionalización, siempre es preferible códigos ISO para los países e idiomas, para que así tu código cumpla con los estándares internacionales y pueda ser comprendido por desarrolladores externos.

Encontrarás más información acerca de la internacionalización y las culturas en el capítulo de i18n del libro de symfony.

Fechas, horas, números, moneda y mediciones

La manera de mostrar la fecha en Francia no es la misma que en los Estados Unidos. Lo que un norte-americano escribiría como:

    Diciembre 16, 2005 9:26 PM

... es escrito por un francés

    16 décembre 2005 21:26

Si recuerdas bien, cada vez que se tiene que mostrar una fecha en un template de askeet, usamos el helper format_date(). Este helper formatea la fecha pasada como parámetro según la cultura del usuario. Como la cultura se establece en el filtro myTagFilter.class.php, el formato de fecha se hará automáticamente.

date formatting in French askeet

Esta es otra buena práctica para los proyectos internacionales: siempre usar los helpers i18n cuando se tiene que mostrar una fecha, una hora, un número, un importe o una medición. Symfony provee helpers para la mayoría de ellos (mira el capítulo de helpers de i18n del libro de symfony para más información).

Traducción de la interfície

La interfície del proyecto askeet contiene texto. En una versión localizada, el texto de la interfície se debería de mostrar en el idioma de la cultura del usuario.

Para permitir la traducción de la interfície, todos lo textos de los templates de askeet, se tienen que codificar con un helper de i18n especial, __(). Además, el helper se debe de declara al principio del template. Por ejemplo, para permitir la traducción de la página principal, abre el template askeet/apps/frontend/modules/question/templates/listSuccess.php y cámbialo a

    [php]             

         $question_pager)) ?>

note

En lugar de tener que añadir el helper i18n en el principio de cada template, lo puedes añadir una sola vez para toda la aplicación en settings.yml, askeet/apps/frontendt/config/:

     all:        .settings:               standard_helpers:       Partial,Cache,Form,I18N

Para cada idioma en el que la interfície es traducida, se tiene que crear un fichero llamado messages.xx.xml en el directorio askeet/apps/frontend/i18n/, donde xx es el idioma de la traducción. Este fichero XML es un diccionario XLIFF que muestra la versión del texto traducida desde el idioma original (Inglés para askeet).

Por ejemplo, para permitir la traducción al francés, debes crear un fichero messages.fr.xml con el siguiente contenido:

    [xml]                                               popular questions             questions populaires                                          

La sintaxis del fichero XLIFF se explica con detalle en el capítulo de i18n del libro de symfony.

Ahora, la parte grande del trabajo es navegar por todos los templates (y los template fragments) para encontrar el texto a traducir. Cada vez que encuentres una frase, tienes que ponerla dentro de <?php echo __(' y ') ?>, y crear una nueva etiqueta <trans-unit> en el fichero messages.fr.xml. Afortunadamente, todos los templates de los proyectos symfony se encuentran en el directorio templates/, por lo tanto no necesitas mirar todos los archivos de tu proyecto.

note

Una traducción sólo tiene sentido si los ficheros de traducción contienen frases enteras. De todas formas, como a veces tienes que formatear variables en un texto, >puedes añadir un segundo argumento al helper __() para hacer la sustitución. Por ejemplo para traducir el siguiente template:

     [php]      There are persons logged.

... llama sólo una vez a __() para así evitar partir la frase en dos partes que no se pueden entender por sí solas:

     [php]      count_logged())) ?>

Por último, para permitir la traducción automática, tienes que establecer el parámetro i18n a on en los settings.yml de la aplicación:

    all:       .settings:              i18n:                on

Ahora puedes navegar hasta fr.askeet.com y ver la interficie traducida:

askeet in French

Traducciones automáticas

Existen algunas herramientas para automatizar la tarea de agrupar el texto y crear los ficheros messages.xx.xml. Desafortunadamente, ninguna lo hará tan bien como tú lo harías. Sólo tú puedes determinar dónde empezar y dónde terminar la llamada a __(). Aunque no las usemos, te ofrecemos un link a los sitios web dónde encontrarás recursos acerca de las herramientas de traducción automática:

  • El comando gettext de las  herramientas GNU getText provee una forma de extraer el texto del código PHP. Genera un fichero .pot (lista de términos traducidos a otro lenguaje).
  • El comando po2xliff de las herramientas XLIFF transforma los ficheros .po en ficheros XLIFF messages.xx.xml.
  • Para los usuarios de windows, el framework Okapi puede ser una buena alternativa.
  • Para editar los ficheros de traducción, poedit propone una interficie intuitiva (que es especialmente útil ya que la mayoría de traductores -humanos- no entienden ni los ficheros XML ni los .po).

No olvides

Una vez el texto de los templates está marcado para la traducción, todavía queda una inspección del código por hacer. De hecho, los mensajes de texto se pueden esconder en partes que no te esperas de tu aplicación. Asegúrate de hacer un inventario para encontrar el siguiente texto "escondido":

  • Carpetas de imágenes (las imágenes pueden incluir texto)

  Si necesitas localizar las imágenes, ponlas en el subdirectorio correspondiente a su cultura y añade la cultura cuando se llama al helper image_tag():             [php]            getCulture().'/myimage.png') ?>

  • Las alternativas de texto para las imágenes, las etiquetas de los botones y todo los mensajes de texto que son parámetros de las instrucciones <?php y ?>.

  • Los mensajes javascript se pueden poner en los helpers (como en link_to('clic', '@rule', 'confirm=¿Estás seguro?')), en los tags javascript de tus templates o en archivos.js` incluidos.

Después de todo, si no diseñas la aplicación con la i18n en mente desde el principio, hay un alto riesgo de que te olvides algún texto sin traducir en alguna parte. Nuestro mejor consejo es que pienses en la i18n antes de empezar a desarrollar, y si sabes que tu aplicación probablemente será traducida, recuérdate de usar __('') cada vez que escribes texto que se mostrará al usuario final.

note

Hay algunos mensajes de texto ocultos en los directorios validate/ de tus módulos que aparecen cuando un formulario no se valida correctamente. Lo realmente genial es que no necesitas hacer un tratamiento especial para esos texto si ya aparecen en las traducciones XLIFF. Symfony encontrará automáticamente la traducción en un nodo <trans-unit> y la usará en lugar del texto original de los ficheros YAML.

error messages

Nos vemos mañana

Askeet está haciendo este camino para así ser una aplicación de código abierto realmente usable. Siendo una aplicación i18n compatible estará disponible para los usuarios que no hablan inglés (aproximadamente un 90% de la población mundial).

El código modificado de la aplicación, incluyendo i18n, está disponible en el repositorio SVN y pude ser navegado directamente desde el trac de askeet. Tus comentarios en el foro serán bienvenidos.

Mañana ya es el último día de las series del calendario de symfony. No te lo pierdas.