Giving your Apps a Multilingual Flair

With the recent introduction of Polish as our ninth spoken language translation in the Valence Portal, developers now have yet another option for supporting multilingual installations running on IBM i. Even if your company does not currently have a multilingual user base, if it may someday become multilingual as a result of international acquisitions or mergers, there are some simple steps you can take in your app development today to make the process of supporting multiple languages on your Valence applications down the road relatively simple.

The key to multilingual support in Valence is to avoid hard-coding any literals in the views of your applications.  This means column headings, field labels, window titles, etc., should all be set up as variables rather than as constants.  For instance, instead of putting a hard-coded label of, say, “User Name” for an entry field in your app, you should instead set it to a JavaScript variable that can be overridden at run time.  One common convention for this is to create a JavaScript object called “Literals” with a property that closely resembles the English literal (i.e., Literals.userName). An example of this concept being used in an Ext JS grid might look something like this:

Ext.create('Ext.grid.Panel', {
    title: Literals.userInfo,
    store: Ext.data.StoreManager.lookup('EmployeeStore'),
    columns: [
        {text: Literals.userName, dataIndex: 'USERNAME'},
        {text: Literals.address1, dataIndex: 'ADDR1'},
        {text: Literals.city,     dataIndex: 'CITY'}
    ],
    width: 400,
    forceFit: true
});

Once you have an app using a “Literals.fieldName” convention for all its literals, you’ll need to create a repository of translation files for each of the languages you wish to support — one file for each language.  Typically you would start with an English translations .js file, then provide that file to a translator to produce separate versions for each of the languages you need to support. The name of your translations files should be consistent, differentiated only by a language ID (i.e., Literals-en.js). The language abbreviation must follow the convention used within Valence and Ext JS:  “en” for English; “de” for German; “es” for Spanish; “fr” for French; “it” for Italian; “ja” for Japanese; “nl” for Dutch; “pl” for Polish; “rl” for Russian.  (Note: These are just the langauges pre-translated in the Valence Portal; You can add additional languages to the Valence Languages list in the Portal Admin app and supply your own literals.) An example of a portion of an English literals file might look something like this:

/*
 * Literals-en.js
 * Valence Custom Literals File
 * English
 * Copyright(c) ACME Corp
 */

Ext.define('Literals', {
    singleton:   true,

    address1:    'Address (line 1)',
    address2:    'Address (line 2)',
    city:        'City',
    country:     'Country',
    phoneNumber: 'Phone Number',
    state:       'State',
    userInfo:    'User Information',
    userName:    'User Name'
});

  The Spanish literals equivalent to this file would look something like this:

/*
 * Literals-es.js
 * Valence Custom Literals File
 * Spanish
 * Copyright(c) ACME Corp
 */

Ext.define('Literals', {
    singleton:   true,

    address1:    'Dirección (línea 1)',
    address2:    'Dirección (línea 2)',
    city:        'Ciudad',
    country:     'País',
    phoneNumber: 'Número de Teléfono',
    state:       'Estado',
    userInfo:    'Información de Usuario',
    userName:    'Nombre de Usuario'
});

You should keep all your literals files in a commonly accessible area on the IFS.  One approach is to use a path similar to what the Valence Portal uses for its own literals, i.e. /resources/locale/custom/.  Note that applying a Valence update on your system will not affect any custom files placed within the Valence directory, but an upgrade to a new Valence release (i.e., from Valence 4.1 to the forthcoming Valence 4.2) will require that you copy those files to the directory structure of the new instance. If you have multiple Valence instances that will all need to access the same translations file, you may be better served storing the translation files in a custom folder accessible to all instances, in which case you’ll need to add an alias to each instance’s Apache configuration so your front-end code can get to it.

Once you have your literals files ready, adding multilingual functionality to your apps is simply a matter of executing the appropriate .js literals file based based on the language the user selected at login. Your apps can then reference these literals.  Though you could have your individual apps pull in the literals each time they’re launched, it would be more efficient to configure the Valence Portal to load the literals globally when each user logs in. You can achieve this by modifying the Hook.js program in /resources/desktop/ (or /resources/touch for mobile apps), adding a couple small blocks of code to the constructor section as follows (new code in red):

constructor : function () {
    var me = this;
    var lang = Valence.util.Helper.getLanguage();
    Valence.util.Helper.execScriptFiles({
            urls: ['/resources/locale/custom/Literals-' + lang + '.js']
    });
...
    launchapp: function (app) {
        if (typeof app.getWin==='function' && !Ext.isEmpty(Literals)) {
            app.getWin().Literals = Literals;
        }
    }

This code uses Valence’s getLanguage() helper function to retrieve the language of the user, and then conveniently injects the Literals object into your app.  Note that changes made to Hook.js like this are not overridden by Valence updates, but you will need to copy over any Hook.js customizations you’ve made to the new instance directory whenever you upgrade to a new Valence release. One thing to keep in mind with this setup is that if your app happens to reference a literal that doesn’t yet exist in your translations file, what shows on the view will likely be either blank or a somewhat nebulous “undefined” value. To handle these cases better, you could add a getter function inside your “Literals” object to control what gets returned on non-defined literals. Something like this:

getLit: function(lit){
   return this[lit] || 'literal '+lit+' not defined';
}

Then instead of referencing Literals directly in your apps (i.e., via Literals.userName), you’d use this getter function (Literals.getLit(‘userName’)), which would make it easier to spot which specific literals need to be set up without having to dig into the app’s code to get the literal name. Of course, there are many other ways you could incorporate literals into your apps, but in our experience we’ve found this approach to be the most elegant.