diff --git a/.changeset/unlucky-cameras-joke.md b/.changeset/unlucky-cameras-joke.md new file mode 100644 index 000000000..6516d418b --- /dev/null +++ b/.changeset/unlucky-cameras-joke.md @@ -0,0 +1,5 @@ +--- +'@lion/localize': minor +--- + +BREAKING: Fires localeChanged event (and as a result, invokes onLocaleChanged / onLocaleUpdated) after localize loading has completed. This means if the user switches the locale to a locale which has not loaded yet, it will load it first before sending the event. This will allow users to immediately call localize.msg and get the right output, without having to await localize.loadingComplete themselves. This is slightly breaking with regards to timing and might break tests in extensions. In that case, you probably need a `await localize.loadingComplete` statement in front of the failing assertion. diff --git a/packages/calendar/test/lion-calendar.test.js b/packages/calendar/test/lion-calendar.test.js index 0bb16a362..8326c118e 100644 --- a/packages/calendar/test/lion-calendar.test.js +++ b/packages/calendar/test/lion-calendar.test.js @@ -1393,6 +1393,7 @@ describe('', () => { ]); localize.locale = 'nl-NL'; + await localize.loadingComplete; await el.updateComplete; expect(elObj.nextMonthButtonEl?.getAttribute('aria-label')).to.equal( 'Volgende maand, december 2019', diff --git a/packages/form-core/test-suites/ValidateMixinFeedbackPart.suite.js b/packages/form-core/test-suites/ValidateMixinFeedbackPart.suite.js index e4f169e4e..4108a99e2 100644 --- a/packages/form-core/test-suites/ValidateMixinFeedbackPart.suite.js +++ b/packages/form-core/test-suites/ValidateMixinFeedbackPart.suite.js @@ -332,6 +332,7 @@ export function runValidateMixinFeedbackPart() { expect(_feedbackNode.feedbackData?.[0].message).to.equal('Message for MinLength'); localize.locale = 'de-DE'; + await localize.loadingComplete; await el.feedbackComplete; expect(_feedbackNode.feedbackData?.[0].message).to.equal('Nachricht für MinLength'); }); diff --git a/packages/form-integrations/test/form-validation-integrations.test.js b/packages/form-integrations/test/form-validation-integrations.test.js index ace537830..2002ff562 100644 --- a/packages/form-integrations/test/form-validation-integrations.test.js +++ b/packages/form-integrations/test/form-validation-integrations.test.js @@ -164,12 +164,14 @@ describe('Form Validation Integrations', () => { expect(_feedbackNode.feedbackData?.[0].message).to.equal('Please enter a(n) Text.'); localize.locale = 'nl-NL'; + await localize.loadingComplete; await el.updateComplete; await el.feedbackComplete; expect(el.label).to.equal('Tekst'); expect(_feedbackNode.feedbackData?.[0].message).to.equal('Vul een Tekst in.'); localize.locale = 'en-GB'; + await localize.loadingComplete; await el.updateComplete; await el.feedbackComplete; expect(el.label).to.equal('Text'); diff --git a/packages/localize/src/LocalizeManager.js b/packages/localize/src/LocalizeManager.js index a4dbdbc4f..1b793181f 100644 --- a/packages/localize/src/LocalizeManager.js +++ b/packages/localize/src/LocalizeManager.js @@ -41,7 +41,7 @@ export class LocalizeManager { this.__namespaceLoadersCache = {}; /** - * @type {Object.>>} + * @type {Object.>>} * @private */ this.__namespaceLoaderPromisesCache = {}; @@ -419,7 +419,7 @@ export class LocalizeManager { /** * @param {string} locale * @param {string} namespace - * @param {Promise.} promise + * @param {Promise.} promise * @protected */ _cacheNamespaceLoaderPromise(locale, namespace, promise) { @@ -495,32 +495,30 @@ export class LocalizeManager { } if (this._autoLoadOnLocaleChange) { this._loadAllMissing(newLocale, oldLocale); + this.loadingComplete.then(() => { + this.dispatchEvent(new CustomEvent('localeChanged', { detail: { newLocale, oldLocale } })); + }); + } else { + this.dispatchEvent(new CustomEvent('localeChanged', { detail: { newLocale, oldLocale } })); } - this.dispatchEvent(new CustomEvent('localeChanged', { detail: { newLocale, oldLocale } })); } /** * @param {string} newLocale * @param {string} oldLocale - * @returns {Promise.} * @protected */ _loadAllMissing(newLocale, oldLocale) { const oldLocaleNamespaces = this.__storage[oldLocale] || {}; const newLocaleNamespaces = this.__storage[newLocale] || {}; - /** @type {Promise[]} */ - const promises = []; Object.keys(oldLocaleNamespaces).forEach(namespace => { const newNamespaceData = newLocaleNamespaces[namespace]; if (!newNamespaceData) { - promises.push( - this.loadNamespace(namespace, { - locale: newLocale, - }), - ); + this.loadNamespace(namespace, { + locale: newLocale, + }); } }); - return Promise.all(promises); } /** diff --git a/packages/localize/test-helpers/fake-imports.js b/packages/localize/test-helpers/fake-imports.js index 18deac662..34eb339c8 100644 --- a/packages/localize/test-helpers/fake-imports.js +++ b/packages/localize/test-helpers/fake-imports.js @@ -21,7 +21,7 @@ export function setupEmptyFakeImportsFor(namespaces, locales) { namespaces.forEach(namespace => { locales.forEach(locale => { setupFakeImport(`./${namespace}/${locale}.js`, { - default: {}, + default: { foo: `bar-${locale}` }, }); }); }); diff --git a/packages/localize/test/LocalizeMixin.test.js b/packages/localize/test/LocalizeMixin.test.js index d61cbfb81..8468d4346 100644 --- a/packages/localize/test/LocalizeMixin.test.js +++ b/packages/localize/test/LocalizeMixin.test.js @@ -29,7 +29,6 @@ describe('LocalizeMixin', () => { }; const tagString = defineCE( - // @ts-ignore class MyElement extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; @@ -97,7 +96,6 @@ describe('LocalizeMixin', () => { 'my-element': locale => fakeImport(`./my-element/${locale}.js`), }; - // @ts-ignore class MyElement extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; @@ -128,7 +126,6 @@ describe('LocalizeMixin', () => { 'my-element': locale => fakeImport(`./my-element/${locale}.js`), }; - // @ts-ignore class MyOtherElement extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; @@ -155,17 +152,55 @@ describe('LocalizeMixin', () => { await localize.loadingComplete; expect(onLocaleChangedSpy.callCount).to.equal(1); // FIXME: Expected 0 arguments, but got 2. ts(2554) --> not sure why this sinon type is not working - // @ts-ignore + // @ts-expect-error expect(onLocaleChangedSpy.calledWithExactly('ru-RU', 'nl-NL')).to.be.true; }); + it('dispatches "localeChanged" after loader promises have resolved, allowing to call .msg() immediately', async () => { + const myElementNs = { + /** @param {string} locale */ + 'my-element': locale => fakeImport(`./my-element/${locale}.js`), + }; + + class MyOtherElement extends LocalizeMixin(LitElement) { + static get localizeNamespaces() { + return [myElementNs, ...super.localizeNamespaces]; + } + + onLocaleChanged() { + // Can call localize.msg immediately, without having to await localize.loadingComplete + // This is because localeChanged event is fired only after awaiting loading + // unless the user disables _autoLoadOnLocaleChange property + this.foo = localize.msg('my-element:foo'); + } + } + + const tagString = defineCE(MyOtherElement); + + setupEmptyFakeImportsFor(['my-element'], ['en-GB', 'nl-NL', 'ru-RU']); + + const el = /** @type {MyOtherElement} */ (document.createElement(tagString)); + const wrapper = await fixture('
'); + wrapper.appendChild(el); + + await aTimeout(500); + + localize.locale = 'nl-NL'; + await localize.loadingComplete; + expect(el.foo).to.equal('bar-nl-NL'); + + localize.locale = 'ru-RU'; + await localize.loadingComplete; + + expect(el.foo).to.equal('bar-ru-RU'); + }); + it('calls "onLocaleUpdated()" after both "onLocaleReady()" and "onLocaleChanged()"', async () => { const myElementNs = { /** @param {string} locale */ 'my-element': locale => fakeImport(`./my-element/${locale}.js`), }; - // @ts-ignore class MyElement extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; @@ -187,6 +222,7 @@ describe('LocalizeMixin', () => { expect(onLocaleUpdatedSpy.callCount).to.equal(1); localize.locale = 'nl-NL'; + await localize.loadingComplete; expect(onLocaleUpdatedSpy.callCount).to.equal(2); }); @@ -206,7 +242,6 @@ describe('LocalizeMixin', () => { }, }); - // @ts-ignore class MyElement extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; @@ -214,7 +249,6 @@ describe('LocalizeMixin', () => { async onLocaleUpdated() { super.onLocaleUpdated(); - await this.localizeNamespacesLoaded; this.label = localize.msg('my-element:label'); } } @@ -223,12 +257,11 @@ describe('LocalizeMixin', () => { const el = /** @type {MyElement} */ (document.createElement(tagString)); el.connectedCallback(); - await el.localizeNamespacesLoaded; await nextFrame(); // needed as both are added to the micro task que expect(el.label).to.equal('one'); localize.locale = 'nl-NL'; - await el.localizeNamespacesLoaded; + await localize.loadingComplete; expect(el.label).to.equal('two'); }); @@ -238,7 +271,6 @@ describe('LocalizeMixin', () => { 'my-element': locale => fakeImport(`./my-element/${locale}.js`), }; - // @ts-ignore class MyElement extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; @@ -255,6 +287,7 @@ describe('LocalizeMixin', () => { await el.localizeNamespacesLoaded; localize.locale = 'nl-NL'; + await localize.loadingComplete; expect(updateSpy.callCount).to.equal(1); }); @@ -269,7 +302,6 @@ describe('LocalizeMixin', () => { }, }); - // @ts-ignore class MyElement extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; @@ -311,7 +343,6 @@ describe('LocalizeMixin', () => { }, }); - // @ts-ignore class MyElement extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; @@ -338,7 +369,6 @@ describe('LocalizeMixin', () => { }, }); - // @ts-ignore class MyLocalizedClass extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; @@ -379,7 +409,6 @@ describe('LocalizeMixin', () => { }, }); - // @ts-ignore class MyLocalizedClass extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; @@ -400,6 +429,7 @@ describe('LocalizeMixin', () => { expect(p.innerText).to.equal('Hi!'); localize.locale = 'en-US'; expect(p.innerText).to.equal('Hi!'); + await localize.loadingComplete; await el.updateComplete; expect(p.innerText).to.equal('Howdy!'); } @@ -416,7 +446,6 @@ describe('LocalizeMixin', () => { }, }); - // @ts-ignore class MyLocalizedClass extends LocalizeMixin(LitElement) { static get waitForLocalizeNamespaces() { return false;