// Tutorial //

Internationalization of AngularJS Applications

Draft updated on Invalid Date
Default avatar
By Sergey Gospodarets
Developer and author at DigitalOcean.
Internationalization of AngularJS Applications

This tutorial is out of date and no longer maintained.

Introduction

There is a time for each project when internationalization (i18n) becomes absolutely needed. Sometimes it’s because of regional specific customers or when the application has to be shown to people in many countries. Usually, when its architecture is not ready for that- it starts becoming a really painful process.

In the AngularJS world, there’s the awesome angular-translate (it’s used for handling language translation stuff) and the angular-dynamic-locale (it’s used for changing angular $locale- which means formatting dates, numbers, currencies, etc.) libraries.

On the other hand, we have the really popular Yeoman AngularJS generator, which is widely used for scaffolding AngularJS applications.

Let’s provide asynchronous translate (without page reload) to Yeoman’s AngularJS application.

Note: Some of the code examples in this article are shown in a reduced manner to show the main code and idea.

These symbols ... mean that some parts of the code are left out.

Creating an AngularJS App with Yeoman

In GitHub, you can find detailed instructions on how to create applications using generator-angular.

In our case just run:

  1. npm install -g generator-angular
  2. mkdir angular-translate-yeoman && cd $_
  3. yo angular translate

Answer "Yes" to all questions regarding the decision to use Sass and Sass Bootstrap versions. Leave the default list of AngularJS modules.

After a while you will have the Yeoman-based Angular application with some pre-generated file structure and pre-defined controllers, views, and even some tests.

To run it in you browser with the live-reload, on any changes, just run:

  1. grunt serve

It might look similar to that.

Adding i18n

  1. We have to download libraries and include them in the project.
  2. Then we’ll need to provide translation files and set them working.
  3. Next, we will change angular $locale after the language change.
  4. Once step 4 is complete, we need to have the drop-down with the list of languages to select from.
  5. Lastly, the selected language should be stored and applied after the page reloads.

Adding Bower Dependencies

Run in the shell:

  1. ### ANGULAR-TRANSLATE STUFF
  2. # adds angular-translate library
  3. bower install --save angular-translate
  4. # util for asynchronous loading translations files
  5. bower install --save angular-translate-loader-static-files
  6. # util to save selected language preferences to localStorage
  7. bower install --save angular-translate-storage-local
  8. # util to track missed IDs in translation files
  9. bower install --save angular-translate-handler-log
  10. # ANGULAR-DYNAMIC-LOCALE STUFF
  11. # adds angular-dynamic-locale library
  12. bower install --save angular-dynamic-locale
  13. # list of predefined settings for each locale
  14. bower install --save angular-i18n

Applying translations in the templates

Let’s add a translation for Splendid! text on the green button. We can do it by applying to this text translate filter in app/views/main.html:

{{ "views.main.Splendid!" | translate }}

I also provided a prefix for translations to quickly figure out where they are used:

views.main.Splendid! -> views folder, file main.html

Using this, in this way, you can provide translations for all your templates.

Providing Translations Files

Create files app/resources/locale-{{locale}}.json where {{locale}} - needed locales to handle. In the current example I’ll provide the following locales:

app/resources/locale-en_US.json for English (United States):

    {
        "views.main.Splendid!": "Splendid!",
        "directives.language-select.Language": "Language"
    }

app/resources/locale-ru_RU.json for Russian (Russia):

    {
        "views.main.Splendid!": "Отлично!",
        "directives.language-select.Language": "Язык"
    }

directives.language-select.Language will be used in the languages dropdown.

Including angular-translate and dynamic loading in AngularJS applications

In appscriptsapp.js file:

Add dependencies for the main app:

    angular.module('translateApp', [
     ...
     'pascalprecht.translate',// angular-translate
     'tmh.dynamicLocale'// angular-dynamic-locale
    ])

Provide info about locales and the preferred locale which are used in your app (key values will be used in languages dropdown):

    .constant('LOCALES', {
        'locales': {
            'ru_RU': 'Русский',
            'en_US': 'English'
        },
        'preferredLocale': 'en_US'
    })

To get warnings in the developer console, regarding forgotten IDs in translations, just add:

    .config(function ($translateProvider) {
        $translateProvider.useMissingTranslationHandlerLog();
    })

