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.
- 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
-
Bundles
- message keys follow the format:
<component_name>_label
- support for loading multiple bundles for the same locale
- message keys follow the format:
-
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
- generated from all existing bundle files in the selected directories. It has the following format:
-
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.
- All interested components have a
- it uses the fallback mechanism of the bundle compiler
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"
}
}