Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Localizing templates using require.js, backbone and underscore

This question is about templating and localizing, using require.js and underscore templates through backbone.js. The application will need to be localised on the fly.

Before embarking down a path that later proved problematic, is there a better solution than the one I'm considering - I'm concerned about speed and memory with repeatedly merging and processing the language array. Assume that are a 2-3 thousand language strings.

Current approach (which works, but looks processor heavy):

  1. Using the I18N bundling approach, create language "includes" that will essentially contain the translated elements for all the templates
  2. Merge this object/array of elements with model attributes (from backbone) and pass the merged lot into the underscore template

.

define(['backbone', 'models/model', 'text!template.html', 'i18n!my/nls/translatedbits'],
  function(Backbone, MyModel, TemplateText, TranslationObject) {
  var View = Backbone.View.extend({
    model: {},

    initialize : function(params) {
      this.model = new MyModel();
    },

    render : function(callBack) {
      // Get the model attributes
      var templateParams = _.clone(this.model.attributes);
      // Bolt on the tranlsated elements (established from require.js I18N plugin)
      templateParams.t = TranslationObject;
      // Pass the lot ot the template
      var template = _.template(TemplateText, this.model.attributes);
      $(this.el).html( template );
      return this;
    }

  });
  return View;
  }
);

Then the template will read

<%= modelAttribute1 %> <%= t.translationString1 %>

Is there a better solution or a better templating engine? [Better for this purpose - mustache may have other advantages, but can it localize more easily, or can it cache localised results allowing model attributes to be passed in later?]

Note that languages may need to be changed "on the fly" - and that's another concern I have with I18N plugin. I may end up getting the transations by JSON request through a template model but this still requires a merge of objects, which is what I'm trying to avoid.

like image 329
Robbie Avatar asked Oct 10 '12 06:10

Robbie


2 Answers

Here's what I am currently doing (just open-sourced since it seems useful to others)

underi18n is a very minimal lib for doing i18n on templates and code.

It provides:

  • Simple conversion of gettext catalogs to json format.
  • Support for variable substitution in translation strings.

It does not deal with pluralization.

From the README:

Catalogs

under18n uses a simple JSON format for catalogs, following the standard gettext format. In the following example,

{
    'Developer': 'Προγραμματιστής',
    'Role ${role} does not exist in ${context}': 'Ο ρόλος ${role} δεν υπάρχει στο ${context}'
}

we have two translation strings, the second one with two variables, role and context. A simple python script is provided to help you convert standard .mo files to this JSON format.

Usage

Create a MessageFactory from a json i18n catalog:

var t = underi18n.MessageFactory(catalog);

You can now translate inline:

t('Developer') // returns "Προγραμματιστής"

t('Role ${role} does not exist in ${context}', {role: 'διαχειριστής', context: 'πρόγραμμα'})
// Returns "Ο ρόλος διαχειριστής δεν υπάρχει στο πρόγραμμα"

Templates

Typically variables in templates are indicated with some delimiter. In mustache for instance {{ var }} is used whereas <%= var %> is default for underscore. We use the same approach to indicate translatable strings. You can specify the delimiters for translatable strings as a RegExp, as well as the left/right delimiters used by your template language of choice in under18n.templateSettings. By default this is following underscore conventions:

templateSettings: {
    translate: /<%_([\s\S]+?)%>/g,
    i18nVarLeftDel: '<%=',
    i18nVarRightDel: '%>'
}

so, <%_ i18n %> are set to denote translatable strings and <%= var %> is used to denote variables inside a template.

You can translate a template by calling under18n.template, for example using underscore, you can do

var templ = _.template(under18n.template(myTemplate, t));

Example

Given the following catalogs, factories and template for english and greek and assuming an underscore template,

var test_en = {
        'files_label': 'Files',
        'num_files': 'There are ${num} files in this folder'
    },

    templ = '<h1><%= title %></h1>' +
            '<label><%_ files_label %></label>' +
            '<span><%_ num_files %></span>',

    t_en = underi18n.MessageFactory(test_en);
    t_el = underi18n.MessageFactory(test_el);

the template can by constructed by,

var toRender = _.template(underi18n.template(templ, t_en));
toRender({title: 'Summary', num: 3});

would yield

<h1>Summary</h1>
<label>Files</label>
<span>There are 3 files in this folder</span>

AMD loading

under18n will register as an anonymous module if you use requireJS.

I hope this solves your problem, let me know if not, I was planning to release it at some stage, but hey better now than never ;)

like image 140
ggozad Avatar answered Dec 16 '22 09:12

ggozad


For completeness, the solution that we came up that felt to most optimised was:

  1. When a template was requested from the server, a cookie determined the language and the correct template was delivered.

  2. Used PHP back end to pre-parse the templates; these were then stored in memcached in the correct language

  3. The language template, once requested, was then cached by the browser and internally in a backbone model so it could be rapidly re-used by JavaScript.

Reasons:

  • faster JS (far fewer regular expression replaces). We never benchmarked, but it is only logical when you remove the functions completely.
  • saved transferring a HUGE language file to the client
like image 20
Robbie Avatar answered Dec 16 '22 07:12

Robbie