Internationalization & Localization with Sencha Ext JS

Guest Blog Post

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

How to Localize Ext JS Applications

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. (See 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 can be found in its localization guide.

We will describe our approach which allows the tier tools to be implemented natively. 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 the 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.

Ext JS App Build

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

Ext.define('Jnesis.Labels', {
    singleton: true,
    button: 'My english button',
    title: 'My english title'
});

This will allow the translations 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 using “override” 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 translation in the selected language.

There are two options:

  1. Loading the data via web services, where the server JSON response would look like this:
  2. {
        "button": "Mon Bouton",
        "title": "Mon titre"
    }

    We then would have to parse data and override our 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');
                }
            });
        }
    });
  3. Loading overriding Ext JS classes:
  4. 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 our 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',
        localized: {
            title: 'Jnesis.Labels.title'
        },
        buttons: [{
            localized: {
                text: 'Jnesis.Labels.button'
            },
            handler: 'onClickButton'
        }]
    });

    Once that is done, we 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', {
        override: '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.

    Additional Resources

    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 storage of the class to which the “mixin” is applied.
    • 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 about these improvements. You can download the Jnesis extension from Sencha Market.

Written by

Alexandre is a software engineer at Jnesis, a Sencha partner located in France and Switzerland.

Léopold Trouillet

Léopold is a software engineer at Jnesis, a Sencha partner located in France and Switzerland.


Comments

  1. Piotr says

    Sencha should stop boasting about having over 40 localizations. Just inspecting couple of them:

    * Polish Translations – Updated by mmar 16-November-2007
    * Afrikaans Translations – by Thys Meintjes (20 July 2007)
    * Spanish/Latin American Translation – by halkon_polako 14-aug-2008
    * Korean Translations – Modify by techbug / 25 February 2008

    This are taken from ExtJS 6.0.2 translation files. Shame on you, Sencha.

    • Gautam says

      @Piotr The localization package in Ext JS is to help developers easily localize applications for any non-English languages. The samples included are intended to be used as a starting point on how to accomplish that goal. We’re considering revising the localizations that come as a part of the package.

  2. Hugo Brizard says

    Thanks for this post!!!

    Quick question though : I suppose you can’t bind titles anymore with this approach?

    • says

      Hi Hugo!

      Actually yes, you can bind the title, for example if you want to append a bound value to a localized title you could do it like that:

      {
      xtype: ‘panel’,
      bind: {
      title: Jnesis.Labels.title + ‘ {myTitle}’
      }
      }

      or you could use a formula :

      formulas: {
      myTitle: {
      bind: ‘{something’},
      get: function(something) {
      return Jnesis.Labels.title + ‘ ‘ + something;
      }
      }
      }

      Hope it helps, if not, feel free to email us!

Leave a Reply

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