mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Add draft implementation of I18n engine (#19555)
* Add draft implementation of I18n engine * Add i18n loader * kbn-i18n refactoring * Fix react i18n context and update doc * i18n engine refactoring * Fix locales data loading and add more jsdoc comments * Fix verify_translations task * I18n tests refactoring * Add build scripts to kbn-i18n package * Fix some bugs * Move uiI18nMixin into ui_i18n folder * Add 'browser' field to kbn-i18n package.json * Get rid of "showError" method * Make i18n and i18nLoader a singleton object * Add default locale as fallback if translation files were not registered * Update yarn.lock * kbn-i18n fix * Add default formats * Try to fix build * Add more examples into kbn-i18n/README.md * kbn-i18n fix * Fix app_bootstrap tests * Add links to issues in TODO comments
This commit is contained in:
parent
3ea4c3e0bc
commit
f522b31ac9
42 changed files with 3478 additions and 566 deletions
|
@ -31,6 +31,7 @@ module.exports = {
|
|||
'packages/kbn-pm/**/*',
|
||||
'packages/kbn-es/**/*',
|
||||
'packages/kbn-datemath/**/*',
|
||||
'packages/kbn-i18n/**/*',
|
||||
'packages/kbn-dev-utils/**/*',
|
||||
'packages/kbn-plugin-helpers/**/*',
|
||||
'packages/kbn-plugin-generator/**/*',
|
||||
|
|
|
@ -83,11 +83,11 @@
|
|||
"@elastic/ui-ace": "0.2.3",
|
||||
"@kbn/babel-preset": "link:packages/kbn-babel-preset",
|
||||
"@kbn/datemath": "link:packages/kbn-datemath",
|
||||
"@kbn/i18n": "link:packages/kbn-i18n",
|
||||
"@kbn/pm": "link:packages/kbn-pm",
|
||||
"@kbn/test-subj-selector": "link:packages/kbn-test-subj-selector",
|
||||
"@kbn/ui-framework": "link:packages/kbn-ui-framework",
|
||||
"JSONStream": "1.1.1",
|
||||
"accept-language-parser": "1.2.0",
|
||||
"angular": "1.6.9",
|
||||
"angular-aria": "1.6.6",
|
||||
"angular-elastic": "2.5.0",
|
||||
|
|
10
packages/kbn-i18n/.babelrc
Normal file
10
packages/kbn-i18n/.babelrc
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"env": {
|
||||
"web": {
|
||||
"presets": ["@kbn/babel-preset/webpack_preset"]
|
||||
},
|
||||
"node": {
|
||||
"presets": ["@kbn/babel-preset/node_preset"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -98,6 +98,8 @@ data to UI frameworks and provides methods for the direct translation.
|
|||
|
||||
Here is the public API exposed by this engine:
|
||||
|
||||
- `addMessages(messages: Map<string, string>, [locale: string])` - provides a way to register
|
||||
translations with the engine
|
||||
- `getMessages()` - returns messages for the current language
|
||||
- `setLocale(locale: string)` - tells the engine which language to use by given
|
||||
language key
|
||||
|
@ -105,9 +107,14 @@ language key
|
|||
- `setDefaultLocale(locale: string)` - tells the library which language to fallback
|
||||
when missing translations
|
||||
- `getDefaultLocale()` - returns the default locale
|
||||
- `defineFormats(formats: object)` - supplies a set of options to the underlying formatter.
|
||||
- `setFormats(formats: object)` - supplies a set of options to the underlying formatter.
|
||||
For the detailed explanation, see the section below
|
||||
- `translate(id: string, [{values: object, defaultMessage: string}])` – translate message by id
|
||||
- `getFormats()` - returns current formats
|
||||
- `getRegisteredLocales()` - returns array of locales having translations
|
||||
- `translate(id: string, [{values: object, defaultMessage: string, context: string}])` –
|
||||
translate message by id. `context` is optional context comment that will be extracted
|
||||
by i18n tools and added as a comment next to translation message at `defaultMessages.json`.
|
||||
- `init(messages: Map<string, string>)` - initializes the engine
|
||||
|
||||
#### I18n engine internals
|
||||
|
||||
|
@ -179,28 +186,22 @@ React Intl uses the provider pattern to scope an i18n context to a tree of compo
|
|||
are able to use `FormattedMessage` component in order to translate messages.
|
||||
`IntlProvider` should wrap react app's root component (inside each react render method).
|
||||
|
||||
In order to translate messages we need to pass them into the `IntlProvider`
|
||||
from I18n engine:
|
||||
In order to translate messages we need to use `I18nProvider` component that
|
||||
uses I18n engine under the hood:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ReactI18n } from '@kbn/i18n';
|
||||
|
||||
import i18n from 'kbn-i18n';
|
||||
import { IntlProvider } from 'ui/i18n/react-intl';
|
||||
|
||||
const locale = i18n.getLocale();
|
||||
const messages = i18n.getMessages();
|
||||
const { I18nProvider } = ReactI18n;
|
||||
|
||||
ReactDOM.render(
|
||||
<IntlProvider
|
||||
locale={locale}
|
||||
messages={messages}
|
||||
>
|
||||
<I18nProvider>
|
||||
<RootComponent>
|
||||
...
|
||||
</RootComponent>
|
||||
</IntlProvider>,
|
||||
</I18nProvider>,
|
||||
document.getElementById('container')
|
||||
);
|
||||
```
|
||||
|
@ -208,8 +209,9 @@ ReactDOM.render(
|
|||
After that we can use `FormattedMessage` components inside `RootComponent`:
|
||||
```js
|
||||
import React, { Component } from 'react';
|
||||
import { ReactI18n } from '@kbn/i18n';
|
||||
|
||||
import { FormattedMessage } from 'ui/i18n/react-intl';
|
||||
const { FormattedMessage } = ReactI18n;
|
||||
|
||||
class RootComponent extends Component {
|
||||
constructor(props) {
|
||||
|
@ -244,6 +246,40 @@ class RootComponent extends Component {
|
|||
}
|
||||
```
|
||||
|
||||
Optionally we can pass `context` prop into `FormattedMessage` component.
|
||||
This prop is optional context comment that will be extracted by i18n tools
|
||||
and added as a comment next to translation message at `defaultMessages.json`
|
||||
|
||||
|
||||
#### Attributes translation in React
|
||||
React wrapper provides an API to inject the imperative formatting API into a React
|
||||
component by using render callback pattern. 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 components into `I18nContext` component. The child of this component
|
||||
should be a function that takes `intl` object into parameters:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { ReactI18n } from '@kbn/i18n';
|
||||
|
||||
const { I18nContext } = ReactI18n;
|
||||
|
||||
const MyComponent = () => (
|
||||
<I18nContext>
|
||||
{intl => (
|
||||
<input
|
||||
type="text"
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER',
|
||||
defaultMessage: 'Search',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</I18nContext>
|
||||
);
|
||||
```
|
||||
|
||||
## Angular
|
||||
|
||||
Angular wrapper has 4 entities: translation `provider`, `service`, `directive`
|
||||
|
@ -260,15 +296,19 @@ language key
|
|||
- `setDefaultLocale(locale: string)` - tells the library which language to fallback
|
||||
when missing translations
|
||||
- `getDefaultLocale()` - returns the default locale
|
||||
- `defineFormats(formats: object)` - supplies a set of options to the underlying formatter
|
||||
- `setFormats(formats: object)` - supplies a set of options to the underlying formatter
|
||||
- `getFormats()` - returns current formats
|
||||
- `getRegisteredLocales()` - returns array of locales having translations
|
||||
- `init(messages: Map<string, string>)` - initializes the engine
|
||||
|
||||
The translation `service` provides only one method:
|
||||
- `translate(id: string, [{values: object, defaultMessage: string}])` – translate message by id
|
||||
- `i18n(id: string, [{values: object, defaultMessage: string, context: string }])`–
|
||||
translate message by id
|
||||
|
||||
The translation `filter` is used for attributes translation and has
|
||||
the following syntax:
|
||||
```
|
||||
{{'translationId' | i18n[:{ values: object, defaultMessage: string }]}}
|
||||
{{'translationId' | i18n[:{ values: object, defaultMessage: string, context: string }]}}
|
||||
```
|
||||
|
||||
Where:
|
||||
|
@ -276,6 +316,8 @@ Where:
|
|||
- `values` - values to pass into translation
|
||||
- `defaultMessage` - will be used unless translation was successful (the final
|
||||
fallback in english, will be used for generating `en.json`)
|
||||
- `context` - optional context comment that will be extracted by i18n tools
|
||||
and added as a comment next to translation message at `defaultMessages.json`
|
||||
|
||||
The translation `directive` has the following syntax:
|
||||
```html
|
||||
|
@ -283,6 +325,7 @@ The translation `directive` has the following syntax:
|
|||
i18n-id="{string}"
|
||||
[i18n-values="{object}"]
|
||||
[i18n-default-message="{string}"]
|
||||
[i18n-context="{string}"]
|
||||
></ANY>
|
||||
```
|
||||
|
||||
|
@ -290,21 +333,11 @@ Where:
|
|||
- `i18n-id` - translation id to be translated
|
||||
- `i18n-values` - values to pass into translation
|
||||
- `i18n-default-message` - will be used unless translation was successful
|
||||
- `i18n-context` - optional context comment that will be extracted by i18n tools
|
||||
and added as a comment next to translation message at `defaultMessages.json`
|
||||
|
||||
In order to initialize the translation service, we need to pass locale and
|
||||
localization messages from I18n engine into the `i18nProvider`:
|
||||
|
||||
```js
|
||||
import { uiModules } from 'ui/modules';
|
||||
import i18n from 'kbn-i18n';
|
||||
|
||||
uiModules.get('kibana').config(function (i18nProvider) {
|
||||
i18nProvider.addMessages(i18n.getMessages());
|
||||
i18nProvider.setLocale(i18n.getLocale());
|
||||
});
|
||||
```
|
||||
|
||||
After that we can use i18n directive in Angular templates:
|
||||
Angular `I18n` module is placed into `autoload` module, so it will be
|
||||
loaded automatically. After that we can use i18n directive in Angular templates:
|
||||
```html
|
||||
<span
|
||||
i18n-id="welcome"
|
||||
|
@ -312,6 +345,16 @@ After that we can use i18n directive in Angular templates:
|
|||
></span>
|
||||
```
|
||||
|
||||
In order to translate attributes in Angular we should use `i18nFilter`:
|
||||
```html
|
||||
<input
|
||||
type="text"
|
||||
placeholder="{{'KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER' | i18n: {
|
||||
defaultMessage: 'Search'
|
||||
} }}"
|
||||
>
|
||||
```
|
||||
|
||||
## Node.JS
|
||||
|
||||
`Intl-messageformat` package assumes that the
|
||||
|
@ -320,10 +363,13 @@ global object exists in the runtime. `Intl` is present in all modern
|
|||
browsers and Node.js 0.10+. In order to load i18n engine
|
||||
in Node.js we should simply `import` this module (in Node.js, the
|
||||
[data](https://github.com/yahoo/intl-messageformat/tree/master/dist/locale-data)
|
||||
for all 200+ languages is loaded along with the library):
|
||||
for all 200+ languages is loaded along with the library) and pass the translation
|
||||
messages into `init` method:
|
||||
|
||||
```js
|
||||
import i18n from 'kbn-i18n';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
i18n.init(messages);
|
||||
```
|
||||
|
||||
After that we are able to use all methods exposed by the i18n engine
|
32
packages/kbn-i18n/package.json
Normal file
32
packages/kbn-i18n/package.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "@kbn/i18n",
|
||||
"browser": "./target/web/browser.js",
|
||||
"main": "./target/node/index.js",
|
||||
"module": "./src/index.js",
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "yarn build:web && yarn build:node",
|
||||
"build:web": "cross-env BABEL_ENV=web babel src --out-dir target/web",
|
||||
"build:node": "cross-env BABEL_ENV=node babel src --out-dir target/node",
|
||||
"kbn:bootstrap": "yarn build",
|
||||
"kbn:watch": "yarn build --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kbn/babel-preset": "link:../kbn-babel-preset",
|
||||
"@kbn/dev-utils": "link:../kbn-dev-utils",
|
||||
"babel-cli": "^6.26.0",
|
||||
"cross-env": "^5.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"accept-language-parser": "^1.5.0",
|
||||
"intl-format-cache": "^2.1.0",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
"intl-relativeformat": "^2.1.0",
|
||||
"json5": "^1.0.1",
|
||||
"prop-types": "^15.5.8",
|
||||
"react": "^16.3.0",
|
||||
"react-intl": "^2.4.0"
|
||||
}
|
||||
}
|
43
packages/kbn-i18n/src/angular/directive.js
vendored
Normal file
43
packages/kbn-i18n/src/angular/directive.js
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export function i18nDirective(i18n) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
id: '@i18nId',
|
||||
defaultMessage: '@i18nDefaultMessage',
|
||||
values: '=i18nValues',
|
||||
},
|
||||
link: function($scope, $element) {
|
||||
$scope.$watchGroup(['id', 'defaultMessage', 'values'], function([
|
||||
id,
|
||||
defaultMessage = '',
|
||||
values = {},
|
||||
]) {
|
||||
$element.html(
|
||||
i18n(id, {
|
||||
values,
|
||||
defaultMessage,
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
27
packages/kbn-i18n/src/angular/filter.js
vendored
Normal file
27
packages/kbn-i18n/src/angular/filter.js
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export function i18nFilter(i18n) {
|
||||
return function(id, { defaultMessage = '', values = {} } = {}) {
|
||||
return i18n(id, {
|
||||
values,
|
||||
defaultMessage,
|
||||
});
|
||||
};
|
||||
}
|
22
packages/kbn-i18n/src/angular/index.js
vendored
Normal file
22
packages/kbn-i18n/src/angular/index.js
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { i18nProvider } from './provider';
|
||||
export { i18nFilter } from './filter';
|
||||
export { i18nDirective } from './directive';
|
34
packages/kbn-i18n/src/angular/provider.js
vendored
Normal file
34
packages/kbn-i18n/src/angular/provider.js
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as i18n from '../core/i18n';
|
||||
|
||||
export function i18nProvider() {
|
||||
this.addMessages = i18n.addMessages;
|
||||
this.getMessages = i18n.getMessages;
|
||||
this.setLocale = i18n.setLocale;
|
||||
this.getLocale = i18n.getLocale;
|
||||
this.setDefaultLocale = i18n.setDefaultLocale;
|
||||
this.getDefaultLocale = i18n.getDefaultLocale;
|
||||
this.setFormats = i18n.setFormats;
|
||||
this.getFormats = i18n.getFormats;
|
||||
this.getRegisteredLocales = i18n.getRegisteredLocales;
|
||||
this.init = i18n.init;
|
||||
this.$get = () => i18n.translate;
|
||||
}
|
28
packages/kbn-i18n/src/browser.js
Normal file
28
packages/kbn-i18n/src/browser.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as angular from './angular';
|
||||
import * as react from './react';
|
||||
import * as i18nCore from './core/i18n';
|
||||
|
||||
export { formats } from './core/formats';
|
||||
|
||||
export const AngularI18n = angular;
|
||||
export const ReactI18n = react;
|
||||
export const i18n = i18nCore;
|
86
packages/kbn-i18n/src/core/formats.js
Normal file
86
packages/kbn-i18n/src/core/formats.js
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default format options used for "en" locale.
|
||||
* These are used when constructing the internal Intl.NumberFormat
|
||||
* (`number` formatter) and Intl.DateTimeFormat (`date` and `time` formatters) instances.
|
||||
* The value of each parameter of `number` formatter is options object which is
|
||||
* described in `options` section of [NumberFormat constructor].
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat}
|
||||
* The value of each parameter of `date` and `time` formatters is options object which is
|
||||
* described in `options` section of [DateTimeFormat constructor].
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat}
|
||||
*/
|
||||
export const formats = {
|
||||
number: {
|
||||
currency: {
|
||||
style: 'currency',
|
||||
},
|
||||
percent: {
|
||||
style: 'percent',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
short: {
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
year: '2-digit',
|
||||
},
|
||||
medium: {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
long: {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
full: {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
},
|
||||
time: {
|
||||
short: {
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
},
|
||||
medium: {
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
},
|
||||
long: {
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
timeZoneName: 'short',
|
||||
},
|
||||
full: {
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
timeZoneName: 'short',
|
||||
},
|
||||
},
|
||||
};
|
49
packages/kbn-i18n/src/core/helper.js
Normal file
49
packages/kbn-i18n/src/core/helper.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export const isString = value => typeof value === 'string';
|
||||
|
||||
export const isObject = value => typeof value === 'object' && value !== null;
|
||||
|
||||
export const hasValues = values => Object.keys(values).length > 0;
|
||||
|
||||
export const unique = (arr = []) => [...new Set(arr)];
|
||||
|
||||
const merge = (a, b) =>
|
||||
unique([...Object.keys(a), ...Object.keys(b)]).reduce((acc, key) => {
|
||||
if (
|
||||
isObject(a[key]) &&
|
||||
isObject(b[key]) &&
|
||||
!Array.isArray(a[key]) &&
|
||||
!Array.isArray(b[key])
|
||||
) {
|
||||
return {
|
||||
...acc,
|
||||
[key]: merge(a[key], b[key]),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[key]: b[key] === undefined ? a[key] : b[key],
|
||||
};
|
||||
}, {});
|
||||
|
||||
export const mergeAll = (...sources) =>
|
||||
sources.filter(isObject).reduce((acc, source) => merge(acc, source));
|
243
packages/kbn-i18n/src/core/i18n.js
Normal file
243
packages/kbn-i18n/src/core/i18n.js
Normal file
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef Messages - messages tree, where leafs are translated strings
|
||||
@type {object<string, object>}
|
||||
@property {string} [locale] - locale of the messages
|
||||
@property {object} [formats] - set of options to the underlying formatter
|
||||
*/
|
||||
|
||||
import IntlMessageFormat from 'intl-messageformat';
|
||||
import IntlRelativeFormat from 'intl-relativeformat';
|
||||
import memoizeIntlConstructor from 'intl-format-cache';
|
||||
import { isString, isObject, hasValues, mergeAll } from './helper';
|
||||
import { formats as EN_FORMATS } from './formats';
|
||||
|
||||
// Add all locale data to `IntlMessageFormat`.
|
||||
import './locales';
|
||||
|
||||
const EN_LOCALE = 'en';
|
||||
const LOCALE_DELIMITER = '-';
|
||||
const messages = {};
|
||||
const getMessageFormat = memoizeIntlConstructor(IntlMessageFormat);
|
||||
|
||||
let defaultLocale = EN_LOCALE;
|
||||
let currentLocale = EN_LOCALE;
|
||||
let formats = EN_FORMATS;
|
||||
|
||||
IntlMessageFormat.defaultLocale = defaultLocale;
|
||||
IntlRelativeFormat.defaultLocale = defaultLocale;
|
||||
|
||||
/**
|
||||
* Returns message by the given message id.
|
||||
* @param {string} id - path to the message
|
||||
* @returns {string} message - translated message from messages tree
|
||||
*/
|
||||
function getMessageById(id) {
|
||||
return getMessages()[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes locale to make it consistent with IntlMessageFormat locales
|
||||
* @param {string} locale
|
||||
* @returns {string} normalizedLocale
|
||||
*/
|
||||
function normalizeLocale(locale) {
|
||||
return locale.toLowerCase().replace('_', LOCALE_DELIMITER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a way to register translations with the engine
|
||||
* @param {Messages} newMessages
|
||||
* @param {string} [locale = messages.locale]
|
||||
*/
|
||||
export function addMessages(newMessages = {}, locale = newMessages.locale) {
|
||||
if (!locale || !isString(locale)) {
|
||||
throw new Error(
|
||||
'[I18n] A `locale` must be a non-empty string to add messages.'
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedLocale = normalizeLocale(locale);
|
||||
|
||||
messages[normalizedLocale] = {
|
||||
...messages[normalizedLocale],
|
||||
...newMessages,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns messages for the current language
|
||||
* @returns {Messages} messages
|
||||
*/
|
||||
export function getMessages() {
|
||||
return messages[currentLocale] || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the engine which language to use by given language key
|
||||
* @param {string} locale
|
||||
*/
|
||||
export function setLocale(locale) {
|
||||
if (!locale || !isString(locale)) {
|
||||
throw new Error('[I18n] A `locale` must be a non-empty string.');
|
||||
}
|
||||
|
||||
currentLocale = normalizeLocale(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current locale
|
||||
* @returns {string} locale
|
||||
*/
|
||||
export function getLocale() {
|
||||
return currentLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the library which language to fallback when missing translations
|
||||
* @param {string} locale
|
||||
*/
|
||||
export function setDefaultLocale(locale) {
|
||||
if (!locale || !isString(locale)) {
|
||||
throw new Error('[I18n] A `locale` must be a non-empty string.');
|
||||
}
|
||||
|
||||
defaultLocale = normalizeLocale(locale);
|
||||
IntlMessageFormat.defaultLocale = defaultLocale;
|
||||
IntlRelativeFormat.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default locale
|
||||
* @returns {string} defaultLocale
|
||||
*/
|
||||
export function getDefaultLocale() {
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplies a set of options to the underlying formatter
|
||||
* [Default format options used as the prototype of the formats]
|
||||
* {@link https://github.com/yahoo/intl-messageformat/blob/master/src/core.js#L62}
|
||||
* These are used when constructing the internal Intl.NumberFormat
|
||||
* and Intl.DateTimeFormat instances.
|
||||
* @param {object} newFormats
|
||||
* @param {object} [newFormats.number]
|
||||
* @param {object} [newFormats.date]
|
||||
* @param {object} [newFormats.time]
|
||||
*/
|
||||
export function setFormats(newFormats) {
|
||||
if (!isObject(newFormats) || !hasValues(newFormats)) {
|
||||
throw new Error('[I18n] A `formats` must be a non-empty object.');
|
||||
}
|
||||
|
||||
formats = mergeAll(formats, newFormats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current formats
|
||||
* @returns {object} formats
|
||||
*/
|
||||
export function getFormats() {
|
||||
return formats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of locales having translations
|
||||
* @returns {string[]} locales
|
||||
*/
|
||||
export function getRegisteredLocales() {
|
||||
return Object.keys(messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate message by id
|
||||
* @param {string} id - translation id to be translated
|
||||
* @param {object} [options]
|
||||
* @param {object} [options.values] - values to pass into translation
|
||||
* @param {string} [options.defaultMessage] - will be used unless translation was successful
|
||||
* @returns {string}
|
||||
*/
|
||||
export function translate(id, { values = {}, defaultMessage = '' } = {}) {
|
||||
if (!id || !isString(id)) {
|
||||
throw new Error(
|
||||
'[I18n] An `id` must be a non-empty string to translate a message.'
|
||||
);
|
||||
}
|
||||
|
||||
const message = getMessageById(id);
|
||||
|
||||
if (!message && !defaultMessage) {
|
||||
throw new Error(
|
||||
`[I18n] Cannot format message: "${id}". Default message must be provided.`
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasValues(values)) {
|
||||
return message || defaultMessage;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
try {
|
||||
const msg = getMessageFormat(message, getLocale(), getFormats());
|
||||
|
||||
return msg.format(values);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`[I18n] Error formatting message: "${id}" for locale: "${getLocale()}".\n${e}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const msg = getMessageFormat(
|
||||
defaultMessage,
|
||||
getDefaultLocale(),
|
||||
getFormats()
|
||||
);
|
||||
|
||||
return msg.format(values);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`[I18n] Error formatting the default message for: "${id}".\n${e}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the engine
|
||||
* @param {Messages} newMessages
|
||||
*/
|
||||
export function init(newMessages) {
|
||||
if (!newMessages) {
|
||||
return;
|
||||
}
|
||||
|
||||
addMessages(newMessages);
|
||||
|
||||
if (newMessages.locale) {
|
||||
setLocale(newMessages.locale);
|
||||
}
|
||||
|
||||
if (newMessages.formats) {
|
||||
setFormats(newMessages.formats);
|
||||
}
|
||||
}
|
207
packages/kbn-i18n/src/core/loader.js
Normal file
207
packages/kbn-i18n/src/core/loader.js
Normal file
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef Messages - messages tree, where leafs are translated strings
|
||||
@type {object<string, object>}
|
||||
@property {string} [locale] - locale of the messages
|
||||
@property {object} [formats] - set of options to the underlying formatter
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { readFile } from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { pick } from 'accept-language-parser';
|
||||
import JSON5 from 'json5';
|
||||
import { unique } from './helper';
|
||||
|
||||
const asyncReadFile = promisify(readFile);
|
||||
|
||||
const TRANSLATION_FILE_EXTENSION = '.json';
|
||||
|
||||
/**
|
||||
* Internal property for storing registered translations paths
|
||||
* @type {Map<string, string[]>|{}} - Key is locale, value is array of registered paths
|
||||
*/
|
||||
const translationsRegistry = {};
|
||||
|
||||
/**
|
||||
* Internal property for caching loaded translations files
|
||||
* @type {Map<string, Messages>|{}} - Key is path to translation file, value is
|
||||
* object with translation messages
|
||||
*/
|
||||
const loadedFiles = {};
|
||||
|
||||
/**
|
||||
* Returns locale by the given translation file name
|
||||
* @param {string} fullFileName
|
||||
* @returns {string} locale
|
||||
* @example
|
||||
* getLocaleFromFileName('./path/to/translation/ru.json') // => 'ru'
|
||||
*/
|
||||
function getLocaleFromFileName(fullFileName) {
|
||||
if (!fullFileName) {
|
||||
throw new Error('Filename is empty');
|
||||
}
|
||||
|
||||
const fileExt = path.extname(fullFileName);
|
||||
|
||||
if (fileExt !== TRANSLATION_FILE_EXTENSION) {
|
||||
throw new Error(
|
||||
`Translations must have 'json' extension. File being registered is ${fullFileName}`
|
||||
);
|
||||
}
|
||||
|
||||
return path.basename(fullFileName, TRANSLATION_FILE_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads file and parses it as JSON5
|
||||
* @param {string} pathToFile
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function loadFile(pathToFile) {
|
||||
return JSON5.parse(await asyncReadFile(pathToFile, 'utf8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the accept-language header from an HTTP request and picks
|
||||
* the best match of the locale from the registered locales
|
||||
* @param {string} header - accept-language header from an HTTP request
|
||||
* @returns {string} locale
|
||||
*/
|
||||
function pickLocaleByLanguageHeader(header) {
|
||||
return pick(getRegisteredLocales(), header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads translations files and adds them into "loadedFiles" cache
|
||||
* @param {string[]} files
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function loadAndCacheFiles(files) {
|
||||
const translations = await Promise.all(files.map(loadFile));
|
||||
|
||||
files.forEach((file, index) => {
|
||||
loadedFiles[file] = translations[index];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers translation file with i18n loader
|
||||
* @param {string} translationFilePath - Absolute path to the translation file to register.
|
||||
*/
|
||||
export function registerTranslationFile(translationFilePath) {
|
||||
if (!path.isAbsolute(translationFilePath)) {
|
||||
throw new TypeError(
|
||||
'Paths to translation files must be absolute. ' +
|
||||
`Got relative path: "${translationFilePath}"`
|
||||
);
|
||||
}
|
||||
|
||||
const locale = getLocaleFromFileName(translationFilePath);
|
||||
|
||||
translationsRegistry[locale] = unique([
|
||||
...(translationsRegistry[locale] || []),
|
||||
translationFilePath,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers array of translation files with i18n loader
|
||||
* @param {string[]} arrayOfPaths - Array of absolute paths to the translation files to register.
|
||||
*/
|
||||
export function registerTranslationFiles(arrayOfPaths = []) {
|
||||
arrayOfPaths.forEach(registerTranslationFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of locales that have been registered with i18n loader
|
||||
* @returns {string[]} registeredTranslations
|
||||
*/
|
||||
export function getRegisteredLocales() {
|
||||
return Object.keys(translationsRegistry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns translations for a suitable locale based on accept-language header.
|
||||
* This object will contain all registered translations for the highest priority
|
||||
* locale which is registered with the i18n loader. This object can be empty
|
||||
* if no locale in the language tags can be matched against the registered locales.
|
||||
* @param {string} header - accept-language header from an HTTP request
|
||||
* @returns {Promise<Messages>} translations - translation messages
|
||||
*/
|
||||
export async function getTranslationsByLanguageHeader(header) {
|
||||
return getTranslationsByLocale(pickLocaleByLanguageHeader(header));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns translation messages by specified locale
|
||||
* @param {string} locale
|
||||
* @returns {Promise<Messages>} translations - translation messages
|
||||
*/
|
||||
export async function getTranslationsByLocale(locale) {
|
||||
const files = translationsRegistry[locale] || [];
|
||||
const notLoadedFiles = files.filter(file => !loadedFiles[file]);
|
||||
|
||||
if (notLoadedFiles.length) {
|
||||
await loadAndCacheFiles(notLoadedFiles);
|
||||
}
|
||||
|
||||
return files.length
|
||||
? files.reduce(
|
||||
(messages, file) => ({
|
||||
...messages,
|
||||
...loadedFiles[file],
|
||||
}),
|
||||
{ locale }
|
||||
)
|
||||
: {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all translations for registered locales
|
||||
* @return {Promise<Map<string, Messages>>} translations - A Promise object
|
||||
* where keys are the locale and values are objects of translation messages
|
||||
*/
|
||||
export async function getAllTranslations() {
|
||||
const locales = getRegisteredLocales();
|
||||
const translations = await Promise.all(locales.map(getTranslationsByLocale));
|
||||
|
||||
return locales.reduce(
|
||||
(acc, locale, index) => ({
|
||||
...acc,
|
||||
[locale]: translations[index],
|
||||
}),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers passed translations files, loads them and returns promise with
|
||||
* all translation messages
|
||||
* @param {string[]} paths - Array of absolute paths to the translation files
|
||||
* @returns {Promise<Map<string, Messages>>} translations - A Promise object
|
||||
* where keys are the locale and values are objects of translation messages
|
||||
*/
|
||||
export async function getAllTranslationsFromPaths(paths) {
|
||||
registerTranslationFiles(paths);
|
||||
|
||||
return await getAllTranslations();
|
||||
}
|
566
packages/kbn-i18n/src/core/locales.js
Normal file
566
packages/kbn-i18n/src/core/locales.js
Normal file
|
@ -0,0 +1,566 @@
|
|||
/* eslint-disable */
|
||||
|
||||
// TODO: Get rid of this file once https://github.com/elastic/kibana/pull/20105
|
||||
// is merged and use dynamic import for asynchronous loading of specific locale data
|
||||
|
||||
import IntlMessageFormat from 'intl-messageformat';
|
||||
import IntlRelativeFormat from 'intl-relativeformat';
|
||||
|
||||
function addLocaleData(localeData) {
|
||||
IntlMessageFormat.__addLocaleData(localeData);
|
||||
IntlRelativeFormat.__addLocaleData(localeData);
|
||||
}
|
||||
|
||||
addLocaleData({"locale":"af","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"af-NA","parentLocale":"af"});
|
||||
addLocaleData({"locale":"agq","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ak","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==0||n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"am","pluralRuleFunction":function (n,ord){if(ord)return"other";return n>=0&&n<=1?"one":"other"}});
|
||||
addLocaleData({"locale":"ar","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n,n100=t0&&s[0].slice(-2);if(ord)return"other";return n==0?"zero":n==1?"one":n==2?"two":n100>=3&&n100<=10?"few":n100>=11&&n100<=99?"many":"other"}});
|
||||
addLocaleData({"locale":"ar-AE","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-BH","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-DJ","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-DZ","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-EG","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-EH","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-ER","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-IL","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-IQ","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-JO","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-KM","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-KW","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-LB","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-LY","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-MA","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-MR","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-OM","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-PS","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-QA","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-SA","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-SD","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-SO","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-SS","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-SY","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-TD","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-TN","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"ar-YE","parentLocale":"ar"});
|
||||
addLocaleData({"locale":"as","pluralRuleFunction":function (n,ord){if(ord)return n==1||n==5||n==7||n==8||n==9||n==10?"one":n==2||n==3?"two":n==4?"few":n==6?"many":"other";return n>=0&&n<=1?"one":"other"}});
|
||||
addLocaleData({"locale":"asa","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ast","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"az","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],i10=i.slice(-1),i100=i.slice(-2),i1000=i.slice(-3);if(ord)return i10==1||i10==2||i10==5||i10==7||i10==8||(i100==20||i100==50||i100==70||i100==80)?"one":i10==3||i10==4||(i1000==100||i1000==200||i1000==300||i1000==400||i1000==500||i1000==600||i1000==700||i1000==800||i1000==900)?"few":i==0||i10==6||(i100==40||i100==60||i100==90)?"many":"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"az-Arab","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"az-Cyrl","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"az-Latn","parentLocale":"az"});
|
||||
addLocaleData({"locale":"bas","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"be","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return(n10==2||n10==3)&&n100!=12&&n100!=13?"few":"other";return n10==1&&n100!=11?"one":n10>=2&&n10<=4&&(n100<12||n100>14)?"few":t0&&n10==0||n10>=5&&n10<=9||n100>=11&&n100<=14?"many":"other"}});
|
||||
addLocaleData({"locale":"bem","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"bez","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"bg","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"bh","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==0||n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"bm","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"bm-Nkoo","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"bn","pluralRuleFunction":function (n,ord){if(ord)return n==1||n==5||n==7||n==8||n==9||n==10?"one":n==2||n==3?"two":n==4?"few":n==6?"many":"other";return n>=0&&n<=1?"one":"other"}});
|
||||
addLocaleData({"locale":"bn-IN","parentLocale":"bn"});
|
||||
addLocaleData({"locale":"bo","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"bo-IN","parentLocale":"bo"});
|
||||
addLocaleData({"locale":"br","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2),n1000000=t0&&s[0].slice(-6);if(ord)return"other";return n10==1&&n100!=11&&n100!=71&&n100!=91?"one":n10==2&&n100!=12&&n100!=72&&n100!=92?"two":(n10==3||n10==4||n10==9)&&(n100<10||n100>19)&&(n100<70||n100>79)&&(n100<90||n100>99)?"few":n!=0&&t0&&n1000000==0?"many":"other"}});
|
||||
addLocaleData({"locale":"brx","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"bs","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],f=s[1]||"",v0=!s[1],i10=i.slice(-1),i100=i.slice(-2),f10=f.slice(-1),f100=f.slice(-2);if(ord)return"other";return v0&&i10==1&&i100!=11||f10==1&&f100!=11?"one":v0&&(i10>=2&&i10<=4)&&(i100<12||i100>14)||f10>=2&&f10<=4&&(f100<12||f100>14)?"few":"other"}});
|
||||
addLocaleData({"locale":"bs-Cyrl","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"bs-Latn","parentLocale":"bs"});
|
||||
addLocaleData({"locale":"ca","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return n==1||n==3?"one":n==2?"two":n==4?"few":"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"ca-AD","parentLocale":"ca"});
|
||||
addLocaleData({"locale":"ca-ES-VALENCIA","parentLocale":"ca-ES"});
|
||||
addLocaleData({"locale":"ca-ES","parentLocale":"ca"});
|
||||
addLocaleData({"locale":"ca-FR","parentLocale":"ca"});
|
||||
addLocaleData({"locale":"ca-IT","parentLocale":"ca"});
|
||||
addLocaleData({"locale":"ce","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"cgg","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"chr","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ckb","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ckb-IR","parentLocale":"ckb"});
|
||||
addLocaleData({"locale":"cs","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],v0=!s[1];if(ord)return"other";return n==1&&v0?"one":i>=2&&i<=4&&v0?"few":!v0?"many":"other"}});
|
||||
addLocaleData({"locale":"cu","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"cy","pluralRuleFunction":function (n,ord){if(ord)return n==0||n==7||n==8||n==9?"zero":n==1?"one":n==2?"two":n==3||n==4?"few":n==5||n==6?"many":"other";return n==0?"zero":n==1?"one":n==2?"two":n==3?"few":n==6?"many":"other"}});
|
||||
addLocaleData({"locale":"da","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],t0=Number(s[0])==n;if(ord)return"other";return n==1||!t0&&(i==0||i==1)?"one":"other"}});
|
||||
addLocaleData({"locale":"da-GL","parentLocale":"da"});
|
||||
addLocaleData({"locale":"dav","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"de","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"de-AT","parentLocale":"de"});
|
||||
addLocaleData({"locale":"de-BE","parentLocale":"de"});
|
||||
addLocaleData({"locale":"de-CH","parentLocale":"de"});
|
||||
addLocaleData({"locale":"de-LI","parentLocale":"de"});
|
||||
addLocaleData({"locale":"de-LU","parentLocale":"de"});
|
||||
addLocaleData({"locale":"dje","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"dsb","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],f=s[1]||"",v0=!s[1],i100=i.slice(-2),f100=f.slice(-2);if(ord)return"other";return v0&&i100==1||f100==1?"one":v0&&i100==2||f100==2?"two":v0&&(i100==3||i100==4)||(f100==3||f100==4)?"few":"other"}});
|
||||
addLocaleData({"locale":"dua","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"dv","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"dyo","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"dz","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ebu","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ee","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ee-TG","parentLocale":"ee"});
|
||||
addLocaleData({"locale":"el","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"el-CY","parentLocale":"el"});
|
||||
addLocaleData({"locale":"en","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"en-001","parentLocale":"en"});
|
||||
addLocaleData({"locale":"en-150","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-AG","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-AI","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-AS","parentLocale":"en"});
|
||||
addLocaleData({"locale":"en-AT","parentLocale":"en-150"});
|
||||
addLocaleData({"locale":"en-AU","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-BB","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-BE","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-BI","parentLocale":"en"});
|
||||
addLocaleData({"locale":"en-BM","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-BS","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-BW","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-BZ","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-CA","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-CC","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-CH","parentLocale":"en-150"});
|
||||
addLocaleData({"locale":"en-CK","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-CM","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-CX","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-CY","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-DE","parentLocale":"en-150"});
|
||||
addLocaleData({"locale":"en-DG","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-DK","parentLocale":"en-150"});
|
||||
addLocaleData({"locale":"en-DM","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-Dsrt","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"en-ER","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-FI","parentLocale":"en-150"});
|
||||
addLocaleData({"locale":"en-FJ","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-FK","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-FM","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-GB","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-GD","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-GG","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-GH","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-GI","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-GM","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-GU","parentLocale":"en"});
|
||||
addLocaleData({"locale":"en-GY","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-HK","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-IE","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-IL","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-IM","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-IN","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-IO","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-JE","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-JM","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-KE","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-KI","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-KN","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-KY","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-LC","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-LR","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-LS","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-MG","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-MH","parentLocale":"en"});
|
||||
addLocaleData({"locale":"en-MO","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-MP","parentLocale":"en"});
|
||||
addLocaleData({"locale":"en-MS","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-MT","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-MU","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-MW","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-MY","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-NA","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-NF","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-NG","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-NL","parentLocale":"en-150"});
|
||||
addLocaleData({"locale":"en-NR","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-NU","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-NZ","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-PG","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-PH","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-PK","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-PN","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-PR","parentLocale":"en"});
|
||||
addLocaleData({"locale":"en-PW","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-RW","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-SB","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-SC","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-SD","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-SE","parentLocale":"en-150"});
|
||||
addLocaleData({"locale":"en-SG","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-SH","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-SI","parentLocale":"en-150"});
|
||||
addLocaleData({"locale":"en-SL","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-SS","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-SX","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-SZ","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-Shaw","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"en-TC","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-TK","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-TO","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-TT","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-TV","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-TZ","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-UG","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-UM","parentLocale":"en"});
|
||||
addLocaleData({"locale":"en-US","parentLocale":"en"});
|
||||
addLocaleData({"locale":"en-VC","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-VG","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-VI","parentLocale":"en"});
|
||||
addLocaleData({"locale":"en-VU","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-WS","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-ZA","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-ZM","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"en-ZW","parentLocale":"en-001"});
|
||||
addLocaleData({"locale":"eo","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"es","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"es-419","parentLocale":"es"});
|
||||
addLocaleData({"locale":"es-AR","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-BO","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-CL","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-CO","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-CR","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-CU","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-DO","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-EA","parentLocale":"es"});
|
||||
addLocaleData({"locale":"es-EC","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-GQ","parentLocale":"es"});
|
||||
addLocaleData({"locale":"es-GT","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-HN","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-IC","parentLocale":"es"});
|
||||
addLocaleData({"locale":"es-MX","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-NI","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-PA","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-PE","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-PH","parentLocale":"es"});
|
||||
addLocaleData({"locale":"es-PR","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-PY","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-SV","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-US","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-UY","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"es-VE","parentLocale":"es-419"});
|
||||
addLocaleData({"locale":"et","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"eu","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ewo","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"fa","pluralRuleFunction":function (n,ord){if(ord)return"other";return n>=0&&n<=1?"one":"other"}});
|
||||
addLocaleData({"locale":"fa-AF","parentLocale":"fa"});
|
||||
addLocaleData({"locale":"ff","pluralRuleFunction":function (n,ord){if(ord)return"other";return n>=0&&n<2?"one":"other"}});
|
||||
addLocaleData({"locale":"ff-CM","parentLocale":"ff"});
|
||||
addLocaleData({"locale":"ff-GN","parentLocale":"ff"});
|
||||
addLocaleData({"locale":"ff-MR","parentLocale":"ff"});
|
||||
addLocaleData({"locale":"fi","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"fil","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],f=s[1]||"",v0=!s[1],i10=i.slice(-1),f10=f.slice(-1);if(ord)return n==1?"one":"other";return v0&&(i==1||i==2||i==3)||v0&&i10!=4&&i10!=6&&i10!=9||!v0&&f10!=4&&f10!=6&&f10!=9?"one":"other"}});
|
||||
addLocaleData({"locale":"fo","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"fo-DK","parentLocale":"fo"});
|
||||
addLocaleData({"locale":"fr","pluralRuleFunction":function (n,ord){if(ord)return n==1?"one":"other";return n>=0&&n<2?"one":"other"}});
|
||||
addLocaleData({"locale":"fr-BE","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-BF","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-BI","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-BJ","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-BL","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-CA","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-CD","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-CF","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-CG","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-CH","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-CI","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-CM","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-DJ","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-DZ","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-GA","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-GF","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-GN","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-GP","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-GQ","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-HT","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-KM","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-LU","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-MA","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-MC","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-MF","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-MG","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-ML","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-MQ","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-MR","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-MU","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-NC","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-NE","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-PF","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-PM","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-RE","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-RW","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-SC","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-SN","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-SY","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-TD","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-TG","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-TN","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-VU","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-WF","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fr-YT","parentLocale":"fr"});
|
||||
addLocaleData({"locale":"fur","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"fy","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"ga","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n;if(ord)return n==1?"one":"other";return n==1?"one":n==2?"two":t0&&n>=3&&n<=6?"few":t0&&n>=7&&n<=10?"many":"other"}});
|
||||
addLocaleData({"locale":"gd","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n;if(ord)return"other";return n==1||n==11?"one":n==2||n==12?"two":t0&&n>=3&&n<=10||t0&&n>=13&&n<=19?"few":"other"}});
|
||||
addLocaleData({"locale":"gl","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"gsw","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"gsw-FR","parentLocale":"gsw"});
|
||||
addLocaleData({"locale":"gsw-LI","parentLocale":"gsw"});
|
||||
addLocaleData({"locale":"gu","pluralRuleFunction":function (n,ord){if(ord)return n==1?"one":n==2||n==3?"two":n==4?"few":n==6?"many":"other";return n>=0&&n<=1?"one":"other"}});
|
||||
addLocaleData({"locale":"guw","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==0||n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"guz","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"gv","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],v0=!s[1],i10=i.slice(-1),i100=i.slice(-2);if(ord)return"other";return v0&&i10==1?"one":v0&&i10==2?"two":v0&&(i100==0||i100==20||i100==40||i100==60||i100==80)?"few":!v0?"many":"other"}});
|
||||
addLocaleData({"locale":"ha","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ha-Arab","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ha-GH","parentLocale":"ha"});
|
||||
addLocaleData({"locale":"ha-NE","parentLocale":"ha"});
|
||||
addLocaleData({"locale":"haw","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"he","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1);if(ord)return"other";return n==1&&v0?"one":i==2&&v0?"two":v0&&(n<0||n>10)&&t0&&n10==0?"many":"other"}});
|
||||
addLocaleData({"locale":"hi","pluralRuleFunction":function (n,ord){if(ord)return n==1?"one":n==2||n==3?"two":n==4?"few":n==6?"many":"other";return n>=0&&n<=1?"one":"other"}});
|
||||
addLocaleData({"locale":"hr","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],f=s[1]||"",v0=!s[1],i10=i.slice(-1),i100=i.slice(-2),f10=f.slice(-1),f100=f.slice(-2);if(ord)return"other";return v0&&i10==1&&i100!=11||f10==1&&f100!=11?"one":v0&&(i10>=2&&i10<=4)&&(i100<12||i100>14)||f10>=2&&f10<=4&&(f100<12||f100>14)?"few":"other"}});
|
||||
addLocaleData({"locale":"hr-BA","parentLocale":"hr"});
|
||||
addLocaleData({"locale":"hsb","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],f=s[1]||"",v0=!s[1],i100=i.slice(-2),f100=f.slice(-2);if(ord)return"other";return v0&&i100==1||f100==1?"one":v0&&i100==2||f100==2?"two":v0&&(i100==3||i100==4)||(f100==3||f100==4)?"few":"other"}});
|
||||
addLocaleData({"locale":"hu","pluralRuleFunction":function (n,ord){if(ord)return n==1||n==5?"one":"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"hy","pluralRuleFunction":function (n,ord){if(ord)return n==1?"one":"other";return n>=0&&n<2?"one":"other"}});
|
||||
addLocaleData({"locale":"id","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ig","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ii","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"in","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"is","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],t0=Number(s[0])==n,i10=i.slice(-1),i100=i.slice(-2);if(ord)return"other";return t0&&i10==1&&i100!=11||!t0?"one":"other"}});
|
||||
addLocaleData({"locale":"it","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return n==11||n==8||n==80||n==800?"many":"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"it-CH","parentLocale":"it"});
|
||||
addLocaleData({"locale":"it-SM","parentLocale":"it"});
|
||||
addLocaleData({"locale":"iu","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":n==2?"two":"other"}});
|
||||
addLocaleData({"locale":"iu-Latn","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"iw","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1);if(ord)return"other";return n==1&&v0?"one":i==2&&v0?"two":v0&&(n<0||n>10)&&t0&&n10==0?"many":"other"}});
|
||||
addLocaleData({"locale":"ja","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"jbo","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"jgo","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ji","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"jmc","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"jv","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"jw","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ka","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],i100=i.slice(-2);if(ord)return i==1?"one":i==0||(i100>=2&&i100<=20||i100==40||i100==60||i100==80)?"many":"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"kab","pluralRuleFunction":function (n,ord){if(ord)return"other";return n>=0&&n<2?"one":"other"}});
|
||||
addLocaleData({"locale":"kaj","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"kam","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"kcg","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"kde","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"kea","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"khq","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ki","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"kk","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n,n10=t0&&s[0].slice(-1);if(ord)return n10==6||n10==9||t0&&n10==0&&n!=0?"many":"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"kkj","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"kl","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"kln","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"km","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"kn","pluralRuleFunction":function (n,ord){if(ord)return"other";return n>=0&&n<=1?"one":"other"}});
|
||||
addLocaleData({"locale":"ko","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ko-KP","parentLocale":"ko"});
|
||||
addLocaleData({"locale":"kok","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ks","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ksb","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ksf","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ksh","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==0?"zero":n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ku","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"kw","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":n==2?"two":"other"}});
|
||||
addLocaleData({"locale":"ky","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"lag","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0];if(ord)return"other";return n==0?"zero":(i==0||i==1)&&n!=0?"one":"other"}});
|
||||
addLocaleData({"locale":"lb","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"lg","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"lkt","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ln","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==0||n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ln-AO","parentLocale":"ln"});
|
||||
addLocaleData({"locale":"ln-CF","parentLocale":"ln"});
|
||||
addLocaleData({"locale":"ln-CG","parentLocale":"ln"});
|
||||
addLocaleData({"locale":"lo","pluralRuleFunction":function (n,ord){if(ord)return n==1?"one":"other";return"other"}});
|
||||
addLocaleData({"locale":"lrc","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"lrc-IQ","parentLocale":"lrc"});
|
||||
addLocaleData({"locale":"lt","pluralRuleFunction":function (n,ord){var s=String(n).split("."),f=s[1]||"",t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return"other";return n10==1&&(n100<11||n100>19)?"one":n10>=2&&n10<=9&&(n100<11||n100>19)?"few":f!=0?"many":"other"}});
|
||||
addLocaleData({"locale":"lu","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"luo","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"luy","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"lv","pluralRuleFunction":function (n,ord){var s=String(n).split("."),f=s[1]||"",v=f.length,t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2),f100=f.slice(-2),f10=f.slice(-1);if(ord)return"other";return t0&&n10==0||n100>=11&&n100<=19||v==2&&(f100>=11&&f100<=19)?"zero":n10==1&&n100!=11||v==2&&f10==1&&f100!=11||v!=2&&f10==1?"one":"other"}});
|
||||
addLocaleData({"locale":"mas","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"mas-TZ","parentLocale":"mas"});
|
||||
addLocaleData({"locale":"mer","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"mfe","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"mg","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==0||n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"mgh","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"mgo","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"mk","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],f=s[1]||"",v0=!s[1],i10=i.slice(-1),i100=i.slice(-2),f10=f.slice(-1);if(ord)return i10==1&&i100!=11?"one":i10==2&&i100!=12?"two":(i10==7||i10==8)&&i100!=17&&i100!=18?"many":"other";return v0&&i10==1||f10==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ml","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"mn","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"mn-Mong","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"mo","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n100=t0&&s[0].slice(-2);if(ord)return n==1?"one":"other";return n==1&&v0?"one":!v0||n==0||n!=1&&(n100>=1&&n100<=19)?"few":"other"}});
|
||||
addLocaleData({"locale":"mr","pluralRuleFunction":function (n,ord){if(ord)return n==1?"one":n==2||n==3?"two":n==4?"few":"other";return n>=0&&n<=1?"one":"other"}});
|
||||
addLocaleData({"locale":"ms","pluralRuleFunction":function (n,ord){if(ord)return n==1?"one":"other";return"other"}});
|
||||
addLocaleData({"locale":"ms-Arab","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ms-BN","parentLocale":"ms"});
|
||||
addLocaleData({"locale":"ms-SG","parentLocale":"ms"});
|
||||
addLocaleData({"locale":"mt","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n,n100=t0&&s[0].slice(-2);if(ord)return"other";return n==1?"one":n==0||n100>=2&&n100<=10?"few":n100>=11&&n100<=19?"many":"other"}});
|
||||
addLocaleData({"locale":"mua","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"my","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"mzn","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"nah","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"naq","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":n==2?"two":"other"}});
|
||||
addLocaleData({"locale":"nb","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"nb-SJ","parentLocale":"nb"});
|
||||
addLocaleData({"locale":"nd","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ne","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n;if(ord)return t0&&n>=1&&n<=4?"one":"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ne-IN","parentLocale":"ne"});
|
||||
addLocaleData({"locale":"nl","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"nl-AW","parentLocale":"nl"});
|
||||
addLocaleData({"locale":"nl-BE","parentLocale":"nl"});
|
||||
addLocaleData({"locale":"nl-BQ","parentLocale":"nl"});
|
||||
addLocaleData({"locale":"nl-CW","parentLocale":"nl"});
|
||||
addLocaleData({"locale":"nl-SR","parentLocale":"nl"});
|
||||
addLocaleData({"locale":"nl-SX","parentLocale":"nl"});
|
||||
addLocaleData({"locale":"nmg","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"nn","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"nnh","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"no","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"nqo","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"nr","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"nso","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==0||n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"nus","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ny","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"nyn","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"om","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"om-KE","parentLocale":"om"});
|
||||
addLocaleData({"locale":"or","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"os","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"os-RU","parentLocale":"os"});
|
||||
addLocaleData({"locale":"pa","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==0||n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"pa-Arab","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"pa-Guru","parentLocale":"pa"});
|
||||
addLocaleData({"locale":"pap","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"pl","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],v0=!s[1],i10=i.slice(-1),i100=i.slice(-2);if(ord)return"other";return n==1&&v0?"one":v0&&(i10>=2&&i10<=4)&&(i100<12||i100>14)?"few":v0&&i!=1&&(i10==0||i10==1)||v0&&(i10>=5&&i10<=9)||v0&&(i100>=12&&i100<=14)?"many":"other"}});
|
||||
addLocaleData({"locale":"prg","pluralRuleFunction":function (n,ord){var s=String(n).split("."),f=s[1]||"",v=f.length,t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2),f100=f.slice(-2),f10=f.slice(-1);if(ord)return"other";return t0&&n10==0||n100>=11&&n100<=19||v==2&&(f100>=11&&f100<=19)?"zero":n10==1&&n100!=11||v==2&&f10==1&&f100!=11||v!=2&&f10==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ps","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"pt","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n;if(ord)return"other";return t0&&n>=0&&n<=2&&n!=2?"one":"other"}});
|
||||
addLocaleData({"locale":"pt-AO","parentLocale":"pt-PT"});
|
||||
addLocaleData({"locale":"pt-PT","parentLocale":"pt","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"pt-CV","parentLocale":"pt-PT"});
|
||||
addLocaleData({"locale":"pt-GW","parentLocale":"pt-PT"});
|
||||
addLocaleData({"locale":"pt-MO","parentLocale":"pt-PT"});
|
||||
addLocaleData({"locale":"pt-MZ","parentLocale":"pt-PT"});
|
||||
addLocaleData({"locale":"pt-ST","parentLocale":"pt-PT"});
|
||||
addLocaleData({"locale":"pt-TL","parentLocale":"pt-PT"});
|
||||
addLocaleData({"locale":"qu","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"qu-BO","parentLocale":"qu"});
|
||||
addLocaleData({"locale":"qu-EC","parentLocale":"qu"});
|
||||
addLocaleData({"locale":"rm","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"rn","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ro","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n100=t0&&s[0].slice(-2);if(ord)return n==1?"one":"other";return n==1&&v0?"one":!v0||n==0||n!=1&&(n100>=1&&n100<=19)?"few":"other"}});
|
||||
addLocaleData({"locale":"ro-MD","parentLocale":"ro"});
|
||||
addLocaleData({"locale":"rof","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ru","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],v0=!s[1],i10=i.slice(-1),i100=i.slice(-2);if(ord)return"other";return v0&&i10==1&&i100!=11?"one":v0&&(i10>=2&&i10<=4)&&(i100<12||i100>14)?"few":v0&&i10==0||v0&&(i10>=5&&i10<=9)||v0&&(i100>=11&&i100<=14)?"many":"other"}});
|
||||
addLocaleData({"locale":"ru-BY","parentLocale":"ru"});
|
||||
addLocaleData({"locale":"ru-KG","parentLocale":"ru"});
|
||||
addLocaleData({"locale":"ru-KZ","parentLocale":"ru"});
|
||||
addLocaleData({"locale":"ru-MD","parentLocale":"ru"});
|
||||
addLocaleData({"locale":"ru-UA","parentLocale":"ru"});
|
||||
addLocaleData({"locale":"rw","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"rwk","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"sah","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"saq","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"sbp","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"sdh","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"se","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":n==2?"two":"other"}});
|
||||
addLocaleData({"locale":"se-FI","parentLocale":"se"});
|
||||
addLocaleData({"locale":"se-SE","parentLocale":"se"});
|
||||
addLocaleData({"locale":"seh","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ses","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"sg","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"sh","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],f=s[1]||"",v0=!s[1],i10=i.slice(-1),i100=i.slice(-2),f10=f.slice(-1),f100=f.slice(-2);if(ord)return"other";return v0&&i10==1&&i100!=11||f10==1&&f100!=11?"one":v0&&(i10>=2&&i10<=4)&&(i100<12||i100>14)||f10>=2&&f10<=4&&(f100<12||f100>14)?"few":"other"}});
|
||||
addLocaleData({"locale":"shi","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n;if(ord)return"other";return n>=0&&n<=1?"one":t0&&n>=2&&n<=10?"few":"other"}});
|
||||
addLocaleData({"locale":"shi-Latn","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"shi-Tfng","parentLocale":"shi"});
|
||||
addLocaleData({"locale":"si","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],f=s[1]||"";if(ord)return"other";return n==0||n==1||i==0&&f==1?"one":"other"}});
|
||||
addLocaleData({"locale":"sk","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],v0=!s[1];if(ord)return"other";return n==1&&v0?"one":i>=2&&i<=4&&v0?"few":!v0?"many":"other"}});
|
||||
addLocaleData({"locale":"sl","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],v0=!s[1],i100=i.slice(-2);if(ord)return"other";return v0&&i100==1?"one":v0&&i100==2?"two":v0&&(i100==3||i100==4)||!v0?"few":"other"}});
|
||||
addLocaleData({"locale":"sma","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":n==2?"two":"other"}});
|
||||
addLocaleData({"locale":"smi","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":n==2?"two":"other"}});
|
||||
addLocaleData({"locale":"smj","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":n==2?"two":"other"}});
|
||||
addLocaleData({"locale":"smn","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":n==2?"two":"other"}});
|
||||
addLocaleData({"locale":"sms","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":n==2?"two":"other"}});
|
||||
addLocaleData({"locale":"sn","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"so","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"so-DJ","parentLocale":"so"});
|
||||
addLocaleData({"locale":"so-ET","parentLocale":"so"});
|
||||
addLocaleData({"locale":"so-KE","parentLocale":"so"});
|
||||
addLocaleData({"locale":"sq","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n==1?"one":n10==4&&n100!=14?"many":"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"sq-MK","parentLocale":"sq"});
|
||||
addLocaleData({"locale":"sq-XK","parentLocale":"sq"});
|
||||
addLocaleData({"locale":"sr","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],f=s[1]||"",v0=!s[1],i10=i.slice(-1),i100=i.slice(-2),f10=f.slice(-1),f100=f.slice(-2);if(ord)return"other";return v0&&i10==1&&i100!=11||f10==1&&f100!=11?"one":v0&&(i10>=2&&i10<=4)&&(i100<12||i100>14)||f10>=2&&f10<=4&&(f100<12||f100>14)?"few":"other"}});
|
||||
addLocaleData({"locale":"sr-Cyrl","parentLocale":"sr"});
|
||||
addLocaleData({"locale":"sr-Cyrl-BA","parentLocale":"sr-Cyrl"});
|
||||
addLocaleData({"locale":"sr-Cyrl-ME","parentLocale":"sr-Cyrl"});
|
||||
addLocaleData({"locale":"sr-Cyrl-XK","parentLocale":"sr-Cyrl"});
|
||||
addLocaleData({"locale":"sr-Latn","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"sr-Latn-BA","parentLocale":"sr-Latn"});
|
||||
addLocaleData({"locale":"sr-Latn-ME","parentLocale":"sr-Latn"});
|
||||
addLocaleData({"locale":"sr-Latn-XK","parentLocale":"sr-Latn"});
|
||||
addLocaleData({"locale":"ss","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ssy","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"st","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"sv","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return(n10==1||n10==2)&&n100!=11&&n100!=12?"one":"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"sv-AX","parentLocale":"sv"});
|
||||
addLocaleData({"locale":"sv-FI","parentLocale":"sv"});
|
||||
addLocaleData({"locale":"sw","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"sw-CD","parentLocale":"sw"});
|
||||
addLocaleData({"locale":"sw-KE","parentLocale":"sw"});
|
||||
addLocaleData({"locale":"sw-UG","parentLocale":"sw"});
|
||||
addLocaleData({"locale":"syr","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ta","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ta-LK","parentLocale":"ta"});
|
||||
addLocaleData({"locale":"ta-MY","parentLocale":"ta"});
|
||||
addLocaleData({"locale":"ta-SG","parentLocale":"ta"});
|
||||
addLocaleData({"locale":"te","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"teo","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"teo-KE","parentLocale":"teo"});
|
||||
addLocaleData({"locale":"th","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"ti","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==0||n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"ti-ER","parentLocale":"ti"});
|
||||
addLocaleData({"locale":"tig","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"tk","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"tl","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],f=s[1]||"",v0=!s[1],i10=i.slice(-1),f10=f.slice(-1);if(ord)return n==1?"one":"other";return v0&&(i==1||i==2||i==3)||v0&&i10!=4&&i10!=6&&i10!=9||!v0&&f10!=4&&f10!=6&&f10!=9?"one":"other"}});
|
||||
addLocaleData({"locale":"tn","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"to","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"tr","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"tr-CY","parentLocale":"tr"});
|
||||
addLocaleData({"locale":"ts","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"twq","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"tzm","pluralRuleFunction":function (n,ord){var s=String(n).split("."),t0=Number(s[0])==n;if(ord)return"other";return n==0||n==1||t0&&n>=11&&n<=99?"one":"other"}});
|
||||
addLocaleData({"locale":"ug","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"uk","pluralRuleFunction":function (n,ord){var s=String(n).split("."),i=s[0],v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2),i10=i.slice(-1),i100=i.slice(-2);if(ord)return n10==3&&n100!=13?"few":"other";return v0&&i10==1&&i100!=11?"one":v0&&(i10>=2&&i10<=4)&&(i100<12||i100>14)?"few":v0&&i10==0||v0&&(i10>=5&&i10<=9)||v0&&(i100>=11&&i100<=14)?"many":"other"}});
|
||||
addLocaleData({"locale":"ur","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"ur-IN","parentLocale":"ur"});
|
||||
addLocaleData({"locale":"uz","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"uz-Arab","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"uz-Cyrl","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"uz-Latn","parentLocale":"uz"});
|
||||
addLocaleData({"locale":"vai","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"vai-Latn","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"vai-Vaii","parentLocale":"vai"});
|
||||
addLocaleData({"locale":"ve","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"vi","pluralRuleFunction":function (n,ord){if(ord)return n==1?"one":"other";return"other"}});
|
||||
addLocaleData({"locale":"vo","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"vun","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"wa","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==0||n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"wae","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"wo","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"xh","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"xog","pluralRuleFunction":function (n,ord){if(ord)return"other";return n==1?"one":"other"}});
|
||||
addLocaleData({"locale":"yav","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"yi","pluralRuleFunction":function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"}});
|
||||
addLocaleData({"locale":"yo","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"yo-BJ","parentLocale":"yo"});
|
||||
addLocaleData({"locale":"zgh","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"zh","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"zh-Hans","parentLocale":"zh"});
|
||||
addLocaleData({"locale":"zh-Hans-HK","parentLocale":"zh-Hans"});
|
||||
addLocaleData({"locale":"zh-Hans-MO","parentLocale":"zh-Hans"});
|
||||
addLocaleData({"locale":"zh-Hans-SG","parentLocale":"zh-Hans"});
|
||||
addLocaleData({"locale":"zh-Hant","pluralRuleFunction":function (n,ord){if(ord)return"other";return"other"}});
|
||||
addLocaleData({"locale":"zh-Hant-HK","parentLocale":"zh-Hant"});
|
||||
addLocaleData({"locale":"zh-Hant-MO","parentLocale":"zh-Hant-HK"});
|
||||
addLocaleData({"locale":"zu","pluralRuleFunction":function (n,ord){if(ord)return"other";return n>=0&&n<=1?"one":"other"}});
|
26
packages/kbn-i18n/src/index.js
Normal file
26
packages/kbn-i18n/src/index.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as i18nCore from './core/i18n';
|
||||
import * as loader from './core/loader';
|
||||
|
||||
export { formats } from './core/formats';
|
||||
|
||||
export const i18n = i18nCore;
|
||||
export const i18nLoader = loader;
|
50
packages/kbn-i18n/src/react/context.js
Normal file
50
packages/kbn-i18n/src/react/context.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { intlShape } from 'react-intl';
|
||||
|
||||
/**
|
||||
* Provides intl context to a child component using React render callback pattern
|
||||
* @example
|
||||
* <I18nContext>
|
||||
* {intl => (
|
||||
* <input
|
||||
* placeholder={intl.formatMessage({
|
||||
id: 'my-id',
|
||||
defaultMessage: 'my default message',
|
||||
})}
|
||||
* />
|
||||
* )}
|
||||
* </I18nContext>
|
||||
*/
|
||||
export class I18nContext extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
};
|
||||
|
||||
render() {
|
||||
return this.props.children(this.context.intl);
|
||||
}
|
||||
}
|
32
packages/kbn-i18n/src/react/index.js
Normal file
32
packages/kbn-i18n/src/react/index.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
intlShape,
|
||||
FormattedDate,
|
||||
FormattedTime,
|
||||
FormattedRelative,
|
||||
FormattedNumber,
|
||||
FormattedPlural,
|
||||
FormattedMessage,
|
||||
FormattedHTMLMessage,
|
||||
} from 'react-intl';
|
||||
|
||||
export { I18nProvider } from './provider';
|
||||
export { I18nContext } from './context';
|
51
packages/kbn-i18n/src/react/provider.js
Normal file
51
packages/kbn-i18n/src/react/provider.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import * as i18n from '../core/i18n';
|
||||
|
||||
/**
|
||||
* The library uses the provider pattern to scope an i18n context to a tree
|
||||
* of components. This component is used to setup the i18n context for a tree.
|
||||
* IntlProvider should wrap react app's root component (inside each react render method).
|
||||
*/
|
||||
export class I18nProvider extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.object,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={i18n.getLocale()}
|
||||
messages={i18n.getMessages()}
|
||||
defaultLocale={i18n.getDefaultLocale()}
|
||||
formats={i18n.getFormats()}
|
||||
defaultFormats={i18n.getFormats()}
|
||||
>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
1745
packages/kbn-i18n/yarn.lock
Normal file
1745
packages/kbn-i18n/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
|
@ -17,8 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import { mkdirp as mkdirpNode } from 'mkdirp';
|
||||
|
||||
|
@ -123,9 +121,7 @@ export default function (kibana) {
|
|||
};
|
||||
},
|
||||
|
||||
translations: [
|
||||
resolve(__dirname, './translations/en.json')
|
||||
],
|
||||
translations: [],
|
||||
|
||||
mappings,
|
||||
uiSettingDefaults: getUiSettingDefaults(),
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"UI-WELCOME_MESSAGE": "Loading Kibana",
|
||||
"UI-WELCOME_ERROR": "Kibana did not load properly. Check the server output for more information."
|
||||
}
|
|
@ -53,3 +53,4 @@ import '../validate_date_interval';
|
|||
import '../watch_multi';
|
||||
import '../courier/saved_object/ui/saved_object_save_as_checkbox';
|
||||
import '../react_components';
|
||||
import '../i18n';
|
||||
|
|
36
src/ui/public/i18n/index.js
Normal file
36
src/ui/public/i18n/index.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { AngularI18n } from '@kbn/i18n';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { metadata } from 'ui/metadata';
|
||||
|
||||
const {
|
||||
i18nProvider,
|
||||
i18nFilter,
|
||||
i18nDirective,
|
||||
} = AngularI18n;
|
||||
|
||||
uiModules.get('i18n')
|
||||
.provider('i18n', i18nProvider)
|
||||
.filter('i18n', i18nFilter)
|
||||
.directive('i18nId', i18nDirective)
|
||||
.config((i18nProvider) => {
|
||||
i18nProvider.init(metadata.translations);
|
||||
});
|
|
@ -35,9 +35,7 @@ export const UI_EXPORT_DEFAULTS = {
|
|||
'moment-timezone$': resolve(ROOT, 'webpackShims/moment-timezone')
|
||||
},
|
||||
|
||||
translationPaths: [
|
||||
resolve(ROOT, 'src/ui/ui_i18n/translations/en.json'),
|
||||
],
|
||||
translationPaths: [],
|
||||
|
||||
appExtensions: {
|
||||
fieldFormatEditors: [
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"test_plugin_1-NO_SSL": "Dont run the DE dev server using HTTPS",
|
||||
"test_plugin_1-DEV": "Run the DE server with development mode defaults"
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"test_plugin_1-NO_SSL": "Dont run the dev server using HTTPS",
|
||||
"test_plugin_1-DEV": "Run the server with development mode defaults",
|
||||
"test_plugin_1-NO_RUN_SERVER": "Dont run the dev server",
|
||||
"test_plugin_1-HOME": "Run along home now!"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"test_plugin_1-NO_SSL": "Dont run the es-ES dev server using HTTPS! I am registered afterwards!"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"test_plugin_1-NO_SSL": "Dont run the DE dev server using HTTPS! I am registered afterwards!"
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"test_plugin_2-XXXXXX": "This is XXXXXX string",
|
||||
"test_plugin_2-YYYY_PPPP": "This is YYYY_PPPP string",
|
||||
"test_plugin_2-FFFFFFFFFFFF": "This is FFFFFFFFFFFF string",
|
||||
"test_plugin_2-ZZZ": "This is ZZZ string"
|
||||
}
|
|
@ -1,245 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import _ from 'lodash';
|
||||
import { join } from 'path';
|
||||
|
||||
import { I18n } from '../';
|
||||
|
||||
const FIXTURES = join(__dirname, 'fixtures');
|
||||
|
||||
describe('ui/i18n module', function () {
|
||||
|
||||
describe('one plugin', function () {
|
||||
|
||||
const i18nObj = new I18n();
|
||||
|
||||
before('registerTranslations - one plugin', function () {
|
||||
const pluginName = 'test_plugin_1';
|
||||
const pluginTranslationPath = join(FIXTURES, 'translations', pluginName);
|
||||
const translationFiles = [
|
||||
join(pluginTranslationPath, 'de.json'),
|
||||
join(pluginTranslationPath, 'en.json')
|
||||
];
|
||||
const filesLen = translationFiles.length;
|
||||
for (let indx = 0; indx < filesLen; indx++) {
|
||||
i18nObj.registerTranslations(translationFiles[indx]);
|
||||
}
|
||||
});
|
||||
|
||||
describe('getTranslations', function () {
|
||||
|
||||
it('should return the translations for en locale as registered', function () {
|
||||
const languageTag = ['en'];
|
||||
const expectedTranslationJson = {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the dev server using HTTPS',
|
||||
'test_plugin_1-DEV': 'Run the server with development mode defaults',
|
||||
'test_plugin_1-NO_RUN_SERVER': 'Dont run the dev server',
|
||||
'test_plugin_1-HOME': 'Run along home now!'
|
||||
};
|
||||
return checkTranslations(expectedTranslationJson, languageTag, i18nObj);
|
||||
});
|
||||
|
||||
it('should return the translations for de locale as registered', function () {
|
||||
const languageTag = ['de'];
|
||||
const expectedTranslationJson = {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the DE dev server using HTTPS',
|
||||
'test_plugin_1-DEV': 'Run the DE server with development mode defaults'
|
||||
};
|
||||
return checkTranslations(expectedTranslationJson, languageTag, i18nObj);
|
||||
});
|
||||
|
||||
it('should pick the highest priority language for which translations exist', function () {
|
||||
const languageTags = ['es-ES', 'de', 'en'];
|
||||
const expectedTranslations = {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the DE dev server using HTTPS',
|
||||
'test_plugin_1-DEV': 'Run the DE server with development mode defaults',
|
||||
};
|
||||
return checkTranslations(expectedTranslations, languageTags, i18nObj);
|
||||
});
|
||||
|
||||
it('should return translations for highest priority locale where best case match is chosen from registered locales', function () {
|
||||
const languageTags = ['es', 'de'];
|
||||
const expectedTranslations = {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the es-ES dev server using HTTPS! I am registered afterwards!'
|
||||
};
|
||||
i18nObj.registerTranslations(join(FIXTURES, 'translations', 'test_plugin_1', 'es-ES.json'));
|
||||
return checkTranslations(expectedTranslations, languageTags, i18nObj);
|
||||
});
|
||||
|
||||
it('should return an empty object for locales with no translations', function () {
|
||||
const languageTags = ['ja-JA', 'fr'];
|
||||
return checkTranslations({}, languageTags, i18nObj);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('getTranslationsForDefaultLocale', function () {
|
||||
|
||||
it('should return translations for default locale which is set to the en locale', function () {
|
||||
const i18nObj1 = new I18n('en');
|
||||
const expectedTranslations = {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the dev server using HTTPS',
|
||||
'test_plugin_1-DEV': 'Run the server with development mode defaults',
|
||||
'test_plugin_1-NO_RUN_SERVER': 'Dont run the dev server',
|
||||
'test_plugin_1-HOME': 'Run along home now!'
|
||||
};
|
||||
i18nObj1.registerTranslations(join(FIXTURES, 'translations', 'test_plugin_1', 'en.json'));
|
||||
return checkTranslationsForDefaultLocale(expectedTranslations, i18nObj1);
|
||||
});
|
||||
|
||||
it('should return translations for default locale which is set to the de locale', function () {
|
||||
const i18nObj1 = new I18n('de');
|
||||
const expectedTranslations = {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the DE dev server using HTTPS',
|
||||
'test_plugin_1-DEV': 'Run the DE server with development mode defaults',
|
||||
};
|
||||
i18nObj1.registerTranslations(join(FIXTURES, 'translations', 'test_plugin_1', 'de.json'));
|
||||
return checkTranslationsForDefaultLocale(expectedTranslations, i18nObj1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('getAllTranslations', function () {
|
||||
|
||||
it('should return all translations', function () {
|
||||
const expectedTranslations = {
|
||||
de: {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the DE dev server using HTTPS',
|
||||
'test_plugin_1-DEV': 'Run the DE server with development mode defaults'
|
||||
},
|
||||
en: {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the dev server using HTTPS',
|
||||
'test_plugin_1-DEV': 'Run the server with development mode defaults',
|
||||
'test_plugin_1-NO_RUN_SERVER': 'Dont run the dev server',
|
||||
'test_plugin_1-HOME': 'Run along home now!'
|
||||
},
|
||||
'es-ES': {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the es-ES dev server using HTTPS! I am registered afterwards!'
|
||||
}
|
||||
};
|
||||
return checkAllTranslations(expectedTranslations, i18nObj);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('multiple plugins', function () {
|
||||
|
||||
const i18nObj = new I18n();
|
||||
|
||||
beforeEach('registerTranslations - multiple plugin', function () {
|
||||
const pluginTranslationPath = join(FIXTURES, 'translations');
|
||||
const translationFiles = [
|
||||
join(pluginTranslationPath, 'test_plugin_1', 'de.json'),
|
||||
join(pluginTranslationPath, 'test_plugin_1', 'en.json'),
|
||||
join(pluginTranslationPath, 'test_plugin_2', 'en.json')
|
||||
];
|
||||
const filesLen = translationFiles.length;
|
||||
for (let indx = 0; indx < filesLen; indx++) {
|
||||
i18nObj.registerTranslations(translationFiles[indx]);
|
||||
}
|
||||
});
|
||||
|
||||
describe('getTranslations', function () {
|
||||
|
||||
it('should return the translations for en locale as registered', function () {
|
||||
const languageTag = ['en'];
|
||||
const expectedTranslationJson = {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the dev server using HTTPS',
|
||||
'test_plugin_1-DEV': 'Run the server with development mode defaults',
|
||||
'test_plugin_1-NO_RUN_SERVER': 'Dont run the dev server',
|
||||
'test_plugin_1-HOME': 'Run along home now!',
|
||||
'test_plugin_2-XXXXXX': 'This is XXXXXX string',
|
||||
'test_plugin_2-YYYY_PPPP': 'This is YYYY_PPPP string',
|
||||
'test_plugin_2-FFFFFFFFFFFF': 'This is FFFFFFFFFFFF string',
|
||||
'test_plugin_2-ZZZ': 'This is ZZZ string'
|
||||
};
|
||||
return checkTranslations(expectedTranslationJson, languageTag, i18nObj);
|
||||
});
|
||||
|
||||
it('should return the translations for de locale as registered', function () {
|
||||
const languageTag = ['de'];
|
||||
const expectedTranslationJson = {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the DE dev server using HTTPS',
|
||||
'test_plugin_1-DEV': 'Run the DE server with development mode defaults'
|
||||
};
|
||||
return checkTranslations(expectedTranslationJson, languageTag, i18nObj);
|
||||
});
|
||||
|
||||
it('should return the most recently registered translation for a key that has multiple translations', function () {
|
||||
i18nObj.registerTranslations(join(FIXTURES, 'translations', 'test_plugin_2', 'de.json'));
|
||||
const languageTag = ['de'];
|
||||
const expectedTranslationJson = {
|
||||
'test_plugin_1-NO_SSL': 'Dont run the DE dev server using HTTPS! I am registered afterwards!',
|
||||
'test_plugin_1-DEV': 'Run the DE server with development mode defaults'
|
||||
};
|
||||
return checkTranslations(expectedTranslationJson, languageTag, i18nObj);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('registerTranslations', function () {
|
||||
|
||||
const i18nObj = new I18n();
|
||||
|
||||
it('should throw error when registering relative path', function () {
|
||||
return expect(i18nObj.registerTranslations).withArgs('./some/path').to.throwError();
|
||||
});
|
||||
|
||||
it('should throw error when registering empty filename', function () {
|
||||
return expect(i18nObj.registerTranslations).withArgs('').to.throwError();
|
||||
});
|
||||
|
||||
it('should throw error when registering filename with no extension', function () {
|
||||
return expect(i18nObj.registerTranslations).withArgs('file1').to.throwError();
|
||||
});
|
||||
|
||||
it('should throw error when registering filename with non JSON extension', function () {
|
||||
return expect(i18nObj.registerTranslations).withArgs('file1.txt').to.throwError();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function checkTranslations(expectedTranslations, languageTags, i18nObj) {
|
||||
return i18nObj.getTranslations(...languageTags)
|
||||
.then(function (actualTranslations) {
|
||||
expect(_.isEqual(actualTranslations, expectedTranslations)).to.be(true);
|
||||
});
|
||||
}
|
||||
|
||||
function checkAllTranslations(expectedTranslations, i18nObj) {
|
||||
return i18nObj.getAllTranslations()
|
||||
.then(function (actualTranslations) {
|
||||
expect(_.isEqual(actualTranslations, expectedTranslations)).to.be(true);
|
||||
});
|
||||
}
|
||||
|
||||
function checkTranslationsForDefaultLocale(expectedTranslations, i18nObj) {
|
||||
return i18nObj.getTranslationsForDefaultLocale()
|
||||
.then(function (actualTranslations) {
|
||||
expect(_.isEqual(actualTranslations, expectedTranslations)).to.be(true);
|
||||
});
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import Promise from 'bluebird';
|
||||
import { readFile } from 'fs';
|
||||
import _ from 'lodash';
|
||||
|
||||
const asyncReadFile = Promise.promisify(readFile);
|
||||
|
||||
const TRANSLATION_FILE_EXTENSION = '.json';
|
||||
|
||||
function getLocaleFromFileName(fullFileName) {
|
||||
if (_.isEmpty(fullFileName)) throw new Error('Filename empty');
|
||||
|
||||
const fileExt = path.extname(fullFileName);
|
||||
if (fileExt.length <= 0 || fileExt !== TRANSLATION_FILE_EXTENSION) {
|
||||
throw new Error('Translations must be in a JSON file. File being registered is ' + fullFileName);
|
||||
}
|
||||
|
||||
return path.basename(fullFileName, TRANSLATION_FILE_EXTENSION);
|
||||
}
|
||||
|
||||
function getBestLocaleMatch(languageTag, registeredLocales) {
|
||||
if (_.contains(registeredLocales, languageTag)) {
|
||||
return languageTag;
|
||||
}
|
||||
|
||||
// Find the first registered locale that begins with one of the language codes from the provided language tag.
|
||||
// For example, if there is an 'en' language code, it would match an 'en-US' registered locale.
|
||||
const languageCode = _.first(languageTag.split('-')) || [];
|
||||
return _.find(registeredLocales, (locale) => _.startsWith(locale, languageCode));
|
||||
}
|
||||
|
||||
export class I18n {
|
||||
static async getAllTranslationsFromPaths(paths) {
|
||||
const i18n = new I18n();
|
||||
|
||||
paths.forEach(path => {
|
||||
i18n.registerTranslations(path);
|
||||
});
|
||||
|
||||
return await i18n.getAllTranslations();
|
||||
}
|
||||
|
||||
_registeredTranslations = {};
|
||||
|
||||
constructor(defaultLocale = 'en') {
|
||||
this._defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all translations for registered locales
|
||||
* @return {Promise<Object>} translations - A Promise object where keys are
|
||||
* the locale and values are Objects
|
||||
* of translation keys and translations
|
||||
*/
|
||||
getAllTranslations() {
|
||||
const localeTranslations = {};
|
||||
|
||||
const locales = this._getRegisteredTranslationLocales();
|
||||
const translations = _.map(locales, (locale) => {
|
||||
return this._getTranslationsForLocale(locale)
|
||||
.then(function (translations) {
|
||||
localeTranslations[locale] = translations;
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(translations)
|
||||
.then(() => _.assign({}, localeTranslations));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return translations for a suitable locale from a user side locale list
|
||||
* @param {...string} languageTags - BCP 47 language tags. The tags are listed in priority order as set in the Accept-Language header.
|
||||
* @returns {Promise<Object>} translations - promise for an object where
|
||||
* keys are translation keys and
|
||||
* values are translations
|
||||
* This object will contain all registered translations for the highest priority locale which is registered with the i18n module.
|
||||
* This object can be empty if no locale in the language tags can be matched against the registered locales.
|
||||
*/
|
||||
getTranslations(...languageTags) {
|
||||
const locale = this._getTranslationLocale(languageTags);
|
||||
return this._getTranslationsForLocale(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all translations registered for the default locale.
|
||||
* @returns {Promise<Object>} translations - promise for an object where
|
||||
* keys are translation keys and
|
||||
* values are translations
|
||||
*/
|
||||
getTranslationsForDefaultLocale() {
|
||||
return this._getTranslationsForLocale(this._defaultLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
* The translation file is registered with i18n plugin. The plugin contains a list of registered translation file paths per language.
|
||||
* @param {String} absolutePluginTranslationFilePath - Absolute path to the translation file to register.
|
||||
*/
|
||||
registerTranslations(absolutePluginTranslationFilePath) {
|
||||
if (!path.isAbsolute(absolutePluginTranslationFilePath)) {
|
||||
throw new TypeError(
|
||||
'Paths to translation files must be absolute. ' +
|
||||
`Got relative path: "${absolutePluginTranslationFilePath}"`
|
||||
);
|
||||
}
|
||||
|
||||
const locale = getLocaleFromFileName(absolutePluginTranslationFilePath);
|
||||
|
||||
this._registeredTranslations[locale] =
|
||||
_.uniq(_.get(this._registeredTranslations, locale, []).concat(absolutePluginTranslationFilePath));
|
||||
}
|
||||
|
||||
_getRegisteredTranslationLocales() {
|
||||
return Object.keys(this._registeredTranslations);
|
||||
}
|
||||
|
||||
_getTranslationLocale(languageTags) {
|
||||
let locale = '';
|
||||
const registeredLocales = this._getRegisteredTranslationLocales();
|
||||
_.forEach(languageTags, (tag) => {
|
||||
locale = locale || getBestLocaleMatch(tag, registeredLocales);
|
||||
});
|
||||
return locale;
|
||||
}
|
||||
|
||||
_getTranslationsForLocale(locale) {
|
||||
if (!this._registeredTranslations.hasOwnProperty(locale)) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
const translationFiles = this._registeredTranslations[locale];
|
||||
const translations = _.map(translationFiles, (filename) => {
|
||||
return asyncReadFile(filename, 'utf8')
|
||||
.then(fileContents => JSON.parse(fileContents))
|
||||
.catch(SyntaxError, function () {
|
||||
throw new Error('Invalid json in ' + filename);
|
||||
})
|
||||
.catch(function () {
|
||||
throw new Error('Cannot read file ' + filename);
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(translations)
|
||||
.then(translations => _.assign({}, ...translations));
|
||||
}
|
||||
}
|
|
@ -17,5 +17,48 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { I18n } from './i18n';
|
||||
export { uiI18nMixin } from './ui_i18n_mixin';
|
||||
/**
|
||||
@typedef Messages - messages tree, where leafs are translated strings
|
||||
@type {object<string, object>}
|
||||
@property {string} [locale] - locale of the messages
|
||||
@property {object} [formats] - set of options to the underlying formatter
|
||||
*/
|
||||
|
||||
import { i18nLoader } from '@kbn/i18n';
|
||||
|
||||
export function uiI18nMixin(kbnServer, server, config) {
|
||||
const defaultLocale = config.get('i18n.defaultLocale');
|
||||
const { translationPaths = [] } = kbnServer.uiExports;
|
||||
|
||||
i18nLoader.registerTranslationFiles(translationPaths);
|
||||
|
||||
/**
|
||||
* Fetch the translations matching the Accept-Language header for a requests.
|
||||
* @name request.getUiTranslations
|
||||
* @returns {Promise<Messages>} translations - translation messages
|
||||
*/
|
||||
server.decorate('request', 'getUiTranslations', async function () {
|
||||
const header = this.headers['accept-language'];
|
||||
|
||||
const [defaultTranslations, requestedTranslations] = await Promise.all([
|
||||
i18nLoader.getTranslationsByLocale(defaultLocale),
|
||||
i18nLoader.getTranslationsByLanguageHeader(header),
|
||||
]);
|
||||
|
||||
return {
|
||||
locale: defaultLocale,
|
||||
...defaultTranslations,
|
||||
...requestedTranslations,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Return all translations for registered locales
|
||||
* @name server.getAllUiTranslations
|
||||
* @return {Promise<Map<string, Messages>>} translations - A Promise object
|
||||
* where keys are the locale and values are objects of translation messages
|
||||
*/
|
||||
server.decorate('server', 'getAllUiTranslations', async () => {
|
||||
return await i18nLoader.getAllTranslations();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"UI-WELCOME_MESSAGE": "Loading",
|
||||
"UI-WELCOME_ERROR": ""
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { defaults, compact } from 'lodash';
|
||||
import langParser from 'accept-language-parser';
|
||||
|
||||
import { I18n } from './i18n';
|
||||
|
||||
function acceptLanguageHeaderToBCP47Tags(header) {
|
||||
return langParser.parse(header).map(lang => (
|
||||
compact([lang.code, lang.region, lang.script]).join('-')
|
||||
));
|
||||
}
|
||||
|
||||
export function uiI18nMixin(kbnServer, server, config) {
|
||||
const defaultLocale = config.get('i18n.defaultLocale');
|
||||
|
||||
const i18n = new I18n(defaultLocale);
|
||||
const { translationPaths = [] } = kbnServer.uiExports;
|
||||
translationPaths.forEach(translationPath => {
|
||||
i18n.registerTranslations(translationPath);
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch the translations matching the Accept-Language header for a requests.
|
||||
* @name request.getUiTranslations
|
||||
* @returns {Promise<Object<id:string,value:string>>} translations
|
||||
*/
|
||||
server.decorate('request', 'getUiTranslations', async function () {
|
||||
const header = this.headers['accept-language'];
|
||||
const tags = acceptLanguageHeaderToBCP47Tags(header);
|
||||
|
||||
const requestedTranslations = await i18n.getTranslations(...tags);
|
||||
const defaultTranslations = await i18n.getTranslationsForDefaultLocale();
|
||||
|
||||
return defaults(
|
||||
{},
|
||||
requestedTranslations,
|
||||
defaultTranslations
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Return all translations for registered locales
|
||||
* @name server.getAllUiTranslations
|
||||
* @return {Promise<Object<locale:string,Object<id:string,value:string>>>}
|
||||
*/
|
||||
server.decorate('server', 'getAllUiTranslations', async () => {
|
||||
return await i18n.getAllTranslations();
|
||||
});
|
||||
|
||||
}
|
|
@ -17,17 +17,17 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import Handlebars from 'handlebars';
|
||||
import { createHash } from 'crypto';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export class AppBootstrap {
|
||||
constructor({ templateData, translations }) {
|
||||
this.templateData = templateData;
|
||||
this.translations = translations;
|
||||
this._rawTemplate = undefined;
|
||||
i18n.init(translations);
|
||||
}
|
||||
|
||||
async getJsFile() {
|
||||
|
@ -35,7 +35,7 @@ export class AppBootstrap {
|
|||
this._rawTemplate = await loadRawTemplate();
|
||||
}
|
||||
|
||||
Handlebars.registerHelper('i18n', key => _.get(this.translations, key, ''));
|
||||
Handlebars.registerHelper('i18n', (id, options) => i18n.translate(id, JSON.parse(options)));
|
||||
const template = Handlebars.compile(this._rawTemplate, {
|
||||
knownHelpers: { i18n: true },
|
||||
knownHelpersOnly: true,
|
||||
|
|
|
@ -23,7 +23,7 @@ import { resolve } from 'path';
|
|||
const mockTemplate = `
|
||||
{{appId}}
|
||||
{{bundlePath}}
|
||||
{{i18n 'foo'}}
|
||||
{{i18n 'foo' '{"defaultMessage": "bar"}'}}
|
||||
`;
|
||||
|
||||
const templatePath = resolve(__dirname, 'template.js.hbs');
|
||||
|
@ -131,6 +131,7 @@ describe('ui_render/AppBootstrap', () => {
|
|||
const config2 = {
|
||||
...mockConfig(),
|
||||
translations: {
|
||||
locale: 'en',
|
||||
foo: 'not translated foo'
|
||||
}
|
||||
};
|
||||
|
@ -147,6 +148,7 @@ describe('ui_render/AppBootstrap', () => {
|
|||
function mockConfig() {
|
||||
return {
|
||||
translations: {
|
||||
locale: 'en',
|
||||
foo: 'translated foo'
|
||||
},
|
||||
templateData: {
|
||||
|
|
|
@ -33,7 +33,7 @@ window.onload = function () {
|
|||
err.style['text-align'] = 'center';
|
||||
err.style['background'] = '#F44336';
|
||||
err.style['padding'] = '25px';
|
||||
err.innerText = '{{i18n 'UI-WELCOME_ERROR'}}';
|
||||
err.innerText = '{{i18n 'UI-WELCOME_ERROR' '{"defaultMessage": "Kibana did not load properly. Check the server output for more information."}'}}';
|
||||
|
||||
document.body.innerHTML = err.outerHTML;
|
||||
}
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { defaults, get } from 'lodash';
|
||||
import { defaults } from 'lodash';
|
||||
import { props, reduce as reduceAsync } from 'bluebird';
|
||||
import Boom from 'boom';
|
||||
import { resolve } from 'path';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AppBootstrap } from './bootstrap';
|
||||
|
||||
export function uiRenderMixin(kbnServer, server, config) {
|
||||
|
@ -141,6 +142,8 @@ export function uiRenderMixin(kbnServer, server, config) {
|
|||
const request = reply.request;
|
||||
const translations = await request.getUiTranslations();
|
||||
|
||||
i18n.init(translations);
|
||||
|
||||
return reply.view('ui_app', {
|
||||
app,
|
||||
kibanaPayload: await getKibanaPayload({
|
||||
|
@ -150,7 +153,7 @@ export function uiRenderMixin(kbnServer, server, config) {
|
|||
injectedVarsOverrides
|
||||
}),
|
||||
bundlePath: `${config.get('server.basePath')}/bundles`,
|
||||
i18n: key => get(translations, key, ''),
|
||||
i18n: (id, options) => i18n.translate(id, options),
|
||||
});
|
||||
} catch (err) {
|
||||
reply(err);
|
||||
|
|
|
@ -108,6 +108,6 @@ block content
|
|||
.kibanaWelcomeLogoCircle
|
||||
.kibanaWelcomeLogo
|
||||
.kibanaWelcomeText
|
||||
| #{i18n('UI-WELCOME_MESSAGE')}
|
||||
| #{i18n('UI-WELCOME_MESSAGE', { defaultMessage: 'Loading Kibana' })}
|
||||
|
||||
script(src='#{bundlePath}/app/#{app.getId()}/bootstrap.js')
|
||||
|
|
|
@ -17,11 +17,14 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// TODO: Integrate a new tool for translations checking
|
||||
// https://github.com/elastic/kibana/pull/19826
|
||||
import { i18nLoader } from '@kbn/i18n';
|
||||
|
||||
import { toArray } from 'rxjs/operators';
|
||||
import { fromRoot, formatListAsProse } from '../src/utils';
|
||||
import { findPluginSpecs } from '../src/plugin_discovery';
|
||||
import { collectUiExports } from '../src/ui';
|
||||
import { I18n } from '../src/ui/ui_i18n/i18n';
|
||||
|
||||
import * as i18nVerify from './utils/i18n_verify_keys';
|
||||
|
||||
|
@ -65,7 +68,7 @@ async function verifyTranslations(uiExports) {
|
|||
}
|
||||
|
||||
// get all of the translations from uiExports
|
||||
const translations = await I18n.getAllTranslationsFromPaths(uiExports.translationPaths);
|
||||
const translations = await i18nLoader.getAllTranslationsFromPaths(uiExports.translationPaths);
|
||||
const keysWithoutTranslations = Object.entries(
|
||||
i18nVerify.getNonTranslatedKeys(keysUsedInViews, translations)
|
||||
);
|
||||
|
|
47
yarn.lock
47
yarn.lock
|
@ -144,6 +144,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/i18n@link:packages/kbn-i18n":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/plugin-generator@link:packages/kbn-plugin-generator":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -441,9 +445,9 @@ abbrev@1.0.x:
|
|||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
|
||||
|
||||
accept-language-parser@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/accept-language-parser/-/accept-language-parser-1.2.0.tgz#6a18942acab3f090a4a09590e03101a99fa22bff"
|
||||
accept-language-parser@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/accept-language-parser/-/accept-language-parser-1.5.0.tgz#8877c54040a8dcb59e0a07d9c1fde42298334791"
|
||||
|
||||
accept@2.x.x:
|
||||
version "2.1.4"
|
||||
|
@ -6447,6 +6451,26 @@ interpret@^1.0.0:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
|
||||
|
||||
intl-format-cache@^2.0.5, intl-format-cache@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-2.1.0.tgz#04a369fecbfad6da6005bae1f14333332dcf9316"
|
||||
|
||||
intl-messageformat-parser@1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz#b43d45a97468cadbe44331d74bb1e8dea44fc075"
|
||||
|
||||
intl-messageformat@^2.0.0, intl-messageformat@^2.1.0, intl-messageformat@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-2.2.0.tgz#345bcd46de630b7683330c2e52177ff5eab484fc"
|
||||
dependencies:
|
||||
intl-messageformat-parser "1.4.0"
|
||||
|
||||
intl-relativeformat@^2.0.0, intl-relativeformat@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-2.1.0.tgz#010f1105802251f40ac47d0e3e1a201348a255df"
|
||||
dependencies:
|
||||
intl-messageformat "^2.0.0"
|
||||
|
||||
into-stream@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6"
|
||||
|
@ -6454,7 +6478,7 @@ into-stream@^3.1.0:
|
|||
from2 "^2.1.1"
|
||||
p-is-promise "^1.1.0"
|
||||
|
||||
invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
|
||||
invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
dependencies:
|
||||
|
@ -7596,6 +7620,12 @@ json5@^0.5.0, json5@^0.5.1:
|
|||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
|
||||
|
||||
json5@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
jsonfile@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
|
||||
|
@ -10586,6 +10616,15 @@ react-input-range@^1.3.0:
|
|||
autobind-decorator "^1.3.4"
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-intl@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-2.4.0.tgz#66c14dc9df9a73b2fbbfbd6021726e80a613eb15"
|
||||
dependencies:
|
||||
intl-format-cache "^2.0.5"
|
||||
intl-messageformat "^2.1.0"
|
||||
intl-relativeformat "^2.0.0"
|
||||
invariant "^2.1.1"
|
||||
|
||||
react-is@^16.4.0:
|
||||
version "16.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.0.tgz#cc9fdc855ac34d2e7d9d2eb7059bbc240d35ffcf"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue