ngx-translate/http-loader

Multiple HTTP requests

SoapyMan opened this issue ยท 6 comments

Current behavior

Currently TranslateHttpLoader produces HTTP request every time getTranslation is called.

Expected behavior

TranslateHttpLoader should store state of language file loading and only one HTTP request is made.

How do you think that we should fix this?

This is how I implemented this (proof of concept, it performs only ONE request, everything works and tokens are translated):

	private errorState = false;
	private waiter = new Subject<Object>();
	private requestedLang: string;

    getTranslation(lang: string): Observable<Object>
	{
		if(this.errorState)
			return of({});

		if(this.requestedLang != null && this.requestedLang == lang)
			return this.waiter.asObservable();

		let languagPath = `assets/locale/${lang}.json`;

		this.requestedLang = lang;

		this.http.get(languagPath).pipe(
				catchError(error => {
					console.warn("can't load language file", languagPath, error);
					this.errorState = true;
					return of({});
				})
			).subscribe(result => {
				//console.log("translation", lang,"loaded!");
				this.waiter.next(result);
			});

		return this.waiter.asObservable();
	}

Minimal reproduction of the problem with instructions

Even standard ngx-translate template calls getTranslation at least two times.
My app got approximately 140 (app with multiple pages and modules) and lots of annoying alerts were displayed in case when translations were not found. If success, other requests fetched (304), but fetch still has delay.

Environment


ngx-translate version: 11.0.1
Angular version: 7.1.4

Browser:
- [x] Chrome (desktop) version XX

 
For Tooling issues:
- Node version: 10.15
- Platform:  Windows

I'm experiencing the same issue.
The json file with i18n translations is requested multiple times. It seems to be requested in an infinite loop
It only occurs with non default language files (= languages that have not been set with setDefaultLang())

See https://cl.ly/f5b0f57313a8

@emeryowa this is indeed not well behaviour. In fact now i'm not using http-loader due to this issue. This TranslateHttpLoader implementation itself is too trivial to be added as package to your project IMO. Instead, it's better to implement your own TranslateLoader with the getTranslation function i've posted in the issue.

@SoapyMan I have implemented a custom loader, identical to the function you posted. I am getting the same error though = the call to the translations file is requested multiple times. Do you have any tips?

@emeryowa @SoapyMan

I've encountered the same error. Fortunately, the error message with your loader @SoapyMan is much better than the one in the ngx-translate loader.

My Error shows:

     my-translate-loader.ts:31 can't load language file assets/i18n/en.json TypeError: Cannot read property 'length' of undefined
    at HttpHeaders.push../node_modules/@angular/common/fesm5/http.js.HttpHeaders.applyUpdate (http.js:244)
    at http.js:215
    at Array.forEach (<anonymous>)
    at HttpHeaders.push../node_modules/@angular/common/fesm5/http.js.HttpHeaders.init (http.js:215)
    at HttpHeaders.push../node_modules/@angular/common/fesm5/http.js.HttpHeaders.forEach (http.js:280)
    at Observable._subscribe (http.js:1596)
    at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable._trySubscribe (Observable.js:43)
    at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable.subscribe (Observable.js:29)
    at subscribeToResult (subscribeToResult.js:13)
    at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber._innerSub (mergeMap.js:74)

I created the following loader which does simple caching:

import {HttpClient} from "@angular/common/http";
import {TranslateLoader} from "@ngx-translate/core";
import {Observable} from "rxjs";
import {shareReplay} from "rxjs/operators";

export class CachedHttpTranslationLoader implements TranslateLoader {
  
    cache$: Observable<Object> = null;
    cachedLang: string = null;
    
    constructor(private http: HttpClient, public prefix: string = "/assets/i18n/", public suffix: string = ".json") {}
  
    /**
     * Gets the translations from the server
     */
    public getTranslation(lang: string): Observable<Object> {
      if (!this.cache$ || this.cachedLang !== lang) {
        this.cache$ = this.http.get(`${this.prefix}${lang}${this.suffix}`).pipe(shareReplay(1));
        this.cachedLang = lang;
      }
      return this.cache$;
    }
  }
mwmw7 commented

@larsvliet thank you
It works very well

 TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useClass: CachedHttpTranslationLoader,
        deps: [HttpClient]
      },
      defaultLanguage: 'ko' 
    }),