diff --git a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts index 8adef08f46..5fea751b14 100644 --- a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts @@ -2,11 +2,12 @@ import { Injectable, NgZone, Optional, SkipSelf } from '@angular/core'; import { ActivatedRouteSnapshot, Router } from '@angular/router'; import { Actions, ofActionSuccessful, Store } from '@ngxs/store'; import { noop, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { SetLanguage } from '../actions/session.actions'; -import { ApplicationConfiguration } from '../models/application-configuration'; import { Config } from '../models/config'; import { ConfigState } from '../states/config.state'; import { registerLocale } from '../utils/initial-utils'; +import { localize, localizeWithFallback } from '../utils/localization-utils'; type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean; @@ -77,30 +78,35 @@ export class LocalizationService { return this.store.selectSnapshot(ConfigState.getLocalization(key, ...interpolateParams)); } - isLocalized(key, sourceName) { - if (sourceName === '_') { - // A convention to suppress the localization - return true; - } - - const localization = this.store.selectSnapshot( - ConfigState.getOne('localization'), - ) as ApplicationConfiguration.Localization; - sourceName = sourceName || localization.defaultResourceName; - if (!sourceName) { - return false; - } + localize(resourceName: string, key: string, defaultValue: string): Observable { + return this.store + .select(ConfigState.getOne('localization')) + .pipe(map(localize(resourceName, key, defaultValue))); + } - const source = localization.values[sourceName]; - if (!source) { - return false; - } + localizeSync(resourceName: string, key: string, defaultValue: string): string { + return localize( + resourceName, + key, + defaultValue, + )(this.store.selectSnapshot(ConfigState.getOne('localization'))); + } - const value = source[key]; - if (value === undefined) { - return false; - } + localizeWithFallback( + resourceNames: string[], + keys: string[], + defaultValue: string, + ): Observable { + return this.store + .select(ConfigState.getOne('localization')) + .pipe(map(localizeWithFallback(resourceNames, keys, defaultValue))); + } - return true; + localizeWithFallbackSync(resourceNames: string[], keys: string[], defaultValue: string): string { + return localizeWithFallback( + resourceNames, + keys, + defaultValue, + )(this.store.selectSnapshot(ConfigState.getOne('localization'))); } } diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts index 8c5b859f9f..e17d53e463 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts @@ -1,7 +1,7 @@ import { Router } from '@angular/router'; import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; -import { Store, Actions } from '@ngxs/store'; -import { Observable, of, Subject } from 'rxjs'; +import { Actions, Store } from '@ngxs/store'; +import { of, Subject } from 'rxjs'; import { LocalizationService } from '../services/localization.service'; describe('LocalizationService', () => { @@ -75,4 +75,172 @@ describe('LocalizationService', () => { } }); }); + + describe('#localize', () => { + test.each` + resource | key | defaultValue | expected + ${'foo'} | ${'bar'} | ${'DEFAULT'} | ${'baz'} + ${'x'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${'y'} | ${'DEFAULT'} | ${'z'} + ${'a'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + `( + 'should return observable $expected when resource name is $resource and key is $key', + async ({ resource, key, defaultValue, expected }) => { + store.select.andReturn( + of({ + values: { foo: { bar: 'baz' }, x: { y: 'z' } }, + defaultResourceName: 'x', + }), + ); + + const result = await service.localize(resource, key, defaultValue).toPromise(); + + expect(result).toBe(expected); + }, + ); + }); + + describe('#localizeSync', () => { + test.each` + resource | key | defaultValue | expected + ${'foo'} | ${'bar'} | ${'DEFAULT'} | ${'baz'} + ${'x'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${'y'} | ${'DEFAULT'} | ${'z'} + ${'a'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + `( + 'should return $expected when resource name is $resource and key is $key', + ({ resource, key, defaultValue, expected }) => { + store.selectSnapshot.andReturn({ + values: { foo: { bar: 'baz' }, x: { y: 'z' } }, + defaultResourceName: 'x', + }); + + const result = service.localizeSync(resource, key, defaultValue); + + expect(result).toBe(expected); + }, + ); + }); + + describe('#localizeWithFallback', () => { + test.each` + resources | keys | defaultValue | expected + ${['foo']} | ${['bar']} | ${'DEFAULT'} | ${'baz'} + ${['x']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['foo']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['x']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['a', 'b', 'c']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${[]} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['foo']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'baz'} + ${['x']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['a', 'b', 'c']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${[]} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['foo']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['x']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['foo']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['x']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + `( + 'should return observable $expected when resource names are $resources and keys are $keys', + async ({ resources, keys, defaultValue, expected }) => { + store.select.andReturn( + of({ + values: { foo: { bar: 'baz' }, x: { y: 'z' } }, + defaultResourceName: 'x', + }), + ); + + const result = await service + .localizeWithFallback(resources, keys, defaultValue) + .toPromise(); + + expect(result).toBe(expected); + }, + ); + }); + + describe('#localizeWithFallbackSync', () => { + test.each` + resources | keys | defaultValue | expected + ${['foo']} | ${['bar']} | ${'DEFAULT'} | ${'baz'} + ${['x']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['foo']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['x']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['a', 'b', 'c']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${[]} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['foo']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'baz'} + ${['x']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['a', 'b', 'c']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${[]} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['foo']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['x']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['foo']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['x']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + `( + 'should return $expected when resource names are $resources and keys are $keys', + ({ resources, keys, defaultValue, expected }) => { + store.selectSnapshot.andReturn({ + values: { foo: { bar: 'baz' }, x: { y: 'z' } }, + defaultResourceName: 'x', + }); + + const result = service.localizeWithFallbackSync(resources, keys, defaultValue); + + expect(result).toBe(expected); + }, + ); + }); }); diff --git a/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts new file mode 100644 index 0000000000..7d5a2c9e70 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts @@ -0,0 +1,34 @@ +import { ApplicationConfiguration } from '../models/application-configuration'; + +export function localize(resourceName: string, key: string, defaultValue: string) { + return function(localization: ApplicationConfiguration.Localization) { + if (resourceName === '_') return key; + + const resource = localization.values[resourceName]; + + if (!resource) return defaultValue; + + return resource[key] || defaultValue; + }; +} + +export function localizeWithFallback( + resourceNames: string[], + keys: string[], + defaultValue: string, +) { + return function(localization: ApplicationConfiguration.Localization) { + resourceNames = resourceNames.concat(localization.defaultResourceName).filter(Boolean); + + for (let i = 0; i < resourceNames.length; i++) { + const resourceName = resourceNames[i]; + + for (let j = 0; j < keys.length; j++) { + const localized = localize(resourceName, keys[j], null)(localization); + if (localized) return localized; + } + } + + return defaultValue; + }; +}