Skip to content

Latest commit

 

History

History
149 lines (130 loc) · 6.04 KB

i18n.md

File metadata and controls

149 lines (130 loc) · 6.04 KB

i18n implementation overview

This is a version of the i18n for the PSP that follows some of the concepts discussed during i18n meetings. We've described some of the decisions we made.

Summary:

  • We are using the same mechanism for both the main and the renderer processes that includes:
  • A single component that provides messages to all components that require such by using distributeOptions blocks
  • This component is backed up by a single “Mega bundle” that is compiled using all available bundles for the different locales. Bundles are searched for and loaded from a given list of directories.
  • In the renderer process locale change are propagated via IPC

Details for the PSP i18n architecture decisions:

  • Bundles

    • message keys follow the format: <component_name>_label
    • support for loading multiple bundles for the same locale
  • Mega Bundle (Bundles compiling)

    • generated from all existing bundle files in the selected directories. It has the following format: { <locale>: {<messages>}, en: { <component name>_message1: value, ... }, en_us: {}, bg: {} }
    • generated with a Grunt task which is executed before the application starts
    • different bundles are merged as follows:
      • bundles from all provided folders are collected
      • bundles for each locale are merged sequentially into a single object, i.e. this object contains all messages for the given locale
        • in case of a key collision, the value from the last bundle will be used
        • order of merging is not guaranteed
        • this algorithm is not currently configurable, as it is done using a Grunt task with a configuration specified in its Gruntfile
      • Fallback: in case keys are missing they will be search for in the other locales. See the Fallback mechanism section for more details
  • Mechanism for messages usage

    • a single grade is added to the root component of a tree which contains i18n-able components
    • this grade is responsible for loading the already generated mega bundle
    • the messages for the current locale are kept in its model( model: { messages: {} } ).These messages:
      • are collected from the loaded mega bundle
      • in order to ease distribution, they are grouped by component name, e.g. { messages: {en: { <component>: { message1: value1, ... } } } }. Components’ names are extracted from the message keys
      • are updated when the locale changes
    • Distributes messages to interested components:
      • All interested components have a messages object in their model which holds the messages used throughout the components
      • once the locale is changed, messages of interested components are updated
      • this update can be intercepted in a model listener in order to make the necessary changes to the UI
      • it is accomplished through the usage of distributeOptions. Ref.
    • it uses the fallback mechanism of the bundle compiler

Fallback mechanism

The following mechanism is currently used to supplement an incomplete set of bundles for a given locale: missing keys for a given locale are searched for sequentially first in one or more less specific locales (if such exist) and finally in the default locale.

Let’s illustrate this with an example: Let’s say we have bundles for three of the en locale variations (en_us, en_ca and en itself) and a default bundle of bg, which look like:

{
    "bg": {
        "gpii_app_component1_label1": "label_bg_1",
        "gpii_app_component2_label1": "label_bg_2",
        "gpii_app_component3_label1": "label_bg_3"
    },
    "en": {
        "gpii_app_component1_label1": "label1",
        "gpii_app_component2_label1": "label2"
    },
    "en-us": {
        "gpii_app_component2_label1": "label_us_2"
    },
    "en-ca": {
        "gpii_app_component1_label1": "label_ca_1",
        "gpii_app_component3_label1": "label_ca_3"
    }
}

Two keys are missing in the en_us locale (gpii_app_component1_label1 and gpii_app_component3_label1) which will be i) first looked for in the en locale and in case a key is missing there, ii) the bg will be used as it is the default locale. (Ref.)

So the resulting (compiled) bundle would look like:

{
    "bg": {
       "gpii_app_component1_label1": "label_bg_1",
       "gpii_app_component2_label1": "label_bg_2",
       "gpii_app_component3_label1": "label_bg_3"
    },
    "en": {
        "gpii_app_component1_label1": "label1",
        "gpii_app_component2_label1": "label2",
        "gpii_app_component3_label1": "label_bg_3"
    },
    "en-us": {
        "gpii_app_component1_label1": "label1",
        "gpii_app_component2_label1": "label_us_2",
        "gpii_app_component3_label1": "label_bg_3"
    },
    "en-ca": {
        "gpii_app_component1_label1": "label_ca_1",
        "gpii_app_component2_label1": "label2",
        "gpii_app_component3_label1": "label_ca_3"
    }
}

As we can see the gpii_app_component1_label1 is loaded from the en locale, whereas for the gpii_app_component2_label1 key the bg bundle is used.

Let’s look at a different example that describes the behaviour when multiple of the same specificity locales (e.g. es_bo and es_co) are present: Let’s have bundles for the locales es_bo, es_co and none for the default locale (e.g. en). As we are missing bundles for both the more generic locale es and the default locale, in case we have a missing key in the es_bo locale, the key won't be look for in the es_co locale as it is of the same generality. Regardless of the key being present in the es_co locale, it will NOT be used for the es_bo, resulting in an empty key.

{
    "es_bo": {
        "key1": "label1"
    },
    "es_co": {
        "key1": "label1",
        "key2": "label2"
    }
}

will result in:

{
    "es_bo": {
        "key1": "label1"
    },
    "es_co": {
        "key1": "label1",
        "key2": "label2"
    }
}