Internationalization and localization
NexJ CRM provides the following internationalization and localization capabilities:
NexJ CRM supports English, and it was built to work with Unicode for supporting other languages.
Specific formatting is supported for dates, currency, and numbers, including highlighting the business hours for your deployment.
Multi-language support is built into the business model for code values.
Display language is selectable at runtime.
String and locale files (files with extensions .string and .locale) are not part of the Model Description Language. They are lists of key and value pairs. They are considered to be properties files or property resources.
Locale files store local preferences for a language, such as date and number formats.
String files store translated captions, messages, and other elements and properties that switch their display text depending on the current user's language preference.
Overview
To support a new language, you need to add resources with a new language tag. The language tags are based on the IETF standard RFC 4646. For more information, see RFC 4646 - Tags for Identifying Languages (ietf.org).
Some examples of language tags are en
for English, en-CA
for Canadian English, or es
for Spanish.
Several types of files are used to record information about specific languages, including the SupportedLocaleEnum enumeration, one or more strings files for each supported language, and a locale file for each supported language.
SupportedLocaleEnum enumeration
An enumeration is a special type of class that contains a list of values along with localizable captions. Enumerations provide a standardized way to create lists of values, such as the days of the week. The values can be localized, for example by providing the terms for each day of the week in multiple languages. Enumerations can be associated in a parent-child relationship, such as country and state. For more information about enumerations, see Enumerations
The SupportedLocaleEnum enumeration contains a list of all supported locales. For each locale, it indicates the long and short descriptions in each supported language, as well as other information, such as whether the value can be updated at runtime.
For each new supported locale, you need to add a locale record and define its associated values. This example SupportedLocaleEnum enumeration describes two supported locales, English and French, and the short and long captions for each locale in both languages. The hasBehavior=true
parameter indicates that there is business logic associated with this value.
Example SupportedLocaleEnum enumeration
Enumeration description="Enumeration of supported locales" typeCode="SSUPPORTEDLOCALE">
<Locales>
<Locale caption="Enumeration of supported locales" name="en"/>
<Locale caption="Énumeration des paramètres régionaux supportés" name="fr"/>
</Locales>
<Values>
<Value hasBehavior="true" name="en" value="en">
<Locales>
<Locale caption="English" name="en" shortCaption="en"/>
<Locale caption="anglais" name="fr" shortCaption="en"/>
</Locales>
</Value>
<Value hasBehavior="true" name="fr" value="fr">
<Locales>
<Locale caption="French" name="en" shortCaption="fr"/>
<Locale caption="français" name="fr" shortCaption="fr"/>
</Locales>
</Value>
</Values>
</Enumeration>
String files
Text strings used in your application are stored in .strings files in the meta/strings
directory for each project. Each strings file can contain one or more captions with their identifiers. The names of the files are in the <uniqueStringsArea>.<languageTag>.strings format. For example, the idsc.Address.en.strings
file might contain the English terms for different parts of an address, such as "City" or "PO Box", which are associated with identifiers such as idsc.Address.city
or idsc.Address.poBox
. If the French language is also supported, then there needs to be an idsc.Address.fr.strings
file in the same directory. This file would contain the French captions for the same identifiers. For example, the isdc.Address.city
value would be "Ville" and the idsc.Address.poBox
value would be "C. P."
You can also use a strings file for a single caption. For example, the idsa.ServiceRequest.en.strings
file contains only the following item:
idsa.ServiceRequest.contactSRFilter=Contact Service Requests
The matching idsa.ServiceRequest.fr.strings
file contains only the following item:
idsa.ServiceRequest.contactSRFilter=Demandes de service du contact
String files can be populated using the export and import functions in the Strings Tool in NexJ Studio.
For more information about strings and the Strings Tool, see Strings.
Locale files
The /meta/locales folder contains a <languageTag>.locale file for every supported locale.
Additionally, a new locale/string file is created for every supported locale in /mod/i18n/<languageTag>.js.
Internationalization resources, plugins, and functions
Localized resources are organized in hierarchical maps, which are maintained in nexj/i18n
plugins and can be looked up by id (string key) using the nexj/i18n()
function. The most common resources are localized strings, used for UI captions and messages. You can also localize arbitrary objects, including functions.
Locale names follow the RFC 4646 standards and the corresponding resource maps form prototype inheritance chains for fallback lookup. For example, the "en-CA" map is based on the "en" map, which in turn is based on the "" (default) map. In addition to the I18N framework modules nexj/i18n/<lang>
with files nexj/i18n/<lang>.js
, each module can have its own set of localization modules <module>/i18n/<lang>
with files <module-file>/i18n/<lang>.js
. The AFL component nexj.core.ui.web.AMDChainLoader
automatically includes the localization modules according to the user language.
Localization modules use functions like nexj/i18n.add()
and nexj/sys.prototype()
to create resource prototype chains, based on the data from the Unicode Common Locale Data Repository (CLDR). For more information, see Unicode CLDR.
Models can parse and format data in locale-specific formats, therefore they can be configured with specific formatting patterns. The actual parsing and formatting is implemented in nexj/cvt
. The function nexj/cvt()
constructs a converter from a data type name, a pattern, a unit of measure and a localized resource map. The data type name must match a class property in nexj/cvt
:
cvt("number");
// => default number converter
new ui.Value(0, "number")
// => value model using a default number converter
Number format patterns can be configured to round values to thousands and millions using one or more M characters and an optional number. Without a number the scale is determined automatically, the numbers 3 and 6 lock the scale to thousands and millions. A single, double and triple M results in short, medium and long names respectively.
Pattern example | Value | Formatted value |
---|---|---|
# #0 MMM | 1e8 | 100 Million |
# #0M3 | 1e8 | 100000k |
Parameterized UI messages are formatted using the nexj/cvt.message
converter, or alternatively with nexj/cvt.format()
or nexj/cvt.message.format()
. Each argument placeholder can specify its own converter, pattern and unit, including those supplied as other arguments. The functionality in id
, choice
and list
allows taking into account different word forms, language constructs and expressions for lists of items, directly in the localized resources, without any additional locale-dependent code. The resource map is typically in the nexj/i18n
plugin.
Resource map example
var locale = sys.prototype(i18n.en, {
Entity: "Entity",
"Entity.pl": "Entities",
"app.delete.confirm": "Delete " +
// The choice converter is given argument 1, i.e. value 3, along with an
// interval [1..2] pattern with nested id converters and all the
// cvt.message.format arguments (*), so that the nested converter
// placeholders could work.
// The first id converter is used if argument 1 is <2. It looks up the
// resource string by the id provided in argument 2, i.e. "Entity", and
// converts it to lower case ("l").
// The second id converter is used if argument 1 is >=2. It appends
// ".pl" to the string resource id before looking it up, thus getting
// the plural form "Entities", which it then converts to lower case.
"{1;choice;1|one {2;id;|l}|2|{$} {2;id;.pl|l};*}: " +
// The list converter puts quotes around each item from the array in
// argument 0 and separates them with ", ", except for the last two
// items, which it separates with " and ".
"{0;list;\"{$}\"|, | and }?"
}),
entities = ["root", "wheel", "admin"];
cvt.message.format("app.delete.confirm",
[entities, entities.length, "Entity"], locale);
// => 'Delete 3 entities: "root", "wheel" and "admin"?'
Localized exceptions are constructed easily with nexj/i18n.Error()
.
Localized exception example
// The exception is an instance of RangeError, the localized message id is
// "val.range" and the message arguments are lo and hi
throw i18n.Error.call(RangeError(), ["val.range", lo, hi]);
...
// Similar to above, but the constructed type is the generic i18n.Error
throw i18n.Error(["val.range", lo, hi]);
The commonly used patterns are stored as localized resources, and specifying the corresponding resource id directly as a pattern is a useful shortcut for calling nexj/i18n()
.
// the resource id "date.s" is used as a shortcut for
// looking up the actual pattern
new ui.Value(new Date(), "date", "date.s");
// the above is equivalent to the following
new ui.Value(new Date(), "date", i18n("date.s"));
Dates and calendars
Date manipulation API is implemented in the nexj/date
module. The nexj/date.Zone
class provides time zone offset computation. By default, only the local time zone used in the browser and the UTC time zone are supported. However, most of the time zones in the world are added by including the nexj/date/olson
module. This module relies on the Olson time zone database module nexj/date/olson/db
, which is automatically generated by the build from files maintained by the Internet Assigned Numbers Authority (IANA). A new version of the database is released regularly. For more information about IANA, see Time Zone Database (iana.org).
The nexj/date.Gregorian
class implements date arithmetic according to Gregorian calendar rules. More calendars can be added by deriving them from the nexj/date.Calendar
class.
Business hour schedule shading
On the Schedule workspace , time slots outside of business hours on day and week views are shaded. By default, the workweek starts on Monday, the workday starts at 8 a.m. and lasts 9 hours. To change these default values, add the following code and change the values as required.
sys.override(i18n["en-CA"].calendar.Gregorian, {
workweekStart: 1,
workweekLength: 5,
businessHourStart: 8,
businessHourLength: 9
});
In this example
workweekStart indicates the first workday in the week. The day of the week values are 1 to 7. The defaults value is 1, which indicates Monday.
workweekLength is the number of workdays in the week. The default value is 5.
businessHourStart indicates when business hours start, using the 24-hour schedule, where 1 is 1a.m. and so on. The default value is 8.
businessHourLength is the length of a workday. The default value is 9.
To remove the shading entirely, you can include schedule.shade=transparent
value in the appropriate theme file.
For more information about theme files, see UI themes and CSS.
Configuring supported client locales
To configure supported client regional locales, you must modify the corresponding .locale file by adding the ids.format.regionalLocale
property, if it does not already exist. The locale files can be found in the Resources > Locales tab in NexJ Studio. They can also be found in the meta/locales directory in the project folder. For example, finance/meta/locales.
The value for this property should be a valid combination of a language code and a region code separated by a hyphen, for example, en-CA
(Canadian English).
The format and capitalization for the value of this property must follow a specific standard. The full property and value added to a locale file should look similar to ids.format.regionalLocale=en-CA
.
Each locale file can have its own value for this property. For example, the property mentioned above can be added to the en.locale file, and the following property can be added to the fr.locale file: ids.format.regionalLocale=fr-CA
.
By adding this property to the locale files, the values attached to them build the supported client locales accepted by the application.
Setting locales
You can change locale via URL or via browser language.
To change locale via URL, you must add the l
parameter to the URL and the value specified should be an exact match for the value of the ids.format.regionalLocale
property in the locale file. For example, if an application's URL is http://www.nexj.com/nexj/ui/portal, then use the following URL to indicate that Canadian English should be used http://www.nexj.com/nexj/ui/portal?l=en-CA.
If there is already an appended URL query parameter, add &l=en-CA
to the URL instead.
To change the locale to match the browser, use the browser settings to change the browser language.
Configuring a project default locale
You can configure the default locale for the project. It will be used if the incoming locale (either entered as the URL or the current user’s browser locale) is not supported by the application. To set a desired project default locale, modify the FALLBACK_LOCALE
attribute in the User class (User.meta
).
Locale selection precedence
Because there are multiple different ways to set locale, which can have varying values, for example, URL, browser, project default, and so on, there is a priority system in place to determine which value has precedence.
The priority, from highest to lowest, is:
URL
User model (locale attribute in
User.meta
)browser locale
project default
en-CA
A locale specified in the URL has the highest priority. As long as the locale value in the URL is supported, that client locale is used.
If there is no locale set in the URL, the user locale value is checked. If supported, the user locale value is used. Otherwise, the browser locale is checked. If the browser locale is supported, it is used. If the browser locale is not supported, the project default locale is used.
If the project default locale that has been configured is not a valid supported client regional locale (by misconfiguration, for example), the application defaults to en-CA
.
Using a flag to determine User model precedence
You can use the ignoreUserLocale
flag from the framework to determine when the User.meta
locale
value should take precedence.
When the value for the flag is false
, which is the default value, the User#locale
is used. If the value is true
, the User model value may not match the locale
value that was set in the URL, and the client locale should be used instead. In this case, the returned value is the existing clientLocale
value, as tracked by the framework.
The following example shows the usage of the flag in finance. It is taken from the locale
attribute for User.meta
in finance. The methods, which get and set the ignoreUserLocale
flag, exist for the nexj.core.runtime.InvocationContext
class, and therefore a nexj.core.runtime.InvocationContext
instance is needed to call these methods.
(cond
//If the flag is TRUE (locale set from URL), then we can’t use the User model value, therefore, use the client locale value tracked by the framework (InvocationContext#getClientLocale).
(((invocation-context)'ignoreUserLocale) ((invocation-context)'clientLocale))
//ELSE the flag is FALSE (locale not set in URL), continue to User model logic
(else
…
//Retrieve user model locale/validate/use fallback/etc.
Enabling en_US language support
By default, the en locale uses the Canadian format for dates (day/month/year). If you want to be able to display dates in the US format (month/day/year), you must enable the en_US locale.
To enable the en_US locale and to provide the ability for users to select either the en_US or the en_CA locale for English, do the following:
In NexJ Studio, in the Business Model layer, open the Enumerations tab.
Open the SupportedLocaleEnum enumeration.
If SupportedLocaleEnum is not already customized, right-click on SupportedLocaleEnum and select Customize Using > Base.
In the Values section, add a new value and specify the following settings:
Field name Setting Name en_US Value en_US Has Behavior true (checkbox selected) Display Updatable true (checkbox selected) Select the new value. In the Value Captions section for en_US, add a new preferred value caption for each supported locale. For example, if you have en, fr, and en_US locales, specify the following settings:
Field name Setting for en locale Setting for fr locale Setting for en_US locale Locale en fr en_US Caption English (United States) anglais (États Unis) English (United States) Short Caption en_US en_US en_US This will be reflected in the source code in the following:
XML<Value hasBehavior="true" name="en_US" value="en_US"> <Locales> <Locale caption="English (United States)" name="en" shortCaption="en_US"/> <Locale caption="English (United States)" name="en_US" shortCaption="en_US"/> <Locale caption="anglais (États Unis)" name="fr" shortCaption="en_US"/> </Locales> </Value>
For each of the existing supported locales, select the locale and add a new value caption entry for the new en_US locale. For example, for the fr locale, add a value caption with the following settings:
XML<Locale caption="French" name="en_US" shortCaption="fr"/>
Add an upgrade load step to Main.upgrade and increment the version number by 1.
Update finance model version to match the latest Main.upgrade version.
Upgrade the database.
The en_US locale is now enabled and en_US data can be seeded.
To disable the en_US locale, do the following:
In the SupportedLocaleEnum enumeration, remove all references to the en_US locale.
In the Main.upgrade file, add an upgrade load step and increment the version by 1.
Update finance model version to match the version on the latest Main.upgrade file.
Upgrade the database.