Loutocký
Loutocký

Reputation: 832

Language localization of Angular 2 app

I need to localize my angular 2 app to language defined by localization of browser (sending POST request to database - give me translations for this language which is specified in header).

What I have implemented now:

I have some shared variable, for example Dictionary in dictionary.ts file. See that I have set it to English by default.

export let Dictionary = {
  1 : "engishVar1",
  2 : "englishVar2"
}

In my AppComponent, which is the entry point of my app (because I am doing Bootstrap(AppComponent), I have something like this in constructor:

this._appService.translationRequest(aptxIdsToTranslatePost)
     .subscribe(
          data => this.handleResponse(data),
          err => console.log("[App component] Error " + JSON.stringify(err)),
          () => console.log("[App component] GET translations finished: " + JSON.stringify(this.responseOnTranslationRequest))
          );

aptxIdsToTranslatePost is JSON body, which I tell to my backend server, give me IDs 1 and 2 and the language is taken from http POST header. Now server returns me the IDs again with language.

In handleResponse method, I am doing this:

this.responseOnTranslationRequest.forEach(element => {
      Dictionary[element.id] = element.text;
});

As I saw above, my Dictionary is something like key value store. What is the problem? If the POST is delayed, my App is not localizated, because the view is rendered before the Dictionary is set. If I reload the page, after that the view is fully localizated, because translation is cached and I can not wait on POST response. Now in my components, I am creating localization in constructor, for example:

constructor() {
        this.welcomeTitle = Dictionary[1];
        this.welcomeDescription = Dictionary[2];
        this.welcomeBuyText = Dictionary[3];
        this.welcomeBuyTicket = Dictionary[4];
    }

I thinking about three other solutions, but I think, that neither of them are good.

First is, that I will transform my localization from constructor of component to something later of angular 2 life cycle, typically ngAfterViewInit or something like this. Here again, there is no guarantee that the answer to POST returns, maybe yes, maybe no.

Next is, that I will create POST synchronously, not async, but this is bad idea because my app may freeze (some problems with translation from POST).

The last is (which I today implemented, but it not works), that I will create Dictionary class (now JSON variable) and on every component I will create instance of it. Then on AppComponent I will subscribe the response (handleResponse) as I have in example above and in handleResponse I will call some method of type EventEmitter(). So on my others component, I will subscribe it (in constructor of component), typically:

//in AppComponent after handleResponse
this._sharedService.dictionaryTranslationEvent.emit(this.dictionary);

//subscription in my component on new instance of Dictionary
this._sharedService.dictionaryTranslationEvent.subscribe(
       (dictionary) => {
           this.dictionary = dictionary;
           this.welcomeTitle = this.dictionary.getDictionary[1];
           this.welcomeDescription = this.dictionary.getDictionary[2];
           this.welcomeBuyText = this.dictionary.getDictionary[3];
           this.welcomeBuyTicket = this.dictionary.getDictionary[4];
 });

To be sure, I attach dictionary.ts now:

export class Dictionary {

    private dict: any;

    constructor() {
        this.dict = {
            1: "englishVar1",
            2: "englishVar2",
        }
    }

    /* GET */
    get getDictionary(): any {
        return this.dict;
    }

    /* SET */
    set setDictionary(dictionary: any) {
        this.dict = dictionary;
    }
}

So what is the best idea to create localization of angular 2 app? Thanks.

Upvotes: 0

Views: 3129

Answers (2)

Ravi Teja
Ravi Teja

Reputation: 192

// Best way to change language Globally is to use pipes and send the language parameter as an argument.
// This would automatically change the Language across the components where the language pipe is utilized.
// The following example can be used to supple multiple language at a time and can be used to change Language dynamically on a single click

// for example: **language.pipe.ts**

`import { Pipe, PipeTransform, OnInit, OnChanges } from '@angular/core';
import { LanguageService } from '../services/language.service';

@Pipe({
    name: 'language'
})
export class LanguagePipe implements PipeTransform {

    constructor(
        public lang: LanguageService
    ) { }

    transform(value: string, args?: any): any {
        return this.lang.getLang(value);
    }
}
`

// **language.service.ts**

`import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Injectable()
export class LanguageService {

    selectedLanguage = 'ch';
    segmentsNames: any = {};
    constantSegmentNames: any = {};
    language = {
        undefined: { 'en': '', 'ch': '' },
        null: { 'en': '', 'ch': '' },
        '': { 'en': '', 'ch': '' },
        'hello': { 'en': 'Hello', 'ch': '你好' },
        'world': { 'en': 'World', 'ch': '世界' }
    };

    constructor(private _http: HttpClient) { }

    getLang(value: string, args?: any): any {
        if (this.language[value]) {
            return this.language[value][this.selectedLanguage];
        }
        return value;
    }

    /**
     * @function that is used to toggle the selected language among 'en' and 'ch'
     */
    changeLanguage() {
        if (this.selectedLanguage === 'en') {
            this.selectedLanguage = 'ch';
        } else {
            this.selectedLanguage = 'en';
        }
    }
}
`

// **Use Language Pipe in HTML AS**

`<strong>{{'hello' | language:lang.selectedLanguage}}{{'world' | language:lang.selectedLanguage}}</strong>`

PS: Don't forget to import the pipe & service in all the components where you want to use this functionality

**Conslusion: ** you can write your own logic to fetch the appropriate data from DB based on user selected language and use it in the service above mentioned

Upvotes: 0

Picci
Picci

Reputation: 17762

Facing a similar problem I have chosen the following strategy.

1) Create a DictionaryService class that implements the Dictionary role; this class holds a JSON object which stores one entry per each string that needs to be translated and a method that provides the translated string for a give stringId and LanguageId, i.e. something like

        @Injectable()
    export class LabelDictionaryService { 
        public dictionary: {};

        constructor(private _session: Session) {
            this.dictionary = this.getDictionary();
        }

    getDictionary() {
       return {
        stringId1: {en: 'String1InEnglish', it: 'String1InItalian', sp: 'String1InSpanish', ....},
        stringId2: {en: 'String2InEnglish', it: 'String2InItalian', sp: 'String2InSpanish', ....}
        ....
        }
    }

     getTranslation(inStringId: string, inLanguageId?: string) {
            let langId;
            if (inLanguageId) {
               langId = inLanguageId; 
            } else {
                langId = getDefualtLanguageId();  //getDefualtLanguageId() needs to be implemented in the DictionaryService class and retrieves the default language from the environment
            }
            let translation;
            if (this.dictionary[inLabelId]) {
                translation = labelTexts[langId];
            }
            if (translation == null) {
               text = inLabelId;
               console.error(inStringId + ' has no defined text for language ' + langId); 
            }
            return translation;
        }

    }

2) Use Dependency Injection to move the dictionary around the application so that components which may need the Dictionary service can access it

This solution does not address directly the load of the Dictionary content from an external service, but I think you can accommodate this requirement changing slightly the above code sample tp implement the load of the Dictionary content in the ngOnInit() method of your AppComponent. If you need also to implement a mechanism that prevents the user from proceeding with your App before the Dictionary is loaded (which makes sense to me) you need to figure out the most elegant way to disable the UI until the dictionary is loaded (maybe with a timeout escape).

I hope this helps

Upvotes: 3

Related Questions