i18n & localization
Although any localization solution can be used, Open Cells offers a localization helper that can be used directly on Web Components or applied as a mixin. It relies in two principles:
- Components that contain messages inside them that are language-dependant should use the
t
function to manage them. - The application should provide the final texts or resources (locales) in JSON format and manage the internationalization configuration.
This helper uses Format.JS Intl MessageFormat to help applying specific formats to translations based on language and other parameters.
Installation
Permalink to “Installation”Any component using localization should install the package as a dependency:
npm install --save @open-cells/localize
Usage & locales files
Permalink to “Usage & locales files”Using t
in components
Permalink to “Using t in components” The t
function is the main piece provided by this module. It can be directly imported from the module and used wherever needed. Example:
import {LitElement, html} from 'lit';
import {t, updateWhenLocaleResourcesChange} from '@open-cells/localize';
class ExampleComponent extends LitElement {
constructor() {
super();
updateWhenLocaleResourcesChange(this);
}
render() {
return html` <p>${t('simple-key')}</p> `;
}
}
The t
function will look for simple-key
in the available locale resources and for the current application language. If found, the value will be passed to Intl MessageFormat (and the result will be cached for future uses), then returned. If the key is not found, t
will return null
.
updateWhenLocaleResourcesChanges
is a helper function that adds a Lit Controller to the component that listens for global intl status change events and triggers a component update accordingly. Thus, if the application language is updated (from 'en-US' to 'es-ES', for example), the component will be automatically re-rendered and its messages will be updated to the new language.
Note: if no resources are loaded when updateWhenLocaleResourcesChanges
is called, it will automatically try to fetch the resources using the current configuration.
Locales files
Permalink to “Locales files”By default, the localization helper will try to fetch a locales/locales.json
file from the current path. As it's explained below, this can be easily configured. Anyway, the JSON file should contain entries for each language with all keys in components. Example:
{
"en": {
"simple-key": "Value",
"key-a": "Value A",
"key-b": "Value B"
},
"en-US": {
"key-b": "Value B for US"
},
"es": {
"simple-key": "Valor",
"key-a": "Valor A",
"key-b": "Valor B"
},
"es-ES": {
"key-b": "Valor B para ES"
},
"es-MX": {
"key-b": "Valor B para MX"
}
}
Base language and region codes
Permalink to “Base language and region codes”When the language code used in the application includes a region code (like 'en-US' or 'es-ES'), t
will look for the key in the locale resources for that specific code first. If the key is not found, then it will look for it in the base language code (like 'en' or 'es'), without the region part. This allows to define a base set of translations for a language and then override them with specific translations for a region.
In the following example, when translating 'key-b' for 'en-US', the value 'Value B for US' will be used. But when translating 'key-b' for 'en-GB', the value 'Value B' (retrieved from 'en' base language) will be used.
{
"en": {
"key-a": "Value A",
"key-b": "Value B"
},
"en-US": {
"key-b": "Value B for US"
}
}
Translation fallbacks
Permalink to “Translation fallbacks”When there are no resources loaded, or a specific key is not found in the locale resources for the current language, null
will be returned. So, it's easy to provide fallbacks for not-found translations. For example:
render() {
return html`
<p>${t('simple-key') ?? 'This is a fallback text'}</p>
`;
}
Using the Nullish coalescing operator (??) ensures that if the key is found but its value is an empty string, it does not get overriden by the fallback.
Translation options
Permalink to “Translation options”The values retrieved from locales will be passed to Intl MessageFormat. This allows to use parameters and gives a lot of flexibility to the system. For example, a component could need to show a message with a dynamic value, but adjusting the text message based on that value. Example:
render() {
return html`
<p>${t('simple-key', { numItems: this.items })}</p>
`;
}
{
"en": {
"simple-key": "You have {numItems, plural, =0 {no elements.} =1 {one element.} other {# elements.}}"
},
"es": {
"simple-key": "{numItems, plural, =0 {No tienes elementos.} =1 {Tienes un elemento.} other {Tienes # elementos.}}"
}
}
You can pass other parameters for the available formatting options as documented in Intl MessageFormat. Examples:
{
"en": {
"html-key": "This is a <b>value</b>",
"simple-key-plural": "You have {numItems, plural, =0 {no elements.} =1 {one element.} other {# elements.}}",
"simple-key-date": "Starts on {exampleDate, date, medium}",
"simple-key-intl-date-lang-demo": "The date is {exampleDate, date, long}"
}
}
render() {
return html`
<p>${t('html-key', {'b': chunks => html`<strong>${chunks}</strong>`})}</p>
<p>${t('simple-key-plural', { numItems: this.items })}</p>
<p>${t('simple-key-date', {'exampleDate': new Date()})}</p>
<p>${t('simple-key-intl-date-lang-demo', {'exampleDate': new Date()})}</p>
`;
}
Mixin
Permalink to “Mixin”The package offers a mixin, LocalizeMixin
, which automatically imports the t
method, invokes updateWhenLocaleResourcesChange
, and assigns t
to a method in the component interface. Example:
import {LitElement, html} from 'lit';
import {LocalizeMixin} from '@open-cells/localize';
class ExampleComponent extends LocalizeMixin(LitElement) {
render() {
return html` <p>${this.t('simple-key')}</p> `;
}
}
You can also combine the mixin with the imported t
method:
import {LitElement, html} from 'lit';
import {LocalizeMixin, t} from '@open-cells/localize';
class ExampleComponent extends LocalizeMixin(LitElement) {
render() {
return html` <p>${t('simple-key')}</p> `;
}
}
The mixin also adds a _intlConfig
state to the component, which will be updated when the global intl configuration changes. This property allows to get the current state of the intl configuration in the component. Example:
import { LitElement, html } from 'lit';
import { LocalizeMixin } from '@open-cells/localize';
class ExampleComponent extends LocalizeMixin(LitElement) {
willUpdate(props) {
super?.willUpdate(props);
if (props.has('_intlConfig')) {
this._globalIntlLang = this._intlConfig.lang;
}
}
render() {
return html`
<p>${this.t('simple-key')}</p>
<p>Current language is: ${this._globalIntlLang}</p>
`;
}
}
This is useful, for instance, when a component has other language-dependant features; for example, if the component needs to show the first day of the week (which could be sunday or monday, generally).
Configuration
Permalink to “Configuration”The global configuration is stored in the module state. The module provides a set of methods for managing and updating the configuration.
setLang
updates the current language used in the application. It will also update the document.documentElement.lang
attribute.
import {setLang} from '@open-cells/localize';
setLang('es-ES');
Note: intl lang defaults to the <html>
tag lang
attribute. In order to define the initial language in your application, you can just set that attribute. For example:
<html lang="es">
...
</html>
setFormats
allows to set formats for Intl MessageFormat.
import {setFormats} from '@open-cells/localize';
setFormats({
number: {
currency: {
style: 'currency',
currency: 'USD',
currencyDisplay: 'symbol',
},
},
});
If setWarnOnMissingKeys
is invoked with true
, it will show a console warning when a key is not found in the locale resources. This can be useful for debugging, in order to locate missing translations in your app.
import {setWarnOnMissingKeys} from '@open-cells/localize';
setWarnOnMissingKeys(true);
Resources & initialization
Permalink to “Resources & initialization”The path for locale resources is set using two properties: url
and localesHost
. They default to locales/locales.json
and .
respectively, so the default path for locales is ./locales/locales.json.
The resources are not automatically fetched when the module is loaded. This allows the user to modify localesHost
and url
without triggering any requests, using the setLocalesHost
and setUrl
methods. When the resources are needed, the requestResources
method can be used to fetch them.
import {setUrl, setLocalesHost, requestResources} from '@open-cells/localize';
setLocalesHost('base/path/for/app');
setUrl('locales/app-locales.json');
// Manually retrieve resources
requestResources();
Alternatively, instead of manually invoking requestResources
, the first component that uses updateWhenLocaleResourcesChange
will automatically trigger the request.
Thus, apps should set all their initial configuration parameters (localesHost
, url
, lang
...) before any other component is loaded; then, it could just wait for the first component using updateWhenLocaleResourcesChange
to trigger the request.
After resources are retrieved, any new modification made to localesHost
or url
will automatically trigger a new request. The new resources will override the previous ones. You can set useBundles
to true to merge the new resources with the previous ones instead of replacing them.
import {
setUrl,
setLocalesHost,
requestResources,
setUseBundles,
} from '@open-cells/localize';
setUseBundles(true);
setLocalesHost('base/path/for/app');
setUrl('locales/app-locales.json');
// Manually retrieve resources
requestResources();
setTimeout(() => {
setUrl('locales/other-locales.json');
}, 3000);
Events
Permalink to “Events”The module fires events on window
to notify about changes.
app-localize-resources-loaded
is fired when a locale resources file is loaded and is available for use.app-localize-resources-error
is fired when an error occurs while loading resources.app-localize-status-change
is fired when the intl configuration changes; for example, when the language is updated fromen
toes
, when the formats are updated, resources are loaded, etc.
MutationObserver on document lang
Permalink to “MutationObserver on document lang”The default lang
of the module will be set to the document.documentElement.lang
property, which matches the lang
attribute in <html>
tag. When loaded, the intl module automatically sets a MutationObserver on the lang
attribute of <html>
: when it changes, the module will update the lang
config property accordingly.