AngularJS–Part 12, Multi language support


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

I am sure there exist other implementations out there but for our purposes the first approach which is based on the Gettext format is the favorite one due to the fact that we do not have to use keywords as placeholders for texts but can just use plain English texts in code, and the rich ecosystem around it. Specifically of interest is the Potedit application which provides a very user friendly way of translating texts.

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


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


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


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


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


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


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


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


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


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


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


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.


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


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


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


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


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


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


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


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


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


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.


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


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


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).


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


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


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


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


After saving refresh the browser and you should see this


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


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


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.


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


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


We directly write the singular (one task) in the <span> 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


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


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


After saving we need to update our controller as follows


and now you can display the page in the browser


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


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


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


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


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


This is the result when selecting French as the target language



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.

About Gabriel Schenker

Gabriel N. Schenker started his career as a physicist. Following his passion and interest in stars and the universe he chose to write his Ph.D. thesis in astrophysics. Soon after this he dedicated all his time to his second passion, writing and architecting software. Gabriel has since been working for over 25 years as a consultant, software architect, trainer, and mentor mainly on the .NET platform. He is currently working as senior software architect at Alien Vault in Austin, Texas. Gabriel is passionate about software development and tries to make the life of developers easier by providing guidelines and frameworks to reduce friction in the software development process. Gabriel is married and father of four children and during his spare time likes hiking in the mountains, cooking and reading.
This entry was posted in AngularJS, introduction. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Nice write-up, thanks. Did you try also ng-i18next?

    • gabrielschenker

      Thank you for the link. No, I did not try it. It looks interesting.

  • jlamande

    The browser as a translation platform ! :-)

  • Gino

    if I have many languages ​​and a lot of text for each language would not be a pain for the browser to handle all that long .js file in the memory? Is there a better method? Maybe making a binding of the one language needed? I’m wrong?

    • gabrielschenker

      What we do is first let the user log-in (in our case the user explicitly chooses the language) and then generate the main page (from a server side template) that it only includes the necessary language file (say German)

      • Gino

        But how if I compile all languages (.po files) in one (translations.js)? Just the name of the file say it “translationS.js”. There is a method for binding from server just one translatioN, like in PHP, for not overhead the browser?

        • gabrielschenker

          You need to create a file per language, e.g. translations-de-DE.js

      • Lev

        Does it mean that if I want to immediately switch language, I should have those translations already included?

        • gabrielschenker

          What does immediately mean? Please specify.

          • Lev

            I mean “on the fly”. So we include translations.js into index.html and it should contain all translations for all languages to make it possible to switch language on the fly, right?

          • gabrielschenker

            If you are planning to switch the language without refreshing the SPA then yes, you have to load all languages at initialization or you provide a mechanism to dynamically load new translations

  • Nuno Santos

    HI, thank you for this step by step tutorial.

    I follow it and in the step we execute grunt nggettext_compile, the result in the js file (translate.js) is:

    angular.module(“gettext”).run(['gettextCatalog', function (gettextCatalog) {
    /* jshint -W100 */

    /* jshint +W100 */

    Why grunt don’t generate the js translation file correctly?
    Can you put here your example project to download?


    • Manuel

      Same… Any solution?

      • Manuel

        emzweb thanks!

        translations.js’ : ['po/*.po'] —> it should be ‘translations.js’ : ['po/*.pot'] (as you created a template.pot with t !)

  • Diego Mesa

    Hello, I was a little bit confused with the official doc but this tutorial is straightforward.

    I come from the silverlight world too, We are migrating a medium silverlight app, and I just want to say that angular is great!

    Thank you

  • Pingback: Localization With Angular-GetText | OutOfMemoryException()

  • Laura

    It would help a lot if you’d post complete files instead of just screenshots of snippets, I lost the oversight of what to include where.

  • Pingback: Angular.js und Lokalisierung | Programmers Blog()

  • Mate, amazing article.
    But console commands as pictures is crazy shit :)

  • emzweb

    Great article. Just came across an error : ‘translations.js’ : ['po/*.po'] —> it should be ‘translations.js’ : ['po/*.pot'] (as you created a template.pot with t !)

  • summ3r

    To swiftly translate .po files, I recommend the online localization tool:
    It’s perfect for collaborative work, and free within a 1000 string limit.

  • Niklas Rasmusson

    How should I handle changes to the original text string, which serves as the key in the pot-file? As soon as I change the original string I have to manually update all the compiled po-files?

  • pizzafroide

    Great article indeed. Just two details though:
    1. Please find a better French translation hereafter.
    2. Even though it’s not the point of your article, maybe you could mention that long internationalized strings should not be split. For instance, your pluralization example could be merged into one string. In fact, its French translation is forced to be something like “John s’est vu attribuer une tâche” (when count != 1); it is correct but a better translation would turn the sentence around, such as “Une tâche a été attribuée à John” which is not possible when the sentence is split up. Therefore, in my opinion, a better example would be “John gets assigned one task”.

    My French translation (it is my native language, btw):
    msgid “Here we don’t have a translation…”
    msgstr “”

    msgid “Enter your password”
    msgstr “Entrer votre mot de passe”

    msgid “Enter your username”
    msgstr “Entrer votre nom d’utilisateur”

    msgid “John gets assigned”
    msgstr “John s’est vu attribuer”

    msgid “Let’s welcome Mr. {{firstname}} {{lastname}} in our team.”
    msgstr “Accueillons M. {{firstname}} {{lastname}} dans notre équipe.”

    msgid “Password:”
    msgstr “Mot de passe :”

    msgid “Username:”
    msgstr “Nom d’utilisateur :”

    msgid “We want to test globalization and localization.”
    msgstr “Nous voulons tester la globalisation et la localisation.”

    msgid “Welcome ladies and gentlemen!”
    msgstr “Bienvenue mesdames et messieurs !”

    msgid “one task”
    msgid_plural “{{count tasks}}”
    msgstr[0] “une tâche”
    msgstr[1] “{{count}} tâches”

    • gabrielschenker

      Merci beaucoup pour ton réponse interessante. Francais c’est que ma deuxième langue. C’est toujours bien d’apprenre quelque chose nouveau.

  • pizzafroide

    Oops, HTML tags have been interpreted.

    A better example would be:
    “<span translate translate-n=”count” translate-plural=”John gets assigned {{count}} tasks”>John gets assigned one task</span>”.

  • LordPayso

    How do you mark text for translation in services or factories?

    In my httpinterceptor when there is a response error I popup an alert to informt the user why there has been an error. Seems I cannot inject gettextCatalog as a dependency?

  • Vahid

    Thanks for such a great info on i18n.
    I followed the rules and made it work perfectly.
    The question is what if we have “Multiple” templates? I am developing an AngularJS app containing different views (partial templates), connected together with routing facilities…
    In one html page that is fine, we use “ng-model”, but what if we want to have a Cross-app translation, moving from a view to another view ??

  • Vaughan Jackson

    Great article. An update regarding the angular-translate URL. I think this has now changed to

  • Sandor Berki

    Thank you very much for this great article.
    I have never used Grunt before, but it was pretty easy to put everything together.

    I have found out that there is also support to change the language on the fly without a page reload. In the changeLanguage function of the controller simply call “gettextCatalog.setCurrentLanguage($scope.lang);”
    instead of “gettextCatalog.currentLanguage = $scope.lang;” This broadcasts a gettextLanguageChanged event and this will result in an update on the translate directive.

  • Joachim Werner

    As I am pretty new to angular, I am wondering how I could store the selected language and use it in all controllers. $cookies?

  • Marco Solari

    Thanks, exactly what I needed, not more, not less… Thanks!
    (I agree with Max Syabro: using screenshots for commands is awful… :-)
    I would like to ask a question: how does angular-gettext affect runtime performance, for an average page? Did anybody make any profiling?

  • mrhieu

    Almost what I’m looking for to develop my current project. Thanks!

  • Brad

    Very helpful! Thanks for the post. I would also recommend readers try out this article:

  • Hi, angular-gettext author here: please note that assigning to currentLanguage is no longer supported and will lead to subtle bugs.

    You probably want to upgrade your changeLanguage function so that it uses gettextCatalog.setCurrentLanguage($scope.lang);

    Great article though!

    • gabrielschenker

      Thank you for sharing

  • joeldavey

    Thanks for such a great article, you give some very useful examples on using this.

  • Akshath Kumar

    superb explanation…Thank you

  • Mauricio Ramirez

    Hi. Is it possible to download your project from somewhere. I have been unable to replicate it exactly. Thanks in advande.

  • David Baruch

    Great article. Thank you. I followed your instructions and evrything worked as expected.

    BUT the translation was not always to my satisfaction. Why would you want to lose control over what your app presents the end user?

  • Karl

    Thanks for your article – the Grunt and automatisation ist very interesting. What would you think in terms of SEO ?

  • hmm, it seem to not change language when i pick something from select. additionally, it sets default lang to EN even if i set it to PL

  • Kamil

    Everything is OK… but parts of code as screenshots? Seriously…? My eyes bleeding, please, fix it :)

  • Greg David

    Hi! If you need to localize your AngularJS app, my recommendation is to evaluate a software localization management platform like that can help you automate your workflow.