web-dev-qa-db-fra.com

Utilisation correcte pour la traduction angulaire dans les contrôleurs

J'utilise angular-translate pour i18n dans une application AngularJS.

Pour chaque vue d’application, il existe un contrôleur dédié. Dans les contrôleurs ci-dessous, je règle la valeur à afficher comme titre de la page.

Code

HTML

<h1>{{ pageTitle }}</h1>

JavaScript

.controller('FirstPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
        $scope.pageTitle = $filter('translate')('HELLO_WORLD');
    }])

.controller('SecondPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
        $scope.pageTitle = 'Second page title';
    }])

Je charge les fichiers de traduction à l'aide de l'extension angular-translate-loader-url .

Problème

Lors du chargement initial de la page, la clé de traduction est affichée à la place de la traduction pour cette clé. La traduction est Hello, World!, mais je vois HELLO_WORLD.

La deuxième fois que je vais à la page, tout va bien et la version traduite est affichée.

Je suppose que le problème tient au fait que le fichier de traduction n'est peut-être pas encore chargé lorsque le contrôleur attribue la valeur à $scope.pageTitle.

Remarque

Lorsque vous utilisez <h1>{{ pageTitle | translate }}</h1> et $scope.pageTitle = 'HELLO_WORLD';, la traduction fonctionne parfaitement dès la première fois. Le problème avec ceci est que je ne veux pas toujours utiliser les traductions (par exemple, pour le second contrôleur, je veux juste passer une chaîne brute).

Question

Est-ce un problème connu/une limitation? Comment cela peut-il être résolu?

118
ndequeker

EDIT: Voir la réponse de PascalPrecht (l'auteur de angular-translate) pour une meilleure solution.


La nature asynchrone du chargement est à l'origine du problème. Vous voyez, avec {{ pageTitle | translate }}, Angular observera l'expression; Lorsque les données de localisation sont chargées, la valeur de l'expression change et l'écran est mis à jour.

Donc, vous pouvez le faire vous-même:

.controller('FirstPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
    $scope.$watch(
        function() { return $filter('translate')('HELLO_WORLD'); },
        function(newval) { $scope.pageTitle = newval; }
    );
});

Cependant, cela exécutera l'expression surveillée à chaque cycle de résumé. Ceci est sous-optimal et peut entraîner ou non une dégradation visible des performances. Quoi qu'il en soit, c'est ce que Angular fait, donc ça ne peut pas être si mauvais ...

68

Recommandé: ne traduisez pas dans le contrôleur, traduisez dans votre vue

Nous recommandons de garder votre contrôleur libre de toute logique de traduction et de traduire vos chaînes directement dans votre vue, comme suit:

<h1>{{ 'TITLE.HELLO_WORLD' | translate }}</h1>

Utilisation du service fourni

Angular Translate fournit le service $translate que vous pouvez utiliser dans vos contrôleurs.

Un exemple d'utilisation du service $translate peut être:

.controller('TranslateMe', ['$scope', '$translate', function ($scope, $translate) {
    $translate('PAGE.TITLE')
        .then(function (translatedValue) {
            $scope.pageTitle = translatedValue;
        });
});

Le service de traduction possède également une méthode pour traduire directement des chaînes sans avoir à gérer une promesse, en utilisant $translate.instant():

.controller('TranslateMe', ['$scope', '$translate', function ($scope, $translate) {
    $scope.pageTitle = $translate.instant('TITLE.DASHBOARD'); // Assuming TITLE.DASHBOARD is defined
});

L'inconvénient d'utiliser $translate.instant() peut être que le fichier de langue n'est pas encore chargé si vous le chargez de manière asynchrone.

Utiliser le filtre fourni

C'est ma manière préférée car je n'ai pas à gérer les promesses de cette façon. La sortie du filtre peut être directement définie sur une variable de portée.

.controller('TranslateMe', ['$scope', '$filter', function ($scope, $filter) {
    var $translate = $filter('translate');

    $scope.pageTitle = $translate('TITLE.DASHBOARD'); // Assuming TITLE.DASHBOARD is defined
});

Utilisation de la directive fournie

Étant donné que @PascalPrecht est le créateur de cette bibliothèque géniale, je vous conseillerais de vous munir de son conseil (voir la réponse ci-dessous) et d'utiliser la directive fournie, qui semble gérer les traductions de manière très intelligente.

La directive prend en charge l'exécution asynchrone et est également assez intelligente pour détacher les identifiants de traduction sur l'étendue si la traduction n'a pas de valeur dynamique.

139
Robin van Baalen

En fait, vous devriez plutôt utiliser la directive de traduction pour ce genre de choses.

<h1 translate="{{pageTitle}}"></h1>

La directive prend en charge l'exécution asynchrone et est également assez intelligente pour détacher les identifiants de traduction sur l'étendue si la traduction n'a pas de valeur dynamique.

Cependant, s'il n'y a pas d'autre solution et que vous devez réellement utiliser le service $translate dans le contrôleur, vous devez envelopper l'appel dans un $translateChangeSuccess événement utilisant $rootScope en combinaison avec $translate.instant() comme ceci:

.controller('foo', function ($rootScope, $scope, $translate) {
  $rootScope.$on('$translateChangeSuccess', function () {
    $scope.pageTitle = $translate.instant('PAGE.TITLE');
  });
})

Alors pourquoi $rootScope et pas $scope? La raison en est que, dans angular-translate, les événements sont $emited sur $rootScope plutôt que $broadcasted sur $scope, car nous n'avons pas besoin de diffuser à travers toute la hiérarchie de la portée. .

Pourquoi $translate.instant() et pas seulement async _ $translate()? Lorsque l'événement $translateChangeSuccess est déclenché, il est certain que les données de traduction nécessaires sont présentes et qu'aucune exécution asynchrone n'est exécutée (par exemple, une exécution avec chargeur asynchrone). Par conséquent, nous pouvons simplement utiliser $translate.instant(), qui est synchrone et suppose simplement que les traductions sont disponibles.

Depuis la version 2.8.0, il existe également $translate.onReady(), qui renvoie une promesse résolue dès que les traductions sont prêtes. Voir le journal des modifications .

122
Pascal Precht

Pour faire une traduction dans le contrôleur, vous pouvez utiliser le service $translate:

$translate(['COMMON.SI', 'COMMON.NO']).then(function (translations) {
    vm.si = translations['COMMON.SI'];
    vm.no = translations['COMMON.NO'];
});

Cette déclaration ne concerne que la traduction lors de l'activation du contrôleur, mais elle ne détecte pas le changement de langue d'exécution. Pour obtenir ce comportement, vous pouvez écouter l'événement $rootScope: $translateChangeSuccess et y effectuer la même traduction:

    $rootScope.$on('$translateChangeSuccess', function () {
        $translate(['COMMON.SI', 'COMMON.NO']).then(function (translations) {
            vm.si = translations['COMMON.SI'];
            vm.no = translations['COMMON.NO'];
        });
    });

Bien sûr, vous pouvez encapsuler le service $translate dans une méthode et l'appeler dans le contrôleur et dans le $translateChangeSucesslistener.

5
MacLeod

Ce qui se passe, c’est que Angular-translate surveille l’expression avec un système basé sur des événements, et comme dans tout autre cas de liaison ou de liaison bidirectionnelle, un événement est déclenché lorsque les données sont récupérées et la valeur modifiée. évidemment ne fonctionne pas pour la traduction. Les données de traduction, contrairement aux autres données dynamiques de la page, doivent bien sûr être immédiatement affichées à l'utilisateur. Il ne peut pas apparaître après le chargement de la page.

Même si vous parvenez à résoudre ce problème, le problème le plus important est que le travail de développement est énorme. Un développeur doit extraire manuellement chaque chaîne du site, le placer dans un fichier .json, le référencer manuellement par un code de chaîne (par exemple, "pageTitle" dans ce cas). La plupart des sites commerciaux ont des milliers de chaînes pour lesquelles cela doit se produire. Et ce n'est que le début. Vous avez maintenant besoin d'un système de synchronisation des traductions lorsque le texte sous-jacent change dans certaines d'entre elles, d'un système d'envoi des fichiers de traduction aux différents traducteurs, de leur réintégration dans la construction, de redéploiement du site afin que les traducteurs puissent voir. leurs changements dans le contexte, et ainsi de suite.

De plus, comme il s’agit d’un système "contraignant" basé sur des événements, un événement est déclenché pour chaque chaîne de la page, ce qui non seulement est un moyen plus lent de transformer la page, mais peut également ralentir toutes les actions de la page, si vous commencez à y ajouter un grand nombre d’événements.

Quoi qu'il en soit, utiliser une plateforme de traduction post-traitement me semble plus logique. En utilisant GlobalizeIt, par exemple, un traducteur peut simplement aller sur une page du site et commencer à modifier le texte directement sur la page de sa langue, et le tour est joué: https://www.globalizeit.com/HowItWorks =. Aucune programmation nécessaire (bien qu’elle puisse être extensible par programmation), elle s’intègre facilement à Angular: https://www.globalizeit.com/Translate/Angular , la transformation de la page s’effectue en une fois, et il affiche toujours le texte traduit avec le rendu initial de la page.

Divulgation complète: je suis un co-fondateur :)

1
Jeff W