Dynamic localization with angular

Recently we noticed that some parts of our UI don't get updated, when the language is changed. We are using dynamic translations with ngx-translate library so we can change the language inside the application at runtime and don't need to compile an application for each language, as it's intended by Angulars basic localization system. I did some research and was surprised, that it's still very complicated to achieve dynamic localization with Angular. I want to summarize the required steps here how to achieve dynamic translations, so you don't have to search through all the different sources.

LocaleId

For pipes like DatePipe, CurrencyPipe etc. we need to implement a hack, so that these pipes are using the current locale. The idea is to implement the LocaleId as an object that extends the String class. By implementing the toString() method, we can use the current language code from the TranslateService of ngx-translate.

export class LocaleId extends String { constructor(private translateService: TranslateService) { super(); } override toString(): string { return this.translateService.currentLang; } override valueOf(): string { return this.toString(); } }

Now you can register a provider in your app.modules.ts

@NgModule({ ... providers: [ { provide: LOCALE_ID, useClass: LocaleId, deps: [TranslateService], } ... }) export class AppModule { }

Be careful when injecting and using the LocaleId in your components. You will have to make sure that .toString() is called either explicitly or implicitly.

Components

Some components will not work with the custom LocaleId implementation. E. g. when you use Angular Material's DatePicker it won't change everything:

Datepicker

On the picture you can see what happened when we changed from French to English. The month names are not updated, only the selected month is in english.

To prevent this issue you will have to use the DateAdapter.setLocale() function. It will change the language that DataAdapter and therefor the DatePicker is using. It's important to resolve the DataAdapter in the right module. There might be different instances, and if you use the wrong one it will not affect anything. In my example I resolve it in the SharedModule, because there I also registered the DateAdapter:

@NgModule({ ... providers: [ ... { provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS] }, ... }) export class SharedModule { constructor(readonly translateService: TranslateService, readonly dateAdapter: DateAdapter<any>) { translateService.onLangChange.subscribe(() => { dateAdapter.setLocale(translateService.currentLang); }); } }

In the constructor of the module we are listening for language changes and passing it to the DateAdapter.

Reload

Usually you will have a place in your application where you can change the language. E. g. you will have a dropdown with all available languages. Here you have to make sure, that you will reload the component so the pipes will be recreated and use the new locale provided by the LocaleId provider. This will also ensure that all the content you load will be created in the new language. E.g. if you use an API to fetch data in a specific language, this problem will be solved as well.

protected changeLanguage(languageCode: string): void { this.translateService.use(languageCode) .subscribe(() => { return this.router.navigateByUrl('/reload', { skipLocationChange: true }).then(() => this.router.navigate([this.router.url])); }); }

Summary

With these three measures you should be able to create a dynamically translatable angular application without reloading the whole page. I hope this will help you with this rather effortful topic. Unfortunately Angular doesn't provide any solution without any hacks required yet.