diff --git a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localize.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localize.controller.test.ts index a105973514..614905af46 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localize.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localize.controller.test.ts @@ -1,256 +1,240 @@ import { aTimeout, elementUpdated, expect, fixture, html } from '@open-wc/testing'; -import { umbTranslationRegistry } from '@umbraco-cms/backoffice/localization'; +import { DefaultTranslationSet, TranslationSet, registerTranslation, translations } from './manager.js'; import { UmbLocalizeController } from './localize.controller.js'; -import { customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { ManifestTranslations, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { LitElement, customElement, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @customElement('umb-localize-controller-host') -class UmbLocalizeControllerHostElement extends UmbLitElement { - @property() - lang = 'en-us'; +class UmbLocalizeControllerHostElement extends UmbElementMixin(LitElement) { + @property() lang = 'en-us'; +} + +interface TestTranslation extends TranslationSet { + close: string; + logout: string; + withInlineToken: string; + withInlineTokenLegacy: string; + notOnRegional: string; + numUsersSelected: (count: number) => string; } //#region Translations -const english: ManifestTranslations = { - type: 'translations', - alias: 'test.en', - name: 'Test English', - meta: { - culture: 'en-us', - direction: 'ltr', - translations: { - general: { - close: 'Close', - logout: 'Log out', - withInlineToken: '{0} {1}', - withInlineTokenLegacy: '%0% %1%', - numUsersSelected: (count: number) => { - if (count === 0) return 'No users selected'; - if (count === 1) return 'One user selected'; - return `${count} users selected`; - }, - }, - }, +const english: TestTranslation = { + $code: 'en-us', + $dir: 'ltr', + close: 'Close', + logout: 'Log out', + withInlineToken: '{0} {1}', + withInlineTokenLegacy: '%0% %1%', + notOnRegional: 'Not on regional', + numUsersSelected: (count: number) => { + if (count === 0) return 'No users selected'; + if (count === 1) return 'One user selected'; + return `${count} users selected`; }, }; -const englishOverride: ManifestTranslations = { - type: 'translations', - alias: 'test.en.override', - name: 'Test English', - meta: { - culture: 'en-us', - translations: { - general: { - close: 'Close 2', - }, - }, - }, +const englishOverride: DefaultTranslationSet = { + $code: 'en-us', + $dir: 'ltr', + close: 'Close 2', }; -const danish: ManifestTranslations = { - type: 'translations', - alias: 'test.da', - name: 'Test Danish', - meta: { - culture: 'da', - translations: { - general: { - close: 'Luk', - notOnRegional: 'Not on regional', - }, - }, - }, +const danish: DefaultTranslationSet = { + $code: 'da', + $dir: 'ltr', + close: 'Luk', + notOnRegional: 'Not on regional', }; -const danishRegional: ManifestTranslations = { - type: 'translations', - alias: 'test.da-DK', - name: 'Test Danish (Denmark)', - meta: { - culture: 'da-DK', - translations: { - general: { - close: 'Luk', - }, - }, - }, +const danishRegional: DefaultTranslationSet = { + $code: 'da-dk', + $dir: 'ltr', + close: 'Luk', }; //#endregion describe('UmbLocalizeController', () => { - umbExtensionsRegistry.register(english); - umbExtensionsRegistry.register(danish); - umbExtensionsRegistry.register(danishRegional); - - let element: UmbLocalizeControllerHostElement; + let controller: UmbLocalizeController; beforeEach(async () => { - umbTranslationRegistry.loadLanguage(english.meta.culture); - element = await fixture(html``); + registerTranslation(english, danish, danishRegional); + document.documentElement.lang = english.$code; + document.documentElement.dir = english.$dir; + await aTimeout(0); + const host = { + getHostElement: () => document.createElement('div'), + addController: () => {}, + removeController: () => {}, + hasController: () => false, + getControllers: () => [], + removeControllerByAlias: () => {}, + } satisfies UmbControllerHost; + controller = new UmbLocalizeController(host); }); - it('should have a localize controller', () => { - expect(element.localize).to.be.instanceOf(UmbLocalizeController); + afterEach(() => { + controller.destroy(); + translations.clear(); }); it('should have a default language', () => { - expect(element.localize.lang()).to.equal(english.meta.culture); + expect(controller.lang()).to.equal(english.$code); + }); + + it('should update the language when the language changes', async () => { + document.documentElement.lang = danishRegional.$code; + await aTimeout(0); + expect(controller.lang()).to.equal(danishRegional.$code); }); it('should have a default dir', () => { - expect(element.localize.dir()).to.equal(english.meta.direction); + expect(controller.dir()).to.equal(english.$dir); + }); + + it('should update the dir when the dir changes', async () => { + document.documentElement.dir = 'rtl'; + await aTimeout(0); + expect(controller.dir()).to.equal('rtl'); }); describe('term', () => { it('should return a term', async () => { - expect(element.localize.term('general_close')).to.equal('Close'); + expect(controller.term('close')).to.equal('Close'); }); it('should update the term when the language changes', async () => { - // Load Danish - umbTranslationRegistry.loadLanguage(danishRegional.meta.culture); - await aTimeout(0); - expect(document.documentElement.lang).to.equal(danishRegional.meta.culture); - - // Force an element update as well - element.lang = danishRegional.meta.culture; - await elementUpdated(element); - expect(element.localize.term('general_close')).to.equal('Luk'); - }); - - it('should update the term when the dir changes', async () => { - expect(element.localize.term('general_close')).to.equal('Close'); - element.dir = 'rtl'; - await elementUpdated(element); - expect(element.localize.term('general_close')).to.equal('Close'); - }); - - it('should provide a secondary term when the term is not found', async () => { - // Load Danish - umbTranslationRegistry.loadLanguage(danishRegional.meta.culture); + // Change language + document.documentElement.lang = danishRegional.$code; await aTimeout(0); - element.lang = danishRegional.meta.culture; - await elementUpdated(element); - expect(element.localize.term('general_notOnRegional')).to.equal('Not on regional'); + expect(controller.term('close')).to.equal('Luk'); }); - it('should provide a fallback term when the term is not found', async () => { + it('should provide a secondary term when the term is not found on the regional language', async () => { // Load Danish - umbTranslationRegistry.loadLanguage(danishRegional.meta.culture); + document.documentElement.lang = danishRegional.$code; await aTimeout(0); - element.lang = danishRegional.meta.culture; - await elementUpdated(element); - expect(element.localize.term('general_close')).to.equal('Luk'); - expect(element.localize.term('general_logout')).to.equal('Log out'); + expect(controller.term('notOnRegional')).to.equal('Not on regional'); }); - it('should override a term if new extension is registered', async () => { - umbExtensionsRegistry.register(englishOverride); + it('should provide a fallback term from the fallback language when the term is not found on primary or secondary', async () => { + // Load Danish + document.documentElement.lang = danishRegional.$code; + await aTimeout(0); + + expect(controller.term('close')).to.equal('Luk'); // Primary + expect(controller.term('logout')).to.equal('Log out'); // Fallback + }); + + it('should override a term if new translation is registered', () => { // Let the registry load the new extension - await aTimeout(0); - await elementUpdated(element); - expect(element.localize.term('general_close')).to.equal('Close 2'); + registerTranslation(englishOverride); + + expect(controller.term('close')).to.equal('Close 2'); }); it('should return a term with a custom format', async () => { - expect(element.localize.term('general_numUsersSelected', 0)).to.equal('No users selected'); - expect(element.localize.term('general_numUsersSelected', 1)).to.equal('One user selected'); - expect(element.localize.term('general_numUsersSelected', 2)).to.equal('2 users selected'); + expect(controller.term('numUsersSelected', 0)).to.equal('No users selected'); + expect(controller.term('numUsersSelected', 1)).to.equal('One user selected'); + expect(controller.term('numUsersSelected', 2)).to.equal('2 users selected'); }); it('should return a term with a custom format with inline tokens', async () => { - expect(element.localize.term('general_withInlineToken', 'Hello', 'World')).to.equal('Hello World'); - expect(element.localize.term('general_withInlineTokenLegacy', 'Hello', 'World')).to.equal('Hello World'); + expect(controller.term('withInlineToken', 'Hello', 'World')).to.equal('Hello World'); + expect(controller.term('withInlineTokenLegacy', 'Hello', 'World')).to.equal('Hello World'); }); it('should return a term with no tokens even though they are provided', async () => { - expect(element.localize.term('general_logout', 'Hello', 'World')).to.equal('Log out'); + expect(controller.term('logout', 'Hello', 'World')).to.equal('Log out'); }); }); describe('date', () => { it('should return a date', async () => { - expect(element.localize.date(new Date(2020, 0, 1))).to.equal('1/1/2020'); + expect(controller.date(new Date(2020, 0, 1))).to.equal('1/1/2020'); }); it('should accept a string input', async () => { - expect(element.localize.date('2020-01-01')).to.equal('1/1/2020'); + expect(controller.date('2020-01-01')).to.equal('1/1/2020'); }); it('should update the date when the language changes', async () => { - expect(element.localize.date(new Date(2020, 11, 31))).to.equal('12/31/2020'); + expect(controller.date(new Date(2020, 11, 31))).to.equal('12/31/2020'); // Switch browser to Danish - element.lang = danish.meta.culture; + document.documentElement.lang = danishRegional.$code; + await aTimeout(0); - await elementUpdated(element); - expect(element.localize.date(new Date(2020, 11, 31))).to.equal('31.12.2020'); + expect(controller.date(new Date(2020, 11, 31))).to.equal('31.12.2020'); }); - it('should update the date when the dir changes', async () => { - expect(element.localize.date(new Date(2020, 11, 31))).to.equal('12/31/2020'); - element.dir = 'rtl'; - await elementUpdated(element); - expect(element.localize.date(new Date(2020, 11, 31))).to.equal('12/31/2020'); - }); - - it('should return a date with a custom format', async () => { - expect( - element.localize.date(new Date(2020, 11, 31), { month: 'long', day: '2-digit', year: 'numeric' }) - ).to.equal('December 31, 2020'); + it('should return a date with a custom format', () => { + expect(controller.date(new Date(2020, 11, 31), { month: 'long', day: '2-digit', year: 'numeric' })).to.equal( + 'December 31, 2020' + ); }); }); describe('number', () => { - it('should return a number', async () => { - expect(element.localize.number(123456.789)).to.equal('123,456.789'); + it('should return a number', () => { + expect(controller.number(123456.789)).to.equal('123,456.789'); }); - it('should accept a string input', async () => { - expect(element.localize.number('123456.789')).to.equal('123,456.789'); + it('should accept a string input', () => { + expect(controller.number('123456.789')).to.equal('123,456.789'); }); it('should update the number when the language changes', async () => { - expect(element.localize.number(123456.789)).to.equal('123,456.789'); - // Switch browser to Danish - element.lang = danish.meta.culture; + document.documentElement.lang = danishRegional.$code; + await aTimeout(0); - await elementUpdated(element); - expect(element.localize.number(123456.789)).to.equal('123.456,789'); + expect(controller.number(123456.789)).to.equal('123.456,789'); }); - it('should update the number when the dir changes', async () => { - expect(element.localize.number(123456.789)).to.equal('123,456.789'); - element.dir = 'rtl'; - await elementUpdated(element); - expect(element.localize.number(123456.789)).to.equal('123,456.789'); - }); - - it('should return a number with a custom format', async () => { - expect(element.localize.number(123456.789, { minimumFractionDigits: 2, maximumFractionDigits: 2 })).to.equal( + it('should return a number with a custom format', () => { + expect(controller.number(123456.789, { minimumFractionDigits: 2, maximumFractionDigits: 2 })).to.equal( '123,456.79' ); }); }); describe('relative time', () => { - it('should return a relative time', async () => { - expect(element.localize.relativeTime(2, 'days')).to.equal('in 2 days'); + it('should return a relative time', () => { + expect(controller.relativeTime(2, 'days')).to.equal('in 2 days'); }); it('should update the relative time when the language changes', async () => { - expect(element.localize.relativeTime(2, 'days')).to.equal('in 2 days'); + // Switch browser to Danish + document.documentElement.lang = danishRegional.$code; + await aTimeout(0); + + expect(controller.relativeTime(2, 'days')).to.equal('om 2 dage'); + }); + }); + + describe('host element', () => { + let element: UmbLocalizeControllerHostElement; + + beforeEach(async () => { + element = await fixture(html``); + }); + + it('should have a localize controller', () => { + expect(element.localize).to.be.instanceOf(UmbLocalizeController); + }); + + it('should update the term when the language changes', async () => { + expect(element.localize.term('close')).to.equal('Close'); // Switch browser to Danish - element.lang = danish.meta.culture; + element.lang = danishRegional.$code; await elementUpdated(element); - expect(element.localize.relativeTime(2, 'days')).to.equal('om 2 dage'); + expect(element.localize.term('close')).to.equal('Luk'); }); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localize.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localize.controller.ts index c99ddc1d58..ff20f3d29a 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localize.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localize.controller.ts @@ -68,8 +68,6 @@ export class UmbLocalizeController = T extends (...args: infer U) => string ? U : []; diff --git a/src/Umbraco.Web.UI.Client/src/libs/localization-api/types/translation.ts b/src/Umbraco.Web.UI.Client/src/libs/localization-api/types/translation.ts index f3216f17eb..351eb4d4b4 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/localization-api/types/translation.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/localization-api/types/translation.ts @@ -1,3 +1,4 @@ -export type UmbTranslationEntry = string | ((...args: never[]) => string); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type UmbTranslationEntry = string | ((...args: any[]) => string); export type UmbTranslationsDictionary = Record>; export type UmbTranslationsFlatDictionary = Record;