feat(localize): add types to localize
Co-authored-by: narzac <narzac@gmail.com>
This commit is contained in:
parent
74cbb10553
commit
09d9675963
57 changed files with 1119 additions and 446 deletions
5
.changeset/new-oranges-knock.md
Normal file
5
.changeset/new-oranges-knock.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/localize': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add types for localize package. JSDocs types in the .js files, TSC generates type definition files. Mixins type definition files are hand-typed.
|
||||||
5
.changeset/pretty-mice-wonder.md
Normal file
5
.changeset/pretty-mice-wonder.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'singleton-manager': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Added basic JSDocs types to SingletonManager, in order for localize to be able to be typed correctly.
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -45,4 +45,4 @@ jobs:
|
||||||
publish: yarn release
|
publish: yarn release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,36 @@ import MessageFormat from '@bundled-es-modules/message-format/MessageFormat.js';
|
||||||
import { LionSingleton } from '@lion/core';
|
import { LionSingleton } from '@lion/core';
|
||||||
import isLocalizeESModule from './isLocalizeESModule.js';
|
import isLocalizeESModule from './isLocalizeESModule.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('../types/LocalizeMixinTypes').NamespaceObject} NamespaceObject
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `LocalizeManager` manages your translations (includes loading)
|
* `LocalizeManager` manages your translations (includes loading)
|
||||||
*/
|
*/
|
||||||
export class LocalizeManager extends LionSingleton {
|
export class LocalizeManager extends LionSingleton {
|
||||||
// eslint-disable-line no-unused-vars
|
// eslint-disable-line no-unused-vars
|
||||||
constructor(params = {}) {
|
constructor({ autoLoadOnLocaleChange = false, fallbackLocale = '' } = {}) {
|
||||||
super(params);
|
super();
|
||||||
this._fakeExtendsEventTarget();
|
this.__delegationTarget = document.createDocumentFragment();
|
||||||
|
this._autoLoadOnLocaleChange = !!autoLoadOnLocaleChange;
|
||||||
|
this._fallbackLocale = fallbackLocale;
|
||||||
|
|
||||||
this._autoLoadOnLocaleChange = !!params.autoLoadOnLocaleChange;
|
/** @type {Object.<string, Object.<string, Object>>} */
|
||||||
this._fallbackLocale = params.fallbackLocale;
|
|
||||||
this.__storage = {};
|
this.__storage = {};
|
||||||
|
|
||||||
|
/** @type {Map.<RegExp|string, function>} */
|
||||||
this.__namespacePatternsMap = new Map();
|
this.__namespacePatternsMap = new Map();
|
||||||
|
|
||||||
|
/** @type {Object.<string, function|null>} */
|
||||||
this.__namespaceLoadersCache = {};
|
this.__namespaceLoadersCache = {};
|
||||||
|
|
||||||
|
/** @type {Object.<string, Object.<string, Promise.<Object>>>} */
|
||||||
this.__namespaceLoaderPromisesCache = {};
|
this.__namespaceLoaderPromisesCache = {};
|
||||||
this.formatNumberOptions = { returnIfNaN: '' };
|
|
||||||
|
this.formatNumberOptions = {
|
||||||
|
returnIfNaN: '',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Via html[data-localize-lang], developers are allowed to set the initial locale, without
|
* Via html[data-localize-lang], developers are allowed to set the initial locale, without
|
||||||
|
|
@ -73,18 +87,24 @@ export class LocalizeManager extends LionSingleton {
|
||||||
this._teardownHtmlLangAttributeObserver();
|
this._teardownHtmlLangAttributeObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
get locale() {
|
get locale() {
|
||||||
if (this._supportExternalTranslationTools) {
|
if (this._supportExternalTranslationTools) {
|
||||||
return this.__locale;
|
return this.__locale || '';
|
||||||
}
|
}
|
||||||
return document.documentElement.lang;
|
return document.documentElement.lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
set locale(value) {
|
set locale(value) {
|
||||||
|
/** @type {string} */
|
||||||
let oldLocale;
|
let oldLocale;
|
||||||
if (this._supportExternalTranslationTools) {
|
if (this._supportExternalTranslationTools) {
|
||||||
oldLocale = this.__locale;
|
oldLocale = /** @type {string} */ (this.__locale);
|
||||||
this.__locale = value;
|
this.__locale = value;
|
||||||
if (this._langAttrSetByTranslationTool === null) {
|
if (this._langAttrSetByTranslationTool === null) {
|
||||||
this._setHtmlLangAttribute(value);
|
this._setHtmlLangAttribute(value);
|
||||||
|
|
@ -101,12 +121,19 @@ export class LocalizeManager extends LionSingleton {
|
||||||
this._onLocaleChanged(value, oldLocale);
|
this._onLocaleChanged(value, oldLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
*/
|
||||||
_setHtmlLangAttribute(locale) {
|
_setHtmlLangAttribute(locale) {
|
||||||
this._teardownHtmlLangAttributeObserver();
|
this._teardownHtmlLangAttributeObserver();
|
||||||
document.documentElement.lang = locale;
|
document.documentElement.lang = locale;
|
||||||
this._setupHtmlLangAttributeObserver();
|
this._setupHtmlLangAttributeObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
* @throws {Error} Language only locales are not allowed(Use 'en-GB' instead of 'en')
|
||||||
|
*/
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
__handleLanguageOnly(value) {
|
__handleLanguageOnly(value) {
|
||||||
throw new Error(`
|
throw new Error(`
|
||||||
|
|
@ -116,6 +143,9 @@ export class LocalizeManager extends LionSingleton {
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise.<Object>}
|
||||||
|
*/
|
||||||
get loadingComplete() {
|
get loadingComplete() {
|
||||||
return Promise.all(Object.values(this.__namespaceLoaderPromisesCache[this.locale]));
|
return Promise.all(Object.values(this.__namespaceLoaderPromisesCache[this.locale]));
|
||||||
}
|
}
|
||||||
|
|
@ -127,6 +157,12 @@ export class LocalizeManager extends LionSingleton {
|
||||||
this.__namespaceLoaderPromisesCache = {};
|
this.__namespaceLoaderPromisesCache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
* @param {string} namespace
|
||||||
|
* @param {object} data
|
||||||
|
* @throws {Error} Namespace can be added only once, for a given locale
|
||||||
|
*/
|
||||||
addData(locale, namespace, data) {
|
addData(locale, namespace, data) {
|
||||||
if (this._isNamespaceInCache(locale, namespace)) {
|
if (this._isNamespaceInCache(locale, namespace)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -138,18 +174,41 @@ export class LocalizeManager extends LionSingleton {
|
||||||
this.__storage[locale][namespace] = data;
|
this.__storage[locale][namespace] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {RegExp|string} pattern
|
||||||
|
* @param {function} loader
|
||||||
|
*/
|
||||||
setupNamespaceLoader(pattern, loader) {
|
setupNamespaceLoader(pattern, loader) {
|
||||||
this.__namespacePatternsMap.set(pattern, loader);
|
this.__namespacePatternsMap.set(pattern, loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {NamespaceObject[]} namespaces
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.locale]
|
||||||
|
* @returns {Promise.<Object>}
|
||||||
|
*/
|
||||||
loadNamespaces(namespaces, { locale } = {}) {
|
loadNamespaces(namespaces, { locale } = {}) {
|
||||||
return Promise.all(namespaces.map(namespace => this.loadNamespace(namespace, { locale })));
|
return Promise.all(
|
||||||
|
namespaces.map(
|
||||||
|
/** @param {NamespaceObject} namespace */
|
||||||
|
namespace => this.loadNamespace(namespace, { locale }),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {NamespaceObject} namespaceObj
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.locale]
|
||||||
|
* @returns {Promise.<Object|void>}
|
||||||
|
*/
|
||||||
loadNamespace(namespaceObj, { locale = this.locale } = { locale: this.locale }) {
|
loadNamespace(namespaceObj, { locale = this.locale } = { locale: this.locale }) {
|
||||||
const isDynamicImport = typeof namespaceObj === 'object';
|
const isDynamicImport = typeof namespaceObj === 'object';
|
||||||
|
|
||||||
const namespace = isDynamicImport ? Object.keys(namespaceObj)[0] : namespaceObj;
|
const namespace = /** @type {string} */ (isDynamicImport
|
||||||
|
? Object.keys(namespaceObj)[0]
|
||||||
|
: namespaceObj);
|
||||||
|
|
||||||
if (this._isNamespaceInCache(locale, namespace)) {
|
if (this._isNamespaceInCache(locale, namespace)) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|
@ -163,6 +222,13 @@ export class LocalizeManager extends LionSingleton {
|
||||||
return this._loadNamespaceData(locale, namespaceObj, isDynamicImport, namespace);
|
return this._loadNamespaceData(locale, namespaceObj, isDynamicImport, namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string | string[]} keys
|
||||||
|
* @param {Object.<string,?>} [vars]
|
||||||
|
* @param {Object} [opts]
|
||||||
|
* @param {string} [opts.locale]
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
msg(keys, vars, opts = {}) {
|
msg(keys, vars, opts = {}) {
|
||||||
const locale = opts.locale ? opts.locale : this.locale;
|
const locale = opts.locale ? opts.locale : this.locale;
|
||||||
const message = this._getMessageForKeys(keys, locale);
|
const message = this._getMessageForKeys(keys, locale);
|
||||||
|
|
@ -186,7 +252,7 @@ export class LocalizeManager extends LionSingleton {
|
||||||
this._langAttrSetByTranslationTool = document.documentElement.lang;
|
this._langAttrSetByTranslationTool = document.documentElement.lang;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._onLocaleChanged(document.documentElement.lang, mutation.oldValue);
|
this._onLocaleChanged(document.documentElement.lang, mutation.oldValue || '');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -199,13 +265,23 @@ export class LocalizeManager extends LionSingleton {
|
||||||
}
|
}
|
||||||
|
|
||||||
_teardownHtmlLangAttributeObserver() {
|
_teardownHtmlLangAttributeObserver() {
|
||||||
this._htmlLangAttributeObserver.disconnect();
|
if (this._htmlLangAttributeObserver) {
|
||||||
|
this._htmlLangAttributeObserver.disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
* @param {string} namespace
|
||||||
|
*/
|
||||||
_isNamespaceInCache(locale, namespace) {
|
_isNamespaceInCache(locale, namespace) {
|
||||||
return !!(this.__storage[locale] && this.__storage[locale][namespace]);
|
return !!(this.__storage[locale] && this.__storage[locale][namespace]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
* @param {string} namespace
|
||||||
|
*/
|
||||||
_getCachedNamespaceLoaderPromise(locale, namespace) {
|
_getCachedNamespaceLoaderPromise(locale, namespace) {
|
||||||
if (this.__namespaceLoaderPromisesCache[locale]) {
|
if (this.__namespaceLoaderPromisesCache[locale]) {
|
||||||
return this.__namespaceLoaderPromisesCache[locale][namespace];
|
return this.__namespaceLoaderPromisesCache[locale][namespace];
|
||||||
|
|
@ -213,22 +289,41 @@ export class LocalizeManager extends LionSingleton {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
* @param {NamespaceObject} namespaceObj
|
||||||
|
* @param {boolean} isDynamicImport
|
||||||
|
* @param {string} namespace
|
||||||
|
* @returns {Promise.<Object|void>}
|
||||||
|
*/
|
||||||
_loadNamespaceData(locale, namespaceObj, isDynamicImport, namespace) {
|
_loadNamespaceData(locale, namespaceObj, isDynamicImport, namespace) {
|
||||||
const loader = this._getNamespaceLoader(namespaceObj, isDynamicImport, namespace);
|
const loader = this._getNamespaceLoader(namespaceObj, isDynamicImport, namespace);
|
||||||
const loaderPromise = this._getNamespaceLoaderPromise(loader, locale, namespace);
|
const loaderPromise = this._getNamespaceLoaderPromise(loader, locale, namespace);
|
||||||
this._cacheNamespaceLoaderPromise(locale, namespace, loaderPromise);
|
this._cacheNamespaceLoaderPromise(locale, namespace, loaderPromise);
|
||||||
return loaderPromise.then(obj => {
|
return loaderPromise.then(
|
||||||
const data = isLocalizeESModule(obj) ? obj.default : obj;
|
/**
|
||||||
this.addData(locale, namespace, data);
|
* @param {Object} obj
|
||||||
});
|
* @param {Object} obj.default
|
||||||
|
*/
|
||||||
|
obj => {
|
||||||
|
const data = isLocalizeESModule(obj) ? obj.default : obj;
|
||||||
|
this.addData(locale, namespace, data);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {NamespaceObject} namespaceObj
|
||||||
|
* @param {boolean} isDynamicImport
|
||||||
|
* @param {string} namespace
|
||||||
|
* @throws {Error} Namespace shall setup properly. Check loader!
|
||||||
|
*/
|
||||||
_getNamespaceLoader(namespaceObj, isDynamicImport, namespace) {
|
_getNamespaceLoader(namespaceObj, isDynamicImport, namespace) {
|
||||||
let loader = this.__namespaceLoadersCache[namespace];
|
let loader = this.__namespaceLoadersCache[namespace];
|
||||||
|
|
||||||
if (!loader) {
|
if (!loader) {
|
||||||
if (isDynamicImport) {
|
if (isDynamicImport) {
|
||||||
loader = namespaceObj[namespace];
|
const _namespaceObj = /** @type {Object.<string,function>} */ (namespaceObj);
|
||||||
|
loader = _namespaceObj[namespace];
|
||||||
this.__namespaceLoadersCache[namespace] = loader;
|
this.__namespaceLoadersCache[namespace] = loader;
|
||||||
} else {
|
} else {
|
||||||
loader = this._lookupNamespaceLoader(namespace);
|
loader = this._lookupNamespaceLoader(namespace);
|
||||||
|
|
@ -245,12 +340,20 @@ export class LocalizeManager extends LionSingleton {
|
||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {function} loader
|
||||||
|
* @param {string} locale
|
||||||
|
* @param {string} namespace
|
||||||
|
* @param {string} [fallbackLocale]
|
||||||
|
* @returns {Promise.<any>}
|
||||||
|
* @throws {Error} Data for namespace and (locale or fallback locale) could not be loaded.
|
||||||
|
*/
|
||||||
_getNamespaceLoaderPromise(loader, locale, namespace, fallbackLocale = this._fallbackLocale) {
|
_getNamespaceLoaderPromise(loader, locale, namespace, fallbackLocale = this._fallbackLocale) {
|
||||||
return loader(locale, namespace).catch(() => {
|
return loader(locale, namespace).catch(() => {
|
||||||
const lang = this._getLangFromLocale(locale);
|
const lang = this._getLangFromLocale(locale);
|
||||||
return loader(lang, namespace).catch(() => {
|
return loader(lang, namespace).catch(() => {
|
||||||
if (fallbackLocale) {
|
if (fallbackLocale) {
|
||||||
return this._getNamespaceLoaderPromise(loader, fallbackLocale, namespace, false).catch(
|
return this._getNamespaceLoaderPromise(loader, fallbackLocale, namespace, '').catch(
|
||||||
() => {
|
() => {
|
||||||
const fallbackLang = this._getLangFromLocale(fallbackLocale);
|
const fallbackLang = this._getLangFromLocale(fallbackLocale);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -268,6 +371,11 @@ export class LocalizeManager extends LionSingleton {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
* @param {string} namespace
|
||||||
|
* @param {Promise.<Object>} promise
|
||||||
|
*/
|
||||||
_cacheNamespaceLoaderPromise(locale, namespace, promise) {
|
_cacheNamespaceLoaderPromise(locale, namespace, promise) {
|
||||||
if (!this.__namespaceLoaderPromisesCache[locale]) {
|
if (!this.__namespaceLoaderPromisesCache[locale]) {
|
||||||
this.__namespaceLoaderPromisesCache[locale] = {};
|
this.__namespaceLoaderPromisesCache[locale] = {};
|
||||||
|
|
@ -275,6 +383,10 @@ export class LocalizeManager extends LionSingleton {
|
||||||
this.__namespaceLoaderPromisesCache[locale][namespace] = promise;
|
this.__namespaceLoaderPromisesCache[locale][namespace] = promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} namespace
|
||||||
|
* @returns {function|null}
|
||||||
|
*/
|
||||||
_lookupNamespaceLoader(namespace) {
|
_lookupNamespaceLoader(namespace) {
|
||||||
/* eslint-disable no-restricted-syntax */
|
/* eslint-disable no-restricted-syntax */
|
||||||
for (const [key, value] of this.__namespacePatternsMap) {
|
for (const [key, value] of this.__namespacePatternsMap) {
|
||||||
|
|
@ -289,18 +401,45 @@ export class LocalizeManager extends LionSingleton {
|
||||||
/* eslint-enable no-restricted-syntax */
|
/* eslint-enable no-restricted-syntax */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
_getLangFromLocale(locale) {
|
_getLangFromLocale(locale) {
|
||||||
return locale.substring(0, 2);
|
return locale.substring(0, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
_fakeExtendsEventTarget() {
|
/**
|
||||||
const delegate = document.createDocumentFragment();
|
* @param {string} type
|
||||||
['addEventListener', 'dispatchEvent', 'removeEventListener'].forEach(funcName => {
|
* @param {EventListener} listener
|
||||||
this[funcName] = (...args) => delegate[funcName](...args);
|
* @param {...Object} options
|
||||||
});
|
*/
|
||||||
|
addEventListener(type, listener, ...options) {
|
||||||
|
this.__delegationTarget.addEventListener(type, listener, ...options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} type
|
||||||
|
* @param {EventListener} listener
|
||||||
|
* @param {...Object} options
|
||||||
|
*/
|
||||||
|
removeEventListener(type, listener, ...options) {
|
||||||
|
this.__delegationTarget.removeEventListener(type, listener, ...options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CustomEvent} event
|
||||||
|
*/
|
||||||
|
dispatchEvent(event) {
|
||||||
|
this.__delegationTarget.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} newLocale
|
||||||
|
* @param {string} oldLocale
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
_onLocaleChanged(newLocale, oldLocale) {
|
_onLocaleChanged(newLocale, oldLocale) {
|
||||||
if (newLocale === oldLocale) {
|
if (newLocale === oldLocale) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -311,19 +450,34 @@ export class LocalizeManager extends LionSingleton {
|
||||||
this.dispatchEvent(new CustomEvent('localeChanged', { detail: { newLocale, oldLocale } }));
|
this.dispatchEvent(new CustomEvent('localeChanged', { detail: { newLocale, oldLocale } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} newLocale
|
||||||
|
* @param {string} oldLocale
|
||||||
|
* @returns {Promise.<Object>}
|
||||||
|
*/
|
||||||
_loadAllMissing(newLocale, oldLocale) {
|
_loadAllMissing(newLocale, oldLocale) {
|
||||||
const oldLocaleNamespaces = this.__storage[oldLocale] || {};
|
const oldLocaleNamespaces = this.__storage[oldLocale] || {};
|
||||||
const newLocaleNamespaces = this.__storage[newLocale] || {};
|
const newLocaleNamespaces = this.__storage[newLocale] || {};
|
||||||
|
/** @type {Promise<Object|void>[]} */
|
||||||
const promises = [];
|
const promises = [];
|
||||||
Object.keys(oldLocaleNamespaces).forEach(namespace => {
|
Object.keys(oldLocaleNamespaces).forEach(namespace => {
|
||||||
const newNamespaceData = newLocaleNamespaces[namespace];
|
const newNamespaceData = newLocaleNamespaces[namespace];
|
||||||
if (!newNamespaceData) {
|
if (!newNamespaceData) {
|
||||||
promises.push(this.loadNamespace(namespace, { locale: newLocale }));
|
promises.push(
|
||||||
|
this.loadNamespace(namespace, {
|
||||||
|
locale: newLocale,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string | string[]} keys
|
||||||
|
* @param {string} locale
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
_getMessageForKeys(keys, locale) {
|
_getMessageForKeys(keys, locale) {
|
||||||
if (typeof keys === 'string') {
|
if (typeof keys === 'string') {
|
||||||
return this._getMessageForKey(keys, locale);
|
return this._getMessageForKey(keys, locale);
|
||||||
|
|
@ -341,16 +495,33 @@ export class LocalizeManager extends LionSingleton {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string | undefined} key
|
||||||
|
* @param {string} locale
|
||||||
|
* @returns {string}
|
||||||
|
* @throws {Error} `key`is missing namespace. The format for `key` is "namespace:name"
|
||||||
|
*
|
||||||
|
*/
|
||||||
_getMessageForKey(key, locale) {
|
_getMessageForKey(key, locale) {
|
||||||
if (key.indexOf(':') === -1) {
|
if (!key || key.indexOf(':') === -1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Namespace is missing in the key "${key}". The format for keys is "namespace:name".`,
|
`Namespace is missing in the key "${key}". The format for keys is "namespace:name".`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const [ns, namesString] = key.split(':');
|
const [ns, namesString] = key.split(':');
|
||||||
const namespaces = this.__storage[locale];
|
const namespaces = this.__storage[locale];
|
||||||
const messages = namespaces ? namespaces[ns] : null;
|
const messages = namespaces ? namespaces[ns] : {};
|
||||||
const names = namesString.split('.');
|
const names = namesString.split('.');
|
||||||
return names.reduce((message, n) => (message ? message[n] : null), messages);
|
const result = names.reduce(
|
||||||
|
/**
|
||||||
|
* @param {Object.<string, any> | string} message
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
(message, name) => (typeof message === 'object' ? message[name] : message),
|
||||||
|
messages,
|
||||||
|
);
|
||||||
|
|
||||||
|
return String(result || '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,111 +2,154 @@ import { dedupeMixin, until, nothing } from '@lion/core';
|
||||||
import { localize } from './localize.js';
|
import { localize } from './localize.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # LocalizeMixin - for self managed templates
|
* @typedef {import('../types/LocalizeMixinTypes').LocalizeMixin} LocalizeMixin
|
||||||
*
|
|
||||||
* @polymerMixin
|
|
||||||
* @mixinFunction
|
|
||||||
*/
|
*/
|
||||||
export const LocalizeMixin = dedupeMixin(
|
|
||||||
superclass =>
|
|
||||||
// eslint-disable-next-line
|
|
||||||
class LocalizeMixin extends superclass {
|
|
||||||
static get localizeNamespaces() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static get waitForLocalizeNamespaces() {
|
/**
|
||||||
return true;
|
* # LocalizeMixin - for self managed templates
|
||||||
}
|
* @type {LocalizeMixin}
|
||||||
|
*/
|
||||||
|
const LocalizeMixinImplementation = superclass =>
|
||||||
|
// eslint-disable-next-line
|
||||||
|
class LocalizeMixin extends superclass {
|
||||||
|
/**
|
||||||
|
* @returns {Object.<string,function>[]}
|
||||||
|
*/
|
||||||
|
static get localizeNamespaces() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
/**
|
||||||
super();
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
static get waitForLocalizeNamespaces() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
this.__boundLocalizeOnLocaleChanged = (...args) => this.__localizeOnLocaleChanged(...args);
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
// should be loaded in advance
|
this.__boundLocalizeOnLocaleChanged =
|
||||||
this.__localizeStartLoadingNamespaces();
|
/** @param {...Object} args */
|
||||||
|
(...args) => {
|
||||||
|
const event = /** @type {CustomEvent} */ (Array.from(args)[0]);
|
||||||
|
this.__localizeOnLocaleChanged(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
// should be loaded in advance
|
||||||
|
this.__localizeStartLoadingNamespaces();
|
||||||
|
|
||||||
|
if (this.localizeNamespacesLoaded) {
|
||||||
this.localizeNamespacesLoaded.then(() => {
|
this.localizeNamespacesLoaded.then(() => {
|
||||||
this.__localizeMessageSync = true;
|
this.__localizeMessageSync = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hook into LitElement to only render once all translations are loaded
|
* hook into LitElement to only render once all translations are loaded
|
||||||
*/
|
* @returns {Promise.<void>}
|
||||||
async performUpdate() {
|
*/
|
||||||
if (this.constructor.waitForLocalizeNamespaces) {
|
async performUpdate() {
|
||||||
await this.localizeNamespacesLoaded;
|
if (Object.getPrototypeOf(this).constructor.waitForLocalizeNamespaces) {
|
||||||
}
|
await this.localizeNamespacesLoaded;
|
||||||
super.performUpdate();
|
}
|
||||||
|
super.performUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (super.connectedCallback) {
|
||||||
|
super.connectedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
if (this.localizeNamespacesLoaded) {
|
||||||
if (super.connectedCallback) {
|
|
||||||
super.connectedCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.localizeNamespacesLoaded.then(() => this.onLocaleReady());
|
this.localizeNamespacesLoaded.then(() => this.onLocaleReady());
|
||||||
this.__localizeAddLocaleChangedListener();
|
}
|
||||||
|
this.__localizeAddLocaleChangedListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
if (super.disconnectedCallback) {
|
||||||
|
super.disconnectedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
this.__localizeRemoveLocaleChangedListener();
|
||||||
if (super.disconnectedCallback) {
|
}
|
||||||
super.disconnectedCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.__localizeRemoveLocaleChangedListener();
|
/**
|
||||||
|
* @param {string | string[]} keys
|
||||||
|
* @param {Object.<string,?>} variables
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.locale]
|
||||||
|
* @return {string | function}
|
||||||
|
*/
|
||||||
|
msgLit(keys, variables, options) {
|
||||||
|
if (this.__localizeMessageSync) {
|
||||||
|
return localize.msg(keys, variables, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
msgLit(...args) {
|
if (!this.localizeNamespacesLoaded) {
|
||||||
if (this.__localizeMessageSync) {
|
return '';
|
||||||
return localize.msg(...args);
|
|
||||||
}
|
|
||||||
return until(
|
|
||||||
this.localizeNamespacesLoaded.then(() => localize.msg(...args)),
|
|
||||||
nothing,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__getUniqueNamespaces() {
|
return until(
|
||||||
const uniqueNamespaces = [];
|
this.localizeNamespacesLoaded.then(() => localize.msg(keys, variables, options)),
|
||||||
|
nothing,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// IE11 does not support iterable in the constructor
|
/**
|
||||||
const s = new Set();
|
* @returns {string[]}
|
||||||
this.constructor.localizeNamespaces.forEach(s.add.bind(s));
|
*/
|
||||||
s.forEach(uniqueNamespace => {
|
__getUniqueNamespaces() {
|
||||||
uniqueNamespaces.push(uniqueNamespace);
|
/** @type {string[]} */
|
||||||
});
|
const uniqueNamespaces = [];
|
||||||
return uniqueNamespaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
__localizeStartLoadingNamespaces() {
|
// IE11 does not support iterable in the constructor
|
||||||
this.localizeNamespacesLoaded = localize.loadNamespaces(this.__getUniqueNamespaces());
|
const s = new Set();
|
||||||
}
|
Object.getPrototypeOf(this).constructor.localizeNamespaces.forEach(s.add.bind(s));
|
||||||
|
s.forEach(uniqueNamespace => {
|
||||||
|
uniqueNamespaces.push(uniqueNamespace);
|
||||||
|
});
|
||||||
|
return uniqueNamespaces;
|
||||||
|
}
|
||||||
|
|
||||||
__localizeAddLocaleChangedListener() {
|
__localizeStartLoadingNamespaces() {
|
||||||
localize.addEventListener('localeChanged', this.__boundLocalizeOnLocaleChanged);
|
this.localizeNamespacesLoaded = localize.loadNamespaces(this.__getUniqueNamespaces());
|
||||||
}
|
}
|
||||||
|
|
||||||
__localizeRemoveLocaleChangedListener() {
|
__localizeAddLocaleChangedListener() {
|
||||||
localize.removeEventListener('localeChanged', this.__boundLocalizeOnLocaleChanged);
|
localize.addEventListener('localeChanged', this.__boundLocalizeOnLocaleChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
__localizeOnLocaleChanged(event) {
|
__localizeRemoveLocaleChangedListener() {
|
||||||
this.onLocaleChanged(event.detail.newLocale, event.detail.oldLocale);
|
localize.removeEventListener('localeChanged', this.__boundLocalizeOnLocaleChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLocaleReady() {
|
/**
|
||||||
this.onLocaleUpdated();
|
* @param {CustomEvent} event
|
||||||
}
|
*/
|
||||||
|
__localizeOnLocaleChanged(event) {
|
||||||
|
this.onLocaleChanged(event.detail.newLocale, event.detail.oldLocale);
|
||||||
|
}
|
||||||
|
|
||||||
onLocaleChanged() {
|
onLocaleReady() {
|
||||||
this.localizeNamespacesLoaded = localize.loadNamespaces(this.__getUniqueNamespaces());
|
this.onLocaleUpdated();
|
||||||
this.onLocaleUpdated();
|
}
|
||||||
this.requestUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
/**
|
||||||
onLocaleUpdated() {}
|
* @param {string} newLocale
|
||||||
},
|
* @param {string} oldLocale
|
||||||
);
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
onLocaleChanged(newLocale, oldLocale) {
|
||||||
|
this.__localizeStartLoadingNamespaces();
|
||||||
|
this.onLocaleUpdated();
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
onLocaleUpdated() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LocalizeMixin = dedupeMixin(LocalizeMixinImplementation);
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import { pad } from './pad.js';
|
||||||
/**
|
/**
|
||||||
* To add a leading zero to a single number
|
* To add a leading zero to a single number
|
||||||
*
|
*
|
||||||
* @param dateString
|
* @param {string} dateString
|
||||||
* @returns {*}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function addLeadingZero(dateString) {
|
export function addLeadingZero(dateString) {
|
||||||
const dateParts = splitDate(dateString);
|
const dateParts = splitDate(dateString);
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { trim } from './trim.js';
|
||||||
/**
|
/**
|
||||||
* To clean date from added characters from IE
|
* To clean date from added characters from IE
|
||||||
*
|
*
|
||||||
* @param dateAsString
|
* @param {string} dateAsString
|
||||||
* @returns {string|XML}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function clean(dateAsString) {
|
export function clean(dateAsString) {
|
||||||
// list of separators is from wikipedia https://www.wikiwand.com/en/Date_format_by_country
|
// list of separators is from wikipedia https://www.wikiwand.com/en/Date_format_by_country
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,31 @@ import { normalizeIntlDate } from './normalizeIntlDate.js';
|
||||||
/**
|
/**
|
||||||
* Formats date based on locale and options
|
* Formats date based on locale and options
|
||||||
*
|
*
|
||||||
* @param date
|
* @param {Date} date
|
||||||
* @param options
|
* @param {Object} [options] Intl options are available
|
||||||
* @returns {*}
|
* @param {string} [options.locale]
|
||||||
|
* @param {string} [options.localeMatcher]
|
||||||
|
* @param {string} [options.formatMatcher]
|
||||||
|
* @param {boolean}[options.hour12]
|
||||||
|
* @param {string} [options.numberingSystem]
|
||||||
|
* @param {string} [options.calendar]
|
||||||
|
* @param {string} [options.timeZone]
|
||||||
|
* @param {string} [options.timeZoneName]
|
||||||
|
* @param {string} [options.weekday]
|
||||||
|
* @param {string} [options.era]
|
||||||
|
* @param {string} [options.year]
|
||||||
|
* @param {string} [options.month]
|
||||||
|
* @param {string} [options.day]
|
||||||
|
* @param {string} [options.hour]
|
||||||
|
* @param {string} [options.minute]
|
||||||
|
* @param {string} [options.second]
|
||||||
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function formatDate(date, options) {
|
export function formatDate(date, options) {
|
||||||
if (!(date instanceof Date)) {
|
if (!(date instanceof Date)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
/** @type {options} */
|
||||||
const formatOptions = options || {};
|
const formatOptions = options || {};
|
||||||
/**
|
/**
|
||||||
* Set smart defaults if:
|
* Set smart defaults if:
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,28 @@ import { splitDate } from './splitDate.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To compute the localized date format
|
* To compute the localized date format
|
||||||
*
|
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getDateFormatBasedOnLocale() {
|
export function getDateFormatBasedOnLocale() {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {ArrayLike.<string>} dateParts
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
function computePositions(dateParts) {
|
function computePositions(dateParts) {
|
||||||
|
/**
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function getPartByIndex(index) {
|
function getPartByIndex(index) {
|
||||||
return { 2012: 'year', 12: 'month', 20: 'day' }[dateParts[index]];
|
/** @type {Object.<string,string>} */
|
||||||
|
const template = {
|
||||||
|
'2012': 'year',
|
||||||
|
'12': 'month',
|
||||||
|
'20': 'day',
|
||||||
|
};
|
||||||
|
const key = dateParts[index];
|
||||||
|
return template[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [1, 3, 5].map(getPartByIndex);
|
return [1, 3, 5].map(getPartByIndex);
|
||||||
|
|
@ -28,6 +43,8 @@ export function getDateFormatBasedOnLocale() {
|
||||||
const dateParts = splitDate(formattedDate);
|
const dateParts = splitDate(formattedDate);
|
||||||
|
|
||||||
const dateFormat = {};
|
const dateFormat = {};
|
||||||
dateFormat.positions = computePositions(dateParts);
|
if (dateParts) {
|
||||||
|
dateFormat.positions = computePositions(dateParts);
|
||||||
|
}
|
||||||
return `${dateFormat.positions[0]}-${dateFormat.positions[1]}-${dateFormat.positions[2]}`;
|
return `${dateFormat.positions[0]}-${dateFormat.positions[1]}-${dateFormat.positions[2]}`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import { normalizeIntlDate } from './normalizeIntlDate.js';
|
import { normalizeIntlDate } from './normalizeIntlDate.js';
|
||||||
|
|
||||||
|
/** @type {Object.<string, Object.<string,string[]>>} */
|
||||||
const monthsLocaleCache = {};
|
const monthsLocaleCache = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Returns month names for locale
|
* @desc Returns month names for locale
|
||||||
* @param {string} options.locale locale
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.locale] locale
|
||||||
* @param {string} [options.style=long] long, short or narrow
|
* @param {string} [options.style=long] long, short or narrow
|
||||||
* @returns {Array} like: ['January', 'February', ...etc].
|
* @returns {string[]} like: ['January', 'February', ...etc].
|
||||||
*/
|
*/
|
||||||
export function getMonthNames({ locale, style = 'long' } = {}) {
|
export function getMonthNames({ locale, style = 'long' } = {}) {
|
||||||
let months = monthsLocaleCache[locale] && monthsLocaleCache[locale][style];
|
let months = monthsLocaleCache[locale] && monthsLocaleCache[locale][style];
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,32 @@
|
||||||
import { normalizeIntlDate } from './normalizeIntlDate.js';
|
import { normalizeIntlDate } from './normalizeIntlDate.js';
|
||||||
|
|
||||||
|
/** @type {Object.<string, Object.<string,string[]>>} */
|
||||||
const weekdayNamesCache = {};
|
const weekdayNamesCache = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Return cached weekday names for locale for all styles ('long', 'short', 'narrow')
|
* @desc Return cached weekday names for locale for all styles ('long', 'short', 'narrow')
|
||||||
* @param {string} locale locale
|
* @param {string} locale locale
|
||||||
* @returns {Object} like { long: ['Sunday', 'Monday'...], short: ['Sun', ...], narrow: ['S', ...] }
|
* @returns {Object.<string,string[]>} - like { long: ['Sunday', 'Monday'...], short: ['Sun', ...], narrow: ['S', ...] }
|
||||||
*/
|
*/
|
||||||
function getCachedWeekdayNames(locale) {
|
function getCachedWeekdayNames(locale) {
|
||||||
let weekdays = weekdayNamesCache[locale];
|
const cachedWeekdayNames = weekdayNamesCache[locale];
|
||||||
|
let weekdays;
|
||||||
|
|
||||||
if (weekdays) {
|
if (cachedWeekdayNames) {
|
||||||
return weekdays;
|
return cachedWeekdayNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
weekdayNamesCache[locale] = { long: [], short: [], narrow: [] };
|
weekdayNamesCache[locale] = {
|
||||||
|
long: [],
|
||||||
|
short: [],
|
||||||
|
narrow: [],
|
||||||
|
};
|
||||||
|
|
||||||
['long', 'short', 'narrow'].forEach(style => {
|
['long', 'short', 'narrow'].forEach(style => {
|
||||||
weekdays = weekdayNamesCache[locale][style];
|
weekdays = weekdayNamesCache[locale][style];
|
||||||
const formatter = new Intl.DateTimeFormat(locale, { weekday: style });
|
const formatter = new Intl.DateTimeFormat(locale, {
|
||||||
|
weekday: style,
|
||||||
|
});
|
||||||
|
|
||||||
const date = new Date('2019/04/07'); // start from Sunday
|
const date = new Date('2019/04/07'); // start from Sunday
|
||||||
for (let i = 0; i < 7; i += 1) {
|
for (let i = 0; i < 7; i += 1) {
|
||||||
|
|
@ -34,10 +42,11 @@ function getCachedWeekdayNames(locale) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Returns weekday names for locale
|
* @desc Returns weekday names for locale
|
||||||
* @param {string} options.locale locale
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.locale] locale
|
||||||
* @param {string} [options.style=long] long, short or narrow
|
* @param {string} [options.style=long] long, short or narrow
|
||||||
* @param {number} [options.firstDayOfWeek=0] 0 (Sunday), 1 (Monday), etc...
|
* @param {number} [options.firstDayOfWeek=0] 0 (Sunday), 1 (Monday), etc...
|
||||||
* @returns {Array} like: ['Sunday', 'Monday', 'Tuesday', ...etc].
|
* @returns {string[]} like: ['Sunday', 'Monday', 'Tuesday', ...etc].
|
||||||
*/
|
*/
|
||||||
export function getWeekdayNames({ locale, style = 'long', firstDayOfWeek = 0 } = {}) {
|
export function getWeekdayNames({ locale, style = 'long', firstDayOfWeek = 0 } = {}) {
|
||||||
const weekdays = getCachedWeekdayNames(locale)[style];
|
const weekdays = getCachedWeekdayNames(locale)[style];
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* @desc Makes suitable for date comparisons
|
* @desc Makes suitable for date comparisons
|
||||||
* @param {Date} d
|
* @param {Date} date
|
||||||
* @returns {Date}
|
* @returns {Date}
|
||||||
*/
|
*/
|
||||||
export function normalizeDateTime(d) {
|
export function normalizeDateTime(date) {
|
||||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* To filter out some added characters in IE
|
* To filter out some added characters in IE
|
||||||
*
|
*
|
||||||
* @param str
|
* @param {string} str
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function normalizeIntlDate(str) {
|
export function normalizeIntlDate(str) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
/**
|
/**
|
||||||
* To get the absolute value of a number.
|
* To get the absolute value of a number.
|
||||||
*
|
*
|
||||||
* @param n
|
* @param {string} n - number in string format
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function pad(n) {
|
export function pad(n) {
|
||||||
const digitRegex = /^\d+$/;
|
const digitRegex = /^\d+$/;
|
||||||
const v = digitRegex.test(n) ? Math.abs(n) : n;
|
const v = digitRegex.test(String(n)) ? Math.abs(Number(n)) : n;
|
||||||
|
|
||||||
return String(v < 10 ? `0${v}` : v);
|
return String(v < 10 ? `0${v}` : v);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,14 @@ import { localize } from '../localize.js';
|
||||||
import { getDateFormatBasedOnLocale } from './getDateFormatBasedOnLocale.js';
|
import { getDateFormatBasedOnLocale } from './getDateFormatBasedOnLocale.js';
|
||||||
import { addLeadingZero } from './addLeadingZero.js';
|
import { addLeadingZero } from './addLeadingZero.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {function} fn
|
||||||
|
*/
|
||||||
const memoize = fn => {
|
const memoize = fn => {
|
||||||
|
/** @type {Object.<any, any>} */
|
||||||
const cache = {};
|
const cache = {};
|
||||||
return parm => {
|
|
||||||
|
return /** @param {any} parm */ parm => {
|
||||||
const n = parm;
|
const n = parm;
|
||||||
if (n in cache) {
|
if (n in cache) {
|
||||||
return cache[n];
|
return cache[n];
|
||||||
|
|
@ -20,11 +25,11 @@ const memoizedGetDateFormatBasedOnLocale = memoize(getDateFormatBasedOnLocale);
|
||||||
/**
|
/**
|
||||||
* To parse a date into the right format
|
* To parse a date into the right format
|
||||||
*
|
*
|
||||||
* @param date
|
* @param {string} dateString
|
||||||
* @returns {Date}
|
* @returns {Date | undefined}
|
||||||
*/
|
*/
|
||||||
export function parseDate(date) {
|
export function parseDate(dateString) {
|
||||||
const stringToParse = addLeadingZero(date);
|
const stringToParse = addLeadingZero(dateString);
|
||||||
let parsedString;
|
let parsedString;
|
||||||
switch (memoizedGetDateFormatBasedOnLocale(localize.locale)) {
|
switch (memoizedGetDateFormatBasedOnLocale(localize.locale)) {
|
||||||
case 'day-month-year':
|
case 'day-month-year':
|
||||||
|
|
@ -51,7 +56,7 @@ export function parseDate(date) {
|
||||||
const parsedDate = new Date(parsedString);
|
const parsedDate = new Date(parsedString);
|
||||||
// Check if parsedDate is not `Invalid Date`
|
// Check if parsedDate is not `Invalid Date`
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
if (!isNaN(parsedDate)) {
|
if (!isNaN(parsedDate.getTime())) {
|
||||||
return parsedDate;
|
return parsedDate;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import { clean } from './clean.js';
|
||||||
/**
|
/**
|
||||||
* To sanitize a date from IE11 handling
|
* To sanitize a date from IE11 handling
|
||||||
*
|
*
|
||||||
* @param date
|
* @param {Date} date
|
||||||
* @returns {string|XML}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function sanitizedDateTimeFormat(date) {
|
export function sanitizedDateTimeFormat(date) {
|
||||||
const fDate = formatDate(date);
|
const fDate = formatDate(date);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* To split a date into days, months, years, etc
|
* To split a date into days, months, years, etc
|
||||||
*
|
*
|
||||||
* @param date
|
* @param {string} dateAsString
|
||||||
* @returns {Array|{index: number, input: string}|*}
|
* @returns {ArrayLike.<string> | null}
|
||||||
*/
|
*/
|
||||||
export function splitDate(date) {
|
export function splitDate(dateAsString) {
|
||||||
return date.match(/(\d{1,4})([^\d]+)(\d{1,4})([^\d]+)(\d{1,4})/);
|
return dateAsString.match(/(\d{1,4})([^\d]+)(\d{1,4})([^\d]+)(\d{1,4})/);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* To trim the date
|
* To trim the date
|
||||||
*
|
*
|
||||||
* @param dateAsString
|
* @param {string} dateAsString
|
||||||
* @returns {string|XML}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function trim(dateAsString) {
|
export function trim(dateAsString) {
|
||||||
return dateAsString.replace(/^[^\d]*/g, '').replace(/[^\d]*$/g, '');
|
return dateAsString.replace(/^[^\d]*/g, '').replace(/[^\d]*$/g, '');
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* @param {Object.<string, Object>} obj
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
export default function isLocalizeESModule(obj) {
|
export default function isLocalizeESModule(obj) {
|
||||||
return !!(obj && obj.default && typeof obj.default === 'object' && Object.keys(obj).length === 1);
|
return !!(obj && obj.default && typeof obj.default === 'object' && Object.keys(obj).length === 1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { singletonManager } from 'singleton-manager';
|
import { singletonManager } from 'singleton-manager';
|
||||||
import { LocalizeManager } from './LocalizeManager.js';
|
import { LocalizeManager } from './LocalizeManager.js';
|
||||||
|
|
||||||
|
/** @type {LocalizeManager} */
|
||||||
// eslint-disable-next-line import/no-mutable-exports
|
// eslint-disable-next-line import/no-mutable-exports
|
||||||
export let localize =
|
export let localize =
|
||||||
singletonManager.get('@lion/localize::localize::0.10.x') ||
|
singletonManager.get('@lion/localize::localize::0.10.x') ||
|
||||||
|
|
@ -9,6 +10,9 @@ export let localize =
|
||||||
fallbackLocale: 'en-GB',
|
fallbackLocale: 'en-GB',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {LocalizeManager} newLocalize
|
||||||
|
*/
|
||||||
export function setLocalize(newLocalize) {
|
export function setLocalize(newLocalize) {
|
||||||
localize.teardown();
|
localize.teardown();
|
||||||
localize = newLocalize;
|
localize = newLocalize;
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@ import { localize } from '../localize.js';
|
||||||
* When number is NaN we should return an empty string or returnIfNaN param
|
* When number is NaN we should return an empty string or returnIfNaN param
|
||||||
*
|
*
|
||||||
* @param {string} returnIfNaN
|
* @param {string} returnIfNaN
|
||||||
* @returns {*}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function emptyStringWhenNumberNan(returnIfNaN) {
|
export function emptyStringWhenNumberNan(returnIfNaN) {
|
||||||
const stringToReturn = returnIfNaN || localize.formatNumberOptions.returnIfNaN;
|
return returnIfNaN || localize.formatNumberOptions.returnIfNaN;
|
||||||
return stringToReturn;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
/**
|
/**
|
||||||
* Add separators when they are not present
|
* Add separators when they are not present
|
||||||
*
|
*
|
||||||
* @param {Array} formattedParts
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
|
* @param {FormatNumberPart[]} formattedParts
|
||||||
* @param {string} groupSeparator
|
* @param {string} groupSeparator
|
||||||
* @returns {Array}
|
* @returns {FormatNumberPart[]}
|
||||||
*/
|
*/
|
||||||
export function forceAddGroupSeparators(formattedParts, groupSeparator) {
|
export function forceAddGroupSeparators(formattedParts, groupSeparator) {
|
||||||
|
/** @type {FormatNumberPart[]} */
|
||||||
let concatArray = [];
|
let concatArray = [];
|
||||||
let firstPart;
|
let firstPart;
|
||||||
let integerPart;
|
let integerPart;
|
||||||
|
|
@ -29,24 +31,41 @@ export function forceAddGroupSeparators(formattedParts, groupSeparator) {
|
||||||
numberPart += integerPart[0].value[i];
|
numberPart += integerPart[0].value[i];
|
||||||
// Create first grouping which is < 3
|
// Create first grouping which is < 3
|
||||||
if (numberPart.length === mod3 && firstGroup === false) {
|
if (numberPart.length === mod3 && firstGroup === false) {
|
||||||
numberArray.push({ type: 'integer', value: numberPart });
|
numberArray.push({
|
||||||
|
type: 'integer',
|
||||||
|
value: numberPart,
|
||||||
|
});
|
||||||
if (numberOfDigits > 3) {
|
if (numberOfDigits > 3) {
|
||||||
numberArray.push({ type: 'group', value: groupSeparator });
|
numberArray.push({
|
||||||
|
type: 'group',
|
||||||
|
value: groupSeparator,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
numberPart = '';
|
numberPart = '';
|
||||||
firstGroup = true;
|
firstGroup = true;
|
||||||
// Create groupings of 3
|
// Create groupings of 3
|
||||||
} else if (numberPart.length === 3 && i < numberOfDigits - 1) {
|
} else if (numberPart.length === 3 && i < numberOfDigits - 1) {
|
||||||
numberOfGroups += 1;
|
numberOfGroups += 1;
|
||||||
numberArray.push({ type: 'integer', value: numberPart });
|
numberArray.push({
|
||||||
|
type: 'integer',
|
||||||
|
value: numberPart,
|
||||||
|
});
|
||||||
if (numberOfGroups !== groups) {
|
if (numberOfGroups !== groups) {
|
||||||
numberArray.push({ type: 'group', value: groupSeparator });
|
numberArray.push({
|
||||||
|
type: 'group',
|
||||||
|
value: groupSeparator,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
numberPart = '';
|
numberPart = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
numberArray.push({ type: 'integer', value: numberPart });
|
numberArray.push({
|
||||||
concatArray = firstPart.concat(numberArray, formattedParts);
|
type: 'integer',
|
||||||
|
value: numberPart,
|
||||||
|
});
|
||||||
|
if (firstPart) {
|
||||||
|
concatArray = firstPart.concat(numberArray, formattedParts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return concatArray;
|
return concatArray;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* For Dutch and Belgian amounts the currency should be at the end of the string
|
* For Dutch and Belgian amounts the currency should be at the end of the string
|
||||||
*
|
*
|
||||||
* @param {Array} formattedParts
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
* @returns {Array}
|
* @param {FormatNumberPart[]} formattedParts
|
||||||
|
* @returns {FormatNumberPart[]}
|
||||||
*/
|
*/
|
||||||
export function forceCurrencyToEnd(formattedParts) {
|
export function forceCurrencyToEnd(formattedParts) {
|
||||||
if (formattedParts[0].type === 'currency') {
|
if (formattedParts[0].type === 'currency') {
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,25 @@
|
||||||
export function forceENAUSymbols(formattedParts, options) {
|
/** @type {Object.<string,string>} */
|
||||||
|
const CURRENCY_CODE_SYMBOL_MAP = {
|
||||||
|
EUR: '€',
|
||||||
|
USD: '$',
|
||||||
|
JPY: '¥',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the symbols for locale 'en-AU', due to bug in Chrome
|
||||||
|
*
|
||||||
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
|
* @param {FormatNumberPart[]} formattedParts
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.currency]
|
||||||
|
* @param {string} [options.currencyDisplay]
|
||||||
|
* @returns {FormatNumberPart[]}
|
||||||
|
*/
|
||||||
|
export function forceENAUSymbols(formattedParts, { currency, currencyDisplay } = {}) {
|
||||||
const result = formattedParts;
|
const result = formattedParts;
|
||||||
const numberOfParts = result.length;
|
if (formattedParts.length > 1 && currencyDisplay === 'symbol') {
|
||||||
// Change the symbols for locale 'en-AU', due to bug in Chrome
|
if (Object.keys(CURRENCY_CODE_SYMBOL_MAP).includes(currency)) {
|
||||||
if (numberOfParts > 1 && options && options.currencyDisplay === 'symbol') {
|
result[0].value = CURRENCY_CODE_SYMBOL_MAP[currency];
|
||||||
switch (options.currency) {
|
|
||||||
case 'EUR':
|
|
||||||
result[0].value = '€';
|
|
||||||
break;
|
|
||||||
case 'USD':
|
|
||||||
result[0].value = '$';
|
|
||||||
break;
|
|
||||||
case 'JPY':
|
|
||||||
result[0].value = '¥';
|
|
||||||
break;
|
|
||||||
/* no default */
|
|
||||||
}
|
}
|
||||||
result[1].value = '';
|
result[1].value = '';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
import { normalSpaces } from './normalSpaces.js';
|
import { normalSpaces } from './normalSpaces.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array} formattedParts
|
* Parts with forced "normal" spaces
|
||||||
* @return {Array} parts with forced "normal" spaces
|
*
|
||||||
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
|
* @param {FormatNumberPart[]} formattedParts
|
||||||
|
* @returns {FormatNumberPart[]}
|
||||||
*/
|
*/
|
||||||
export function forceNormalSpaces(formattedParts) {
|
export function forceNormalSpaces(formattedParts) {
|
||||||
|
/** @type {FormatNumberPart[]} */
|
||||||
const result = [];
|
const result = [];
|
||||||
formattedParts.forEach(part => {
|
formattedParts.forEach(part => {
|
||||||
result.push({
|
result.push({
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,20 @@
|
||||||
/**
|
/**
|
||||||
* When in some locales there is no space between currency and amount it is added
|
* When in some locales there is no space between currency and amount it is added
|
||||||
*
|
*
|
||||||
* @param {Array} formattedParts
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
* @param {Object} options
|
* @param {FormatNumberPart[]} formattedParts
|
||||||
* @returns {*}
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.currency]
|
||||||
|
* @param {string} [options.currencyDisplay]
|
||||||
|
* @returns {FormatNumberPart[]}
|
||||||
*/
|
*/
|
||||||
export function forceSpaceBetweenCurrencyCodeAndNumber(formattedParts, options) {
|
export function forceSpaceBetweenCurrencyCodeAndNumber(
|
||||||
|
formattedParts,
|
||||||
|
{ currency, currencyDisplay } = {},
|
||||||
|
) {
|
||||||
const numberOfParts = formattedParts.length;
|
const numberOfParts = formattedParts.length;
|
||||||
const literalObject = { type: 'literal', value: ' ' };
|
const literalObject = { type: 'literal', value: ' ' };
|
||||||
if (numberOfParts > 1 && options && options.currency && options.currencyDisplay === 'code') {
|
if (numberOfParts > 1 && currency && currencyDisplay === 'code') {
|
||||||
if (formattedParts[0].type === 'currency' && formattedParts[1].type !== 'literal') {
|
if (formattedParts[0].type === 'currency' && formattedParts[1].type !== 'literal') {
|
||||||
// currency in front of a number: EUR 1.00
|
// currency in front of a number: EUR 1.00
|
||||||
formattedParts.splice(1, 0, literalObject);
|
formattedParts.splice(1, 0, literalObject);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* @desc Intl uses 0 as group separator for bg-BG locale.
|
* @desc Intl uses 0 as group separator for bg-BG locale.
|
||||||
* This should be a ' '
|
* This should be a ' '
|
||||||
* @param {{type,value}[]} formattedParts
|
*
|
||||||
* @returns {{type,value}[]} corrected formatted parts
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
|
* @param {FormatNumberPart[]} formattedParts
|
||||||
|
* @returns {FormatNumberPart[]} corrected formatted parts
|
||||||
*/
|
*/
|
||||||
export function forceSpaceInsteadOfZeroForGroup(formattedParts) {
|
export function forceSpaceInsteadOfZeroForGroup(formattedParts) {
|
||||||
return formattedParts.map(p => {
|
return formattedParts.map(p => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
export function forceTryCurrencyCode(formattedParts, options) {
|
/**
|
||||||
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
|
* @param {FormatNumberPart[]} formattedParts
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.currency]
|
||||||
|
* @param {string} [options.currencyDisplay]
|
||||||
|
* @returns {FormatNumberPart[]}
|
||||||
|
*/
|
||||||
|
export function forceTryCurrencyCode(formattedParts, { currency, currencyDisplay } = {}) {
|
||||||
const result = formattedParts;
|
const result = formattedParts;
|
||||||
// Chage the currencycode from TRY to TL, for Turkey
|
// Change the currency code from TRY to TL, for Turkey
|
||||||
if (options.currency === 'TRY' && options.currencyDisplay === 'code') {
|
if (currency === 'TRY' && currencyDisplay === 'code') {
|
||||||
if (result[0].value === 'TRY') {
|
if (result[0].value === 'TRY') {
|
||||||
result[0].value = 'TL';
|
result[0].value = 'TL';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
export function forceYenSymbol(formattedParts, options) {
|
/**
|
||||||
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
|
* @param {FormatNumberPart[]} formattedParts
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.currency]
|
||||||
|
* @param {string} [options.currencyDisplay]
|
||||||
|
* @returns {FormatNumberPart[]}
|
||||||
|
*/
|
||||||
|
export function forceYenSymbol(formattedParts, { currency, currencyDisplay } = {}) {
|
||||||
const result = formattedParts;
|
const result = formattedParts;
|
||||||
const numberOfParts = result.length;
|
const numberOfParts = result.length;
|
||||||
// Change the symbol from JPY to ¥, due to bug in Chrome
|
// Change the symbol from JPY to ¥, due to bug in Chrome
|
||||||
if (
|
if (numberOfParts > 1 && currency === 'JPY' && currencyDisplay === 'symbol') {
|
||||||
numberOfParts > 1 &&
|
|
||||||
options &&
|
|
||||||
options.currency === 'JPY' &&
|
|
||||||
options.currencyDisplay === 'symbol'
|
|
||||||
) {
|
|
||||||
result[numberOfParts - 1].value = '¥';
|
result[numberOfParts - 1].value = '¥';
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,41 @@ import { formatNumberToParts } from './formatNumberToParts.js';
|
||||||
/**
|
/**
|
||||||
* Formats a number based on locale and options. It uses Intl for the formatting.
|
* Formats a number based on locale and options. It uses Intl for the formatting.
|
||||||
*
|
*
|
||||||
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
* @param {number} number Number to be formatted
|
* @param {number} number Number to be formatted
|
||||||
* @param {Object} options Intl options are available extended by roundMode
|
* @param {Object} [options] Intl options are available extended by roundMode and returnIfNaN
|
||||||
* @returns {*} Formatted number
|
* @param {string} [options.roundMode]
|
||||||
|
* @param {string} [options.returnIfNaN]
|
||||||
|
* @param {string} [options.locale]
|
||||||
|
* @param {string} [options.localeMatcher]
|
||||||
|
* @param {string} [options.numberingSystem]
|
||||||
|
* @param {string} [options.style]
|
||||||
|
* @param {string} [options.currency]
|
||||||
|
* @param {string} [options.currencyDisplay]
|
||||||
|
* @param {boolean}[options.useGrouping]
|
||||||
|
* @param {number} [options.minimumIntegerDigits]
|
||||||
|
* @param {number} [options.minimumFractionDigits]
|
||||||
|
* @param {number} [options.maximumFractionDigits]
|
||||||
|
* @param {number} [options.minimumSignificantDigits]
|
||||||
|
* @param {number} [options.maximumSignificantDigits]
|
||||||
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function formatNumber(number, options) {
|
export function formatNumber(number, options = {}) {
|
||||||
if (number === undefined || number === null) return '';
|
if (number === undefined || number === null) return '';
|
||||||
const formattedToParts = formatNumberToParts(number, options);
|
const formattedToParts = formatNumberToParts(number, options);
|
||||||
// If number is not a number
|
// If number is not a number
|
||||||
if (
|
if (
|
||||||
formattedToParts === (options && options.returnIfNaN) ||
|
formattedToParts === options.returnIfNaN ||
|
||||||
formattedToParts === localize.formatNumberOptions.returnIfNaN
|
formattedToParts === localize.formatNumberOptions.returnIfNaN
|
||||||
) {
|
) {
|
||||||
return formattedToParts;
|
return /** @type {string} */ (formattedToParts);
|
||||||
}
|
}
|
||||||
let printNumberOfParts = '';
|
let printNumberOfParts = '';
|
||||||
// update numberOfParts because there may be some parts added
|
// update numberOfParts because there may be some parts added
|
||||||
const numberOfParts = formattedToParts && formattedToParts.length;
|
const numberOfParts = formattedToParts && formattedToParts.length;
|
||||||
for (let i = 0; i < numberOfParts; i += 1) {
|
for (let i = 0; i < numberOfParts; i += 1) {
|
||||||
printNumberOfParts += formattedToParts[i].value;
|
const part = /** @type {FormatNumberPart} */ (formattedToParts[i]);
|
||||||
|
printNumberOfParts += part.value;
|
||||||
}
|
}
|
||||||
return printNumberOfParts;
|
return printNumberOfParts;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,26 @@ import { roundNumber } from './roundNumber.js';
|
||||||
/**
|
/**
|
||||||
* Splits a number up in parts for integer, fraction, group, literal, decimal and currency.
|
* Splits a number up in parts for integer, fraction, group, literal, decimal and currency.
|
||||||
*
|
*
|
||||||
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
* @param {number} number Number to split up
|
* @param {number} number Number to split up
|
||||||
* @param {Object} options Intl options are available extended by roundMode
|
* @param {Object} [options] Intl options are available extended by roundMode,returnIfNaN
|
||||||
* @returns {Array} Array with parts
|
* @param {string} [options.roundMode]
|
||||||
|
* @param {string} [options.returnIfNaN]
|
||||||
|
* @param {string} [options.locale]
|
||||||
|
* @param {string} [options.localeMatcher]
|
||||||
|
* @param {string} [options.numberingSystem]
|
||||||
|
* @param {string} [options.style]
|
||||||
|
* @param {string} [options.currency]
|
||||||
|
* @param {string} [options.currencyDisplay]
|
||||||
|
* @param {boolean}[options.useGrouping]
|
||||||
|
* @param {number} [options.minimumIntegerDigits]
|
||||||
|
* @param {number} [options.minimumFractionDigits]
|
||||||
|
* @param {number} [options.maximumFractionDigits]
|
||||||
|
* @param {number} [options.minimumSignificantDigits]
|
||||||
|
* @param {number} [options.maximumSignificantDigits]
|
||||||
|
* @returns {string | FormatNumberPart[]} Array with parts or (an empty string or returnIfNaN if not a number)
|
||||||
*/
|
*/
|
||||||
export function formatNumberToParts(number, options) {
|
export function formatNumberToParts(number, options = {}) {
|
||||||
let parsedNumber = typeof number === 'string' ? parseFloat(number) : number;
|
let parsedNumber = typeof number === 'string' ? parseFloat(number) : number;
|
||||||
const computedLocale = getLocale(options && options.locale);
|
const computedLocale = getLocale(options && options.locale);
|
||||||
// when parsedNumber is not a number we should return an empty string or returnIfNaN
|
// when parsedNumber is not a number we should return an empty string or returnIfNaN
|
||||||
|
|
@ -25,6 +40,7 @@ export function formatNumberToParts(number, options) {
|
||||||
parsedNumber = roundNumber(number, options.roundMode);
|
parsedNumber = roundNumber(number, options.roundMode);
|
||||||
}
|
}
|
||||||
let formattedParts = [];
|
let formattedParts = [];
|
||||||
|
|
||||||
const formattedNumber = Intl.NumberFormat(computedLocale, options).format(parsedNumber);
|
const formattedNumber = Intl.NumberFormat(computedLocale, options).format(parsedNumber);
|
||||||
const regexCurrency = /[.,\s0-9]/;
|
const regexCurrency = /[.,\s0-9]/;
|
||||||
const regexMinusSign = /[-]/; // U+002D, Hyphen-Minus, -
|
const regexMinusSign = /[-]/; // U+002D, Hyphen-Minus, -
|
||||||
|
|
@ -95,7 +111,7 @@ export function formatNumberToParts(number, options) {
|
||||||
if (numberPart) {
|
if (numberPart) {
|
||||||
formattedParts.push({ type: 'fraction', value: numberPart });
|
formattedParts.push({ type: 'fraction', value: numberPart });
|
||||||
}
|
}
|
||||||
// If there are no fractions but we reached the end write the numberpart as integer
|
// If there are no fractions but we reached the end write the number part as integer
|
||||||
} else if (i === formattedNumber.length - 1 && numberPart) {
|
} else if (i === formattedNumber.length - 1 && numberPart) {
|
||||||
formattedParts.push({ type: 'integer', value: numberPart });
|
formattedParts.push({ type: 'integer', value: numberPart });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,18 @@ import { formatNumberToParts } from './formatNumberToParts.js';
|
||||||
/**
|
/**
|
||||||
* Based on number, returns currency name like 'US dollar'
|
* Based on number, returns currency name like 'US dollar'
|
||||||
*
|
*
|
||||||
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
* @param {string} currencyIso iso code like USD
|
* @param {string} currencyIso iso code like USD
|
||||||
* @param {Object} options Intl options are available extended by roundMode
|
* @param {Object} options Intl options are available extended by roundMode
|
||||||
* @returns {string} currency name like 'US dollar'
|
* @returns {string} currency name like 'US dollar'
|
||||||
*/
|
*/
|
||||||
export function getCurrencyName(currencyIso, options) {
|
export function getCurrencyName(currencyIso, options) {
|
||||||
const parts = formatNumberToParts(1, {
|
const parts = /** @type {FormatNumberPart[]} */ (formatNumberToParts(1, {
|
||||||
...options,
|
...options,
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: currencyIso,
|
currency: currencyIso,
|
||||||
currencyDisplay: 'name',
|
currencyDisplay: 'name',
|
||||||
});
|
}));
|
||||||
const currencyName = parts
|
const currencyName = parts
|
||||||
.filter(p => p.type === 'currency')
|
.filter(p => p.type === 'currency')
|
||||||
.map(o => o.value)
|
.map(o => o.value)
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import { getLocale } from './getLocale.js';
|
||||||
* To get the decimal separator
|
* To get the decimal separator
|
||||||
*
|
*
|
||||||
* @param {string} locale To override the browser locale
|
* @param {string} locale To override the browser locale
|
||||||
* @returns {Object} the separator
|
* @returns {string} The separator
|
||||||
*/
|
*/
|
||||||
export function getDecimalSeparator(locale) {
|
export function getDecimalSeparator(locale) {
|
||||||
const computedLocale = getLocale(locale);
|
const computedLocale = getLocale(locale);
|
||||||
const formattedNumber = Intl.NumberFormat(computedLocale, {
|
const formattedNumber = Intl.NumberFormat(computedLocale, {
|
||||||
style: 'decimal',
|
style: 'decimal',
|
||||||
minimumFractionDigits: 1,
|
minimumFractionDigits: 1,
|
||||||
}).format('1');
|
}).format(1);
|
||||||
return formattedNumber[1];
|
return formattedNumber[1];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,15 @@ import { formatNumberToParts } from './formatNumberToParts.js';
|
||||||
* @example
|
* @example
|
||||||
* getFractionDigits('JOD'); // return 3
|
* getFractionDigits('JOD'); // return 3
|
||||||
*
|
*
|
||||||
* @param {string} currency Currency code e.g. EUR
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
* @return {number} fraction for the given currency
|
* @param {string} [currency="EUR"] Currency code e.g. EUR
|
||||||
|
* @returns {number} fraction for the given currency
|
||||||
*/
|
*/
|
||||||
export function getFractionDigits(currency = 'EUR') {
|
export function getFractionDigits(currency = 'EUR') {
|
||||||
const parts = formatNumberToParts(123, {
|
const parts = /** @type {FormatNumberPart[]} */ (formatNumberToParts(123, {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency,
|
currency,
|
||||||
});
|
}));
|
||||||
const [fractionPart] = parts.filter(part => part.type === 'fraction');
|
const [fractionPart] = parts.filter(part => part.type === 'fraction');
|
||||||
return fractionPart ? fractionPart.value.length : 0;
|
return fractionPart ? fractionPart.value.length : 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,16 @@ import { getLocale } from './getLocale.js';
|
||||||
import { normalSpaces } from './normalSpaces.js';
|
import { normalSpaces } from './normalSpaces.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To get the group separator
|
* Gets the group separator
|
||||||
*
|
*
|
||||||
* @param {string} locale To override the browser locale
|
* @param {string} [locale] To override the browser locale
|
||||||
* @returns {Object} the separator
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getGroupSeparator(locale) {
|
export function getGroupSeparator(locale) {
|
||||||
const computedLocale = getLocale(locale);
|
const computedLocale = getLocale(locale);
|
||||||
const formattedNumber = Intl.NumberFormat(computedLocale, {
|
const formattedNumber = Intl.NumberFormat(computedLocale, {
|
||||||
style: 'decimal',
|
style: 'decimal',
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
}).format('10000');
|
}).format(10000);
|
||||||
return normalSpaces(formattedNumber[2]);
|
return normalSpaces(formattedNumber[2]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { localize } from '../localize.js';
|
||||||
/**
|
/**
|
||||||
* Gets the locale to use
|
* Gets the locale to use
|
||||||
*
|
*
|
||||||
* @param {string} locale Locale to override browser locale
|
* @param {string} [locale] Locale to override browser locale
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getLocale(locale) {
|
export function getLocale(locale) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* @param {Array} value
|
* @param {string} value
|
||||||
* @return {Array} value with forced "normal" space
|
* @returns {string} value with forced "normal" space
|
||||||
*/
|
*/
|
||||||
export function normalSpaces(value) {
|
export function normalSpaces(value) {
|
||||||
// If non-breaking space (160) or narrow non-breaking space (8239) then return ' '
|
// If non-breaking space (160) or narrow non-breaking space (8239) then return ' '
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* Function that fixes currency label with locale options
|
* For Turkey fixes currency label with locale options
|
||||||
*
|
*
|
||||||
* @param {String} currency
|
* @param {string} currency
|
||||||
* @param {string} locale
|
* @param {string} locale
|
||||||
* @returns {string} currency
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function normalizeCurrencyLabel(currency, locale) {
|
export function normalizeCurrencyLabel(currency, locale) {
|
||||||
if (currency === 'TRY' && locale === 'tr-TR') {
|
return currency === 'TRY' && locale === 'tr-TR' ? 'TL' : currency;
|
||||||
return 'TL';
|
|
||||||
}
|
|
||||||
return currency;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,19 @@ import { forceENAUSymbols } from './forceENAUSymbols.js';
|
||||||
/**
|
/**
|
||||||
* Function with all fixes on localize
|
* Function with all fixes on localize
|
||||||
*
|
*
|
||||||
* @param {Array} formattedParts
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
* @param {Object} options
|
* @param {FormatNumberPart[]} formattedParts
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.style]
|
||||||
|
* @param {string} [options.currency]
|
||||||
|
* @param {string} [options.currencyDisplay]
|
||||||
* @param {string} _locale
|
* @param {string} _locale
|
||||||
* @returns {*}
|
* @returns {FormatNumberPart[]}
|
||||||
*/
|
*/
|
||||||
export function normalizeIntl(formattedParts, options, _locale) {
|
export function normalizeIntl(formattedParts, options = {}, _locale) {
|
||||||
let normalize = forceNormalSpaces(formattedParts, options);
|
let normalize = forceNormalSpaces(formattedParts);
|
||||||
// Dutch and Belgian currency must be moved to end of number
|
// Dutch and Belgian currency must be moved to end of number
|
||||||
if (options && options.style === 'currency') {
|
if (options.style === 'currency') {
|
||||||
if (options.currencyDisplay === 'code' && _locale.slice(0, 2) === 'nl') {
|
if (options.currencyDisplay === 'code' && _locale.slice(0, 2) === 'nl') {
|
||||||
normalize = forceCurrencyToEnd(normalize);
|
normalize = forceCurrencyToEnd(normalize);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
*
|
*
|
||||||
* @param {number} number
|
* @param {number} number
|
||||||
* @param {string} roundMode
|
* @param {string} roundMode
|
||||||
* @returns {*}
|
* @throws {Error} roundMode can only be round|floor|ceiling
|
||||||
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
export function roundNumber(number, roundMode) {
|
export function roundNumber(number, roundMode) {
|
||||||
switch (roundMode) {
|
switch (roundMode) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,22 @@
|
||||||
|
/**
|
||||||
|
* @type {Object.<String, Object>}
|
||||||
|
*/
|
||||||
let fakeImports = {};
|
let fakeImports = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path
|
||||||
|
* @param {Object} data
|
||||||
|
*/
|
||||||
export function setupFakeImport(path, data) {
|
export function setupFakeImport(path, data) {
|
||||||
const fakeExports = { ...data };
|
const fakeExports = { ...data };
|
||||||
Object.defineProperty(fakeExports, '__esModule', { value: true });
|
Object.defineProperty(fakeExports, '__esModule', { value: true });
|
||||||
fakeImports[path] = fakeExports;
|
fakeImports[path] = fakeExports;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} namespaces
|
||||||
|
* @param {string[]} locales
|
||||||
|
*/
|
||||||
export function setupEmptyFakeImportsFor(namespaces, locales) {
|
export function setupEmptyFakeImportsFor(namespaces, locales) {
|
||||||
namespaces.forEach(namespace => {
|
namespaces.forEach(namespace => {
|
||||||
locales.forEach(locale => {
|
locales.forEach(locale => {
|
||||||
|
|
@ -20,6 +31,11 @@ export function resetFakeImport() {
|
||||||
fakeImports = {};
|
fakeImports = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} result
|
||||||
|
* @param {Function} resolve
|
||||||
|
* @param {Function} reject
|
||||||
|
*/
|
||||||
function resolveOrReject(result, resolve, reject) {
|
function resolveOrReject(result, resolve, reject) {
|
||||||
if (result) {
|
if (result) {
|
||||||
resolve(result);
|
resolve(result);
|
||||||
|
|
@ -28,6 +44,11 @@ function resolveOrReject(result, resolve, reject) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path
|
||||||
|
* @param {number} [ms=0]
|
||||||
|
* @returns {Promise.<Object>}
|
||||||
|
*/
|
||||||
export async function fakeImport(path, ms = 0) {
|
export async function fakeImport(path, ms = 0) {
|
||||||
const result = fakeImports[path];
|
const result = fakeImports[path];
|
||||||
if (ms > 0) {
|
if (ms > 0) {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
import { expect, oneEvent, aTimeout } from '@open-wc/testing';
|
import { expect, oneEvent, aTimeout } from '@open-wc/testing';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
|
||||||
import { fetchMock } from '@bundled-es-modules/fetch-mock';
|
import { fetchMock } from '@bundled-es-modules/fetch-mock';
|
||||||
import { setupFakeImport, resetFakeImport, fakeImport } from '../test-helpers.js';
|
import { setupFakeImport, resetFakeImport, fakeImport } from '../test-helpers.js';
|
||||||
|
|
||||||
import { LocalizeManager } from '../src/LocalizeManager.js';
|
import { LocalizeManager } from '../src/LocalizeManager.js';
|
||||||
|
|
||||||
// useful for IE11 where LTR and RTL symbols are put by Intl when rendering dates
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* Useful for IE11 where LTR and RTL symbols are put by Intl when rendering dates
|
||||||
|
*/
|
||||||
function removeLtrRtl(str) {
|
function removeLtrRtl(str) {
|
||||||
return str.replace(/(\u200E|\u200E)/g, '');
|
return str.replace(/(\u200E|\u200E)/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('LocalizeManager', () => {
|
describe('LocalizeManager', () => {
|
||||||
|
/** @type {LocalizeManager} */
|
||||||
let manager;
|
let manager;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -43,7 +46,10 @@ describe('LocalizeManager', () => {
|
||||||
|
|
||||||
it('has teardown() method removing all side effects', () => {
|
it('has teardown() method removing all side effects', () => {
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
const disconnectObserverSpy = sinon.spy(manager._htmlLangAttributeObserver, 'disconnect');
|
const disconnectObserverSpy = sinon.spy(
|
||||||
|
manager._htmlLangAttributeObserver,
|
||||||
|
/** @type {never} */ ('disconnect'),
|
||||||
|
);
|
||||||
manager.teardown();
|
manager.teardown();
|
||||||
expect(disconnectObserverSpy.callCount).to.equal(1);
|
expect(disconnectObserverSpy.callCount).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
@ -54,7 +60,11 @@ describe('LocalizeManager', () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
manager.locale = 'en-US';
|
manager.locale = 'en-US';
|
||||||
});
|
});
|
||||||
const event = await oneEvent(manager, 'localeChanged');
|
|
||||||
|
const event = await oneEvent(
|
||||||
|
/** @type {EventTarget} */ (/** @type {unknown} */ (manager)),
|
||||||
|
'localeChanged',
|
||||||
|
);
|
||||||
expect(event.detail.newLocale).to.equal('en-US');
|
expect(event.detail.newLocale).to.equal('en-US');
|
||||||
expect(event.detail.oldLocale).to.equal('en-GB');
|
expect(event.detail.oldLocale).to.equal('en-GB');
|
||||||
});
|
});
|
||||||
|
|
@ -62,6 +72,7 @@ describe('LocalizeManager', () => {
|
||||||
it('does not fire "localeChanged" event if it was set to the same locale', () => {
|
it('does not fire "localeChanged" event if it was set to the same locale', () => {
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
const eventSpy = sinon.spy();
|
const eventSpy = sinon.spy();
|
||||||
|
|
||||||
manager.addEventListener('localeChanged', eventSpy);
|
manager.addEventListener('localeChanged', eventSpy);
|
||||||
manager.locale = 'en-US';
|
manager.locale = 'en-US';
|
||||||
manager.locale = 'en-US';
|
manager.locale = 'en-US';
|
||||||
|
|
@ -127,6 +138,7 @@ describe('LocalizeManager', () => {
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
|
|
||||||
await manager.loadNamespace({
|
await manager.loadNamespace({
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -145,6 +157,7 @@ describe('LocalizeManager', () => {
|
||||||
|
|
||||||
await manager.loadNamespace(
|
await manager.loadNamespace(
|
||||||
{
|
{
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
||||||
},
|
},
|
||||||
{ locale: 'nl-NL' },
|
{ locale: 'nl-NL' },
|
||||||
|
|
@ -164,8 +177,14 @@ describe('LocalizeManager', () => {
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
|
|
||||||
await manager.loadNamespaces([
|
await manager.loadNamespaces([
|
||||||
{ 'my-defaults': locale => fakeImport(`./my-defaults/${locale}.js`) },
|
{
|
||||||
{ 'my-send-button': locale => fakeImport(`./my-send-button/${locale}.js`) },
|
/** @param {string} locale */
|
||||||
|
'my-defaults': locale => fakeImport(`./my-defaults/${locale}.js`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-send-button': locale => fakeImport(`./my-send-button/${locale}.js`),
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(manager.__storage).to.deep.equal({
|
expect(manager.__storage).to.deep.equal({
|
||||||
|
|
@ -185,8 +204,14 @@ describe('LocalizeManager', () => {
|
||||||
|
|
||||||
await manager.loadNamespaces(
|
await manager.loadNamespaces(
|
||||||
[
|
[
|
||||||
{ 'my-defaults': locale => fakeImport(`./my-defaults/${locale}.js`) },
|
{
|
||||||
{ 'my-send-button': locale => fakeImport(`./my-send-button/${locale}.js`) },
|
/** @param {string} locale */
|
||||||
|
'my-defaults': locale => fakeImport(`./my-defaults/${locale}.js`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-send-button': locale => fakeImport(`./my-send-button/${locale}.js`),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
{ locale: 'nl-NL' },
|
{ locale: 'nl-NL' },
|
||||||
);
|
);
|
||||||
|
|
@ -205,6 +230,7 @@ describe('LocalizeManager', () => {
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
|
|
||||||
await manager.loadNamespace({
|
await manager.loadNamespace({
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -220,6 +246,7 @@ describe('LocalizeManager', () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await manager.loadNamespace({
|
await manager.loadNamespace({
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -242,6 +269,7 @@ describe('LocalizeManager', () => {
|
||||||
setupFakeImport('./my-component/en-GB.js', { default: { greeting: 'Hello!' } });
|
setupFakeImport('./my-component/en-GB.js', { default: { greeting: 'Hello!' } });
|
||||||
|
|
||||||
await manager.loadNamespace({
|
await manager.loadNamespace({
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -259,6 +287,7 @@ describe('LocalizeManager', () => {
|
||||||
setupFakeImport('./my-component/en.js', { default: { greeting: 'Hello!' } });
|
setupFakeImport('./my-component/en.js', { default: { greeting: 'Hello!' } });
|
||||||
|
|
||||||
await manager.loadNamespace({
|
await manager.loadNamespace({
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -275,6 +304,7 @@ describe('LocalizeManager', () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await manager.loadNamespace({
|
await manager.loadNamespace({
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
'my-component': locale => fakeImport(`./my-component/${locale}.js`),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -315,10 +345,14 @@ describe('LocalizeManager', () => {
|
||||||
|
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
|
|
||||||
manager.setupNamespaceLoader('my-component', async locale => {
|
manager.setupNamespaceLoader(
|
||||||
const response = await fetch(`./my-component/${locale}.json`);
|
'my-component',
|
||||||
return response.json();
|
/** @param {string} locale */
|
||||||
});
|
async locale => {
|
||||||
|
const response = await fetch(`./my-component/${locale}.json`);
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await manager.loadNamespace('my-component');
|
await manager.loadNamespace('my-component');
|
||||||
|
|
||||||
|
|
@ -330,26 +364,43 @@ describe('LocalizeManager', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads multiple namespaces via loadNamespaces() using string routes', async () => {
|
it('loads multiple namespaces via loadNamespaces() using string routes', async () => {
|
||||||
fetchMock.get('./my-defaults/en-GB.json', { submit: 'Submit' });
|
fetchMock.get('./my-defaults/en-GB.json', {
|
||||||
fetchMock.get('./my-send-button/en-GB.json', { submit: 'Send' });
|
submit: 'Submit',
|
||||||
|
});
|
||||||
|
fetchMock.get('./my-send-button/en-GB.json', {
|
||||||
|
submit: 'Send',
|
||||||
|
});
|
||||||
|
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
|
|
||||||
manager.setupNamespaceLoader('my-defaults', async locale => {
|
manager.setupNamespaceLoader(
|
||||||
const response = await fetch(`./my-defaults/${locale}.json`);
|
'my-defaults',
|
||||||
return response.json();
|
/** @param {string} locale */
|
||||||
});
|
async locale => {
|
||||||
manager.setupNamespaceLoader('my-send-button', async locale => {
|
const response = await fetch(`./my-defaults/${locale}.json`);
|
||||||
const response = await fetch(`./my-send-button/${locale}.json`);
|
return response.json();
|
||||||
return response.json();
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
|
manager.setupNamespaceLoader(
|
||||||
|
'my-send-button',
|
||||||
|
/** @param {string} locale */
|
||||||
|
async locale => {
|
||||||
|
const response = await fetch(`./my-send-button/${locale}.json`);
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await manager.loadNamespaces(['my-defaults', 'my-send-button']);
|
await manager.loadNamespaces(['my-defaults', 'my-send-button']);
|
||||||
|
|
||||||
expect(manager.__storage).to.deep.equal({
|
expect(manager.__storage).to.deep.equal({
|
||||||
'en-GB': {
|
'en-GB': {
|
||||||
'my-send-button': { submit: 'Send' },
|
'my-send-button': {
|
||||||
'my-defaults': { submit: 'Submit' },
|
submit: 'Send',
|
||||||
|
},
|
||||||
|
'my-defaults': {
|
||||||
|
submit: 'Submit',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -359,10 +410,17 @@ describe('LocalizeManager', () => {
|
||||||
|
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
|
|
||||||
manager.setupNamespaceLoader(/my-.+/, async (locale, namespace) => {
|
manager.setupNamespaceLoader(
|
||||||
const response = await fetch(`./${namespace}/${locale}.json`);
|
/my-.+/,
|
||||||
return response.json();
|
/**
|
||||||
});
|
* @param {string} locale
|
||||||
|
* @param {string} namespace
|
||||||
|
*/
|
||||||
|
async (locale, namespace) => {
|
||||||
|
const response = await fetch(`./${namespace}/${locale}.json`);
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await manager.loadNamespace('my-component');
|
await manager.loadNamespace('my-component');
|
||||||
|
|
||||||
|
|
@ -379,10 +437,17 @@ describe('LocalizeManager', () => {
|
||||||
|
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
|
|
||||||
manager.setupNamespaceLoader(/my-.+/, async (locale, namespace) => {
|
manager.setupNamespaceLoader(
|
||||||
const response = await fetch(`./${namespace}/${locale}.json`);
|
/my-.+/,
|
||||||
return response.json();
|
/**
|
||||||
});
|
* @param {string} locale
|
||||||
|
* @param {string} namespace
|
||||||
|
*/
|
||||||
|
async (locale, namespace) => {
|
||||||
|
const response = await fetch(`./${namespace}/${locale}.json`);
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await manager.loadNamespaces(['my-defaults', 'my-send-button']);
|
await manager.loadNamespaces(['my-defaults', 'my-send-button']);
|
||||||
|
|
||||||
|
|
@ -403,6 +468,7 @@ describe('LocalizeManager', () => {
|
||||||
manager = new LocalizeManager({ autoLoadOnLocaleChange: true });
|
manager = new LocalizeManager({ autoLoadOnLocaleChange: true });
|
||||||
|
|
||||||
await manager.loadNamespace({
|
await manager.loadNamespace({
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => fakeImport(`./my-component/${locale}.js`, 25),
|
'my-component': locale => fakeImport(`./my-component/${locale}.js`, 25),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -426,6 +492,7 @@ describe('LocalizeManager', () => {
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
|
|
||||||
manager.loadNamespace({
|
manager.loadNamespace({
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => fakeImport(`./my-component/${locale}.js`, 25),
|
'my-component': locale => fakeImport(`./my-component/${locale}.js`, 25),
|
||||||
});
|
});
|
||||||
expect(manager.__storage).to.deep.equal({});
|
expect(manager.__storage).to.deep.equal({});
|
||||||
|
|
@ -468,6 +535,7 @@ describe('LocalizeManager', () => {
|
||||||
|
|
||||||
let called = 0;
|
let called = 0;
|
||||||
await manager.loadNamespace({
|
await manager.loadNamespace({
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => {
|
'my-component': locale => {
|
||||||
called += 1;
|
called += 1;
|
||||||
return fakeImport(`./my-component/${locale}.js`);
|
return fakeImport(`./my-component/${locale}.js`);
|
||||||
|
|
@ -527,7 +595,7 @@ describe('LocalizeManager', () => {
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
manager.addData('en-GB', 'my-ns', { greeting: 'Hi!' });
|
manager.addData('en-GB', 'my-ns', { greeting: 'Hi!' });
|
||||||
manager.addData('nl-NL', 'my-ns', { greeting: 'Hey!' });
|
manager.addData('nl-NL', 'my-ns', { greeting: 'Hey!' });
|
||||||
expect(manager.msg('my-ns:greeting', null, { locale: 'nl-NL' })).to.equal('Hey!');
|
expect(manager.msg('my-ns:greeting', undefined, { locale: 'nl-NL' })).to.equal('Hey!');
|
||||||
manager.reset();
|
manager.reset();
|
||||||
manager.addData('en-GB', 'my-ns', { greeting: 'Hi {name}!' });
|
manager.addData('en-GB', 'my-ns', { greeting: 'Hi {name}!' });
|
||||||
manager.addData('nl-NL', 'my-ns', { greeting: 'Hey {name}!' });
|
manager.addData('nl-NL', 'my-ns', { greeting: 'Hey {name}!' });
|
||||||
|
|
@ -561,6 +629,7 @@ describe('When supporting external translation tools like Google Translate', ()
|
||||||
let manager;
|
let manager;
|
||||||
const originalLang = document.documentElement.lang;
|
const originalLang = document.documentElement.lang;
|
||||||
|
|
||||||
|
/** @param {string} lang */
|
||||||
async function simulateGoogleTranslateOn(lang) {
|
async function simulateGoogleTranslateOn(lang) {
|
||||||
document.documentElement.lang = lang;
|
document.documentElement.lang = lang;
|
||||||
}
|
}
|
||||||
|
|
@ -569,6 +638,10 @@ describe('When supporting external translation tools like Google Translate', ()
|
||||||
document.documentElement.lang = 'auto';
|
document.documentElement.lang = 'auto';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {...*} [cfg]
|
||||||
|
* @returns {LocalizeManager}
|
||||||
|
*/
|
||||||
function getInstance(cfg) {
|
function getInstance(cfg) {
|
||||||
LocalizeManager.resetInstance();
|
LocalizeManager.resetInstance();
|
||||||
return LocalizeManager.getInstance(cfg || {});
|
return LocalizeManager.getInstance(cfg || {});
|
||||||
|
|
@ -660,6 +733,7 @@ describe('When supporting external translation tools like Google Translate', ()
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('[deprecated] When not supporting external translation tools like Google Translate', () => {
|
describe('[deprecated] When not supporting external translation tools like Google Translate', () => {
|
||||||
|
/** @type {LocalizeManager} */
|
||||||
let manager;
|
let manager;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -677,16 +751,20 @@ describe('[deprecated] When not supporting external translation tools like Googl
|
||||||
});
|
});
|
||||||
|
|
||||||
it('initializes locale from <html> by default', () => {
|
it('initializes locale from <html> by default', () => {
|
||||||
manager = new LocalizeManager({ supportExternalTranslationTools: false });
|
manager = new LocalizeManager({});
|
||||||
expect(manager.locale).to.equal('en-GB');
|
expect(manager.locale).to.equal('en-GB');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fires "localeChanged" event if locale was changed via <html lang> attribute', async () => {
|
it('fires "localeChanged" event if locale was changed via <html lang> attribute', async () => {
|
||||||
manager = new LocalizeManager({ supportExternalTranslationTools: false });
|
manager = new LocalizeManager({});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.documentElement.lang = 'en-US';
|
document.documentElement.lang = 'en-US';
|
||||||
});
|
});
|
||||||
const event = await oneEvent(manager, 'localeChanged');
|
|
||||||
|
const event = await oneEvent(
|
||||||
|
/** @type {EventTarget} */ (/** @type {unknown} */ (manager)),
|
||||||
|
'localeChanged',
|
||||||
|
);
|
||||||
expect(event.detail.newLocale).to.equal('en-US');
|
expect(event.detail.newLocale).to.equal('en-US');
|
||||||
expect(event.detail.oldLocale).to.equal('en-GB');
|
expect(event.detail.oldLocale).to.equal('en-GB');
|
||||||
});
|
});
|
||||||
|
|
@ -696,11 +774,11 @@ describe('[deprecated] When not supporting external translation tools like Googl
|
||||||
setupFakeImport('./my-component/nl-NL.js', { default: { greeting: 'Hallo!' } });
|
setupFakeImport('./my-component/nl-NL.js', { default: { greeting: 'Hallo!' } });
|
||||||
|
|
||||||
manager = new LocalizeManager({
|
manager = new LocalizeManager({
|
||||||
supportExternalTranslationTools: false,
|
|
||||||
autoLoadOnLocaleChange: true,
|
autoLoadOnLocaleChange: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await manager.loadNamespace({
|
await manager.loadNamespace({
|
||||||
|
/** @param {string} locale */
|
||||||
'my-component': locale => fakeImport(`./my-component/${locale}.js`, 25),
|
'my-component': locale => fakeImport(`./my-component/${locale}.js`, 25),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -709,7 +787,7 @@ describe('[deprecated] When not supporting external translation tools like Googl
|
||||||
});
|
});
|
||||||
|
|
||||||
document.documentElement.lang = 'nl-NL';
|
document.documentElement.lang = 'nl-NL';
|
||||||
await aTimeout(); // wait for mutation observer to be called
|
await aTimeout(0); // wait for mutation observer to be called
|
||||||
await manager.loadingComplete;
|
await manager.loadingComplete;
|
||||||
|
|
||||||
expect(manager.__storage).to.deep.equal({
|
expect(manager.__storage).to.deep.equal({
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
fixtureSync,
|
fixtureSync,
|
||||||
html,
|
html,
|
||||||
nextFrame,
|
nextFrame,
|
||||||
|
unsafeStatic,
|
||||||
} from '@open-wc/testing';
|
} from '@open-wc/testing';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { localize } from '../src/localize.js';
|
import { localize } from '../src/localize.js';
|
||||||
|
|
@ -19,6 +20,10 @@ import {
|
||||||
setupFakeImport,
|
setupFakeImport,
|
||||||
} from '../test-helpers.js';
|
} from '../test-helpers.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('../types/LocalizeMixinTypes').LocalizeMixin} LocalizeMixinHost
|
||||||
|
*/
|
||||||
|
|
||||||
describe('LocalizeMixin', () => {
|
describe('LocalizeMixin', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetFakeImport();
|
resetFakeImport();
|
||||||
|
|
@ -26,21 +31,26 @@ describe('LocalizeMixin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads namespaces defined in "get localizeNamespaces()" when created before attached to DOM', async () => {
|
it('loads namespaces defined in "get localizeNamespaces()" when created before attached to DOM', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`),
|
||||||
|
};
|
||||||
|
|
||||||
class MyElement extends LocalizeMixin(class {}) {
|
const tagString = defineCE(
|
||||||
static get localizeNamespaces() {
|
// @ts-ignore
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
class MyElement extends LocalizeMixin(LitElement) {
|
||||||
}
|
static get localizeNamespaces() {
|
||||||
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
requestUpdate() {}
|
}
|
||||||
}
|
},
|
||||||
|
);
|
||||||
|
const tag = unsafeStatic(tagString);
|
||||||
|
|
||||||
setupEmptyFakeImportsFor(['my-element'], ['en-GB']);
|
setupEmptyFakeImportsFor(['my-element'], ['en-GB']);
|
||||||
|
|
||||||
const loadNamespaceSpy = sinon.spy(localize, 'loadNamespace');
|
const loadNamespaceSpy = sinon.spy(localize, 'loadNamespace');
|
||||||
|
await fixture(html`<${tag}></${tag}>`);
|
||||||
|
|
||||||
new MyElement(); // eslint-disable-line no-new
|
|
||||||
expect(loadNamespaceSpy.callCount).to.equal(1);
|
expect(loadNamespaceSpy.callCount).to.equal(1);
|
||||||
expect(loadNamespaceSpy.calledWith(myElementNs)).to.be.true;
|
expect(loadNamespaceSpy.calledWith(myElementNs)).to.be.true;
|
||||||
|
|
||||||
|
|
@ -48,27 +58,41 @@ describe('LocalizeMixin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores duplicates in "get localizeNamespaces()" chain', async () => {
|
it('ignores duplicates in "get localizeNamespaces()" chain', async () => {
|
||||||
const defaultNs = { default: loc => fakeImport(`./default/${loc}.js`) };
|
const defaultNs = {
|
||||||
const parentElementNs = { 'parent-element': loc => fakeImport(`./parent-element/${loc}.js`) };
|
/** @param {string} loc */
|
||||||
const childElementNs = { 'child-element': loc => fakeImport(`./child-element/${loc}.js`) };
|
default: loc => fakeImport(`./default/${loc}.js`),
|
||||||
|
};
|
||||||
|
const parentElementNs = {
|
||||||
|
/** @param {string} loc */
|
||||||
|
'parent-element': loc => fakeImport(`./parent-element/${loc}.js`),
|
||||||
|
};
|
||||||
|
const childElementNs = {
|
||||||
|
/** @param {string} loc */
|
||||||
|
'child-element': loc => fakeImport(`./child-element/${loc}.js`),
|
||||||
|
};
|
||||||
|
|
||||||
class ParentElement extends LocalizeMixin(class {}) {
|
// @ts-ignore
|
||||||
|
class ParentElement extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [parentElementNs, defaultNs, ...super.localizeNamespaces];
|
return [parentElementNs, defaultNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChildElement extends LocalizeMixin(ParentElement) {
|
const tagString = defineCE(
|
||||||
static get localizeNamespaces() {
|
// @ts-ignore
|
||||||
return [childElementNs, defaultNs, ...super.localizeNamespaces];
|
class ChildElement extends LocalizeMixin(ParentElement) {
|
||||||
}
|
static get localizeNamespaces() {
|
||||||
}
|
return [childElementNs, defaultNs, ...super.localizeNamespaces];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const tag = unsafeStatic(tagString);
|
||||||
|
|
||||||
setupEmptyFakeImportsFor(['default', 'parent-element', 'child-element'], ['en-GB']);
|
setupEmptyFakeImportsFor(['default', 'parent-element', 'child-element'], ['en-GB']);
|
||||||
|
|
||||||
const loadNamespaceSpy = sinon.spy(localize, 'loadNamespace');
|
const loadNamespaceSpy = sinon.spy(localize, 'loadNamespace');
|
||||||
|
|
||||||
new ChildElement(); // eslint-disable-line no-new
|
await fixture(html`<${tag}></${tag}>`);
|
||||||
expect(loadNamespaceSpy.callCount).to.equal(3);
|
expect(loadNamespaceSpy.callCount).to.equal(3);
|
||||||
expect(loadNamespaceSpy.calledWith(childElementNs)).to.be.true;
|
expect(loadNamespaceSpy.calledWith(childElementNs)).to.be.true;
|
||||||
expect(loadNamespaceSpy.calledWith(defaultNs)).to.be.true;
|
expect(loadNamespaceSpy.calledWith(defaultNs)).to.be.true;
|
||||||
|
|
@ -78,49 +102,56 @@ describe('LocalizeMixin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls "onLocaleReady()" after namespaces were loaded for the first time (only if attached to DOM)', async () => {
|
it('calls "onLocaleReady()" after namespaces were loaded for the first time (only if attached to DOM)', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`),
|
||||||
|
};
|
||||||
|
|
||||||
class MyElement extends LocalizeMixin(class {}) {
|
// @ts-ignore
|
||||||
|
class MyElement extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUpdate() {}
|
|
||||||
|
|
||||||
onLocaleReady() {}
|
onLocaleReady() {}
|
||||||
}
|
}
|
||||||
|
const tagString = defineCE(MyElement);
|
||||||
|
|
||||||
setupEmptyFakeImportsFor(['my-element'], ['en-GB']);
|
setupEmptyFakeImportsFor(['my-element'], ['en-GB']);
|
||||||
|
|
||||||
const element = new MyElement();
|
const el = /** @type {MyElement} */ (document.createElement(tagString));
|
||||||
const onLocaleReadySpy = sinon.spy(element, 'onLocaleReady');
|
const wrapper = await fixture('<div></div>');
|
||||||
|
const onLocaleReadySpy = sinon.spy(el, 'onLocaleReady');
|
||||||
|
|
||||||
await localize.loadingComplete;
|
await localize.loadingComplete;
|
||||||
expect(onLocaleReadySpy.callCount).to.equal(0);
|
expect(onLocaleReadySpy.callCount).to.equal(0);
|
||||||
|
|
||||||
element.connectedCallback();
|
wrapper.appendChild(el);
|
||||||
|
|
||||||
await localize.loadingComplete;
|
await localize.loadingComplete;
|
||||||
expect(onLocaleReadySpy.callCount).to.equal(1);
|
expect(onLocaleReadySpy.callCount).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls "onLocaleChanged(newLocale, oldLocale)" after locale was changed (only if attached to DOM)', async () => {
|
it('calls "onLocaleChanged(newLocale, oldLocale)" after locale was changed (only if attached to DOM)', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`),
|
||||||
|
};
|
||||||
|
|
||||||
class MyElement extends LocalizeMixin(class {}) {
|
// @ts-ignore
|
||||||
|
class MyOtherElement extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUpdate() {}
|
|
||||||
|
|
||||||
onLocaleChanged() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tagString = defineCE(MyOtherElement);
|
||||||
|
|
||||||
setupEmptyFakeImportsFor(['my-element'], ['en-GB', 'nl-NL', 'ru-RU']);
|
setupEmptyFakeImportsFor(['my-element'], ['en-GB', 'nl-NL', 'ru-RU']);
|
||||||
|
|
||||||
const element = new MyElement();
|
const el = /** @type {MyOtherElement} */ (document.createElement(tagString));
|
||||||
const onLocaleChangedSpy = sinon.spy(element, 'onLocaleChanged');
|
const wrapper = await fixture('<div></div>');
|
||||||
|
const onLocaleChangedSpy = sinon.spy(el, 'onLocaleChanged');
|
||||||
|
|
||||||
await localize.loadingComplete;
|
await localize.loadingComplete;
|
||||||
|
|
||||||
|
|
@ -128,33 +159,40 @@ describe('LocalizeMixin', () => {
|
||||||
await localize.loadingComplete;
|
await localize.loadingComplete;
|
||||||
expect(onLocaleChangedSpy.callCount).to.equal(0);
|
expect(onLocaleChangedSpy.callCount).to.equal(0);
|
||||||
|
|
||||||
element.connectedCallback();
|
wrapper.appendChild(el);
|
||||||
|
|
||||||
localize.locale = 'ru-RU';
|
localize.locale = 'ru-RU';
|
||||||
await localize.loadingComplete;
|
await localize.loadingComplete;
|
||||||
expect(onLocaleChangedSpy.callCount).to.equal(1);
|
expect(onLocaleChangedSpy.callCount).to.equal(1);
|
||||||
expect(onLocaleChangedSpy.calledWith('ru-RU', 'nl-NL')).to.be.true;
|
// FIXME: Expected 0 arguments, but got 2. ts(2554) --> not sure why this sinon type is not working
|
||||||
|
// @ts-ignore
|
||||||
|
expect(onLocaleChangedSpy.calledWithExactly('ru-RU', 'nl-NL')).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls "onLocaleUpdated()" after both "onLocaleReady()" and "onLocaleChanged()"', async () => {
|
it('calls "onLocaleUpdated()" after both "onLocaleReady()" and "onLocaleChanged()"', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`),
|
||||||
|
};
|
||||||
|
|
||||||
class MyElement extends LocalizeMixin(class {}) {
|
// @ts-ignore
|
||||||
|
class MyElement extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUpdate() {}
|
|
||||||
|
|
||||||
onLocaleUpdated() {}
|
onLocaleUpdated() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tagString = defineCE(MyElement);
|
||||||
|
|
||||||
setupEmptyFakeImportsFor(['my-element'], ['en-GB', 'nl-NL']);
|
setupEmptyFakeImportsFor(['my-element'], ['en-GB', 'nl-NL']);
|
||||||
|
|
||||||
const el = new MyElement();
|
const el = /** @type {MyElement} */ (document.createElement(tagString));
|
||||||
|
const wrapper = await fixture('<div></div>');
|
||||||
const onLocaleUpdatedSpy = sinon.spy(el, 'onLocaleUpdated');
|
const onLocaleUpdatedSpy = sinon.spy(el, 'onLocaleUpdated');
|
||||||
|
|
||||||
el.connectedCallback();
|
wrapper.appendChild(el);
|
||||||
await el.localizeNamespacesLoaded;
|
await el.localizeNamespacesLoaded;
|
||||||
expect(onLocaleUpdatedSpy.callCount).to.equal(1);
|
expect(onLocaleUpdatedSpy.callCount).to.equal(1);
|
||||||
|
|
||||||
|
|
@ -162,8 +200,11 @@ describe('LocalizeMixin', () => {
|
||||||
expect(onLocaleUpdatedSpy.callCount).to.equal(2);
|
expect(onLocaleUpdatedSpy.callCount).to.equal(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have the localizeNamespacesLoaded avaliable within "onLocaleUpdated()"', async () => {
|
it('should have the localizeNamespacesLoaded available within "onLocaleUpdated()"', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`),
|
||||||
|
};
|
||||||
setupFakeImport('./my-element/en-GB.js', {
|
setupFakeImport('./my-element/en-GB.js', {
|
||||||
default: {
|
default: {
|
||||||
label: 'one',
|
label: 'one',
|
||||||
|
|
@ -174,13 +215,13 @@ describe('LocalizeMixin', () => {
|
||||||
label: 'two',
|
label: 'two',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
class MyElement extends LocalizeMixin(class {}) {
|
|
||||||
|
// @ts-ignore
|
||||||
|
class MyElement extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUpdate() {}
|
|
||||||
|
|
||||||
async onLocaleUpdated() {
|
async onLocaleUpdated() {
|
||||||
super.onLocaleUpdated();
|
super.onLocaleUpdated();
|
||||||
await this.localizeNamespacesLoaded;
|
await this.localizeNamespacesLoaded;
|
||||||
|
|
@ -188,7 +229,8 @@ describe('LocalizeMixin', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const el = new MyElement();
|
const tagString = defineCE(MyElement);
|
||||||
|
const el = /** @type {MyElement} */ (document.createElement(tagString));
|
||||||
el.connectedCallback();
|
el.connectedCallback();
|
||||||
|
|
||||||
await el.localizeNamespacesLoaded;
|
await el.localizeNamespacesLoaded;
|
||||||
|
|
@ -201,19 +243,22 @@ describe('LocalizeMixin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls "requestUpdate()" after locale was changed', async () => {
|
it('calls "requestUpdate()" after locale was changed', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`),
|
||||||
|
};
|
||||||
|
|
||||||
class MyElement extends LocalizeMixin(class {}) {
|
// @ts-ignore
|
||||||
|
class MyElement extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUpdate() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEmptyFakeImportsFor(['my-element'], ['en-GB']);
|
setupEmptyFakeImportsFor(['my-element'], ['en-GB']);
|
||||||
|
|
||||||
const el = new MyElement();
|
const tagString = defineCE(MyElement);
|
||||||
|
const el = /** @type {MyElement} */ (document.createElement(tagString));
|
||||||
const updateSpy = sinon.spy(el, 'requestUpdate');
|
const updateSpy = sinon.spy(el, 'requestUpdate');
|
||||||
|
|
||||||
el.connectedCallback();
|
el.connectedCallback();
|
||||||
|
|
@ -224,23 +269,29 @@ describe('LocalizeMixin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has msgLit() which integrates with lit-html', async () => {
|
it('has msgLit() which integrates with lit-html', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`),
|
||||||
|
};
|
||||||
setupFakeImport('./my-element/en-GB.js', {
|
setupFakeImport('./my-element/en-GB.js', {
|
||||||
default: {
|
default: {
|
||||||
greeting: 'Hi!',
|
greeting: 'Hi!',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
class MyElement extends LocalizeMixin(class {}) {
|
// @ts-ignore
|
||||||
|
class MyElement extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = new MyElement();
|
const tagString = defineCE(MyElement);
|
||||||
|
const el = /** @type {MyElement} */ (document.createElement(tagString));
|
||||||
|
el.connectedCallback();
|
||||||
const lionLocalizeMessageSpy = sinon.spy(localize, 'msg');
|
const lionLocalizeMessageSpy = sinon.spy(localize, 'msg');
|
||||||
|
|
||||||
const messageDirective = element.msgLit('my-element:greeting');
|
const messageDirective = el.msgLit('my-element:greeting');
|
||||||
expect(lionLocalizeMessageSpy.callCount).to.equal(0);
|
expect(lionLocalizeMessageSpy.callCount).to.equal(0);
|
||||||
|
|
||||||
expect(isDirective(messageDirective)).to.be.true;
|
expect(isDirective(messageDirective)).to.be.true;
|
||||||
|
|
@ -250,7 +301,7 @@ describe('LocalizeMixin', () => {
|
||||||
expect(lionLocalizeMessageSpy.callCount).to.equal(1);
|
expect(lionLocalizeMessageSpy.callCount).to.equal(1);
|
||||||
expect(lionLocalizeMessageSpy.calledWith('my-element:greeting')).to.be.true;
|
expect(lionLocalizeMessageSpy.calledWith('my-element:greeting')).to.be.true;
|
||||||
|
|
||||||
const message = element.msgLit('my-element:greeting');
|
const message = el.msgLit('my-element:greeting');
|
||||||
expect(message).to.equal('Hi!');
|
expect(message).to.equal('Hi!');
|
||||||
expect(typeof message).to.equal('string');
|
expect(typeof message).to.equal('string');
|
||||||
expect(lionLocalizeMessageSpy.callCount).to.equal(2);
|
expect(lionLocalizeMessageSpy.callCount).to.equal(2);
|
||||||
|
|
@ -259,22 +310,25 @@ describe('LocalizeMixin', () => {
|
||||||
lionLocalizeMessageSpy.restore();
|
lionLocalizeMessageSpy.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has a Promise "localizeNamespacesLoaded" which resolves once tranlations are available', async () => {
|
it('has a Promise "localizeNamespacesLoaded" which resolves once translations are available', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`, 25) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`, 25),
|
||||||
|
};
|
||||||
setupFakeImport('./my-element/en-GB.js', {
|
setupFakeImport('./my-element/en-GB.js', {
|
||||||
default: {
|
default: {
|
||||||
greeting: 'Hi!',
|
greeting: 'Hi!',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
class MyElement extends LocalizeMixin(class {}) {
|
// @ts-ignore
|
||||||
|
class MyElement extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUpdate() {}
|
|
||||||
}
|
}
|
||||||
const el = new MyElement();
|
const tagString = defineCE(MyElement);
|
||||||
|
const el = /** @type {MyElement} */ (document.createElement(tagString));
|
||||||
|
|
||||||
const messageDirective = el.msgLit('my-element:greeting');
|
const messageDirective = el.msgLit('my-element:greeting');
|
||||||
expect(isDirective(messageDirective)).to.be.true;
|
expect(isDirective(messageDirective)).to.be.true;
|
||||||
|
|
@ -284,33 +338,46 @@ describe('LocalizeMixin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders only once all translations have been loaded (if BaseElement supports it)', async () => {
|
it('renders only once all translations have been loaded (if BaseElement supports it)', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`, 25) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`, 25),
|
||||||
|
};
|
||||||
setupFakeImport('./my-element/en-GB.js', {
|
setupFakeImport('./my-element/en-GB.js', {
|
||||||
default: {
|
default: {
|
||||||
greeting: 'Hi!',
|
greeting: 'Hi!',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const tag = defineCE(
|
// @ts-ignore
|
||||||
class extends LocalizeMixin(LitElement) {
|
class MyLocalizedClass extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<p>${this.msgLit('my-element:greeting')}</p>`;
|
return html`<p>${this.msgLit('my-element:greeting')}</p>`;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
|
||||||
|
|
||||||
const el = await fixtureSync(`<${tag}></${tag}>`);
|
const tag = defineCE(MyLocalizedClass);
|
||||||
expect(el.shadowRoot.children.length).to.equal(0);
|
const el = /** @type {MyLocalizedClass} */ (await fixtureSync(`<${tag}></${tag}>`));
|
||||||
await el.updateComplete;
|
expect(el.shadowRoot).to.exist;
|
||||||
expect(el.shadowRoot.querySelector('p').innerText).to.equal('Hi!');
|
if (el.shadowRoot) {
|
||||||
|
expect(el.shadowRoot.children.length).to.equal(0);
|
||||||
|
await el.updateComplete;
|
||||||
|
const pTag = el.shadowRoot.querySelector('p');
|
||||||
|
expect(pTag).to.exist;
|
||||||
|
if (pTag) {
|
||||||
|
expect(pTag.innerText).to.equal('Hi!');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rerender on locale change once all translations are loaded (if BaseElement supports it)', async () => {
|
it('re-render on locale change once all translations are loaded (if BaseElement supports it)', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`, 25) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`, 25),
|
||||||
|
};
|
||||||
setupFakeImport('./my-element/en-GB.js', {
|
setupFakeImport('./my-element/en-GB.js', {
|
||||||
default: {
|
default: {
|
||||||
greeting: 'Hi!',
|
greeting: 'Hi!',
|
||||||
|
|
@ -322,57 +389,69 @@ describe('LocalizeMixin', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const tag = defineCE(
|
// @ts-ignore
|
||||||
class TestPromise extends LocalizeMixin(LitElement) {
|
class MyLocalizedClass extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<p>${this.msgLit('my-element:greeting')}</p>`;
|
return html`<p>${this.msgLit('my-element:greeting')}</p>`;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
|
||||||
|
|
||||||
const el = await fixture(`<${tag}></${tag}>`);
|
const tag = defineCE(MyLocalizedClass);
|
||||||
|
const el = /** @type {MyLocalizedClass} */ (await fixture(`<${tag}></${tag}>`));
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(el.shadowRoot.querySelector('p').innerText).to.equal('Hi!');
|
|
||||||
|
|
||||||
localize.locale = 'en-US';
|
expect(el.shadowRoot).to.exist;
|
||||||
expect(el.shadowRoot.querySelector('p').innerText).to.equal('Hi!');
|
if (el.shadowRoot) {
|
||||||
await el.updateComplete;
|
const p = /** @type {HTMLParagraphElement} */ (el.shadowRoot.querySelector('p'));
|
||||||
expect(el.shadowRoot.querySelector('p').innerText).to.equal('Howdy!');
|
expect(p.innerText).to.equal('Hi!');
|
||||||
|
localize.locale = 'en-US';
|
||||||
|
expect(p.innerText).to.equal('Hi!');
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(p.innerText).to.equal('Howdy!');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it can still render async by setting "static get waitForLocalizeNamespaces() { return false; }" (if BaseElement supports it)', async () => {
|
it('it can still render async by setting "static get waitForLocalizeNamespaces() { return false; }" (if BaseElement supports it)', async () => {
|
||||||
const myElementNs = { 'my-element': locale => fakeImport(`./my-element/${locale}.js`, 50) };
|
const myElementNs = {
|
||||||
|
/** @param {string} locale */
|
||||||
|
'my-element': locale => fakeImport(`./my-element/${locale}.js`, 50),
|
||||||
|
};
|
||||||
setupFakeImport('./my-element/en-GB.js', {
|
setupFakeImport('./my-element/en-GB.js', {
|
||||||
default: {
|
default: {
|
||||||
greeting: 'Hi!',
|
greeting: 'Hi!',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const tag = defineCE(
|
// @ts-ignore
|
||||||
class extends LocalizeMixin(LitElement) {
|
class MyLocalizedClass extends LocalizeMixin(LitElement) {
|
||||||
static get waitForLocalizeNamespaces() {
|
static get waitForLocalizeNamespaces() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<p>${this.msgLit('my-element:greeting')}</p>`;
|
return html`<p>${this.msgLit('my-element:greeting')}</p>`;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
|
||||||
|
|
||||||
const el = await fixture(`<${tag}></${tag}>`);
|
const tag = defineCE(MyLocalizedClass);
|
||||||
|
|
||||||
|
const el = /** @type {MyLocalizedClass} */ (await fixture(`<${tag}></${tag}>`));
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(el.shadowRoot.querySelector('p').innerText).to.equal('');
|
expect(el.shadowRoot).to.exist;
|
||||||
await el.localizeNamespacesLoaded;
|
if (el.shadowRoot) {
|
||||||
await el.updateComplete;
|
const p = /** @type {HTMLParagraphElement} */ (el.shadowRoot.querySelector('p'));
|
||||||
expect(el.shadowRoot.querySelector('p').innerText).to.equal('Hi!');
|
expect(p.innerText).to.equal('');
|
||||||
|
await el.localizeNamespacesLoaded;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(p.innerText).to.equal('Hi!');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,10 @@ describe('formatDate', () => {
|
||||||
locale: 'en-US',
|
locale: 'en-US',
|
||||||
};
|
};
|
||||||
localize.locale = 'hu-HU';
|
localize.locale = 'hu-HU';
|
||||||
let date = parseDate('2018-5-28');
|
let date = /** @type {Date} */ (parseDate('2018-5-28'));
|
||||||
expect(formatDate(date)).to.equal('2018. 05. 28.');
|
expect(formatDate(date)).to.equal('2018. 05. 28.');
|
||||||
|
|
||||||
date = parseDate('1970-11-3');
|
date = /** @type {Date} */ (parseDate('1970-11-3'));
|
||||||
expect(formatDate(date, options)).to.equal('Tuesday, November 03, 1970');
|
expect(formatDate(date, options)).to.equal('Tuesday, November 03, 1970');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -73,13 +73,13 @@ describe('formatDate', () => {
|
||||||
locale: 'en-US',
|
locale: 'en-US',
|
||||||
};
|
};
|
||||||
localize.locale = 'bg-BG';
|
localize.locale = 'bg-BG';
|
||||||
let date = parseDate('29-12-2017');
|
let date = /** @type {Date} */ (parseDate('29-12-2017'));
|
||||||
expect(formatDate(date)).to.equal('29.12.2017 г.');
|
expect(formatDate(date)).to.equal('29.12.2017 г.');
|
||||||
|
|
||||||
date = parseDate('13-1-1940');
|
date = /** @type {Date} */ (parseDate('13-1-1940'));
|
||||||
expect(formatDate(date)).to.equal('13.01.1940 г.');
|
expect(formatDate(date)).to.equal('13.01.1940 г.');
|
||||||
|
|
||||||
date = parseDate('3-11-1970');
|
date = /** @type {Date} */ (parseDate('3-11-1970'));
|
||||||
expect(formatDate(date, options)).to.equal('Tuesday, November 03, 1970');
|
expect(formatDate(date, options)).to.equal('Tuesday, November 03, 1970');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -92,13 +92,13 @@ describe('formatDate', () => {
|
||||||
locale: 'en-US',
|
locale: 'en-US',
|
||||||
};
|
};
|
||||||
localize.locale = 'en-US';
|
localize.locale = 'en-US';
|
||||||
let date = parseDate('12-29-1940');
|
let date = /** @type {Date} */ (parseDate('12-29-1940'));
|
||||||
expect(formatDate(date)).to.equal('12/29/1940');
|
expect(formatDate(date)).to.equal('12/29/1940');
|
||||||
|
|
||||||
date = parseDate('1-13-1940');
|
date = /** @type {Date} */ (parseDate('1-13-1940'));
|
||||||
expect(formatDate(date)).to.equal('01/13/1940');
|
expect(formatDate(date)).to.equal('01/13/1940');
|
||||||
|
|
||||||
date = parseDate('11-3-1970');
|
date = /** @type {Date} */ (parseDate('11-3-1970'));
|
||||||
expect(formatDate(date, options)).to.equal('Tuesday, November 03, 1970');
|
expect(formatDate(date, options)).to.equal('Tuesday, November 03, 1970');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -110,10 +110,10 @@ describe('formatDate', () => {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
locale: 'en-US',
|
locale: 'en-US',
|
||||||
};
|
};
|
||||||
let parsedDate = parseDate('05.11.2017');
|
let parsedDate = /** @type {Date} */ (parseDate('05.11.2017'));
|
||||||
expect(formatDate(parsedDate, options)).to.equal('Sunday, November 05, 2017');
|
expect(formatDate(parsedDate, options)).to.equal('Sunday, November 05, 2017');
|
||||||
|
|
||||||
parsedDate = parseDate('01-01-1940');
|
parsedDate = /** @type {Date} */ (parseDate('01-01-1940'));
|
||||||
options = {
|
options = {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
|
|
@ -130,7 +130,7 @@ describe('formatDate', () => {
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
};
|
};
|
||||||
const parsedDate = parseDate('12.10.2019');
|
const parsedDate = /** @type {Date} */ (parseDate('12.10.2019'));
|
||||||
expect(formatDate(parsedDate, options)).to.equal('Saturday, 12 October');
|
expect(formatDate(parsedDate, options)).to.equal('Saturday, 12 October');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -140,7 +140,7 @@ describe('formatDate', () => {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
};
|
};
|
||||||
const parsedDate = parseDate('12.10.2019');
|
const parsedDate = /** @type {Date} */ (parseDate('12.10.2019'));
|
||||||
expect(formatDate(parsedDate, options)).to.equal('Saturday 12 2019');
|
expect(formatDate(parsedDate, options)).to.equal('Saturday 12 2019');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -150,12 +150,13 @@ describe('formatDate', () => {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
};
|
};
|
||||||
const parsedDate = parseDate('12.10.2019');
|
const parsedDate = /** @type {Date} */ (parseDate('12.10.2019'));
|
||||||
expect(formatDate(parsedDate, options)).to.equal('October 2019 Saturday');
|
expect(formatDate(parsedDate, options)).to.equal('October 2019 Saturday');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns empty string when input is not a Date object', async () => {
|
it('returns empty string when input is not a Date object', async () => {
|
||||||
const date = '1-1-2016';
|
const date = '1-1-2016';
|
||||||
|
// @ts-ignore tests what happens if you use a wrong type
|
||||||
expect(formatDate(date)).to.equal('');
|
expect(formatDate(date)).to.equal('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ import { expect } from '@open-wc/testing';
|
||||||
|
|
||||||
import { getMonthNames } from '../../src/date/getMonthNames.js';
|
import { getMonthNames } from '../../src/date/getMonthNames.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {TemplateStringsArray} strings
|
||||||
|
*/
|
||||||
function s(strings) {
|
function s(strings) {
|
||||||
return strings[0].split(' ');
|
return strings[0].split(' ');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ import { expect } from '@open-wc/testing';
|
||||||
|
|
||||||
import { getWeekdayNames } from '../../src/date/getWeekdayNames.js';
|
import { getWeekdayNames } from '../../src/date/getWeekdayNames.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {TemplateStringsArray} strings
|
||||||
|
*/
|
||||||
function s(strings) {
|
function s(strings) {
|
||||||
return strings[0].split(' ');
|
return strings[0].split(' ');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,15 @@ import { localizeTearDown } from '../../test-helpers.js';
|
||||||
|
|
||||||
import { parseDate } from '../../src/date/parseDate.js';
|
import { parseDate } from '../../src/date/parseDate.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Date | undefined} value
|
||||||
|
* @param {Date} date
|
||||||
|
*/
|
||||||
function equalsDate(value, date) {
|
function equalsDate(value, date) {
|
||||||
return (
|
return (
|
||||||
Object.prototype.toString.call(value) === '[object Date]' && // is Date Object
|
Object.prototype.toString.call(value) === '[object Date]' && // is Date Object
|
||||||
|
value &&
|
||||||
value.getDate() === date.getDate() && // day
|
value.getDate() === date.getDate() && // day
|
||||||
value.getMonth() === date.getMonth() && // month
|
value.getMonth() === date.getMonth() && // month
|
||||||
value.getFullYear() === date.getFullYear() // year
|
value.getFullYear() === date.getFullYear() // year
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ describe('isLocalizeESModule', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores if not an object', () => {
|
it('ignores if not an object', () => {
|
||||||
|
// @ts-ignore passing a non-object is not allowed by ts, but we still want to test the outcome
|
||||||
expect(isLocalizeESModule(undefined)).to.equal(false);
|
expect(isLocalizeESModule(undefined)).to.equal(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { LocalizeManager } from '../src/LocalizeManager.js';
|
||||||
import { localize, setLocalize } from '../src/localize.js';
|
import { localize, setLocalize } from '../src/localize.js';
|
||||||
|
|
||||||
describe('localize', () => {
|
describe('localize', () => {
|
||||||
// this is an importan mindset:
|
// this is an important mindset:
|
||||||
// we don't test the singleton
|
// we don't test the singleton
|
||||||
// we check that it is an instance of the right class
|
// we check that it is an instance of the right class
|
||||||
// we test newly created instances of this class separately
|
// we test newly created instances of this class separately
|
||||||
|
|
@ -26,9 +26,11 @@ describe('localize', () => {
|
||||||
const oldLocalizeTeardown = localize.teardown;
|
const oldLocalizeTeardown = localize.teardown;
|
||||||
localize.teardown = sinon.spy();
|
localize.teardown = sinon.spy();
|
||||||
|
|
||||||
const newLocalize = { teardown: () => {} };
|
const newLocalize = /** @type {LocalizeManager} */ ({ teardown: () => {} });
|
||||||
setLocalize(newLocalize);
|
setLocalize(newLocalize);
|
||||||
expect(localize).to.equal(newLocalize);
|
expect(localize).to.equal(newLocalize);
|
||||||
|
|
||||||
|
// @ts-ignore since we're testing another reference to the same global instance
|
||||||
expect(oldLocalize.teardown.callCount).to.equal(1);
|
expect(oldLocalize.teardown.callCount).to.equal(1);
|
||||||
|
|
||||||
setLocalize(oldLocalize);
|
setLocalize(oldLocalize);
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,22 @@ import { localize } from '../../src/localize.js';
|
||||||
import { formatNumber } from '../../src/number/formatNumber.js';
|
import { formatNumber } from '../../src/number/formatNumber.js';
|
||||||
import { localizeTearDown } from '../../test-helpers.js';
|
import { localizeTearDown } from '../../test-helpers.js';
|
||||||
|
|
||||||
const currencyCode = currency => ({ style: 'currency', currencyDisplay: 'code', currency });
|
const currencyCode = /** @param {string} currency */ currency => ({
|
||||||
const currencySymbol = currency => ({ style: 'currency', currencyDisplay: 'symbol', currency });
|
style: 'currency',
|
||||||
|
currencyDisplay: 'code',
|
||||||
|
currency,
|
||||||
|
});
|
||||||
|
const currencySymbol = /** @param {string} currency */ currency => ({
|
||||||
|
style: 'currency',
|
||||||
|
currencyDisplay: 'symbol',
|
||||||
|
currency,
|
||||||
|
});
|
||||||
|
|
||||||
describe('formatNumber', () => {
|
describe('formatNumber', () => {
|
||||||
afterEach(localizeTearDown);
|
afterEach(localizeTearDown);
|
||||||
|
|
||||||
it('displays the appropriate amount of decimal places based on currencies spec http://www.currency-iso.org/en/home/tables/table-a1.html', () => {
|
it('displays the appropriate amount of decimal places based on currencies spec http://www.currency-iso.org/en/home/tables/table-a1.html', () => {
|
||||||
const clean = str => str.replace(/[a-zA-Z]+/g, '').trim();
|
const clean = /** @param {string} str */ str => str.replace(/[a-zA-Z]+/g, '').trim();
|
||||||
expect(clean(formatNumber(123456.789, currencyCode('JPY')))).to.equal('123,457');
|
expect(clean(formatNumber(123456.789, currencyCode('JPY')))).to.equal('123,457');
|
||||||
expect(clean(formatNumber(123456.789, currencyCode('EUR')))).to.equal('123,456.79');
|
expect(clean(formatNumber(123456.789, currencyCode('EUR')))).to.equal('123,456.79');
|
||||||
expect(clean(formatNumber(123456.789, currencyCode('BHD')))).to.equal('123,456.789');
|
expect(clean(formatNumber(123456.789, currencyCode('BHD')))).to.equal('123,456.789');
|
||||||
|
|
@ -54,11 +62,10 @@ describe('formatNumber', () => {
|
||||||
expect(formatNumber(-12.6, { roundMode: 'floor' })).to.equal('−13');
|
expect(formatNumber(-12.6, { roundMode: 'floor' })).to.equal('−13');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns empty string when NaN', () => {
|
it('returns empty string when passing wrong type', () => {
|
||||||
|
// @ts-ignore tests what happens if you pass wrong type
|
||||||
expect(formatNumber('foo')).to.equal('');
|
expect(formatNumber('foo')).to.equal('');
|
||||||
});
|
// @ts-ignore tests what happens if you pass wrong type
|
||||||
|
|
||||||
it('returns empty string when number is undefined', () => {
|
|
||||||
expect(formatNumber(undefined)).to.equal('');
|
expect(formatNumber(undefined)).to.equal('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -66,12 +73,14 @@ describe('formatNumber', () => {
|
||||||
const savedReturnIfNaN = localize.formatNumberOptions.returnIfNaN;
|
const savedReturnIfNaN = localize.formatNumberOptions.returnIfNaN;
|
||||||
|
|
||||||
localize.formatNumberOptions.returnIfNaN = '-';
|
localize.formatNumberOptions.returnIfNaN = '-';
|
||||||
|
// @ts-ignore
|
||||||
expect(formatNumber('foo')).to.equal('-');
|
expect(formatNumber('foo')).to.equal('-');
|
||||||
|
|
||||||
localize.formatNumberOptions.returnIfNaN = savedReturnIfNaN;
|
localize.formatNumberOptions.returnIfNaN = savedReturnIfNaN;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can set what to returns when NaN via `returnIfNaN: 'foo'`", () => {
|
it("can set what to returns when NaN via `returnIfNaN: 'foo'`", () => {
|
||||||
|
// @ts-ignore
|
||||||
expect(formatNumber('foo', { returnIfNaN: '-' })).to.equal('-');
|
expect(formatNumber('foo', { returnIfNaN: '-' })).to.equal('-');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,26 @@
|
||||||
import { expect } from '@open-wc/testing';
|
import { expect } from '@open-wc/testing';
|
||||||
import { localize } from '../../src/localize.js';
|
import { localize } from '../../src/localize.js';
|
||||||
import { localizeTearDown } from '../../test-helpers.js';
|
import { localizeTearDown } from '../../test-helpers.js';
|
||||||
|
|
||||||
import { formatNumberToParts } from '../../src/number/formatNumberToParts.js';
|
import { formatNumberToParts } from '../../src/number/formatNumberToParts.js';
|
||||||
|
|
||||||
const c = v => ({ type: 'currency', value: v });
|
const c = /** @param {string} v */ v => ({
|
||||||
const d = v => ({ type: 'decimal', value: v });
|
type: 'currency',
|
||||||
const i = v => ({ type: 'integer', value: v });
|
value: v,
|
||||||
const f = v => ({ type: 'fraction', value: v });
|
});
|
||||||
const g = v => ({ type: 'group', value: v });
|
const d = /** @param {string} v */ v => ({ type: 'decimal', value: v });
|
||||||
const l = v => ({ type: 'literal', value: v });
|
const i = /** @param {string} v */ v => ({ type: 'integer', value: v });
|
||||||
|
const f = /** @param {string} v */ v => ({ type: 'fraction', value: v });
|
||||||
|
const g = /** @param {string} v */ v => ({ type: 'group', value: v });
|
||||||
|
const l = /** @param {string} v */ v => ({ type: 'literal', value: v });
|
||||||
const m = { type: 'minusSign', value: '−' };
|
const m = { type: 'minusSign', value: '−' };
|
||||||
|
|
||||||
const stringifyParts = parts => parts.map(part => part.value).join('');
|
const stringifyParts =
|
||||||
|
/**
|
||||||
|
* @typedef {import('../../types/LocalizeMixinTypes').FormatNumberPart} FormatNumberPart
|
||||||
|
* @param {FormatNumberPart[]} parts
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
parts => parts.map(part => part.value).join('');
|
||||||
|
|
||||||
describe('formatNumberToParts', () => {
|
describe('formatNumberToParts', () => {
|
||||||
afterEach(localizeTearDown);
|
afterEach(localizeTearDown);
|
||||||
|
|
@ -32,12 +40,14 @@ describe('formatNumberToParts', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
specs.forEach(([locale, currency, amount, expectedResult]) => {
|
specs.forEach(([locale, currency, amount, expectedResult]) => {
|
||||||
it(`formats ${locale} ${currency} ${amount} as "${stringifyParts(expectedResult)}"`, () => {
|
it(`formats ${locale} ${currency} ${amount} as "${stringifyParts(
|
||||||
|
/** @type {FormatNumberPart[]} */ (expectedResult),
|
||||||
|
)}"`, () => {
|
||||||
expect(
|
expect(
|
||||||
formatNumberToParts(amount, {
|
formatNumberToParts(Number(amount), {
|
||||||
locale,
|
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency,
|
locale: String(locale),
|
||||||
|
currency: String(currency),
|
||||||
}),
|
}),
|
||||||
).to.deep.equal(expectedResult);
|
).to.deep.equal(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
@ -59,13 +69,15 @@ describe('formatNumberToParts', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
specs.forEach(([locale, currency, amount, expectedResult]) => {
|
specs.forEach(([locale, currency, amount, expectedResult]) => {
|
||||||
it(`formats ${locale} ${currency} ${amount} as "${stringifyParts(expectedResult)}"`, () => {
|
it(`formats ${locale} ${currency} ${amount} as "${stringifyParts(
|
||||||
|
/** @type {FormatNumberPart[]} */ (expectedResult),
|
||||||
|
)}"`, () => {
|
||||||
expect(
|
expect(
|
||||||
formatNumberToParts(amount, {
|
formatNumberToParts(Number(amount), {
|
||||||
locale,
|
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currencyDisplay: 'code',
|
currencyDisplay: 'code',
|
||||||
currency,
|
locale: String(locale),
|
||||||
|
currency: String(currency),
|
||||||
}),
|
}),
|
||||||
).to.deep.equal(expectedResult);
|
).to.deep.equal(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
@ -74,6 +86,7 @@ describe('formatNumberToParts', () => {
|
||||||
|
|
||||||
describe("style: 'decimal'", () => {
|
describe("style: 'decimal'", () => {
|
||||||
describe('no minimumFractionDigits', () => {
|
describe('no minimumFractionDigits', () => {
|
||||||
|
/** @type {Array.<Array.<?>>} */
|
||||||
const specs = [
|
const specs = [
|
||||||
['en-GB', 3500, [i('3'), g(','), i('500')]],
|
['en-GB', 3500, [i('3'), g(','), i('500')]],
|
||||||
['en-GB', -3500, [m, i('3'), g(','), i('500')]],
|
['en-GB', -3500, [m, i('3'), g(','), i('500')]],
|
||||||
|
|
@ -88,10 +101,12 @@ describe('formatNumberToParts', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
specs.forEach(([locale, amount, expectedResult]) => {
|
specs.forEach(([locale, amount, expectedResult]) => {
|
||||||
it(`formats ${locale} ${amount} as "${stringifyParts(expectedResult)}"`, () => {
|
it(`formats ${locale} ${amount} as "${stringifyParts(
|
||||||
|
/** @type {FormatNumberPart[]} */ (expectedResult),
|
||||||
|
)}"`, () => {
|
||||||
localize.locale = locale;
|
localize.locale = locale;
|
||||||
expect(
|
expect(
|
||||||
formatNumberToParts(amount, {
|
formatNumberToParts(Number(amount), {
|
||||||
style: 'decimal',
|
style: 'decimal',
|
||||||
}),
|
}),
|
||||||
).to.deep.equal(expectedResult);
|
).to.deep.equal(expectedResult);
|
||||||
|
|
@ -100,6 +115,7 @@ describe('formatNumberToParts', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('minimumFractionDigits: 2', () => {
|
describe('minimumFractionDigits: 2', () => {
|
||||||
|
/** @type {Array.<Array.<?>>} */
|
||||||
const specs = [
|
const specs = [
|
||||||
['en-GB', 3500, [i('3'), g(','), i('500'), d('.'), f('00')]],
|
['en-GB', 3500, [i('3'), g(','), i('500'), d('.'), f('00')]],
|
||||||
['en-GB', -3500, [m, i('3'), g(','), i('500'), d('.'), f('00')]],
|
['en-GB', -3500, [m, i('3'), g(','), i('500'), d('.'), f('00')]],
|
||||||
|
|
@ -114,10 +130,12 @@ describe('formatNumberToParts', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
specs.forEach(([locale, amount, expectedResult]) => {
|
specs.forEach(([locale, amount, expectedResult]) => {
|
||||||
it(`formats ${locale} ${amount} as "${stringifyParts(expectedResult)}"`, () => {
|
it(`formats ${locale} ${amount} as "${stringifyParts(
|
||||||
|
/** @type {FormatNumberPart[]} */ (expectedResult),
|
||||||
|
)}"`, () => {
|
||||||
localize.locale = locale;
|
localize.locale = locale;
|
||||||
expect(
|
expect(
|
||||||
formatNumberToParts(amount, {
|
formatNumberToParts(Number(amount), {
|
||||||
style: 'decimal',
|
style: 'decimal',
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
}),
|
}),
|
||||||
|
|
@ -142,12 +160,14 @@ describe('formatNumberToParts', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
specs.forEach(([locale, amount, expectedResult]) => {
|
specs.forEach(([locale, amount, expectedResult]) => {
|
||||||
it(`formats ${locale} ${amount} as "${stringifyParts(expectedResult)}"`, () => {
|
it(`formats ${locale} ${amount} as "${stringifyParts(
|
||||||
|
/** @type {FormatNumberPart[]} */ (expectedResult),
|
||||||
|
)}"`, () => {
|
||||||
expect(
|
expect(
|
||||||
formatNumberToParts(amount / 100, {
|
formatNumberToParts(Number(amount) / 100, {
|
||||||
locale,
|
|
||||||
style: 'percent',
|
style: 'percent',
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
|
locale: String(locale),
|
||||||
}),
|
}),
|
||||||
).to.deep.equal(expectedResult);
|
).to.deep.equal(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
52
packages/localize/types/LocalizeMixinTypes.d.ts
vendored
Normal file
52
packages/localize/types/LocalizeMixinTypes.d.ts
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||||
|
|
||||||
|
export interface FormatNumberPart {
|
||||||
|
type: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StringToFunctionMap {
|
||||||
|
[key: string]: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NamespaceObject = StringToFunctionMap | string;
|
||||||
|
|
||||||
|
interface msgVariables {
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface msgOptions {
|
||||||
|
locale?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class LocalizeMixinHost {
|
||||||
|
// FIXME: return value type check doesn't seem to be `working!
|
||||||
|
static get localizeNamespaces(): StringToFunctionMap[];
|
||||||
|
|
||||||
|
static get waitForLocalizeNamespaces(): boolean;
|
||||||
|
|
||||||
|
public localizeNamespacesLoaded(): Promise<Object>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook into LitElement to only render once all translations are loaded
|
||||||
|
*/
|
||||||
|
public performUpdate(): Promise<void>;
|
||||||
|
|
||||||
|
public onLocaleReady(): void;
|
||||||
|
public onLocaleChanged(): void;
|
||||||
|
public onLocaleUpdated(): void;
|
||||||
|
public connectedCallback(): void;
|
||||||
|
public disconnectedCallback(): void;
|
||||||
|
public msgLit(keys: string | string[], variables?: msgVariables, options?: msgOptions): void;
|
||||||
|
|
||||||
|
private __getUniqueNamespaces(): void;
|
||||||
|
private __localizeAddLocaleChangedListener(): void;
|
||||||
|
private __localizeRemoveLocaleChangedListener(): void;
|
||||||
|
private __localizeOnLocaleChanged(event: CustomEvent): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare function LocalizeMixinImplementation<T extends Constructor<HTMLElement>>(
|
||||||
|
superclass: T,
|
||||||
|
): T & Constructor<LocalizeMixinHost> & typeof LocalizeMixinHost;
|
||||||
|
|
||||||
|
export type LocalizeMixin = typeof LocalizeMixinImplementation;
|
||||||
8
packages/localize/types/index.d.ts
vendored
Normal file
8
packages/localize/types/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
declare module '@bundled-es-modules/message-format/MessageFormat.js' {
|
||||||
|
let main: any;
|
||||||
|
export = main;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@bundled-es-modules/fetch-mock' {
|
||||||
|
export const fetchMock: any;
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,11 @@ export class SingletonManagerClass {
|
||||||
this._map = new Map();
|
this._map = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
* @param {any} value
|
||||||
|
* @throws {Error} Will throw if the key is already defined
|
||||||
|
*/
|
||||||
set(key, value) {
|
set(key, value) {
|
||||||
if (this.has(key)) {
|
if (this.has(key)) {
|
||||||
throw new Error(`The key "${key}" is already defined and can not be overridden.`);
|
throw new Error(`The key "${key}" is already defined and can not be overridden.`);
|
||||||
|
|
@ -10,10 +15,17 @@ export class SingletonManagerClass {
|
||||||
this._map.set(key, value);
|
this._map.set(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
get(key) {
|
get(key) {
|
||||||
return this._map.get(key);
|
return this._map.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
*/
|
||||||
has(key) {
|
has(key) {
|
||||||
return this._map.has(key);
|
return this._map.has(key);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,24 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"alwaysStrict": true,
|
"alwaysStrict": true,
|
||||||
"types": ["node", "mocha"],
|
"types": ["node", "mocha", "sinon"],
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["packages/core/**/*.js", "packages/tabs/**/*.js"],
|
"include": [
|
||||||
|
"packages/core/**/*.js",
|
||||||
|
"packages/tabs/**/*.js",
|
||||||
|
"packages/singleton-manager/**/*.js",
|
||||||
|
"packages/localize/**/*.js",
|
||||||
|
"packages/localize/**/*.ts"
|
||||||
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"**/node_modules/*",
|
"**/node_modules/*",
|
||||||
"**/coverage/*",
|
"**/coverage/*",
|
||||||
"**/dist/**/*",
|
"**/dist/**/*",
|
||||||
"packages/**/test-helpers"
|
"packages/**/test-helpers",
|
||||||
|
// ignore test/demos for singleton manager until overlays are typed as it's used in there
|
||||||
|
"packages/singleton-manager/demo/",
|
||||||
|
"packages/singleton-manager/test/"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,13 @@ module.exports = {
|
||||||
sessionStartTimeout: 60000,
|
sessionStartTimeout: 60000,
|
||||||
concurrency: 1,
|
concurrency: 1,
|
||||||
coverageConfig: {
|
coverageConfig: {
|
||||||
|
report: true,
|
||||||
|
reportDir: 'coverage',
|
||||||
threshold: {
|
threshold: {
|
||||||
statements: 80,
|
statements: 90,
|
||||||
branches: 70,
|
branches: 70,
|
||||||
functions: 70,
|
functions: 80,
|
||||||
lines: 80,
|
lines: 90,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
browsers: [
|
browsers: [
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,13 @@ module.exports = {
|
||||||
sessionStartTimeout: 30000,
|
sessionStartTimeout: 30000,
|
||||||
concurrency: 5,
|
concurrency: 5,
|
||||||
coverageConfig: {
|
coverageConfig: {
|
||||||
|
report: true,
|
||||||
|
reportDir: 'coverage',
|
||||||
threshold: {
|
threshold: {
|
||||||
statements: 80,
|
statements: 90,
|
||||||
branches: 70,
|
branches: 65,
|
||||||
functions: 70,
|
functions: 80,
|
||||||
lines: 80,
|
lines: 90,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue