📜 ⬆️ ⬇️

Using angular-translate to localize AngularJS applications

Hi, Habr! This article will discuss the use of the angular-translate library to localize the application. We will tell about the possibilities of this library, describe the problems that may arise, and give tips on how to solve them (based, of course, on our own experience).


Introduction


For localization, that is, the translation of an application into another language, there is the concept of language resources . The essence of it is simple: instead of starting a constant with text, we put in the code some identifier, usually called the translation key, which can be used both during the application assembly and during its execution. When meeting the translation key, the application linker (or runtime) replaces it with the necessary text, and the user sees the inscription already in the desired language. Of course, the whole problem of translation is not solved by this. For example, lines of text may well have different lengths in different languages, and then the translation can spoil the user interface by issuing truncated inscriptions. A nightmare for a developer can be a translation into Arabic, where the text is read from right to left. Nevertheless, such technology takes place and is successfully used in a number of cases.

In the .Net world, the standard for working with language resources is * .Resx files. The resgen special utility turns them into * .resources. Files, after which these resources are embedded either in the main assembly or in auxiliary assemblies, and then can be used in the application.

In web projects, the same technology was originally used, and this solution was successful, since the pages were generated mainly on the server, where language resources could be placed. However, over time, the client part became more complicated, and the problems with it became more and more. They were usually solved in two ways: either they made alternative JS for different languages (this approach is used, for example, in MS SharePoint), or they transferred localized data as parameters in the code of * .ASPX-pages . It is clear that the second approach is more suitable for small blocks of client code — one where there are relatively few messages and test items. As for the first, its disadvantage is that localization is “smeared” between resource files and local versions of JavaScript.
')
Recently, in many applications, the UI has been generated directly on the client, the need for server resources has become less. At the same time, application development frameworks that implement rich enough programming models (such as knockout.js, angular.js, backbone.js, etc.) are widely used. Each model recommends its own project organization; therefore, it seems that the most convenient would be for the framework to have its own localization model.

So, we have an application that needs to be taught to work with another language. Of course, we understand that in an ideal world this should be taken care of at the design stage of the application, but in reality support for localization or “localizability” is something that is often sacrificed at the first stages of development for obvious reasons.

By the way, MSDN recommends adding support for ensuring work in 3 steps:
  1. Globalization - i.e. support for recording systems, calendars used, agreements on date and time format, agreements on the presentation of numerical and monetary values, as well as sorting rules.
  2. Localizability - i.e. separation of application logic from localizable resources.
  3. Localization - directly translate the application to the target language.

When developing an application using AngularJS, there are several ways to localize the application. The easiest way is to create separate page templates for all supported languages. The disadvantages of this approach are obvious: laboriousness, code duplication, lack of modularity, inconsistency with the SPA approach, etc. The best solution would be a universal way to translate pages based on resources (dictionaries). Here you can go two ways: implement your own localization service or use ready-made tools , such as the angular-translate library . We will tell about it in more detail.

The main features of the angular-translate library


The angular-translate library implements solid functionality for localization needs. It is represented by a set of services, filters and directives for localization. The library requires JSON files with keys and translations.

/* file: “~/fruits/en.json” */ /* file “~/fruits/ru.json” */ { { "APPLE": "Apple", "APPLE": "", "ORANGE": "Orange" "ORANGE": "" } } 

The library is able to load the necessary files from the server as needed (the so-called lazy-loading).
You can connect angular-translate to the project through the Bower package manager. Unfortunately, at the time of this writing, there was no package for angular-translate in the standard Nuget repository, so I had to manually add the angular-translate.js library and a link to it, and also add the “pascalprecht.translate” module to angualr as a dependency.

The main angular-translate service is the $ translate provider. At the configuration stage (note that you need to use the $ translateProvider call, for example ) the service allows you to register tables with embedded translations in the application, asynchronous loaders (urlLoader, staticFilesLoader, partialLoader), and also select a storage (cookie, local) for localization settings. At runtime, the service is available as $ translate, being both a function used for translation and an “object” containing functions for setting up the translation.

Transfer


In angular-translate translation can be done in three different ways:

Using the $ translate service :

  $translate('APPLE').then(function (result) { $scope.fruitName = result; }); 

$ translate does not provide a two-way binding default, with the result that when changing language at runtime, the text translated using $ trnaslate does not automatically change. This can be fixed by subscribing to the $ translateChangeSucces event in $ rootScope:

  $rootScope.$on('$translateChangeSuccess', function () { $translate('HEADLINE').then(function (translation) { $scope.headline = translation; }); }); 

$ trnaslate works asynchronously as standard, but it also supports the synchronous translation variant using the built-in “instant” method.

Using the “translate” directive :

 <ANY translate>TRANSLATION_ID</ANY> <ANY translate="TRANSLATION_ID"></ANY> 


Using the filter “... | translate ” :
 <p>{{'APPLE' | translate}}</p> 

Download dictionaries


In angular-translate there are several ways to add the necessary dictionaries (note that you can use namespaces in dictionaries, implemented using nested json objects):

Dictionaries built into the application
Placing dictionaries directly in the code and / or from json-files of resources in the project. Connects like this:

  var fruits = { APPLE: 'Apple' CITRIC: { ORANGE: 'Orange' } }; $translateProvider .translations('en', fruits) .preferredLanguage('en'); 

Asynchronous loading
Allows you to download dictionaries from the server as needed. It is implemented by standard loaders (it is possible to define your own loader):
  1. urlLoader . Loads dictionaries for the specified url. Implemented as a separate package in the angular-translate-loader-url Bower. Connects like this:
      $translateProvider.useUrlLoader('foo/bar.json'); $translateProvider.preferredLanguage('en'); 

    As a result, the loader from the example will send the following request:
     foo/bar.json?lang=en. 

  2. staticFilesLoader. Used to download multiple localization files of the given format (prefix and suffix). Implemented as a separate package in Bower. Connect as follows:
      $translateProvider.useStaticFilesLoader({ prefix: 'locale-', suffix: '.json' }); $translateProvider.preferredLanguage('en'); 

  3. partialLoader. Used to partially load localization data structured on the server by a pattern (for example, “/ l10n / {part} / {lang}” or “/ l10n / {lang} / {part}). It asynchronously loads only the specified files (parts) after calling the corresponding function (addPart). It is included in the package angular-translate-loader-partial. Example:
      $translatePartialLoaderProvider.addPart('fruits'); $translateProvider.useLoader('$translatePartialLoader', { urlTemplate: '/l10n/{part}/{lang}.json' }); $translateProvider.preferredLanguage('en'); 


Interpolated Variables

Angular-translate supports the presence of “interpolated variables” in json-dictionaries, for example:

 { "DELICIOUS_FRUIT": "{{fruit_name}} is delicious!" } 

Similar strings can be further used as follows:

Service:
 $translate('DELICIOUS_FRUIT', { fruit_name: 'Apple' }); 

By filter:
 {{ 'DELICIOUS_FRUIT' | translate:'{ fruit_name: “Apple” }' }} 

or
 {{ 'DELICIOUS_FRUIT' | translate: fruitData }} 

Where
  $scope.fruitData = { o fruit_name: 'Apple' o }; 

Directive:
  <ANY translate="DELICIOUS_FRUIT " translate-values='{ fruit_name: "Ap-ple"}'></ANY> <ANY translate="DELICIOUS_FRUIT "translate-values="{ fruit_name: fruitData.fruit_name }"></ANY> <ANY translate="DELICIOUS_FRUIT "translate-values="{{fruitData}}"></ANY> 

Language stack


The library supports the creation of a stack of supported languages ​​that are added in order of priority. So, if the selected language is unavailable for one reason or another, the application will search for the key further along the stack on a first-fit basis. Example:

  $translateProvider .translations('de', { /* ... */ }) .translations('en', { /* ... */ }) .translations('fr', { /* ... */ }) .fallbackLanguage(['en', 'fr']); 

Here the search iteration will start from en to fr.

Related Adverb Groups (Languages)


Supports the association of related adverbs into groups. For example:

  .registerAvailableLanguageKeys(['en', 'de'], { 'en_US': 'en', 'en_UK': 'en', 'de_DE': 'de', 'de_CH': 'de' }) 

Pluralization


There is support for pluralization using the MessageFormat library. It is installed from the angular-translate-interpolation-messageformat (bower) package. Example :
A complete list can be found on the official page .

Problems of translation of a real application and related difficulties


We have analyzed various methods of localization, and now let's see what problems we may encounter by adding localization support to the real application.

Synchronization of client and server localization

In our case, a small part of the language-dependent logic was still on the server, so the first thing we encountered was the need to synchronize the selected languages ​​on the client and the server. On the server side there was a private service responsible for server localization, and an API controller used by the client side to synchronize changes in the language. For the initial synchronization of the web application language, the server sets the corresponding variable to the @ViewBag, which is translated into the appropriate scope using the AngularJS directive, where it is used by the angular-translate service using $ translate.use ().

Localization of angular directives containing template

To translate keys used in directive templates, you can use the filter “... | translate ”. However, there is a certain difficulty: if you simply add a two-way binding filter, it may not be available, that is, the directive will not respond to language switching. The reason lies in the fact that the $ translateChangeSuccess event is not translated into scope directives. The problem can be solved by using another directive that broadcasts the event:
 angular.module('app').directive('uiBroadcastTranslate', function ($rootScope) { return { link: function (scope) { $rootScope.$on('$translateChangeSuccess', function () { scope.$broadcast('uiBroadcastTranslateDirectiveEvent'); }); } } }); 

Creating dictionaries with optional variables

In the process, we stumbled upon another interesting opportunity. Consider an example. Let the application have lines like “Single”, “Single choice”. You can make localized phrases, localizing each of the lines separately. However, you can create a dictionary line like "SINGLE": "Single {{choice}}". Then, if you do not specify the values ​​of the choice parameter, {{'SINGLE' | translate}} will be interpolated as simply “Single”.

Problem reloading resources when using nested controllers

Let the application use partialLoader. There are several ng-controllers associated with a scope nested in each other.
shellCtrl [shellScope] <- workspaceCtrl [workspaceScope] <- toolCtrl [toolScoope]).
Each of them has its own “part” for localizing $ translate.addPart (“ctrl [] Part”).
With this approach, partialLoader will load:

Thus, the shellCtrlPart module will be loaded three times and the workspaceCtrlPart twice, which is not very good. The cause of the problem lies in the fact that in the $ http partialLoader GET request, caching is not used. You can solve the problem, for example, by turning on caching to load language resources:

 $http({ method: 'GET', url: this.parseUrl(urlTemplate, lang), cache:true }); 

Conclusion


In our opinion, the library is quite convenient. If the application was not intended to be initially localizable, then support can be added by deep refactoring. In our case, the volume of the text was small, so it took ~ 1.5 weeks to support localizability and localization into another language [German], along with the translation.

Links


1. blog.novanet.no/creating-multilingual-support-using-angularjs
2. angular-translate.imtqy.com/docs/#/api/pascalprecht.translate . $ TranslateProvider
3. www.beabigrockstar.com/blog/share-translations-between-aspnet-mvc-and -angularjs
4. github.com/beabigrockstar/SharedTranslationsAspnetAngular

Source: https://habr.com/ru/post/242455/


All Articles