Server-side internationalization for backbones and hand panels

I am working on a Grails / Backbone / Handlebars application, which is the interface for a much older Java system, in which (for historical and custom reasons) internationalization messages deep in the database, hidden behind several SOAP services, are in turn hidden behind various internal Java libraries. Receiving these messages from the Grails level is easy and great.

I am wondering how to get (for example) internationalized shortcuts in Handlebars templates.

I am currently using GSP snippets to create templates, including a special tag that receives a message that interests me, for example:

<li><myTags:message msgKey="title"/> {{title}}</li> 

However, for performance and code reasons, I want to get away from GSP templates and get them in direct HTML. I looked a bit at client-side internationalization options such as i18n.js , but they seem to depend on the existence of the message file I did not receive. (I could create it, perhaps, but it would be ginormous and expensive.)

So far, the best thing I can think of is to wedge the shortcuts into the Backbone model, so that I get something like

 <li>{{titleLabel}} {{title}}</li> 

However, this does away with the ideal of building Backbone models on top of a clean, clean RESTful JSON API - either the JSON returned by the RESTful service is clogged with presentation data (i.e. localized labels), or I have to do extra work to insert labels into the Backbone model, and also clutter The Backbone model with presentation data also seems to be incorrect.

I think what I would like to do in terms of clean data and clean APIs is to write another RESTful service that accepts a list of message keys and the like, and returns a JSON data structure containing all localized messages. However, questions remain:

  • What is the best way to indicate (possibly in a template) which message keys are needed for a given view?
  • What is the correct format for the data?
  • How to get localized messages in Backbone views?
  • Are there any existing Javascript libraries that will help, or should I just start creating files?
  • Is there a more standard alternative approach?
+7
source share
2 answers

We use http://i18next.com to internationalize the Backbone / Handlebars app. (And Require.js, which also downloads and compiles templates through a plugin.)

i18next can be configured to dynamically load resources. It supports JSON in gettext format (supporting multiple and contextual options). An example from his page on how to download remote resources:

 var option = { resGetPath: 'resources.json?lng=__lng__&ns=__ns__', dynamicLoad: true }; i18n.init(option); 

(Of course, you will need more configuration, for example, setting the language, backup language, etc.)

Then you can configure the Handlebars helper, which calls i18next on the provided variable (the simplest version, without plural, without context):

 // namespace: "translation" (default) Handlebars.registerHelper('_', function (i18n_key) { i18n_key = Handlebars.compile(i18n_key)(this); var result = i18n.t(i18n_key); if (!result) { console.log("ERROR : Handlebars-Helpers : no translation result for " + i18n_key); } return new Handlebars.SafeString(result); }); 

And in your template, you can either provide a dynamic variable that expands to a key:

 <li>{{_ titleLabeli18nKey}} {{title}}</li> 

or directly specify the key:

 <li>{{_ "page.fancy.title"}} {{title}}</li> 

For localization of datetime we use http://momentjs.com (conversion to local time, formatting, translation, etc.).

+1
source

I think you could create a rather elegant solution by combining Handelbars helpers and some regular expressions.

Here is what I would suggest:

  • Create a service that accepts an array of message keys in JSON and returns a JSON object, where the keys are message keys and the values ​​are localized texts.
  • Define a Handlebars helper that accepts the message key (which corresponds to the message keys on the server) and displays the translated text. Something like {{localize "messageKey"}} . Use this helper for all template localizations.
  • Write a template preprocessor that smooths the message keys from the template and sends a request for your service. The preprocessor caches all message keys that it receives and requests only those that it does not already have.
    • You can either call this preprocessor on demand when you need to display your templates, or call it in the foreground and cache message keys so that they are ready when you need them.
    • For further optimization, you can save the cache in the local storage of the browser.

Here is a little proof of concept . It does not yet support local preservation or support for extracting texts of several templates at once for caching purposes, but it was easy enough to crack them, which I think could work well with further work.

The client API might look something like this:

 var localizer = new HandlebarsLocalizer(); //compile a template var html = $("#tmpl").html(); localizer.compile(html).done(function(template) { //..template is now localized and ready to use }); 

Here is the source for the lazy reader:

 var HandlebarsLocalizer = function() { var _templateCache = {}; var _localizationCache = {}; //fetches texts, adds them to cache, resolves deferred with template var _fetch = function(keys, template, deferred) { $.ajax({ type:'POST', dataType:'json', url: '/echo/json', data: JSON.stringify({ keys: keys }), success: function(response) { //handle response here, this is just dummy _.each(keys, function(key) { _localizationCache[key] = "(" + key + ") localized by server"; }); console.log(_localizationCache); deferred.resolve(template); }, error: function() { deferred.reject(); } }); }; //precompiles html into a Handlebars template function and fetches all required //localization keys. Returns a promise of template. this.compile = function(html) { var cacheObject = _templateCache[html], deferred = new $.Deferred(); //cached -> return if(cacheObject && cacheObject.ready) { deferred.resolve(cacheObject.template); return deferred.promise(); } //grep all localization keys from template var regex = /{{\s*?localize\s*['"](.*)['"]\s*?}}/g, required = [], match; while((match = regex.exec(html))) { var key = match[1]; //if we don't have this key yet, we need to fetch it if(!_localizationCache[key]) { required.push(key); } } //not cached -> create if(!cacheObject) { cacheObject = { template:Handlebars.compile(html), ready: (required.length === 0) }; _templateCache[html] = cacheObject; } //we have all the localization texts -> if(cacheObject.ready) { deferred.resolve(cacheObject.template); } //we need some more texts -> else { deferred.done(function() { cacheObject.ready = true; }); _fetch(required, cacheObject.template, deferred); } return deferred.promise(); }; //translates given key this.localize = function(key) { return _localizationCache[key] || "TRANSLATION MISSING:"+key; }; //make localize function available to templates Handlebars.registerHelper('localize', this.localize); } 
+3
source

All Articles