13 Internationalize and Localize Oracle JET Apps
About Internationalizing and Localizing Oracle JET Apps
Internationalization (I18N) is the process of designing software so that it can be adapted to various languages and regions easily, cost effectively, and, in particular, without engineering changes to the software. Localization (L10N) is the use of locale-specific language and constructs at runtime.
Oracle has adopted the industry standards for I18N and L10N such as World Wide Web Consortium (W3C) recommendations, Unicode technologies, and Internet Engineering Task Force (IETF) specifications to enable support for the various languages, writing systems, and regional conventions of the world. Languages and locales are identified with a standard language tag and processed as defined in BCP 47. Oracle JET includes Oracle National Language Support (NLS) translation support for the languages listed in the following table.
Language | Language Tag |
---|---|
Arabic |
|
Brazilian Portuguese |
|
Bulgarian | bg-BG |
Canadian French |
|
Chinese (Simplified) |
|
Chinese (Traditional) |
|
Croatian |
|
Czech | cs |
Danish |
|
Dutch |
|
Estonian |
|
Finnish |
|
French |
|
German |
|
Greek |
|
Hebrew |
|
Hungarian |
|
Icelandic | is |
Italian |
|
Japanese |
|
Korean |
|
Latin_Serbian | sr-Latn |
Latvian |
|
Lithuanian |
|
Malay | ms-MY |
Norwegian |
|
Polish |
|
Portuguese |
|
Romania |
|
Russian |
|
Serbian | sr |
Slovak |
|
Slovenian | sl |
Spanish |
|
Swedish |
|
Thai |
|
Turkish |
|
Ukrainian | uk-UA |
Vietnamese | vi |
Oracle JET translations are stored in a resource bundle. You can add your own translations to the bundles. For additional information see Add Translation Bundles to Oracle JET.
Oracle JET also includes formatting support for over 196 locales. Oracle JET locale elements are based upon the Unicode Common Locale Data Repository (CLDR) and are stored in locale bundles. For additional information about Unicode CLDR, see http://cldr.unicode.org. You can find the supported locale bundles in the Oracle JET distribution:
js/libs/oj/16.0.0/resources/nls
It is the app's responsibility to determine the locale used on the page. Typically the app determines the locale by calculating it on the server side from the browser locale setting or by using the user locale preference stored in an identity store and the supported translation languages of the app.
Once the locale is determined, your app must communicate this locale to Oracle JET for its locale sensitive operations, such as loading resource bundles and formatting date-time data. Oracle JET determines the locale for locale sensitive operations in the following order:
-
Locale specification in the ojL10n plugin of the RequireJS configuration.
-
lang
attribute of thehtml
tag. -
navigator.language
browser property.
If your app will not provide an option for users to change the locale
dynamically, then setting the lang
attribute on the
html
tag is the recommended practice because, in addition to
setting the locale for Oracle JET, it sets the locale for all HTML elements as well.
Oracle JET automatically loads the translations bundle for the current language and the
locale bundle for the locale that was set. If you don't set a locale, Oracle JET will
default to the browser property. If, however, your app provides an option to change the
locale dynamically, we recommend that you set the locale specification in the ojL10n
plugin if your app uses RequireJS. Oracle JET loads the locale and resource bundles
automatically when your app initializes.
If you use Webpack rather than RequireJS as the module bundler for your Oracle JET app, we recommend that you generate one code bundle for each locale that you want to support, and deploy each bundle to a different URL for the locale that you want to support. If, for example, your app URL is https://www.oracle.com/index.html and you want to support the French and Spanish locales, deploy the bundles for these locales to https://www.oracle.com/fr/index.html and https://www.oracle.com/es/index.html respectively.
Finally, Oracle JET includes validators and converters that use the locale bundles. When you change the locale on the page, an Oracle JET component has built-in support for displaying content in the new locale. For additional information about Oracle JET's validators and converters, see Validate and Convert Input.
Internationalize and Localize Oracle JET Apps
Configure your app to use Oracle JET's built-in support for internationalization and localization.
Use Oracle JET's Internationalization and Localization Support
To use Oracle JET's built-in internationalization and localization support, you
can specify one of the supported languages or locales on the lang
attribute
of the html
element on your page. For example, the following setting will set
the language to the French (France) locale.
<html lang="fr-FR">
If you want to specify the French (Canada) locale, you would specify the following instead:
<html lang="fr-CA">
Tip:
Thelocale
specification isn’t case
sensitive. Oracle JET will accept FR-FR
, fr-fr
, and so
on, and map it to the correct resource bundle directory.
When you specify the locale in this manner, any Oracle JET component on your page will display in the specified language and use locale constructs appropriate for the locale.
If the locale doesn’t have an associated resource bundle, Oracle JET will load the lesser significant language bundle. If Oracle JET doesn’t have a bundle for the lesser significant language, it will use the default root bundle. For example, if Oracle JET doesn’t have a translation bundle for fr-CA
, it will look for the fr
resource bundle. If the fr
bundle doesn’t exist, Oracle JET will use the default root bundle and display the strings in English.
In the image below, the page is configured with the
oj-input-date-time
component. The image shows the effect of
changing the lang
attribute to fr-FR
.
If you type an incorrect value in the oj-input-date-time
field, the error text is displayed in the specified language. In this example, the error is displayed in French.
Enable Bidirectional (BiDi) Support in Oracle JET
If the language you specify uses a right-to-left (RTL) direction instead of the
default left-to-right (LTR) direction, such as Arabic and Hebrew, you must specify the
dir
attribute on the html
tag.
<html dir="rtl">
The image below shows the oj-input-date-time
field that displays if you
specify the Arabic (Egypt) language code and change the dir
attribute
to rtl
.
Once you have enabled BiDi support in your Oracle JET app, you must still ensure that your app displays properly in the desired layout and renders strings as expected.
Note:
JET does not support the setting of the dir
attribute on individual HTML elements which would cause a page to show mixed directions. Also, if you programmatically change the dir
attribute after the page has been initialized, you must reload the page or refresh each JET component.
Set the Locale and Direction Dynamically
You can configure your app to change its locale and direction dynamically by setting a key value pair in the app’s local storage that your app’s RequireJS ojL10n plugin reads when you reload the app URL.
The image below shows an Oracle JET app configured to display a menu that displays a
department list when clicked and a date picker. By default, the app is set to the
en-US
locale. Both the menu and date picker display in English.
You can run the Oracle JET app shown in the image if you download the
JET-Localization.zip and run the Oracle JET CLI ojet restore
and ojet serve
commands in the directory where you extract the ZIP file.
The app also includes a button set which shows the United States of America,
France, Czech Republic, and Egypt flags. When the user clicks one of the flags, the app locale
is set to the locale represented by the flag: en-US
, fr-FR
,
cs-CZ
or ar-EG
.
Note:
The flags used in this example are for illustrative use only. Using national flags to select a UI language is strongly discouraged because multiple languages are spoken in one country, and a language may be spoken in multiple countries as well. In a real app, you can use clickable text instead that indicates the preferred language to replace the flag icons.
The image below shows the updated page after the user clicks the Egyptian flag.
Implementing this behavior requires you to make changes in the view, the viewModel, and the
appRootDir/src/main.js
file of your app. In the view code of the app, the
on-value-changed
property change listener attribute specifies a
setLang
function that is called when a user changes the selected
button.
<oj-buttonset-one . . . on-value-changed="[[setLang]]">
In the viewModel code of the app, this setLang
function determines what
locale the user selected and sets entries in Window.localStorage so that a user’s selection persists across
browser sessions. The final step in the function is to reload the current URL using location.reload().
setLang = (evt) => {
let newLocale = "";
let lang = evt.detail.value;
switch (lang) {
case "Čeština":
newLocale = "cs-CZ";
break;
case "Français":
newLocale = "fr-FR";
break;
case "عربي":
newLocale = "ar-EG";
break;
default:
newLocale = "en-US";
}
window.localStorage.setItem('mylocale',newLocale);
window.localStorage.setItem('mylang',lang);
location.reload();
};
To set the newly-selected locale in the ojL10n plugin of our app’s
appRootDir/src/main.js
file, we write the following entries that read the
updated locale value from local storage, and set it on the locale
specification in the ojL10n plugin. We also include a check that sets the direction to
rtl
if the specified locale is Egyptian Arabic
(ar-EG
).
(function () {
...
const localeOverride = window.localStorage.getItem("mylocale");
if (localeOverride) {
// Set dir attribute on <html> element.
// Note that other Arabic locales and Hebrew also use the rtl direction.
// Include a check here for other locales that your app must support.
if(localeOverride === "ar-EG"){
document.getElementsByTagName('html')[0].setAttribute('dir','rtl');
} else {
document.getElementsByTagName('html')[0].setAttribute('dir','ltr');
}
requirejs.config({
config: {
ojL10n: {
locale: localeOverride,
},
},
});
}
})();
...
For information about defining your own translation strings and adding them to the Oracle JET resource bundle, see Add Translation Bundles to Oracle JET.
When you use this approach to internationalize and localize your app, you must consider every component and element on your page and provide translation strings where needed. If your page includes a large number of translation strings, the page can take a performance hit.
Also, if SEO (Search Engine Optimization) is important for your app, be aware that search engines normally do not run JavaScript and access static text only.
Tip:
To work around issues with performance or SEO, you can add pages to your app that are already translated in the desired language. When you use pages that are already translated, the Knockout bindings are executed only for truly dynamic pieces.
Work with Currency, Dates, Time, and Numbers
When you use the converters included with Oracle JET, dates, times, numbers, and currency are automatically converted based on the locale settings. You can also provide custom converters if the Oracle JET converters are not sufficient for your app. For additional information about Oracle JET converters, see About Oracle JET Converters. For information about adding custom converters to your app, see Use Custom Converters in Oracle JET.
Work with Oracle JET Translation Bundles
Oracle JET includes a translation bundle that translates strings generated by Oracle JET components into all supported languages. Add your own translation bundle by merging it with the Oracle JET bundle.
About Oracle JET Translation Bundles
Oracle JET includes a translation bundle that translates strings generated by Oracle JET components into all supported languages. You can add your own translation bundle following the same format used in Oracle JET.
The Oracle JET translation bundle follows a specified format for the content and directory layout but also allows some leniency regarding case and certain characters.
Translation Bundle Location
The location of the Oracle JET translation bundle, which is named
ojtranslations.js
, is in the following directory:
libs/oj/v16.0.0/resources/nls/ojtranslations
Each supported language is contained in a directory under the nls
directory. The directory names use the following convention:
-
lowercase for the language sub-tag (
zh
,sr
, and so on.) -
title case for the script sub-tag (
Hant
,Latn
, and so on.) -
uppercase for the region sub-tag (
HK
,BA
, and so on.)
The language, script, and region sub-tags are separated by hyphens (-). The following image shows a portion of the directory structure.
Top Level Module
The ojtranslations.js
file contains the strings that Oracle JET
translates and lists the languages that have translations. This is the top-level module or
root bundle. In the root bundle, the strings are in English and are the runtime default
values when a translation isn’t available for the user’s preferred language.
Translation Bundle Format
Oracle JET expects the top-level root bundle and translations to follow a specified
format. The root bundle contains the Oracle JET strings with default translations and the
list of locales that have translations. //
indicates a comment.
define({
// root bundle
root: {
"oj-message":{
fatal:"Fatal",
error:"Error",
warning:"Warning",
info:"Info",
confirmation:"Confirmation",
"compact-type-summary":"{0}: {1}"
},
// ... contents omitted
},
// supported locales.
"fr-CA":1,
ar:1,
ro:1,
"zh-Hant":1,
nl:1,
it:1,
fr:1,
// ... contents omitted
tr:1,fi:1
});
The strings are defined in nested JSON objects so that each string is referenced by
a name with a prefix: oj-message.fatal
, oj-message.error
,
and so on.
The language translation resource bundles contain the Oracle JET string definitions with the translated strings. For example, the following code sample shows a portion of the French (Canada) translation resource bundle, contained in nls/fr-CA/ojtranslations.js
.
define({
"oj-message":{
fatal:"Fatale",
error:"Erreur",
warning:"Avertissement",
info:"Infos",
confirmation:"Confirmation",
"compact-type-summary":"{0}: {1}"
},
// ... contents omitted
});
When there is no translation available for the user's dialect, the strings in the base language bundle will be displayed. If there are no translations for the user's preferred language, the root language bundle, English, will be displayed.
Named Message Tokens
Some messages may contain values that aren't known until runtime. For example, in the message "User foo was not found in group bar"
, the foo
user and bar
group is determined at runtime. In this case, you can define {username}
and {groupname}
as named message tokens as shown in the following code.
"MyUserKey":"User{username}
was not found in group{groupname}
."
At runtime, the actual values are replaced into the message at the position of the
tokens by calling Translations.applyParameters()
with the key of the
message as the first argument and the parameters to be inserted into the translated pattern
as the second argument.
let parMyUserKey = { 'username': 'Foo', 'groupname': 'Test' };
let tmpString = Translations.applyParameters(MenuBundle.MyUserKey, parMyUserKey);
this.MyUserKey = Translations.getTranslatedString(tmpString);
Numeric Message Tokens
Alternatively, you can define numeric tokens instead of named tokens. For example, in the message "This item will be available in 5 days"
, the number 5
is determined at runtime. In this case, you can define the message with a message token of {0}
as shown in the following code.
"MyKey": "This item will be available in {0}
days."
A message can have up to 10 numeric tokens. For example, the message "Sales
order
{0}
has
{1}
items"
contains two numeric tokens.
When translated, the tokens can be reordered so that message token {1}
appears before message token {0}
in the translated string, if required by
the target language grammar. The JavaScript code that calls
applyParameters()
and getTranslatedString()
remains the
same no matter how the tokens are reordered in the translated string.
Tip:
Use named tokens instead of numeric tokens to improve readability and reuse.
Escape Characters in Resource Bundle Strings
The dollar sign, curly braces and square brackets require escaping if you want them to show up in the output. Add a dollar sign ($) before the characters as shown in the following table.
Escaped Form | Output |
---|---|
$$ | $ |
${ | { |
$} | } |
$[ | [ |
$] | ] |
For example, if you want your output to display [Date: {01/02/2020}, Time:
{01:02 PM}, Cost: $38.99, Book Name: JET developer's guide]
, enter the following
in your resource bundle string:
"productDetail": "$[Date: ${01/02/2020$}, Time: ${01:02 PM$}, Cost: $$38.99, Book Name: {bookName}$]"
You then use the Translations.applyParameters()
to return the string with
the escaped characters and substituted tokens, if any, to display in the UI, as shown in the
following example:
let parProductDetail = { bookName: "JET developer's guide"};
this.productDetail = Translations.applyParameters(MenuBundle.productDetail, parProductDetail);
Format Translated Strings
In some situations, you may want to apply formatting to strings in the resource
bundle to appear in the UI. Take, for example, a book title to which we may want to apply
the <i>
tag so that the book title renders using italics in the HTML
output. In this scenario, you might define the following entry in the resource
bundle(s):
// root bundle
// appRootDir/src/ts/resources/nls/menu.js
"FormatTranslatedString": "The <i>{booktitle}</i> describes how to develop Oracle JET apps"
And then use Oracle JET’s oj-bind-dom element to render the string in the UI, as in the following example:
<p><span>
<oj-bind-dom config="{{ formatTranslatedString() }}"></oj-bind-dom>
</span></p>
Note:
The oj-bind-dom element does not validate HTML input provided by an app for integrity or security violations. It is the app's responsibility to sanitize the input to prevent unsafe content from being added to the page.In our viewModel, we use Oracle JET’s HtmlUtils utility class to parse the string from the resource bundle.
. . .
import * as HtmlUtils from "ojs/ojhtmlutils";
import "ojs/ojbinddom";
. . .
class DashboardViewModel {
. . .
FormatTranslatedString: String;
. . .
formatTranslatedString = () => {
var parBookTitle = { 'booktitle': 'Oracle JET Developer Guide' };
let strTitle = Translations.applyParameters(MenuBundle.FormatTranslatedString, parBookTitle);
this.FormatTranslatedString = Translations.getTranslatedString(strTitle);
return {
view: HtmlUtils.stringToNodeArray(
`<span>${this.FormatTranslatedString}</span>`,
),
};
};
. . .