core icon indicating copy to clipboard operation
core copied to clipboard

translate pipe doesn't work for dynamic translation files when we use 'use' function (change the current language)

Open AnnaKozlova opened this issue 1 year ago • 1 comments

Hello. Thank you for the amazing library!

Current behavior

Our project has dynamic translation files loading for lazy-loading modules. This fine-tuning is obtained by implementing custom AppMissingTranslationHandler, which requests missing translations from custom TranslateLoader implementation.

Problem is reproduced when we use the translate pipe in our templates and if we change the language => those template parts where we use translate pipe do not get a new translation (they obtain that ones which were for default language)

Important note: If we use setDefaultLang instead of use problems do not reproduce

Steps to reproduce:

  1. Language by default - for example ‘en';
  2. Open a web page where there are several lazy-loading modules;
  3. MissingTranslationHandler helps us load additional translations for the default language;
  4. We have some translation string in template: 'SOME_TRANSLATION' | translate
  5. Change the language through the menu - for example to 'es';
  6. Inside the library a language change takes place and as a result: currentLang has a new value and is equal to the selected language ('es'), we have loaded the base translation file (not lazy-loading) and get the onLangChange event =>
  7. Inside pipe: translate.pipe.ts we got new event from onLangChange and call updateValue();

Issues:

First issue - getParsedResult returns default value, not new one for currentLang (for the complex case of retrieving a translation from the MissingTranslationHandler) Second issue - in pipe: onTranslation function executed twice Third issue - in pipe - updateValue has invalid condition: isObservable(res.subscribe)

Expected behavior

When we switch translations using the use function from the TranslateService we get a new translations for our templates where we are using translate pipe, even if the translation is taken from lazy-loading files.

How do you think that we should fix this?

First issue - getParsedResult returns default value, not for currentLang (for the complex case of retrieving a translation from the MissingTranslationHandler)

translate.pipe.ts

 updateValue(key: string, interpolateParams?: Object, translations?: any): void {
    ...
    if (translations) {
      let res = this.translate.getParsedResult(translations, key, interpolateParams);
      if (isObservable(res.subscribe)) {
        res.subscribe(onTranslation);
      } else {
        onTranslation(res);
      }
    }
    this.translate.get(key, interpolateParams).subscribe(onTranslation);
  }

here we have translations equal the base file with translations (no lazy-loading files) so we go into the if(translations) block and get res by getParsedResult execution translate.service.ts

  public getParsedResult(translations: any, key: any, interpolateParams?: Object): any {
    let res: string | Observable<string> | undefined;
    ... 
    
    // until the translations for the new language are not loaded res variable equals 'undefined'
    if (translations) {
      res = this.parser.interpolate(this.parser.getValue(translations, key), interpolateParams);
    }
    
    // (!!!) main problem - all conditions are fulfilled and
    // res variable gets a value equal to the translation from the default language, although it should remain 'undefined' in order to load the needed translations by the missingTranslationHandler in the next step
    // useDefaultLang - has a place to use, but it should be possible to use it for the missingTranslationHandler if we do not get a correct translation there
    if (typeof res === "undefined" && this.defaultLang != null && this.defaultLang !== this.currentLang && this.useDefaultLang) {
      res = this.parser.interpolate(this.parser.getValue(this.translations[this.defaultLang], key), interpolateParams);
    }

    if (typeof res === "undefined") {
      let params: MissingTranslationHandlerParams = {key, translateService: this};
      if (typeof interpolateParams !== 'undefined') {
        params.interpolateParams = interpolateParams;
      }
      res = this.missingTranslationHandler.handle(params);
    }

    return typeof res !== "undefined" ? res : key;
  }

see comments above in snippet

Issue: res variable gets an invalid value equal to the value from the default translations, although we already have a different current language

Possible solution: Perhaps We should be able to avoid useDefaultLang check before missingTranslationHandler execution here and add the ability to handle this case after missingTranslationHandler fails


Second issue translate.pipe.ts

 updateValue(key: string, interpolateParams?: Object, translations?: any): void {
    ...
    if (translations) {
      let res = this.translate.getParsedResult(translations, key, interpolateParams);
      if (isObservable(res.subscribe)) {
        res.subscribe(onTranslation);
      } else {
        onTranslation(res);
      }
    }
    this.translate.get(key, interpolateParams).subscribe(onTranslation);
  }

Everything in the translation block has the same logic as in this.translate.get(key, interpolateParams).subscribe(onTranslation); => we have the onTranslation function executed twice (because translations === this.translations[this.currentLang] from get function)

Third issue the same pipe and the same snippet as above: if (isObservable(res.subscribe)) {

I think there is a flaw here. Here should be not isObservable(res.subscribe), only isObservable(res) according to the same logic as in this.translate.get(...) function; getParsedResult - should return Observable or string

Environment


ngx-translate version: 14.0.0
Angular version: 15.0.2

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

AnnaKozlova avatar May 15 '23 17:05 AnnaKozlova

I also have the same issue with even if I use setDefaultLang, I figure it out and I don't know if it's a good practice to do it, what I did was to use setDefaultLang but in the app.component and shared service with the app.component and use a var to set the Language. Any update on this issue please let me know.

EJNunez1311 avatar May 31 '23 16:05 EJNunez1311