diff --git a/packages/localize/README.md b/packages/localize/README.md index 3bbf5965d..312a02735 100644 --- a/packages/localize/README.md +++ b/packages/localize/README.md @@ -4,11 +4,11 @@ The localization system helps to manage localization data split into locales and automate its loading. The loading of data tries to be as unobtrusive as possible for a typical workflow while providing a flexible and controllable mechanism for non-trivial use cases. -The formatting of data containing numbers and dates takes current locale into accout by using Intl MessageFormat specification. +The formatting of data containing numbers and dates takes current locale into account by using Intl MessageFormat specification. ## LocalizeManager -The core of the system is a `LocalizeManager` instance which is responsible for data loading and working with this data. +The core of the system is a `LocalizeManager` instance which is responsible for data loading and working with these data. It is exposed as a `localize` singleton instance. This ensures that the data can be cached in the single place and reused across different components and same component instances. @@ -25,12 +25,14 @@ Component developers will have a unified way to integrate with localization syst As a component developer you get: - unified data structure for different locales; -- promisified helper to load data; +- async helper to load data; - notification about page locale changes; - formatting using Intl MessageFormat; - mixins simplifying integration with components. -Data is split into locales. +### Storing data + +Data are split into locales. Typically the locale is an ES module which is by convention put into the `/translations` directory of your project. But there is also a possibility to serve data from an API using JSON format. Localization data modules for `my-hello-component` might look like these: @@ -80,11 +82,13 @@ The approach with ES modules is great because it allows to simply reuse basic lo }; ``` -To load this data the method `loadNamespace()` which returns a promise can be used. +### Loading data + +Async method `loadNamespace()` can be used to load these data. ```js localize.loadNamespace(namespace).then(() => { - // do smth when data is loaded + // do smth when data are loaded }); ``` @@ -106,7 +110,7 @@ localize.loadNamespace({ Usage of dynamic imports is recommended if you want to be able to create smart bundles later on for a certain locale. The module must have a `default` export as shown above to be handled properly. -But in fact you are not limited in the way how exactly the data is loaded. +But in fact you are not limited in the way how exactly the data are loaded. If you want to fetch it from some API this is also possible. ```js @@ -114,7 +118,7 @@ If you want to fetch it from some API this is also possible. localize.loadNamespace({ 'my-hello-component': async locale => { const response = await fetch( - `http://api.example.com/?namespace=my-hello-component&locale=${locale}`, + `https://api.example.com/?namespace=my-hello-component&locale=${locale}`, ); return response.json(); // resolves to the JSON object `{ greeting: 'Hallo {name}!' }` }, @@ -129,62 +133,79 @@ And this is there the second option comes in handy. ```js // using the regexp to match all component names staring with 'my-' localize.setupNamespaceLoader(/my-.+/, async (locale, namespace) => { - const response = await fetch(`http://api.example.com/?namespace=${namespace}&locale=${locale}`); + const response = await fetch(`https://api.example.com/?namespace=${namespace}&locale=${locale}`); return response.json(); }); -Promise.all([ +await Promise.all([ localize.loadNamespace('my-hello-component'); - localize.loadNamespace('my-goodbuy-component'); -]) + localize.loadNamespace('my-goodbye-component'); +]); ``` -Thus there is a loder function for all components having a certain prefix in a name. +Thus there is a loader function for all components having a certain prefix in a name. -The locale which will be loaded by default is accesed via the `localize.locale`. +### Page locale -The single source of truth for page's locale is ``. -At the same time the interaction should happen via `localize.locale` getter/setter to be able to notify and react to the change. +The single source of truth for page locale is ``. +Accessing it via `localize.locale` getter/setter is preferable. + +External tools like Google Chrome translate or a demo plugin to change a page locale (e.g. in the Storybook) can directly change the attribute on the `` tag. + +If `` is empty the default locale will be set to `en-GB`. + +#### Changing the page locale + +Changing the locale is simple yet powerful way to change the language on the page without reloading it: ```js localize.addEventListener('localeChanged', () => { - // do smth when data is loaded for a new locale + // do smth when data are loaded for a new locale }); -// changes locale, syncs to `` and fires the event above +// change locale (syncs to `` and fires the event above) localize.locale = 'es-ES'; ``` -If the locale is changed when a few namespaces have been already loaded for the previous one, all the data will be requested for existing namespaces for a new locale and only after that the event listeneres will be called. +If the locale is changed when a few namespaces have been already loaded for the previous one, all the data will be requested for existing namespaces for a new locale and only after that the event listeners will be called. This ensures that all data necessary for localization is loaded prior to rendering. If a certain namespace for a certain locale has been loaded previously, it will never be fetched again until the tab is reloaded in the browser. -When all necessary data is loaded and you want to show localized content on the page you need to format the data. +#### Fallback locale + +Due to the need to develop application code with not everything translated (yet) to all languages, it is good to have a fallback. +By default the fallback is `en-GB`, meaning that if some namespace does not have data for the current page locale, the `en-GB` will be loaded, making it an important foundation for all other locales data. +In addition to that the fallback is a necessary mechanism to allow some features of the browsers like Google Chrome translate to work and use the same original data for translations into all not officially supported languages. + +### Rendering data + +When all necessary data are loaded and you want to show localized content on the page you need to format the data. `localize.msg` comes into play here. It expects a key in the format of `namespace:name` and can also receive variables as a second argument. ```js _onNameChanged() { // inserts 'Hello John!' into the element with id="name" - const name = localize.msg('my-hello-component:greeting', { name: 'John' }); - this.$idNameElement.innerText = name; + const greeting = localize.msg('my-hello-component:greeting', { name: 'John' }); + this.shadowRoot.querySelector('#greeting').innerText = greeting; } ``` `localize.msg` uses [Intl MessageFormat implementation](https://www.npmjs.com/package/message-format) under the hood, so you can use all of its powerful features like placing a little bit different content based on number ranges or format a date according to the current locale. -### Using with LocalizeMixin +#### Rendering with LocalizeMixin -This mixin was created to significantly simplify integration with LionLitElement. -It provides several capabilities: +This mixin was created to significantly simplify integration with LitElement. +It provides many capabilities: - automatic loading of specified namespaces; -- life-cycle callbacks for localization events; -- alias `_m` for `localize.msg`; -- promisified alias `_msgAsync` for `localize.msg` resolved when data is loaded. +- control of the rendering flow via `waitForLocalizeNamespaces`; +- smart wrapper `msgLit` for `localize.msg`; +- life-cycle callbacks and properties for localization events; +- automatic update of DOM after locale was changed. ```js -class MyHelloComponent extends LocalizeMixin(LionLitElement) { +class MyHelloComponent extends LocalizeMixin(LitElement) { static get localizeNamespaces() { // using an explicit loader function return [ @@ -196,49 +217,46 @@ class MyHelloComponent extends LocalizeMixin(LionLitElement) { return ['my-hello-component', ...super.localizeNamespaces]; } - setupShadowDom() { - // setup initial DOM with ids for insertion points + static get waitForLocalizeNamespaces() { + // return false to unblock the rendering till the data are loaded + return true; + } + + render() { + return html` + + ${this.msgLit('my-hello-component:greeting')} + `; } onLocaleReady() { - // life-cycle callback - when data is loaded for initial locale + super.onLocaleReady(); + // life-cycle callback - when data are loaded for initial locale // (reaction to loaded namespaces defined in `localizeNamespaces`) } onLocaleChanged() { - // life-cycle callback - when data is loaded for new locale + super.onLocaleChanged(); + // life-cycle callback - when data are loaded for new locale // (reaction to `localize.locale` change and namespaces loaded for it) } onLocaleUpdated() { + super.onLocaleUpdated(); // life-cycle callback - when localized content needs to be updated // (literally after `onLocaleReady` or `onLocaleChanged`) // most DOM updates should be done here with the help of `this.msgLit()` and cached id selectors } + + async inYourOwnMethod() { + // before data are loaded or reloaded + await this.localizeNamespacesLoaded; + // after data are loaded or reloaded + } } ``` -Refer to demos to see a full example. - -### Using with LocalizeLitRenderMixin - -This is an extension of LocalizeMixin for usage with LionLitElement and LitRenderMixin. -It provides extra capabilities on top of LocalizeMixin: - -- smart wrapper `msg` for `localize.msg`; -- automatic update of DOM after locale was changed. - -With the help of this mixin writing a component can be as easy as defining namespaces in `localizeNamespaces` and writing lit-html template using `this.msgLit()`: - -```js -render() { - return html` -