How to localized AngularJS app

So, the past week I've been working on localizing new Webmaker's event page which was written in AngularJs and that is one of my biggest challenge because I'm not very expert with AngularJs and have to come up with a solution for localization in AngularJs based app within a very short period of time.

I've spent a lot of time planning and thinking if I should write my own DIY localization module or use something that's already out there, and finally I have found something and I thought I should share this with you guys.

I've found an Angularjs LocalizationServer written by Jim Lavin which was a really good start for me because I don't have to go through and understand everything in Angular to write my own module for localization from ground up and Jim's module was very similar to what we're using in our node app, so I took it and did a lot modification for our needs at Webmaker.

This i18n.js file can simply be include in your app and with few setup as I'm going to go through in this post and you will be ready to go with your AngularJs i18n.

Though, at first this module can be independently included and use, but I've chose to rely on our node-i18n to provide some accuracy on language request and to serve the translation file the same way we have for other apps simply because we want that file to be supported by Transifex so we can get our community to help us translate.

So, let's look at how to do it if you want to include this library in your app.

Step 01: Download that i18n.js file from this link
Step 02: Include this i18n.js file in your app directory or anywhere as you wish

app/js  
├── app.js
├── controllers.js
├── directives.js
├── filters.js
├── i18n.js
└── services.js

Step 03: In your index.html make sure you have i18n.js included in your app.

  <script src="js/i18n.js"></script>

Step 04: Now you have to make sure you include the i18n Angular module in your module list

angular.module('myApp', [  
  'ngRoute',
  'ngResource',
  'ngAnimate',
  'ui.bootstrap',
  'localization', // Here
  'myApp.filters',
  'myApp.services',
  'myApp.directives',
  'myApp.controllers'
])
...
...

This is pretty much done for including the module, but in order to use this version of the module we need to use that node-i18n module that I've mentioed above since we want the translation file and the locale information.

In your backend server include this module

  var i18n = require('webmaker-i18n');

And you can use this:

  // Setup locales with i18n
  app.use( i18n.middleware({
    supported_languages: env.get('SUPPORTED_LANGS') || [defaultLang],
    default_lang: defaultLang,
    mappings: require('webmaker-locale-mapping'),
    translation_directory: path.resolve(__dirname, '../locale')
  }));

This will now allow you to have access to your translation file which located in your root directory

locale  
├── en_US
│   └── events2.json
└── th_TH
    └── events2.json

The format we use here is Chrome.i18n json file

{
  "_Welcome_home_page_": {
    "message": "Welcome to Webmaker Events.",
    "description": "Home page greeting text"
  },
  "_Sub_home_page_": {
    "message": "(Add super fun imagery here.)",
    "description": "Sub message in home page"
  }
}

With the setup of our node-i18n and i18n.js for AngularJS we will also have to setup the route for translation file and with our node-i18n module we have API that allow that to be expose with express by simply use StringsRoute method.

app.get( "/strings/:lang?", i18n.stringsRoute( "en-US" ) );  

So, when we acess http://yourdomain.com/strings/en-US that will return JSON object for that specific language.

And as you can see from this i18n.js file on line 32 where we make a request to get our translation file.

One last thing before we can go to localize any of our template file is to have language information available throughout our app.

What I did was to have a config file in our template file let say config.js which is acessible by URL and what I meant by that? here let see the example in your backend file

  var config = {};
  // Serve up virtual configuration "file"
  app.get('/config.js', function (req, res) {
    config.lang = req.localeInfo.lang;
    config.direction = req.localeInfo.direction;
    config.defaultLang = defaultLang;
    config.supported_languages = i18n.getSupportLanguages();
    res.setHeader('Content-type', 'text/javascript');
    res.send('window.eventsConfig = ' + JSON.stringify(config));
  });

And in your template file just have this line include:

  <script src="config.js"></script>

Now we have to make sure that the above is a relative path because we are going to allow user to change language from the requested URL such as: http://yourdomain.com/th-TH/# if their browser's language preference was set to other language.

Last but not least you will have to include that config.lang in your $rootScope so that it will be accessible by i18n.js file

In your services.js include this as constant

angular.module('myApp.services', ['ngResource'])  
  .constant('config', window.eventsConfig) // Here
  .factory('eventService', ['$rootScope', '$resource', 'config',
    function ($rootScope, $resource, config) {
      return $resource(config.eventsLocation + '/events/:id', null, {
        update: {
          method: 'PUT'
        }
      });
    }
  ])

Then here come the magic

      // Set locale information
      if(config.supported_languages.indexOf(config.lang) > 0) {
        $rootScope.lang = config.lang;
      } else {
        $rootScope.lang = config.defaultLang;
      }
      $rootScope.direction = config.direction;

So now you will have lang and other language information available in the scope and template.

Ok, so let's start with localizing some text in our template file.

We have three methods here.

First:

{{ '_toggle_navigation_' | i18n }}

Second:

<title ng-bind="'_webmaker_events_' | i18n"></title>  

Thrid:

<span ng-bind-html="'_webmaker_welcome_' | i18n"></title>  

Fourth:

<span bind-unsafe-html="'_agree_to_terms_and_conditions_' | i18n"></span>  

Now let's see the explaination for these three and how it's being use.

For the first method it's very straightforward where we have our braces around with key_name in that single quote you can name anything as long as it's match with the translation file.

Second one is the biding it with the element itself and for this case you want to use it when you just want to make sure things happen only when template is ready.

Third one is when you have some markup and you want that to be display as markup and not being escape by Angular.

Fourth one you will see the word unsafe and felt a bit scary, but here is the use case for this one.

If you have variable in your value such as

"_agree_to_terms_and_conditions_": {
    "message": "I agree to your <a href=\"https://webmaker.org/{{lang}}/terms\">terms</a> and <a href=\"https://webmaker.org/{{lang}}/privacy\">conditions</a>",
    "description": "Agree to terms and condition message"
  }

If you don't use this method it will simply be escape by Angular and your value won't be render correctly and what we have there it will tell it to use $compile for that one call that we are watching and just rerender that again. You can look at the code from line 126-148.

That's it people you can now start localizing your apps and happy localizing :)

If you have any question please feel free to drop me a comment here.