Core: safely use translate.instant()

Created on 21 Apr 2017  ·  21Comments  ·  Source: ngx-translate/core

is theire a way to load translations when application starts so that we can safely usetranslate.instant()

Most helpful comment

All you need to do (as it was partially mentioned) is to preload translations using .use() method. The problem is the method is async (we should wait for the result). The best way to force the application to wait for "something" to finish before it shows up is using APP_INITIALIZER function in your AppModule.

You have to add following provider to your AppModule's providers section:

providers: [
  {
    provide: APP_INITIALIZER,
    useFactory: appInitializerFactory,
    deps: [TranslateService, Injector],
    multi: true
  }
]

And define factory function appInitializerFactory upper in the same file:

import { Injector, APP_INITIALIZER } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { LOCATION_INITIALIZED } from '@angular/common';

export function appInitializerFactory(translate: TranslateService, injector: Injector) {
  return () => new Promise<any>((resolve: any) => {
    const locationInitialized = injector.get(LOCATION_INITIALIZED, Promise.resolve(null));
    locationInitialized.then(() => {
      const langToSet = 'en-GB'
      translate.setDefaultLang('en-US');
      translate.use(langToSet).subscribe(() => {
        console.info(`Successfully initialized '${langToSet}' language.'`);
      }, err => {
        console.error(`Problem with '${langToSet}' language initialization.'`);
      }, () => {
        resolve(null);
      });
    });
  });
}

Now application will wait for translations initialization before it shows up for user.

All 21 comments

Load the translations via .use() in the ngOnInit of your AppComponent. In some cases (slow server response) you might still end up with not translate values. That's why we recommend using the pipe as much as possible. As the docs describe, that's just how instant works. Nothing we can do about it I'm afraid.

I am using it in the component.ts file. the need that makes me think of instant() instead of get() is the fact that it is much code cleaner since I don't need to embedd blocks to subscription

@SamVerschueren I want the same thing :)
Something like in Angular 1: https://angular-translate.github.io/docs/#/api/pascalprecht.translate.$translate#methods_instant

Me too. This is too troubles right now. Especially if you need to get multiple words. There has to be a way to load the all the translations and them use translate.instant everywhere.

All you need to do (as it was partially mentioned) is to preload translations using .use() method. The problem is the method is async (we should wait for the result). The best way to force the application to wait for "something" to finish before it shows up is using APP_INITIALIZER function in your AppModule.

You have to add following provider to your AppModule's providers section:

providers: [
  {
    provide: APP_INITIALIZER,
    useFactory: appInitializerFactory,
    deps: [TranslateService, Injector],
    multi: true
  }
]

And define factory function appInitializerFactory upper in the same file:

import { Injector, APP_INITIALIZER } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { LOCATION_INITIALIZED } from '@angular/common';

export function appInitializerFactory(translate: TranslateService, injector: Injector) {
  return () => new Promise<any>((resolve: any) => {
    const locationInitialized = injector.get(LOCATION_INITIALIZED, Promise.resolve(null));
    locationInitialized.then(() => {
      const langToSet = 'en-GB'
      translate.setDefaultLang('en-US');
      translate.use(langToSet).subscribe(() => {
        console.info(`Successfully initialized '${langToSet}' language.'`);
      }, err => {
        console.error(`Problem with '${langToSet}' language initialization.'`);
      }, () => {
        resolve(null);
      });
    });
  });
}

Now application will wait for translations initialization before it shows up for user.

Yes sorry guys, that's how instant works. If you want you want "clean" code but don't mind with the fact that it's async or not, you could try using async/await since it works in typescript 2.3 now

I'm interested in having several languages loaded before using instant(). Does it work if I call use() on each of the languages and then, once everything has been loaded, use use() again to choose the current language for instant()?

What happens when you have a multilanguage application and you have an option to switch between languages? How do you preload a new language then (on button click), without showing the translate token instead of actual translation (Angular 1 cloak, if you remember)?