Next step is about adding asynchronous loading for the translations:

    .config(function ($translateProvider) {
        $translateProvider.useStaticFilesLoader({
            prefix: 'resources/locale-',// path to translations files
            suffix: '.json'// suffix, currently- extension of the translations
        });
        $translateProvider.preferredLanguage('en_US');// is applied on first load
        $translateProvider.useLocalStorage();// saves selected language to localStorage
    })

And, finally, provide the config with direction of where to load the $locale settings files for angular-dynamic-locale:

    .config(function (tmhDynamicLocaleProvider) {
        tmhDynamicLocaleProvider.localeLocationPattern('bower_components/angular-i18n/angular-locale_{{locale}}.js');
    })

AngularJS service for getting / setting current locale

We need to have some Services to change language and apply some additional logic (e.g. change AngularJS $locale). This will be used for creation and interaction with the languages drop down later.

Create app/scripts/services/LocaleService.js file with the following content:

    angular.module('translateApp') .service('LocaleService', function ($translate, LOCALES, $rootScope, tmhDynamicLocale) {
        'use strict';
        // PREPARING LOCALES INFO
        var localesObj = LOCALES.locales;

        // locales and locales display names
        var _LOCALES = Object.keys(localesObj);
        if (!_LOCALES || _LOCALES.length === 0) {
          console.error('There are no _LOCALES provided');
        }
        var _LOCALES_DISPLAY_NAMES = [];
        _LOCALES.forEach(function (locale) {
          _LOCALES_DISPLAY_NAMES.push(localesObj[locale]);
        });

        // STORING CURRENT LOCALE
        var currentLocale = $translate.proposedLanguage();// because of async loading

        // METHODS
        var checkLocaleIsValid = function (locale) {
          return _LOCALES.indexOf(locale) !== -1;
        };

        var setLocale = function (locale) {
          if (!checkLocaleIsValid(locale)) {
            console.error('Locale name "' + locale + '" is invalid');
            return;
          }
          currentLocale = locale;// updating current locale

          // asking angular-translate to load and apply proper translations
          $translate.use(locale);
        };

        // EVENTS
        // on successful applying translations by angular-translate
        $rootScope.$on('$translateChangeSuccess', function (event, data) {
          document.documentElement.setAttribute('lang', data.language);// sets "lang" attribute to html

           // asking angular-dynamic-locale to load and apply proper AngularJS $locale setting
          tmhDynamicLocale.set(data.language.toLowerCase().replace(/_/g, '-'));
        });

        return {
          getLocaleDisplayName: function () {
            return localesObj[currentLocale];
          },
          setLocaleByDisplayName: function (localeDisplayName) {
            setLocale(
              _LOCALES[
                _LOCALES_DISPLAY_NAMES.indexOf(localeDisplayName)// get locale index
                ]
            );
          },
          getLocalesDisplayNames: function () {
            return _LOCALES_DISPLAY_NAMES;
          }
        };
    });

Language Dropdown

In this part, we will add the language drop-down and set actions to changing the language in it. This select element has to be shown only when there is more than 1 language provided.

Let’s create an AngularJS directive app/scripts/directives/LanguageSelectDirective.js:

    angular.module('translateApp') .directive('ngTranslateLanguageSelect', function (LocaleService) { 'use strict';

            return {
                restrict: 'A',
                replace: true,
                template: ''+
                '<div class="language-select" ng-if="visible">'+
                    '<label>'+
                        '{{"directives.language-select.Language" | translate}}:'+
                        '<select ng-model="currentLocaleDisplayName"'+
                            'ng-options="localesDisplayName for localesDisplayName in localesDisplayNames"'+
                            'ng-change="changeLanguage(currentLocaleDisplayName)">'+
                        '</select>'+
                    '</label>'+
                '</div>'+
                '',
                controller: function ($scope) {
                    $scope.currentLocaleDisplayName = LocaleService.getLocaleDisplayName();
                    $scope.localesDisplayNames = LocaleService.getLocalesDisplayNames();
                    $scope.visible = $scope.localesDisplayNames &&
                    $scope.localesDisplayNames.length > 1;

                    $scope.changeLanguage = function (locale) {
                        LocaleService.setLocaleByDisplayName(locale);
                    };
                }
            };
        });

Including locale service and language select

And don’t forget to include these scripts and language selects in the app/index.html:

    ...
    <div ng-translate-language-select></div>
    ...

    <!-- build:js({.tmp,app}) scripts/scripts.js -->
    ...
    <script src="scripts/services/LocaleService.js"></script>
    <script src="scripts/directives/LanguageSelectDirective.js"></script>
    <!-- endbuild -->

