mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
i18n engine typescript migration (#22441)
* configure typescript build, add necessary dependencies, change extensions, react migration * migrate lib files in root * update tests snapshots, resolve core loader, helper * fix types for core components * fix angular components * fix angular staff * use Messages type * first-upper-case letter while using classs * use stable latest babel, fix ts issues * optimize .babelrc * update lock file * Fix x-pack/yarn.lock * fix issue with unknown babel plugin * add babel-config.js file with babel configuration for i18n engine build process instead of .babelrc file to fix jest issue * Resolve comments * Fix babel config * Fix packages incompatibility issue * Fix tslint errors * Fix tests * Resolve comments * Fix types
This commit is contained in:
parent
6b3bc45b9a
commit
a002ee4369
39 changed files with 1793 additions and 1271 deletions
|
@ -210,8 +210,9 @@
|
|||
"@kbn/eslint-plugin-license-header": "link:packages/kbn-eslint-plugin-license-header",
|
||||
"@kbn/plugin-generator": "link:packages/kbn-plugin-generator",
|
||||
"@kbn/test": "link:packages/kbn-test",
|
||||
"@types/angular": "^1.6.50",
|
||||
"@types/angular-mocks": "^1.7.0",
|
||||
"@octokit/rest": "^15.10.0",
|
||||
"@types/angular": "^1.6.45",
|
||||
"@types/babel-core": "^6.25.5",
|
||||
"@types/bluebird": "^3.1.1",
|
||||
"@types/boom": "^7.2.0",
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"web": {
|
||||
"presets": ["@kbn/babel-preset/webpack_preset"]
|
||||
},
|
||||
"node": {
|
||||
"presets": ["@kbn/babel-preset/node_preset"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"browser": "../target/web/angular",
|
||||
"main": "../target/node/angular"
|
||||
"main": "../target/node/angular",
|
||||
"types": "./target/types/angular/index.d.ts"
|
||||
}
|
||||
|
|
51
packages/kbn-i18n/babel.config.js
Normal file
51
packages/kbn-i18n/babel.config.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.
|
||||
*/
|
||||
|
||||
// We can't use common Kibana presets here because of babel versions incompatibility
|
||||
module.exports = {
|
||||
plugins: ['@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-object-rest-spread'],
|
||||
presets: ['@babel/preset-react', '@babel/typescript'],
|
||||
env: {
|
||||
web: {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
browsers: ['last 2 versions', '> 5%', 'Safari 7'],
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
node: {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
node: 'current',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
ignore: ['**/*.test.ts', '**/*.test.tsx'],
|
||||
};
|
|
@ -2,30 +2,40 @@
|
|||
"name": "@kbn/i18n",
|
||||
"browser": "./target/web/browser.js",
|
||||
"main": "./target/node/index.js",
|
||||
"types": "./target/types/index.d.ts",
|
||||
"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",
|
||||
"build": "yarn build:web && yarn build:node && yarn build:types",
|
||||
"build:types": "tsc --emitDeclarationOnly",
|
||||
"build:web": "cross-env BABEL_ENV=web babel src --config-file ./babel.config.js --out-dir target/web --extensions \".ts,.js,.tsx\"",
|
||||
"build:node": "cross-env BABEL_ENV=node babel src --config-file ./babel.config.js --out-dir target/node --extensions \".ts,.js,.tsx\"",
|
||||
"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"
|
||||
"@babel/cli": "^7.1.0",
|
||||
"@babel/core": "^7.1.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.1.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.1.0",
|
||||
"@types/intl-relativeformat": "^2.1.0",
|
||||
"@types/json5": "^0.0.30",
|
||||
"@types/react-intl": "^2.3.11",
|
||||
"cross-env": "^5.2.0",
|
||||
"typescript": "^3.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"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",
|
||||
"json5": "^2.0.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.3.0",
|
||||
"react-intl": "^2.4.0"
|
||||
"react-intl": "^2.7.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"browser": "../target/web/react",
|
||||
"main": "../target/node/react"
|
||||
"main": "../target/node/react",
|
||||
"types": "./target/types/react/index.d.ts"
|
||||
}
|
||||
|
|
|
@ -19,27 +19,30 @@
|
|||
|
||||
import angular from 'angular';
|
||||
import 'angular-mocks';
|
||||
|
||||
import { i18nDirective } from './directive';
|
||||
import { i18nProvider } from './provider';
|
||||
import { I18nProvider } from './provider';
|
||||
|
||||
angular
|
||||
.module('app', [])
|
||||
.provider('i18n', i18nProvider)
|
||||
.provider('i18n', I18nProvider)
|
||||
.directive('i18nId', i18nDirective);
|
||||
|
||||
describe('i18nDirective', () => {
|
||||
let compile;
|
||||
let scope;
|
||||
let compile: angular.ICompileService;
|
||||
let scope: angular.IRootScopeService;
|
||||
|
||||
beforeEach(angular.mock.module('app'));
|
||||
beforeEach(
|
||||
angular.mock.inject(($compile, $rootScope) => {
|
||||
compile = $compile;
|
||||
scope = $rootScope.$new();
|
||||
})
|
||||
angular.mock.inject(
|
||||
($compile: angular.ICompileService, $rootScope: angular.IRootScopeService) => {
|
||||
compile = $compile;
|
||||
scope = $rootScope.$new();
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
it('inserts correct translation html content', () => {
|
||||
test('inserts correct translation html content', () => {
|
||||
const id = 'id';
|
||||
const defaultMessage = 'default-message';
|
||||
|
||||
|
@ -56,7 +59,7 @@ describe('i18nDirective', () => {
|
|||
expect(element.html()).toEqual(defaultMessage);
|
||||
});
|
||||
|
||||
it('inserts correct translation html content with values', () => {
|
||||
test('inserts correct translation html content with values', () => {
|
||||
const id = 'id';
|
||||
const defaultMessage = 'default-message {word}';
|
||||
const compiledContent = 'default-message word';
|
|
@ -17,7 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export function i18nDirective(i18n) {
|
||||
import { IDirective, IRootElementService, IScope } from 'angular';
|
||||
|
||||
import { I18nServiceType } from './provider';
|
||||
|
||||
export function i18nDirective(i18n: I18nServiceType): IDirective {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
|
@ -25,19 +29,18 @@ export function i18nDirective(i18n) {
|
|||
defaultMessage: '@i18nDefaultMessage',
|
||||
values: '=i18nValues',
|
||||
},
|
||||
link: function($scope, $element) {
|
||||
$scope.$watchGroup(['id', 'defaultMessage', 'values'], function([
|
||||
id,
|
||||
defaultMessage = '',
|
||||
values = {},
|
||||
]) {
|
||||
$element.html(
|
||||
i18n(id, {
|
||||
values,
|
||||
defaultMessage,
|
||||
})
|
||||
);
|
||||
});
|
||||
link($scope: IScope, $element: IRootElementService) {
|
||||
$scope.$watchGroup(
|
||||
['id', 'defaultMessage', 'values'],
|
||||
([id, defaultMessage = '', values = {}]) => {
|
||||
$element.html(
|
||||
i18n(id, {
|
||||
values,
|
||||
defaultMessage,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
|
@ -17,23 +17,24 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import angular from 'angular';
|
||||
import 'angular-mocks';
|
||||
import { i18nProvider } from './provider';
|
||||
import { i18nFilter } from './filter';
|
||||
import * as i18n from '../core/i18n';
|
||||
|
||||
jest.mock('../core/i18n', () => ({
|
||||
translate: jest.fn().mockImplementation(() => 'translation'),
|
||||
}));
|
||||
|
||||
import angular from 'angular';
|
||||
import 'angular-mocks';
|
||||
|
||||
import * as i18n from '../core/i18n';
|
||||
import { i18nFilter as angularI18nFilter } from './filter';
|
||||
import { I18nProvider, I18nServiceType } from './provider';
|
||||
|
||||
angular
|
||||
.module('app', [])
|
||||
.provider('i18n', i18nProvider)
|
||||
.filter('i18n', i18nFilter);
|
||||
.provider('i18n', I18nProvider)
|
||||
.filter('i18n', angularI18nFilter);
|
||||
|
||||
describe('i18nFilter', () => {
|
||||
let filter;
|
||||
let filter: I18nServiceType;
|
||||
|
||||
beforeEach(angular.mock.module('app'));
|
||||
beforeEach(
|
||||
|
@ -45,15 +46,15 @@ describe('i18nFilter', () => {
|
|||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('provides wrapper around i18n engine', () => {
|
||||
test('provides wrapper around i18n engine', () => {
|
||||
const id = 'id';
|
||||
const defaultMessage = 'default-message';
|
||||
const values = {};
|
||||
|
||||
const result = filter(id, { defaultMessage, values });
|
||||
|
||||
expect(result).toEqual('translation');
|
||||
expect(i18n.translate).toHaveBeenCalledTimes(1);
|
||||
expect(i18n.translate).toHaveBeenCalledWith(id, { defaultMessage, values });
|
||||
expect(result).toEqual('translation');
|
||||
});
|
||||
});
|
|
@ -17,8 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export function i18nFilter(i18n) {
|
||||
return function(id, { defaultMessage = '', values = {} } = {}) {
|
||||
import { I18nServiceType } from './provider';
|
||||
|
||||
export function i18nFilter(i18n: I18nServiceType) {
|
||||
return (id: string, { defaultMessage = '', values = {} } = {}) => {
|
||||
return i18n(id, {
|
||||
values,
|
||||
defaultMessage,
|
|
@ -17,6 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { i18nProvider } from './provider';
|
||||
export { I18nProvider } from './provider';
|
||||
export { i18nFilter } from './filter';
|
||||
export { i18nDirective } from './directive';
|
|
@ -19,36 +19,37 @@
|
|||
|
||||
import angular from 'angular';
|
||||
import 'angular-mocks';
|
||||
import { i18nProvider } from './provider';
|
||||
import * as i18n from '../core/i18n';
|
||||
|
||||
angular.module('app', []).provider('i18n', i18nProvider);
|
||||
import * as i18nCore from '../core/i18n';
|
||||
import { I18nProvider, I18nServiceType } from './provider';
|
||||
|
||||
angular.module('app', []).provider('i18n', I18nProvider);
|
||||
|
||||
describe('i18nProvider', () => {
|
||||
let provider;
|
||||
let service;
|
||||
let provider: I18nProvider;
|
||||
let service: I18nServiceType;
|
||||
|
||||
beforeEach(
|
||||
angular.mock.module('app', [
|
||||
'i18nProvider',
|
||||
i18n => {
|
||||
service = i18n;
|
||||
(i18n: I18nProvider) => {
|
||||
provider = i18n;
|
||||
},
|
||||
])
|
||||
);
|
||||
beforeEach(
|
||||
angular.mock.inject(i18n => {
|
||||
provider = i18n;
|
||||
angular.mock.inject((i18n: I18nServiceType) => {
|
||||
service = i18n;
|
||||
})
|
||||
);
|
||||
|
||||
it('provides wrapper around i18n engine', () => {
|
||||
expect(provider).toEqual(i18n.translate);
|
||||
test('provides wrapper around i18n engine', () => {
|
||||
expect(service).toEqual(i18nCore.translate);
|
||||
});
|
||||
|
||||
it('provides service wrapper around i18n engine', () => {
|
||||
const serviceMethodNames = Object.keys(service);
|
||||
const pluginMethodNames = Object.keys(i18n);
|
||||
test('provides service wrapper around i18n engine', () => {
|
||||
const serviceMethodNames = Object.keys(provider);
|
||||
const pluginMethodNames = Object.keys(i18nCore);
|
||||
|
||||
expect([...serviceMethodNames, 'translate'].sort()).toEqual(
|
||||
[...pluginMethodNames, '$get'].sort()
|
36
packages/kbn-i18n/src/angular/provider.ts
Normal file
36
packages/kbn-i18n/src/angular/provider.ts
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 * as i18n from '../core';
|
||||
|
||||
export type I18nServiceType = ReturnType<I18nProvider['$get']>;
|
||||
|
||||
export class I18nProvider implements angular.IServiceProvider {
|
||||
public addMessages = i18n.addMessages;
|
||||
public getMessages = i18n.getMessages;
|
||||
public setLocale = i18n.setLocale;
|
||||
public getLocale = i18n.getLocale;
|
||||
public setDefaultLocale = i18n.setDefaultLocale;
|
||||
public getDefaultLocale = i18n.getDefaultLocale;
|
||||
public setFormats = i18n.setFormats;
|
||||
public getFormats = i18n.getFormats;
|
||||
public getRegisteredLocales = i18n.getRegisteredLocales;
|
||||
public init = i18n.init;
|
||||
public $get = () => i18n.translate;
|
||||
}
|
|
@ -28,7 +28,7 @@
|
|||
* described in `options` section of [DateTimeFormat constructor].
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat}
|
||||
*/
|
||||
export const formats = {
|
||||
export const formats: Formats = {
|
||||
number: {
|
||||
currency: {
|
||||
style: 'currency',
|
||||
|
@ -84,3 +84,43 @@ export const formats = {
|
|||
},
|
||||
},
|
||||
};
|
||||
|
||||
interface NumberFormatOptions<TStyle extends string> extends Intl.NumberFormatOptions {
|
||||
style?: TStyle;
|
||||
localeMatcher?: 'lookup' | 'best fit';
|
||||
currencyDisplay?: 'symbol' | 'code' | 'name';
|
||||
}
|
||||
|
||||
export interface Formats {
|
||||
number?: Partial<{
|
||||
[key: string]: NumberFormatOptions<'currency' | 'percent' | 'decimal'>;
|
||||
currency: NumberFormatOptions<'currency'>;
|
||||
percent: NumberFormatOptions<'percent'>;
|
||||
}>;
|
||||
date?: Partial<{
|
||||
[key: string]: DateTimeFormatOptions;
|
||||
short: DateTimeFormatOptions;
|
||||
medium: DateTimeFormatOptions;
|
||||
long: DateTimeFormatOptions;
|
||||
full: DateTimeFormatOptions;
|
||||
}>;
|
||||
time?: Partial<{
|
||||
[key: string]: DateTimeFormatOptions;
|
||||
short: DateTimeFormatOptions;
|
||||
medium: DateTimeFormatOptions;
|
||||
long: DateTimeFormatOptions;
|
||||
full: DateTimeFormatOptions;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface DateTimeFormatOptions extends Intl.DateTimeFormatOptions {
|
||||
weekday?: 'narrow' | 'short' | 'long';
|
||||
era?: 'narrow' | 'short' | 'long';
|
||||
year?: 'numeric' | '2-digit';
|
||||
month?: 'numeric' | '2-digit' | 'narrow' | 'short' | 'long';
|
||||
day?: 'numeric' | '2-digit';
|
||||
hour?: 'numeric' | '2-digit';
|
||||
minute?: 'numeric' | '2-digit';
|
||||
second?: 'numeric' | '2-digit';
|
||||
timeZoneName?: 'short' | 'long';
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { isString, isObject, hasValues, unique, mergeAll } from './helper';
|
||||
import { hasValues, isObject, isString, mergeAll, unique } from './helper';
|
||||
|
||||
describe('I18n helper', () => {
|
||||
describe('isString', () => {
|
||||
|
@ -26,6 +26,7 @@ describe('I18n helper', () => {
|
|||
});
|
||||
|
||||
test('should return false for string object', () => {
|
||||
// tslint:disable-next-line:no-construct
|
||||
expect(isString(new String('test'))).toBe(false);
|
||||
});
|
||||
|
|
@ -17,15 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export const isString = value => typeof value === 'string';
|
||||
export const isString = (value: any): value is string => typeof value === 'string';
|
||||
|
||||
export const isObject = value => typeof value === 'object' && value !== null;
|
||||
export const isObject = (value: any): value is object =>
|
||||
typeof value === 'object' && value !== null;
|
||||
|
||||
export const hasValues = values => Object.keys(values).length > 0;
|
||||
export const hasValues = (values: any) => Object.keys(values).length > 0;
|
||||
|
||||
export const unique = (arr = []) => [...new Set(arr)];
|
||||
export const unique = <T>(arr: T[] = []): T[] => [...new Set(arr)];
|
||||
|
||||
const merge = (a, b) =>
|
||||
const merge = (a: any, b: any): { [k: string]: any } =>
|
||||
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 {
|
||||
|
@ -40,5 +41,5 @@ const merge = (a, b) =>
|
|||
};
|
||||
}, {});
|
||||
|
||||
export const mergeAll = (...sources) =>
|
||||
export const mergeAll = (...sources: any[]) =>
|
||||
sources.filter(isObject).reduce((acc, source) => merge(acc, source));
|
|
@ -17,11 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import * as i18nModule from './i18n';
|
||||
|
||||
describe('I18n engine', () => {
|
||||
let i18n;
|
||||
let i18n: typeof i18nModule;
|
||||
|
||||
beforeEach(() => {
|
||||
i18n = require('./i18n');
|
||||
i18n = require.requireActual('./i18n');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -181,11 +183,11 @@ describe('I18n engine', () => {
|
|||
|
||||
describe('setLocale', () => {
|
||||
test('should throw error if locale is not a non-empty string', () => {
|
||||
expect(() => i18n.setLocale(undefined)).toThrow();
|
||||
expect(() => i18n.setLocale(null)).toThrow();
|
||||
expect(() => i18n.setLocale(true)).toThrow();
|
||||
expect(() => i18n.setLocale(5)).toThrow();
|
||||
expect(() => i18n.setLocale({})).toThrow();
|
||||
expect(() => i18n.setLocale(undefined as any)).toThrow();
|
||||
expect(() => i18n.setLocale(null as any)).toThrow();
|
||||
expect(() => i18n.setLocale(true as any)).toThrow();
|
||||
expect(() => i18n.setLocale(5 as any)).toThrow();
|
||||
expect(() => i18n.setLocale({} as any)).toThrow();
|
||||
expect(() => i18n.setLocale('')).toThrow();
|
||||
});
|
||||
|
||||
|
@ -214,11 +216,11 @@ describe('I18n engine', () => {
|
|||
|
||||
describe('setDefaultLocale', () => {
|
||||
test('should throw error if locale is not a non-empty string', () => {
|
||||
expect(() => i18n.setDefaultLocale(undefined)).toThrow();
|
||||
expect(() => i18n.setDefaultLocale(null)).toThrow();
|
||||
expect(() => i18n.setDefaultLocale(true)).toThrow();
|
||||
expect(() => i18n.setDefaultLocale(5)).toThrow();
|
||||
expect(() => i18n.setDefaultLocale({})).toThrow();
|
||||
expect(() => i18n.setDefaultLocale(undefined as any)).toThrow();
|
||||
expect(() => i18n.setDefaultLocale(null as any)).toThrow();
|
||||
expect(() => i18n.setDefaultLocale(true as any)).toThrow();
|
||||
expect(() => i18n.setDefaultLocale(5 as any)).toThrow();
|
||||
expect(() => i18n.setDefaultLocale({} as any)).toThrow();
|
||||
expect(() => i18n.setDefaultLocale('')).toThrow();
|
||||
});
|
||||
|
||||
|
@ -265,16 +267,16 @@ describe('I18n engine', () => {
|
|||
|
||||
describe('setFormats', () => {
|
||||
test('should throw error if formats parameter is not a non-empty object', () => {
|
||||
expect(() => i18n.setFormats(undefined)).toThrow();
|
||||
expect(() => i18n.setFormats(null)).toThrow();
|
||||
expect(() => i18n.setFormats(true)).toThrow();
|
||||
expect(() => i18n.setFormats(5)).toThrow();
|
||||
expect(() => i18n.setFormats('foo')).toThrow();
|
||||
expect(() => i18n.setFormats({})).toThrow();
|
||||
expect(() => i18n.setFormats(undefined as any)).toThrow();
|
||||
expect(() => i18n.setFormats(null as any)).toThrow();
|
||||
expect(() => i18n.setFormats(true as any)).toThrow();
|
||||
expect(() => i18n.setFormats(5 as any)).toThrow();
|
||||
expect(() => i18n.setFormats('foo' as any)).toThrow();
|
||||
expect(() => i18n.setFormats({} as any)).toThrow();
|
||||
});
|
||||
|
||||
test('should merge current formats with a passed formats', () => {
|
||||
expect(i18n.getFormats().date.short).not.toEqual({
|
||||
expect(i18n.getFormats().date!.short).not.toEqual({
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
|
@ -290,7 +292,7 @@ describe('I18n engine', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(i18n.getFormats().date.short).toEqual({
|
||||
expect(i18n.getFormats().date!.short).toEqual({
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
|
@ -304,7 +306,7 @@ describe('I18n engine', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(i18n.getFormats().date.short).toEqual({
|
||||
expect(i18n.getFormats().date!.short).toEqual({
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
|
@ -323,12 +325,23 @@ describe('I18n engine', () => {
|
|||
const { formats } = require('./formats');
|
||||
|
||||
i18n.setFormats({
|
||||
foo: 'bar',
|
||||
number: {
|
||||
currency: {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.getFormats()).toEqual({
|
||||
...formats,
|
||||
foo: 'bar',
|
||||
number: {
|
||||
...formats.number,
|
||||
currency: {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -349,25 +362,28 @@ describe('I18n engine', () => {
|
|||
locale: 'ru',
|
||||
});
|
||||
|
||||
expect(i18n.getRegisteredLocales()).toContain('en', 'ru');
|
||||
expect(i18n.getRegisteredLocales()).toContain('en');
|
||||
expect(i18n.getRegisteredLocales()).toContain('ru');
|
||||
expect(i18n.getRegisteredLocales().length).toBe(2);
|
||||
|
||||
i18n.addMessages({
|
||||
locale: 'fr',
|
||||
});
|
||||
|
||||
expect(i18n.getRegisteredLocales()).toContain('en', 'ru', 'fr');
|
||||
expect(i18n.getRegisteredLocales()).toContain('en');
|
||||
expect(i18n.getRegisteredLocales()).toContain('fr');
|
||||
expect(i18n.getRegisteredLocales()).toContain('ru');
|
||||
expect(i18n.getRegisteredLocales().length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('translate', () => {
|
||||
test('should throw error if id is not a non-empty string', () => {
|
||||
expect(() => i18n.translate(undefined)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.translate(null)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.translate(true)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.translate(5)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.translate({})).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.translate(undefined as any)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.translate(null as any)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.translate(true as any)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.translate(5 as any)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.translate({} as any)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.translate('')).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
|
@ -764,7 +780,7 @@ describe('I18n engine', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(i18n.getFormats().date.custom).toEqual({
|
||||
expect((i18n.getFormats().date as any).custom).toEqual({
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
|
@ -17,25 +17,20 @@
|
|||
* 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 memoizeIntlConstructor from 'intl-format-cache';
|
||||
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';
|
||||
|
||||
import { Messages, PlainMessages } from '../messages';
|
||||
import { Formats, formats as EN_FORMATS } from './formats';
|
||||
import { hasValues, isObject, isString, mergeAll } from './helper';
|
||||
|
||||
// Add all locale data to `IntlMessageFormat`.
|
||||
import './locales';
|
||||
import './locales.js';
|
||||
|
||||
const EN_LOCALE = 'en';
|
||||
const LOCALE_DELIMITER = '-';
|
||||
const messages = {};
|
||||
const messages: Messages = {};
|
||||
const getMessageFormat = memoizeIntlConstructor(IntlMessageFormat);
|
||||
|
||||
let defaultLocale = EN_LOCALE;
|
||||
|
@ -47,28 +42,26 @@ 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
|
||||
* @param id - path to the message
|
||||
*/
|
||||
function getMessageById(id) {
|
||||
function getMessageById(id: string): string {
|
||||
return getMessages()[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes locale to make it consistent with IntlMessageFormat locales
|
||||
* @param {string} locale
|
||||
* @returns {string} normalizedLocale
|
||||
* @param locale
|
||||
*/
|
||||
function normalizeLocale(locale) {
|
||||
function normalizeLocale(locale: string) {
|
||||
return locale.toLowerCase().replace('_', LOCALE_DELIMITER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a way to register translations with the engine
|
||||
* @param {Messages} newMessages
|
||||
* @param {string} [locale = messages.locale]
|
||||
* @param newMessages
|
||||
* @param [locale = messages.locale]
|
||||
*/
|
||||
export function addMessages(newMessages = {}, locale = newMessages.locale) {
|
||||
export function addMessages(newMessages: PlainMessages = {}, locale = newMessages.locale) {
|
||||
if (!locale || !isString(locale)) {
|
||||
throw new Error('[I18n] A `locale` must be a non-empty string to add messages.');
|
||||
}
|
||||
|
@ -89,17 +82,16 @@ export function addMessages(newMessages = {}, locale = newMessages.locale) {
|
|||
|
||||
/**
|
||||
* Returns messages for the current language
|
||||
* @returns {Messages} messages
|
||||
*/
|
||||
export function getMessages() {
|
||||
export function getMessages(): PlainMessages {
|
||||
return messages[currentLocale] || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the engine which language to use by given language key
|
||||
* @param {string} locale
|
||||
* @param locale
|
||||
*/
|
||||
export function setLocale(locale) {
|
||||
export function setLocale(locale: string) {
|
||||
if (!locale || !isString(locale)) {
|
||||
throw new Error('[I18n] A `locale` must be a non-empty string.');
|
||||
}
|
||||
|
@ -109,7 +101,6 @@ export function setLocale(locale) {
|
|||
|
||||
/**
|
||||
* Returns the current locale
|
||||
* @returns {string} locale
|
||||
*/
|
||||
export function getLocale() {
|
||||
return currentLocale;
|
||||
|
@ -117,9 +108,9 @@ export function getLocale() {
|
|||
|
||||
/**
|
||||
* Tells the library which language to fallback when missing translations
|
||||
* @param {string} locale
|
||||
* @param locale
|
||||
*/
|
||||
export function setDefaultLocale(locale) {
|
||||
export function setDefaultLocale(locale: string) {
|
||||
if (!locale || !isString(locale)) {
|
||||
throw new Error('[I18n] A `locale` must be a non-empty string.');
|
||||
}
|
||||
|
@ -129,10 +120,6 @@ export function setDefaultLocale(locale) {
|
|||
IntlRelativeFormat.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default locale
|
||||
* @returns {string} defaultLocale
|
||||
*/
|
||||
export function getDefaultLocale() {
|
||||
return defaultLocale;
|
||||
}
|
||||
|
@ -143,12 +130,12 @@ export function getDefaultLocale() {
|
|||
* {@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]
|
||||
* @param newFormats
|
||||
* @param [newFormats.number]
|
||||
* @param [newFormats.date]
|
||||
* @param [newFormats.time]
|
||||
*/
|
||||
export function setFormats(newFormats) {
|
||||
export function setFormats(newFormats: Formats) {
|
||||
if (!isObject(newFormats) || !hasValues(newFormats)) {
|
||||
throw new Error('[I18n] A `formats` must be a non-empty object.');
|
||||
}
|
||||
|
@ -158,7 +145,6 @@ export function setFormats(newFormats) {
|
|||
|
||||
/**
|
||||
* Returns current formats
|
||||
* @returns {object} formats
|
||||
*/
|
||||
export function getFormats() {
|
||||
return formats;
|
||||
|
@ -166,21 +152,30 @@ export function getFormats() {
|
|||
|
||||
/**
|
||||
* Returns array of locales having translations
|
||||
* @returns {string[]} locales
|
||||
*/
|
||||
export function getRegisteredLocales() {
|
||||
return Object.keys(messages);
|
||||
}
|
||||
|
||||
interface TranslateArguments {
|
||||
values?: { [key: string]: string | number | Date };
|
||||
defaultMessage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* @param id - translation id to be translated
|
||||
* @param [options]
|
||||
* @param [options.values] - values to pass into translation
|
||||
* @param [options.defaultMessage] - will be used unless translation was successful
|
||||
*/
|
||||
export function translate(id, { values = {}, defaultMessage = '' } = {}) {
|
||||
export function translate(
|
||||
id: string,
|
||||
{ values = {}, defaultMessage = '' }: TranslateArguments = {
|
||||
values: {},
|
||||
defaultMessage: '',
|
||||
}
|
||||
) {
|
||||
if (!id || !isString(id)) {
|
||||
throw new Error('[I18n] An `id` must be a non-empty string to translate a message.');
|
||||
}
|
||||
|
@ -218,9 +213,9 @@ export function translate(id, { values = {}, defaultMessage = '' } = {}) {
|
|||
|
||||
/**
|
||||
* Initializes the engine
|
||||
* @param {Messages} newMessages
|
||||
* @param newMessages
|
||||
*/
|
||||
export function init(newMessages) {
|
||||
export function init(newMessages?: PlainMessages) {
|
||||
if (!newMessages) {
|
||||
return;
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
import { join } from 'path';
|
||||
|
||||
describe('I18n loader', () => {
|
||||
let i18nLoader;
|
||||
let i18nLoader: typeof import('./loader');
|
||||
|
||||
beforeEach(() => {
|
||||
i18nLoader = require('./loader');
|
||||
|
@ -33,7 +33,9 @@ describe('I18n loader', () => {
|
|||
|
||||
describe('registerTranslationFile', () => {
|
||||
test('should throw error if path to translation file is not specified', () => {
|
||||
expect(() => i18nLoader.registerTranslationFile()).toThrowErrorMatchingSnapshot();
|
||||
expect(() =>
|
||||
i18nLoader.registerTranslationFile(undefined as any)
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('should throw error if path to translation file is not an absolute', () => {
|
||||
|
@ -69,7 +71,8 @@ describe('I18n loader', () => {
|
|||
join(__dirname, './__fixtures__/test_plugin_1/translations/en-US.json')
|
||||
);
|
||||
|
||||
expect(i18nLoader.getRegisteredLocales()).toContain('en', 'en-US');
|
||||
expect(i18nLoader.getRegisteredLocales()).toContain('en');
|
||||
expect(i18nLoader.getRegisteredLocales()).toContain('en-US');
|
||||
expect(i18nLoader.getRegisteredLocales().length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
@ -83,7 +86,8 @@ describe('I18n loader', () => {
|
|||
join(__dirname, './__fixtures__/test_plugin_1/translations/en-US.json'),
|
||||
]);
|
||||
|
||||
expect(i18nLoader.getRegisteredLocales()).toContain('en', 'en-US');
|
||||
expect(i18nLoader.getRegisteredLocales()).toContain('en');
|
||||
expect(i18nLoader.getRegisteredLocales()).toContain('en-US');
|
||||
expect(i18nLoader.getRegisteredLocales().length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
@ -228,7 +232,7 @@ describe('I18n loader', () => {
|
|||
});
|
||||
|
||||
test('should return empty object if there are no translation files', async () => {
|
||||
expect(await i18nLoader.getAllTranslationsFromPaths()).toEqual({});
|
||||
expect(await i18nLoader.getAllTranslationsFromPaths(undefined as any)).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,44 +17,38 @@
|
|||
* 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 * as JSON5 from 'json5';
|
||||
import * as path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import JSON5 from 'json5';
|
||||
|
||||
import { unique } from './core/helper';
|
||||
import { Messages, PlainMessages } from './messages';
|
||||
|
||||
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
|
||||
* Internal property for storing registered translations paths.
|
||||
* Key is locale, value is array of registered paths
|
||||
*/
|
||||
const translationsRegistry = {};
|
||||
const translationsRegistry: { [key: string]: string[] } = {};
|
||||
|
||||
/**
|
||||
* Internal property for caching loaded translations files
|
||||
* @type {Map<string, Messages>|{}} - Key is path to translation file, value is
|
||||
* object with translation messages
|
||||
* Internal property for caching loaded translations files.
|
||||
* Key is path to translation file, value is object with translation messages
|
||||
*/
|
||||
const loadedFiles = {};
|
||||
const loadedFiles: { [key: string]: PlainMessages } = {};
|
||||
|
||||
/**
|
||||
* Returns locale by the given translation file name
|
||||
* @param {string} fullFileName
|
||||
* @returns {string} locale
|
||||
* @param fullFileName
|
||||
* @returns locale
|
||||
* @example
|
||||
* getLocaleFromFileName('./path/to/translation/ru.json') // => 'ru'
|
||||
*/
|
||||
function getLocaleFromFileName(fullFileName) {
|
||||
function getLocaleFromFileName(fullFileName: string) {
|
||||
if (!fullFileName) {
|
||||
throw new Error('Filename is empty');
|
||||
}
|
||||
|
@ -72,19 +66,19 @@ function getLocaleFromFileName(fullFileName) {
|
|||
|
||||
/**
|
||||
* Loads file and parses it as JSON5
|
||||
* @param {string} pathToFile
|
||||
* @returns {Promise<object>}
|
||||
* @param pathToFile
|
||||
* @returns
|
||||
*/
|
||||
async function loadFile(pathToFile) {
|
||||
async function loadFile(pathToFile: string): Promise<PlainMessages> {
|
||||
return JSON5.parse(await asyncReadFile(pathToFile, 'utf8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads translations files and adds them into "loadedFiles" cache
|
||||
* @param {string[]} files
|
||||
* @returns {Promise<void>}
|
||||
* @param files
|
||||
* @returns
|
||||
*/
|
||||
async function loadAndCacheFiles(files) {
|
||||
async function loadAndCacheFiles(files: string[]) {
|
||||
const translations = await Promise.all(files.map(loadFile));
|
||||
|
||||
files.forEach((file, index) => {
|
||||
|
@ -94,9 +88,9 @@ async function loadAndCacheFiles(files) {
|
|||
|
||||
/**
|
||||
* Registers translation file with i18n loader
|
||||
* @param {string} translationFilePath - Absolute path to the translation file to register.
|
||||
* @param translationFilePath - Absolute path to the translation file to register.
|
||||
*/
|
||||
export function registerTranslationFile(translationFilePath) {
|
||||
export function registerTranslationFile(translationFilePath: string) {
|
||||
if (!path.isAbsolute(translationFilePath)) {
|
||||
throw new TypeError(
|
||||
'Paths to translation files must be absolute. ' +
|
||||
|
@ -114,15 +108,15 @@ export function registerTranslationFile(translationFilePath) {
|
|||
|
||||
/**
|
||||
* Registers array of translation files with i18n loader
|
||||
* @param {string[]} arrayOfPaths - Array of absolute paths to the translation files to register.
|
||||
* @param arrayOfPaths - Array of absolute paths to the translation files to register.
|
||||
*/
|
||||
export function registerTranslationFiles(arrayOfPaths = []) {
|
||||
export function registerTranslationFiles(arrayOfPaths: string[] = []) {
|
||||
arrayOfPaths.forEach(registerTranslationFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of locales that have been registered with i18n loader
|
||||
* @returns {string[]} registeredTranslations
|
||||
* @returns registeredTranslations
|
||||
*/
|
||||
export function getRegisteredLocales() {
|
||||
return Object.keys(translationsRegistry);
|
||||
|
@ -130,10 +124,10 @@ export function getRegisteredLocales() {
|
|||
|
||||
/**
|
||||
* Returns translation messages by specified locale
|
||||
* @param {string} locale
|
||||
* @returns {Promise<Messages>} translations - translation messages
|
||||
* @param locale
|
||||
* @returns translation messages
|
||||
*/
|
||||
export async function getTranslationsByLocale(locale) {
|
||||
export async function getTranslationsByLocale(locale: string): Promise<PlainMessages> {
|
||||
const files = translationsRegistry[locale] || [];
|
||||
const notLoadedFiles = files.filter(file => !loadedFiles[file]);
|
||||
|
||||
|
@ -154,10 +148,10 @@ export async function getTranslationsByLocale(locale) {
|
|||
|
||||
/**
|
||||
* Returns all translations for registered locales
|
||||
* @return {Promise<Map<string, Messages>>} translations - A Promise object
|
||||
* @returns A Promise object
|
||||
* where keys are the locale and values are objects of translation messages
|
||||
*/
|
||||
export async function getAllTranslations() {
|
||||
export async function getAllTranslations(): Promise<{ [key: string]: Messages }> {
|
||||
const locales = getRegisteredLocales();
|
||||
const translations = await Promise.all(locales.map(getTranslationsByLocale));
|
||||
|
||||
|
@ -173,11 +167,11 @@ export async function getAllTranslations() {
|
|||
/**
|
||||
* 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
|
||||
* @param paths - Array of absolute paths to the translation files
|
||||
* @returns A Promise object where
|
||||
* keys are the locale and values are objects of translation messages
|
||||
*/
|
||||
export async function getAllTranslationsFromPaths(paths) {
|
||||
export async function getAllTranslationsFromPaths(paths: string[]) {
|
||||
registerTranslationFiles(paths);
|
||||
|
||||
return await getAllTranslations();
|
|
@ -17,18 +17,23 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import * as i18n from '../core';
|
||||
import { Formats } from './core/formats';
|
||||
|
||||
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;
|
||||
/**
|
||||
* Messages tree, where leafs are translated strings
|
||||
*/
|
||||
export interface Messages {
|
||||
[key: string]: PlainMessages;
|
||||
}
|
||||
|
||||
export interface PlainMessages {
|
||||
[key: string]: any;
|
||||
/**
|
||||
* locale of the messages
|
||||
*/
|
||||
locale?: string;
|
||||
/**
|
||||
* set of options to the underlying formatter
|
||||
*/
|
||||
formats?: Formats;
|
||||
}
|
|
@ -132,7 +132,9 @@ Object {
|
|||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": null,
|
||||
}
|
||||
`;
|
||||
|
|
@ -17,15 +17,15 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
import { intlShape } from 'react-intl';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import { I18nProvider } from './provider';
|
||||
import { injectI18n } from './inject';
|
||||
import { I18nProvider } from './provider';
|
||||
|
||||
describe('I18nProvider', () => {
|
||||
it('renders children', () => {
|
||||
const ChildrenMock = () => {};
|
||||
test('renders children', () => {
|
||||
const ChildrenMock = () => null;
|
||||
|
||||
const wrapper = shallow(
|
||||
<I18nProvider>
|
||||
|
@ -36,7 +36,7 @@ describe('I18nProvider', () => {
|
|||
expect(wrapper.children()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('provides with context', () => {
|
||||
test('provides with context', () => {
|
||||
const childrenMock = () => <div />;
|
||||
const WithIntl = injectI18n(childrenMock);
|
||||
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import * as React from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import * as i18n from '../core';
|
||||
|
@ -28,12 +28,12 @@ import * as i18n from '../core';
|
|||
* 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 = {
|
||||
export class I18nProvider extends React.PureComponent {
|
||||
public static propTypes = {
|
||||
children: PropTypes.object,
|
||||
};
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
16
packages/kbn-i18n/tsconfig.json
Normal file
16
packages/kbn-i18n/tsconfig.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"types/intl_format_cache.d.ts",
|
||||
"types/intl_relativeformat.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"target"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationDir": "./target/types",
|
||||
}
|
||||
}
|
2
packages/kbn-i18n/tslint.yml
Normal file
2
packages/kbn-i18n/tslint.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
extends:
|
||||
- ../../tslint.yaml
|
32
packages/kbn-i18n/types/intl_format_cache.d.ts
vendored
Normal file
32
packages/kbn-i18n/types/intl_format_cache.d.ts
vendored
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.
|
||||
*/
|
||||
|
||||
declare module 'intl-format-cache' {
|
||||
import IntlMessageFormat from 'intl-messageformat';
|
||||
|
||||
interface Message {
|
||||
format: (values: { [key: string]: string | number | Date }) => string;
|
||||
}
|
||||
|
||||
function memoizeIntlConstructor(
|
||||
IntlMessageFormatCtor: typeof IntlMessageFormat
|
||||
): (msg: string, locale: string, formats: any) => Message;
|
||||
|
||||
export = memoizeIntlConstructor;
|
||||
}
|
22
packages/kbn-i18n/types/intl_relativeformat.d.ts
vendored
Normal file
22
packages/kbn-i18n/types/intl_relativeformat.d.ts
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.
|
||||
*/
|
||||
|
||||
declare module 'intl-relativeformat' {
|
||||
export let defaultLocale: string;
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -20,13 +20,13 @@
|
|||
import { uiModules } from 'ui/modules';
|
||||
import { metadata } from 'ui/metadata';
|
||||
import {
|
||||
i18nProvider,
|
||||
I18nProvider,
|
||||
i18nFilter,
|
||||
i18nDirective,
|
||||
} from '@kbn/i18n/angular';
|
||||
|
||||
uiModules.get('i18n')
|
||||
.provider('i18n', i18nProvider)
|
||||
.provider('i18n', I18nProvider)
|
||||
.filter('i18n', i18nFilter)
|
||||
.directive('i18nId', i18nDirective)
|
||||
.config((i18nProvider) => {
|
||||
|
|
|
@ -922,7 +922,7 @@ babel-plugin-pegjs-inline-precompile@^0.1.0:
|
|||
|
||||
babel-plugin-syntax-object-rest-spread@^6.13.0:
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
|
||||
resolved "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
|
||||
|
||||
babel-plugin-transform-es2015-arrow-functions@^6.22.0:
|
||||
version "6.22.0"
|
||||
|
@ -3818,7 +3818,7 @@ hoist-non-react-statics@^2.3.0:
|
|||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
|
||||
|
||||
hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
|
||||
hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
||||
|
||||
|
@ -4016,7 +4016,7 @@ intl-messageformat@^2.0.0, intl-messageformat@^2.1.0, intl-messageformat@^2.2.0:
|
|||
dependencies:
|
||||
intl-messageformat-parser "1.4.0"
|
||||
|
||||
intl-relativeformat@^2.0.0, intl-relativeformat@^2.1.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:
|
||||
|
@ -4918,9 +4918,9 @@ 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"
|
||||
json5@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.0.1.tgz#3d6d0d1066039eb50984e66a7840e4f4b7a2c660"
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
|
@ -6690,7 +6690,7 @@ prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8,
|
|||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
prop-types@^15.5.7:
|
||||
prop-types@^15.5.7, prop-types@^15.6.2:
|
||||
version "15.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||
dependencies:
|
||||
|
@ -6973,13 +6973,14 @@ react-input-autosize@^2.1.2, react-input-autosize@^2.2.1:
|
|||
dependencies:
|
||||
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"
|
||||
react-intl@^2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-2.7.0.tgz#be1244769ce51f71476afb9556485a46090db5fa"
|
||||
dependencies:
|
||||
hoist-non-react-statics "^2.5.5"
|
||||
intl-format-cache "^2.0.5"
|
||||
intl-messageformat "^2.1.0"
|
||||
intl-relativeformat "^2.0.0"
|
||||
intl-relativeformat "^2.1.0"
|
||||
invariant "^2.1.1"
|
||||
|
||||
react-is@^16.3.1:
|
||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -291,9 +291,15 @@
|
|||
url-join "^4.0.0"
|
||||
ws "^4.1.0"
|
||||
|
||||
"@types/angular@^1.6.45":
|
||||
version "1.6.45"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.45.tgz#5b0b91a51d717f6fc816d59e1234d5292f33f7b9"
|
||||
"@types/angular-mocks@^1.7.0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular-mocks/-/angular-mocks-1.7.0.tgz#310d999a3c47c10ecd8eef466b5861df84799429"
|
||||
dependencies:
|
||||
"@types/angular" "*"
|
||||
|
||||
"@types/angular@*", "@types/angular@^1.6.50":
|
||||
version "1.6.50"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.50.tgz#8b6599088d80f68ef0cad7d3a2062248ebe72b3d"
|
||||
|
||||
"@types/babel-core@^6.25.5":
|
||||
version "6.25.5"
|
||||
|
@ -6454,7 +6460,7 @@ hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.5.0:
|
|||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
|
||||
|
||||
hoist-non-react-statics@^2.3.1:
|
||||
hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.5:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
||||
|
||||
|
@ -6829,7 +6835,7 @@ intl-messageformat@^2.0.0, intl-messageformat@^2.1.0, intl-messageformat@^2.2.0:
|
|||
dependencies:
|
||||
intl-messageformat-parser "1.4.0"
|
||||
|
||||
intl-relativeformat@^2.0.0, intl-relativeformat@^2.1.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:
|
||||
|
@ -8001,6 +8007,12 @@ json5@^1.0.1:
|
|||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
json5@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.0.1.tgz#3d6d0d1066039eb50984e66a7840e4f4b7a2c660"
|
||||
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"
|
||||
|
@ -11233,13 +11245,14 @@ 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"
|
||||
react-intl@^2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-2.7.0.tgz#be1244769ce51f71476afb9556485a46090db5fa"
|
||||
dependencies:
|
||||
hoist-non-react-statics "^2.5.5"
|
||||
intl-format-cache "^2.0.5"
|
||||
intl-messageformat "^2.1.0"
|
||||
intl-relativeformat "^2.0.0"
|
||||
intl-relativeformat "^2.1.0"
|
||||
invariant "^2.1.1"
|
||||
|
||||
react-is@^16.3.1:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue