Dynamically change locale for DatePipe in Angular 2

Viewed 32086

I'm making an Angular project where the user has the ability to switch languages. Is it possible to make the locale dynamic?

I have seen that you can add it in the NgModule but i'm guessing it's not dynamic when i put it there? Or can i change it somehow through a service or something?

6 Answers

Have your Service like

import { Injectable } from '@angular/core';

@Injectable()
export class LocaleService {

  //Chosse Locale From This Link
  //https://github.com/angular/angular/tree/master/packages/common/locales
  constructor() { }

  private _locale: string;

  set locale(value: string) {
    this._locale = value;
  }
  get locale(): string {
    return this._locale || 'en-US';
  }

  public registerCulture(culture: string) {
    debugger;
    if (!culture) {
      return;
    }
    switch (culture) {
      case 'en-uk': {
        this._locale = 'en';
        console.log('Application Culture Set to English');
        break;
      }
      case 'zh-hk': {
        this._locale = 'zh-Hant';
        console.log('Application Culture Set to Traditional Chinese');
        break;
      }
      case 'zh-cn': {
        this._locale = 'zh-Hans';
        console.log('Application Culture Set to Simplified Chinese');
        break;
      }
      default: {
        this._locale = 'en';
        console.log('Application Culture Set to English');
        break;
      }
    }
  }
}

And in App.module.ts

first import localization that you need , say

import localeEN from '@angular/common/locales/en';
import localezhHant from '@angular/common/locales/zh-Hant';
import localezhHans from '@angular/common/locales/zh-Hans';

Than under providers section

{
  provide: LOCALE_ID,
  deps: [LocaleService],
  useFactory: (LocaleService: { locale: string; }) => LocaleService.locale
}

At the end

registerLocaleData(localeEN);
registerLocaleData(localezhHant);
registerLocaleData(localezhHans);

Hope it will help someone

if you want to change locale dynamically , inject LocaleService in your desired component and use registerCulture method , pass your required culture into this

Great answers already provided here! However, it did not completely satisfy my scenario working in a hybrid AngularJs/Angular environment. Here's my solution that includes aspects from the previous answers with an alternate approach for importing of locales using dynamic import making bundling more efficient via lazy loading.

Summary:

Key points (also included in previous answers)

  • The LOCALE_ID is configured using the localization service as setup in the app.module.ts provider, via useFactory option
  • The registerLocaleData function registers the locale data globally

Extended implementation points (not included in previous answers)

  • The registerLocaleData function requires the import of the locale which in previous answers is included hard-coded and results in bundling of each locale:

    `import localeEN from '@angular/common/locales/en';
    

    We can use dynamic loading (available as of TypeScript 2.4) to load a given locale on demand, making our code and bundling more efficient. The import returns a Promise and we can then register our locale:

    import(`@angular/common/locales/${localeId}.js`)
    .then(lang => registerLocaleData(lang.default));
    
  • To improve bundling even more, we can include some magic comments to limit to only the locale we support:

    /* webpackInclude: /(en|fr|es)\.js$/ */

  • To take advantage of dynamic import we must configure our module type to esnext, see tsconfig.json

  • You can read about dynamic import and webpack magic comments here: https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import

Code:

app.module.ts

@NgModule({
    declarations: [ /* ... */ ],
    imports: [ /* ... */ ],
    providers: [
        { provide: LOCALE_ID, deps: [LocalizationService], useFactory: (localizationService) => localizationService.getLocale() }
    ]
})

localization.service.ts

export class LocalizationService {

    /**
     * Gets the current locale (abbreviation)
    */
    getLocale() : string {
        return localStorage.getItem("current-locale");
    }

    /** 
     * Set the locale across the app
     * @param {string} abbr Abbreviation of the locale to set
     */
    setLocale(abbr : string) : Promise<any> {
        return new Promise(resolve => {
            return this.http.get<Translation[]>(url)
                .subscribe((response) => {
                    //code ommited to load response data into translation cache

                    if (localStorage) {
                        localStorage.setItem("current-locale", abbr);
                    }
                    
                    moment.locale(abbr);
                    this.loadLocale(abbr).then;

                    resolve();
                },
                (error) => {
                    resolve;
                });
        });
    }

    /**
     * Imports the Angular data for the given locale
     * @param {string} localeId The locale to load data
     */
    private loadLocale(localeId : string) : Promise<any> {
        localeId = localeId.substr(0, 2);

        //limit loading to the languages defined in webpack comment below
        return import(
            /* webpackInclude: /(en|fr|es)\.js$/ */
            `@angular/common/locales/${localeId}.js`
        ).then(lang =>
            registerLocaleData(lang.default)
        );
    }
}

tsconfig.json

    "compilerOptions": {
        /* ... */ 
        "module": "esnext"
        /* ... */ 
    }

This solution works for me:

loadLocales(localeId) {
    import(
    /* webpackExclude: /\.d\.ts$/ */
    /* webpackMode: "lazy-once" */
    /* webpackChunkName: "i18n-extra" */
    
    `@/../node_modules/@angular/common/locales/${localeId}`)
        .then(module => {registerLocaleData(module.default)});
}

For calling the function in a component:

this.loadLocales('de') // fr
            
// necessary import
import {registerLocaleData} from '@angular/common';
import {  APP_INITIALIZER } from '@angular/core';
            
            
//Add provider      
providers: [
    {
        provide: APP_INITIALIZER,
        useFactory: TestFunc,
        multi: true,
        deps: [CommonService]
    }
]
        
culture = service.culture; //'de-DE'
        
// for template reference
{{ showDateStart | date: "EEE, MMMM d, y, HH:mm": '' : culture}} //culture

If you define it for your application, you have to add it into app.module.ts.

Related