• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..21-May-2021-

angular/H21-May-2021-65

react/H21-May-2021-65

scripts/H03-May-2022-

src/H21-May-2021-3328

target/H21-May-2021-

tasks/H03-May-2022-

GUIDELINE.mdH A D21-May-202117.4 KiB458345

README.mdH A D21-May-202115.2 KiB440355

package.jsonH A D21-May-20211.1 KiB3938

tslint.ymlH A D21-May-202131 32

README.md

1# I18n
2
3Kibana relies on several UI frameworks (ReactJS and AngularJS) and
4requires localization in different environments (browser and NodeJS).
5Internationalization engine is framework agnostic and consumable in
6all parts of Kibana (ReactJS, AngularJS and NodeJS). In order to simplify
7internationalization in UI frameworks, the additional abstractions are
8built around the I18n engine: `react-intl` for React and custom
9components for AngularJS. [React-intl](https://github.com/yahoo/react-intl)
10is built around [intl-messageformat](https://github.com/yahoo/intl-messageformat),
11so both React and AngularJS frameworks use the same engine and the same
12message syntax.
13
14## Localization files
15
16Localization files are JSON files.
17
18Using comments can help to understand which section of the application
19the localization key is used for. Also `namespaces`
20are used in order to simplify message location search. For example, if
21we are going to translate the title of `/management/sections/objects/_objects.html`
22file, we should use message path like this: `'management.objects.objectsTitle'`.
23
24Each Kibana plugin has a separate folder with translation files located at
25```
26{path/to/plugin}/translations/{locale}.json
27```
28
29where `locale` is [ISO 639 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
30
31For example:
32```
33src/legacy/core_plugins/kibana/translations/fr.json
34```
35
36The engine scans `x-pack/plugins/*/translations`, `src/core_plugins/*/translations`, `plugins/*/translations` and `src/ui/translations` folders on initialization, so there is no need to register translation files.
37
38The engine uses a `config/kibana.yml` file for locale resolution process. If locale is
39defined via `i18n.locale` option in `config/kibana.yml` then it will be used as a base
40locale, otherwise i18n engine will fall back to `en`. The `en` locale will also be used
41if translation can't be found for the base non-English locale.
42
43One of our technical requirements is to have default messages in the templates
44themselves, and those messages will always be in English, so we don't have to keep
45`en.json` file in repository. We can generate that file from `defaultMessage`s
46defined inline.
47
48__Note:__ locale defined in `i18n.locale` and the one used for translation files should
49match exactly, e.g. `i18n.locale: zh` and `.../translations/zh-CN.json` won't match and
50default English translations will be used, but `i18n.locale: zh-CN` and`.../translations/zh-CN.json`
51or `i18n.locale: zh` and `.../translations/zh.json` will work as expected.
52
53__Note:__ locale should look like `zh-CN` where `zh` - lowercase two-letter or three-letter ISO-639 code
54and `CN` - uppercase two-letter ISO-3166 code (optional).
55[ISO-639](https://www.iso.org/iso-639-language-codes.html) and [ISO-3166](https://www.iso.org/iso-3166-country-codes.html) codes should be separated with `-` character.
56
57## I18n engine
58
59I18n engine is the platform agnostic abstraction that helps to supply locale
60data to UI frameworks and provides methods for the direct translation.
61
62Here is the public API exposed by this engine:
63
64- `addMessages(messages: Map<string, string>, [locale: string])` - provides a way to register
65translations with the engine
66- `getMessages()` - returns messages for the current language
67- `setLocale(locale: string)` - tells the engine which language to use by given
68language key
69- `getLocale()` - returns the current locale
70- `setDefaultLocale(locale: string)` - tells the library which language to fallback
71when missing translations
72- `getDefaultLocale()` - returns the default locale
73- `setFormats(formats: object)` - supplies a set of options to the underlying formatter.
74For the detailed explanation, see the section below
75- `getFormats()` - returns current formats
76- `getRegisteredLocales()` - returns array of locales having translations
77- `translate(id: string, { values: object, defaultMessage: string, description: string })` –
78translate message by id. `description` is optional context comment that will be extracted
79by i18n tools and added as a comment next to translation message at `defaultMessages.json`.
80- `init(messages: Map<string, string>)` - initializes the engine
81
82#### I18n engine internals
83
84The engine uses the ICU Message syntax and works for all CLDR languages which
85have pluralization rules defined. It's built around `intl-messageformat` package
86which exposes `IntlMessageFormat` class. Messages are provided into the constructor
87as a string message, or a pre-parsed AST object.
88
89```js
90import IntlMessageFormat from 'intl-messageformat';
91
92const msg = new IntlMessageFormat(message, locales, [formats]);
93```
94
95The string `message` is parsed, then stored internally in a
96compiled form that is optimized for the `format()` method to
97produce the formatted string for displaying to the user.
98
99```js
100const output = msg.format(values);
101```
102
103`formats` parameter in `IntlMessageFormat` constructor allows formatting numbers
104and dates/times in messages using `Intl.NumberFormat` and `Intl.DateTimeFormat`,
105respectively.
106
107```js
108const msg = new IntlMessageFormat('The price is: {price, number, USD}', 'en-US', {
109  number: {
110    USD: {
111      style   : 'currency',
112      currency: 'USD',
113    },
114  },
115});
116
117const output = msg.format({ price: 100 });
118
119console.log(output); // => "The price is: $100.00"
120```
121
122In this example, we're defining a USD number format style which is passed to
123the underlying `Intl.NumberFormat` instance as its options.
124[Here](https://github.com/yahoo/intl-messageformat/blob/master/src/core.js#L62)
125you can find default format options used as the prototype of the formats
126provided to the constructor.
127
128Creating instances of `IntlMessageFormat` is expensive.
129[Intl-format-cache](https://github.com/yahoo/intl-format-cache)
130library is simply to make it easier to create a cache of format
131instances of a particular type to aid in their reuse. Under the
132hood, this package creates a cache key based on the arguments passed
133to the memoized constructor.
134
135```js
136import memoizeIntlConstructor from 'intl-format-cache';
137
138const getMessageFormat = memoizeIntlConstructor(IntlMessageFormat);
139```
140
141## Vanilla JS
142
143`Intl-messageformat` package assumes that the
144[Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl)
145global object exists in the runtime. `Intl` is present in all modern
146browsers and Node.js 0.10+. In order to load i18n engine
147in Node.js we should simply `import` this module (in Node.js, the
148[data](https://github.com/yahoo/intl-messageformat/tree/master/dist/locale-data)
149for all 200+ languages is loaded along with the library) and pass the translation
150messages into `init` method:
151
152```js
153import { i18n } from '@kbn/i18n';
154
155i18n.init(messages);
156```
157
158One common use-case is that of internationalizing a string constant. Here's an
159example of how we'd do that:
160
161```js
162import { i18n } from '@kbn/i18n';
163
164export const HELLO_WORLD = i18n.translate('hello.wonderful.world', {
165  defaultMessage: 'Greetings, planet Earth!',
166});
167```
168
169One more example with a parameter:
170
171```js
172import { i18n } from '@kbn/i18n';
173
174export function getGreetingMessage(userName) {
175  return i18n.translate('hello.wonderful.world', {
176    defaultMessage: 'Greetings, {name}!',
177    values: { name: userName },
178    context: 'This is greeting message for main screen.'
179  });
180}
181```
182
183We're also able to use all methods exposed by the i18n engine
184(see [I18n engine](#i18n-engine) section above for more details).
185
186## React
187
188[React-intl](https://github.com/yahoo/react-intl) library is used for internalization
189React part of the application. It provides React components and an API to format
190dates, numbers, and strings, including pluralization and handling translations.
191
192React Intl uses the provider pattern to scope an i18n context to a tree of components.
193`IntlProvider` component is used to setup the i18n context for a tree. After that we
194are able to use `FormattedMessage` component in order to translate messages.
195`IntlProvider` should wrap react app's root component (inside each react render method).
196
197In order to translate messages we need to use `I18nProvider` component that
198uses I18n engine under the hood:
199
200```js
201import React from 'react';
202import ReactDOM from 'react-dom';
203import { I18nProvider } from '@kbn/i18n/react';
204
205ReactDOM.render(
206  <I18nProvider>
207    <RootComponent>
208      ...
209    </RootComponent>
210  </I18nProvider>,
211  document.getElementById('container')
212);
213```
214
215After that we can use `FormattedMessage` components inside `RootComponent`:
216```jsx
217import React, { Component } from 'react';
218import { FormattedMessage } from '@kbn/i18n/react';
219
220class RootComponent extends Component {
221  constructor(props) {
222    super(props);
223
224    this.state = {
225      name: 'Eric',
226      unreadCount: 1000,
227    };
228  }
229
230  render() {
231    const {
232      name,
233      unreadCount,
234    } = this.state;
235
236    return (
237      <p>
238        <FormattedMessage
239          id="welcome"
240          defaultMessage="Hello {name}, you have {unreadCount, number} {unreadCount, plural,
241            one {message}
242            other {messages}
243          }"
244          values={{name: <b>{name}</b>, unreadCount}}
245        />
246        ...
247      </p>
248    );
249  }
250}
251```
252
253Optionally we can pass `description` prop into `FormattedMessage` component.
254This prop is optional context comment that will be extracted by i18n tools
255and added as a comment next to translation message at `defaultMessages.json`
256
257**NOTE:** To minimize the chance of having multiple `I18nProvider` components in the React tree, try to use `I18nProvider` only to wrap the topmost component that you render, e.g. the one that's passed to `reactDirective` or `ReactDOM.render`.
258
259### FormattedRelative
260
261`FormattedRelative` expects several attributes (read more [here](https://github.com/yahoo/react-intl/wiki/Components#formattedrelative)), including
262
263- `value` that can be parsed as a date,
264- `formats` that should be one of `'years' | 'months' | 'days' | 'hours' | 'minutes' | 'seconds'` (this options are configured in [`formats.ts`](./src/core/formats.ts))
265-  etc.
266
267If `formats` is not provided then it will be chosen automatically:\
268`x seconds ago` for `x < 60`, `1 minute ago` for `60 <= x < 120`, etc.
269
270```jsx
271<FormattedRelative
272  value={Date.now() - 90000}
273  format="seconds"
274/>
275```
276Initial result: `90 seconds ago`
277```jsx
278<FormattedRelative
279  value={Date.now() - 90000}
280/>
281```
282Initial result: `1 minute ago`
283
284### Attributes translation in React
285
286React wrapper provides an ability to inject the imperative formatting API into a React component via its props using `injectI18n` Higher-Order Component. This should be used when your React component needs to format data to a string value where a React element is not suitable; e.g., a `title` or `aria` attribute. In order to use it you should wrap your component with `injectI18n` Higher-Order Component. The formatting API will be provided to the wrapped component via `props.intl`.
287
288React component as a pure function:
289
290```js
291import React from 'react';
292import { injectI18n, intlShape } from '@kbn/i18n/react';
293
294export const MyComponent = injectI18n({ intl }) => (
295  <input
296    type="text"
297    placeholder={intl.formatMessage(
298      {
299        id: 'welcome',
300        defaultMessage: 'Hello {name}, you have {unreadCount, number}\
301{unreadCount, plural, one {message} other {messages}}',
302        description: 'Message description',
303      },
304      { name, unreadCount }
305    )}
306  />
307));
308
309MyComponent.WrappedComponent.propTypes = {
310  intl: intlShape.isRequired,
311};
312```
313
314React component as a class:
315
316```js
317import React from 'react';
318import { injectI18n, intlShape } from '@kbn/i18n/react';
319
320export const MyComponent = injectI18n(
321  class MyComponent extends React.Component {
322    static propTypes = {
323      intl: intlShape.isRequired,
324    };
325
326    render() {
327      const { intl } = this.props;
328
329      return (
330        <input
331          type="text"
332          placeholder={intl.formatMessage({
333            id: 'kbn.management.objects.searchPlaceholder',
334            defaultMessage: 'Search',
335          })}
336        />
337      );
338    }
339  }
340);
341```
342
343## AngularJS
344
345AngularJS wrapper has 4 entities: translation `provider`, `service`, `directive`
346and `filter`. Both the directive and the filter use the translation `service`
347with i18n engine under the hood.
348
349The translation `provider` is used for `service` configuration and
350has the following methods:
351- `addMessages(messages: Map<string, string>, [locale: string])` - provides a way to register
352translations with the library
353- `setLocale(locale: string)` - tells the library which language to use by given
354language key
355- `getLocale()` - returns the current locale
356- `setDefaultLocale(locale: string)` - tells the library which language to fallback
357when missing translations
358- `getDefaultLocale()` - returns the default locale
359- `setFormats(formats: object)` - supplies a set of options to the underlying formatter
360- `getFormats()` - returns current formats
361- `getRegisteredLocales()` - returns array of locales having translations
362- `init(messages: Map<string, string>)` - initializes the engine
363
364The translation `service` provides only one method:
365- `i18n(id: string, { values: object, defaultMessage: string, description: string })` –
366translate message by id
367
368The translation `filter` is used for attributes translation and has
369the following syntax:
370```
371{{ ::'translationId' | i18n: { values: object, defaultMessage: string, description: string } }}
372```
373
374Where:
375- `translationId` - translation id to be translated
376- `values` - values to pass into translation
377- `defaultMessage` - will be used unless translation was successful (the final
378  fallback in english, will be used for generating `en.json`)
379- `description` - optional context comment that will be extracted by i18n tools
380and added as a comment next to translation message at `defaultMessages.json`
381
382The translation `directive` has the following syntax:
383```html
384<ANY
385  i18n-id="{string}"
386  i18n-default-message="{string}"
387  [i18n-values="{object}"]
388  [i18n-description="{string}"]
389></ANY>
390```
391
392Where:
393- `i18n-id` - translation id to be translated
394- `i18n-default-message` - will be used unless translation was successful
395- `i18n-values` - values to pass into translation
396- `i18n-description` - optional context comment that will be extracted by i18n tools
397and added as a comment next to translation message at `defaultMessages.json`
398
399If HTML rendering in `i18n-values` is required then value key in `i18n-values` object
400should have `html_` prefix. Otherwise the value will be inserted to the message without
401HTML rendering.\
402Example:
403```html
404<p
405  i18n-id="namespace.id"
406  i18n-default-message="Text with an emphasized {text}."
407  i18n-values="{
408    html_text: '<em>text</em>',
409  }"
410></p>
411```
412
413Angular `I18n` module is placed into `autoload` module, so it will be
414loaded automatically. After that we can use i18n directive in Angular templates:
415```html
416<span
417  i18n-id="welcome"
418  i18n-default-message="Hello!"
419></span>
420```
421
422In order to translate attributes in AngularJS we should use `i18nFilter`:
423```html
424<input
425  type="text"
426  placeholder="{{ ::'kbn.management.objects.searchAriaLabel' | i18n: {
427    defaultMessage: 'Search { title } Object',
428    values: { title }
429  } }}"
430>
431```
432
433## I18n tools
434
435In order to simplify localization process, some additional tools were implemented:
436- tool for verifying all translations have translatable strings and extracting default messages from templates
437- tool for verifying translation files and integrating them to Kibana
438
439[I18n tools documentation](../../src/dev/i18n/README.md)
440