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 RequireJS Translation Bundles to an Oracle JET App.
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/19.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 displays in the specified language. In this example, the error displays 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:
Oracle 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 that 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 the location.reload() method.
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 translation bundle, see Add RequireJS Translation Bundles to an Oracle JET App or Add ICU Translation Bundles to an Oracle JET Virtual DOM App, depending on the type of translation bundle that you use.
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 RequireJS Translation Bundles
Oracle JET includes a RequireJS translation bundle that translates strings generated by Oracle JET components into all supported languages. Add your own RequireJS translation bundle by merging it with the Oracle JET RequireJS translation bundle.
Note:
If you are creating a translation bundle for the first time in your Oracle JET app, we recommend that you use the other type of translation bundle (ICU translation bundle) that Oracle JET supports. For more information, see Work with ICU Translation Bundles in an Oracle JET Virtual DOM App.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/v19.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.
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 the Translations.applyParameters()
method 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 code that calls the applyParameters()
and
getTranslatedString()
methods 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()
method 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
"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>
Caution:
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>`,
),
};
};
. . .
Work with ICU Translation Bundles in an Oracle JET Virtual DOM App
Oracle JET supports ICU message format translation bundles that enable Oracle JET apps to provide localized UI strings and support runtime substitution using placeholders.
International Components for Unicode (ICU) is a set of libraries that provides support for the internationalization of text, numbers, dates, times, currencies, and other locale-sensitive data in formatting user-visible strings (messages). To read more about how ICU formats messages, see its documentation.
To use Oracle JET’s support for ICU message format translation bundles, run the Oracle JET CLI’s add translation
command in the root directory of your Oracle JET project. This installs the NPM package (@oracle/oraclejet-icu-l10n
) that you need to generate the runtime ICU translation bundles. It also creates a resources
directory with ICU translation bundles in your project’s src
directory with the following files to help you get started:
appRootDir\src\resources
\---nls
| translationBundle.json
|
\---de
translationBundle.json
Finally, it updates your project’s oraclejetconfig.json
file with the following properties to facilitate generation of the runtime ICU translation bundles when you run the Oracle JET CLI’s build
or serve
commands:
. . .
"translationIcuLibraries": "@oracle/oraclejet-icu-l10n",
. . .
"buildICUTranslationsBundle": true,
"translation": {
"type": "icu",
"options": {
"rootDir": "./src/resources/nls",
. . .
"supportedLocales": "de"
}
}
. . .
When you build your Oracle JET project, @oracle/oraclejet-icu-l10n parses the ICU translation bundles (for example, translationBundle.json) and converts them to TypeScript modules (translationBundle.ts using our example). These TypeScript modules are the runtime ICU translation bundles. They return an object with functions to get the localized string for a message key. The function takes parameters to format the message. For plurals, the parameter is always a number. Note that the same message can have multiple parameters (for example, a number for the plural rule and a string for a text placeholder). The parameter types will be described by TypeScript.
As you’ll notice from the example ICU translation bundle that the add translation
command creates, translation bundle files must have a .JSON
file extension and follow the JSON format. Keys for translation bundle resources should always appear at the top level. Creating sub-objects for translated resources is not supported. Component authors may use some convention-based character like '_ ' in the key name (for example, "input_messages_error"
) to indicate the intended usage of the resource.
"input_message_error": "Error",
"input_message_warning": "Warning"
The value is a string that may contain placeholders.
Note:
The examples that follow are formatted as multi-line strings for readability. Actual values in an ICU translation bundle must be single-line strings, as required by the JSON specification. Enable word wrapping in your editor to view them conveniently.Keys starting with the @
character specify metadata. If a
metadata key starts with @@
, the metadata is considered global. Otherwise,
the metadata is associated with the key whose name follows the @
character:
"input_message_error": "Error: {MESSAGE}",
"@input_message_error":
"placeholders": {
"MESSAGE":
"type": "text",
"description": "translated error message"
}
},
"description": "Error with an embedded message"
}
Metadata is optional. Supply it in the following cases:
-
The usage of the text placeholder is going to be unclear to the translator, and supplying an extra description in the metadata is going to help with translating the rest of the message.
-
A particular placeholder is not being used in the message included in the root/development bundle, but the corresponding parameter should be settable for some other locales.
ICU translation bundle's build process derives parameter type information from the message in the root/development bundle. Type information for any parameters used only in other locales will have to come from metadata.
The reserved special characters within the translated resource are pound #
and curly braces ({
, }
). Use the ASCII apostrophe ('
, U+0027) around these characters so that they appear in the message. Use double ASCII apostrophe if a single ASCII apostrophe should appear in the message, but only when it is immediately followed by a pound character or a curly brace. We recommend that you use the real apostrophe (single quote) character ’
(U+2019) for human-readable text, and use the ASCII apostrophe '
(U+0027) only as an escape character. This avoids having to use the double ASCII apostrophe.
If a particular part of a message should not be translated, provide it as a parameter for a text placeholder.
Format Placeholders Tokens
Placeholder tokens enable dynamic content generation by substituting values into translated messages.
Text
Use text placeholders to perform simple token substitution. Avoid the use of numeric placeholder names (0
, 1
, and so on).
"input_message_error": "Error: {MESSAGE}"
In the previous example, MESSAGE
identifies a named parameter to insert into the translated string.
Plural
Plural placeholders define translated messages intended for either certain groups of numbers or for exact numbers. You can think of them as switch statements.
"shopping_card_items": "You have {item_count, plural, offset:0
=0 {no items}
one {# item}
other {# items}
} in your cart"
To understand the previous example, consider the following:
item_count
is the name of the parameter whose numeric value is used to for matching the message.plural
is the type of the placeholder.=0
will match exactly 0.one
is one of the language-specific categories that are matched against the category of the parameter's value. For example,1
,21
and31
will all be in the categoryone
for Ukrainian. Other categories arezero
,two
,few
,many
. Note that the match for an exact number (=1
) has a higher precedence than a category-based match.offset
is the number that will be subtracted from the parameter's value before a category-based rule is applied. The default is0
. Note thatoffset
is not used in exact matches (=1
, and so on).other
is just like adefault
label in a switch statement. That is, it is a catch all category. Note thatother
is required in every plural placeholder.#
will be substituted with the actual numeric value after the offset is subtracted.
Select
Select placeholders specify translated messages that will be chosen based on the value of a string parameter. Just like with the plural selector, you can think of them as a switch statement.
"response_message": "{gender, select,
male {He}
female {She}
other {They}
} will respond shortly."
In the example above, gender
is the name of the parameter whose string value is used to match the translated message. Possible parameter values are male
and female
. Similar to the plural placeholder, other
is a special category acting like a default
label in a switch
statement. Note that other
is required in every select placeholder.
Nesting Select and Plural Placeholders
"invite_message": "{gender_of_host, select,
female {
{num_guests, plural, offset:1
=0 {{host} does not give a party.}
=1 {{host} invites {guest} to her party.}
=2 {{host} invites {guest} and one other person to her party.}
other {{host} invites {guest} and # other people to her party.}}}
male {
{num_guests, plural, offset:1
=0 {{host} does not give a party.}
=1 {{host} invites {guest} to his party.}
=2 {{host} invites {guest} and one other person to his party.}
other {{host} invites {guest} and # other people to his party.}}}
other {
{num_guests, plural, offset:1
=0 {{host} does not give a party.}
=1 {{host} invites {guest} to their party.}
=2 {{host} invites {guest} and one other person to their party.}
other {{host} invites {guest} and # other people to their party.}}}}"
In the example above, plural placeholders are nested in a select placeholder. The result is that plural rules are applied for each gender-specific group of messages. Note that the value of the offset in plural rules is used only for printing the number of guests, not counting the main guest identified by the guest
parameter (substituting #
).
Set Up an Oracle JET Virtual DOM App to Create ICU Translation Bundles
To set up an Oracle JET virtual DOM app to create and use ICU translation bundles, you run the add translation
command and edit the properties that this command inserts in your app's oraclejetconfig.json
file.
To set up your Oracle JET app to support ICU translation bundles:
Add ICU Translation Bundles to an Oracle JET Virtual DOM App
Add ICU translation bundles to your Oracle JET virtual DOM app with the custom strings that your app UI needs and the translations that you want your app to support.
To add ICU translation bundles to your Oracle JET app:
Generate Runtime ICU Translation Bundles
Once you have created the .JSON
files with the UI strings for the locales that your Oracle JET virtual DOM app supports, you need to build the runtime ICU translation bundles to a location from where your app retrieves the UI strings at runtime.
Change to your app's top-level directory, open a terminal window, and enter the following command:
npx ojet build
This creates the runtime ICU translation bundles (.TS files) and the supportedLocales.ts file in the directory specified by the outDir property in the oraclejetconfig.json file. By default, it is ./src/resources/nls.
appRootDir/src/resources
\---nls
| supportedLocales.ts
| translationBundle.json
| translationBundle.ts
|
+---de
| translationBundle.json
| translationBundle.ts
Now that you have generated the runtime ICU translation bundles, you can use the UI strings that they contain in your Oracle JET virtual DOM app and in your VComponents.
Use an ICU Translation Bundle in an Oracle JET Virtual DOM App Component
Once you generate the runtime ICU translation bundles, you use the UI strings from the generated bundles.
A runtime ICU translation bundle defines an object with function(s) to get the UI string value for the specified message key. The function can take parameters to format the message. For plurals, the parameter is always a number.
Note:
The same message can have multiple parameters. For example, a number for the plural rule and a string for a text placeholder. TypeScript describes the parameter types.Your app dynamically loads the translation bundles like any other Typescript module. You first determine what locale your app uses. Once you know the desired locale, you find the best match out of the supported locales that the ./appRootDir/src/resources-dist/supportedLocales.ts
file lists. For example, the add translation
command adds support for de
by default:
export default ["de"];
The following code sample demonstrates how to load the UI string for the label-hint
attribute value of the oj-input-text
element in the appRootDir/src/components/content/index.tsx
file of a virtual DOM app. It first imports the runtime ICU translation bundle and the supported locales. A Preact useEffect
hook loads the imported ICU translation bundles and if one is found to match the preferred locale, it is used. If not, the ICU translation bundle for the German locale is used (de
).
import { h } from "preact";
import { BundleType as ICUBundleType } from "../../resources/nls/translationBundle";
import supportedLocales from "../../resources/nls/supportedLocales";
import { useState, useEffect } from "preact/hooks";
import "ojs/ojlabel";
import "ojs/ojinputtext";
import "ojs/ojformlayout";
export function Content() {
const [ICUBundle, setICUBundle] = useState<ICUBundleType>();
// Load runtime ICU translation bundle in Preact's useEffect hook.
useEffect(() => {
_loadTranslationBundle().then((bundle) => setICUBundle(bundle));
}, []);
return (
<div class="oj-web-applayout-max-width oj-web-applayout-content">
<div id="icu-app">
<p>
Render UI string from the <code>./src/resources/nls/de</code> runtime
ICU translation bundle.{" "}
</p>
{ICUBundle && (
<oj-form-layout id="ofl1" max-columns="1" direction="column">
<oj-input-text
id="input"
value=" "
labelHint={ICUBundle.greeting()}
labelEdge="inside"
></oj-input-text>
</oj-form-layout>
)}
</div>
</div>
);
}
async function _loadTranslationBundle(): Promise<ICUBundleType> {
const preferredLocale = navigator.languages[0];
const localeToLoad = _matchTranslationBundle(
preferredLocale,
supportedLocales
);
// The ICU translation bundle name (translationBundle in this example) derives
// its name from the value that you specify for the bundleName property
// in the oraclejetconfig.json file.
const module = await import(
`../../resources/nls/${localeToLoad}/translationBundle`
);
return module.default;
}
function _matchTranslationBundle(
preferredLocale: string,
supportedLocales: string[]
) {
let match = null;
const supportedLocaleSet = new Set(supportedLocales);
if (supportedLocaleSet.has(preferredLocale)) {
match = preferredLocale;
} else {
match = _findPartialMatch(preferredLocale, supportedLocaleSet);
}
// Normally, the fallback locale would be 'en-US', but for
// this example, we're using 'de'.
return match || 'de';
}
function _findPartialMatch(locale: string, supportedLocales: Set<string>) {
let match = null;
const sep = "-";
const parts = locale.split(sep);
while (match === null && parts.length > 1) {
parts.pop();
const partial = parts.join(sep);
if (supportedLocales.has(partial)) {
match = partial;
}
}
return match;
}
Use an ICU Translation Bundle in an Oracle JET VComponent
The steps to create and use ICU translation bundles in a VComponent are the same as those for a virtual DOM app. That is, you create ICU translation bundle source files (.JSON
) in a directory location that matches the pattern specified by the rootDir
property in the oraclejetconfig.json
file. By default, this is "./src/resources/nls"
. When you build the Oracle JET project that contains the VComponent, the runtime ICU translation bundles are generated to the location specified by the outDir
property. Again, the default is "./src/resources/nls"
. One additional property in the oraclejetconfig.json
file to be aware of is componentBundleName
. This specifies the naming pattern for the VComponent’s ICU translation bundle so that Oracle JET can generate the runtime ICU translation bundle. Its default value is "${componentName}-strings.json"
.
So, assume for example, that you create a VComponent named translation-icu
in your Oracle JET virtual DOM app:
npx ojet create component translation-icu --vcomponent
This leads to the creation of a translation-icu
directory within your virtual DOM app’s project file and this directory includes resources
and nls
sub-directories as follows:
appRootDir/src/components/translation-icu/resources/nls
Within the nls
sub-directory, you create the ICU translation bundles as needed for the locales that you are going to support:
appRootDir/src/components/translation-icu/resources/nls
| translation-icu-strings.json
+---de
| translation-icu-strings.json
Once you have generated the runtime ICU translation bundles by building the Oracle JET virtual DOM app that contains the VComponent, you can use UI strings from the runtime ICU translation bundle in your VComponent. The following code snippets demonstrate how you import and reference a UI string from the ICU translation bundle. For brevity, the full VComponent code and helper functions that are unchanged from the code sample in the previous section for the virtual DOM app have been omitted.
. . .
import "css!./translation-icu-styles.css";
import { useState, useEffect } from "preact/hooks";
import "ojs/ojinputtext";
import "ojs/ojformlayout";
import { BundleType as ICUBundleType } from "./resources/nls/translation-icu-strings";
import supportedLocales from "./resources/nls/supportedLocales";
. . .
function TranslationIcuImpl() {
const [ICUBundle, setICUBundle] = useState<ICUBundleType>();
// Load ICU translation bundle in Preact's useEffect hook.
useEffect(() => {
_loadTranslationBundle().then((bundle) => setICUBundle(bundle));
}, []);
return (
<div>
<p>Render the string from the ICU translation bundle</p>
{ICUBundle ? (
<oj-form-layout id="ofl1" max-columns="1" direction="column">
<oj-input-text
id="input"
value=" "
labelHint={ICUBundle.greeting()}
labelEdge="inside"
></oj-input-text>
</oj-form-layout>
) : (
<p>ICU translation bundle did not load</p>
)}
</div>
);
}
// Helper functions for ICU translation bundle
async function _loadTranslationBundle(): Promise<ICUBundleType> {
const preferredLocale = navigator.languages[0];
const localeToLoad = _matchTranslationBundle(
preferredLocale,
supportedLocales
);
// The component ICU translation bundle name
// (translation-icu-strings in this example) derives
// its name from the VComponent name and other values
// that you specify for the componentBundleName property
// in the oraclejetconfig.json file.
const module = await import(
`./resources/nls/${localeToLoad}/translation-icu-strings`
);
return module.default;
}
function _matchTranslationBundle(
. . .
// Omitted for brevity. See previous section that includes full code
// for this function
function _findPartialMatch(locale: string, supportedLocales: Set<string>) {
. . .
// Omitted for brevity. See previous section that includes full code
// for this function
export const TranslationIcu: ComponentType<ExtendGlobalProps<
ComponentProps<typeof TranslationIcuImpl>
>> = registerCustomElement("translation-icu", TranslationIcuImpl);