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.
Table of Contents
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. (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.
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:
- Loading the data via web services, where the server JSON response would look like this:
- Loading overriding Ext JS classes:
- 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.
{ "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'); } }); } });
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:
Please leave comments below, so we can share more information about these improvements. You can download the Jnesis extension from Sencha Market.
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.
@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.
Thanks for this post!!!
Quick question though : I suppose you can’t bind titles anymore with this approach?
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!
Hi
is there a way to prepare the files to be translated without any source code in it?
(no Ext.define, no [ ], etc
Thanks
Hi Alexandre,
As I did not fully understand your question, I will send you an email so that it will be easier for us to communicate on your issue.
Regards
Can you please post an override for modern toolkit. Also, will be very interested to see how to implement it without refresh. I would I assume it would be done through ViewModel. However, using ViewModel how would you include this into base ViewModel and how would you use a reference so it would not copy over data.
Thank you for the great article.
Hi dev4life,
We do have an extension on github (https://github.com/jnesislabs/extjs-remote-i18n) that provides remote i18n that also works with the modern toolkit. Feel free to clone the code and see if it fulfills your needs.
You can actually implement i18n on your components without refresh without using ViewModel but just by listening the switching of languages and calling the right setters on the different properties that need to be translated.
The tricky part when you want to change the language without refreshing is the localization of native ExtJS components’ labels and formats like for the datepicker component. Indeed they need to be re-rendered to display their labels/format in the right language so it is not trivial at all to implement that kind on behaviour. Especially on a heavy application.
Hello,
I try to localize my app in Sencha Architect.
I create this file in MyApp/resource/locale/myapp-locale.js with this content :
Ext.define(‘Jnesis.Labels’, {
singleton: true,
button: ‘My english button’,
title: ‘My english title’
});
I add in the js section of MyApp/app.json :
{
“path”: “resource/locale/tso-locale.js”
},
Then, i put the title property of a a panel with : Jnesis.Labels.button
But Sencha architect add quotes around it, so in the Architect code there’s :
title: ‘Jnesis.Labels.button’
So, it’s the string Jnesis.Labels.button which appears.
If i edit the code manually and put title: Jnesis.Labels.button, it’s ok, but next time i open Architect, the quotes appear again.
Hi,
It seems that this is a limitation of Sencha Architect and to be honest, we are not really familiar with that tool.
Maybe it would be better for you to directly ask the Sencha team (via their forum or support portal) to figure out a way to pass variables inside String configurations.
Again, sorry not to be able to help at this point, let us know if there is anything else we can do for you.
Hi sc,
The trick is to use “Process Config” parameter in Sencha Architect.
Hope it helps
Stefan
I use the option number 1.
When i launch my app, i’ve the following error message :
TypeError: overrides is undefined in ext-all-rtl-debug.js:2074:21
Need to put quotes around Jnesis.Labels like this :
Ext.override(‘Jnesis.Labels’, data);
Some change to my previous post.
Doing this : Ext.override(‘Jnesis.Labels’, data);
doesn’t override anything.
Ext.override(this.Jnesis.Labels, data);
works perfectly.
And the class can be as simple as :
Ext.define(‘Localization’, {
singleton: true
});
All translations are coming from the json data from the remote server.
This way, you can share your translation between the client part (Extjs) and the server part (php or other code language).
Some mistake again.
Here is the good :
***********************************
Ext.override(this.Jnesis.Labels, data);
works perfectly.
And the class can be as simple as :
Ext.define(‘Jnesis.Labels’, {
singleton: true
});
All translations are coming from the json data from the remote server.
This way, you can share your translation between the client part (Extjs) and the server part (php or other code language).
Very nice solution, it solves our translation issue perfectly, but it only works for components, do you have an idea on how to apply this to data models ( eg. NodeInterface )
Hi Sem,
If you chose the “singleton” solution from our article, you can use the “calculate” function on a Model’s fields to get the localized value from the singleton like that:
Ext.define(‘MyApp.model.Mymodel’, {
extend: ‘Ext.data.Model’,
fields: [{
name: ‘localizedLabelKey’,
type: ‘string’
}, {
name: ‘label’,
type: ‘string’,
calculate: function(data) {
return MyApp.Labels[data.localizedLabelKey];
}
}]
});
Hope it helps you,
Léo
I have other elegant solution, that do not require complex overrides.
You can write localized strings as objects with defined toString() method.
For example:
// constructor
Ext,L10N = function (str) {
this.str = str;
this.otString = function () {
// localize string, for example search in preloaded dictionary
return messages[this.str] || str;
};
}
// in ExtJS class
{
xtype: ‘button’,
text: new Ext,L10N(‘Cancel’),
}
Localization dictionaries can be parsed from sources automatically using gettext and preloaded with application.
Respect one of the Additional Resources “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.”
How it should be implemented in a Extjs6 modern project, i have to override Component and modify config properties, someone can help me!!