Implementing Internationalization in Angular 6+ Universal Application Using i18n

by Aug 17, 2020Angular0 comments

Q. What is Internationalization?

Internationalization is a process of developing an application having the support of multiple languages.

Q. Why Internationalization? 

Applications having internationalization implemented can only be Localised or translated into different languages, so it could be beneficial for the applications having worldwide audience i.e- Different language-speaking audiences.

Internationalization in Angular Universal using i18n

Reference Document : https://angular.io/guide/i18n

Q. What support do i18n provides in angular applications?

I18n provides the following support to the angular application to be internationalized:-

  • It provides support to display dates, numbers, percentages, and currencies in a local format.
  • Preparing text in component templates for translation.
  • To Handel plural forms of words.
  • To Handel alternative text.

Q. What support do Angular CLI provides in Localization?

Angular CLI provides us with following support for internationalization:-

  • Generates localizable text into a file that we can send out for translation.
  • Building and serving the application for the given locale, using the text which we have translated.
  • Creating multiple language versions of your app.

Internationalization on Angular Universal App

Here we will discuss the multiple steps involved in developing and deploying an internationalized application-

  1. Making static text in component templates for translation(Marking Static text for further use)
  2. Using the Angular CLI tool to extract the marked text into a translation source file.
  3. Using a translator(Or by ourselves) for translating the extracted text into a different language.
  4. Creating the build according to the different translated languages.
  5. Updating the server.ts file to switch between different projects(of different languages) available in the build.

Step 1. Marking static text in component templates for translation

This is the first step of developing an internationalized application, Here we have to identify and have to mark each and every text with i18n attribute. Below is an example of this.

<h1 i18n="@@SomeStaticTextIdentifier">Some Static Text</h1>

Options in i18n:-

  • ID: By default, Angular CLI provides a unique ID for each and every i18n marked texts, But it would be a good practice to provide a unique identifier for the text as it can be used for further operations like setting the target text for the marked text.
  • Description: We can also provide a description of the marked text, as it could be used for getting the much info about the marked text. Example.
<h1 i18n="SomeStaticTextDescription@@SomeStaticTextIdentifier">Some Static Text</h1>
  • i18n Marking on Attributes: i18n provides the support for marking the attributes of the HTML tag. example:-
<input placeholder=” Some Placeholder” i18n-placeholder”>

Step 2. Using the Angular CLI tool to extract the marked text into a translation source file.

In this step, we will use the Angular CLI command to extract the marked text into an industry-standard source file.

Use below command from the root of your application to generate messages.xlf file.

project-root:~$ ng xi18n

The generated file will look like this-

The above command is used for generating the source file of the marked text.

By default, it will generate the file of name messages.xlf.

For advanced info about the xi18n please refer this document.

Here is a snapshot of how messages.xlf look like:-

<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="toolbar-title" datatype="html">
<source>Some Static Text</source>
<context-group purpose="location">
<context context-type="sourcefile">app/app.component.html</context>
<context context-type="linenumber">6</context>
</context-group>
</trans-unit>
//more translation units
</body>
</file>
</xliff>

Note:- We have to run ng xi18n command every time a new Static text is added to the application.

Step 3. Using a translator(Or by ourselves) for translating the extracted text into a different language.

In this step, we have to create the copies of the messages.xlf for our target language files eg:  messages.en.xlf and messages.hi.xlf. For this, we just have to create the files and have to copy and paste everything written in the messages.xlf file

The translation files have a list of <trans-unit> and inside them, there is a tag <source> with the text that we need to translate for example.

<trans-unit id="toolbar-title" datatype="html">  <source>Some Static text</source>  <context-group purpose="location">    <context context-type="sourcefile">app/app.component.html</context>    <context context-type="linenumber">6</context>  </context-group></trans-unit>

Duplicate the tag  <source> ,  and rename it to <target> and in the place of the text, write the translation for the text. Like

<trans-unit id="toolbar-title" datatype="html">  <source>Some Static text</source>  <target>कुछ स्थिर पाठ</target>  <context-group purpose="location">    <context context-type="sourcefile">app/app.component.html</context>    <context context-type="linenumber">6</context>  </context-group></trans-unit>

Step 4. Creating the build according to the different translated languages.

To combine the translated text into the components we need to compile the app with all the completed translation files. In order to do this, we need to provide the Angular compiler with the following translation-specific pieces of information: 

  • The path to the translation file.
  • The translation file format. 
  • The locale of the file, en or hi for instance.

As we are using angular with universal we have to compile our application with angular’s AOT compiler. And to do it we will have to tell the compiler about how to use the translation configuration, and for this, we have to make the following changes in angular.json file.

We add into the angular.json file the qa-en and qa-hi build configurations and also the serve configurations which will run the build.

Below is an example of the configuration which is supposed to be added in angular.json file under architect.

