Trabe ya no escribe aquí. Puedes encontrarnos en nuestra publicación en Medium: medium.com/trabe.

4Trabes Historias de una empresa en 100 metros cuadrados

El blog de Trabe Soluciones

Generación dinámica de librerías Javascript internacionalizadas con GetText

|

La internacionalización es uno de los requisitos del proyecto que estamos desarrollando actualmente en Trabe Soluciones con Ruby on Rails. Para cumplirlo estamos utilizando GetText y Ruby GetText para la internacionalización de cadenas y código propio para internacionalizar ActiveRecord (un enfoque habitual cuando uno se enfrenta a este problema).

La aplicación además, cuenta con una adminsitración que, por su interfaz, utiliza una cantidad considerable de Javascript. Dicho Javascript también debe estar internacionalizado. Lo primero que se nos puede pasar por la cabeza es separar las cadenas que requieran traducción a unos ficheros separados y que nuestro layout enlace uno u otro en función del idioma de la petición (o el idioma preferido del usuario). Si lo hacemos así, estamos desaprovechando todas las ventajas que nos aporta el uso de GetText, en especial las facilidades para gestionar la traducción de cadenas (utilizando herramientas como poEdit).

Lo que vamos a hacer es generar los Javascripts dinámicamente utilizando Ruby embebido, como un rhtml cualquiera, con lo que podremos incrustar nuestras llamadas a GetText con total impunidad. Resumiendo, que lo que queremos es utilizar algo como ésto en nuestro layout:

1
<%= javascript_include_tag "/dyn_javascripts/#{Locale.get}/application" %>

Para ello necesitamos un poco de código.

Creamos un controlador que responda a las peticiones y que simplemente delegue en la vista correspondiente. Estas vistas estarán en la carpeta app/views/dyn_javascripts y tendrán la extensión .djs (por ejemplo).

1
2
3
4
5
6
7
8
9
10
class DynJavascriptsController < ApplicationController

  layout false

  def method_missing(symbol, *args)
    if symbol.to_s =~ /(.*)\.js$/
      render :action => $1
    end
  end
end

Añadimos la ruta correspondiente al fichero de rutas (routes.rb). En ella pasamos el idioma como parametro porque nuestros filtros de GetText lo buscan en la URL, no por vicio.

1
map.connect 'dyn_javascripts/:language/:action.js', :controller => 'dyn_javascripts'

Ahora creamos un fichero que requeriremos al iniciar la aplicación (en el fichero environment.rb). Este fichero incluye una clase que procesa nuestras plantillas .djs y que debemos registrar con el métodod register_template_handler de ActionView::Base::. Además incluimos el código necesario para que la aplicación encuentre el controlador que hemos definido previamente (la última línea de código) de tal modo que se recargue con cada petición en modo desarrollo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'erb'

module DynJavascripts
  class View

    def initialize(action_view) #:nodoc:
      @action_view = action_view
    end

    def render(template, local_assigns={})
      @action_view.controller.headers["Content-Type"] = 'text/javascript'
      @action_view.instance_eval { ERB.new(template).result(binding) }
    end

  end

end

ActionView::Base::register_template_handler 'r_js', DynJavascripts::View

# Nuestro controladoe está situado en 
# lib/common/dyn_js/dyn_javascripts_controller.rb
Dependencies.load_paths << "#{RAILS_ROOT}/lib/common/dyn_js"

Hecho todo esto ya podemos escribir código simpático como el siguiente en nuestra carpeta app/view/dyn_javasctripts:

1
2
3
4
5
6
# Archivo app/views/dyn_javascripts/application.djs
function changeALink(whichLink) {
    var the_link = document.getElementById(whichLink);
    the_link.href =  "<%= url_for :controller => 'a_controller'  %>";
    the_link.innerHTML = "_('Some text')";
}

El último detalle es conseguir que Ruby-GetText extraiga las cadenas de nuestros ficheros .djs. Para ello, vamos a indicarle al parser de rhtml que “sabe” procesar nuestros ficheros djs. La tarea “Rake”: de actualización de los ficheros .po quedaría tal que así:

1
2
3
4
5
6
7
8
9
10
11
12
13
desc "Update pot/po files."
task :updatepo do
  require 'gettext/utils'

  # 
  # ¡¡¡ Abracadabra!!!
  # 
  GetText::ErbParser.instance_variable_set(
      '@config', :extnames => ['.rhtml', '.r_js'])

   GetText.update_pofiles("app_domain",
   Dir.glob("{app,lib,bin}/**/*.{rb,rhtml,rjs,r_js}"), "App 1.0.0"")
end

Sencillo, ¿no?

Lo sentimos, pero los comentarios están cerrados