Search code examples
angulartranslationngx-translate

Angular - NgxTranslate - Missing Translation Handler on condition


I have created a missing translation handler that looks up the translation in an external api, if it cannot find it then returns the original key.

export class MyMissingTranslationHandler implements MissingTranslationHandler {
        constructor(public injector: Injector) {}
    
        handle(params: MissingTranslationHandlerParams): string {
            const result = ApiCallToFindTheKey(params.key)
            return result ? result : params.key;
        }
    }

I also have some json translations files (for static app translations) that are being requested also via an api call.

export class MultiTranslateHttpLoader implements TranslateLoader {
    constructor(private http: HttpClient) {}

    public getTranslation(lang: string): Observable<any> {
        const resources: string[] = [
            '_global.json',
            ...
        ];

        const requests = resources.map(resource => {
            const path = 'i18n/' + lang + '/' + resource;
            return this.http.get(path).pipe(
                catchError(res => {
                    console.error(res.message);
                    return of({});
                })
            );
        });
        return forkJoin(requests).pipe(map(response => merge.all(response)));
    }
}

The translate pipe is configured to use these json files and if it cannot find the translations it falls into my missing translation handler.

export function HttpLoaderFactory(http: HttpClient): TranslateLoader {
        return new MultiTranslateHttpLoader(http);
    }

    @NgModule({
        imports: [
            TranslateModule.forRoot({
                defaultLanguage: 'fr',
                loader: {
                    provide: TranslateLoader,
                    useFactory: HttpLoaderFactory,
                    deps: [HttpClient]
                },
                missingTranslationHandler: {
                    provide: MissingTranslationHandler,
                    useClass: MyMissingTranslationHandler
                }
            }),
            ...
        ]

Sometimes, the json files take a little bit too long and the | translate pipe thinks that translation does not exists and goes look in the missing translation handler instead of waiting for json file to be loaded.

So the result of the translation is what the missing translation handler returns instead of the real translation. How can I do to make translate pipe wait for the json files to be loaded and then translate? I thought about using some kind of flag but I don't know if there is a more elegant solution.


Solution

  • As suggested by @KanishkAnand I use the APP_INITIALIZER.

    /**
     * Application won't load until the translations have been downloaded
     */
    export function initializeTranslations(translationLoadedService: TranslationLoadedService): () => Promise<void> {
        return async (): Promise<void> => {
            return new Promise(resolve => {
                translationLoadedService.loaded$().subscribe(() => resolve());
            });
        };
    }
    
    @NgModule({
        imports: [
            TranslateModule.forRoot({
                defaultLanguage: 'fr',
                loader: {
                    provide: TranslateLoader,
                    useFactory: HttpLoaderFactory,
                    deps: [HttpClient, TranslationLoadedService]
                },
                missingTranslationHandler: {
                    provide: MissingTranslationHandler,
                    useClass: MyMissingTranslationHandler
                }
            }),
            ...
        ],
        exports: [TranslateModule],
        providers: [
            ...,
            { provide: APP_INITIALIZER, useFactory: initializeTranslations, deps: [TranslationLoadedService], multi: true }
        ],
        entryComponents: ... ,
        declarations: ...
    })
    

    I created an intermediate service that will resolve the promise after loading all the translations.

    @Injectable({
        providedIn: 'root'
    })
    export class TranslationLoadedService {
        private translationLoaded: Subject<void> = new Subject<void>();
    
        loaded$(): Subject<void> {
            return this.translationLoaded;
        }
    }
    

    So that once I get all translations downloaded I notify the subscribers.

    export class MultiTranslateHttpLoader implements TranslateLoader {
        constructor(private http: HttpClient, private translationLoaded: TranslationLoadedService) {}
    
        public getTranslation(lang: string): Observable<any> {
            const resources: string[] = [
                '_global.json',
                ...
            ];
    
            const requests = resources.map(resource => {
                const path = 'i18n/' + lang + '/' + resource;
                return this.http.get(path).pipe(
                    catchError(res => {
                        console.error('Something went wrong for the following translation file:', path);
                        console.error(res.message);
                        return of({});
                    })
                );
            });
            return forkJoin(requests).pipe(
                map(response => {
                    **this.translationLoaded.loaded$().next();**
                    return merge.all(response);
                })
            );
        }
    }