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

121

Я использую 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';перевод работает идеально с первого раза. Проблема в том, что я не всегда хочу использовать переводы (например, для второго контроллера я просто хочу передать необработанную строку).

Вопрос

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

ndequeker
источник

Ответы:

69

РЕДАКТИРОВАТЬ : Пожалуйста, посмотрите ответ 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, так что это не может быть так плохо ...

Никос Параскевопулос
источник
Спасибо! Я ожидал, что использование фильтра в представлении или в контроллере будет вести себя точно так же. Похоже, здесь дело обстоит не так.
ndequeker
Я бы сказал, что использование a $scope.$watchявляется излишним, поскольку Angular Translate предлагает службу для использования в контроллерах. Смотрите мой ответ ниже.
Робин ван Баален,
1
Фильтр Angular Translate не требуется, так как $translate.instant()предлагает то же самое, что и услуга. Кроме этого, обратите внимание на ответ Паскаля.
knalli
Я согласен, использование $ watch излишне. Ниже приведены ответы на более правильное использование.
jpblancoder
141

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

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

<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 является создателем этой замечательной библиотеки, я бы порекомендовал последовать его совету (см. Его ответ ниже) и использовать предоставленную директиву, которая, кажется, очень умно обрабатывает переводы.

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

Робин ван Баален
источник
Если бы вы попробовали это вместо того, чтобы писать этот несвязанный комментарий, вы бы уже знали ответ. Короткий ответ: да. Это возможно.
Робин ван Баален,
1
в вашем примере с фильтром в контроллере: как и в случае с Instant (), если языковой файл не загружен, это не будет работать, верно? Разве в таком случае нельзя использовать часы? Или вы хотите сказать «используйте фильтр, только если знаете, что переводы загружены»?
Bombinosh
@Bombinosh Я бы сказал, используйте метод фильтрации, если вы знаете, что переводы загружены. Лично я бы даже рекомендовал не загружать переводы динамически, если вам это не нужно. Это обязательная часть вашего приложения, поэтому вам лучше не ждать, чтобы пользователь ее ждал. Но это личное мнение.
Робин ван Баален,
Суть переводов в том, что они могут изменяться в зависимости от предпочтений пользователя или даже действий пользователя. В общем, вам нужно загружать их динамически. По крайней мере, если количество строк для перевода важно и / или если у вас много переводов.
PhiLho
4
Когда перевод выполняется в HTML, цикл дайджеста запускается дважды, но только один раз в контроллере. В 99% случаев это, вероятно, не имеет значения, но у меня была проблема с ужасной производительностью в угловой сетке пользовательского интерфейса с переводами во многих ячейках. Несомненно,
крайний
123

Собственно, вместо этого вам следует использовать директиву 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? Причина этого в том, что в угловых перевод'S события $emitэд на $rootScopeчем - $broadcastе изд на $scopeпотому , что мы не должны вещания по всей иерархии областей видимости.

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

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

Паскаль Прехт
источник
Могут ли возникнуть проблемы с производительностью, если я использую директиву translate вместо фильтра? Также я считаю, что внутренне он наблюдает за возвращаемым значением Instant (). Так удаляются ли часы при уничтожении текущей области видимости?
Nilesh
Я пробовал использовать ваше предложение, но оно не работает, когда значение переменной области видимости изменяется динамически.
Nilesh
10
На самом деле всегда лучше избегать фильтров, где это возможно, поскольку они замедляют работу вашего приложения, потому что всегда устанавливают новые часы. Однако директива идет немного дальше. Он проверяет, нужно ли ему следить за значением идентификатора перевода или нет. Это позволяет работать с вашим приложением лучше. Не могли бы вы сделать кусок и связать меня с ним, чтобы я мог посмотреть дальше?
Pascal Precht
Plunk: plnkr.co/edit/j53xL1EdJ6bT20ldlhxr Вероятно, в моем примере директива решает не смотреть значение. Также как отдельная проблема, мой пользовательский обработчик ошибок вызывается, если ключ не найден, но он не отображает возвращаемую строку. Я сделаю за это еще один кусок.
Nilesh
2
@PascalPrecht Просто вопрос, это хорошая практика - использовать bind-once с переводом? Вот так {{::'HELLO_WORLD | translate}}'.
Zunair Zubair
5

Чтобы сделать перевод в контроллере, вы можете использовать $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'];
        });
    });

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

MacLeod
источник
1

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

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

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

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

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

Джефф В
источник