Updating Grunt Config

The last thing is we have to let Grunt know about our additions.

For that we will update Gruntfile.js:

  • "live-reload" task config - will add live reload on changing of translations.
    // Watches files for changes and runs tasks based on the changed files
    watch: {
        ...
        livereload: {
            ...
            files: [
                ...
                '<%= yeoman.app %>/resources/{,*/}*.json'
            ]
        }
    }
  • "copy" task config - to copy resources files and locales settings to the result build.
    // Copies remaining files to places other tasks can use
    copy: {
        dist: {
            files: [{
                ...
                src: [
                    ...
                    'resources/{,*/}*.*'
                ]
            },
            ...
            {
            expand: true,
            cwd: 'bower_components/angular-i18n/',
            src: '*.js',
            dest: '<%= yeoman.dist %>/bower_components/angular-i18n'
            }]
        }
        ...
    }

Running

After that, you can test the text Splendid! and see that it really is changed after switching language in the dropdown. For that just run:

  1. grunt serve # will compile and open project in the browser

To test the distribution build just run:

  1. grunt # will create distribution version of the application

And then open dist/index.html in your browser.

Demo

You can play with the working project at GitHub.

As you can see, until the page is loaded, the translations with $locale are not set and an animation is shown. Also, this can be due to the added on language change.

On changing the language, you can see that the page title, all of the content, and the time format have changed.

You can compare which parts of the original Yeoman AngularJS project were changed to add localization looking at GitHub diff between the branches with the clear AngularJs Yeoman app and with the applied asynchronous translation.

Tips and tricks

In this section, we will review some examples of providing language-specific content.

Templates

Text

As above, we can apply translations for templates using e.g. translate filter:

{{ "views.main.Splendid!" | translate }}

There are also a couple of other techniques.

Title and attributes

To apply a changing page title and a meta[name="description"] attribute (which is used to provide text for sharing in social networks), you can use angular ng-bind and ng-attr-content directives (see how it’s done in the demo app):

<title ng-bind="pageTitle">i18n for your AngularJS applications</title>
<meta name="description" ng-attr-content="{{pageContent}}" content="How to translate your AngularJS applications without page reload with angular-translate">

and to provide an update to these fields in controller:

    $translate(pageTitleTranslationId, pageContentTranslationId) // ON INIT
     .then(function (translatedPageTitle, translatedPageContent) {
      $rootScope.pageTitle = translatedPageTitle;
      $rootScope.pageContent = translatedPageContent;
    });

    $rootScope.$on('$translateChangeSuccess', function (event, data) { // ON LANGUAGE CHANGED
     $rootScope.pageTitle = $translate.instant(pageTitleTranslationId);
     $rootScope.pageContent = $translate.instant(pageContentTranslationId);
    });

Images

You can replace your images when you switch the language providing {{locale}} part to ng-src attribute in your views:

    <img ng-src="images/no-filerev/yeoman-{{locale}}.png" />

And in the controller:

    $scope.locale = $translate.use(); // ON INIT

    $rootScope.$on('$translateChangeSuccess', function (event, data) { // ON LANGUAGE CHANGED
     $scope.locale = data.language;
    });

You, also, can check it out on the Home page (Yeoman image).

CSS

If you want to apply some specific CSS, depending on the current locale, you can do it, because in our example we provided the lang attribute for the <html/> tag with the current locale value. e.g.:

    [lang="ru_RU"]{
        /*some special styles*/
    }

You can see that by switching the language on the About page - it will, then, change the page layout.

Conclusion

Step-by-step we have provided localization for our Yeoman AngularJS app.

We understand how to create translations files, handling translations in html-templates, and AngularJs controllers.

Also, we discussed how to provide specific styles/images/page titles and even some HTML attribute values for different locales.

Who knows, maybe it’s a good idea for creating a Yeoman “angular-translate” generator.

Let me know what your experience has been with the localization? Would you like to see some more of the above or some other parts to be highlighted/described in more detail? I want to hear from you!

All code and working AngularJS applications, with asynchronous languages loading, can be found in GitHub.


Want to learn more? Join the DigitalOcean Community!

Join our DigitalOcean community of over a million developers for free! Get help and share knowledge in our Questions & Answers section, find tutorials and tools that will help you grow as a developer and scale your project or business, and subscribe to topics of interest.

Sign up
About the authors
Default avatar
Developer and author at DigitalOcean.

Still looking for an answer?

Was this helpful?
Leave a comment