Ever since I implemented Voice of America’s content managed web site (b. 1999, d. 2005) and had to support 44 different languages, I’ve had a rather strange interest in developing multilingual apps. I consider it to be ‘strange’ because no one could have predicted that the guy who once got a D+ in junior high French class would go on to accumulate a ton of experience in successful enterprise application localization. As you can tell from the title of this post, I’m not bitter about the grade that I received 30 years ago …not bitter…at…all. For as I told my teacher, “I already speak three languages: Basic, Pascal, and English. French seems rather superfluous.”
As an adult, I’ve repeatedly told my kids “You can learn any foreign language that you’d like, as long as it’s Java, C++, or Javascript. Just stay the hell away from COBOL – for it’s the modern day equivalent of Latin.” They stare at me blankly, quickly turning back to playing Minecraft.
But.. I digress.
Since my consulting practice is based in Washington, DC, I generally don’t get a lot of opportunity to use my i10N-fu…but when an opportunity comes around, I like to dig deep.
To support latin-based, left-to-right languages (English, Spanish, French, Italian, etc), there are four major concerns:
- All text prompts need to be translated into language
- Enough space needs to be allocated in order to accomodate variable string lengths in word spelling
- Text translations should be encapsulated into separate, easy to edit files so that translations can be easily outsourced to human beings (Google Translate just doesn’t cut it…yet)
- Dates and numbers need to be translated into the locale. The French, for example, swap their commas and periods which I’m fairly confident was a central cause of the European banking crisis.
Dynamically Assigning Text Prompts
As the code listing below illustrates, all on-screen prompts are implemented as properties of the View class. Prompts are assigned at runtime in the initialize() method of the view.
Ext.define('MyApp.view.Login', { extend: 'Ext.form.Panel', alias: 'widget.loginform', requires: [ 'Ext.field.Email', 'Ext.field.Password', 'Ext.Button'], txtEmailPrompt: 'Email:', txtPasswordPrompt: 'Password', btnSubmitPrompt: 'Submit', headerHtml: 'Heading Info', footerHtml: 'Footing Html', loginErrorMsg: 'Sorry, try again', config: { scrollable: null }, initialize: function() { this.setItems( [{ xtype: 'component', itemId: 'header', html: this.headerHtml }, { xtype: 'emailfield', name: 'email', label: this.txtEmailPrompt, labelWidth: '40%' }, { xtype: 'passwordfield', name: 'password', label: this.txtPasswordPrompt, labelWidth: '40%' }, { xtype: 'button', text: this.btnSubmitPrompt }, { xtype: 'component', itemId: 'footer', html: this.footerHtml }]); this.callParent(arguments); } });
Localizing Text Prompts and Applying Translations to Views
The next step is to encapsulate the translations into a class that can be easily edited. In Ext JS 4, the standard is to place translations in separate files that are read in at runtime. However, since I’m dealing with a mobile app, I wanted to minimize the http overhead and decided to place all the translations into a singleton that is packaged along with the rest of the app.
Note the following:
- Each language is represented by a two-character language code
- Translations are grouped by the view in which they’ve been defined
- The localize() method applies the translations to each of the view classes for which a translation is available.
Ext.define('MyApp.config.Locale', { singleton: true, config: { en: { Login: { txtEmailPrompt: 'Email:', txtPasswordPrompt: 'Password', btnSubmitPrompt: 'Submit', headerHtml: 'Heading in English', footerHtml: 'Footer in English', loginErrorMsg: 'Sorry, try again' } }, fr: { Login: { txtEmailPrompt: 'Votre email:', txtPasswordPrompt: 'Votre mot de passe', btnSubmitPrompt: 'Transmettre', headerHtml: 'Heading en Francais', footerHtml: 'Footer en Francais', loginErrorMsg: 'Merde. Où sont les toilettes?' } } }, localize: function(locale) { var translations = this.config[locale]; for (var view in translations) { Ext.apply(MyApp.view[view].prototype,translations[view]); } } });
Using this technique, I’m able to easily apply the translations just prior to launching the main view of the app:
Ext.application({ name: 'MyApp', requires: [ 'Ext.MessageBox', 'MyApp.config.Locale' ], views: ['Main'], launch: function() { MyApp.config.Locale.localize('fr'); Ext.Viewport.add(Ext.create('MyApp.view.Main')); } });
Localizing Dates
Unfortunately, the aforementioned solution is still a “half-measure” in that we still need to localize the built-in Sencha Touch controls, such as the date selector. In order to do this, you’ll need to override the Ext.Date class (which loads the Ext.DateExtras class as a mixin). Fortunately, the Ext.Date/Ext.DateExtras classes in Sencha Touch closely parallel Ext JS 4, so you can repurpose Ext JS 4 locale files as illustrated below:
Ext.define('MyApp.config.Locale', { singleton: true, config: { en: { LoginView: { txtEmailPrompt: 'Email:', txtPasswordPrompt: 'Password', btnSubmitPrompt: 'Submit', headerHtml: 'Heading in English', footerHtml: 'Footer in English', loginErrorMsg: 'Sorry, try again' } }, fr: { LoginView: { txtEmailPrompt: 'Le Email:', txtPasswordPrompt: 'Password', btnSubmitPrompt: 'Submit', headerHtml: 'Heading En Francais', footerHtml: 'Footer En Francais', loginErrorMsg: 'Sorry, try again' } } }, fr: function() { // date localization borrowed from Ext JS 4 fr locale if (Ext.Date) { Ext.Date.shortMonthNames = ["Janv", "Févr", "Mars", "Avr", "Mai", "Juin", "Juil", "Août", "Sept", "Oct", "Nov", "Déc"]; Ext.Date.getShortMonthName = function(month) { return Ext.Date.shortMonthNames[month]; }; Ext.Date.monthNames = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"]; Ext.Date.monthNumbers = { "Janvier": 0, "Janv": 0, "Février": 1, "Févr": 1, "Mars": 2, "Mars": 2, "Avril": 3, "Avr": 3, "Mai": 4, "Juin": 5, "Juillet": 6, "Juil": 6, "Août": 7, "Septembre": 8, "Sept": 8, "Octobre": 9, "Oct": 9, "Novembre": 10, "Nov": 10, "Décembre": 11, "Déc": 11 }; Ext.Date.getMonthNumber = function(name) { return Ext.Date.monthNumbers[Ext.util.Format.capitalize(name)]; }; Ext.Date.dayNames = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"]; Ext.Date.getShortDayName = function(day) { return Ext.Date.dayNames[day].substring(0, 3); }; Ext.Date.parseCodes.S.s = "(?:er)"; Ext.override(Date, { getSuffix: function() { return (this.getDate() == 1) ? "er" : ""; } }); } }, en: function() { // reset all dates to english here }, localize: function(locale) { var translations = this.config[locale]; for (var view in translations) { Ext.apply(MyApp.view[view].prototype, translations[view]); } this[locale](); } });
So, as my old french teachers would have said…”C’est Bon!”..right after giving my latest effort a C-.
Thanks for the tutorial… I am going to try this out. Out of curiosity, do you have any pointers to different approaches to the issue? best regards!
Need help?
Email / Votre email / Le Email
-> “Email” is OK in France (French-speaking inhabitants of Quebec certainly prefer “Courriel”)
Password / Votre mot de passe / Password
-> “Mot de passe” is generally used.
Submit / Transmettre / Submit
-> “Envoyer” is generally used.
Headind in English / Heading en Francais
-> “Entête en Français”
Footer en Français
-> “Pied de page en Français” or “Bas de page en Français”
Sorry, try again / Merde. Où sont les toillettes ?
-> This one is correct actually 🙂 but you can also use : “Désolé, essayez ecnore”
My 2 cents: http://lamscommunity.org/i18n/
Sources: http://edutechwiki.unige.ch/en/Software_localization
PS (unrelated): Thank you a lot for the post on “jQuery Mobile vs. Sencha Touch vs. Appcelerator Titanium!” + video
And that would be why Google translate will never be a real substitute for human translation services….
“Désolé, essayez ecnore” -> “Désolé, essayez encore”
I reversed the C and N in “Encore”
Great info. Thanks for sharing!
Hi!, I have some problems, I tried to implement the first option of the post, but, like this
… titleBarHome: ‘Bienvenidos a Sencha Touch 2’,
config: {
items: [
{
title: this.titleBarHome,
iconCls: ‘home’…
But not show the message, I don’t know what happen, help me please 😦
Thank you so much! 😀
how to change language on click, for example there are two links in top right, user can switch language anytime from one to other….
In the button’s handler, use Ext.Loader.loadScriptFile() to load the appropriate localization files, then destroy and re-instantiate the view.