1. Introduction
  2. Getting Started
  3. Using the CLI
  4. Coding Guide
  5. Assertions
  6. OOP
  7. Utils & Async
  8. Data Structures
  9. Universal Events
  10. Templating
  11. Indexed Tables
  12. Entities
  13. I18n
  14. Routing
  15. API Access
  16. Widgets
  17. Basic Widgets
  18. Asset Mgmt
  19. Session Mgmt
  20. Best Practices

Internationalization

Module Namespace Stability
npm install giant-i18n $i18n Stable

Giant facilitates Gettext-compatible translations through its internationalization (i18n) module, with support for standards-compliant pluralization and substitutions.

Without any translations available, 'translatable' strings resolve to themselves regardless of the current locale.

'Hello World'.toTranslatable() + "" // "Hello World"

Current locale

State of the current locale is stored in a $i18n.LocaleEnvironmentDocument entity, but it's usually being manipulated through the $i18n.LocaleEnvironment singleton.

To set the current locale:

$i18n.LocaleEnvironment.create()
    .setCurrentLocale('de-DE'.toLocale());

or, using the more convenient form:

'de-DE'.toLocale().setAsCurrentLocale();

Note that setting the current locale does not mean that the locale is ready for use, as translations might still have to be loaded.

Readiness of current locale

The current locale should only be applied (to the UI, etc) when its ready flag is set to true, also signaled by the $i18n.EVENT_CURRENT_LOCALE_READY event triggered on the LocalEnvironment singleton.

$i18n.LocaleEnvironment.create().subscribeTo(
    $i18n.EVENT_CURRENT_LOCALE_READY,
    function () {
        // current locale ready for use
    });

Alternatively, you may use the $i18n.LocaleBound trait to add the capability of listening to EVENT_CURRENT_LOCALE_READY to application classes.

var LocaleBoundClass = $oop.Base.extend()
    .addTrait($i18n.LocaleBound)
    .addMethods({
        init: function () {
            $i18n.LocaleBound.init.call(this);
            this.bindToCurrentLocaleReady(
                'onCurrentLocaleReady');
        },

        onCurrentLocaleReady: function () {
            // current locale ready for use
        }
    });

In the example above we bind to the event in the constructor, but this is only for brevity. Binding / unbinding may be done at any time throughout the instance's life cycle, what's important is to always unbind before discarding the instance.

When a locale is already marked ready, setting it as current will trigger EVENT_CURRENT_LOCALE_READY immediately.

When the current locale is not yet ready, it's the application's responsibility to mark it ready when appropriate.

Loading translations

Giant's i18n module is agnostic about how translation data reaches the browser. The module offers however the means of storing translation data in the entity store, and to notify the rest of the application when loading has finished and a particular locale has become ready for use.

Once you have translations loaded (either via network or from file system) as key-value pairs, LocaleDocument, a document entity class designed specifically to manage locale data, will store the translations in the structure giant-i18n expects it:

'locale/de-DE'.toDocument()
    .setTranslations({
        "Hello World": "Hallo Welt"
    });

After translations are set for a locale, the application is expected to mark it as ready:

'de-DE'.toLocale().markAsReady()

Re-running the first example will now yield the expected result:

'de-DE'.toLocale().setAsCurrentLocale();
'Hello World'.toTranslatable() + "" // "Hallo Welt"

Initiating loading

One of Giant's central philosophies is to make everything work on-demand. Whenever the application attempts - and fails - to read from the translations field of a locale document, that signals the absence of a required locale. By listening to an $entity.EVENT_ENTITY_ACCESS event in the entity store we'll know that a locale is being accessed but is not there - and hence needs to be loaded.

function onLocaleAccess(event) {
    var affectedKey = event.sender,
        localeKey = affectedKey.isA($entity.DocumentKey) ?
            affectedKey :
            affectedKey.documentKey;

    if (!localeKey.toDocument()
        .getField('translations')
        .getSilentNode()
    ) {
        // translations missing - start loading
    }
}

$entity.entityEventSpace
    .subscribeTo(
        $entity.EVENT_ENTITY_CHANGE,
        'entity>document>locale'.toPath(),
        onLocaleAccess);

When translations finished loading, make sure to mark it as ready as above.

Substitutions

A localized string will most likely contain substitutions - with values of either other localized strings, or field / item entities. Substitutions like that are handled by Giant's templating module.

// building string
var template = "You have {{what}}".toTranslatable()
    .toLiveTemplate()
    .setParameterValues({
        '{{what}}': "{{kind}} apples".toTranslatable(),
        '{{kind}}': "red".toTranslatable()
    });

'en-US'.toLocale().setAsCurrentLocale();
template + "" // "You have red apples"

// setting translations
'locale/de-DE'.toDocument()
    .setTranslations({
        "You have {{what}}": "Du hast {{what}}",
        "{{kind}} apples": "{{kind}} Äpfel",
        "red": "rote"
    });

'de-DE'.toLocale().setAsCurrentLocale();
template + "" // "Du hast rote Äpfel"

As the example shows, Translatable instances may be converted to LiveTemplate instances. From then on, the template behaves exactly the same way as a regular live template, except that the template 'string', as well as the parameter values are really Stringifiable instances.

Pluralization

What lies at the heart of any good translation mechanism is handling plural forms correctly. Giant i18n follows Gettext in this regard, with the ability to import its plural formulas and translations in JSON format. For more information about pluralization in Gettext, visit http://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html

The pluralization formula is set on the locale document, similarly to translations:

'locale/de-DE'.toDocument()
    .setPluralFormula('nplurals=2; plural=(n != 1);');

The formula we set here is the most common one, used in English, German, and many other languages. It has two forms, one for singular, and one for everything else. In certain languages, such as Polish, there are more than 2 forms.

In the formula, nplurals defines the number of forms, and plural is a function that associates multiplicity n with an array index specifying which value to take from a list of strings. (See example below.) The plural function is allowed to return boolean, where false evaluates to 0, true to 1.

Plural formulas are validated before application, and attempting to set an invalid plural formula will throw an exception.

Once the plural formula is set, we must ensure that translations come with the different forms:

'locale/en-US'.toDocument()
    .setTranslations({
        "apple": ["apple", "apples"]
    });

'locale/de-DE'.toDocument()
    .setTranslations({
        "apple": ["Apfel", "Äpfel"]
    });

Now if we specify multiplicity for translatables, Giant will pick the corresponding form.

var translatable = 'apple'.toTranslatable();

'de-DE'.toLocale().setAsCurrentLocale();
translatable.setMultiplicity(1) + "" // "Apfel"
translatable.setMultiplicity(999) + "" // "Äpfel"

'en-US'.toLocale().setAsCurrentLocale();
translatable.setMultiplicity(1) + "" // "apple"
translatable.setMultiplicity(999) + "" // "apples"

Gettext

To complete the circle, we need to look at our options re. the extraction and processing of localization-affected strings in our application. Giant follows the conventions laid out by Gettext for a reason: it is the most ubiquitous open source tool for exporting marked strings to .POT files.

There are tools available facilitating the extraction, editing, and conversion of localized strings. The following list is a very brief outlook on some of the more popular ones.