Software localization

Angular Tutorial on Localizing with ngx-translate

Learn all about Angular i18n with ngx-translate to change the language of your global application at runtime without reloading the whole app.
Software localization blog category featured image | Phrase

When it comes to Angular localization, one of the most popular open-source i18n libraries, ngx-translate, lets you define translations for your app and switch between them dynamically. You can either use a service, directive, or pipe to handle the translated content.

To make you familiar with all of them, this tutorial will walk you through localizing in Angular with ngx-translate step by step. For demonstration purposes, we will create a sample feedback form for Phrase, the most reliable localization solution, and launch our demo app in two different languages.

You can access the demo app via Google Firebase to understand how ngx-translate works with an Angular app in a production environment. To get the source code for the demo app, make sure you stop by at GitHub.

🗒 Note » Make sure you have an Angular dev environment set up on your machine. Should this not be the case, please refer to the Angular setup guide.

Why use ngx-translate instead of Angular i18n?

The Angular framework has a robust built-in i18n library. However, the ngx-translate library has some shiny advantages over the built-in one:

  • The ngx-translate library allows us to change the language of the application at runtime without reloading the whole app. However, Angular allows us to use only one language at a time. If you want to use a different language, then you need to reload the application with a new set of translations.
  • The ngx-translate library allows us to use JSON files for translation by default. We can also create our own loader to support any format we want.
  • The ngx-translate library has a wide range of APIs, which allows us to manipulate the translation data during runtime.

Configuring ngx-translate for an Angular app

Navigate to the directory where you want to create the new project. Open the command prompt, and run the command shown below to create a new Angular app named ngx-translate-i18n.

ng new ngx-translate-i18n --routing=false --style=scss

Run the following command to install the ngx-translate/core library in your app:

npm install @ngx-translate/core

We will need to install a loader that will help us load the translations from files using HttpClient. Run the command as follows:

npm install @ngx-translate/http-loader

We will add a separate module for ngx-translate. Run the following command to create a new module in your app.

ng g m translate

Add the following code to the translate.module.ts file:

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

import { CommonModule } from '@angular/common';

import { TranslateHttpLoader } from '@ngx-translate/http-loader';

import { HttpClientModule, HttpClient } from '@angular/common/http';

import { TranslateModule, TranslateLoader } from '@ngx-translate/core';

export function HttpLoaderFactory(http: HttpClient) {

  return new TranslateHttpLoader(http,

    './assets/i18n/',

    '.json');

}

@NgModule({

  declarations: [],

  imports: [

    CommonModule,

    HttpClientModule,

    TranslateModule.forRoot({

      defaultLanguage: 'en',

      loader: {

        provide: TranslateLoader,

        useFactory: HttpLoaderFactory,

        deps: [HttpClient],

      },

    }),

  ],

  exports: [TranslateModule],

})

export class NgxTranslateModule { }

We have configured the TranslateLoader for our application. The TranslateHttpLoader class is used to define the path and file extension for the translation files. We have imported the TranslateModule, and the default language for the app is set to English.

ngx-bootstrap is an open-source library that provides an easy way to integrate Bootstrap components into an Angular app. To add ngx-bootstrap to your application, run the following command in the root directory of the project.

ng add ngx-bootstrap

Font Awesome is an open-source library that provides a wide array of icons that can be used to style our app. To include it in your app, add the following code lines to the <head> section of the index.html file:

<link

  rel="stylesheet"

  type="text/css"

  href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"

/>

Updating the AppModule

Add the following lines of code in the src/app/app.module.ts file to import the required modules.

import { BsDropdownModule } from 'ngx-bootstrap/dropdown';

import { NgxTranslateModule } from './translate/translate.module';

import { FormsModule } from '@angular/forms';

@NgModule({

    ...

  imports: [

    ...

    BsDropdownModule.forRoot(),

    NgxTranslateModule,

    FormsModule,

  ],

    ...

})

We will use the dropdown module of ngx-bootstrap to display a language selection dropdown in the navbar of our app. For the creation of the feedback form, we will use template-driven forms. That is why we import the FormsModule as well as the custom NgxTranslateModule.

Creating a nav-bar component

Run the following command to create the nav-bar component for the application.

ng g c nav-bar --module app

Open src\app\nav-bar\nav-bar.component.ts and replace the existing code with the following one:

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

import { TranslateService } from '@ngx-translate/core';

@Component({

  selector: 'app-nav-bar',

  templateUrl: './nav-bar.component.html',

  styleUrls: ['./nav-bar.component.scss'],

})

export class NavBarComponent {

  siteLanguage = 'English';

  languageList = [

    { code: 'en', label: 'English' },

    { code: 'de', label: 'Deutsch' },

  ];

  constructor(private translate: TranslateService) { }

  changeSiteLanguage(localeCode: string): void {

    const selectedLanguage = this.languageList

      .find((language) => language.code === localeCode)

      ?.label.toString();

    if (selectedLanguage) {

      this.siteLanguage = selectedLanguage;

      this.translate.use(localeCode);

    }

    const currentLanguage = this.translate.currentLang;

    console.log('currentLanguage', currentLanguage);

  }

}

Here, we have defined a list of languages and their standard locale codes.

The changeSiteLanguage function will invoke the use function of the TranslateService to set the active language of the app to the language selected in the nav-bar menu. We will then set the siteLanguage by fetching the language name from the list of languages corresponding to the currently active language.

Open src\app\nav-bar\nav-bar.component.html and replace what you see in there with the following code:

<nav class="navbar navbar-dark navbar-expand-lg">

  <a class="navbar-brand">Phrase</a>

  <span class="spacer"></span>

  <div class="btn-group" dropdown>

    <button

      id="button-animated"

      dropdownToggle

      type="button"

      class="btn btn-link dropdown-toggle"

      aria-controls="dropdown-animated"

    >

      <i class="fa fa-globe" aria-hidden="true"></i>

       {{ siteLanguage }}

      <span class="caret"></span>

    </button>

    <ul

      id="dropdown-animated"

      *dropdownMenu

      class="dropdown-menu dropdown-menu-right"

      role="menu"

      aria-labelledby="button-animated"

    >

      <ng-container *ngFor="let language of languageList">

        <li role="menuitem">

          <a

          class="dropdown-item"

          (click)="changeSiteLanguage(language.code)">

            {{ language.label }}

          </a>

        </li>

      </ng-container>

    </ul>

  </div>

</nav>

We have created a nav-bar that has a drop-down menu with two options for selecting the language of our app during runtime. When we click on the menu item, it will invoke the changeSiteLanguage function, which will then change the app language dynamically, and the content will be served in the selected language. We will also display the siteLanguage in the nav-bar so it gets updated as we change the language of our app.

Creating a model

We will use the template-driven form for creating the feedback form for our application. Therefore, we need to create a model. Set up a new folder called models inside the src/app folder. Create a file called feedback.ts inside the Models folder and insert the following code:

export class Feedback {

  name: string;

  gender: string;

  rating: string;

  comment: string;

  constructor() {

    this.name = '';

    this.gender = '';

    this.rating = '';

    this.comment = '';

  }

}

Creating translation files

Before creating the feedback form, we will add the translation file for both languages—English and German. Add a folder called i18n inside the src/assets folder; add a file called en.json to this folder and enter the following code:

{

  "title": "Service feedback for {{company}}",

  "name": {

    "label": "Name",

    "placeholder": "Enter Name"

  },

  "gender": {

    "label": "Gender",

    "select": "Select gender",

    "options": {

      "male": "Male",

      "female": "Female",

      "other": "Other"

    }

  },

  "comment": {

    "label": "Comment",

    "placeholder": "Enter Comment"

  },

  "rating": {

    "label": "Rate our customer service: ",

    "options": {

      "excellent": "Excellent",

      "good": "Good",

      "bad": "Bad"

    }

  },

  "submit": {

    "label": "Submit"

  },

  "requiredErrorMessage": "This is a required field.",

  "successfulSubmitMessage": "Thanks for your valuable feedback!!!\nThe feedback has been submitted successfully.",

  "copyright": "All rights reserved. {{currentYear}} {{company}}"

}

We have now created the key-value pairs for all our translatable content. The feedback form will have four fields: "Name," "Gender," "Comment," and "Rating".

The title key will accept a parameter called "company", which will help us add the value dynamically. The copyright key will accept currentYear and "company" as the parameter.

Similarly, create the translation keys for German in the de.json file.

{

  "title": "Service-Feedback fĂĽr {{company}}",

  "name": {

    "label": "Namen",

    "placeholder": "Name eingeben"

  },

  "gender": {

    "label": "Geschlecht",

    "select": "Wähle Geschlecht",

    "options": {

      "male": "Männlich",

      "female": "Weiblich",

      "other": "Andere"

    }

  },

  "comment": {

    "label": "Kommentar",

    "placeholder": "Kommentar eingeben"

  },

  "rating": {

    "label": "Bewerten Sie unseren Kundenservice:",

    "options": {

      "excellent": "Ausgezeichnet",

      "good": "Gute",

      "bad": "Schlecht"

    }

  },

  "submit": {

    "label": "Senden"

  },

  "requiredErrorMessage": "Dies ist ein Pflichtfeld.",

  "successfulSubmitMessage": "Vielen Dank fĂĽr Ihr wertvolles Feedback!!!\nDas Feedback wurde erfolgreich ĂĽbermittelt.",

  "copyright": "Alle Rechte vorbehalten. {{currentYear}} {{company}}"

}

Using the translate pipe

The label for all the fields in the form is translated with the help of the translate pipe.

<label>{{ "name.label" | translate }}</label>

...

<label>{{ "gender.label" | translate }}</label>

We can also pass the parameter to the translation key as follows:

<h3>{{ "title" | translate: titleParam }}</h3>

Using the translate directive

We have used the translate directive to fetch the translation values for the required error message for all the fields. The translation key can be supplied as the content of the HTML element.

<span

  translate

  class="text-danger"

  *ngIf="

    (name.touched || feedbackForm.submitted) &&

    name.errors?.required

  "

>

  requiredErrorMessage

</span>

To display the copyright information, we have used the translate directive and supplied the required parameter using the translateParams input property.

<p>

  &copy;

  <small

    translate

    [translateParams]="copyrightInfoParam"

    class="text-muted"

    >copyright

  </small>

  <br />

</p>

Another way of using the translate directive is to use the translation key as the attribute value.

<p>

  &copy;

  <small

    [translate]="'copyright'"

    [translateParams]="copyrightInfoParam"

    class="text-muted"

  >

  </small>

</p>

Dynamic translation keys

For the dropdown of the gender field in the form, we are creating the translation keys dynamically using a ngFor directive. We have used the translate pipe to fetch the values for gender.

<select

  class="form-control"

  data-val="true"

  [(ngModel)]="customerFeedback.gender"

  name="gender"

  #gender="ngModel"

  required

>

  <option value="">{{ "gender.select" | translate }}</option>

  <ng-container *ngFor="let gender of genderList">

    <option value="{{ 'gender.options.' + gender | translate }}">

      {{ "gender.options." + gender | translate }}

    </option>

  </ng-container>

</select>

Fetching a translation key in TypeScript files

The get() function of  TranslateService is used to fetch the translated value of a key in a TypeScript file.

saveFeedback() {

  this.translate

    .get('successfulSubmitMessage')

    .subscribe((successMessage: string) => {

      alert(successMessage);

    });

  console.table(this.customerFeedback);

}

We are fetching the value of the successfulSubmitMessage key to display a success alert message. We are mocking the save functionality of the form by logging the output in the browser.

