Putting a headlock on the coding lifestyle!

Localizing Your AngularJS App

Overview

If you plan on being in the Web App development business for any amount of time, sooner or later you are going to be faced with building an app that supports multiple languages. When this time comes you’ll be faced with the localization challenge that so many developers before you have faced.

To solve this challenge some developers have built entire localization frameworks and libraries, while others have resorted to re-creating their entire site in the desired language and redirecting users based on their browser culture.

In this article, I’ll show you an easy way to use an AngularJS Service and Filter to pull localized strings from a resource file and populate the page content based on the user’s browser culture.

Architecture

Our solution is going to be based on a simple architecture. We will use localized resource files for each language we want to support. We will also have a default resource file that will be used to fall back to the site’s native language if a given user’s language is not supported.

We will build an AngularJS Service that will be responsible for checking the user’s browser culture and requesting the appropriate resource based on the language. If the resource file does not exist it will request the default resource file and use it.

The service will also provide a lookup method that will return a localized string for a given key from the loaded resource file.
Since the service may not be called directly by a controller or app module we’ll also provide a mechanism for the service to initialize itself, load the appropriate localized resource file and prepare itself to handle requests.

We will also build an AngularJS Filter that can be used in your HTML as a front-end to the localization service. Using the filter will help you keep your code clean and keep you controllers from having to know about the localization service.

To use the filter you can use an expression such as; {{‘_FormControllerTitle_’ | i18n}} if you want to inject the localized string directly into a tag or you can use the ng-bind=’_FormControllerTitle_’ | i18n” method to inject the localized string into an element, when AngluarJS compiles and links the DOM.

Finally let’s talk a bit about how we’ll store the localized data for the service. Since our service will be requesting the resource files once the app in bootstrapped, we need a place to store them. To keep the overall size of the service small I thought it was best not to embed the strings in the service class, but put them in directory off the root of the site named i18n. This follows the same pattern you see with several libraries where the localized resources are in a directory co-located with the module.

The files also have a specific file naming format; resources-locale_xx-yy.js where xx is the language identifier and yy is the country identifier. So resources-locale_en-US.js would mean the file is for English, United States and resources-locale_es_es.js would mean the file is for Spanish, Spain.

There is one more file naming convention we’ll use and that is for the default resource file. For the default file that will be loaded is a language resource file for language does not exist on your system it will be named resources-locale_default.js

The format of the language resource file is simple. Since we only really need a few pieces of information I’ve kept it to key, value and description. This way if you need to hand the file off to someone for translation they’ll have a general description of what the test is for.

This format will also help other developers on the project. When they are getting ready to add a new string resource they’ll be able to search the file to see if maybe there is already a string they can use. This comes in real handy for buttons, table headers, etc.

We start off by calling angular.module which will define our module that we’ll name ‘localization’. Since our module does not depend on anything but the built in AngularJS service we will pass in an empty array to the method as well.

Then we chain a factory method off of the module to define our service which we’ll call ‘localize’ and add a function that will be used to define our service. You can see this in the code above.

Next we need to add the services the service is dependent on so Angular can inject them. This will be using the following services:

$http – This will be used to retrieve the localized resource file from the web server.

$rootScope – This will be used to broadcast a message once the localized resource has been retrieved and loaded by the system. I’m using this is in case a controller or other service might use the service directly and needs to know when the service is ready.

$window – This will be used to find out the culture of the user’s browser, which we’ll use to request the appropriate resource file from the web server. There is an Angular service called $locale which should provide this information, but currently it seems to be hard coded to en-us from what I’ve experimented with and from what I’ve read over at Google Groups. If anyone has gotten this to work, please leave me a comment so I can revise the code to use the proper service.

$filter – This will be used to filter the dictionary array and return back only those resource objects that has the desired key the user is looking for.

To add our dependencies and to ensure that any minification doesn’t muck them up, we are going to use the [] around our function to tell Angular what services we depend on. The revised code is below:

As you can see each one of the dependencies are specified as an array of strings with the service definition function last. This way Angular will know what to inject into our service without issue. We then also repeat the dependencies in our function declaration so they are visible to our service.

Now let’s define the service interface. We are going to expose two methods:

initLocalizedResources – Responsible for loading the localized resource file from the server.

getLocalizedString – responsible for returning a localized string based on the given key.

To begin with we declare the local variable localize that will be used as a wrapper for our object. We then need a couple of internal variables so we can store local data the service will use.

language – stores the user’s browser language. We will use this value to build the Url in order to request the appropriate localized resource file.

dictionary – stores the localized resource file.

resourceFileLoaded – indicates if the service has loaded the localized resource file, and is used to self init the service if needed.

I’ve also added a callback function that will be used when our http request succeeds. It will take the data retrieved from the web server and store it in the dictionary, update the init flag and broadcast that the localized resource file has been loaded.

The initLocalizedResources function takes the language we got from the user’s browser and creates a Url we can use to request the localized resource file from the web server. We also provide an error callback function should the request fail. By default we’ll assume there is no localized resource file and will request the default resource file.

The getLocalizedString function is called by consumers of the service to get the localized string for a specific key. By default we’ll return an empty string if the dictionary has not been loaded or there is no entry in the dictionary for the key.

Next, the function checks to see if the service has been initialized, if not it calls the initLocalizedResources function and then sets the init flag so we do not go into a continuous loop.

Finally, the function checks the dictionary for the key using the $filter service to retrieve the objects that match the filter parameters of key = value and then we further reduce the returned values by only taking the first item in the array. If we have a valid entry then the function returns the value and processing is complete.

Building the Filter

Now that we’ve finished with the service, let’s build the filter so we can easily use the service in our HTML.

First we’ll start off by chaining the filter definition off the factory definition by appending .filter() to the end of method. We’ll then define our filter by setting it’s name to ‘i18n’ along with the filter definition function that will be used to return the filter when called.

Next since the filter will be making calls to the localization service on behalf of our app we need to add the ‘localize’ service to our dependency list and ensure we include it in the filter definition function declaration.

The rest of the code for the filter is pretty simple since all we are doing is passing through the request to the localize service. so we are just going to return a function that calls the localize service with the given input.

That pretty much all we have to do. The final code for both the service and filter is given below:

A Sample App

So now we have a service and a filter, but we need to show how to use both in an app. So included in the project on GitHub is a sample app that uses the service and filter to populate all of the text displayed in both the index.html and two partials.

First we need to add a dependency to our app so, it will load our service and filter at bootstrap time. so we are going to add the name of the module, ‘localization’, into the app’s dependency list as shown in the code below:

Now when ever we need to pull a localized string, we can use the filter. There are two ways you can call the filter, inside of a ng-bind method and by enclosing it inside of {{ }}. Below is a example of how to use each:

Remember, you must use the {{ }} notation when you are not passing the value to a angular directive, if you don’t then the compiler will not handle the expression correctly and you’ll end up with text like ‘_BioLabel_’ | i18n all over your web page.

Below are two examples of the filter in use, the first is using the default of U.S. English and the second is when you change Chrome’s language to display Spanish. Since I don’t speak Spanish, I’ve converted the resource strings into Pig Latin so you can see the difference.

The Wrap Up

So in this article, I covered how to build a simple service and filter that can be used to localize you application based on the user’s language settings. By using a filter to front-end the service you have separated the concerns and provided a cross cutting service that can be used by your app without any of the controllers needing to deal with the service.

I also covered the basics of defining a service and a filter, injecting dependencies into both the service and filter, as well as how to use a filter in your HTML markup and directives.

Although this service handles a good share of the localization challenge for you a more advanced version would take advantage of Angular’s ng-pluralize directive and $locale service to help handle the harder semantics of language pluralization and gender. Hopefully it will get you started on the way to localizing you AngularJS apps and by following tutorial you’ve learned a little bit more about AngularJS.

29 thoughts on “Localizing Your AngularJS App”

Maximiliansays:

Great work. I tried to create a similar solution but it’s not as detailed as yours.
About $window and $locale: as far as I found out the surroundings for that ‘en-us’ is indeed kind of hard coded into ‘default’ angularJS if you don’t provide any localisation file from their i18n. If you include maybe ‘angular-locale_ar-eg.js’ in your index.html your $locale.id will change to ‘ar-eg’
My big problem is still how to include these files dynamically.

Great article! I’m currently using my own “home grown” approach but I prefer yours. My solution was to create a t(…) method similar to rails and it accepts an optional object for interpolation. So, given my locale resource…

You could modify the filter to accept the text string and an optional data object. In the filter function find/replace the data object key with the data. For example data object = {email: ’email@adress.com’}, in i18n string ‘Your email address is {{email}}’ and then in filter function you replace {{email}} with data.email using a regex.

Actually, if you look at the code in GitHub. I have already provided a way to handle dynamic data like you are looking for. There are a series of directives that will take dynamic data and populate them. I just need to provide a new post that describes how to use them.

How would I use this in javascript, programmatically? I tried using $filter(‘i18n’)(‘some-key’) which works if the language resources have been loaded. If they have not, it returns an empty string. I could listen for the event ‘localizeResourcesUpdates’, but I would like to avoid that if possible. The view seems to work somehow anyway, it doesn’t use the event. If I delay the resource loading, the view starts out empty and automatically populates when the language resources are available. How does that work? Can we do similar for the javascript filtering calls?

Filters are re-evaluated every time the scope is digested, so once the resource files are loaded they should update automatically.

If you want you can add a .run method to your main app modules that injects the localize service and then call initLocalizedResources which will force the service to pull the file before the app starts up.

Hi everyone,
I’m relatively new to Angular JS and web-development in general. I’m trying to use your library in my application for a while now but I didn’t manage to get it running.
I followed the tutorial/set up steps and debugged a lot. I think I found why it’s not working for me.

In the “localize.js” file the following lines return an “undefined”, although the dictionary is set up correctly.
“var entry = $filter(‘filter’)(localize.dictionary, function(element) {
return element.key === value;
}
)[0];”

Does someone know why this is happening? As already mentioned I’m still a ‘noob’ and now running low on ideas :/.

When say ‘link to a string’ are you talking about an a tag or are you talking about a dynamic string that is is created using a tokenizer?

If you are talking about the later, check the source code out on Github, we’ve added tokenization so you can have a default string Hello {0}, that gets turned into Hello Diego, whenever the library sees KEY|VALUE.