Правильное использование angular-translate в контроллерах

Я использую angular-translate для i18n в приложении AngularJS.

Для каждого представления приложения есть выделенный контроллер. В контроллерах ниже я установил значение, которое будет отображаться в качестве заголовка страницы.

Код

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';
    }])

Я загружаю файлы перевода с помощью angular-translate-loader-url расширение.

Проблема

При начальной загрузке страницы вместо перевода для этого ключа отображается ключ перевода. Перевод Hello, World!, но я вижу HELLO_WORLD.

Второй раз захожу на страницу, все хорошо и отображается переведенная версия.

Я предполагаю, что проблема связана с тем, что, возможно, файл перевода еще не загружен, когда контроллер присваивает значение $scope.pageTitle.

Замечание

При использовании <h1>{{ pageTitle | translate }}</h1> и $scope.pageTitle = 'HELLO_WORLD'; перевод работает идеально с первого раза. Проблема в том, что я не всегда хочу использовать переводы (например, для второго контроллера я просто хочу передать необработанную строку).

Вопрос

Это известная проблема / ограничение? Как это решить?


person ndequeker    schedule 12.12.2013    source источник


Ответы (5)


РЕДАКТИРОВАТЬ: см. ответ PascalPrecht (автора angular-translate) для лучшего решения.


Асинхронный характер загрузки вызывает проблему. Видите ли, с {{ pageTitle | translate }} Angular будет следить за выражением; при загрузке данных локализации значение выражения изменяется и экран обновляется.

Итак, вы можете сделать это сами:

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

Однако это будет запускать наблюдаемое выражение в каждом цикле дайджеста. Это неоптимально и может вызвать или не вызвать видимое снижение производительности. В любом случае это то, что делает Angular, так что это не может быть так уж плохо ...

person Nikos Paraskevopoulos    schedule 12.12.2013
comment
Спасибо! Я ожидал, что использование фильтра в представлении или в контроллере будет вести себя точно так же. Похоже, здесь дело обстоит не так. - person ndequeker; 12.12.2013
comment
Я бы сказал, что использование $scope.$watch является излишним, поскольку Angular Translate предлагает службу для использования в контроллерах. Смотрите мой ответ ниже. - person Robin van Baalen; 20.06.2014
comment
Фильтр Angular Translate не требуется, поскольку $translate.instant() предлагает то же самое, что и услуга. Кроме этого, обратите внимание на ответ Паскаля. - person knalli; 28.10.2014
comment
Я согласен, использование $ watch излишне. Ниже приведены ответы на более правильное использование. - person jpblancoder; 14.11.2014

Рекомендуется: не переводите в контроллере, переводите в вашем представлении

Я бы рекомендовал освободить ваш контроллер от логики перевода и переводить ваши строки прямо внутри вашего представления следующим образом:

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

Воспользовавшись предоставленной услугой

Angular Translate предоставляет службу $translate, которую вы можете использовать в своих контроллерах.

Примером использования службы $translate может быть:

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

Служба перевода также имеет метод прямого перевода строк без необходимости обрабатывать обещание, используя $translate.instant():

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

Обратной стороной использования $translate.instant() может быть то, что языковой файл еще не загружен, если вы загружаете его асинхронно.

Использование предоставленного фильтра

Это мой предпочтительный способ, так как мне не нужно обрабатывать обещания таким образом. Выходные данные фильтра могут быть напрямую установлены в переменную области видимости.

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

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

Использование предоставленной директивы

Поскольку @PascalPrecht является создателем этой замечательной библиотеки, я бы рекомендовал воспользоваться его советом (см. Его ответ ниже) и используйте предоставленную директиву, которая, кажется, очень умно обрабатывает переводы.

Директива заботится об асинхронном выполнении, а также достаточно умен, чтобы не отслеживать идентификаторы перевода в области видимости, если перевод не имеет динамических значений.

person Robin van Baalen    schedule 13.03.2014
comment
Если бы вы попробовали это вместо того, чтобы написать этот несвязанный комментарий, вы бы к настоящему времени не знали ответ. Короткий ответ: да. Это возможно. - person Robin van Baalen; 04.06.2015
comment
в вашем примере с фильтром в контроллере: как и в случае с Instant (), если языковой файл не загружен, это не будет работать, верно? Разве в таком случае нельзя использовать часы? Или вы хотите сказать «используйте фильтр, только если знаете, что переводы загружены»? - person Bombinosh; 16.06.2015
comment
@Bombinosh Я бы сказал, используйте метод фильтрации, если вы знаете, что переводы загружены. Лично я бы даже рекомендовал не загружать переводы динамически, если вам это не нужно. Это обязательная часть вашего приложения, поэтому вам лучше не ждать, чтобы пользователь ее ждал. Но это личное мнение. - person Robin van Baalen; 07.07.2015
comment
Суть переводов в том, что они могут изменяться в зависимости от предпочтений пользователя или даже действий пользователя. В общем, вам нужно загружать их динамически. По крайней мере, если важно количество переводимых строк и / или если у вас много переводов. - person PhiLho; 07.10.2015
comment
Когда перевод выполняется в HTML, цикл дайджеста запускается дважды, но только один раз в контроллере. В 99% случаев это, вероятно, не имеет значения, но у меня была проблема с ужасной производительностью в угловой сетке пользовательского интерфейса с переводами во многих ячейках. Конечно, крайний случай, просто нужно знать - person tykowale; 12.02.2016

Собственно, вместо этого вам следует использовать директиву translate.

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

Директива заботится об асинхронном выполнении, а также достаточно умен, чтобы не отслеживать идентификаторы перевода в области видимости, если перевод не имеет динамических значений.

Однако, если выхода нет и вам действительно нужно использовать $translate службу в контроллере, вы должны заключить вызов в событие $translateChangeSuccess, используя $rootScope в сочетании с $translate.instant() следующим образом:

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

Так почему $rootScope, а не $scope? Причина этого в том, что в angular-translate события $emited на $rootScope, а не $broadcasted на $scope, потому что нам не нужно транслировать через всю иерархию области видимости.

Почему $translate.instant(), а не только асинхронный $translate()? Когда запускается событие $translateChangeSuccess, это означает, что необходимые данные перевода есть и асинхронное выполнение не происходит (например, выполнение асинхронного загрузчика), поэтому мы можем просто использовать $translate.instant(), который является синхронным и просто предполагает, что переводы доступны.

Начиная с версии 2.8.0 существует также $translate.onReady(), который возвращает обещание, которое выполняется, как только переводы готовы. См. журнал изменений.

person Pascal Precht    schedule 26.05.2014
comment
Могут ли возникнуть проблемы с производительностью, если я использую директиву translate вместо фильтра? Также я внутренне верю, что он наблюдает за возвращаемым значением Instant (). Так удаляются ли часы, когда текущая область видимости уничтожена? - person Nilesh; 11.06.2014
comment
Я пробовал использовать ваше предложение, но оно не работает, когда значение переменной области видимости изменяется динамически. - person Nilesh; 12.06.2014
comment
На самом деле всегда лучше избегать фильтров, где это возможно, поскольку они замедляют работу вашего приложения, потому что всегда устанавливают новые часы. Однако директива идет немного дальше. Он проверяет, нужно ли ему следить за значением идентификатора перевода или нет. Это позволяет работать с вашим приложением лучше. Не могли бы вы сделать кусок и связать меня с ним, чтобы я мог посмотреть дальше? - person Pascal Precht; 12.06.2014
comment
Plunk: plnkr.co/edit/j53xL1EdJ6bT20ldlhxr Вероятно, в моем примере значение директивы решает не смотреть . Также как отдельная проблема, мой пользовательский обработчик ошибок вызывается, если ключ не найден, но он не отображает возвращаемую строку. Я сделаю за это еще один кусок. - person Nilesh; 12.06.2014
comment
Вот пример, демонстрирующий проблему с настраиваемым обработчиком ошибок: plnkr.co/edit/OaRwyI08cLFnpWKp предварительный просмотр Я видел, что аналогичная проблема с мгновенным переводом была недавно исправлена, так что это может быть связано с этим ... и позвольте мне сказать, что мне нравится angular-translate, и особенно насколько хорошо он задокументирован. Отличная работа!! - person Nilesh; 12.06.2014
comment
Использование директивы $ translate создает новую область видимости, не может использоваться с другими директивами, которые создают новую область видимости, и приводит к неправильной работе ng-click (по крайней мере, для строк, которые не переведены) - person wiherek; 10.08.2014
comment
этот шаблон бесполезен, если у вас есть динамические значения. stackoverflow.com/questions/25743715/ - person praveenpds; 11.09.2014
comment
@PascalPrecht Просто вопрос, это хорошая практика - использовать bind-once с переводом? Как это {{::'HELLO_WORLD | translate}}'. - person Zunair Zubair; 18.08.2015
comment
@PascalPrecht Событие $ translateChangeSuccess не запускается при запуске приложения. Он запускается только при изменении языка перевода. Можно ли использовать $ translate.onReady () как лучший механизм для ожидания правильно загруженного перевода? - person Max Barnas; 22.09.2015
comment
Я использую динамические значения во вложенных ng-repeats и ng-ifs. Мне удалось заставить поле проблемы работать в Firefox (где у меня была моя первоначальная ошибка), реализовав $rootScope watcher, но я не мог заставить работать функцию .instant (). - person Kraken; 24.08.2016

Чтобы сделать перевод в контроллере, вы можете использовать сервис $translate:

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

Этот оператор выполняет перевод только при активации контроллера, но не обнаруживает изменения языка во время выполнения. Чтобы добиться такого поведения, вы можете прослушать событие $rootScope: $translateChangeSuccess и сделать там такой же перевод:

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

Конечно, вы можете инкапсулировать $translateservice в метод и вызывать его в контроллере и в $translateChangeSucesslistener.

person John Cardozo    schedule 28.02.2015

Происходит то, что Angular-translate наблюдает за выражением с помощью системы, основанной на событиях, и, как и в любом другом случае привязки или двусторонней привязки, событие запускается, когда данные извлекаются, и значение изменяется, что явно не работает на перевод. Данные перевода, в отличие от других динамических данных на странице, конечно же, должны немедленно отображаться для пользователя. Он не может появиться после загрузки страницы.

Даже если вы можете успешно отладить эту проблему, более серьезная проблема заключается в том, что требуется огромная работа по разработке. Разработчик должен вручную извлечь каждую строку на сайте, поместить ее в файл .json, вручную указать на нее код строки (например, pageTitle в данном случае). На большинстве коммерческих сайтов есть тысячи строк, для которых это должно произойти. И это только начало. Теперь вам нужна система синхронизации переводов при изменении основного текста в некоторых из них, система для отправки файлов перевода различным переводчикам, их реинтеграции в сборку, повторного развертывания сайта, чтобы переводчики могли видеть их изменения в контексте, и так далее.

Кроме того, поскольку это «привязанная» система, основанная на событиях, событие запускается для каждой отдельной строки на странице, что не только является более медленным способом преобразования страницы, но и может замедлить все действия на странице, если вы начнете добавлять к нему большое количество событий.

В любом случае, использование платформы для постобработки переводов имеет для меня больше смысла. Например, используя GlobalizeIt, переводчик может просто перейти на страницу сайта и начать редактировать текст прямо на странице для своего языка, и все: https://www.globalizeit.com/HowItWorks. Никакого программирования не требуется (хотя его можно расширять программно), он легко интегрируется с Angular: https://www.globalizeit.com/Translate/Angular, преобразование страницы происходит за один раз, и он всегда отображает переведенный текст с первоначальной визуализацией страницы.

Полное раскрытие: я соучредитель :)

person Jeff W    schedule 10.10.2017