Miscellaneous example component

We will create a new component called MiscellaneousExampleComponent to cover the following features of ngx-translate:

  • Pluralization
  • Using the translate pipe with other pipes
  • Using raw HTML tags within the translation

Configuring the app to use pluralization with ngx-translate

We first install the ngx-translate-messageformat-compiler package. It is a compiler for ngx-translate that uses messageformat.js to compile translations using ICU syntax for handling pluralization and gender.

Run the following command:

npm install ngx-translate-messageformat-compiler messageformat

Currently, the ngx-translate-messageformat-compiler package has a dependency on messageformat, which is a deprecated package. The author is already working on adapting to the new dependency.

Refer to the GitHub issue - https://github.com/lephyrus/ngx-translate-messageformat-compiler/issues/74

The next step is to update the translate.module.ts file as shown below:

import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler';

import { TranslateCompiler } from '@ngx-translate/core';

...

@NgModule({

  imports: [

    ...

    TranslateModule.forRoot({

            ...

      compiler: {

        provide: TranslateCompiler,

        useClass: TranslateMessageFormatCompiler

      }

    })

  ],

    ...

})

This will allow ngx-translate to use the messageformat compiler for rendering the translated messages.

Updating the translation file

Since the ngx-translate-messageformat-compiler package is using the ICU syntax, we need to update the interpolation syntax in the translation files.

Earlier, we used double curly braces for interpolation:

"title": "Service feedback for {{company}}"

However, the ICU parser is using only a single bracket for interpolation. That is why we need to update the translation as follows:

"title": "Service feedback for {company}"

We will also add the translations for pluralization to the en.json file:

{

  "title": "Service feedback for {company}",

  "name": {

    "label": "Name",

    "placeholder": "Enter Name"

  },

  "gender": {

    "label": "Gender",

    "select": "Select gender",

    "options": {

      "male": "Male",

      "female": "Female",

      "other": "Other"

    }

  },

  "comment": {

    "label": "Comment",

    "placeholder": "Enter Comment"

  },

  "rating": {

    "label": "Rate our customer service: ",

    "options": {

      "excellent": "Excellent",

      "good": "Good",

      "bad": "Bad"

    }

  },

  "submit": {

    "label": "Submit"

  },

  "requiredErrorMessage": "This is a required field.",

  "successfulSubmitMessage": "Thanks for your valuable feedback!!!\nThe feedback has been submitted successfully.",

  "copyright": "All rights reserved. {currentYear} {company}",

  "pluralization": {

    "items": "You have selected {count, plural, =0{nothing} one{one} other{multiple}} {count, plural, =0{} one{item} other{items}}.",

    "gender": "{gender, select, male{He is a} female{She is a} other{They are}} {gender}."

  }

}

Similarly, we will update the de.json file as shown below:

{

  "title": "Service-Feedback fĂĽr {company}",

  "name": {

    "label": "Namen",

    "placeholder": "Name eingeben"

  },

  "gender": {

    "label": "Geschlecht",

    "select": "Wähle Geschlecht",

    "options": {

      "male": "Männlich",

      "female": "Weiblich",

      "other": "Andere"

    }

  },

  "comment": {

    "label": "Kommentar",

    "placeholder": "Kommentar eingeben"

  },

  "rating": {

    "label": "Bewerten Sie unseren Kundenservice:",

    "options": {

      "excellent": "Ausgezeichnet",

      "good": "Gute",

      "bad": "Schlecht"

    }

  },

  "submit": {

    "label": "Senden"

  },

  "requiredErrorMessage": "Dies ist ein Pflichtfeld.",

  "successfulSubmitMessage": "Vielen Dank fĂĽr Ihr wertvolles Feedback!!!\nDas Feedback wurde erfolgreich ĂĽbermittelt.",

  "copyright": "Alle Rechte vorbehalten. {currentYear} {company}",

  "pluralization": {

    "items": "Du hast ausgewählt {count, plural, =0{nichts} one{eins} other{mehrere}} {count, plural, =0{} one{Artikel} other{Produkte}}.",

    "gender": "{gender, select, male{er ist ein} female{Sie ist ein} other{Sie sind }} {gender}."

  }

}

