Jason
Jason

Reputation: 1376

Angular 5 Pipes and dynamic locale

I currently have apps built in angular 4 and would like to upgrade to angular 5. I was reading about how they handle locale in their pipes and it seems like upgrading is no longer an option. From what I can tell they expect you to either 1. manually import a different culture 2. build a separate app for each culture

The problem is that I work for an international company that supports 351 different cultures and we have 15 apps. Is the angular team really saying I have to now build 5265 different applications if I want to keep upgrading as they do? If I went to my boss with that I would probably be thrown out of the room.

In our angularJs apps we just downloaded the $locale service we needed at runtime when the user logged in and set the provider to that. is there nothing like that for Angular? If not I am not sure how an enterprise level app could ever use this language unless the devs were lucky enough to only have to support one culture.

Upvotes: 3

Views: 6047

Answers (3)

Christophe KIFFEL
Christophe KIFFEL

Reputation: 41

i was facing the same problem, and found a workaround.

I decided to publish angular locales data (coming from cldr) in an asset folder. Cldr data come from node_mopdules\@angular\common\locales (what you would have imported in typescript). Just before the bootstrap, using APP_INITIALIZER, i load the currect culture data from assets given a specific locale_id.

The trick is to avoid using the import key word, because dynamic imports are not support in EC2015. To get the culture data from the js file, i wrote this 'dirty' code :

import { Injectable, Inject, LOCALE_ID } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class LoaderService {

  constructor(protected httpClient: HttpClient,
              @Inject(LOCALE_ID) protected locale: string) { }

private async loadAngularCulture(locale) {
      let angularLocaleText = await this.httpClient.get(`assets/angular-locales/${locale}.js`).toPromise();

      // extracting the part of the js code before the data, 
      // and i didn't need the plural so i just replace plural by null.
      const startPos = angularLocaleText.indexOf('export default ');
      angularLocaleText = 'return ' + angularLocaleText.substring(startPos + 15).replace('plural', null);

      // The trick is here : to read cldr data, i use a function
      const f = new Function(angularLocaleText);
      const angularLocale = f();

      // console.log(angularLocale);

      // And now, just registrer the object you just created with the function
      registerLocaleData(angularLocale);
  }

And is my mainModule, using the APP_INITIALIZER :

export function configFactory(intlLoader: SfkIntlLoaderService) {
  return intlLoader.initializer();
}

export function localFunction() {
  // the rule to get you language according. 
  // Here, for demo, i just read it from localstorage
  return localStorage.getItem('LANGUGAGE');
}

@NgModule({
 declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  )
  ],
  providers: [
    LoaderService,
    {
      provide: LOCALE_ID,
      deps: [],
      useFactory: localFunction
    },
    {
      provide: APP_INITIALIZER,
      useFactory: configFactory,
      deps: [LoaderService],
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Hope this will help !

Upvotes: 0

Jason
Jason

Reputation: 1376

After communicating on github about this issue an idea given that have started using is to create a service that I call in APP_INITIALIZER which you can see below. Because of the dynamic string at build time the angular-cli will create a chunk for every culture and then load the correct one at run time once I supply the culture string I received from a service call.

setLocale(localeId: string): Promise<any> {
    this.culture = localeId;
    return new Promise((resolve, reject) => {
      System.import(`@angular/common/locales/${localeId}.js`).then(
        module => {
          registerLocaleData(module.default);
          resolve();
        },
        () => {
          System.import(`@angular/common/locales/${localeId.split('-')[0]}.js`).then(module => {
            registerLocaleData(module.default);
            resolve();
          }, reject);
        }
      );
    });
  }

Upvotes: 2

Robin Dijkhof
Robin Dijkhof

Reputation: 19288

I had the same problem with angular4 I wanted to use AOT with multple languages. This is what I did:

Simple wrapper around a pipe to set the locale.

@Pipe({name: 'datepipe', pure: true})
export class MyDatePipe extends DatePipe implements PipeTransform {
  constructor(private win: WindowRef) {
    super(win.ln);
  }

  transform(value: any, pattern?: string): string | null {
    return super.transform(value, pattern);
  }
}

Service for the locale:

function _window(): any {
  // return the global native browser window object
  return window;
}

@Injectable()
export class WindowRef {
  get nativeWindow(): any {
    return _window();
  }

  //default locale
  public ln = 'en';


  constructor() {
    try {
       if (!isNullOrUndefined(this.nativeWindow.navigator.language) && this.nativeWindow.navigator.language !== '') {
        this.ln = this.nativeWindow.navigator.language;
      }
    }finally {}
  }
}

This worked for angular4 but with angular5 I had to import the supported locales in my main.ts

import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';

registerLocaleData(localeFr);

Upvotes: 0

Related Questions