From 2a2d701e000cfd645ed5fea5c6acb474f7e8ce4a Mon Sep 17 00:00:00 2001 From: Stijn Taelemans Date: Wed, 2 Feb 2022 16:11:07 +0100 Subject: [PATCH 01/10] feat: implement factory pattern for translator WIP --- packages/dgt-utils/.componentsignore | 4 +- .../factories/memory-translator-factory.ts | 15 ++ packages/dgt-utils/lib/i18n/main.spec.ts | 93 ++++++++++++ packages/dgt-utils/lib/i18n/main.ts | 50 +++++++ .../lib/i18n/models/translator-factory.ts | 14 ++ .../lib/i18n/models/translator.spec.ts | 40 +++++ .../dgt-utils/lib/i18n/models/translator.ts | 71 +++++++++ .../translators/memory-translator.spec.ts | 138 ++++++++++++++++++ .../lib/i18n/translators/memory-translator.ts | 77 ++++++++++ .../lib/i8n/memory-translator.spec.ts | 86 ----------- .../dgt-utils/lib/i8n/memory-translator.ts | 54 ------- packages/dgt-utils/lib/i8n/translation.ts | 19 --- packages/dgt-utils/lib/i8n/translator.ts | 15 -- packages/dgt-utils/lib/index.ts | 8 +- packages/dgt-utils/package-lock.json | 13 ++ packages/dgt-utils/package.json | 11 +- packages/dgt-utils/tests/setup.ts | 6 + 17 files changed, 534 insertions(+), 180 deletions(-) create mode 100644 packages/dgt-utils/lib/i18n/factories/memory-translator-factory.ts create mode 100644 packages/dgt-utils/lib/i18n/main.spec.ts create mode 100644 packages/dgt-utils/lib/i18n/main.ts create mode 100644 packages/dgt-utils/lib/i18n/models/translator-factory.ts create mode 100644 packages/dgt-utils/lib/i18n/models/translator.spec.ts create mode 100644 packages/dgt-utils/lib/i18n/models/translator.ts create mode 100644 packages/dgt-utils/lib/i18n/translators/memory-translator.spec.ts create mode 100644 packages/dgt-utils/lib/i18n/translators/memory-translator.ts delete mode 100644 packages/dgt-utils/lib/i8n/memory-translator.spec.ts delete mode 100644 packages/dgt-utils/lib/i8n/memory-translator.ts delete mode 100644 packages/dgt-utils/lib/i8n/translation.ts delete mode 100644 packages/dgt-utils/lib/i8n/translator.ts create mode 100644 packages/dgt-utils/tests/setup.ts 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/memory-translator-factory.ts b/packages/dgt-utils/lib/i18n/factories/memory-translator-factory.ts new file mode 100644 index 00000000..a44d091f --- /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(label: string, language: string): MemoryTranslator { + + return new MemoryTranslator(label, 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..6dd728d4 --- /dev/null +++ b/packages/dgt-utils/lib/i18n/main.spec.ts @@ -0,0 +1,93 @@ +/* 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('test-translator', '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('test-translator', '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', () => { + + const testTranslator = getTranslatorFor('test-translator', 'en-GB'); + + expect(testTranslator['label']).toEqual('test-translator'); + 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', () => { + + const testClass = { constructor: { name: 'test-constructor-name' } }; + const testTranslator = getTranslatorFor(testClass, 'en-GB'); + + expect(testTranslator['label']).toEqual('test-constructor-name'); + expect(testTranslator.getLang()).toEqual('en-GB'); + + }); + + it('should error when no translatorFactory is set', () => { + + setTranslatorFactory((undefined as unknown) as TranslatorFactory); + expect(() => getTranslatorFor('test-translator', 'en-GB')).toThrow('No TranslatorFactory was set to create translators.'); + + }); + + }); + + describe('setTranslatorFactory', () => { + + it('should set the translator factory', () => { + + const testTranslator = getTranslatorFor('test-translator', 'en-GB'); + expect(testTranslator instanceof MemoryTranslator).toEqual(true); + + setTranslatorFactory(new MemoryTranslatorFactory()); + + const newTranslator = getTranslatorFor('test-translator', '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..7dc969ea --- /dev/null +++ b/packages/dgt-utils/lib/i18n/main.ts @@ -0,0 +1,50 @@ +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 class instance. + * + * @param loggable - A class instance or a class string name. + */ +export const getTranslatorFor = ( + loggable: string | { constructor: { name: string } }, + language: string, +): Translator => { + + if (!translatorFactory) { + + throw new Error('No TranslatorFactory was set to create translators.'); + + } + + return translatorFactory.createTranslator(typeof loggable === 'string' ? loggable : loggable.constructor.name, 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..91a69a0b --- /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: (label: string, language: string) => Translator; +} diff --git a/packages/dgt-utils/lib/i18n/models/translator.spec.ts b/packages/dgt-utils/lib/i18n/models/translator.spec.ts new file mode 100644 index 00000000..edcfc4e7 --- /dev/null +++ b/packages/dgt-utils/lib/i18n/models/translator.spec.ts @@ -0,0 +1,40 @@ +/* eslint-disable @typescript-eslint/dot-notation */ +import { MemoryTranslator } from '../translators/memory-translator'; +import { TRANSLATIONS_LOADED } from './translator'; + +describe('Translator', () => { + + let service: MemoryTranslator; + + beforeEach(async () => { + + service = new MemoryTranslator('label', 'en-GB'); + + }); + + it('should create', () => { + + expect(service).toBeTruthy(); + + }); + + describe('addEventListener', () => { + + it('should dispatch event when loaded is true', (done) => { + + service.addEventListener(TRANSLATIONS_LOADED, () => done()); + + }); + + it('should not dispatch event when loaded is false', () => { + + service['loaded'] = false; + service.dispatchEvent = jest.fn(); + service.addEventListener(TRANSLATIONS_LOADED, () => undefined); + expect(service.dispatchEvent).not.toHaveBeenCalled(); + + }); + + }); + +}); diff --git a/packages/dgt-utils/lib/i18n/models/translator.ts b/packages/dgt-utils/lib/i18n/models/translator.ts new file mode 100644 index 00000000..343b1e84 --- /dev/null +++ b/packages/dgt-utils/lib/i18n/models/translator.ts @@ -0,0 +1,71 @@ +export const TRANSLATIONS_LOADED = 'TRANSLATIONS_LOADED'; + +export class TranslationsLoadedEvent extends CustomEvent { + + constructor() { + + super(TRANSLATIONS_LOADED); + + } + +} + +/** + * An abstract definition of a class which can retrieve translations. + */ +export abstract class Translator extends EventTarget { + + /** + * Whether the translations have loaded + */ + protected loaded = false; + + constructor( + protected readonly label: string, + public lang: string + ) { + + super(); + this.setLang(lang); + + } + + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions + ): void { + + super.addEventListener(type, listener, options); + + if(this.loaded) { + + this.dispatchEvent(new TranslationsLoadedEvent()); + + } + + } + /** + * Translates a key to a specific locale. + * + * @param key The key of the translation. + * @returns The corresponding translation. + */ + abstract translate(key: string): string; + + /** + * Returns the language currently used by translator + * + * @returns The language currently used by translator + */ + abstract getLang(): string; + + /** + * Updates the translator's language if a relevant translation file exists + * for this new language. Otherwise, falls back to the previously used language + * + * @param lang The new language to use + */ + abstract setLang(lang: string): Promise; + +} diff --git a/packages/dgt-utils/lib/i18n/translators/memory-translator.spec.ts b/packages/dgt-utils/lib/i18n/translators/memory-translator.spec.ts new file mode 100644 index 00000000..88994e51 --- /dev/null +++ b/packages/dgt-utils/lib/i18n/translators/memory-translator.spec.ts @@ -0,0 +1,138 @@ +import fetchMock from 'jest-fetch-mock'; +import { TRANSLATIONS_LOADED } from '../models/translator'; +import { MemoryTranslator } from './memory-translator'; + +describe('MemoryTranslator', () => { + + let service: MemoryTranslator; + + const mockResponse = JSON.stringify({ + 'foo': { + 'foo': 'Foo', + 'bar': 'Bar', + }, + }); + + beforeEach(async () => { + + fetchMock.resetMocks(); + + fetchMock.mockResponse(mockResponse); + + service = new MemoryTranslator('label', 'en-GB'); + + }); + + afterEach(() => { + + fetchMock.resetMocks(); + + }); + + 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.foo'); + + expect(value).toEqual('Foo'); + + }); + + it('Should translate by using the default locale when no locale was given.', () => { + + const value = service.translate('foo.bar'); + + expect(value).toEqual('Bar'); + + }); + + it('Should return the input key with an non-existing key in an existing locale.', () => { + + const value = service.translate('lorem'); + + expect(value).toEqual('[lorem]'); + + }); + + it('Should throw error when key is undefined.', () => { + + expect(()=>service.translate('')).toThrow(Error); + + }); + + }); + + describe('setLang', () => { + + const newLang = 'en-US'; + + it('should not set new language when invalid JSON', async () => { + + fetchMock.mockIf(/en-US/, ''); + fetchMock.mockIf(/en-GB/, mockResponse); + + await service.setLang(newLang); + expect(service.lang).not.toEqual(newLang); + + }); + + it('should not set new language when fetch throws error', async () => { + + fetchMock.mockResponse(async (req) => { + + if (req.url.includes('en-US')) { + + throw new Error(); + + } else { + + return mockResponse; + + } + + }); + + await service.setLang(newLang); + expect(service.lang).not.toEqual(newLang); + + }); + + it('should set new language correctly', async () => { + + await service.setLang('nl-BE'); + expect(service.getLang()).toEqual('nl-BE'); + + }); + + it('should fire event when done', (done) => { + + service.addEventListener(TRANSLATIONS_LOADED, () => { + + done(); + + }); + + service.setLang('nl-BE'); + + }); + + }); + + describe('getLang', () => { + + it('should return the current language', async () => { + + expect(service.getLang()).toEqual(service.lang); + + }); + + }); + +}); diff --git a/packages/dgt-utils/lib/i18n/translators/memory-translator.ts b/packages/dgt-utils/lib/i18n/translators/memory-translator.ts new file mode 100644 index 00000000..5f955ae7 --- /dev/null +++ b/packages/dgt-utils/lib/i18n/translators/memory-translator.ts @@ -0,0 +1,77 @@ +import { registerTranslateConfig, use, get, Values, ValuesCallback, ITranslateConfig, Strings } from '@appnest/lit-translate'; +import { TranslationsLoadedEvent, Translator } from '../models/translator'; + +/** + * An implementation of a Translator which stores translations in-memory. + */ +export class MemoryTranslator extends Translator { + + /** + * 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 Error} + * This error is thrown when either no locale or key have been given. + */ + + translate(key: string, values?: Values | ValuesCallback, config?: ITranslateConfig): string { + + if (!key) { + + throw new Error('Argument key should be set.'); + + } + + return get(key, values, config); + + } + + /** + * Returns the language currently used by translator + * + * @returns The language currently used by translator + */ + getLang(): string { + + return this.lang; + + } + + /** + * Updates the translator's language if a relevant translation file exists + * for this new language. Otherwise, falls back to the previously used language + * + * @param lang The new language to use + */ + async setLang(lang: string): Promise{ + + this.loaded = false; + + let translations: Promise; + + try { + + translations = await (await fetch(`${window.location.origin}/${lang}.json`)).json(); + this.lang = lang; + + } catch(e) { + + translations = await (await fetch(`${window.location.origin}/${this.lang}.json`)).json(); + + } + + registerTranslateConfig({ + loader: async () => translations, + }); + + await use(this.lang); + + this.loaded = true; + this.dispatchEvent(new TranslationsLoadedEvent()); + + } + +} 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..875c9abf 100644 --- a/packages/dgt-utils/lib/index.ts +++ b/packages/dgt-utils/lib/index.ts @@ -17,11 +17,13 @@ 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'; diff --git a/packages/dgt-utils/package-lock.json b/packages/dgt-utils/package-lock.json index f05976c5..104f3afb 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 f0578b96..4dc7b000 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,9 +76,15 @@ "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, + "branches": 41.66, "functions": 34.56, "lines": 38.72, "statements": 40.61 @@ -90,4 +97,4 @@ "/lib/index.ts" ] } -} \ No newline at end of file +} 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$/, '{}'); From 6dc57eb960f2593e3aeacb58c102430d4a06e648 Mon Sep 17 00:00:00 2001 From: Arthur Joppart Date: Thu, 3 Feb 2022 09:35:15 +0100 Subject: [PATCH 02/10] test: remove componentsjs from crypto service test --- .../dgt-crypto-browser.service.spec.ts | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/packages/dgt-utils/lib/crypto/services/dgt-crypto-browser.service.spec.ts b/packages/dgt-utils/lib/crypto/services/dgt-crypto-browser.service.spec.ts index 81cd87bb..7008b6b3 100644 --- a/packages/dgt-utils/lib/crypto/services/dgt-crypto-browser.service.spec.ts +++ b/packages/dgt-utils/lib/crypto/services/dgt-crypto-browser.service.spec.ts @@ -1,28 +1,14 @@ -/** - * @jest-environment node - */ - -import * as path from 'path'; -import { ComponentsManager } from 'componentsjs'; import { DGTCryptoBrowserService } from './dgt-crypto-browser.service'; +import { DGTLoggerService } from '../../logging/services/dgt-logger.service'; describe('DGTCryptoBrowserService', () => { let service: DGTCryptoBrowserService; + const mockLogger: DGTLoggerService = { debug: jest.fn() } as any; beforeEach(async () => { - const mainModulePath = path.join(__dirname, '../../../'); - const configPath = path.join(mainModulePath, 'config/config-test.json'); - - const manager = await ComponentsManager.build({ - mainModulePath, - logLevel: 'silly', - }); - - await manager.configRegistry.register(configPath); - - service = await manager.instantiate('urn:dgt-utils:test:DGTCryptoBrowserService'); + service = new DGTCryptoBrowserService(mockLogger); }); From fd6c76d144cf3563016acff24f09c7a917077b10 Mon Sep 17 00:00:00 2001 From: Arthur Joppart Date: Thu, 3 Feb 2022 09:39:22 +0100 Subject: [PATCH 03/10] fix: eslint error --- .../lib/crypto/services/dgt-crypto-browser.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dgt-utils/lib/crypto/services/dgt-crypto-browser.service.spec.ts b/packages/dgt-utils/lib/crypto/services/dgt-crypto-browser.service.spec.ts index 7008b6b3..0e3982f5 100644 --- a/packages/dgt-utils/lib/crypto/services/dgt-crypto-browser.service.spec.ts +++ b/packages/dgt-utils/lib/crypto/services/dgt-crypto-browser.service.spec.ts @@ -1,5 +1,5 @@ -import { DGTCryptoBrowserService } from './dgt-crypto-browser.service'; import { DGTLoggerService } from '../../logging/services/dgt-logger.service'; +import { DGTCryptoBrowserService } from './dgt-crypto-browser.service'; describe('DGTCryptoBrowserService', () => { From 2d6e95070512a48e501142e11f950081b7125751 Mon Sep 17 00:00:00 2001 From: Stijn Taelemans Date: Thu, 3 Feb 2022 10:08:14 +0100 Subject: [PATCH 04/10] chore: remove old translator from dgt-components --- .../services/i18n/memory-translator.spec.ts | 138 ------------------ .../lib/services/i18n/memory-translator.ts | 89 ----------- .../lib/services/i18n/mock-translator.spec.ts | 56 ------- .../lib/services/i18n/mock-translator.ts | 35 ----- .../lib/services/i18n/translator.spec.ts | 39 ----- .../lib/services/i18n/translator.ts | 62 -------- packages/dgt-utils/package.json | 8 +- 7 files changed, 4 insertions(+), 423 deletions(-) delete mode 100644 packages/dgt-components/lib/services/i18n/memory-translator.spec.ts delete mode 100644 packages/dgt-components/lib/services/i18n/memory-translator.ts delete mode 100644 packages/dgt-components/lib/services/i18n/mock-translator.spec.ts delete mode 100644 packages/dgt-components/lib/services/i18n/mock-translator.ts delete mode 100644 packages/dgt-components/lib/services/i18n/translator.spec.ts delete mode 100644 packages/dgt-components/lib/services/i18n/translator.ts diff --git a/packages/dgt-components/lib/services/i18n/memory-translator.spec.ts b/packages/dgt-components/lib/services/i18n/memory-translator.spec.ts deleted file mode 100644 index 6242906a..00000000 --- a/packages/dgt-components/lib/services/i18n/memory-translator.spec.ts +++ /dev/null @@ -1,138 +0,0 @@ -import fetchMock from 'jest-fetch-mock'; -import { MemoryTranslator } from './memory-translator'; -import { TRANSLATIONS_LOADED } from './translator'; - -describe('MemoryTranslator', () => { - - let service: MemoryTranslator; - - const mockResponse = JSON.stringify({ - 'foo': { - 'foo': 'Foo', - 'bar': 'Bar', - }, - }); - - beforeEach(async () => { - - fetchMock.resetMocks(); - - fetchMock.mockResponse(mockResponse); - - service = new MemoryTranslator('en-GB'); - - }); - - afterEach(() => { - - fetchMock.resetMocks(); - - }); - - 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.foo'); - - expect(value).toEqual('Foo'); - - }); - - it('Should translate by using the default locale when no locale was given.', () => { - - const value = service.translate('foo.bar'); - - expect(value).toEqual('Bar'); - - }); - - it('Should return the input key with an non-existing key in an existing locale.', () => { - - const value = service.translate('lorem'); - - expect(value).toEqual('[lorem]'); - - }); - - it('Should throw error when key is undefined.', () => { - - expect(()=>service.translate('')).toThrow(Error); - - }); - - }); - - describe('setLang', () => { - - const newLang = 'en-US'; - - it('should not set new language when invalid JSON', async () => { - - fetchMock.mockIf(/en-US/, ''); - fetchMock.mockIf(/en-GB/, mockResponse); - - await service.setLang(newLang); - expect(service.lang).not.toEqual(newLang); - - }); - - it('should not set new language when fetch throws error', async () => { - - fetchMock.mockResponse(async (req) => { - - if (req.url.includes('en-US')) { - - throw new Error(); - - } else { - - return mockResponse; - - } - - }); - - await service.setLang(newLang); - expect(service.lang).not.toEqual(newLang); - - }); - - it('should set new language correctly', async () => { - - await service.setLang('nl-BE'); - expect(service.getLang()).toEqual('nl-BE'); - - }); - - it('should fire event when done', (done) => { - - service.addEventListener(TRANSLATIONS_LOADED, () => { - - done(); - - }); - - service.setLang('nl-BE'); - - }); - - }); - - describe('getLang', () => { - - it('should return the current language', async () => { - - expect(service.getLang()).toEqual(service.lang); - - }); - - }); - -}); diff --git a/packages/dgt-components/lib/services/i18n/memory-translator.ts b/packages/dgt-components/lib/services/i18n/memory-translator.ts deleted file mode 100644 index 7a5281b7..00000000 --- a/packages/dgt-components/lib/services/i18n/memory-translator.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { registerTranslateConfig, use, get, Values, ValuesCallback, ITranslateConfig, Strings } from '@appnest/lit-translate'; -import { TranslationsLoadedEvent, Translator } from './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. - * - * @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 Error} - * This error is thrown when either no locale or key have been given. - */ - - translate(key: string, values?: Values | ValuesCallback, config?: ITranslateConfig): string { - - if (!key) { - - throw new Error('Argument key should be set.'); - - } - - return get(key, values, config); - - } - - /** - * Returns the language currently used by translator - * - * @returns The language currently used by translator - */ - getLang(): string { - - return this.lang; - - } - - /** - * Updates the translator's language if a relevant translation file exists - * for this new language. Otherwise, falls back to the previously used language - * - * @param lang The new language to use - */ - async setLang(lang: string): Promise{ - - this.loaded = false; - - let translations: Promise; - - try { - - translations = await (await fetch(`${window.location.origin}/${lang}.json`)).json(); - this.lang = lang; - - } catch(e) { - - translations = await (await fetch(`${window.location.origin}/${this.lang}.json`)).json(); - - } - - registerTranslateConfig({ - loader: async () => translations, - }); - - await use(this.lang); - - this.loaded = true; - this.dispatchEvent(new TranslationsLoadedEvent()); - - } - -} 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/lib/services/i18n/translator.spec.ts b/packages/dgt-components/lib/services/i18n/translator.spec.ts deleted file mode 100644 index 9b9d7589..00000000 --- a/packages/dgt-components/lib/services/i18n/translator.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { MockTranslator } from './mock-translator'; -import { TRANSLATIONS_LOADED } from './translator'; - -describe('Translator', () => { - - let service: MockTranslator; - - beforeEach(async () => { - - service = new MockTranslator('en-GB'); - - }); - - it('should create', () => { - - expect(service).toBeTruthy(); - - }); - - describe('addEventListener', () => { - - it('should dispatch event when loaded is true', (done) => { - - service.addEventListener(TRANSLATIONS_LOADED, () => done()); - - }); - - it('should not dispatch event when loaded is 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-components/lib/services/i18n/translator.ts deleted file mode 100644 index 56b1beb8..00000000 --- a/packages/dgt-components/lib/services/i18n/translator.ts +++ /dev/null @@ -1,62 +0,0 @@ - -export const TRANSLATIONS_LOADED = 'TRANSLATIONS_LOADED'; - -export class TranslationsLoadedEvent extends CustomEvent { - - constructor() { - - super(TRANSLATIONS_LOADED); - - } - -} - -/** - * An abstract definition of a class which can retrieve translations. - */ -export abstract class Translator extends EventTarget { - - /** - * Whether the translations have loaded - */ - protected loaded = false; - - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - options?: boolean | AddEventListenerOptions - ): void { - - super.addEventListener(type, listener, options); - - if(this.loaded) { - - this.dispatchEvent(new TranslationsLoadedEvent()); - - } - - } - /** - * Translates a key to a specific locale. - * - * @param key The key of the translation. - * @returns The corresponding translation. - */ - abstract translate(key: string): string; - - /** - * Returns the language currently used by translator - * - * @returns The language currently used by translator - */ - abstract getLang(): string; - - /** - * Updates the translator's language if a relevant translation file exists - * for this new language. Otherwise, falls back to the previously used language - * - * @param lang The new language to use - */ - abstract setLang(lang: string): Promise; - -} diff --git a/packages/dgt-utils/package.json b/packages/dgt-utils/package.json index 4dc7b000..496195f6 100644 --- a/packages/dgt-utils/package.json +++ b/packages/dgt-utils/package.json @@ -85,9 +85,9 @@ "coverageThreshold": { "global": { "branches": 41.66, - "functions": 34.56, - "lines": 38.72, - "statements": 40.61 + "functions": 42.22, + "lines": 47.45, + "statements": 49.07 } }, "coveragePathIgnorePatterns": [ @@ -97,4 +97,4 @@ "/lib/index.ts" ] } -} +} \ No newline at end of file From 269978db3111e4177367ab87dc5d0b3f6f09454b Mon Sep 17 00:00:00 2001 From: Stijn Taelemans Date: Thu, 3 Feb 2022 10:17:02 +0100 Subject: [PATCH 05/10] test: fixed tests in dgt-components --- .../lib/components/alerts/alert.component.spec.ts | 9 ++++----- .../components/authentication/authenticate.component.ts | 2 +- .../lib/components/authentication/webid.component.ts | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) 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 { From 127696e8992f84c01fe11e57c89b142b2e5cafc5 Mon Sep 17 00:00:00 2001 From: Stijn Taelemans Date: Thu, 3 Feb 2022 10:22:08 +0100 Subject: [PATCH 06/10] test: update coverage --- .../source/source-list.component.spec.ts | 26 +++++++++++++++++++ .../source/source.component.spec.ts | 26 +++++++++++++++++++ packages/dgt-components/package.json | 8 +++--- 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 packages/dgt-components/lib/components/source/source-list.component.spec.ts create mode 100644 packages/dgt-components/lib/components/source/source.component.spec.ts 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/package.json b/packages/dgt-components/package.json index c42db8ae..13b92ac6 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": [ From fafef543219a8ed7f973ffa8381ab6307bea007e Mon Sep 17 00:00:00 2001 From: Stijn Taelemans Date: Thu, 3 Feb 2022 10:32:58 +0100 Subject: [PATCH 07/10] chore: remove old i18n exports from index.ts --- packages/dgt-components/lib/index.ts | 3 --- 1 file changed, 3 deletions(-) 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'; From 4ef75790656b00fa84b27166ddd793b01742d497 Mon Sep 17 00:00:00 2001 From: Stijn Taelemans Date: Tue, 8 Feb 2022 10:32:36 +0100 Subject: [PATCH 08/10] chore: add demo for translator --- .../dgt-components/demo/demo.component.ts | 21 +++++++++++++++++++ .../dgt-components/demo/public/en-US.json | 3 +++ packages/dgt-components/package.json | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 packages/dgt-components/demo/public/en-US.json diff --git a/packages/dgt-components/demo/demo.component.ts b/packages/dgt-components/demo/demo.component.ts index 2d9f00eb..90f9f5ad 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,22 @@ 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); + console.log(this.translator.translate('example-translation')); + // set global translator + setTranslator(this.translator); + // to use in other components: + this.translator = getTranslator(); + + super.connectedCallback(); + } private onCheckboxClicked = (e: Event) => { @@ -84,6 +103,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/package.json b/packages/dgt-components/package.json index 13b92ac6..e1c148c1 100644 --- a/packages/dgt-components/package.json +++ b/packages/dgt-components/package.json @@ -106,4 +106,4 @@ "/demo/" ] } -} \ No newline at end of file +} From 94eee85895c33f88aed50331d5eb826f18e7f59d Mon Sep 17 00:00:00 2001 From: Stijn Taelemans Date: Tue, 8 Feb 2022 11:01:58 +0100 Subject: [PATCH 09/10] chore: removed label, refactored getTranslatorFor --- .../cached-translator-factory.spec.ts | 47 +++++++++++++++++++ .../factories/cached-translator-factory.ts | 23 +++++++++ .../factories/memory-translator-factory.ts | 4 +- packages/dgt-utils/lib/i18n/main.spec.ts | 27 +++++------ packages/dgt-utils/lib/i18n/main.ts | 11 ++--- .../lib/i18n/models/translator-factory.ts | 2 +- .../lib/i18n/models/translator.spec.ts | 2 +- .../dgt-utils/lib/i18n/models/translator.ts | 9 ++-- .../translators/memory-translator.spec.ts | 2 +- .../lib/i18n/translators/memory-translator.ts | 21 +++++---- packages/dgt-utils/lib/index.ts | 1 + packages/dgt-utils/package.json | 6 +-- 12 files changed, 112 insertions(+), 43 deletions(-) create mode 100644 packages/dgt-utils/lib/i18n/factories/cached-translator-factory.spec.ts create mode 100644 packages/dgt-utils/lib/i18n/factories/cached-translator-factory.ts 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 index a44d091f..9224f682 100644 --- a/packages/dgt-utils/lib/i18n/factories/memory-translator-factory.ts +++ b/packages/dgt-utils/lib/i18n/factories/memory-translator-factory.ts @@ -6,9 +6,9 @@ import { TranslatorFactory } from '../models/translator-factory'; */ export class MemoryTranslatorFactory implements TranslatorFactory { - createTranslator(label: string, language: string): MemoryTranslator { + createTranslator(language: string): MemoryTranslator { - return new MemoryTranslator(label, language); + return new MemoryTranslator(language); } diff --git a/packages/dgt-utils/lib/i18n/main.spec.ts b/packages/dgt-utils/lib/i18n/main.spec.ts index 6dd728d4..1183ba4a 100644 --- a/packages/dgt-utils/lib/i18n/main.spec.ts +++ b/packages/dgt-utils/lib/i18n/main.spec.ts @@ -6,7 +6,7 @@ import { TranslatorFactory } from './models/translator-factory'; describe('main', () => { - const translator = new MemoryTranslator('test-translator', 'en-GB'); + const translator = new MemoryTranslator('en-GB'); const translatorFactory = new MemoryTranslatorFactory(); beforeEach(() => { @@ -32,7 +32,7 @@ describe('main', () => { it('should set the global translator', () => { const get = getTranslator(); - const newTranslator = new MemoryTranslator('test-translator', 'en-GB'); + const newTranslator = new MemoryTranslator('en-GB'); setTranslator(newTranslator); const newGet = getTranslator(); expect(get).toEqual(translator); @@ -46,29 +46,24 @@ describe('main', () => { describe('getTranslatorFor', () => { - it('should create a translator with a label when given a string', () => { + it('should create a translator with a label when given a string', async () => { - const testTranslator = getTranslatorFor('test-translator', 'en-GB'); - - expect(testTranslator['label']).toEqual('test-translator'); + 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', () => { - - const testClass = { constructor: { name: 'test-constructor-name' } }; - const testTranslator = getTranslatorFor(testClass, 'en-GB'); + it('should create a translator with a label based on constructor name when given an instance of a class', async () => { - expect(testTranslator['label']).toEqual('test-constructor-name'); + const testTranslator = await getTranslatorFor('en-GB'); expect(testTranslator.getLang()).toEqual('en-GB'); }); - it('should error when no translatorFactory is set', () => { + it('should error when no translatorFactory is set', async () => { setTranslatorFactory((undefined as unknown) as TranslatorFactory); - expect(() => getTranslatorFor('test-translator', 'en-GB')).toThrow('No TranslatorFactory was set to create translators.'); + await expect(getTranslatorFor('en-GB')).rejects.toThrow('No TranslatorFactory was set to create translators.'); }); @@ -76,14 +71,14 @@ describe('main', () => { describe('setTranslatorFactory', () => { - it('should set the translator factory', () => { + it('should set the translator factory', async () => { - const testTranslator = getTranslatorFor('test-translator', 'en-GB'); + const testTranslator = await getTranslatorFor('en-GB'); expect(testTranslator instanceof MemoryTranslator).toEqual(true); setTranslatorFactory(new MemoryTranslatorFactory()); - const newTranslator = getTranslatorFor('test-translator', 'en-US'); + 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 index 7dc969ea..b4e25caf 100644 --- a/packages/dgt-utils/lib/i18n/main.ts +++ b/packages/dgt-utils/lib/i18n/main.ts @@ -24,14 +24,13 @@ export const setTranslator = (newTranslator: Translator): void => { }; /** - * Gets a translator instance for the given class instance. + * Gets a translator instance for the given language * - * @param loggable - A class instance or a class string name. + * @param language - The given language to use. */ -export const getTranslatorFor = ( - loggable: string | { constructor: { name: string } }, +export const getTranslatorFor = async ( language: string, -): Translator => { +): Promise => { if (!translatorFactory) { @@ -39,7 +38,7 @@ export const getTranslatorFor = ( } - return translatorFactory.createTranslator(typeof loggable === 'string' ? loggable : loggable.constructor.name, language); + return await translatorFactory.createTranslator(language); }; diff --git a/packages/dgt-utils/lib/i18n/models/translator-factory.ts b/packages/dgt-utils/lib/i18n/models/translator-factory.ts index 91a69a0b..9338ea49 100644 --- a/packages/dgt-utils/lib/i18n/models/translator-factory.ts +++ b/packages/dgt-utils/lib/i18n/models/translator-factory.ts @@ -10,5 +10,5 @@ export interface TranslatorFactory { * @param label - A label that is used to identify the given translator. * @param language - The language tag. */ - createTranslator: (label: string, language: string) => Translator; + createTranslator: (language: string) => Translator | Promise; } diff --git a/packages/dgt-utils/lib/i18n/models/translator.spec.ts b/packages/dgt-utils/lib/i18n/models/translator.spec.ts index edcfc4e7..ba923efb 100644 --- a/packages/dgt-utils/lib/i18n/models/translator.spec.ts +++ b/packages/dgt-utils/lib/i18n/models/translator.spec.ts @@ -8,7 +8,7 @@ describe('Translator', () => { beforeEach(async () => { - service = new MemoryTranslator('label', 'en-GB'); + service = new MemoryTranslator('en-GB'); }); diff --git a/packages/dgt-utils/lib/i18n/models/translator.ts b/packages/dgt-utils/lib/i18n/models/translator.ts index 343b1e84..b46bf1be 100644 --- a/packages/dgt-utils/lib/i18n/models/translator.ts +++ b/packages/dgt-utils/lib/i18n/models/translator.ts @@ -21,12 +21,13 @@ export abstract class Translator extends EventTarget { protected loaded = false; constructor( - protected readonly label: string, - public lang: string + public lang: string, + protected translations?: { [key: string]: string } ) { super(); - this.setLang(lang); + + this.setLang(lang, translations); } @@ -66,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-utils/lib/i18n/translators/memory-translator.spec.ts b/packages/dgt-utils/lib/i18n/translators/memory-translator.spec.ts index 88994e51..1e511efe 100644 --- a/packages/dgt-utils/lib/i18n/translators/memory-translator.spec.ts +++ b/packages/dgt-utils/lib/i18n/translators/memory-translator.spec.ts @@ -19,7 +19,7 @@ describe('MemoryTranslator', () => { fetchMock.mockResponse(mockResponse); - service = new MemoryTranslator('label', 'en-GB'); + service = new MemoryTranslator('en-GB'); }); diff --git a/packages/dgt-utils/lib/i18n/translators/memory-translator.ts b/packages/dgt-utils/lib/i18n/translators/memory-translator.ts index 5f955ae7..cc908140 100644 --- a/packages/dgt-utils/lib/i18n/translators/memory-translator.ts +++ b/packages/dgt-utils/lib/i18n/translators/memory-translator.ts @@ -46,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 { - this.loaded = false; + if (!translations) { - let translations: Promise; + this.loaded = false; - try { + try { - translations = await (await fetch(`${window.location.origin}/${lang}.json`)).json(); - this.lang = lang; + this.translations = await (await fetch(`${window.location.origin}/${lang}.json`)).json(); + this.lang = lang; - } catch(e) { + } catch(e) { - translations = await (await fetch(`${window.location.origin}/${this.lang}.json`)).json(); + // eslint-disable-next-line no-console + console.error('Failed to load translations for language: ' + lang); + + } } registerTranslateConfig({ - loader: async () => translations, + loader: async () => this.translations, }); await use(this.lang); diff --git a/packages/dgt-utils/lib/index.ts b/packages/dgt-utils/lib/index.ts index 875c9abf..57a5e339 100644 --- a/packages/dgt-utils/lib/index.ts +++ b/packages/dgt-utils/lib/index.ts @@ -27,3 +27,4 @@ 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.json b/packages/dgt-utils/package.json index 496195f6..5f0cafb7 100644 --- a/packages/dgt-utils/package.json +++ b/packages/dgt-utils/package.json @@ -85,9 +85,9 @@ "coverageThreshold": { "global": { "branches": 41.66, - "functions": 42.22, - "lines": 47.45, - "statements": 49.07 + "functions": 42.85, + "lines": 48.5, + "statements": 50 } }, "coveragePathIgnorePatterns": [ From a8c658fec5487133bbcbc302827ab6868702ea0e Mon Sep 17 00:00:00 2001 From: Stijn Taelemans Date: Tue, 8 Feb 2022 11:19:47 +0100 Subject: [PATCH 10/10] chore: remove console.log --- packages/dgt-components/demo/demo.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dgt-components/demo/demo.component.ts b/packages/dgt-components/demo/demo.component.ts index 90f9f5ad..548a18b0 100644 --- a/packages/dgt-components/demo/demo.component.ts +++ b/packages/dgt-components/demo/demo.component.ts @@ -76,7 +76,6 @@ export class DemoComponent extends RxLitElement { async connectedCallback(): Promise { this.translator = await getTranslatorFor(navigator.language); - console.log(this.translator.translate('example-translation')); // set global translator setTranslator(this.translator); // to use in other components: