-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adding CachingTranslateLoader (#96)
* feat: adding CachingTranslateLoader * fix: linter issues * fix: linter issues * fix: createTranslateLoader fix * fix: add defaultIfEmpty, fix unit tests --------- Co-authored-by: kim.tran <[email protected]>
- Loading branch information
1 parent
b4ca427
commit 52fc2c6
Showing
10 changed files
with
269 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
libs/portal-integration-angular/src/lib/core/utils/caching-translate-loader.utils.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { HttpClient } from '@angular/common/http' | ||
import { HttpClientTestingModule } from '@angular/common/http/testing' | ||
import { TestBed } from '@angular/core/testing' | ||
import { of } from 'rxjs' | ||
import { TranslationCacheService } from '../../services/translation-cache.service' | ||
import { CachingTranslateLoader } from './caching-translate-loader.utils' | ||
|
||
describe('CachingTranslateLoader', () => { | ||
const origAddEventListener = window.addEventListener | ||
const origPostMessage = window.postMessage | ||
|
||
let listeners: any[] = [] | ||
window.addEventListener = (_type: any, listener: any) => { | ||
listeners.push(listener) | ||
} | ||
|
||
window.removeEventListener = (_type: any, listener: any) => { | ||
listeners = listeners.filter((l) => l !== listener) | ||
} | ||
|
||
window.postMessage = (m: any) => { | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
listeners.forEach((l) => l({ data: m, stopImmediatePropagation: () => {}, stopPropagation: () => {} })) | ||
} | ||
|
||
afterAll(() => { | ||
window.addEventListener = origAddEventListener | ||
window.postMessage = origPostMessage | ||
}) | ||
|
||
let http: HttpClient | ||
let translationCache: TranslationCacheService | ||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
declarations: [], | ||
imports: [HttpClientTestingModule], | ||
}).compileComponents() | ||
http = TestBed.inject(HttpClient) | ||
translationCache = TestBed.inject(TranslationCacheService) | ||
}) | ||
|
||
it('should get translations', (done) => { | ||
const translateLoader = new CachingTranslateLoader(translationCache, http, `./assets/i18n/`, '.json') | ||
const translation = { testKey: 'my translation' } | ||
http.get = jest.fn().mockReturnValue(of(translation)) | ||
translateLoader.getTranslation('en').subscribe((t) => { | ||
expect(t).toEqual(translation) | ||
done() | ||
}) | ||
}) | ||
|
||
it('should load translations only once', (done) => { | ||
let httpCalls = 0 | ||
const responses = [] | ||
const translation = { testKey: 'my translation' } | ||
http.get = jest.fn().mockImplementation(() => { | ||
httpCalls++ | ||
return of(translation) | ||
}) | ||
|
||
const translateLoader = new CachingTranslateLoader(translationCache, http, `./assets/i18n/`, '.json') | ||
translateLoader.getTranslation('en').subscribe((t) => { | ||
responses.push(t) | ||
expect(t).toEqual(translation) | ||
expect(httpCalls).toEqual(1) | ||
if (responses.length == 2) { | ||
done() | ||
} | ||
}) | ||
const translateLoader2 = new CachingTranslateLoader(translationCache, http, `./assets/i18n/`, '.json') | ||
translateLoader2.getTranslation('en').subscribe((t) => { | ||
responses.push(t) | ||
expect(t).toEqual(translation) | ||
expect(httpCalls).toEqual(1) | ||
if (responses.length == 2) { | ||
done() | ||
} | ||
}) | ||
}) | ||
}) |
34 changes: 34 additions & 0 deletions
34
libs/portal-integration-angular/src/lib/core/utils/caching-translate-loader.utils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { HttpClient } from '@angular/common/http' | ||
import { TranslateLoader } from '@ngx-translate/core' | ||
import { TranslateHttpLoader } from '@ngx-translate/http-loader' | ||
import { filter, first, mergeMap, Observable, of } from 'rxjs' | ||
import { TranslationCacheService } from '../../services/translation-cache.service' | ||
|
||
export class CachingTranslateLoader implements TranslateLoader { | ||
private translateLoader = new TranslateHttpLoader(this.http, this.prefix, this.suffix) | ||
|
||
constructor( | ||
private translationCache: TranslationCacheService, | ||
private http: HttpClient, | ||
private prefix?: string, | ||
private suffix?: string | ||
) {} | ||
|
||
getTranslation(lang: string): Observable<any> { | ||
const url = `${this.prefix}${lang}${this.suffix}` | ||
|
||
return this.translationCache.getTranslationFile(url).pipe( | ||
filter((tf) => tf !== null), | ||
first(), | ||
mergeMap((tf) => { | ||
if (tf) { | ||
return of(tf) | ||
} | ||
return this.translationCache.updateTranslationFile(url, null).pipe( | ||
mergeMap(() => this.translateLoader.getTranslation(lang)), | ||
mergeMap((t) => this.translationCache.updateTranslationFile(url, t)) | ||
) | ||
}) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
libs/portal-integration-angular/src/lib/services/translation-cache.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Injectable, OnDestroy } from '@angular/core' | ||
import { SyncableTopic } from '@onecx/accelerator' | ||
import { Observable, concat, first, from, map, mergeMap, of } from 'rxjs' | ||
|
||
// This topic is defined here and not in integration-interface, because | ||
// it is not used as framework independent integration but for improving | ||
// angular specific things | ||
class TranslationCacheTopic extends SyncableTopic<Record<string, any>> { | ||
constructor() { | ||
super('translationCache', 1) | ||
} | ||
} | ||
|
||
@Injectable({ providedIn: 'root' }) | ||
export class TranslationCacheService implements OnDestroy { | ||
translationCache$ = new TranslationCacheTopic() | ||
|
||
ngOnDestroy(): void { | ||
this.translationCache$.destroy() | ||
} | ||
|
||
getTranslationFile(url: string): Observable<any> { | ||
return this.getCache().pipe(map((t) => t[url])) | ||
} | ||
|
||
updateTranslationFile(url: string, translations: any): Observable<any> { | ||
return this.getCache().pipe( | ||
first(), | ||
mergeMap((t) => { | ||
return from(this.translationCache$.publish({ ...t, [url]: translations })).pipe(map(() => translations)) | ||
}) | ||
) | ||
} | ||
|
||
private getCache(): Observable<Record<string, any>> { | ||
if (this.translationCache$.getValue()) { | ||
return this.translationCache$.asObservable() | ||
} | ||
return concat(of({}), this.translationCache$.asObservable()) | ||
} | ||
} |
Oops, something went wrong.