AngularJS–Part 12, Multi language support


Introduction

Our application is a product used by many global companies and thus we support multiple languages like English, French, Spanish, German and more. The question is now, how does AngularJS help us to provide our product in all the necessary languages? It turns out that Angular itself does only have limited support for globalization and localization (also called internationalization and localization). But a quick research on the web quickly leads to the following three implementations helping with translating texts into different languages depending on the locale of the user. Here are the links

  • http://angular-gettext.rocketeer.be/
    • https://pascalprecht.github.io/angular-translate/

      Preparing the system

      Since this time we need a couple of more libraries than just AngularJS we will use Bower to install all our dependencies. Bower is a package management system for the client similar to the nodeJS package manager npm which is used to manage server side packages. If you do not have node installed then please do it now from here.

      Open a command prompt as Administrator or a bash console and enter the following command

      image

      This will install Bower globally such as that it is available in any directory of the system. Now we create a new folder for our sample app. In my case I will call it c:\samples\translation. In your console navigate to this directory.

      To install Angular locally in the current directory enter

      image

      To install Bootstrap (which we use for our layout) locally enter

      image

      Finally we want to install the angular-gettext module, thus enter

      image

      Bower has created a sub folder bower_components in our current directory and placed the downloaded components into it. The folder structure should look like this

      image

      As we can see Bower has created four folders, one for angular, one for bootstrap, one for angular-gettext and one for the implicitly downloaded jQuery (bootstrap depends on jQuery). Now we need one more install. We need to install Grunt which is a JavaScript task runner. We need Grunt to help us automate the extraction of texts to translate from the source code. We will install Grunt globally such as that it is available for all projects on our system. Still in the console enter the following command

      image

      This uses npm to install the Grunt command line interface. Now we install Grunt locally with this command

      image

      and then we also need the node module containing the Grunt tasks for text extraction and compilation

      image

      Sample application

      Finally after all this setup we can start to implement a sample application which shows the various aspects affected by globalization and localization. Using your favorite code editor (in my case Sublime Text 3) create a new file app.js which will contain the JavaScript code. Create a new Angular module and define a controller TestCtrl as follows

      image

      Now add another file index.html which will contain the template for our sample application. Add the base HTML to make it an Angular application

      image

      Note how on line 5 we have added a link to the bootstrap CSS file and on line 12 through 14 we have included the angular, angular-gettext and our own app JavaScript file.

      Now lets add some simple texts that later on we want to have translated into the language of the user. Add this code snippet to the div of the template

      image

      Now open index.html in your favorite browser. The result is very unspectacular yet expected. We will just see two paragraphs with the texts as entered above.

      To enable translation support we have to add the directive translate to the two paragraph tags.

      image

      If we refresh the browser nothing changes at all. This is clear since we did not yet initialize angular-gettext. Lets do this now. We use the run function of the Angular module to do so

      image

      On line 3 we ask the Angular injector service to provide us the gettextCatalog service defined in the angular-gettext library. Then on line 4 we use this service and declare that we want to use the language German (code ‘de’) as the current language. On line 5 we declare that we want the service to help us to easily spot missing translations by marking the (original) text with a [Missing] prefix. After adding this code snippet save and refresh the browser. Nothing changed so far and the text is still in English as we have added it in code.

      Extracting and translating

      Now we need to extract all texts that need a translation from our source code. If we have a lot of texts that would be a nightmare if we did have to do it by hand. But don’t worry, we can easily automate this daunting task. We will use Grunt for this. Still within your favorite text editor create a new file called Gruntfile.js in the sample folder. This file will contain the instructions for the Grunt task runner (for an introduction on how to write a Grunt file see this tutorial).  The base structure of a simple Grunt file always looks like this

      image

      That is, we export a function containing the various tasks that Grunt can execute. Inside the above function we first need to load the Grunt tasks provided by the node module grunt-angular-gettext

      image

      Now we want to use the first task provided by grunt-angular-gettext module and configure it. This task will parse all our source files and extract the texts we want to have translated – the ones that have an associated translate directive – and add them to a so called pot file. We do this by adding the following snippet to the Gruntfile.js

      image

      On line 5 we define the task name we want to configure or initialize. On line 8 we declare that we want the task to parse all html files in the current directory or any sub directory of it and output the result of the task into a file called template.pot residing in the sub folder po of the current directory.

      Once we have saved the Grunt file we can issue the following command in the console

      image

      If we have done everything right then we should see the following output

      image

      and we will have a new subfolder po containing a file template.pot. We can open this file in our text editor and it should look like this

      image

      We can see on line 7 and 11 that the two texts to translate have been correctly identified and extracted. This file will be used as input by one of the many editors available that are able to handle the gettext format. In this sample we will use the (free) Poedit application. Please head to this site and download and install the application on your system.

      Run Poedit and create a new catalog from pot file. Select the template.pot file just created when asked for it. Select the language – German in our case. And then translate the texts

      image

      Hit Save when done. Save the result as suggested with the name de.po in the po subdirectory of our working folder. We can then once again (just for curiosity) open the saved file with our text editor just to see this

      image

      Now this although human readable is not the format we need in our Angular application. But no worries, we have another task provided by the grunt-angular-gettext module called nggettext_compile which will compile the output of the Poedit application into a format that we can consume in our Angular application. Let’s configure this task in the Grunt file. Add the following snippet right after the first task configuration and save the file.

      image

      On line 12 we define the task we want to configure and on line 15 we declare that we want this task to load all po files in the subfolder po of the current directory and compile them into a resulting file translations.js located in the current directory.

      In the console run this command

      image

      Again, if we did everything correctly the console output should show this

      image

      Opening the translations.js file in our text editor we get a (halfway minified) version of a new Angular module called gettext which contains the configuration of the gettextCatalog service with the various translations provided (in our case its only German so far).

      image

      Back in our index.html we have to include the above file.

      image

      And as a last step in the app.js file we have to declare that our app module depends on the gettext module.

      image

      Go back to your browser and refresh the page. Hurray our text has been translated!

      image

      Let’s now see how the translation plugin handles missing translations. Add another paragraph to the template

      image

      After saving refresh the browser and you should see this

      image

      The application gives us a hint by prefixing the text whose translation could not be found with [MISSING]. This is very helpful, QA can easily spot missing translations. We can even write an end-to-end test (by using protractor) to find missing translations.

      More advanced scenarios

      What about scenarios where we have dynamic values in the text whose value is only known at runtime through data binding? Something like this

      image

      will translate just fine.

      Now what about situations where the usage of the translate directive is not evident like the value of the placeholder attribute of a textbox (an input of type text). Can we also have this value translated? It turns out that this can indeed be achieved by using Angular expressions and filters. Let’s assume that we have a login dialog where we display an input box for the username and password. For both we want to display a placeholder text if they are empty. Here’s how we do it

      image

      Please note how the expression is written using inner (single) quotes to delimit the text to be translated and outer (double) quotes do delimit the value of the placeholder attribute which is an expression.

      image

      In the above case the filter translate is applied to the text Enter your username.

      Pluralization

      An important case is the situation where we want to correctly translate the singular and plural form of a text. Take the sample

      • John gets assigned one task [singular] – e.g. in German einen Auftrag
      • John gets assigned 3 tasks [plural] – in German 3 Aufträge

      Our translation library also handles this situation. We can write

      image

      We directly write the singular (one task) in the </font> and use the two directives **translate-n** and **translate-plural** to define which is the counter/number and which is the plural form when using the counter/number.

      Now we use the nggettext_extract grunt task again to extract all new texts

      image

      we can use Poedit to translate the new values. In Poedit open your existing catalog (de.po in my case) and then click the menu Catalog –> Update from pot file and select the template.pot file generated by the previous grunt task. You should see this

      image

      The new not yet translated texts are in bold. Translate the values as follows

      image

      After saving we need to update our controller as follows

      image

      and now you can display the page in the browser

      image 

      If we now change the model value count to say 5 we get this

      image

      Changing the language on the fly

      Can we change the language during run-time? Yes we can and it is very easy. Let’s first create another translation, say French using Poedit.

      Add an array languages and a variable lang to the controller as follows

      image

      Also add a function to the controller to change the current language

      image

      In order to make this function work we have to inject the gettextCatalog service into our controller.

      image

      Add a drop-down bound to the languages array to the view

      image

      This is the result when selecting French as the target language

      image

      Conclusion

      In this post I have shown how we can get first class support in Angular for globalization and localization using the angular-gettext extension. All scenarios that we encounter in our large application are covered. Translating texts is straight forward and very user friendly when using a (free) tool like Poedit. The extraction of texts that need to be translated from source code can be fully automated as well as the compilation of the translated texts into a JavaScript library.

AngularJS–Part 11, Promises