The Legend
Our problem, the problem of translating and rendering our applications in different languages is not new, contrarily, it started about 4-5 thousand years ago. People then spoke just one language so anybody understood anybody from all corners of world and all walks of life. But, people wanted to equate with God and started to build the Tower of Babel that would top in the Heaven. God, seeing that, came down and confused their languages. Not understanding each other, they stopped building and scattered all over the world.
And now we need to translate our applications.
Background
From the user viewpoint, the localization is very easy: He just selects his preferred language at the operating system install time or changes it in settings afterward. However, it was not always so. Computers were invented and their production started in United States so they spoke and understood only English. In 80-ties, if you didn’t understand English you were entirely lost in the world of computers. Computers’ origin and spread over the whole world may be the reason of popularity of English language mainly among the youngsters.
The problem was that Americans did not need translations because you can travel 3,000 miles in US and still speak one language. If you travel 300 miles in Europe you may need 5 languages to speak to locals. Not needing localization, the first computers and software did not include any infrastructure for implementing translations.
The situation improved over time, we cannot imagine that our iPhone or OS would not speak our native language, yet we cannot call it ideal especially when talking about ExtJS. It is not that localization of Ext applications is impossible, there are even locale files packed with the library. It is that the current official localization system of Ext is awkward, lengthy and lacking features.
The Ext official way
Imagine you have an application that defines a panel with a title:
Ext.define('MyApp.view.MyPanel', { extend:'Ext.panel.Panel ,title:'Hello World!' });
How could we translate the title into a different language?
Now imagine you have a locale file with translations:
Ext.define('MyApp.locale.view.MyPanel', { override:'MyApp.view.Panel' ,title:'Hallo Welt!' // German translation });
What happens? When locale file is processed Ext sees that we are overriding a base class so ‘MyApp.view.Panel’ is loaded first (if it has not yet been loaded) and then its title is overridden with German translation. Subsequently
Ext.create('MyApp.view.MyPanel');
renders the panel with the German title.
We only need a thin layers of locale overrides between the application definition files and the actual run of the app that creates instances of the language-overridden classes. Nice, isn’t it? Well, not so much or not at all. Why?
Problems with locale overrides
1. Locale files are incorporated into production build
1. Locale files are incorporated into production build
At the time of Ext 3.x and older we had to manually include Ext and the application classes in index.html. It was usually a three steps sequence:
- include Ext
- include concatenated application classes
- include app.js that started the app in onReady
Therefore, it was really easy to add one extra include between 2. and 3.: concatenated localization overrides. I had one Ext 2.x application (it is still working) that used server-side gettext
system to generate the locale file on the fly and injected it just before the application start. It works more than satisfactory.
However now, Sencha Cmd packs everything into one file leaving the developer with no option to dynamically include anything before the app start. If you want a translation then you must make a different build. This is also true for themes so if you want 3 themes and 5 languages you need 15 different builds of the same app.
Yes, it can be automated, you can configure app.json or build.xml to do it for you and in some cases it may be everything what is needed. Nevertheless, it is not up to par with the elegance of the old override-to-localize way.
2. Changing the locale (or theme) means app restart
With this build-per-language system, there is no other way but to restart the application, reload the page, with a different locale sent to the server for different app.js build to be included in index.html.
Well, in many cases users just set their preferred language once and never change it so this might not be a great issue.
3. Translations can hardly be done by laymen
When I was developing the application I mention above, I gave gettext source files to (semi)professional translators that knew nothing about programming, javascript or Ext and you can bet what happened. I had to go through the files they turned in and I had to fix all nonmatching quotes. They just saw the quotes as the spoken language element, not as a text-enclosing marks for a programming language.
Now imagine what would happen if you gave them files full of Ext overrides. They may translate even “extend”, “override” and “title” property names.
4. There is no way of per-component translation
It is not possible to have the application in English, for example, but one window or panel in it in German. It is questionable if this behavior is necessary, but it might be needed sometimes. With one build including only one language, it is just not possible. (You could load the locale from the server on demand for that window, but it a different story.)
5. Translations are dependent on application names
What happens if you change ‘MyApp.view.MyPanel’ to ‘MyApp.view.YourPanel’? You need to walk through all locales to change the name in them too, otherwise the build would fail or, even worse, the build would succeed but a language wouldn’t work.
Advantage of override based localization
Enough problems! There is one advantage, and it is an important one: This localization system can be implemented on the top of existing application without changes in it. Of course, the application must have been written with a (future) localization in mind because overrides cannot go any further than class properties.
This is not translatable by overriding:
Ext.define('MyApp.view.MyPanel',{ displayDialog:function() { alert('Hello World!'); } });
This is:
Ext.define('MyApp.view.MyPanel',{ message:'Hello World!' ,displayDialog:function() { alert(this.message); } });
How to use build multiple locales
I do not use this official way myself, nevertheless, I know of a perfect localization howto on Sencha forums by Michal Charvát.
Other localization systems
One other class of localization systems would be variable or function system. To illustrate, consider the following chunks:
MyApp.currentLocale = 'de'; Ext.define('MyApp.Locales', { statics:{ title:{ en:'Hello World!' ,de:'Hallo Welt!' } } }); Ext.define('MyApp.view.MyPanel', { extend:'Ext.panel.Panel' ,title:MyApp.Locales.title[MyApp.currentLocale] });
THis very simple example shows how variable localization could work. The title of MyPanel is not a text but a variable that points to global “repository” of translations, the static class Locales
. I’m not going to speculate on how clumsy it is to type “MyApp.Locales.title[MyApp.currentLocale]” instead of “Hello World”, or about how Locales gets populated, how translations are made and how it is maintained. I leave these speculations to you if you want to use this system. (I wouldn’t.)
Another way is to define a global translation function
MyApp.currentLocale = 'de'; Ext.define('MyApp.store.Locales',{ extend:'Ext.data.Store' ,storeId:'locales' }); _ = function(textId) { var store = Ext.getStore('locales') ,rec = store.findRecord('textId', textId) ; return rec ? rec.get(MyApp.currentLocale) : text; }; Ext.define('MyApp.view.MyPanel',{ extend:'Ext.panel.Panel' ,title:_('title') });
I have opted for function name “_” (underscore) that is consistent with GNU gettext, however, you must be careful in real life and make sure that this function is not already defined by a library other than Ext. The example illustrates how the function could look if we kept the translations in a store.
Both methods have their advantages and disadvantages, however, the main drawback is that they need to write the application a specific way. It may be fine when starting from the scratch but image you need to replace each string in your 1,000 files application with _(“that string”). Then, you have to maintain text ids – this can be alleviated by considering English text as a text id.
You may now think “There must be something better” and you are right. Let me explain how would an ideal localization system look.
An Ideal Localization System
Here I summarize features of a hypothetical localization system so that it could be called ideal.
- No changes of the original application code are necessary. Really none:
title:'Hello World!'
stays as it is, in English. - Texts are completely separate from the code. Translators are not fed with the language spoiled by programmatic constructs, not even quotes. Ideally, translators can work over internet in an editable grid immediately seeing translated texts in the application.
- No application restarts are necessary on locale change, no separate builds are required and the locale change is immediate.
- Localization system is completely separate and independent of the application, it can be added or removed at any time and changes in the application have no impact on the system. (New or changed texts must be (re)translated, of course.)
- There is a possibility to use many languages in the application at the same time.
- It must be easy to implement. Setting aside translations themselves that could take time depending on the size of the application, implementation of the system itself must fast and easy.
Now I could conclude with a call to Sencha developers: “Please invent and implement such system!” but I won’t. I’ve already coded it, however, it is not yet in the production quality. I would rather call it a concept proof. Let me know if you want to see it, test it and improve it. Then may come a day when it will be incorporated in the core library.
- Ext, Angular, React, and Vue - 27. June 2019
- The Site Resurgence - 11. February 2018
- Configuring ViewModel Hierarchy - 19. June 2015