Translations for Angular

I've been working a lot recently with Angular.JS, and building apps that will (eventually) need to be translated to different languages. Despite the fact that Angular is reputed to be the "Kitchen Sink" of Single Page App frameworks it doesn't actually include any out of the box support for translation. Fortunately, it was easy to cobble together one in a very simple fashion.

The practice of translation is basically mapping one string onto another. JavaScript provides a very simple has mapping structure that works very well for this kind of work, and Angular allows us to turn those maps easily into "Constants" that are available throughout the app. I put constants in quotes, because they're not really constants, since JavaScript doesn't support constants in most browsers, but Angular approxmiates constants pretty well.

Here's an example of the string mapping that I set up:

(function(angular) {
    "use strict";

    var ENGLISH = {
        // Simple Translations
        "Toggle Errors": "Toggle Errors",

        // Longer messages
        "validation-error": "An error occured validating your document.",
        "validation-success": "Your document was successfully validated."
    };

    angular.module("greylurk.translationConstant",[])
        .constant('language',ENGLISH);
})(angular);

Obviously this table is just a short sample of my translations, but it can be expanded easily.

Now how would you use this translation system? There are two different ways I can think of to do this in angular: Directives and Filters. Directives provide a lot of power for interacting with the DOM. You can apply a Directive to an HTML element, either as an attribute, class, or a separate element itself. Filters on the other hand, are simply functions that accept one or more inputs and return a string. A filter sounds like it'll work fantastically well for translation, so I whipped up this quick filter:

(function(angular) {
    "use strict";

    TranslateFilter.$inject = ['language'];

    function TranslateFilter(language) {
        return function(input) {
            var translation;
            if( input in language ) {
                translation = language[input];
            } else {
                translation = input;
            }
            return translation;
        }
    }

    angular.module("greylurk.translationFilter",["greylurk.translationLConstant"])
        .filter("translate", TranslateFilter);
})(angular);

This filter will accept a string, look for it in the translation table, and if it finds it, translate it. If it can't find the string in the translation table, it will return the string unchanged. This prevents mysterious errors where the string isn't in your translation file.

Now, in any views/tempaltes rendered by Angular, you can get a translated string simply by applying the filter to any english text that you are inserting into the document:

<div ng-show="vm.showErrors">
    <div ng-if="vm.error">{{ "validation-error" | translate }}</div>
    <div ng-if="!vm.error">{{ "validation-success" | translate }}</div>
</div>
<button ng-click="vm.showErrors = !vm.showErrors">{{ "Toggle Errors" | translate }}</button>

I quickly realized I'd also need a string templating service to handle more complex error messages which require substitutions and string formatting, which I might go into more in a future post, leveraging the $interpolate service in angular.

Of course, a few days after I implemented this, I found out about angular-translate from a dev on a different team. Fortunately, it looks like angular-translate is a simple drop-in replacement for the translation stuff I built.