* Update output file format for i18n_check tool * Update i18n engine to work with new format * Update tests * Fix UI bootstrap test * Update loader tests and fix getTranslationsByLocale function * Fix messages passing to IntlProvider * Update messages interface * Resolve comments * Resolve comment
This commit is contained in:
parent
31d30f814e
commit
b3086f294b
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"a.b.c": "bar",
|
||||
"d.e.f": "foo"
|
||||
"messages": {
|
||||
"a.b.c": "bar",
|
||||
"d.e.f": "foo"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"a.b.c": "foo",
|
||||
"d.e.f": "bar"
|
||||
"messages": {
|
||||
"a.b.c": "foo",
|
||||
"d.e.f": "bar"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"a.b.c.custom": "foo.custom",
|
||||
"d.e.f.custom": "bar.custom"
|
||||
"messages": {
|
||||
"a.b.c.custom": "foo.custom",
|
||||
"d.e.f.custom": "bar.custom"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
{
|
||||
test: 'test' // JSON5 test
|
||||
messages: {
|
||||
test: 'test' // JSON5 test
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
{
|
||||
"test": "test"
|
||||
"messages": {
|
||||
"test": "test"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ 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 addTranslation = i18n.addTranslation;
|
||||
public getTranslation = i18n.getTranslation;
|
||||
public setLocale = i18n.setLocale;
|
||||
public getLocale = i18n.getLocale;
|
||||
public setDefaultLocale = i18n.setDefaultLocale;
|
||||
|
|
|
@ -4,7 +4,7 @@ exports[`I18n engine addMessages should throw error if locale is not specified o
|
|||
|
||||
exports[`I18n engine addMessages should throw error if locale is not specified or empty 2`] = `"[I18n] A \`locale\` must be a non-empty string to add messages."`;
|
||||
|
||||
exports[`I18n engine addMessages should throw error if locale specified in messages is different from one provided as second argument 1`] = `"[I18n] A \`locale\` in the messages object is different from the one provided as a second argument."`;
|
||||
exports[`I18n engine addMessages should throw error if locale specified in messages is different from one provided as second argument 1`] = `"[I18n] A \`locale\` in the translation object is different from the one provided as a second argument."`;
|
||||
|
||||
exports[`I18n engine translate should throw error if id is not a non-empty string 1`] = `"[I18n] An \`id\` must be a non-empty string to translate a message."`;
|
||||
|
||||
|
|
|
@ -33,46 +33,54 @@ describe('I18n engine', () => {
|
|||
|
||||
describe('addMessages', () => {
|
||||
test('should throw error if locale is not specified or empty', () => {
|
||||
expect(() => i18n.addMessages({ foo: 'bar' })).toThrowErrorMatchingSnapshot();
|
||||
expect(() => i18n.addMessages({ locale: '' })).toThrowErrorMatchingSnapshot();
|
||||
expect(() =>
|
||||
i18n.addTranslation({ messages: { foo: 'bar' } })
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
expect(() =>
|
||||
i18n.addTranslation({ locale: '', messages: {} })
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('should throw error if locale specified in messages is different from one provided as second argument', () => {
|
||||
expect(() =>
|
||||
i18n.addMessages({ foo: 'bar', locale: 'en' }, 'ru')
|
||||
i18n.addTranslation({ messages: { foo: 'bar' }, locale: 'en' }, 'ru')
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('should add messages if locale prop is passed as second argument', () => {
|
||||
const locale = 'ru';
|
||||
|
||||
expect(i18n.getMessages()).toEqual({});
|
||||
expect(i18n.getTranslation()).toEqual({ messages: {} });
|
||||
|
||||
i18n.addMessages({ foo: 'bar' }, locale);
|
||||
i18n.addTranslation({ messages: { foo: 'bar' } }, locale);
|
||||
|
||||
expect(i18n.getMessages()).toEqual({});
|
||||
expect(i18n.getTranslation()).toEqual({ messages: {} });
|
||||
|
||||
i18n.setLocale(locale);
|
||||
|
||||
expect(i18n.getMessages()).toEqual({ foo: 'bar' });
|
||||
expect(i18n.getTranslation()).toEqual({ messages: { foo: 'bar' } });
|
||||
});
|
||||
|
||||
test('should add messages if locale prop is passed as messages property', () => {
|
||||
const locale = 'ru';
|
||||
|
||||
expect(i18n.getMessages()).toEqual({});
|
||||
expect(i18n.getTranslation()).toEqual({ messages: {} });
|
||||
|
||||
i18n.addMessages({
|
||||
i18n.addTranslation({
|
||||
locale,
|
||||
foo: 'bar',
|
||||
messages: {
|
||||
foo: 'bar',
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.getMessages()).toEqual({});
|
||||
expect(i18n.getTranslation()).toEqual({ messages: {} });
|
||||
|
||||
i18n.setLocale(locale);
|
||||
|
||||
expect(i18n.getMessages()).toEqual({
|
||||
foo: 'bar',
|
||||
expect(i18n.getTranslation()).toEqual({
|
||||
messages: {
|
||||
foo: 'bar',
|
||||
},
|
||||
locale: 'ru',
|
||||
});
|
||||
});
|
||||
|
@ -81,25 +89,33 @@ describe('I18n engine', () => {
|
|||
const locale = 'ru';
|
||||
|
||||
i18n.setLocale(locale);
|
||||
i18n.addMessages({
|
||||
i18n.addTranslation({
|
||||
locale,
|
||||
['a.b.c']: 'foo',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.getMessages()).toEqual({
|
||||
expect(i18n.getTranslation()).toEqual({
|
||||
locale: 'ru',
|
||||
['a.b.c']: 'foo',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
},
|
||||
});
|
||||
|
||||
i18n.addMessages({
|
||||
i18n.addTranslation({
|
||||
locale,
|
||||
['d.e.f']: 'bar',
|
||||
messages: {
|
||||
['d.e.f']: 'bar',
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.getMessages()).toEqual({
|
||||
expect(i18n.getTranslation()).toEqual({
|
||||
locale: 'ru',
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -107,24 +123,32 @@ describe('I18n engine', () => {
|
|||
const locale = 'ru';
|
||||
|
||||
i18n.setLocale(locale);
|
||||
i18n.addMessages({
|
||||
i18n.addTranslation({
|
||||
locale,
|
||||
['a.b.c']: 'foo',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.getMessages()).toEqual({
|
||||
expect(i18n.getTranslation()).toEqual({
|
||||
locale: 'ru',
|
||||
['a.b.c']: 'foo',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
},
|
||||
});
|
||||
|
||||
i18n.addMessages({
|
||||
i18n.addTranslation({
|
||||
locale,
|
||||
['a.b.c']: 'bar',
|
||||
messages: {
|
||||
['a.b.c']: 'bar',
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.getMessages()).toEqual({
|
||||
expect(i18n.getTranslation()).toEqual({
|
||||
locale: 'ru',
|
||||
['a.b.c']: 'bar',
|
||||
messages: {
|
||||
['a.b.c']: 'bar',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -132,52 +156,64 @@ describe('I18n engine', () => {
|
|||
const locale = 'en-us';
|
||||
i18n.setLocale(locale);
|
||||
|
||||
i18n.addMessages(
|
||||
i18n.addTranslation(
|
||||
{
|
||||
['a.b.c']: 'bar',
|
||||
messages: {
|
||||
['a.b.c']: 'bar',
|
||||
},
|
||||
},
|
||||
'en_US'
|
||||
);
|
||||
|
||||
expect(i18n.getLocale()).toBe(locale);
|
||||
expect(i18n.getMessages()).toEqual({
|
||||
['a.b.c']: 'bar',
|
||||
expect(i18n.getTranslation()).toEqual({
|
||||
messages: {
|
||||
['a.b.c']: 'bar',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMessages', () => {
|
||||
describe('getTranslation', () => {
|
||||
test('should return messages for the current language', () => {
|
||||
i18n.addMessages({
|
||||
i18n.addTranslation({
|
||||
locale: 'ru',
|
||||
foo: 'bar',
|
||||
messages: {
|
||||
foo: 'bar',
|
||||
},
|
||||
});
|
||||
i18n.addMessages({
|
||||
i18n.addTranslation({
|
||||
locale: 'en',
|
||||
bar: 'foo',
|
||||
messages: {
|
||||
bar: 'foo',
|
||||
},
|
||||
});
|
||||
|
||||
i18n.setLocale('ru');
|
||||
expect(i18n.getMessages()).toEqual({
|
||||
expect(i18n.getTranslation()).toEqual({
|
||||
locale: 'ru',
|
||||
foo: 'bar',
|
||||
messages: {
|
||||
foo: 'bar',
|
||||
},
|
||||
});
|
||||
|
||||
i18n.setLocale('en');
|
||||
expect(i18n.getMessages()).toEqual({
|
||||
expect(i18n.getTranslation()).toEqual({
|
||||
locale: 'en',
|
||||
bar: 'foo',
|
||||
messages: {
|
||||
bar: 'foo',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should return an empty object if messages for current locale are not specified', () => {
|
||||
expect(i18n.getMessages()).toEqual({});
|
||||
expect(i18n.getTranslation()).toEqual({ messages: {} });
|
||||
|
||||
i18n.setLocale('fr');
|
||||
expect(i18n.getMessages()).toEqual({});
|
||||
expect(i18n.getTranslation()).toEqual({ messages: {} });
|
||||
|
||||
i18n.setLocale('en');
|
||||
expect(i18n.getMessages()).toEqual({});
|
||||
expect(i18n.getTranslation()).toEqual({ messages: {} });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -352,22 +388,25 @@ describe('I18n engine', () => {
|
|||
});
|
||||
|
||||
test('should return array of registered locales', () => {
|
||||
i18n.addMessages({
|
||||
i18n.addTranslation({
|
||||
locale: 'en',
|
||||
messages: {},
|
||||
});
|
||||
|
||||
expect(i18n.getRegisteredLocales()).toEqual(['en']);
|
||||
|
||||
i18n.addMessages({
|
||||
i18n.addTranslation({
|
||||
locale: 'ru',
|
||||
messages: {},
|
||||
});
|
||||
|
||||
expect(i18n.getRegisteredLocales()).toContain('en');
|
||||
expect(i18n.getRegisteredLocales()).toContain('ru');
|
||||
expect(i18n.getRegisteredLocales().length).toBe(2);
|
||||
|
||||
i18n.addMessages({
|
||||
i18n.addTranslation({
|
||||
locale: 'fr',
|
||||
messages: {},
|
||||
});
|
||||
|
||||
expect(i18n.getRegisteredLocales()).toContain('en');
|
||||
|
@ -394,7 +433,9 @@ describe('I18n engine', () => {
|
|||
test('should return message as is if values are not provided', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
['a.b.c']: 'foo',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.translate('a.b.c')).toBe('foo');
|
||||
|
@ -407,7 +448,9 @@ describe('I18n engine', () => {
|
|||
test('should not return defaultMessage as is if values are provided', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
['a.b.c']: 'foo',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
},
|
||||
});
|
||||
expect(i18n.translate('a.b.c', { defaultMessage: 'bar' })).toBe('foo');
|
||||
});
|
||||
|
@ -415,8 +458,10 @@ describe('I18n engine', () => {
|
|||
test('should interpolate variables', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
['a.b.c']: 'foo {a}, {b}, {c} bar',
|
||||
['d.e.f']: '{foo}',
|
||||
messages: {
|
||||
['a.b.c']: 'foo {a}, {b}, {c} bar',
|
||||
['d.e.f']: '{foo}',
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -440,11 +485,13 @@ describe('I18n engine', () => {
|
|||
test('should format pluralized messages', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
['a.b.c']: `You have {numPhotos, plural,
|
||||
messages: {
|
||||
['a.b.c']: `You have {numPhotos, plural,
|
||||
=0 {no photos.}
|
||||
=1 {one photo.}
|
||||
other {# photos.}
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.translate('a.b.c', { values: { numPhotos: 0 } })).toBe('You have no photos.');
|
||||
|
@ -494,11 +541,13 @@ describe('I18n engine', () => {
|
|||
test('should throw error if wrong context is provided to the translation string', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
['a.b.c']: `You have {numPhotos, plural,
|
||||
messages: {
|
||||
['a.b.c']: `You have {numPhotos, plural,
|
||||
=0 {no photos.}
|
||||
=1 {one photo.}
|
||||
other {# photos.}
|
||||
}`,
|
||||
},
|
||||
});
|
||||
i18n.setDefaultLocale('en');
|
||||
|
||||
|
@ -519,7 +568,9 @@ describe('I18n engine', () => {
|
|||
test('should format messages with percent formatter', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
['a.b.c']: 'Result: {result, number, percent}',
|
||||
messages: {
|
||||
['a.b.c']: 'Result: {result, number, percent}',
|
||||
},
|
||||
});
|
||||
i18n.setDefaultLocale('en');
|
||||
|
||||
|
@ -536,10 +587,12 @@ describe('I18n engine', () => {
|
|||
test('should format messages with date formatter', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
['a.short']: 'Sale begins {start, date, short}',
|
||||
['a.medium']: 'Sale begins {start, date, medium}',
|
||||
['a.long']: 'Sale begins {start, date, long}',
|
||||
['a.full']: 'Sale begins {start, date, full}',
|
||||
messages: {
|
||||
['a.short']: 'Sale begins {start, date, short}',
|
||||
['a.medium']: 'Sale begins {start, date, medium}',
|
||||
['a.long']: 'Sale begins {start, date, long}',
|
||||
['a.full']: 'Sale begins {start, date, full}',
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -602,8 +655,10 @@ describe('I18n engine', () => {
|
|||
test('should format messages with time formatter', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
['a.short']: 'Coupon expires at {expires, time, short}',
|
||||
['a.medium']: 'Coupon expires at {expires, time, medium}',
|
||||
messages: {
|
||||
['a.short']: 'Coupon expires at {expires, time, short}',
|
||||
['a.medium']: 'Coupon expires at {expires, time, medium}',
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -645,8 +700,10 @@ describe('I18n engine', () => {
|
|||
usd: { style: 'currency', currency: 'USD' },
|
||||
},
|
||||
},
|
||||
['a.b.c']: 'Your total is {total, number, usd}',
|
||||
['d.e.f']: 'Your total is {total, number, eur}',
|
||||
messages: {
|
||||
['a.b.c']: 'Your total is {total, number, usd}',
|
||||
['d.e.f']: 'Your total is {total, number, eur}',
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.translate('a.b.c', { values: { total: 1000 } })).toBe('Your total is $1,000.00');
|
||||
|
@ -670,6 +727,7 @@ describe('I18n engine', () => {
|
|||
usd: { style: 'currency', currency: 'USD' },
|
||||
},
|
||||
},
|
||||
messages: {},
|
||||
});
|
||||
i18n.setDefaultLocale('en');
|
||||
|
||||
|
@ -704,7 +762,9 @@ describe('I18n engine', () => {
|
|||
test('should use default format if passed format option is not specified', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
['a.b.c']: 'Your total is {total, number, usd}',
|
||||
messages: {
|
||||
['a.b.c']: 'Your total is {total, number, usd}',
|
||||
},
|
||||
});
|
||||
i18n.setDefaultLocale('en');
|
||||
|
||||
|
@ -721,7 +781,9 @@ describe('I18n engine', () => {
|
|||
test('should throw error if used format is not specified', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
['a.b.c']: 'Your total is {total, foo}',
|
||||
messages: {
|
||||
['a.b.c']: 'Your total is {total, foo}',
|
||||
},
|
||||
});
|
||||
i18n.setDefaultLocale('en');
|
||||
|
||||
|
@ -741,28 +803,32 @@ describe('I18n engine', () => {
|
|||
describe('init', () => {
|
||||
test('should not initialize the engine if messages are not specified', () => {
|
||||
i18n.init();
|
||||
expect(i18n.getMessages()).toEqual({});
|
||||
expect(i18n.getTranslation()).toEqual({ messages: {} });
|
||||
});
|
||||
|
||||
test('should throw error if messages are empty', () => {
|
||||
expect(() => i18n.init({})).toThrow();
|
||||
expect(i18n.getMessages()).toEqual({});
|
||||
expect(() => i18n.init({ messages: {} })).toThrow();
|
||||
expect(i18n.getTranslation()).toEqual({ messages: {} });
|
||||
});
|
||||
|
||||
test('should add messages if locale is specified', () => {
|
||||
i18n.init({
|
||||
locale: 'en',
|
||||
foo: 'bar',
|
||||
messages: {
|
||||
foo: 'bar',
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.getMessages()).toEqual({
|
||||
expect(i18n.getTranslation()).toEqual({
|
||||
locale: 'en',
|
||||
foo: 'bar',
|
||||
messages: {
|
||||
foo: 'bar',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should set the current locale', () => {
|
||||
i18n.init({ locale: 'ru' });
|
||||
i18n.init({ locale: 'ru', messages: {} });
|
||||
expect(i18n.getLocale()).toBe('ru');
|
||||
});
|
||||
|
||||
|
@ -778,6 +844,7 @@ describe('I18n engine', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
messages: {},
|
||||
});
|
||||
|
||||
expect((i18n.getFormats().date as any).custom).toEqual({
|
||||
|
|
|
@ -21,7 +21,7 @@ import memoizeIntlConstructor from 'intl-format-cache';
|
|||
import IntlMessageFormat from 'intl-messageformat';
|
||||
import IntlRelativeFormat from 'intl-relativeformat';
|
||||
|
||||
import { Messages, PlainMessages } from '../messages';
|
||||
import { Translation } from '../translation';
|
||||
import { Formats, formats as EN_FORMATS } from './formats';
|
||||
import { hasValues, isObject, isString, mergeAll } from './helper';
|
||||
import { isPseudoLocale, translateUsingPseudoLocale } from './pseudo_locale';
|
||||
|
@ -31,7 +31,7 @@ import './locales.js';
|
|||
|
||||
const EN_LOCALE = 'en';
|
||||
const LOCALE_DELIMITER = '-';
|
||||
const messages: Messages = {};
|
||||
const translationsForLocale: Record<string, Translation> = {};
|
||||
const getMessageFormat = memoizeIntlConstructor(IntlMessageFormat);
|
||||
|
||||
let defaultLocale = EN_LOCALE;
|
||||
|
@ -45,8 +45,9 @@ IntlRelativeFormat.defaultLocale = defaultLocale;
|
|||
* Returns message by the given message id.
|
||||
* @param id - path to the message
|
||||
*/
|
||||
function getMessageById(id: string): string {
|
||||
return getMessages()[id];
|
||||
function getMessageById(id: string): string | undefined {
|
||||
const translation = getTranslation();
|
||||
return translation.messages ? translation.messages[id] : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,33 +60,38 @@ function normalizeLocale(locale: string) {
|
|||
|
||||
/**
|
||||
* Provides a way to register translations with the engine
|
||||
* @param newMessages
|
||||
* @param newTranslation
|
||||
* @param [locale = messages.locale]
|
||||
*/
|
||||
export function addMessages(newMessages: PlainMessages = {}, locale = newMessages.locale) {
|
||||
export function addTranslation(newTranslation: Translation, locale = newTranslation.locale) {
|
||||
if (!locale || !isString(locale)) {
|
||||
throw new Error('[I18n] A `locale` must be a non-empty string to add messages.');
|
||||
}
|
||||
|
||||
if (newMessages.locale && newMessages.locale !== locale) {
|
||||
if (newTranslation.locale && newTranslation.locale !== locale) {
|
||||
throw new Error(
|
||||
'[I18n] A `locale` in the messages object is different from the one provided as a second argument.'
|
||||
'[I18n] A `locale` in the translation object is different from the one provided as a second argument.'
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedLocale = normalizeLocale(locale);
|
||||
const existingTranslation = translationsForLocale[normalizedLocale] || { messages: {} };
|
||||
|
||||
messages[normalizedLocale] = {
|
||||
...messages[normalizedLocale],
|
||||
...newMessages,
|
||||
translationsForLocale[normalizedLocale] = {
|
||||
formats: newTranslation.formats || existingTranslation.formats,
|
||||
locale: newTranslation.locale || existingTranslation.locale,
|
||||
messages: {
|
||||
...existingTranslation.messages,
|
||||
...newTranslation.messages,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns messages for the current language
|
||||
*/
|
||||
export function getMessages(): PlainMessages {
|
||||
return messages[currentLocale] || {};
|
||||
export function getTranslation(): Translation {
|
||||
return translationsForLocale[currentLocale] || { messages: {} };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -155,7 +161,7 @@ export function getFormats() {
|
|||
* Returns array of locales having translations
|
||||
*/
|
||||
export function getRegisteredLocales() {
|
||||
return Object.keys(messages);
|
||||
return Object.keys(translationsForLocale);
|
||||
}
|
||||
|
||||
interface TranslateArguments {
|
||||
|
@ -218,20 +224,20 @@ export function translate(
|
|||
|
||||
/**
|
||||
* Initializes the engine
|
||||
* @param newMessages
|
||||
* @param newTranslation
|
||||
*/
|
||||
export function init(newMessages?: PlainMessages) {
|
||||
if (!newMessages) {
|
||||
export function init(newTranslation?: Translation) {
|
||||
if (!newTranslation) {
|
||||
return;
|
||||
}
|
||||
|
||||
addMessages(newMessages);
|
||||
addTranslation(newTranslation);
|
||||
|
||||
if (newMessages.locale) {
|
||||
setLocale(newMessages.locale);
|
||||
if (newTranslation.locale) {
|
||||
setLocale(newTranslation.locale);
|
||||
}
|
||||
|
||||
if (newMessages.formats) {
|
||||
setFormats(newMessages.formats);
|
||||
if (newTranslation.formats) {
|
||||
setFormats(newTranslation.formats);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,8 +100,10 @@ describe('I18n loader', () => {
|
|||
|
||||
expect(await i18nLoader.getTranslationsByLocale('en')).toEqual({
|
||||
locale: 'en',
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -110,7 +112,7 @@ describe('I18n loader', () => {
|
|||
join(__dirname, './__fixtures__/test_plugin_1/translations/en.json')
|
||||
);
|
||||
|
||||
expect(await i18nLoader.getTranslationsByLocale('ru')).toEqual({});
|
||||
expect(await i18nLoader.getTranslationsByLocale('ru')).toEqual({ messages: {} });
|
||||
});
|
||||
|
||||
test('should return translation messages from a couple of files by specified locale', async () => {
|
||||
|
@ -121,10 +123,12 @@ describe('I18n loader', () => {
|
|||
|
||||
expect(await i18nLoader.getTranslationsByLocale('en')).toEqual({
|
||||
locale: 'en',
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
['a.b.c.custom']: 'foo.custom',
|
||||
['d.e.f.custom']: 'bar.custom',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
['a.b.c.custom']: 'foo.custom',
|
||||
['d.e.f.custom']: 'bar.custom',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -138,21 +142,27 @@ describe('I18n loader', () => {
|
|||
|
||||
expect(await i18nLoader.getTranslationsByLocale('en')).toEqual({
|
||||
locale: 'en',
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
['a.b.c.custom']: 'foo.custom',
|
||||
['d.e.f.custom']: 'bar.custom',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
['a.b.c.custom']: 'foo.custom',
|
||||
['d.e.f.custom']: 'bar.custom',
|
||||
},
|
||||
});
|
||||
|
||||
expect(await i18nLoader.getTranslationsByLocale('en-US')).toEqual({
|
||||
locale: 'en-US',
|
||||
['a.b.c']: 'bar',
|
||||
['d.e.f']: 'foo',
|
||||
messages: {
|
||||
['a.b.c']: 'bar',
|
||||
['d.e.f']: 'foo',
|
||||
},
|
||||
});
|
||||
|
||||
expect(await i18nLoader.getTranslationsByLocale('ru')).toEqual({
|
||||
locale: 'ru',
|
||||
test: 'test',
|
||||
messages: {
|
||||
test: 'test',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -163,7 +173,9 @@ describe('I18n loader', () => {
|
|||
|
||||
expect(await i18nLoader.getTranslationsByLocale('fr')).toEqual({
|
||||
locale: 'fr',
|
||||
test: 'test',
|
||||
messages: {
|
||||
test: 'test',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -180,19 +192,25 @@ describe('I18n loader', () => {
|
|||
expect(await i18nLoader.getAllTranslations()).toEqual({
|
||||
en: {
|
||||
locale: 'en',
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
['a.b.c.custom']: 'foo.custom',
|
||||
['d.e.f.custom']: 'bar.custom',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
['a.b.c.custom']: 'foo.custom',
|
||||
['d.e.f.custom']: 'bar.custom',
|
||||
},
|
||||
},
|
||||
['en-US']: {
|
||||
locale: 'en-US',
|
||||
['a.b.c']: 'bar',
|
||||
['d.e.f']: 'foo',
|
||||
messages: {
|
||||
['a.b.c']: 'bar',
|
||||
['d.e.f']: 'foo',
|
||||
},
|
||||
},
|
||||
ru: {
|
||||
locale: 'ru',
|
||||
test: 'test',
|
||||
messages: {
|
||||
test: 'test',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -214,19 +232,25 @@ describe('I18n loader', () => {
|
|||
).toEqual({
|
||||
en: {
|
||||
locale: 'en',
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
['a.b.c.custom']: 'foo.custom',
|
||||
['d.e.f.custom']: 'bar.custom',
|
||||
messages: {
|
||||
['a.b.c']: 'foo',
|
||||
['d.e.f']: 'bar',
|
||||
['a.b.c.custom']: 'foo.custom',
|
||||
['d.e.f.custom']: 'bar.custom',
|
||||
},
|
||||
},
|
||||
['en-US']: {
|
||||
locale: 'en-US',
|
||||
['a.b.c']: 'bar',
|
||||
['d.e.f']: 'foo',
|
||||
messages: {
|
||||
['a.b.c']: 'bar',
|
||||
['d.e.f']: 'foo',
|
||||
},
|
||||
},
|
||||
ru: {
|
||||
locale: 'ru',
|
||||
test: 'test',
|
||||
messages: {
|
||||
test: 'test',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ import * as path from 'path';
|
|||
import { promisify } from 'util';
|
||||
|
||||
import { unique } from './core/helper';
|
||||
import { Messages, PlainMessages } from './messages';
|
||||
import { Translation } from './translation';
|
||||
|
||||
const asyncReadFile = promisify(readFile);
|
||||
|
||||
|
@ -39,7 +39,7 @@ const translationsRegistry: { [key: string]: string[] } = {};
|
|||
* Internal property for caching loaded translations files.
|
||||
* Key is path to translation file, value is object with translation messages
|
||||
*/
|
||||
const loadedFiles: { [key: string]: PlainMessages } = {};
|
||||
const loadedFiles: { [key: string]: Translation } = {};
|
||||
|
||||
/**
|
||||
* Returns locale by the given translation file name
|
||||
|
@ -69,7 +69,7 @@ function getLocaleFromFileName(fullFileName: string) {
|
|||
* @param pathToFile
|
||||
* @returns
|
||||
*/
|
||||
async function loadFile(pathToFile: string): Promise<PlainMessages> {
|
||||
async function loadFile(pathToFile: string): Promise<Translation> {
|
||||
return JSON5.parse(await asyncReadFile(pathToFile, 'utf8'));
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ export function getRegisteredLocales() {
|
|||
* @param locale
|
||||
* @returns translation messages
|
||||
*/
|
||||
export async function getTranslationsByLocale(locale: string): Promise<PlainMessages> {
|
||||
export async function getTranslationsByLocale(locale: string): Promise<Translation> {
|
||||
const files = translationsRegistry[locale] || [];
|
||||
const notLoadedFiles = files.filter(file => !loadedFiles[file]);
|
||||
|
||||
|
@ -135,15 +135,21 @@ export async function getTranslationsByLocale(locale: string): Promise<PlainMess
|
|||
await loadAndCacheFiles(notLoadedFiles);
|
||||
}
|
||||
|
||||
return files.length
|
||||
? files.reduce(
|
||||
(messages, file) => ({
|
||||
...messages,
|
||||
...loadedFiles[file],
|
||||
}),
|
||||
{ locale }
|
||||
)
|
||||
: {};
|
||||
if (!files.length) {
|
||||
return { messages: {} };
|
||||
}
|
||||
|
||||
return files.reduce(
|
||||
(translation: Translation, file) => ({
|
||||
locale: loadedFiles[file].locale || translation.locale,
|
||||
formats: loadedFiles[file].formats || translation.formats,
|
||||
messages: {
|
||||
...loadedFiles[file].messages,
|
||||
...translation.messages,
|
||||
},
|
||||
}),
|
||||
{ locale, messages: {} }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -151,7 +157,7 @@ export async function getTranslationsByLocale(locale: string): Promise<PlainMess
|
|||
* @returns A Promise object
|
||||
* where keys are the locale and values are objects of translation messages
|
||||
*/
|
||||
export async function getAllTranslations(): Promise<{ [key: string]: Messages }> {
|
||||
export async function getAllTranslations(): Promise<{ [key: string]: Translation }> {
|
||||
const locales = getRegisteredLocales();
|
||||
const translations = await Promise.all(locales.map(getTranslationsByLocale));
|
||||
|
||||
|
|
|
@ -66,62 +66,7 @@ Object {
|
|||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {
|
||||
"date": Object {
|
||||
"full": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"weekday": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"long": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"medium": Object {
|
||||
"day": "numeric",
|
||||
"month": "short",
|
||||
"year": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"day": "numeric",
|
||||
"month": "numeric",
|
||||
"year": "2-digit",
|
||||
},
|
||||
},
|
||||
"number": Object {
|
||||
"currency": Object {
|
||||
"style": "currency",
|
||||
},
|
||||
"percent": Object {
|
||||
"style": "percent",
|
||||
},
|
||||
},
|
||||
"time": Object {
|
||||
"full": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"long": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"medium": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
},
|
||||
},
|
||||
},
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
|
|
|
@ -66,62 +66,7 @@ Object {
|
|||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {
|
||||
"date": Object {
|
||||
"full": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"weekday": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"long": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"medium": Object {
|
||||
"day": "numeric",
|
||||
"month": "short",
|
||||
"year": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"day": "numeric",
|
||||
"month": "numeric",
|
||||
"year": "2-digit",
|
||||
},
|
||||
},
|
||||
"number": Object {
|
||||
"currency": Object {
|
||||
"style": "currency",
|
||||
},
|
||||
"percent": Object {
|
||||
"style": "percent",
|
||||
},
|
||||
},
|
||||
"time": Object {
|
||||
"full": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"long": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"medium": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
},
|
||||
},
|
||||
},
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
|
|
|
@ -58,9 +58,9 @@ export class I18nProvider extends React.PureComponent {
|
|||
return (
|
||||
<IntlProvider
|
||||
locale={i18n.getLocale()}
|
||||
messages={i18n.getMessages()}
|
||||
messages={i18n.getTranslation().messages}
|
||||
defaultLocale={i18n.getDefaultLocale()}
|
||||
formats={i18n.getFormats()}
|
||||
formats={i18n.getTranslation().formats}
|
||||
defaultFormats={i18n.getFormats()}
|
||||
textComponent={React.Fragment}
|
||||
>
|
||||
|
|
|
@ -19,21 +19,17 @@
|
|||
|
||||
import { Formats } from './core/formats';
|
||||
|
||||
/**
|
||||
* Messages tree, where leafs are translated strings
|
||||
*/
|
||||
export interface Messages {
|
||||
[key: string]: PlainMessages;
|
||||
}
|
||||
|
||||
export interface PlainMessages {
|
||||
[key: string]: any;
|
||||
export interface Translation {
|
||||
/**
|
||||
* locale of the messages
|
||||
* Actual translated messages.
|
||||
*/
|
||||
messages: Record<string, string>;
|
||||
/**
|
||||
* Locale of the translated messages.
|
||||
*/
|
||||
locale?: string;
|
||||
/**
|
||||
* set of options to the underlying formatter
|
||||
* Set of options to the underlying formatter.
|
||||
*/
|
||||
formats?: Formats;
|
||||
}
|
|
@ -58,10 +58,12 @@ exports[`dev/i18n/serializers/json should serialize default messages to JSON 1`]
|
|||
}
|
||||
}
|
||||
},
|
||||
\\"plugin1.message.id-1\\": \\"Message text 1 \\",
|
||||
\\"plugin2.message.id-2\\": {
|
||||
\\"text\\": \\"Message text 2\\",
|
||||
\\"comment\\": \\"Message context\\"
|
||||
\\"messages\\": {
|
||||
\\"plugin1.message.id-1\\": \\"Message text 1 \\",
|
||||
\\"plugin2.message.id-2\\": {
|
||||
\\"text\\": \\"Message text 2\\",
|
||||
\\"comment\\": \\"Message context\\"
|
||||
}
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -58,8 +58,10 @@ exports[`dev/i18n/serializers/json5 should serialize default messages to JSON5 1
|
|||
},
|
||||
},
|
||||
},
|
||||
'plugin1.message.id-1': 'Message text 1',
|
||||
'plugin2.message.id-2': 'Message text 2', // Message context
|
||||
messages: {
|
||||
'plugin1.message.id-1': 'Message text 1',
|
||||
'plugin2.message.id-2': 'Message text 2', // Message context
|
||||
},
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
|
|
@ -20,13 +20,13 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export function serializeToJson(defaultMessages) {
|
||||
const resultJsonObject = { formats: i18n.formats };
|
||||
const resultJsonObject = { formats: i18n.formats, messages: {} };
|
||||
|
||||
for (const [mapKey, mapValue] of defaultMessages) {
|
||||
if (mapValue.context) {
|
||||
resultJsonObject[mapKey] = { text: mapValue.message, comment: mapValue.context };
|
||||
resultJsonObject.messages[mapKey] = { text: mapValue.message, comment: mapValue.context };
|
||||
} else {
|
||||
resultJsonObject[mapKey] = mapValue.message;
|
||||
resultJsonObject.messages[mapKey] = mapValue.message;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,11 @@ import { i18n } from '@kbn/i18n';
|
|||
const ESCAPE_SINGLE_QUOTE_REGEX = /\\([\s\S])|(')/g;
|
||||
|
||||
export function serializeToJson5(defaultMessages) {
|
||||
// .slice(0, -1): remove closing curly brace from json to append messages
|
||||
// .slice(0, -4): remove closing curly braces from json to append messages
|
||||
let jsonBuffer = Buffer.from(
|
||||
JSON5.stringify({ formats: i18n.formats }, { quote: `'`, space: 2 }).slice(0, -1)
|
||||
JSON5.stringify({ formats: i18n.formats, messages: {} }, { quote: `'`, space: 2 })
|
||||
.slice(0, -4)
|
||||
.concat('\n')
|
||||
);
|
||||
|
||||
for (const [mapKey, mapValue] of defaultMessages) {
|
||||
|
@ -36,13 +38,13 @@ export function serializeToJson5(defaultMessages) {
|
|||
|
||||
jsonBuffer = Buffer.concat([
|
||||
jsonBuffer,
|
||||
Buffer.from(` '${mapKey}': '${formattedMessage}',`),
|
||||
Buffer.from(` '${mapKey}': '${formattedMessage}',`),
|
||||
Buffer.from(formattedContext ? ` // ${formattedContext}\n` : '\n'),
|
||||
]);
|
||||
}
|
||||
|
||||
// append previously removed closing curly brace
|
||||
jsonBuffer = Buffer.concat([jsonBuffer, Buffer.from('}\n')]);
|
||||
// append previously removed closing curly braces
|
||||
jsonBuffer = Buffer.concat([jsonBuffer, Buffer.from(' },\n}\n')]);
|
||||
|
||||
return jsonBuffer;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue