Internationalization of AngularJS Applications

Free Course

Getting Started with Angular 2

Angular 2 is the shiny new framework that comes with a lot of new concepts. Learn all the great new features.

There is a time for each project when internationalization 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.

Be sure to checkout the demo.

i18n-angular-applications

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:

npm install -g generator-angular

mkdir angular-translate-yeoman && cd $_

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:

yeoman-angular-install

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:

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:

### ANGULAR-TRANSLATE STUFF
# adds angular-translate library
bower install --save angular-translate

# util for asynchronous loading translations files
bower install --save angular-translate-loader-static-files
# util to save selected language preferences to localStorage
bower install --save angular-translate-storage-local
# util to track missed IDs in translation files
bower install --save angular-translate-handler-log

# ANGULAR-DYNAMIC-LOCALE STUFF
# adds angular-dynamic-locale library
bower install --save angular-dynamic-locale

# list of predefined settings for each locale
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 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 are more than 1 languages 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 dropdown. For that just run:

grunt serve # will compile and open project in the browser

To test the distribution build just run:

grunt # will create distribution version of the application

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

Demo

You can play with working project at Github.

As you can see, until 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 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 current locale, you can do it, because in our example we provided the lang attribute for the <html/> tag with 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 creation 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!

P.S.

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.

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

Serg

JS, HTML and CSS enthusiast.

Author of Serg Hospodarets Blog.

Enjoying life :)