To learn more about the ICU syntax, please refer to the ICU Documentation.

🗒 Note » The key name for pluralization can have any string value. Here, for simplicity, we use pluralization as the key name.

Adding pluralization

Add the following code to the miscellaneous-example.component.html file:

<h3>Pluralization</h3>

<br />

<h5>Select Items</h5>

<div class="row">

  <div class="col-md-4">

    <select [(ngModel)]="itemQuantity" class="form-control" data-val="true">

      <option value="0">0</option>

      <option value="1">1</option>

      <option value="2">2</option>

    </select>

  </div>

  <div class="col-md-4">

    <strong translate [translateParams]="{ count: itemQuantity }"

      >pluralization.items</strong

    >

  </div>

</div>

<br />

<h5>Select Gender</h5>

<div class="row">

  <div class="col-md-4">

    <select [(ngModel)]="selectedGender" class="form-control" data-val="true">

      <option value="male">Male</option>

      <option value="female">Female</option>

      <option value="others">Others</option>

    </select>

  </div>

  <div class="col-md-4">

    <strong>{{

      "pluralization.gender" | translate: { gender: selectedGender }

    }}</strong>

  </div>

</div>

We have added a drop-down list to select the number of items. We will display the translated text based on the quantity chosen. We are using the translate directive to pluralize the selected items.

Similarly, we will display the pluralized gender data based on the selection from the drop-down list. Here, we used the translate pipe and passed the selectedGender as the parameter.

Using the translate pipe with built-in Angular pipes

Add the following translations to the en.json file:

"CASESPECIFICKEY": "THE TRANSLATION KEY IS DEFINED AS {case}.",

"casespecifickey": "the translation key is defined as {case}.",

"Casespecifickey": "The Translation Key Is Defined As {case}.",

We will update the de.json file as well:

"CASESPECIFICKEY": "DER ĂśBERSETZUNGSSCHLĂśSSEL IST ALS {case} DEFINIERT.",

"casespecifickey": "der ĂśbersetzungsschlĂĽssel ist als {case} definiert.",

"Casespecifickey": "Der ĂśbersetzungsschlĂĽssel ist als {case} definiert."

We can cascade the Angular built-in pipes with the translate pipe. As shown in the example below, we are using the built-in pipes uppercase, lowercase, and title case to generate the translation key.

<p>{{ caseSpecificKey | uppercase | translate: { case: "UPPERCASE" } }}</p>

<p>{{ caseSpecificKey | lowercase | translate: { case: "lowercase" } }}</p>

<p>{{ caseSpecificKey | titlecase | translate: { case: "Titlecase" } }}</p>

Using raw HTML tags within a translation

Add the following translations to the en.json file:

"aboutPhrase": "<h1>What is Phrase?</h1><p>Phrase is an all-in-one platform for scalable software localization and translation management. Be it web or mobile apps, Phrase enables you to translate any kind of software.</p>",

We will also update the de.json file with the corresponding translations:

"aboutPhrase": "<h1>Was ist Phrase?</h1><p>Phrase ist eine All-in-One-Plattform für skalierbare Softwarelokalisierung und Übersetzungsmanagement. Sei es Web- oder mobile Apps, Phrase ermöglicht es Ihnen, jede Art von Software zu übersetzen.</p>",

The ngx-translate library allows us to use raw HTML tags within our translation. We can use the innerHTML attribute with the translate pipe on any HTML element.

<div [innerHTML]="'aboutPhrase' | translate"></div>

🗒 Note » Using innerHTML makes the website vulnerable to cross-site scripting (XSS) attacks. Please read the Security considerations for details.

Exploring TranslateService

Let us explore some useful properties provided by TranslateService.

We can use the currentLang property to fetch the locale code of the currently active language.

const currentLanguage = this.translate.currentLang;

We can use the onLangChange EventEmitter to "listen" to the language change events:

this.translate.onLangChange

  .subscribe((event: LangChangeEvent) => {

    console.log('onLangChange', event);

  });

The LangChangeEvent is an interface having the following two properties:

  • lang—a string denoting the locale code of the currently active language
  • translations—an object containing the key-value translation pairs.

We can use the onTranslationChange EventEmitter to "listen" to the translation change events:

this.translate.onTranslationChange

  .subscribe((event: TranslationChangeEvent) => {

    console.log('onTranslationChange', event);

  });

The TranslationChangeEvent provides us with two properties—lang and translations—similar to the LangChangeEvent interface.

The onDefaultLangChange is an EventEmitter that is fired when the default language of the application is changed.

this.translate.onDefaultLangChange

  .subscribe((event: DefaultLangChangeEvent) => {

    console.log('onDefaultLangChange', event);

  });

The DefaultLangChangeEvent provides us with two properties—lang and translations—similar to the LangChangeEvent and TranslationChangeEvent interfaces.

Creating a Feedback Component

Run the following command to create the feedback component:

ng g c feedback --module app

Open src\app\feedback\feedback.component.ts and replace the existing code with the following one:

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

import { TranslateService } from '@ngx-translate/core';

import { Feedback } from '../models/feedback';

@Component({

  selector: 'app-feedback',

  templateUrl: './feedback.component.html',

  styleUrls: ['./feedback.component.scss'],

})

export class FeedbackComponent {

  companyName = 'Phrase';

  genderList = ['male', 'female', 'other'];

  customerFeedback = new Feedback();

  copyrightInfoParam: any;

  titleParam: any;

  constructor(private translate: TranslateService) {

    this.titleParam = { company: this.companyName };

    this.copyrightInfoParam = {

      currentYear: new Date().getFullYear(),

      company: this.companyName,

    };

  }

  saveFeedback() {

    this.translate

      .get('successfulSubmitMessage')

      .subscribe((successMessage: string) => {

        alert(successMessage);

      });

    console.table(this.customerFeedback);

  }

}

The constructor is used to initialize the titleParam and the copyrightInfoParam properties. We will use both in the template file while fetching the translation values.

We will create an object of the Feedback class type, which will bind to the form. The saveFeedback function will be invoked as soon as the form submission is successfully submitted.

Open src\app\feedback\feedback.component.html and add the form as displayed below. You can refer to GitHub for the complete source code.

