Internationalization & Localization with Sencha Ext JS

Ext JS offers internationalization features out of the box, and the framework is easily extensible to cover additional requirements with a small amount of effort. Ext JS comes bundled with a localization package that supports over 40 languages ranging from Indonesian to Macedonian, and it’s easy to implement.

In this article, we’ll review solutions for handling some special circumstances and requirements that we’ve offered to our customers at Jnesis, but let’s first sum up what we know about internationalization.

The main focus is giving users the option to choose their preferred language for the application. This feature requires the application to be “translatable”, a feature that is often expressed with the words “i18n” or “internationalization.” “Translatable” meaning not only the possibility of translating into another language, but also considering all of the technical requirements attached to it.

Sencha provides a good internationalization and localization solution for the most common situations. It can also be extended, taking into consideration specific requirements of an enterprise environment.

How to Localize Ext JS Applications

You will find details on the Sencha recommended way to localize your applications in the Sencha localization documentation. There are also some additional recommendations in community member Saki’s blog post. In the following post, we will address how to:

  • Write language files (singleton or override) directly in the application, so they can be integrated afterwards into the production build
  • Generate, via Sencha Cmd, as many production builds of the same application as there are languages set in the configuration
  • Reload the entire application each time the language is changed
  • Easily load additional resources before starting the application
  • Use different languages in different parts of the application

Externalization of Sources

Enterprise developers view internationalization differently than the general developer because of their unique needs. Before choosing to adopt a framework and extend it, they must carefully assess their users’ actual needs. (Sencha Day 2015 presentation by Vincent Munier, Technical Manager at Jnesis.)

Companies of a certain size generally come to the same conclusion: multilingual management is a concern that spans beyond the development framework. Multilingualism is a general concern for large organizations, because their business processes require different languages at different stages of product development and company evolution. In some cases, where language requirements are changing regularly, translations should be done outside the application, mostly managed by third party software and loaded into the application via one or several web services.

The advantages of this approach are:

  • Changing the language doesn’t require the developer to rebuild the application – the change is applied instantly after reloading.
  • Translation doesn’t necessarily have to be done by the same people who write the code.

It could be argued that entrusting translation to people who do not know the code or how a JavaScript file is coded could pose some problems. However, it is perfectly conceivable to use a translation tier tool to translate content, one that can be used by non-programmers, and to load the data into the application.

As mentioned above, the official Ext JS solution (which can be found here: online localization guide) doesn’t allow any tier tools to be natively implemented.

It is important to introduce good practices for building internationalization into applications at the beginning of a project to avoid a painful refactoring phase.

The Elegant way

Let’s first follow Sencha localization guide and insights from Saki’s blog post: loading language data before the application main screen is built.

This solution is close to what was possible before Ext JS 5: adding dependencies to specific resources in the application’s “index.html” file.

To achieve that, let’s first store the localization values as variables of a “singleton” class, like that:

This will allow them to be accessed from anywhere in the application. How ? As simply as typing Jnesis.Labels.button or Jnesis.Labels.title

The advantage of this approach compared to the use of “override” (i.e what is recommended by Sencha) is that localized values will all be in one file, and easier to use, because each element is a variable, as explained above.

Once that is done, we simply have to override the default language file (« Jnesis.Labels ») with the translations in the selected language.

There are two options:

  • Loading the data via web-services:

Where the server JSON response would look like this:

You then would have to parse data and override your default localization singleton with those values:

Ext.define('Jnesis.Application', {
    launch: function () {
        Ext.Ajax.request({
            url: 'get-localization',
            params: {locale:'fr'},
            callback: function (options, success, response) {
                var data = Ext.decode(response.responseText, true);
                Ext.override(Jnesis.Labels, data);
                Ext.create('Jnesis.view.main.Main');
            }
        });
    }
}); 
  • Loading overriding Ext JS classes:
Ext.define('Jnesis.locale.fr.Labels', {
    override: 'Jnesis.Labels',
    button: 'Mon bouton',
    title: 'Mon titre'
}); 

Then using Ext.Loader.loadScript function to process it and loading your Main view when it’s done.

Ext.define('Jnesis.Application', {
    launch: function () {
        var lang = 'fr',
            url = 'resources/locale/'+lang+'/Labels.js';
        Ext.Loader.loadScript({
            url: url,
            onLoad: function (options) {
                Ext.create('Jnesis.view.main.Main');
            }
        });
    }
});     

We would then want to use the native inheritance mechanism to our advantage by defining a new property (“localized”, for example) in the base component, and generate the attribute to be translated. This attribute would be a simple JavaScript object with keys and values (remember the Jnesis.Labels.button and Jnesis.Labels.title from above).

Values can be updated only when the object is read, by defining the keys as strings:

Ext.define('Jnesis.view.main.Main', {
    extend: 'Ext.panel.Panel',
    initComponent: function () {
        this.title = Jnesis.Labels.title;
        this.buttons = [{
            text: Jnesis.Labels.button,
            handler: 'onClickButton'
        }];
        this.callParent(arguments);
    }
}); 

Once that is done, you just have to override the “initComponent” method of the base class and evaluate the keys, so they can be used in any Ext JS component:

Ext.define('overrides.localized.Component', {
    overrride: 'Ext.Component',
    initComponent: function () {
        var me = this,
            localized = me.localized,
            value;
        if (Ext.isObject(Localized)) {
            for (var prop in localized) {
                value = localized[prop];
                if (value) {
                    me[prop] = eval(value);
                }
            }
        }
        me.callParent(arguments);
    }
}); 

This solution works at any hierarchical level in the component’s declaration (container configuration or item configuration) in a transparent way.

To go further

The latter solution works with both Ext JS 5 and 6 (Classic toolkit). You will find below examples of possible adaptations made by Jnesis in the past:

  • Deporting the loading functions and their associated processing in a specific class like “Ext.mixin.Mashup”. It ensures that every necessary file or JSON data from web-services are loaded before the class to which the “mixin” is applied is stored in memory.
  • Ensuring compatibility with the Ext JS 6 Modern Toolkit. It has no “initComponent” function. Another option can be found in the “config” property and in the generation of the associated “apply” method.
  • Loading and interpreting the translation variables in a dynamic way without having to reload the entire application.

Please leave comments below, so we can share more information on these improvements.

The source code is available on our github here

One Response

Leave a Reply

Your email address will not be published. Required fields are marked *