diff --git a/projects/rero/ng-core/src/lib/core-config.service.ts b/projects/rero/ng-core/src/lib/core-config.service.ts
index 5808b71f..16d47306 100644
--- a/projects/rero/ng-core/src/lib/core-config.service.ts
+++ b/projects/rero/ng-core/src/lib/core-config.service.ts
@@ -1,6 +1,6 @@
/*
* RERO angular core
- * Copyright (C) 2020 RERO
+ * Copyright (C) 2020-2023 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -20,6 +20,7 @@ import { Injectable } from '@angular/core';
* Interface for configuration.
*/
export interface Config {
+ appVersion?: string;
production?: boolean;
prefixWindow?: string;
apiBaseUrl?: string;
@@ -39,6 +40,7 @@ export interface Config {
providedIn: 'root'
})
export class CoreConfigService implements Config {
+ appVersion = undefined;
production = false;
prefixWindow = undefined;
apiBaseUrl = '';
diff --git a/projects/rero/ng-core/src/lib/core.module.ts b/projects/rero/ng-core/src/lib/core.module.ts
index b1150677..0d749bbd 100644
--- a/projects/rero/ng-core/src/lib/core.module.ts
+++ b/projects/rero/ng-core/src/lib/core.module.ts
@@ -1,6 +1,6 @@
/*
* RERO angular core
- * Copyright (C) 2020 RERO
+ * Copyright (C) 2020-2023 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -25,8 +25,10 @@ import { NgxSpinnerModule } from 'ngx-spinner';
import { ToastrModule } from 'ngx-toastr';
import { CoreConfigService } from './core-config.service';
import { DialogComponent } from './dialog/dialog.component';
+import { AutofocusDirective } from './directives/autofocus.directive';
import { NgVarDirective } from './directives/ng-var.directive';
import { ErrorComponent } from './error/error.component';
+import { ComponentCanDeactivateGuard } from './guard/component-can-deactivate.guard';
import { MenuWidgetComponent } from './menu/menu-widget/menu-widget.component';
import { CallbackArrayFilterPipe } from './pipe/callback-array-filter.pipe';
import { DefaultPipe } from './pipe/default.pipe';
@@ -43,8 +45,6 @@ import { TranslateLanguagePipe } from './translate/translate-language.pipe';
import { TranslateLoader } from './translate/translate-loader';
import { MenuComponent } from './widget/menu/menu.component';
import { SortListComponent } from './widget/sort-list/sort-list.component';
-import { AutofocusDirective } from './directives/autofocus.directive';
-import { ComponentCanDeactivateGuard } from './guard/component-can-deactivate.guard';
@NgModule({
declarations: [
diff --git a/projects/rero/ng-core/src/lib/interface/icache.ts b/projects/rero/ng-core/src/lib/interface/icache.ts
new file mode 100644
index 00000000..e9c54036
--- /dev/null
+++ b/projects/rero/ng-core/src/lib/interface/icache.ts
@@ -0,0 +1,22 @@
+import { Observable } from "rxjs";
+
+/*
+ * RERO angular core
+ * Copyright (C) 2020-2023 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+export interface ICache {
+ set(key: string, data: any): this;
+ get(key: string): Observable | undefined;
+}
diff --git a/projects/rero/ng-core/src/lib/translate/translate-cache.service.spec.ts b/projects/rero/ng-core/src/lib/translate/translate-cache.service.spec.ts
new file mode 100644
index 00000000..c5958248
--- /dev/null
+++ b/projects/rero/ng-core/src/lib/translate/translate-cache.service.spec.ts
@@ -0,0 +1,81 @@
+/*
+ * RERO angular core
+ * Copyright (C) 2020-2023 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import { TestBed } from '@angular/core/testing';
+import { TranslateCacheService } from './translate-cache.service';
+import { CoreConfigService } from '../core-config.service';
+import { LocalStorageService } from '../service/local-storage.service';
+
+class AppConfigService extends CoreConfigService {
+ constructor() {
+ super();
+ this.appVersion = '1.0.0';
+ }
+}
+
+export const translations = {
+ foo: 'bar'
+};
+
+export const historyLocaleStorage1 = {
+ version: '1.0.0',
+ storageName: ['translations_fr', 'translations_de']
+};
+
+export const historyLocaleStorage11 = {
+ version: '1.0.1',
+ storageName: []
+};
+
+describe('TranslateCacheService', () => {
+ let service: TranslateCacheService;
+ let localeStorage: LocalStorageService;
+ let appConfigService: AppConfigService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ { provide: CoreConfigService, useClass: AppConfigService }
+ ]
+ });
+ service = TestBed.inject(TranslateCacheService);
+ localeStorage = TestBed.inject(LocalStorageService);
+ appConfigService = TestBed.inject(CoreConfigService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ it('Should return the correct cache values', () => {
+ localeStorage.clear();
+ expect(service.get('fr')).toBeUndefined();
+ service.set('fr', translations);
+ expect(service.get('fr')).toEqual(translations);
+ expect(service.get('de')).toBeUndefined();
+ service.set('de', translations);
+ const history = localeStorage.get(service.translateHistoryName);
+ expect(history).toEqual(historyLocaleStorage1);
+ const {storageName} = history;
+ storageName.forEach((name: string) => expect(localeStorage.has(name)).toBeTrue());
+ // Application version change
+ // The translation cache is cleared
+ appConfigService.appVersion = '1.0.1';
+ expect(service.get('fr')).toBeUndefined();
+ expect(localeStorage.get(service.translateHistoryName)).toEqual(historyLocaleStorage11);
+ storageName.forEach((name: string) => expect(localeStorage.has(name)).toBeFalse());
+ });
+});
diff --git a/projects/rero/ng-core/src/lib/translate/translate-cache.service.ts b/projects/rero/ng-core/src/lib/translate/translate-cache.service.ts
new file mode 100644
index 00000000..2ea8d1b3
--- /dev/null
+++ b/projects/rero/ng-core/src/lib/translate/translate-cache.service.ts
@@ -0,0 +1,120 @@
+/*
+ * RERO angular core
+ * Copyright (C) 2020-2023 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import { Injectable } from '@angular/core';
+import { CoreConfigService } from '../core-config.service';
+import { ICache } from '../interface/icache';
+import { LocalStorageService } from '../service/local-storage.service';
+
+export interface ITranslations {
+ version: string;
+ storageName: string[];
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class TranslateCacheService implements ICache {
+
+ /** Name of local storage history */
+ readonly translateHistoryName: string = 'translations';
+
+ /**
+ * Constructor
+ * @param localeStorage LocalStorageService
+ * @param coreConfigService CoreConfigService
+ */
+ constructor(
+ private localeStorage: LocalStorageService,
+ private coreConfigService: CoreConfigService
+ ) {
+ if (this.coreConfigService.appVersion === undefined) {
+ throw new Error('Please set your application version to use the cache.');
+ }
+ }
+
+ /**
+ * Adding content to locale storage
+ * @param key - Locale storage key
+ * @param data - Locale storage contents
+ * @returns TranslateCacheService
+ */
+ set(key: string, data: any): this {
+ const history = this.localeStorage.has(this.translateHistoryName)
+ ? this.localeStorage.get(this.translateHistoryName)
+ : this.initializeTranslationsHistory();
+ const name = this.storageName(key);
+ if (!history.storageName.includes(name)) {
+ history.storageName.push(name);
+ this.localeStorage.set(this.translateHistoryName, history);
+ }
+ this.localeStorage.set(name, data);
+ return this;
+ }
+
+ /**
+ * Retrieving content from locale storage
+ * @param key - Locale storage key
+ * @returns The contents of the locale storage or undefined
+ */
+ get(key: string): any | undefined {
+ if (this.localeStorage.has(this.translateHistoryName)) {
+ const history = this.localeStorage.get(this.translateHistoryName);
+ if (history.version !== this.coreConfigService.appVersion) {
+ this.resetTranslationsHistory(history);
+ } else {
+ const name = this.storageName(key);
+ if (history.storageName.includes(name) && this.localeStorage.has(name)) {
+ return this.localeStorage.get(name);
+ }
+ }
+ }
+ return;
+ }
+
+ /**
+ * Key generation for translations
+ * @param language - current language
+ * @returns Key name for locale storage
+ */
+ private storageName(language: string): string {
+ return `${this.translateHistoryName}_${language}`;
+ }
+
+ /**
+ * Delete languages in locale storage and reset object history.
+ * @param history - history object (ITranslations)
+ */
+ private resetTranslationsHistory(history: ITranslations): void {
+ history.storageName.forEach(name => {
+ if (this.localeStorage.has(name)) {
+ this.localeStorage.remove(name);
+ }
+ });
+ this.localeStorage.set(this.translateHistoryName, this.initializeTranslationsHistory());
+ }
+
+ /**
+ * Generate object history stored in locale storage.
+ * @returns - ITranslations
+ */
+ private initializeTranslationsHistory(): ITranslations {
+ return {
+ version: this.coreConfigService.appVersion,
+ storageName: []
+ }
+ }
+}
diff --git a/projects/rero/ng-core/src/lib/translate/translate-loader.ts b/projects/rero/ng-core/src/lib/translate/translate-loader.ts
index 30ea0615..8cf55ea8 100644
--- a/projects/rero/ng-core/src/lib/translate/translate-loader.ts
+++ b/projects/rero/ng-core/src/lib/translate/translate-loader.ts
@@ -1,6 +1,6 @@
/*
* RERO angular core
- * Copyright (C) 2020 RERO
+ * Copyright (C) 2020-2023 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -19,6 +19,7 @@ import { TranslateLoader as BaseTranslateLoader } from '@ngx-translate/core';
import { forkJoin, Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { CoreConfigService } from '../core-config.service';
+import { ICache } from '../interface/icache';
import de from './i18n/de.json';
import en from './i18n/en.json';
import fr from './i18n/fr.json';
@@ -36,25 +37,32 @@ export class TranslateLoader implements BaseTranslateLoader {
// with angular<9 assets are not available for libraries
// See: https://angular.io/guide/creating-libraries#managing-assets-in-a-library
private _coreTranslations = { de, en, fr, it };
+
/**
* Constructor.
*
* @param _coreConfigService Configuration service.
+ * @param _http HttpClient
+ * @param _cache ICache
*/
constructor(
private _coreConfigService: CoreConfigService,
- private _http: HttpClient
+ private _http: HttpClient,
+ private _cache?: ICache
) { }
/**
* Return observable used by ngx-translate to get translations.
- * @param lang - string, language to rerieve translations from.
+ * @param lang - string, language to retrieve translations from.
*/
getTranslation(lang: string): Observable {
- // Already in cache
- if (this._translations[lang] != null) {
- return of(this._translations[lang]);
+ if (this._cache) {
+ const translateCacheData = this._cache.get(lang);
+ if (translateCacheData) {
+ return translateCacheData;
+ }
}
+
const urls = this._coreConfigService.translationsURLs;
// create the list of http requests
const observers = urls.map(
@@ -82,6 +90,9 @@ export class TranslateLoader implements BaseTranslateLoader {
...trans
}
);
+ if (this._cache) {
+ this._cache.set(lang, this._translations[lang]);
+ }
return this._translations[lang];
})
);
diff --git a/projects/rero/ng-core/src/public-api.ts b/projects/rero/ng-core/src/public-api.ts
index 584354b0..d35e4bf8 100644
--- a/projects/rero/ng-core/src/public-api.ts
+++ b/projects/rero/ng-core/src/public-api.ts
@@ -27,6 +27,7 @@ export * from './lib/directives/ng-var.directive';
export * from './lib/error/error';
export * from './lib/error/error.component';
export * from './lib/guard/component-can-deactivate.guard';
+export * from './lib/interface/icache';
export * from './lib/menu/menu-factory';
export * from './lib/menu/menu-item';
export * from './lib/menu/menu-item-interface';
@@ -76,6 +77,7 @@ export * from './lib/service/logger.service';
export * from './lib/service/title-meta.service';
export * from './lib/text-read-more/text-read-more.component';
export * from './lib/translate/date-translate-pipe';
+export * from './lib/translate/translate-cache.service';
export * from './lib/translate/translate-language.pipe';
export * from './lib/translate/translate-language.service';
export * from './lib/translate/translate-loader';