"build": {
    ...
    "configurations": {
       ...
       "qa-hi": {
          "optimization": true,
          "outputHashing": "all",
          "outputPath": "dist/browser/hi/",
          "sourceMap": false,
          "extractCss": true,
          "namedChunks": false,
          "aot": true,
          "extractLicenses": true,
          "vendorChunk": false,
          "buildOptimizer": true,
          "fileReplacements": [
             {
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.prod.ts"
             }
          ],
          "baseHref": "/hi/",
          "i18nFile": "src/locale/messages.hi.xlf",
          "i18nFormat": "xlf",
          "i18nLocale": "hi",
          "i18nMissingTranslation": "error"
       },
       "qa-en": {
          "optimization": true,
          "outputHashing": "all",
          "outputPath": "dist/browser/en/",
          "sourceMap": false,
          "extractC ss": true,
          "namedChunks": false,
          "aot": true,
          "extractLicenses": true,
          "vendorChunk": false,
          "buildOptimizer": true,
          "fileReplacements": [
             {
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.prod.ts"
             }
          ],
          "baseHref": "/en/",
          "i18nFile": "src/locale/messages.en.xlf",
          "i18nFormat": "xlf",
          "i18nLocale": "en",
          "i18nMissingTranslation": "error"
       }
   }
},
"serve": {
   ...
      "configurations": {
         ...
         "hi": {
            "browserTarget": "project-name:build:qa-hi"
         },
         "en": {
            "browserTarget": "project-name:build:qa-en"
         }
      }
}

Look at the following commands we have updated here:-

"outputPath": "dist/browser/hi/", Output path of the Hindi version of the project. 

"baseHref": "/hi/", Base url param for the hindi version of the application

"i18nFile": "src/locale/messages.hi.xlf", Path of the translation file in the directory

"i18nFormat": "xlf", Format of the translation file

"i18nLocale": "hi", locale ID

As we have configured our angular.json file with the specific information, now we can serve our application using Angular CLI command like-

For Hindi translated project – 

:~$  ng serve --configuration=hi 

For English translated project – 

:~$  ng serve --configuration=en

Step 5. Updating the server.ts(if express server is used) file for multilingual support.

As of now, we have created the translations for the application, added configurations in the application and have tried switching the language of the application using this ng serve command by adding required configuration to it.

But for multilingual application, we must require our application to be available in different languages as https://xyz/en/ for English version of the website or https://xyz/hi/  for the Hindi version of the website.

To achieve this functionality we have to make some changes in our server.ts file ie:-

For this, we have to replace this

app.get('*', (req, res) => {
  res.render('index', { req });
});

With

app.get('*', (req, res) => {

//this is for i18n
  const supportedLocales = ['en', 'hi'];
  const defaultLocale = 'en';
  const matches = req.url.match(/^/([a-z]{2}(?:-[A-Z]{2})?)//);
  //check if the requested url has a correct format '/locale' and matches    any of the supportedLocales
  const locale = (matches && supportedLocales.indexOf(matches[1]) !== -1) ? matches[1] : defaultLocale;

  res.render(`${locale}/index`, {req});
});

Here we can see that the script

 res.render(`${locale}/index`, {req});

will render the index file of the project according to the locale selected, as our build command will create different versions of the project in the browser folder of the dist folder.

So as of now, we have changed our file to render the correct version of the application according to the locale id provided it through the URL param. Now we have to make some changes in our package.json file for building the multiple versions of the application using ng run architect.

The following are the changes which we are supposed to make in the package.json file.

We have to add the following script in the script section of the package.json file-

"scripts": {
...
"build:client-and-server-bundles-i18n": "ng run project-name:build:qa-es && ng run project-name:build:qa-en && ng run project-name:server:production",
"build:i18n-ssr": "npm run build:client-and-server-bundles-i18n && npm run webpack:server",
...
}

Now after building the project using the above scripts, Our dist will look like this-

Here we can see that in browser folder, we have multiple versions of the project ie- Hindi and English.

As an Add-on I am also including the steps to cache the selected version of the application.

Here for caching the selected language, we are going to use cookies as the language switching is done by the server.ts so we have to provide the preferred language’s information to the server.ts file.

To use cookies in Angular we have to use two libraries ie-

  • ngx-cookie-service
  • cookie-parser

Here ngx-cookie-service would be used to set the preferred language of the application as a cookie, and cookie-parser is used to parse the cookie in the express server ie- server.ts

Make the following changes in the server.ts file-

// Import cookie parser to use it for parsing the cookies
import * as cookieParser from 'cookie-parser';


app.use(cookieParser()); //For cookie Parsing for selected language


// All regular routes use the Universal engine
app.get('*', (req, res) => {
  //this is for i18n
  const supportedLocales = ['en', 'hi'];
  const defaultLocale = req.cookies.language ? req.cookies.language.toString() : 'en';
  const matches = req.url.match(/^/([a-z]{2}(?:-[A-Z]{2})?)//);
 //check if the requested url has a correct format of locale
  const locale = (matches && supportedLocales.indexOf(matches[1]) !== -1) ? matches[1] : defaultLocale;


 res.render(`${locale}/index`, { req });
});

References

  • For more information on cookies please refer this document
  • For examples of plularization and select expressions refer this document.
  • For Better readability and structuring of the application, it is suggested that we have to put all the .xlf files in a folder in the src folder.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *