Angular 6 Internationalization (with examples)

Angular is one of the most widely used front-end framework for modern web applications. The component-based design of Angular is suitable for modern web applications because each component can have its own UI, DataBinding, Events, etc.

We can use Angular with its object model to design and develop a web application's front-end. Since there could be several users requesting the web application from different parts of the globe, it is important for us to manage the rendering of the UI in a localized format.

Angular 6 Internationalization (i18n)

Angular framework has provided an Internationalization (i18n) tool. This tool helps developers to make application available in multiple languages.

The demo project discussed for this article is implemented using Angular-CLI. If you are new to Angular-CLI then please visit this link. Visual Studio Code, a free IDE tool, is used for this application. It can be downloaded from this link.

The i18n tools helps to simplify the following aspects of Internationalization:

1. Currencies, dates, numbers
2. Translating static text in the HTML template of the Angular component
3. Handling plural forms of words

Step 1: Open Node.js command prompt as Administrator. Navigate to the System Drive (on Windows 10) e.g. c:\ or any other drive. Run the following command from the command prompt:
npm install -g @angular/cli
This will install Angular CLI in the global scope. To create a new Angular application run the following command from the command prompt
ng new ng-internationalization-app
This will create a ng-internationalization-app folder and application of name  ng-internationalization-app in it.

Step 2: Open the VSCode IDE and open the ng-internationalization-app folder in it.  We will create model classes and components using following commands.

To create model classes run following two commands

ng g class Person
ng g class PersonLogic


These commands will add person.ts and person-logic.ts files in the app folder.

Add the following code in person.ts
export class PersonInfo {
  constructor(
      public PersonId: number,
      public PersonName: string,
      public Address: string,
      public City: string,
      public State: string,
      public Occupation:  string
  ){}
}

export const States: Array = [
  'Maharashtra',
  'Telengana',
  'Karnataka',
  'Gujarat',
  'Rajasthan'
];
export const Occupations:Array = [
  'Employeed','Self-Employeed','Un-Employeed'
];
Listing 1: The Person Class and Constants


The Person class contains public members for personal data. This file also contains constant arrays for States and Occupations. Add the following code in person-logic.ts
import { PersonInfo} from './person';
export class PersonLogic {
  person: PersonInfo;
  persons: Array;
  constructor() {
    this.person = new PersonInfo(0, '', '', '', '', '');
    this.persons = new Array();
    this.persons.push(
      new PersonInfo(
        101,
        'Mahesh',
        'Bavdhan',
        'Pune',
        'Maharashtra',
        'Self-Employeed'
      )
    );
    this.persons.push(
      new PersonInfo(
        102,
        'Sachin',
        'Kothrud',
        'Pune',
        'Maharashtra',
        'Employeed'
      )
    );
  }
  getPersonsInfo(): Array {
    return this.persons;
  }
  savePersonInfo(per: PersonInfo): Array {
    this.persons.push(per);
    return this.persons;
  }
}
Listing 2: The PersonLogic class


The above class defines methods for reading Person information and adding a new person.

Step 3: To create a new component in the project, run the following command from the command prompt
ng g component PersonInfo
This command will add a new folder of name person-info in the app sub-folder of src folder. The person-info folder will contains following files:
  • person-info.component.css
  • person-info.component.html
  • person-info.component.spec.ts
  • person-info.component.ts

Add the following code in PersonInfoComponent class:
import { Component, OnInit } from '@angular/core';
import { PersonInfo, States, Occupations } from './../person';
import { PersonLogic } from './../person-logic';
@Component({
    selector: 'app-personinfo-component',
    templateUrl: './person-info.component.html',
})
export class PersonInfoComponent implements OnInit {
    person: PersonInfo;
    persons: Array;
    logic: PersonLogic;
    states = States;
    occupations = Occupations;
    tableColumns: Array;
    constructor() {
        this.person = new PersonInfo(0,'','','','','');
        this.persons = new Array();
        this.logic = new PersonLogic();
        this.tableColumns = new Array();
    }
    ngOnInit(): void {
        for(let p in this.person) {
            this.tableColumns.push(p);
        }
        this.persons = this.logic.getPersonsInfo();
     }
    clear():void {
        this.person = new PersonInfo(0,'','','','','');
    }
    save():void {
        this.logic.savePersonInfo(this.person);
    }
    getSelectedData(p:PersonInfo):void {
       this.person = Object.assign({},p);
    }
}
Listing 3: The PersonComponent class


The above component class uses Person and PersonLogic classes for performing read/write operations on Person object. The ngOnInit() method iterate over the Person object to read its members. These members will be pushed in the tableColumns array. This array will be used to generate table headers of the HTML table which will be used to display Person details.