<div class="card">

  <div class="card-header">

    <h3>{{ "title" | translate: titleParam }}</h3>

  </div>

  <div class="card-body">

    <form

      #feedbackForm="ngForm"

      (ngSubmit)="feedbackForm.form.valid && saveFeedback()"

      novalidate

    >

      <div class="form-group">

        <label>{{ "name.label" | translate }}</label>

        <input

          type="text"

          class="form-control"

          [placeholder]="'name.placeholder' | translate"

          [(ngModel)]="customerFeedback.name"

          name="name"

          #name="ngModel"

          required

        />

        <span

          translate

          class="text-danger"

          *ngIf="

            (name.touched || feedbackForm.submitted) && name.errors?.required

          "

        >

          requiredErrorMessage

        </span>

      </div>

      <div class="form-group">

        <label>{{ "gender.label" | translate }}</label>

        <select

          class="form-control"

          data-val="true"

          [(ngModel)]="customerFeedback.gender"

          name="gender"

          #gender="ngModel"

          required

        >

          <option value="">{{ "gender.select" | translate }}</option>

          <ng-container *ngFor="let gender of genderList">

            <option value="{{ 'gender.options.' + gender | translate }}">

              {{ "gender.options." + gender | translate }}

            </option>

          </ng-container>

        </select>

        <span

          translate

          class="text-danger"

          *ngIf="

            (gender.touched || feedbackForm.submitted) &&

            gender.errors?.required

          "

        >

          requiredErrorMessage

        </span>

      </div>

      <div class="form-group">

        <label>{{ "comment.label" | translate }}</label>

        <textarea

          type="text"

          class="form-control"

          [placeholder]="'comment.placeholder' | translate"

          [(ngModel)]="customerFeedback.comment"

          name="comment"

          #comment="ngModel"

          required

        ></textarea>

        <span

          translate

          class="text-danger"

          *ngIf="

            (comment.touched || feedbackForm.submitted) &&

            comment.errors?.required

          "

        >

          requiredErrorMessage

        </span>

      </div>

      <div class="form-group">

        <label>{{ "rating.label" | translate }}</label>

        <div class="">

          <div class="custom-control custom-radio custom-control-inline">

            <input

              id="excellent"

              type="radio"

              class="custom-control-input"

              value="excellent"

              name="rating"

              [(ngModel)]="customerFeedback.rating"

              #rating="ngModel"

              required

            />

            <label class="custom-control-label" for="excellent"

              >{{ "rating.options.excellent" | translate }}

            </label>

          </div>

          <div class="custom-control custom-radio custom-control-inline">

            <input

              id="good"

              type="radio"

              class="custom-control-input"

              value="good"

              name="rating"

              [(ngModel)]="customerFeedback.rating"

              #rating="ngModel"

              required

            />

            <label class="custom-control-label" for="good">{{

              "rating.options.good" | translate

            }}</label>

          </div>

          <div class="custom-control custom-radio custom-control-inline">

            <input

              id="bad"

              type="radio"

              class="custom-control-input"

              value="bad"

              name="rating"

              [(ngModel)]="customerFeedback.rating"

              #rating="ngModel"

              required

            />

            <label class="custom-control-label" for="bad">{{

              "rating.options.bad" | translate

            }}</label>

          </div>

        </div>

        <span

          translate

          class="text-danger"

          *ngIf="

            (rating.touched || feedbackForm.submitted) &&

            rating.errors?.required

          "

        >

          requiredErrorMessage

        </span>

      </div>

      <div class="row form-group">

        <div class="col d-flex justify-content-end">

          <button translate type="submit" class="btn btn-success">

            {{ "submit.label" }}

          </button>

        </div>

      </div>

    </form>

    <div class="row">

      <div class="col">

        <p>

          &copy;

          <!-- translation directive — key as a child  -->

          <small

            translate

            [translateParams]="copyrightInfoParam"

            class="text-muted"

            >copyright

          </small>

          <br />

        </p>

      </div>

    </div>

  </div>

</div>

We now have a template-driven form created, which will have the following fields:

  • Name—an input field used to store the name of the user
  • Gender—a select field displaying three different options to choose from
  • Comment—a text area field used to store comments provided by the user
  • Rating—a radio group field that asks the user to rate Phrase

All fields are mandatory.

Angular I18n Execution Demo

Run the following command to execute the app in your local environment:

ng serve -o

As soon as the app is launched, you will see the screen below. Select the language value from the drop-down in the nav-bar, and you will get the content displayed in the chosen language.

Localizing Angular Apps with ngx-translate

Concluding our Angular i18n tutorial with ngx-translate

Great job, you have reached the end of this Angular i18n tutorial. Let us do a recap of what we learned about internationalizing with ngx-translate:

  • Using the translate pipe
  • Using the translate directive
  • Cascading the translate pipe with built-in Angular pipes
  • Using interpolation with translation keys
  • Pluralization
  • Using raw HTML tags within the translation
  • Change the language of the app dynamically during run time
  • Dynamically generating the translation keys

If you think your app is now fully ready for localization, give Phrase a try. A software localization platform engineered to streamline app localization end to end, Phrase features a flexible API and CLI, as well as a beautiful web interface for effective collaboration with your translators.

Check out all Phrase features for developers and see for yourself how it can help you take your apps global.