Internationalization with angular

Internationalization is the process of designing and preparing your app to be usable in different languages.

Localization is the process of translating your internationalized app into specific languages for particular locales.

Internalization in angular

Angular simplifies the following aspects of internationalization:

  • Displaying dates, numbers, percentages, and currencies in a local format.
  • Preparing text in component templates for translation.
  • Handling plural forms of words.
  • Handling alternative text.

You can find the official technical documentation of the current major version of angular here.

What’s new in angular 9?

Currently, Angular 9 is in the release candidate version.

Angular provides the option to explore the upcoming version to the users.

The official documentation of next-gen release can be found here.

Earlier to angular 9, let’s say angular less than version 8.

The internalization implementation isn’t straight forward or easy to go implementation.

npm tools

So, We need the support of a third party plugin to achieve the concepts. Popular plugins for il8n(Internationalization) are

However, angular 9 brings the power of il8n in it and makes it easy to implement and use.

How to add il8n?

Now, the angular CLI supports the option to add the localize option in our project

ng add @angular/localize

Once we have run the above command in our project, the CLI will add necessary dependencies to the project.

And it adds following the entry into the polyfills.ts

import ‘@angular/localize/init’

Then we need to configure the angular.json with multiple locale files as below

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1, 
  "newProjectRoot": "projects",
  "projects": {
    "angular-localize": {
      "projectType": "application",
      
      "i18n": {
        "locales": {
          "fr":"messages.fr.xlf" ,
          "tm": "messages.tm.xlf"
        }
      },
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/angular-localize",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "aot": true,
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.scss"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb",
                  "maximumError": "10kb"
                }
              ]
            },
            "fr": {
              "localize": ["fr"]
            },
            "tm": {
              "localize": ["tm"]
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "angular-localize:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "angular-localize:build:production"
            },
            "fr": {
              "browserTarget": "angular-localize:build:fr"
            },
            "tm": {
              "browserTarget": "angular-localize:build:tm"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "angular-localize:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.scss"
            ],
            "scripts": []
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "tsconfig.app.json",
              "tsconfig.spec.json",
              "e2e/tsconfig.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        },
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "angular-localize:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "angular-localize:serve:production"
            }
          }
        }
      }
    }},
  "defaultProject": "angular-localize"
}

The angular package provides an option called $localize which can be used in the component

export class AppComponent {
  title = $localize`angular-localize`;
}

Adding language files

When the application process $localize, it will look for the translated or il8n text the target text in the locale file.

The locale files are configured in the angular.json already.

Let’s explore that file now.

<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en-US" datatype="plaintext" original="ng2.template">
    <body>
    <trans-unit id="8298328021206315701">
  <source>angular-localize</source>
  <target>Bonjour</target>
</trans-unit>
    </body>
  </file>
</xliff>
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en-US" datatype="plaintext" original="ng2.template">
    <body>
    <trans-unit id="3902961887793684628">
  <source>hello</source>
  <target>வணக்கம்</target>
</trans-unit>
    </body>
  </file>
</xliff>

Here, we two files to support two locales. And it has been stored in xlf format.

XLF is an XML localization file format.

Running application

ng serve

The above picture shows the default text from the application.

Now let’s run the custom command to point it to french language and see the output.

ng serve –configuration=fr

Therefore the word angular-localise is changed into Bonjour based on the language we have configured.

In conclusion, we can configure N number of languages and host it based on needs.

Disclaimer

Finally, Few things need to be done while experimenting with the above code.

Since this package is supported in angular 9 we need to maintain a matching node environment to experiment with the code. And you can find the entire source code in this GitHub Repository.

Happy Coding!

Everything you need to know about Change Detection

Introduction

Angular is a TypeScript-based open-source web application framework led by the Angular Team at Google and by a community of individuals and corporations.

Story

Web application these days are designed to be interactive. Whenever the state of application changes, the code should detect and handle the changes and reflect the changes in Web User Interface. The mechanism is named as

Change Detection

Almost, all the popular web frameworks has this mechanism.

Why you need to know about Change Detection?

Everyone who works in angular code should aware of Change Detection.

Change Detection is an integral part of application which deals with DOM updates whenever there is a change in model or data.

Performance of application also depends on Change Detection as well.

So, now you are ready to know what is Change Detection?

What is Change Detection?

Change detection is the mechanism designed to track changes in an application state and render the updated state on the screen.

Application Architecture

Normally, architecture will contain

Data or Model → Template → DOM

Data will provide the data that’s needs to be displayed, DOM will be showed in UI and template will be used to fit the data into DOM.

The binding mechanism will happen under the hood which is main part of this architecture. We are going to discuss about it only.

The process of keeping the data in UI updated similar the state of application. This mechanism is called as Change Detection.

A quick example:

https://stackblitz.com/edit/angular-binding-example-t360?file=src/app/app.component.ts

In this example, we are change variable ‘name’ value using timeOut function() after 3 seconds.

Once we have changed the variable value, the UI automatically gets refreshed without any delay.

How that happens?

That’s the work of angular framework here. It tracks the value and reflects in the UI.

Frameworks take care of synchronization between the internal state of the application and the user interfaces for us.

When should change detection happen?

As soon as the application state changes. The application state can change in following cases

  • Event callback
  • Network calls
  • Timers

Any asynchronous call can cause a change in application state and that’s the instance where we should update our state on UI.

A simple detectChange will look like this,

let detectChanges => () => {
  if(currentState != prevState) {
    updateView(currentState);
  }
}

ZoneJS

ZoneJS is an API which has been mostly ported out from the dart language.

Behind the scenes, ZoneJS monkey patches the function. Basically, it helps to keep an eye on all async tasks, and provide an ability to call code before or after a task has been completed. The Zone API has different hooks to place your code onBeforeTask, onAfterTask, onZoneCreated, onError.

var myZoneSpec = {
  beforeTask: function () {
    console.log('Before task');
  },
  afterTask: function () {
    console.log('After task');
  }
};

var myZone = zone.fork(myZoneSpec);
myZone.run(function() {
   console.log('My task successfully got executed.')
});

// Output:
// Before task
// My task successfully got executed.
// After task

So Angular uses the power of Zones to fire change detection. What happens is, if any asynchronous call happens, ZoneJS API emits data to the onMicrotaskEmpty observable, and angular calls the detectChanges method based on the same.

Change Detection Strategy

  1. Default
  2. OnPush

The strategy that the default change detector uses to detect changes. When set, takes effect the next time change detection is triggered.

Default

Use the default CheckAlways strategy, in which change detection is automatic until explicitly deactivated.

OnPush

Use the CheckOnce strategy, meaning that automatic change detection is deactivated until reactivated by setting the strategy to Default (CheckAlways). It can still be explicitly invoked. This strategy applies to all child directives and cannot be overridden.

Example

Let’s add OnPush option in the same example. Now the output will not render until there is a state of change happens in the application. You can find the example here.

Happy Coding!