Contenido

l10n.js, localización con Javascript

13 may

+ 14

Uno de los problemas con los que nos encontramos al desarrollar una aplicación rica en Javascript es la falta de idiomatización desde el cliente. Generalmente se suele usar un sistema de variables que cargamos desde un lenguaje de servidor y que al llegar al usuario las frases ya están en el idioma seleccionado.

Hoy via Ajaxian, descubro l10n.js, una librería javascript que permite realizar esta idiomatización desde el cliente, usando una estructura JSON donde alojas las frases en los diferentes idiomas.


{
	"en": {
		"l10n.js demo": "English - l10n.js demo",
		"You are viewing the original page locale.": "You are viewing an English localization of this page."
	},
	"pt": {
		"en": "pt",
		"l10n.js demo": "Português - Demo do l10n.js",
		"You are viewing the original page locale.": "Vocé esta a ver uma localização Portuguesa desta página."
	},
	"es": {
		"en": "es",
		"l10n.js demo": "Español - l10n.js demo",
		"You are viewing the original page locale.": "Mira ustéd una versión español de esta pagina."
	},
	"fr": {
		"en": "fr",
		"l10n.js demo": "Français - l10n.js démo",
		"You are viewing the original page locale.": "Vous sont voyez une localisation français de cette page."
	}

}

Como podemos ver, se trata de una estructura sencilla que aprovecha la potencia de JSON (clave-valor) para identificar las cadenas de texto que deseamos mostrar al usuario.

El fichero, lo cargaremos como un fichero externo usando el elemento <link /> en nuestro <head />


<link rel="localizations" href="localizations.json" type="application/x-l10n+json" />

Con el diccionario cargado, solo tendremos que cargar la libraría javascript y empezar a editar nuestro código para que las cadenas de texto aparezcan en el idioma deseado.

<script type="text/javascript" src="path/to/l10n.js"></script>

Automáticamente la librería nos extiende nuestros objectos String para que dispongamos del método toLocaleString() que nos permite cargar la cadena en el idioma del usuario.  Tenemos una API más completa donde ver las opciones disponibles.

  • Gracias Andres, buena librería.

  • No me parece muy eficiente el utilizar los literales originales como clave. Estamos repitiendo texto innecesariamente y eso, con una gran cantidad de datos, no es una buena idea.

    ¿Por que no aprovechar la posibilidad que nos da JSON de crear estructuras asociativas más complejas?

    • @fselich: ¿Por que dices que no es eficiente? JSON se basa en clave-valor, ¿como lo podrías optimizar con estructuras más complejas?

      La ventaja es que en caso de no existir la clave en la estructura JSON y esta no ser un “texto_boton” es que nos mostrará un texto (en otro idioma, pero un texto :D)

  • @aNieto2k:
    (se me ha borrado el comentario, lo intento de nuevo ;))

    En el JSON que aparece como ejemplo estamos repitiendo, por ejemplo, la frase: “You are viewing the original page locale.” una vez por cada idioma. Además de otra vez en el string al que se le aplica toLocaleString().

    Yo haría algo así, sin pensarlo mucho (y sin usar nada complejo) :

    {
    1: {'en':'hello','es':'hola'},
    2: {'en':'bye','es':'adios'}
    }

    Claro, es más cómodo utilizar texto.toLocaleString() que idTraduccion.toLocaleString(). Pero utilizar un texto como clave de un array puede traer montón de problemas (mayúsculas != minúsculas, espacios, etc).

    • @fselich: Estoy de acuerdo, puede generar muchos problemas. Aunque este ejemplo que propones no me gustaría encontrarmelo con 3000 etiquetas y 5-6 idiomas (que es lo que estamos usando en el curro).

      El problema que le veo es que se pierde visibilidad, pero es cierto que la otra tampoco es una solución muy práctica. A ver si la gente se anima y sale una buena propuesta :D

    • @aNieto2k: En mi caso, similar al tuyo (miles de etiquetas y los 5 ó 6 idiomas más comunes), lo que hacemos es devolver un javascript que ha pasado antes por un parser php que se encarga de sustituir unas etiquetas propias de traducción por el texto en el idioma del cliente. Tampoco es la panacea, pero se gana en rapidez al estar los textos en una BD real y tratar todo (html y javascript) de la misma forma.

      Problemas, los propios que te encontrarías con un fichero javascript dinámico: más difícil de cachear y más complicado de “debugear”.

      Tampoco es la solución perfecta, es cierto. A ver si sale alguna buena :)

    • @fselich: Si, así es más o menos como lo hacemos. Solo que nosotros usamos Java. Además, las tenemos codificadas por página así que solo cargamos las relacionadas con una página (obviamente hay etiquetas repetidas :S).

      Como dices no es la ostia, pero funciona :D

  • Muy bueno pero como se haria con php, la logica es la misma?

  • Estoy con @fselich en que no es eficiente, aunque no me gusta la idea de usar índices cuando puedes usar directamente la cadena en cuestión.

    Mi conclusión (extraida ya hace un par de años), es usar las propias frases como índices de un hash con el nombre del idioma y la traducción. Para ello, lo único que hace falta, para hacer esto más eficiente, es definir un idioma base.

    Por ejemplo:

    
    var g_currentLanguage = 'es';
    var g_translationMap = {
      // esto simplemente indicaría que las claves del mapa son en inglés
       '__language_base':  'en',  
    
       "English - l10n.js demo" : {
          'pt' :  "Português - Demo do l10n.js",
          'es' : "Español - l10n.js demo",
          'fr' : "Français - l10n.js démo"
       },
    
       "You are viewing the original page locale.": {
           'pt' : "Vocé esta a ver uma localização Portuguesa desta página.",
           'es' : "Mire usted, una versión español de esta pagina.",
           'fr': "Vous sont voyez une localisation français de cette page."
        },
        /// ...
        'text_identifier_1' : {
           'en' : 'This is a sentence in English',
           'es' : 'Esta es una frase en Español'
         }
    }
    

    De esta manera, buscas la frase en tu mapa, y si no existe, o no tienes definida la frase para el idioma X, devuelves la cadena tal cual (ojo que también podrías definir un segundo idioma y usar éste como fallback).

    Este esquema también te permite redefinir la frase en inglés dentro del propio mapa, cosa que parece estúpida, pero resulta útil si por ejemplo, en vez de poner la frase tal cual, decides usar identificadores como claves del mapa.

    Con este esquema lo tienes todo metido en el mismo mapa y no repites las frases. Vas a ahorrar varios KB si hablamos de cientos de traducciones y 5 o 6 idiomas.

    La función de traducción, si no me equivoco, se podría escribir como:

    
    function translate (str) {
       if (str in g_translationMap) {
          if (g_currentLanguage in g_translationMap [str]) {
             return  g_translationMap [str][g_currentLanguage];
          }
       }
       // not found!
       return str;
    }
    

    Por supuesto, esto es en plan cutre, con variables globales, pero es para mostrar el concepto. A la hora de programarlo se puede hacer bonito ;)

    En cualquier caso, si son tantas las frases, prefiero la opción de que el javascript lo escupa un PHP, así se optimiza más y no pones traducciones que no hacen falta. Además se pueden cachear fácil (usando el idioma como distintivo).

    En fin, seguro que hay mil soluciones.

    Saludos!

    • @Pau Sanchez: Buena solución. Pero el usar índices numéricos también nos sirve para evitar redundancias en el texto a traducir. También nos ayuda, en el caso de que sea necesario, a separar contenido de funcionalidad. Por último nos permite la modificación directa de textos, evitando tener que tocar en dos sitios cada vez que queramos cambiar un texto o corregir un error.

      Se puede hacer una solución mixta, haciendo que la función de traducción compruebe si el parámetro es numérico (buscará por índice) o si es alfanumérico (buscará dentro de los valores).

      De todas maneras, estoy contigo. Es preferible la traducción del lado del servidor.

  • @fselich: Estoy de acuerdo en el problema que hay, a la hora de modificar textos. Si no haces un sistema automatizado que te ayude, entonces lo tienes que modificar en 2 sitios, en el código, y en el fichero de traducción. 100% de acuerdo. Es el único “fallo” de este sistema, sin embargo, justo por el mismo motivo tiene la ventaja, de que si se te olvida añadir traducciones de un texto, no pasa nada, el usuario va a seguir viendo un texto legible en el idioma base que hayas escogido. Graceful degradation.

    En cuanto a la redundancia, puedo entenderlo, sin embargo, ganas mucho en legibilidad.

    Hace un par de años empecé con una solución donde usaba identificadores (similar a lo de índices numéricos). Al final, era inmantenible. También depende de si eres 1 persona o un grupo. Yo sinceramente, prefiero ver en el código una frase, que ver un identificador o un índice, porque a la larga no voy a ir al código y voy a decir, ostia, un 873, ¿cual era la frase 873?

    Lógicamente, por eso he llegado a la solución que he propuesto, después de darme de ostias con otras soluciones ;)

  • Hay otra ventaja a la hora de usar identificativos, y es la posibilidad de usar frases aparentemente iguales pero que pueden ser diferentes en determinado idioma según el contexto en el que aparezcan. Un ejemplo irreal: quizás necesitemos  que “adiós” se traduzca en ocasiones por “bye” y en otras como “good bye”, pero que en español siga poniendo “adiós”.

    Con respecto al código legible, es cierto. Con identiicativos pierdes legibilidad. Con literales ese problema no existe.

  • Sí hay un poco más de una repetición.

Comentar

#

Me reservo el derecho de eliminar y/o modificar los comentarios que contengan lenguaje inapropiado, spam u otras conductas no apropiadas en una comunidad civilizada. Si tu comentario no aparece, puede ser que akismet lo haya capturado, cada día lo reviso y lo coloco en su lugar. Siento las molestias.