Hi,
I don't know if this is the best solution but worked for me:
string "init" is any key you want

translate.get('init').subscribe((text:string) => {
//use instant call here
});

Hi,
I don't know if this is the best solution but worked for me:
string "init" is any key you want

translate.get('init').subscribe((text:string) => {
//use instant call here
});

Yeah, and if you need to set multiple variables, you could subscribe just to one of them, and use anyone you need:

this.translateService.get('_back').subscribe(value => { this.config.set('ios', 'backButtonText', value); this.config.set('monthNames', [ this.translateService.instant('_january'), this.translateService.instant('_february'), this.translateService.instant('_march'), this.translateService.instant('_april'), this.translateService.instant('_may'), this.translateService.instant('_june'), this.translateService.instant('_july'), this.translateService.instant('_august'), this.translateService.instant('_september'), this.translateService.instant('_october'), this.translateService.instant('_november'), this.translateService.instant('_december') ]) });;

this.translate.get(this.pageTitle).subscribe((text:string) => {
this.translate.onLangChange.subscribe((event: LangChangeEvent) => {
title.setTitle(this.translate.instant(text))
});
});

But I am not able to return value inside this:
translate.get('key').subscribe((text:string) => {
return text;
});
This is not returning any text instead returns an observable even after returning response from inside subscriber, Does any one know how to tackle this situation?

Mythrim, In my answer I used get('key') just to wait until translation service starts before accessing instant, your calls must be inside the subscribe callback, or you can just call a callback inside it because subscribe are asyncronous.

is theire a way to load translations when application starts so that we can safely usetranslate.instant()

If you want to be sure that the translations are loaded, consider converting the Observable to a Promise and then await that before calling instant():
await this.translate.use('en').toPromise<void>();

You could also do something like this:

async doSomething(): void {

    await this.translateService.get("").toPromise();
    // It loads the translate data now, and you can now safely use instant

    const hello: string = this.translateService.instant("HELLO");
    console.log(hello);

}

Obviously, you could also just always use await/get in this method.

@xStarman Thanks for your comment, It worked for me.
@andreas-aeschlimann can we use this approach in the ngOnInit() method. Since await requires async method, it will not work on ngOnInit()? What do you think?

It wouldn't work directly in ngOnInit I guess, but you could probably wrap it in a setTimeout() function, then it runs async.

@kneefer 's answer was absolute gold. I went ahead and converted it to an async function that's a little more succinct codewise, but performs the same trick. We are going to be loading in the user's language from a settings table later on, so we will be using multiple awaits inside the async function - if you wanted to have multiple things initializing asynchronously, I would recommend returning a Promise.all() instead.

Also - Angular gets mad if you try to set the factory as an async function, hence how I'm immediately returning an anonymous async function inside the factory. If anyone has a better example of this, I would love to learn from it!

import { Injector, APP_INITIALIZER } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { LOCATION_INITIALIZED } from '@angular/common';

export function appInitializerFactory(translate: TranslateService, injector: Injector) {
  return async () => {
    await injector.get(LOCATION_INITIALIZED, Promise.resolve(null));

    translate.setDefaultLang('en-US');
    try {
      await translate.use('client').toPromise();
    } catch (err) { }
  }
}

_Edit - Added trycatch and reverted function name for consistency with Keefer's original example._

All you need to do (as it was partially mentioned) is to preload translations using .use() method. The problem is the method is async (we should wait for the result). The best way to force the application to wait for "something" to finish before it shows up is using APP_INITIALIZER function in your AppModule.

You have to add following provider to your AppModule's providers section:

providers: [
  {
    provide: APP_INITIALIZER,
    useFactory: appInitializerFactory,
    deps: [TranslateService, Injector],
    multi: true
  }
]

And define factory function appInitializerFactory upper in the same file:

import { Injector, APP_INITIALIZER } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { LOCATION_INITIALIZED } from '@angular/common';

export function appInitializerFactory(translate: TranslateService, injector: Injector) {
  return () => new Promise<any>((resolve: any) => {
    const locationInitialized = injector.get(LOCATION_INITIALIZED, Promise.resolve(null));
    locationInitialized.then(() => {
      const langToSet = 'en-GB'
      translate.setDefaultLang('en-US');
      translate.use(langToSet).subscribe(() => {
        console.info(`Successfully initialized '${langToSet}' language.'`);
      }, err => {
        console.error(`Problem with '${langToSet}' language initialization.'`);
      }, () => {
        resolve(null);
      });
    });
  });
}

Now application will wait for translations initialization before it shows up for user.

All you need to do (as it was partially mentioned) is to preload translations using .use() method. The problem is the method is async (we should wait for the result). The best way to force the application to wait for "something" to finish before it shows up is using APP_INITIALIZER function in your AppModule.

You have to add following provider to your AppModule's providers section:

providers: [
  {
    provide: APP_INITIALIZER,
    useFactory: appInitializerFactory,
    deps: [TranslateService, Injector],
    multi: true
  }
]

And define factory function appInitializerFactory upper in the same file:

import { Injector, APP_INITIALIZER } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { LOCATION_INITIALIZED } from '@angular/common';

export function appInitializerFactory(translate: TranslateService, injector: Injector) {
  return () => new Promise<any>((resolve: any) => {
    const locationInitialized = injector.get(LOCATION_INITIALIZED, Promise.resolve(null));
    locationInitialized.then(() => {
      const langToSet = 'en-GB'
      translate.setDefaultLang('en-US');
      translate.use(langToSet).subscribe(() => {
        console.info(`Successfully initialized '${langToSet}' language.'`);
      }, err => {
        console.error(`Problem with '${langToSet}' language initialization.'`);
      }, () => {
        resolve(null);
      });
    });
  });
}

Now application will wait for translations initialization before it shows up for user.

amazing!

All you need to do (as it was partially mentioned) is to preload translations using .use() method. The problem is the method is async (we should wait for the result). The best way to force the application to wait for "something" to finish before it shows up is using APP_INITIALIZER function in your AppModule.

You have to add following provider to your AppModule's providers section:

providers: [
  {
    provide: APP_INITIALIZER,
    useFactory: appInitializerFactory,
    deps: [TranslateService, Injector],
    multi: true
  }
]

And define factory function appInitializerFactory upper in the same file:

import { Injector, APP_INITIALIZER } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { LOCATION_INITIALIZED } from '@angular/common';

export function appInitializerFactory(translate: TranslateService, injector: Injector) {
  return () => new Promise<any>((resolve: any) => {
    const locationInitialized = injector.get(LOCATION_INITIALIZED, Promise.resolve(null));
    locationInitialized.then(() => {
      const langToSet = 'en-GB'
      translate.setDefaultLang('en-US');
      translate.use(langToSet).subscribe(() => {
        console.info(`Successfully initialized '${langToSet}' language.'`);
      }, err => {
        console.error(`Problem with '${langToSet}' language initialization.'`);
      }, () => {
        resolve(null);
      });
    });
  });
}

Now application will wait for translations initialization before it shows up for user.

Gold! Thanks so much for this

As an addition to @kneefer's answer;

Since RxJS has a built-in toPromise() function that converts observables to promises, you can shorten the appInitializerFactory code as the following:

function appInitializerFactory(translateService: TranslateService) {
  return () => {
    translate.setDefaultLang('en-US');
    return translateService.use('en-US').toPromise();
  };
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

crebuh picture crebuh  ·  3Comments

pndewit picture pndewit  ·  3Comments

guysan picture guysan  ·  4Comments

alkemist picture alkemist  ·  3Comments

egornoveo picture egornoveo  ·  4Comments