1/12/2018 - 12:24 PM

Angular Internationalization Translations

// ----------------------------------------------------------
// https://malyw.github.io/angular-translate-yeoman/dist/
// *Some of the code examples in this article are shown in a reduced manner. CHECK THE REPO FOR COMPLETE CODE
// Repo: https://scotch.io/tutorials/internationalization-of-angularjs-applications
// Demo: https://hospodarets.com/angular-translate-yeoman/dist/#/

// Let's provide asynchronous translate (without page reload) to our AngularJS application. We'll use:

//1) Download libraries and include in project

// ANGULAR-TRANSLATE STUFF (handles language translation stuff)
//adds angular-translate library
npm install --save angular-translate
//util for asynchronous loading translations files
npm install --save angular-translate-loader-static-files
//util to save selected language preferences to localStorage
npm install --save angular-translate-storage-local
//util to track missed IDs in translation files
npm install --save angular-translate-handler-log

// ANGULAR-DYNAMIC-LOCALE STUFF (changes angular $locale, which means formatting dates, numbers, currencies, etc)
//adds angular-dynamic-locale library
npm install --save angular-dynamic-locale
//list of predefined settings for each locale
npm install --save angular-i18n
//**ALTERNATIVE: angular-translate-storage-local

//Including angular-translate and dynamic loading in AngularJS applications

angular.module('translateApp', [
 'pascalprecht.translate',// angular-translate
 'tmh.dynamicLocale'// angular-dynamic-locale
.constant('LOCALES', { //Provide info about locales
    'locales': {
        'ru_RU': 'Русский', //key values will be used in languages dropdown
        'en_US': 'English'
    'preferredLocale': 'en_US' //preferred locale which are used in your app
.config(function ($translateProvider) {
    $translateProvider.useMissingTranslationHandlerLog(); //get warnings in the developer console: forgotten IDs in translations
.config(function ($translateProvider) { //adding asynchronous loading for the translations
        prefix: 'resources/locale-',// path to translations files
        suffix: '.json'// extension of the translation files
    $translateProvider.preferredLanguage('en_US');// is applied on first load
    $translateProvider.useLocalStorage();// saves selected language to localStorage
//finally, provide the config with direction of where to load the $locale settings files for angular-dynamic-locale:
.config(function (tmhDynamicLocaleProvider) {

//2) Providing Translations Files and applying translations in the templates
//Create one file per locale you need, follwowing this syntax:
app/resources/locale-en_US.json //for English (United States):
    "views.main.Splendid!": "Splendid!", //naming conventions: 'views' folder, 'main.html' file
    "directives.language-select.Language": "Language" //* will be used in languages dropdown
app/resources/locale-ru_RU.json //for Russian (Russia):
    "views.main.Splendid!": "Отлично!",
    "directives.language-select.Language": "Язык"

// so you can use like this in the templates
{{"views.main.Splendid!" | translate}} 

//3) AngularJS service for getting / setting current locale + Language dropdown
// we need a service 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.


angular.module('translateApp') .service('LocaleService', function ($translate, LOCALES, $rootScope, tmhDynamicLocale) {
    'use strict';
    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');
    _LOCALES.forEach(function (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');
      currentLocale = locale;// updating current locale

      // asking angular-translate to load and apply proper translations

    // 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) {
            _LOCALES_DISPLAY_NAMES.indexOf(localeDisplayName)// get locale index
      getLocalesDisplayNames: function () {
        return _LOCALES_DISPLAY_NAMES;

// Language Dropdown
//This select element has to be shown only when there are more than 1 languages provided.

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

            $scope.changeLanguage = function (locale) {

// Including locale service and language select
<div ng-translate-language-select></div>


// apply translations for templates using translate filter:
{{"views.main.Splendid!" | translate}}

//To apply a changing page title and a meta[name="description"] attribute 
//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);

//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;

//apply some specific css depending on current locale
//in our example we provided the lang attribute for the <html/> tag with current locale value **CHECK ESTO**
//so we can do:
    /*some special styles*/
$ npm install --save angular-gettext
import gettext from 'angular-gettext';

//Configuring angular-gettext
angular.module('myApp', [gettext])
  .run(function (gettextCatalog) { //You can do this anywhere in your application, the use of a run block above is just an example.
    gettextCatalog.setCurrentLanguage('nl'); //Set the language
    gettextCatalog.debug = true; //Eenable a debugging mode: will prepend [MISSING] to untranslated strings

//1) Annotate your application using the 'translate' directive
// mark the strings that are translatable
<h1 translate>Hello!</h1>
//You can also use the translate directive as an element:
//interpolation support is available in translated strings
<div translate>Hello {{name}}!</div>
//Plural strings can be annotated using two extra attributes: translate-n and translate-plural:
<div translate translate-n="COUNTEXPR" translate-plural="PLURALSTR">SINGULARSTR</div>
//Depending on the value of COUNTEXPR, either the singular string or the plural string will be selected.
//Inside the strings, you can use any variable that is available in the scope. 
//A special $count variable is also available, which resolves to the value of the translate-n attribute (accepts any valid Angular.JS expression, even functions).
<div translate translate-n="boatCount" translate-plural="{{$count}} boats">One boat</div>

//Sometimes it's not an option to use an attribute (e.g. when you want to annotate an attribute value). 
//Here you use filter instead of directive.
<input type="text" placeholder="{{'Username'|translate}}" />
//This filter does not support plural strings.

//*You may want to use custom annotations <https://angular-gettext.rocketeer.be/dev-guide/custom-annotations/> to avoid using the translate filter all the time.

//Contexts can be added via 'translate-context' to strings to be translated. 
//The translation for the same string in a different context can be different.
<h1 translate-context="Verb" translate>File</h1>
<h1 translate-context="Noun" translate>File</h1>
//The contexts will appear in the catalog (as msgctxt).
//In the application Poedit, the contexts will appear under "Context" (ver pantallazo)

//Comments can be added to the catalog to help translators with the context of the translatable string.
<div translate-comment="{{name}} expands to the logged in user name" translate>Hello {{name}}!</div>
//In the application Poedit, the comment will appear under "Notes for translators" section (ver pantallazo)

// If you have text that should be translated in your JavaScript code, wrap it with a call to a function named gettext.
// This module provides an injectable function to do so:
angular.module("myApp").controller("helloController", function (gettext) {
    var myString = gettext("Hello"); //Hello string will be added to your .pot file
// update your Grunt/Webpack task to include the JavaScript files:
  nggettext_extract: {
    pot: {
      'po/template.pot': ['src/views/*.html', 'src/js/*.js']

//You can inject gettextCatalog to fetch translated strings in JavaScript. Use the getString method for this.
angular.module("myApp").controller("helloController", function (gettextCatalog) {
    var translated = gettextCatalog.getString("Hello"); //Any string passed to gettextCatalog.getString() is automatically marked for translation. In other words: no need to annotate these strings with gettext().
//This also works for plurals:
angular.module("myApp").controller("helloController", function (gettextCatalog) {
    var myString2 = gettextCatalog.getPlural(3, "One Bird", "{{$count}} Birds", {});
//*It's not recommended to put translated strings on the scope. Use the 'filter' for translations in views. 
//*The JavaScript API is meant for strings that are used in library code.

//You can pass a data object to interpolate the translated strings:
var translated = gettextCatalog.getString("Hello {{name}}", { name: "Ruben" });

//you can insert context comments for poedit using the triple forward slash syntax of gettext:
angular.module("myApp").controller("helloController", function (gettext) {
    /// Verb
    var myString = gettext("File");

//You can set translated strings by injecting the gettextCatalog and using the setStrings method.
angular.module("myApp").run(function (gettextCatalog) {
    // Load the strings automatically during initialization.
    gettextCatalog.setStrings("nl", { //language code
    	//dictionary of strings:
    	// - Keys: Singular English strings (as defined in the source files)
		// - Values: Either a single string for signular-only strings or an array of plural forms.
        "Hello": "Hallo",
        "One boat": ["Een boot", "{{$count}} boats"]

//* CHECK: CUSTOM ANNOTATIONS: https://angular-gettext.rocketeer.be/dev-guide/custom-annotations/

//2) Extracting strings
// Once you've annotated your application is time to extract these strings into a translation template: a standard .pot file
// Angular-gettext-tools (grunt) is the official library to extract strings from HTML templates and JavaScript code. Several wrappers exist that integrate it with other tools:
// Third party
// - CLI utility: https://github.com/huston007/angular-gettext-cli
// - Webpack loader (compilation):https://github.com/princed/angular-gettext-loader
// - ***[ESTE ES EL GUAY, REVISAR] Webpack plugin (compilation and extraction):https://github.com/augusto-altman/angular-gettext-plugin

//3) Translating strings 
// There are several translation tools. Here we'll use Poedit: http://www.poedit.net/
// 1) Use the .pot template as the basis for translation. This will generate a new translation catalog (a .po file) or updates an existing .po file.
// 2) Start Poedit and choose 'File > New Catalog from POT File...' Locate your .pot file. 
// 3) In "Catalog properties" dialog:
// > Language: example 'fr'
// > Plural Forms: example 'nplurals=2; plural=(n > 1)' *be sure to not to put ';' at the end!
// Check here the correct values for these fields: http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms
//4) Save the new fr.po file in a 'po' folder, next to the .pot file (convention for gettext projects). Some gettext translation tools will also generate compiled .mo files (we won't be using them).
//5) Translate the strings and save your catalog.

//*If you make changes to your project later, simply run 'grunt' (or webpack) again to generate a new .pot template. 
// Then in poedit open your .po file and ' Catalogue > Update from POT File'
// This will update your translation catalog by adding new strings, removing obsolete ones and flagging slightly changed ones for review.

//4) Compiling translations 

// Converting translated .po files into angular-compatible JavaScript files can be done automatically using the grunt-angular-gettext module (or corresponding webpack https://github.com/augusto-altman/angular-gettext-plugin or CLI tool https://github.com/huston007/angular-gettext-cli)
// the task converts a set of translated .po files into a JavaScript file that can be included in your project.
// Be sure to add this file (in this case src/js/translations.js) to your project.
//*Optionally, you can specify a module parameter, which defines the Angular.JS module for which the translations JavaScript is generated:

//If you have a lot of languages (or a lot of strings) the resulting JavaScript file would become too big.
//There's another option: lazy-load the languages, only when you need them.

//Instead of compiling to JavaScript (the default), configure your grunt task to output JSON files (ver webpack plugin)
//This will generate a dist/languages/XX.json file for each po/XX.po.
//gettextCatalog() provides a 'loadRemote' method to easily load new languages. 
//Whenever you change the application languages, do something like this:

angular.module("myApp").controller("helloController", function ($scope, gettextCatalog) {
    $scope.switchLanguage = function (lang) {
        gettextCatalog.loadRemote("/languages/" + lang + ".json");
//All translations will be updated once the strings have been loaded.

//MORE INFO: https://angular-gettext.rocketeer.be/dev-guide/api/angular-gettext/