diff --git a/packages/dgt-components/demo/demo.component.ts b/packages/dgt-components/demo/demo.component.ts index 2d9f00eb..548a18b0 100644 --- a/packages/dgt-components/demo/demo.component.ts +++ b/packages/dgt-components/demo/demo.component.ts @@ -9,6 +9,7 @@ import { FormValidator } from '../lib/components/forms/form-validator'; import { FormElementComponent } from '../lib/components/forms/form-element.component'; import { define } from '../lib/util/define'; import { hydrate } from '../lib/util/hydrate'; +import { getTranslator, getTranslatorFor, MemoryTranslatorFactory, setTranslator, setTranslatorFactory, TRANSLATIONS_LOADED, Translator } from '@digita-ai/dgt-utils'; const emailValidator: FormValidator<{ email: string }> = async (context, event) => { @@ -49,6 +50,8 @@ export class DemoComponent extends RxLitElement { // eslint-disable-next-line max-len private formActor: Interpreter, FormStateSchema<{ email: string }>, FormEvent, FormState<{ email: string }>>; + private translator: Translator; + constructor() { super(); @@ -65,6 +68,21 @@ export class DemoComponent extends RxLitElement { define('checkbox-component', CheckboxComponent); define('form-element', hydrate(FormElementComponent)(this.formActor)); + // create single translator + setTranslatorFactory(new MemoryTranslatorFactory); + + } + + async connectedCallback(): Promise { + + this.translator = await getTranslatorFor(navigator.language); + // set global translator + setTranslator(this.translator); + // to use in other components: + this.translator = getTranslator(); + + super.connectedCallback(); + } private onCheckboxClicked = (e: Event) => { @@ -84,6 +102,8 @@ export class DemoComponent extends RxLitElement { render() { return html` +

translator

+

${this.translator.translate('example-translation')}

checkbox component

I agree diff --git a/packages/dgt-components/demo/public/en-US.json b/packages/dgt-components/demo/public/en-US.json new file mode 100644 index 00000000..8eb38baf --- /dev/null +++ b/packages/dgt-components/demo/public/en-US.json @@ -0,0 +1,3 @@ +{ + "example-translation": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut lacinia arcu vel aliquam interdum. Aliquam bibendum vitae magna id molestie." +} diff --git a/packages/dgt-components/lib/components/alerts/alert.component.spec.ts b/packages/dgt-components/lib/components/alerts/alert.component.spec.ts index 742373e7..8184871d 100644 --- a/packages/dgt-components/lib/components/alerts/alert.component.spec.ts +++ b/packages/dgt-components/lib/components/alerts/alert.component.spec.ts @@ -1,5 +1,4 @@ import { ArgumentError } from '@digita-ai/dgt-utils'; -import { MockTranslator } from '../../services/i18n/mock-translator'; import { Alert } from './alert'; import { AlertComponent } from './alert.component'; @@ -11,6 +10,10 @@ describe('AlertComponent', () => { component = window.document.createElement('alert-component') as AlertComponent; + (component.translator as unknown) = { + translate: (key: string) => key, + }; + }); afterEach(() => { @@ -48,8 +51,6 @@ describe('AlertComponent', () => { message: 'foo', }; - component.translator = new MockTranslator('en-GB'); - window.document.body.appendChild(component); await component.updateComplete; @@ -66,8 +67,6 @@ describe('AlertComponent', () => { message: 'foo', }; - component.translator = new MockTranslator('en-GB'); - window.document.body.appendChild(component); await component.updateComplete; diff --git a/packages/dgt-components/lib/components/authentication/authenticate.component.ts b/packages/dgt-components/lib/components/authentication/authenticate.component.ts index a93f9c67..9fc644d6 100644 --- a/packages/dgt-components/lib/components/authentication/authenticate.component.ts +++ b/packages/dgt-components/lib/components/authentication/authenticate.component.ts @@ -5,12 +5,12 @@ import { from } from 'rxjs'; import { map } from 'rxjs/operators'; import { Theme, DigitaBlue } from '@digita-ai/dgt-theme'; import { SolidService } from '@digita-ai/inrupt-solid-service'; +import { Translator } from '@digita-ai/dgt-utils'; import { Issuer } from '../../models/issuer.model'; import { ProviderListComponent } from '../provider/provider-list.component'; import { SeparatorComponent } from '../separator/separator.component'; import { LoadingComponent } from '../loading/loading.component'; import { define } from '../../util/define'; -import { Translator } from '../../services/i18n/translator'; import { WebIdComponent } from './webid.component'; import { AuthenticateContext, AuthenticateEvent, AuthenticateEvents, authenticateMachine, AuthenticateState, AuthenticateStates, AuthenticateStateSchema, ClickedLoginEvent, SelectedIssuerEvent, WebIdEnteredEvent, WebIdValidator } from './authenticate.machine'; diff --git a/packages/dgt-components/lib/components/authentication/webid.component.ts b/packages/dgt-components/lib/components/authentication/webid.component.ts index 24e6dde9..746fa386 100644 --- a/packages/dgt-components/lib/components/authentication/webid.component.ts +++ b/packages/dgt-components/lib/components/authentication/webid.component.ts @@ -4,9 +4,9 @@ import { RxLitElement } from 'rx-lit'; import { Theme } from '@digita-ai/dgt-theme'; import { debounce } from 'debounce'; import { unsafeSVG } from 'lit-html/directives/unsafe-svg'; +import { Translator } from '@digita-ai/dgt-utils'; import { define } from '../../util/define'; import { AlertComponent } from '../alerts/alert.component'; -import { Translator } from '../../services/i18n/translator'; export class WebIdComponent extends RxLitElement { diff --git a/packages/dgt-components/lib/components/source/source-list.component.spec.ts b/packages/dgt-components/lib/components/source/source-list.component.spec.ts new file mode 100644 index 00000000..8b89cfc7 --- /dev/null +++ b/packages/dgt-components/lib/components/source/source-list.component.spec.ts @@ -0,0 +1,26 @@ +import { define } from '../../util/define'; +import { SourceListComponent } from './source-list.component'; + +describe('SourceListComponent', () => { + + let component: SourceListComponent; + const tag = 'source-list-component'; + + beforeEach(() => { + + define(tag, SourceListComponent); + component = document.createElement(tag) as SourceListComponent; + + }); + + it('should instantiate', async () => { + + window.document.body.appendChild(component); + await component.updateComplete; + + expect(component).toBeTruthy(); + expect(component).toBeInstanceOf(SourceListComponent); + + }); + +}); diff --git a/packages/dgt-components/lib/components/source/source.component.spec.ts b/packages/dgt-components/lib/components/source/source.component.spec.ts new file mode 100644 index 00000000..d5d800a3 --- /dev/null +++ b/packages/dgt-components/lib/components/source/source.component.spec.ts @@ -0,0 +1,26 @@ +import { define } from '../../util/define'; +import { SourceComponent } from './source.component'; + +describe('SourceComponent', () => { + + let component: SourceComponent; + const tag = 'source-component'; + + beforeEach(() => { + + define(tag, SourceComponent); + component = document.createElement(tag) as SourceComponent; + + }); + + it('should instantiate', async () => { + + window.document.body.appendChild(component); + await component.updateComplete; + + expect(component).toBeTruthy(); + expect(component).toBeInstanceOf(SourceComponent); + + }); + +}); diff --git a/packages/dgt-components/lib/index.ts b/packages/dgt-components/lib/index.ts index a4a286c2..d01fc5fd 100644 --- a/packages/dgt-components/lib/index.ts +++ b/packages/dgt-components/lib/index.ts @@ -41,8 +41,5 @@ export * from './models/purpose.model'; export * from './models/session.model'; export * from './models/source.model'; export * from './models/triple.model'; -export * from './services/i18n/memory-translator'; -export * from './services/i18n/mock-translator'; -export * from './services/i18n/translator'; export * from './util/define'; export * from './util/hydrate'; diff --git a/packages/dgt-components/lib/services/i18n/mock-translator.spec.ts b/packages/dgt-components/lib/services/i18n/mock-translator.spec.ts deleted file mode 100644 index 1b7f1568..00000000 --- a/packages/dgt-components/lib/services/i18n/mock-translator.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { MockTranslator } from './mock-translator'; - -describe('MockTranslator', () => { - - let service: MockTranslator; - - beforeEach(async () => { - - service = new MockTranslator('en-GB'); - - }); - - it('should create', () => { - - service = new MockTranslator(); - expect(service).toBeTruthy(); - expect(service.lang).toEqual('nl-NL'); - - service = new MockTranslator('en-GB'); - expect(service).toBeTruthy(); - expect(service.lang).toEqual('en-GB'); - - }); - - describe('translate', () => { - - it('should return key', () => { - - expect(service.translate('test.key')).toEqual('test.key'); - - }); - - }); - - describe('getLang', () => { - - it('should return lang', () => { - - expect(service.getLang()).toEqual('en-GB'); - - }); - - }); - - describe('setLang', () => { - - it('should set lang', async () => { - - await service.setLang('nl-NL'); - expect(service.lang).toEqual('nl-NL'); - - }); - - }); - -}); diff --git a/packages/dgt-components/lib/services/i18n/mock-translator.ts b/packages/dgt-components/lib/services/i18n/mock-translator.ts deleted file mode 100644 index 979c495f..00000000 --- a/packages/dgt-components/lib/services/i18n/mock-translator.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TranslationsLoadedEvent, Translator } from './translator'; - -export class MockTranslator extends Translator { - - public loaded = true; - - constructor(public lang: string = 'nl-NL') { - - super(); - this.setLang(this.lang); - - } - - translate(key: string): string { - - return key; - - } - - getLang(): string { - - return this.lang; - - } - setLang(lang: string): Promise { - - this.lang = lang; - this.loaded = true; - this.dispatchEvent(new TranslationsLoadedEvent()); - - return new Promise((r) => r()); - - } - -} diff --git a/packages/dgt-components/package.json b/packages/dgt-components/package.json index 24a17da1..ba6a0147 100644 --- a/packages/dgt-components/package.json +++ b/packages/dgt-components/package.json @@ -92,10 +92,10 @@ ], "coverageThreshold": { "global": { - "branches": 72.26, - "functions": 71.03, - "lines": 84.57, - "statements": 84.09 + "branches": 75, + "functions": 72.94, + "lines": 89.15, + "statements": 88.69 } }, "coveragePathIgnorePatterns": [ diff --git a/packages/dgt-utils/.componentsignore b/packages/dgt-utils/.componentsignore index b7242042..d7bd6223 100644 --- a/packages/dgt-utils/.componentsignore +++ b/packages/dgt-utils/.componentsignore @@ -14,5 +14,7 @@ "ArgumentError", "HttpError", "HttpClient", - "EventObject" + "EventObject", + "EventTarget", + "CustomEvent" ] diff --git a/packages/dgt-utils/lib/i18n/factories/cached-translator-factory.spec.ts b/packages/dgt-utils/lib/i18n/factories/cached-translator-factory.spec.ts new file mode 100644 index 00000000..75f1b420 --- /dev/null +++ b/packages/dgt-utils/lib/i18n/factories/cached-translator-factory.spec.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/dot-notation */ +import fetchMock from 'jest-fetch-mock'; +import { CachedTranslatorFactory } from './cached-translator-factory'; + +describe('', () => { + + let factory: CachedTranslatorFactory; + + beforeEach(() => { + + factory = new CachedTranslatorFactory(); + + }); + + it('should create translator with correct language', async () => { + + const translator = await factory.createTranslator('en'); + expect(translator.getLang()).toEqual('en'); + + }); + + it('should set translations when creating translator', async () => { + + expect(factory['translations']).toBeUndefined(); + await factory.createTranslator('en'); + expect(factory['translations']).toBeTruthy(); + + }); + + it('should only fetch translations once', async () => { + + fetchMock.resetMocks(); + fetchMock.mockIf(/.*\.json$/, '{}'); + + expect(factory['translations']).toBeUndefined(); + + for (let i = 0; i < 10; i++) { + + await factory.createTranslator('en'); + + } + + expect(fetchMock.mock.calls.length).toEqual(1); + + }); + +}); diff --git a/packages/dgt-utils/lib/i18n/factories/cached-translator-factory.ts b/packages/dgt-utils/lib/i18n/factories/cached-translator-factory.ts new file mode 100644 index 00000000..992b0afc --- /dev/null +++ b/packages/dgt-utils/lib/i18n/factories/cached-translator-factory.ts @@ -0,0 +1,23 @@ +import { MemoryTranslator } from '../translators/memory-translator'; +import { TranslatorFactory } from '../models/translator-factory'; + +/** + * Creates {@link Translator } instances for the given language tag. + */ +export class CachedTranslatorFactory implements TranslatorFactory { + + private translations: { [key: string]: string }; + + async createTranslator(language: string): Promise { + + if (!this.translations) { + + this.translations = await (await fetch(`${window.location.origin}/${language}.json`)).json(); + + } + + return new MemoryTranslator(language, this.translations); + + } + +} diff --git a/packages/dgt-utils/lib/i18n/factories/memory-translator-factory.ts b/packages/dgt-utils/lib/i18n/factories/memory-translator-factory.ts new file mode 100644 index 00000000..9224f682 --- /dev/null +++ b/packages/dgt-utils/lib/i18n/factories/memory-translator-factory.ts @@ -0,0 +1,15 @@ +import { MemoryTranslator } from '../translators/memory-translator'; +import { TranslatorFactory } from '../models/translator-factory'; + +/** + * Creates {@link Translator } instances for the given language tag. + */ +export class MemoryTranslatorFactory implements TranslatorFactory { + + createTranslator(language: string): MemoryTranslator { + + return new MemoryTranslator(language); + + } + +} diff --git a/packages/dgt-utils/lib/i18n/main.spec.ts b/packages/dgt-utils/lib/i18n/main.spec.ts new file mode 100644 index 00000000..1183ba4a --- /dev/null +++ b/packages/dgt-utils/lib/i18n/main.spec.ts @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/dot-notation */ +import { MemoryTranslator } from './translators/memory-translator'; +import { MemoryTranslatorFactory } from './factories/memory-translator-factory'; +import { getTranslator, getTranslatorFor, setTranslator, setTranslatorFactory } from './main'; +import { TranslatorFactory } from './models/translator-factory'; + +describe('main', () => { + + const translator = new MemoryTranslator('en-GB'); + const translatorFactory = new MemoryTranslatorFactory(); + + beforeEach(() => { + + setTranslator(translator); + setTranslatorFactory(translatorFactory); + + }); + + describe('getTranslator', () => { + + it('should return the global translator', () => { + + const get = getTranslator(); + expect(get).toEqual(translator); + + }); + + }); + + describe('setTranslator', () => { + + it('should set the global translator', () => { + + const get = getTranslator(); + const newTranslator = new MemoryTranslator('en-GB'); + setTranslator(newTranslator); + const newGet = getTranslator(); + expect(get).toEqual(translator); + expect(get).not.toEqual(newTranslator); + expect(newGet).not.toEqual(translator); + expect(newGet).toEqual(newTranslator); + + }); + + }); + + describe('getTranslatorFor', () => { + + it('should create a translator with a label when given a string', async () => { + + const testTranslator = await getTranslatorFor('en-GB'); + expect(testTranslator.getLang()).toEqual('en-GB'); + + }); + + it('should create a translator with a label based on constructor name when given an instance of a class', async () => { + + const testTranslator = await getTranslatorFor('en-GB'); + expect(testTranslator.getLang()).toEqual('en-GB'); + + }); + + it('should error when no translatorFactory is set', async () => { + + setTranslatorFactory((undefined as unknown) as TranslatorFactory); + await expect(getTranslatorFor('en-GB')).rejects.toThrow('No TranslatorFactory was set to create translators.'); + + }); + + }); + + describe('setTranslatorFactory', () => { + + it('should set the translator factory', async () => { + + const testTranslator = await getTranslatorFor('en-GB'); + expect(testTranslator instanceof MemoryTranslator).toEqual(true); + + setTranslatorFactory(new MemoryTranslatorFactory()); + + const newTranslator = await getTranslatorFor('en-US'); + expect(newTranslator instanceof MemoryTranslator).toEqual(true); + + }); + + }); + +}); diff --git a/packages/dgt-utils/lib/i18n/main.ts b/packages/dgt-utils/lib/i18n/main.ts new file mode 100644 index 00000000..b4e25caf --- /dev/null +++ b/packages/dgt-utils/lib/i18n/main.ts @@ -0,0 +1,49 @@ +import { Translator } from './models/translator'; +import { TranslatorFactory } from './models/translator-factory'; + +let translator: Translator; +let translatorFactory: TranslatorFactory; + +/** + * returns the global translator. + * + * @returns { Translator } + */ +export const getTranslator = (): Translator => translator; + +/** + * Sets the global translator. + * This will cause the translator returned by {@link getTranslator} to be changed. + * + * @param { Translator } translator - The (new) translator to set globally. + */ +export const setTranslator = (newTranslator: Translator): void => { + + translator = newTranslator; + +}; + +/** + * Gets a translator instance for the given language + * + * @param language - The given language to use. + */ +export const getTranslatorFor = async ( + language: string, +): Promise => { + + if (!translatorFactory) { + + throw new Error('No TranslatorFactory was set to create translators.'); + + } + + return await translatorFactory.createTranslator(language); + +}; + +export const setTranslatorFactory = (newTranslatorFactory: TranslatorFactory): void => { + + translatorFactory = newTranslatorFactory; + +}; diff --git a/packages/dgt-utils/lib/i18n/models/translator-factory.ts b/packages/dgt-utils/lib/i18n/models/translator-factory.ts new file mode 100644 index 00000000..9338ea49 --- /dev/null +++ b/packages/dgt-utils/lib/i18n/models/translator-factory.ts @@ -0,0 +1,14 @@ +import type { Translator } from './translator'; + +/** + * Instantiates new translator instances. + */ +export interface TranslatorFactory { + /** + * Create a translator instance for the given label. + * + * @param label - A label that is used to identify the given translator. + * @param language - The language tag. + */ + createTranslator: (language: string) => Translator | Promise; +} diff --git a/packages/dgt-components/lib/services/i18n/translator.spec.ts b/packages/dgt-utils/lib/i18n/models/translator.spec.ts similarity index 73% rename from packages/dgt-components/lib/services/i18n/translator.spec.ts rename to packages/dgt-utils/lib/i18n/models/translator.spec.ts index 9b9d7589..ba923efb 100644 --- a/packages/dgt-components/lib/services/i18n/translator.spec.ts +++ b/packages/dgt-utils/lib/i18n/models/translator.spec.ts @@ -1,13 +1,14 @@ -import { MockTranslator } from './mock-translator'; +/* eslint-disable @typescript-eslint/dot-notation */ +import { MemoryTranslator } from '../translators/memory-translator'; import { TRANSLATIONS_LOADED } from './translator'; describe('Translator', () => { - let service: MockTranslator; + let service: MemoryTranslator; beforeEach(async () => { - service = new MockTranslator('en-GB'); + service = new MemoryTranslator('en-GB'); }); @@ -27,7 +28,7 @@ describe('Translator', () => { it('should not dispatch event when loaded is false', () => { - service.loaded = false; + service['loaded'] = false; service.dispatchEvent = jest.fn(); service.addEventListener(TRANSLATIONS_LOADED, () => undefined); expect(service.dispatchEvent).not.toHaveBeenCalled(); diff --git a/packages/dgt-components/lib/services/i18n/translator.ts b/packages/dgt-utils/lib/i18n/models/translator.ts similarity index 83% rename from packages/dgt-components/lib/services/i18n/translator.ts rename to packages/dgt-utils/lib/i18n/models/translator.ts index 56b1beb8..b46bf1be 100644 --- a/packages/dgt-components/lib/services/i18n/translator.ts +++ b/packages/dgt-utils/lib/i18n/models/translator.ts @@ -1,4 +1,3 @@ - export const TRANSLATIONS_LOADED = 'TRANSLATIONS_LOADED'; export class TranslationsLoadedEvent extends CustomEvent { @@ -21,6 +20,17 @@ export abstract class Translator extends EventTarget { */ protected loaded = false; + constructor( + public lang: string, + protected translations?: { [key: string]: string } + ) { + + super(); + + this.setLang(lang, translations); + + } + addEventListener( type: string, listener: EventListenerOrEventListenerObject, @@ -57,6 +67,6 @@ export abstract class Translator extends EventTarget { * * @param lang The new language to use */ - abstract setLang(lang: string): Promise; + abstract setLang(lang: string, translations?: { [key: string]: string }): Promise; } diff --git a/packages/dgt-components/lib/services/i18n/memory-translator.spec.ts b/packages/dgt-utils/lib/i18n/translators/memory-translator.spec.ts similarity index 97% rename from packages/dgt-components/lib/services/i18n/memory-translator.spec.ts rename to packages/dgt-utils/lib/i18n/translators/memory-translator.spec.ts index 6242906a..1e511efe 100644 --- a/packages/dgt-components/lib/services/i18n/memory-translator.spec.ts +++ b/packages/dgt-utils/lib/i18n/translators/memory-translator.spec.ts @@ -1,6 +1,6 @@ import fetchMock from 'jest-fetch-mock'; +import { TRANSLATIONS_LOADED } from '../models/translator'; import { MemoryTranslator } from './memory-translator'; -import { TRANSLATIONS_LOADED } from './translator'; describe('MemoryTranslator', () => { diff --git a/packages/dgt-components/lib/services/i18n/memory-translator.ts b/packages/dgt-utils/lib/i18n/translators/memory-translator.ts similarity index 68% rename from packages/dgt-components/lib/services/i18n/memory-translator.ts rename to packages/dgt-utils/lib/i18n/translators/memory-translator.ts index 7a5281b7..cc908140 100644 --- a/packages/dgt-components/lib/services/i18n/memory-translator.ts +++ b/packages/dgt-utils/lib/i18n/translators/memory-translator.ts @@ -1,23 +1,11 @@ import { registerTranslateConfig, use, get, Values, ValuesCallback, ITranslateConfig, Strings } from '@appnest/lit-translate'; -import { TranslationsLoadedEvent, Translator } from './translator'; +import { TranslationsLoadedEvent, Translator } from '../models/translator'; /** * An implementation of a Translator which stores translations in-memory. */ export class MemoryTranslator extends Translator { - /** - * Instantiates a MemoryTranslator. - * - * @param lang The default locale to use when translating. - */ - constructor(public lang: string) { - - super(); - this.setLang(lang); - - } - /** * Translates a key to a specific locale. * @@ -58,25 +46,28 @@ export class MemoryTranslator extends Translator { * * @param lang The new language to use */ - async setLang(lang: string): Promise{ + async setLang(lang: string, translations?: { [key: string]: string }): Promise { + + if (!translations) { - this.loaded = false; + this.loaded = false; - let translations: Promise; + try { - try { + this.translations = await (await fetch(`${window.location.origin}/${lang}.json`)).json(); + this.lang = lang; - translations = await (await fetch(`${window.location.origin}/${lang}.json`)).json(); - this.lang = lang; + } catch(e) { - } catch(e) { + // eslint-disable-next-line no-console + console.error('Failed to load translations for language: ' + lang); - translations = await (await fetch(`${window.location.origin}/${this.lang}.json`)).json(); + } } registerTranslateConfig({ - loader: async () => translations, + loader: async () => this.translations, }); await use(this.lang); diff --git a/packages/dgt-utils/lib/i8n/memory-translator.spec.ts b/packages/dgt-utils/lib/i8n/memory-translator.spec.ts deleted file mode 100644 index d4dea5d0..00000000 --- a/packages/dgt-utils/lib/i8n/memory-translator.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { ArgumentError } from '../errors/models/argument-error'; -import { MemoryTranslator } from './memory-translator'; - -describe('MemoryTranslator', () => { - - let service: MemoryTranslator; - - beforeEach(async () => { - - service = new MemoryTranslator([ - { - locale: 'en-GB', - key: 'foo', - value: 'Foo in English', - }, - { - locale: 'nl-NL', - key: 'foo', - value: 'Foo in Dutch', - }, - { - locale: 'en-GB', - key: 'foo.bar', - value: 'Bar in English', - }, - ], 'en-GB'); - - }); - - it('should be correctly instantiated', () => { - - expect(service).toBeTruthy(); - - }); - - describe('translate', () => { - - it('Should return an existing key in an existing locale.', () => { - - const value = service.translate('foo', 'en-GB'); - - expect(value).toEqual('Foo in English'); - - }); - - it('Should translate by using the default locale when no locale was given.', () => { - - const value = service.translate('foo'); - - expect(value).toEqual('Foo in English'); - - }); - - it('Should return the input key with an existing key in an non-existing locale.', () => { - - const value = service.translate('foo.bar', 'nl-NL'); - - expect(value).toEqual('foo.bar'); - - }); - - it('Should return the input key with an non-existing key in an existing locale.', () => { - - const value = service.translate('lorem', 'nl-NL'); - - expect(value).toEqual('lorem'); - - }); - - it('Should throw error when key is null.', () => { - - expect(()=>service.translate(null, 'bla')).toThrow(ArgumentError); - - }); - - it('Should throw error when locale and defaultLocale is null.', () => { - - service.defaultLocale = null; - - expect(()=>service.translate('bla', null)).toThrow(ArgumentError); - - }); - - }); - -}); diff --git a/packages/dgt-utils/lib/i8n/memory-translator.ts b/packages/dgt-utils/lib/i8n/memory-translator.ts deleted file mode 100644 index a50054f9..00000000 --- a/packages/dgt-utils/lib/i8n/memory-translator.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ArgumentError } from '../errors/models/argument-error'; -import { Translation } from './translation'; - -/** - * An implementation of a Translator which stores translations in-memory. - */ -export class MemoryTranslator { - - /** - * Instantiates a MemoryTranslator. - * - * @param translations The translations to be stored in-memory. - * @param defaultLocale The default locale to use when translating. - */ - constructor(public translations: Translation[], public defaultLocale: string) { } - - /** - * Translates a key to a specific locale. - * - * @param key The key of the translation. - * @param locale The locale to which the message should be translated. Overrides the default locale. - * @returns The translated text. - * - * @throws {@link ArgumentError} - * This error is thrown when either no locale or key have been given. - */ - translate(key: string, locale?: string): string { - - if (!key) { - - throw new ArgumentError('Argument key should be set.', key); - - } - - if (!locale && ! this.defaultLocale) { - - throw new ArgumentError('Argument locale should be set.', locale); - - } - - // Use default locale if no locale was passed to function - const usedLocale = locale? locale : this.defaultLocale; - - // Find translation based on locale - const foundTranslation = this.translations?.find( - (translation) => translation.locale === usedLocale && translation.key === key, - ); - - // return key when no translation was found - return foundTranslation?.value || key; - - } - -} diff --git a/packages/dgt-utils/lib/i8n/translation.ts b/packages/dgt-utils/lib/i8n/translation.ts deleted file mode 100644 index 27c20ef2..00000000 --- a/packages/dgt-utils/lib/i8n/translation.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Represents a translation of a string in a specific locale. - */ -export interface Translation { - /** - * The locale of the translation. - */ - locale: string; - - /** - * The key of the translation. - */ - key: string; - - /** - * The value of the translation. - */ - value: string; -} diff --git a/packages/dgt-utils/lib/i8n/translator.ts b/packages/dgt-utils/lib/i8n/translator.ts deleted file mode 100644 index fc95b73c..00000000 --- a/packages/dgt-utils/lib/i8n/translator.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * An abstract definition of a class which can retrieve translations. - */ -export abstract class Translator { - - /** - * Translates a key to a specific locale. - * - * @param locale The locale to which the message should be translated. - * @param key The key of the translation. - * @returns The corresponding translation. - */ - abstract translate(key: string, locale?: string): string; - -} diff --git a/packages/dgt-utils/lib/index.ts b/packages/dgt-utils/lib/index.ts index 4ed1d189..57a5e339 100644 --- a/packages/dgt-utils/lib/index.ts +++ b/packages/dgt-utils/lib/index.ts @@ -17,11 +17,14 @@ export * from './origin/services/dgt-origin.service'; export * from './origin/services/dgt-origin-mock.service'; export * from './parameters/services/parameter-checker.service'; export * from './cache/models/dgt-cache-type.model'; -export * from './i8n/memory-translator'; -export * from './i8n/translation'; -export * from './i8n/translator'; export * from './utils/debounce'; export * from './utils/fulltext-match'; export * from './utils/is-equal'; export * from './utils/add-protocol-prefix'; export * from './router/router'; +export * from './i18n/main'; +export * from './i18n/models/translator'; +export * from './i18n/models/translator-factory'; +export * from './i18n/translators/memory-translator'; +export * from './i18n/factories/memory-translator-factory'; +export * from './i18n/factories/cached-translator-factory'; diff --git a/packages/dgt-utils/package-lock.json b/packages/dgt-utils/package-lock.json index 8e83cb47..cd081a34 100644 --- a/packages/dgt-utils/package-lock.json +++ b/packages/dgt-utils/package-lock.json @@ -4,6 +4,14 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@appnest/lit-translate": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@appnest/lit-translate/-/lit-translate-1.1.18.tgz", + "integrity": "sha512-iVdyYw6EXsg8/+WeAJ3Ex2izp3nsfhBs1wyo1AAqOEUcbKOa7xzQhWHFoejNgoV+PpDBeqDlfbqrCZsKqV6F3Q==", + "requires": { + "lit-html": "^1.1.2" + } + }, "@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", @@ -5298,6 +5306,11 @@ "type-check": "~0.4.0" } }, + "lit-html": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.4.1.tgz", + "integrity": "sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==" + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", diff --git a/packages/dgt-utils/package.json b/packages/dgt-utils/package.json index 07643d12..e69cf53f 100644 --- a/packages/dgt-utils/package.json +++ b/packages/dgt-utils/package.json @@ -28,6 +28,7 @@ ".componentsignore" ], "dependencies": { + "@appnest/lit-translate": "^1.1.18", "@types/node": "^14.14.14", "rxjs": "^7.4.0", "xstate": "^4.26.1", @@ -75,12 +76,18 @@ "displayName": "components", "preset": "@digita-ai/jest-config", "testEnvironment": "jsdom", + "setupFiles": [ + "/tests/setup.ts" + ], + "transformIgnorePatterns": [ + "node_modules/(?!(lit-html|@appnest/lit-translate)/)" + ], "coverageThreshold": { "global": { - "branches": 44.55, - "functions": 35.8, - "lines": 41.35, - "statements": 43 + "branches": 41.66, + "functions": 42.85, + "lines": 48.5, + "statements": 50 } }, "coveragePathIgnorePatterns": [ diff --git a/packages/dgt-utils/tests/setup.ts b/packages/dgt-utils/tests/setup.ts new file mode 100644 index 00000000..e807a9d4 --- /dev/null +++ b/packages/dgt-utils/tests/setup.ts @@ -0,0 +1,6 @@ +import fetchMock from 'jest-fetch-mock'; + +fetchMock.enableMocks(); + +// mock all requests to localization assets +fetchMock.mockIf(/.*\.json$/, '{}');