The PersonInfoComponent accepts person-info.component.html as templateUrl for displaying View. We will add Html in this view in forthcoming steps.

Understanding i18n Attribute

The Html template will contain static text for showing Person information e.g. PersonId, Person Name, etc. To display this static text in the localize form, we need to define i18n attribute for Html element. This attribute is available using an i18n template translation tool. This tool allows extracting localize file where we can define localized  translation for static text which will be rendered on Html document. The i18n attribute has a following process for implementing transformation
  • Marking the static text in the component template for translation
  • The Angular i18n tool extract the marked text in a separate translation source file
  • The translator edits the file by translating the extracted text into the target language
  • The Angular compiler will import this translated file and replaces original static text as per the translated text. This will render the app in the specified target translated language.

The translation file has the naming standard as <filename>.<language-symbol>.xlf. e.g. source.fr.xlf. These XML localization Interchange File Format files are generated using an  i18n tool. The project generated using Angular CLI by default provides locales for various cultures as shown in the following image

locales
Figure 1: The locale folder structure


Step 4: In the src folder add a new folder and name it as locale. We will use this folder to store all xlf files. To generate the xlf file run following commands from the command prompt

ng xi18n --output-path locale --out-file source.fr.xlf --i18n-locale fr


This will generate file which we will be used for French transformation.

ng xi18n --output-path locale --out-file source.de.xlf --i18n-locale de


This will generate file which we will be used for German transformation.

Step 5: We need to define a translation for the specific language  using a translator, for this article I have used google translator. Lets modify the source.fr.xlf file for French translation as shown in the following Xml code
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="fr" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="pageH1Tag" datatype="html">
        <source>Personal Information System</source>
        <target>Système d'information personnel</target>
        <note priority="1" from="description">A header</note>
        <note priority="1" from="meaning">Screen notification</note>
      </trans-unit>
      <trans-unit id="label1" datatype="html">
        <source>Person Id</source>
        <target>Identifiant de la personne</target>
      </trans-unit>
      <trans-unit id="label2" datatype="html">
        <source>Person Name</source>
        <target>Nom d'une personne</target>
      </trans-unit>
      <trans-unit id="label3" datatype="html">
        <source>Person Address</source>
        <target>Adresse de la personne</target>
      </trans-unit>
      <trans-unit id="label4" datatype="html">
        <source>Person City</source>
        <target>Personne Ville</target>
      </trans-unit>
      <trans-unit id="label5" datatype="html">
        <source>Person State</source>
        <target>État personne</target>
      </trans-unit>
      <trans-unit id="label6" datatype="html">
        <source>Person Occupation</source>
        <target>Personne Occupation</target>
      </trans-unit>
      <trans-unit id="label7" datatype="html">
        <source>New</source>
        <target>Nouveau</target>
      </trans-unit>
      <trans-unit id="label8" datatype="html">
        <source>Save</source>
        <target>sauvegarder</target>
      </trans-unit>
      <trans-unit id="columnHeaderCaptions" datatype="html">
        <source>{VAR_SELECT, select, c,{c}}</source>
        <target>{VAR_SELECT, select, PersonId{PersonId} 
PersonName{Nom d'une personne} Address{Adresse} City{Ville} 
State{Etat} Occupation{Occupation}}</target>
      </trans-unit>
    </body>
  </file>
</xliff>
Listing 4: The source.fr.xlf file


The above file defines trans-unit with an id attribute. This attribute will be used for i18n attribute which will be defined in Html template of which static text needs to be translated. The trans-unit has the source and the target children which contains the source static text and the expected translated text. This file contains French translations for all static texts. The file contains trans-unit with id as columnHeaderCaptions. We will use this translation for the dynamically generated Html e.g. Table headers. We need to use select expressions. These expressions help to define the translation for dynamically generated texts on Html document. Here we have the expression {c}, this will be used to on Html template to generate table headers using *ngFor Angular Directive. We will see this in Html document in forthcoming steps.

Similar to French source file we will also add translation in source.de.xlf. This file is based on source.fr.xlf file, the only difference that its contains target as German text.

Step 6: The most important step of internationalization is that, we need to merge these translation files in AOT compiler so that the build will be generated used these translated files. Open the angular.json file and modify it to use production configurations as shown in the following code
"configurations": {
            …,
            "production-fr": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "outputPath": "dist/my-project-fr/",
              "i18nFile": "src/locale/source.fr.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "fr",
              "i18nMissingTranslation": "error"
            },
            "fr": {
              "aot": true,
              "outputPath": "dist/my-project-fr/",
              "i18nFile": "src/locale/source.fr.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "fr",
              "i18nMissingTranslation": "error"
            },
            "production-de": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "outputPath": "dist/my-project-de/",
              "i18nFile": "src/locale/source.de.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "de",
              "i18nMissingTranslation": "error"
            },
            "de": {
              "aot": true,
              "outputPath": "dist/my-project-de/",
              "i18nFile": "src/locale/source.de.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "de",
              "i18nMissingTranslation": "error"
            }
          } ,
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "ng-internationalization-app:build"
          },
          "configurations": {
           …
            "fr": {
              "browserTarget": "ng-internationalization-app:build:fr"
            },
            "de": {
              "browserTarget": "ng-internationalization-app:build:de"
            }
          }
Listing 5: The angular.json file modifications


We have defined following properties for a production configuration
  • i18nFile: This is the path of the translation file
  • i18nFormat: This is the format of the file
  • i18nLcalte: The locale id. This is the id for the file which is to be used while translation

The angular.json modification will make sure that the corresponding translation file will be used by JIT compiler to create corresponding translation and then bootstrap the application.

Step 7: Modify the app.module.ts file as shown in the following code to bootstrap PersonInfoComponent.
import { BrowserModule } from '@angular/platform-browser';
import {NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { PersonInfoComponent } from './person-info/person-info.component';

@NgModule({
  declarations: [
  PersonInfoComponent],
  imports: [
    BrowserModule, FormsModule
  ],
  providers: [  ],
  bootstrap: [PersonInfoComponent]
})
export class AppModule { }
Listing 6: The AppModule with bootstrapper
Step 8: Modify the personinfo-logic.component.html as shown in the following Html markup
<h1 i18n="@@pageH1Tag" style="text-align: center">
Personal Information System
</h1>
<div class="container">
  <table class="table table-bordered table-striped">
    <tr>
      <td i18n="@@label1">
        Person Id
      </td>
      <td>
        <input type="text" class="form-control" 
[(ngModel)]="person.PersonId">
      </td>
    </tr>
    <tr>
      <td i18n="@@label2">
        Person Name
      </td>
      <td>
        <input type="text" class="form-control" 
[(ngModel)]="person.PersonName">
      </td>
    </tr>
    <tr>
      <td i18n="@@label3">
        Person Address
      </td>
      <td>
        <input type="text" class="form-control" 
[(ngModel)]="person.Address">
      </td>
    </tr>
    <tr>
      <td i18n="@@label4">
        Person City
      </td>
      <td>
        <input type="text" class="form-control" 
[(ngModel)]="person.City">
      </td>
    </tr>
    <tr>
      <td i18n="@@label5">
        Person State
      </td>
      <td>
        <select class="form-control" [(ngModel)]="person.State">
          <option *ngFor="let s of states" value="{{s}}">
{{s}}
</option>
        </select>
      </td>
    </tr>
    <tr>
      <td i18n="@@label6">
        Person Occupation
      </td>
      <td>
        <select class="form-control" [(ngModel)]="person.Occupation">
          <option *ngFor="let o of occupations" value="{{o}}">
{{o}}
</option>
        </select>
      </td>
    </tr>
    <tr>
      <td>
        <button i18n="@@label7" type="button" (click)="clear()" 
class="btn btn-default">New</button>
      </td>
      <td>
        <button i18n="@@label8" type="button" (click)="save()" 
class="btn btn-success">Save</button>
      </td>
    </tr>
  </table>
</div>
<div class="container">
  <table class="table table-bordered table-striped">
    <thead>
      <tr>
          <th i18n="@@columnHeaderCaptions" 
*ngFor="let c of tableColumns">{c,select,c{c}}</th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let per of persons" (click)="getSelectedData(per)">
        <td *ngFor="let c of tableColumns">{{per[c]}}</td>
      </tr>
    </tbody>
  </table>
</div>
Listing 7: The person-logic.component.html file with i18n attribute 
Take a look at the Html markup, we have applied i18n attribute for Html tag and we have passed the id of trans-unit from .xlf file to the matching static text containing Html element. This will make sure that the rendering of a static test of the Html element will be done based on the translation. To Run the application, enter the following command from the command prompt

ng serve --configuration=fr

The configuration=fr switch will use the source.fr.xlf file for a dynamic translation of the Html rendering in French language. Open the chrome browser and enter the following address in address-bar
http://localhost:4200
frence-rendering

Figure 2: The French Text rendering


Run the following command for German language

ng serve --configuration=de


The result will be as follows:

german-rendering
Figure 3: The German Text rendering

Conclusion: Internationalization in Angular with i18n provides an easy way for a dynamic implementation of localization.





About The Author

Mahesh Sabnis is a Microsoft MVP having over 18 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). He also blogs regularly at DotNetCurry.com. Follow him on twitter @maheshdotnet

No comments: