J4N
J4N

Reputation: 20707

How to localize data coming from the backend with ngx-translate

I'm building an angular app with ngx-translate. It's working nicely. Now I'm starting to have data coming from my backend(firebase), for which I will have translations.

What is the correct way of displaying localized data from a model? I've not yet settled on the data format, so either it will be something like:

{
  'title_fr': 'mon titre',
  'title_en': 'my title',
  'title_de': 'mein Titel',
  'description_fr' : '....'
  //...
}

either something like:

{
   title: {'fr':'mon titre', 'en': 'my title', 'de': 'mein Titel'}
   description: { 'fr': 'ma description', /*...*/}
}

and I'm not sure how to display them:

  1. I will have a lot of ngFor iterating on observable of those items, if I could avoid to having to map every data, it would be great
  2. when the language change, I also need to have the correct translation displayed

What would you recommend me?

Upvotes: 0

Views: 5225

Answers (2)

francisco neto
francisco neto

Reputation: 807

The library @ngx-translate/http-loader allows loading the translations using http.

Bellow is an example of how I use ngx-translate along with http-loader in the applications I develop.

I have made a working stackblitz of the code bellow (available here):

Defining the data structure for the i18n strings

In that example, for each language there is a url where the translation data (json) can be retrieved in the following format:

{
  welcome: "Welcome",
  subscription_msg: "Subscribe now"
}

Creating one webservice for each supported language

Consider one application that supports 3 languages: en, pt and fr.

I usually have one url for each language. Each url returns a json with key - value pairs where the keys are the labels I use in the frontend, and the values are the messages I want to show the users in that language.

In that example, I have create 3 mock urls with the translations for each language as follows:

English - Mock url: https://run.mocky.io/v3/33f736b0-e73a-499e-8a50-01e66041d634

{
  welcome: "Welcome",
  subscription_msg: "Subscribe now"
}

Portuguese - Mock url: https://run.mocky.io/v3/db1e37da-342e-4918-8ce2-bd30aa12fe79

{
  welcome: "Bem-vindo",
  subscription_msg: "Inscreva-se gratuitamente"
}

French - Mock url: https://run.mocky.io/v3/6960c960-ea66-42a0-87f1-f34568ecb740

{
  welcome: "Bienvenue",
  subscription_msg: "Abbonez-vous"
}

To output the subscription_msg in the frontend, I use the ngx-translate syntax like that:

{{ 'subscription_msg' | translate}}

Installing @ngx-translate/http-loader

Assuming ngx-translate is already installed, this is the command for installing http-loader:

npm install @ngx-translate/http-loader --save

Creating your own TranslationLoader

We need to make an implementaion of TranslationLoader interface. It has just one method (getLanguage()) which is responsible for loading the map of i18n messages for the current language.

Bellow is our simple implementation called TranslationHttpLoader. It is invoked when the language is changed:

export class TranslationHttpLoader implements TranslateLoader {
  constructor(private httpClient: HttpClient) {}

  public getTranslation(lang: string): Observable<Object> {
    if (lang == null) {
      lang == "en";
    }

    let urls = {
      en: "https://run.mocky.io/v3/33f736b0-e73a-499e-8a50-01e66041d634",
      pt: "https://run.mocky.io/v3/db1e37da-342e-4918-8ce2-bd30aa12fe79",
      fr: "https://run.mocky.io/v3/6960c960-ea66-42a0-87f1-f34568ecb740"
    };

    let observer = new Observable(observer => {
      this.httpClient.get(urls[lang]).subscribe(
        data => {
          observer.next(data);
          observer.complete();
        }
      );
    });
    return observer;
  }
}

Configuring the translation provider

In the app.module.ts we should define a HttpLoaderFactory:

export function HttpLoaderFactory(httpClient: HttpClient) {
  return new TranslationHttpLoader(httpClient);
}

and associate it with the provided TranslateLoader:

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      }
    })
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

Testing - changing language and showing localized messages

To test

  • we create a simple component that has a <select> with the languages as options and a (change) event handler.
  • to output localized strings, we use the ngx-translate syntax {{ 'key_in_json_file' | translate }}.

app.component.html

<div>
    <label>Language</label>
    <select (change)="changeLanguage($event)">
    <option value="en">English</option>
    <option value="pt">Português</option>
    <option value="fr">Français</option>
  </select>

    <p>
        {{'welcome' | translate}}.
    </p>
    <p>
        {{'subscription_msg' | translate}}
    </p>
</div>

When the (change) event takes place, all we have to do is to call translate.use(lang). Our loader will retrieve the data for the selected language in the url provided and will supply it to ngx-translate:

app.component.ts

export class AppComponent {
  defaultLang: string = "en";

  constructor(private translateService: TranslateService) {}
  ngOnInit() {
    this.translateService.use(this.defaultLang);
  }
  changeLanguage(event) {
    let lang = (event.target as HTMLInputElement).value;
    this.translateService.use(lang);
  }
}

Output preview

Output preview for the 3 languages

Next steps

Some enhacements can be done in the code provided here, for example:

  • Detect browser language on user first access using translateService.getBrowserLang()
  • Store the user current language in localstorage or other persistent storage.
  • Adapt the TranslationHttpLoader so it can work offline with default tokens for each language (especially useful in mobile applications).

The code described above can be seen working here in this stackblitz: https://stackblitz.com/edit/angular-ivy-pwcrvy?file=src/app/translation-http-loader.ts

Upvotes: 2

Vinita
Vinita

Reputation: 115

If you are able to define the data you can get from your backend then probably you can make the data structure as below,

{
    "dataLocalization": [{
                            "locale": "fr",
                            "title": "mon titre",
                            "description":"ma description"
                        },
                        {
                            "locale": "en",
                            "title": "my title",
                            "description":"my description"
                        }]
}

Then you can find your locale by findIndex method in the array dataLocalization and use its title and description values

Upvotes: 0

Related Questions