ngx-translate / http-loader

A loader for ngx-translate that loads translations with http calls
MIT License
191 stars 69 forks source link

Multiple HTTP requests #68

Open SoapyMan opened 4 years ago

SoapyMan commented 4 years ago

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

emeryowa commented 4 years ago

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

SoapyMan commented 4 years ago

@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.

emeryowa commented 4 years ago

@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?

bjesuiter commented 4 years ago

@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)
larsvliet commented 4 years ago

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 3 years ago

@larsvliet thank you It works very well

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