Localization With Angular-GetText


Translate

Recently I’ve been working on a project that utilizes the AngularJS framework. We were looking into doing localization for our product and we looked a few different ideas. The one that we settled on is Angular-GetText. Angular-gettext let’s you focus on developing your application. Just write everything in English and annotate which parts should be translated. One of the nice parts about this framework is that the translations are on the client and you do not need to make calls to an outside vendor to get your translated values. Consequently I won’t need to call back to the server to have all of my pages redrawn should I decide to change the current language that I am using. Now it’s time to show you some code samples.
The first thing that I suggest that you do is following the Angular-GetText Developer Guide and become familiar with the topics discussed there. I will use the terminology from this guide and want you to be familiar with it before you proceed. Also Gabriel Schenker has written a good blog post on this topic, check it out here.
Here is an example of annotated text.

<!DOCTYPE html>
<html>
<head>
    <title>Translation Sample</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" type="text/css" href="/bootstrap/dist/css/bootstrap.css">
</head>
<body ng-app="app">
<div ng-controller="TestCtrl">
    <p translate>Welcome ladies and gentleman!</p>
    <p translate>We want to test globalization and localization.</p>
    <p translate>Good Morning Sir</p>
    <p translate>Your current location is '{{locationCode}}'</p>
    <a class="dropdown-toggle">
        <img ng-src="{{productSelectorImageUrl}}" />
        <span translate>Product Selector</span>
        <span class="caret">Don't translate this</span>
    </a>
    <p>
        <li ng-repeat="product in productList">
            <a href="#{{product}}"> -[ {{product | translate}}</a>
        </li>
    </p>
</div>

<script type="text/javascript" src="components/angular/angular.js"></script>
<script type="text/javascript" src="components/angular-gettext/dist/angular-gettext.min.js"></script>
<script type="text/javascript" src="scripts/translations.js"></script>
<script type="text/javascript" src="scripts/app.js"></script>
</body>
</html>

Please note that for the most part all you need to do is use the translate directive to mark the item for translation. The only other one that is unique is when we are translating using the filter for the products. This one we used a {{product | translate}} to translate each product item. The product list might be values that are coming from the back-end server. That list will not come back from the server localized, so we need to account for it in a separate JavaScript file. I will show you how I’ve accomplished this further down.

If you are a .NET user like I am you more than likely have *.aspx files that you want to search for translated values. In this case you will need to modify your Gruntfile.js to accept the inclusion of the *.aspx files, as it does not search these types of files by default.

module.exports = function(grunt) {
    grunt.initConfig({
        'nggettext_extract': {
            pot: {
                options: {
                    extensions: {
                        htm: 'html',
                        html: 'html',
                        php: 'html',
                        phtml: 'html',
                        tml: 'html',
                        aspx: 'html',
                        js: 'js'
                    },
                },
                files: {
                    'lang/template.pot': [
                            '**/*.html',
                            '**/*.aspx',
                            '**/langkeys.js' ]
                }
            },
        },
        'nggettext_compile': {
            all: {
                files: {
                    'app/translations.js': ['lang/*.po']
                }
            }
        },
    });

    grunt.loadNpmTasks('grunt-angular-gettext');

    grunt.registerTask('extract', ['nggettext_extract']);
    grunt.registerTask('compile', ['nggettext_compile']);
};

Now that you have the Gruntfile.js configured you will be ready to extract the strings from your project. As you can see I’ve aliased the task ‘nggettext_extract’ to ‘extract’ to keep things simple. Use the following command ‘grunt extract’ from the same folder as your Gruntfile.js and you should get the following output.

C:\Development\Angular\Translate\src>grunt extract
Running "nggettext_extract:pot" (nggettext_extract) task

Done, without errors.

The grunt task will generate a *.pot file that you will use as the translation catalog. Your *.pot file might look like this:

msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: app/firstpage.html
#: app/secondpage.html
msgid "Name Is Required"
msgstr ""

#: app/firstpage.html
msgid "Active"
msgstr ""

#: app/secondpage.html
#: app/thirdpage.html
msgid "Add"
msgstr ""
...

After looking at the catalog document you might be wondering where your product list comes from. After all you’ve added the translate filter to the product entry, {{product | translate}}. Since I’m going to assume these values come from the back-end code we do not have a list of products to extract the translations for on the front end. In our case, we are going to assume that these products are not products that you buy in a store but rather individual portions of the software product that you can purchase as a customer. Think if you could purchase each of the Microsoft office components individually and you’ve got the idea. In order to do this you need to create a JavaScript file called langkeys.js. This files does not need to be loaded by your code, nor do you have to redistribute this to the customer when you deploy or ship your product. This file is only used by the grunt task to extract strings to be localized. You will see from your Gruntfile.js it is specifically called out by adding the ‘**/langkeys.js’ to the files array.

angular.module('Test')
    .controller(['gettext', function(gettext) {
        //  Arrays Required for Translation Grunt Task (Do Not Remove)
        var productNames = [
            gettext("Access"),
            gettext("Database Compare"),
            gettext("Excel"),
            gettext("Lync"),
            gettext("OneNote"),
            gettext("Outlook"),
            gettext("PowerPoint"),
            gettext("Publisher"),
            gettext("Visio"),
            gettext("Word")
        ];
    }]);

Now when you run the ‘grunt extract’ task again, you should see these values in your *.pot file.

Great! Now that you have verified that you have all of the strings that you want to localize, it is time to enter the translations. You can use the program PoEdit to create your translations files. As a beginner, I would do this until you become familiar with the format. Please turn off translation memory by un-checking it in the preferences. In our case the translation memory option is not your friend because this relies on a person familiar with the language to determine if the translation is correct.

PoEdit - Uncheck Translation Memory

As you can see it will reference which pages have the text to be translated on it using the comment ‘#: app/firstpage.html‘. The next two element tags are the msgid and the msgstr tags. The first one the msgid is the key. In this case it happens to be the English translation that was used to create the page. The second tag, the msgstr is where the translated value will be stored in your *.po file. The *.po file is the file generated by PoEdit when you create a new language file. It will have a very similar format to the catalog file, but it will contain the translated strings for the given language. Here is the es_ES.po translation file for Spanish (Spain) language.

msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.6.4\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: app/firstpage.html
#: app/secondpage.html
msgid "* Name is required"
msgstr "* El nombre es necesario"

#: app/firstpage.html
msgid "Active"
msgstr "Activo"

#: app/secondpage.html
#: app/thirdpage.html
msgid "Add"
msgstr "Añadir"
...

As you can see the translated values are stored in the msgstr tag of this file. Now that you have created all of the *.po files that you need for you project; its time to compile them into your translations.js JavaScript file. Use the ‘grunt compile’ task to accomplish this.

C:\Development\Angular\Translate\src>grunt compile
Running "nggettext_compile:all" (nggettext_compile) task

Done, without errors.

Now all that is left is the refresh your application in the browser and look for any [MISSING] tags!

This is just the first post of many that I will have on this topic, please feel free to message me with any questions. My next post on the subject will talk about specific tags and automation. Stay tuned…

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s