AngularJS, code.explode, Coding

Localizing Your AngularJS Apps: Yet Another Update

So you are probably really jazzed about the addition of the new directive to the angularjs-localizationservice project and the fixes to help improve it’s performance. But, now that you are probably using the project I bet you are probably saying the same thing my devs and designers were saying, “Hey Jim, this new directive really works great, but how do we do translations for placeholders and titles?”

Well, this post is going to cover a new directive that’s been added to the project just to handle such things.

The i18n Attribute Directive

In the beginning there was the localize service which provided you with localized resource strings, but did nothing for binding them into your HTML. Next came along the i18n filter which did a good job of binding the localized string into your HTML, but proved to be a performance pig and couldn’t handle dynamic values in a localize string. Then came along the i18n directive which helped to improve performance and handle dynamic values in localized strings.

But, then came the need to have all text within the page localized, even that text which was hidden away from the end user, and so, along came the i18n Attribute directive, which handle just such a problem.

The Code

The i18n Attribute directive is pretty much the same as the i18n directive except it expects an additional parameter to be appended to the localization resource string token which indicates which attribute to set the value when the localized string is returned.

If you want to pass dynamic values for the string, those should come after the attribute parameter. Then the directive will walk through and replace the numbered place holders with their values.

    .directive('i18nAttr', ['localize', function (localize) {
        var i18NAttrDirective = {
            restrict: "EAC",
            updateText:function(elm, token){
                var values = token.split('|');
                // construct the tag to insert into the element
                var tag = localize.getLocalizedString(values[0]);
                // update the element only if data was returned
                if ((tag !== null) && (tag !== undefined) && (tag !== '')) {
                    if (values.length > 2) {
                        for (var index = 2; index < values.length; index++) {
                            var target = '{' + (index - 2) + '}';
                            tag = tag.replace(target, values[index]);
                        }
                    }
                    // insert the text into the element
                    elm.attr(values[1], tag);
                }
            },
            link: function (scope, elm, attrs) {
                scope.$on('localizeResourcesUpdated', function() {
                    i18NAttrDirective.updateText(elm, attrs.i18nAttr);
                });

                attrs.$observe('i18nAttr', function (value) {
                    i18NAttrDirective.updateText(elm, value);
                });
            }
        };

        return i18NAttrDirective;
    }]);

How to Use It

Using the i18n Attribute directive is pretty much the same as using the i18n directive. The only difference is that the name of the attribute you want to populate.

So if we have a local resource file that looks like below:

[
    {
        "key":"_Greeting_",
        "value":"Site localization example using the resource localization service",
        "description":"Home page greeting text"
    },
    {
        "key":"_HomeTitle_",
        "value":"Resource Localization Service",
        "description":"Home page title text"
    },
    {
        "key":"_HomeControllerTitle_",
        "value":"List Example",
        "description":"Home Pane Title"
    },
    {
        "key":"_HomeControllerBlob_",
        "value":"Now is the time for all good men to come to the aide of their country.",
        "description":"text example"
    },
    {
        "key":"_FormControllerTitle_",
        "value":"Form example",
        "description":"Add/Edit Person Form Title"
    },
    {
        "key":"_FirstNameLabel_",
        "value":"First Name",
        "description":"Label for first name field"
    },
    {
        "key":"_LastNameLabel_",
        "value":"Last Name",
        "description":"Label for last name field"
    },
    {
        "key":"_EMailLabel_",
        "value":"Email",
        "description":"Label for email field"
    },
    {
        "key":"_BioLabel_",
        "value":"Bio (tell us something about you) ",
        "description":"Label for biography field"
    },
    {
        "key":"_SaveButtonLabel_",
        "value":"Save",
        "description":"Label for Save button"
    },
    {
        "key":"_CancelButtonLabel_",
        "value":"Cancel",
        "description":"Label for Cancel button"
    },
    {
        "key":"_NameHeader_",
        "value":"Name",
        "description":"Person List Name Header"
    },
    {
        "key":"_EmailHeader_",
        "value":"Email",
        "description":"Person List Name Header"
    },
    {
        "key":"_BioHeader_",
        "value":"Biography",
        "description":"Person List Bio Header"
    }
]

We can set the placeholders of a registration form like so:

<div class="container-fluid" >
    <div class="row-fluid">
        <h2 data-i18n="_FormControllerTitle_"></h2>
    </div>
    <div class="row-fluid">
        <form name="myForm" class="form-horizontal span5 well">
            <div class="row-fluid">
                <input type="text" ng-model="person.FirstName" required id="FirstName" name="FirstName" class="input-large" data-i18n-attr="_FirstNameLabel_|placeholder" />
            </div>
            <div class="row-fluid">   </div>
            <div class="row-fluid">
                <input type="text" ng-model="person.LastName" required id="LastName" name="LastName" class="input-large" data-i18n-attr="_LastNameLabel_|placeholder"/>
            </div>
            <div class="row-fluid">   </div>
            <div class="row-fluid">
                <input type="text" ng-model="person.Email" required id="Email" name="Email" class="input-large" data-i18n-attr="_EMailLabel_|placeholder"/>
            </div>
            <div class="row-fluid">   </div>
            <div class="row-fluid">
                <textarea ng-model="person.Bio" required id="Bio" name="Bio" class="input-xxlarge" data-i18n-attr="_BioLabel_|placeholder"></textarea>
            </div>
            <div class="row-fluid">   </div>
            <div class="row-fluid">
                <button class="btn-primary" ng-click="savePerson()" data-i18n="_SaveButtonLabel_"></button>
                <button class="btn-primary" ng-click="cancel()" data-i18n="_CancelButtonLabel_"></button>
            </div>
        </form>
    </div>
</div>

You can do the same with any other attribute where you need the text to change with the language.

Updating values dynamically is as easy as setting up your resource string with the numbered placeholders, such as; “This is a multi replace test response {0} {1}” and add the parameters to the end of the localization token like so, TEST_ITEM2|title|ABC|123, which will result in the title attribute to be set to title=”This is a multi replace test response ABC 123”.

The Wrap Up

Hopefully, the changes I’ve made to the library will provide you what you need to address the heavy lifting of making your app support multiple languages and you find it useful.

As part of the latest update I’ve also added unit tests for the service, filter and both directives to give you an idea how things work and as always you have the sample app to play with as well.

If you find an issue or come up with an enhancement you’d like to see included in the library, file an issue or send me a pull request and I’ll work on getting it resolved or added into the library.

Good Luck!

Standard
Loading Facebook Comments ...

One thought on “Localizing Your AngularJS Apps: Yet Another Update

  1. Gael BALES says:

    Great job – maybe can be improved by modifying Angular $locale

    this.$get = [‘$http’, ‘$rootScope’, ‘$window’, ‘$filter’, ‘$locale’, function ($http, $rootScope, $window, $filter, $locale)

    // allows setting of language on the fly
    setLanguage: function(value) {
    localize.language = this.fallbackLanguage(value);
    localize.initLocalizedResources();
    $locale.DATETIME_FORMATS = that.angularLocale[value].DATETIME_FORMATS;
    $locale.NUMBER_FORMATS = that.angularLocale[value].NUMBER_FORMATS;
    $locale.id = value;
    },

    ….

    that.angularLocale[value] is json cotent from i18n files from angular.

    See http://sbouchard.github.io/angular-dynamic-i18n/#!/usage for more…

Got a comment? I'd love to hear it!