Проблема асинхронного синхронизации Angular Translate с $translateProvider.useStaticFilesLoader

Я использую отличную директиву/службу Angular Translate ($translate) для работы с несколькими языками локали, и, поскольку у меня есть несколько файлов локалей, я использую удобный $translateProvider.useStaticFilesLoader для загрузки файлов перевода через структуру localeAbbr.json, например en.json, es.json, и т. д. Я создал плункер, чтобы показать свой проект с открытым исходным кодом, и этот проект использует локаль через необработанные файлы Git (указывая на фактический репозиторий Github, что означает, что он не является локальным для демонстрации плунжера). Мой проект построен как директива и служба, я сделал небольшой плункер, чтобы показать мою проблему с синхронизацией при загрузке файла JSON.

Все это говорит о том, что кажется, что $translateProvider.useStaticFilesLoader работает asynchronous, в то время как мне действительно нужно, чтобы это было synchronous, потому что к тому времени, когда плункер запускается, файлы JSON еще не проанализированы, а я уже вызывал $translate.instant() в своих сообщениях.

У меня есть Plunker, показывающий проблему.

А вот часть моей быстрой демонстрации сервиса:

app.factory('validationService', ['$filter', '$translate', function ($filter, $translate) {
  var service = this;
  var validationSummary = [];
  var errorMessages = [
    'INVALID_ALPHA',
    'INVALID_ALPHA_SPACE',
    'INVALID_ALPHA_NUM',
    'INVALID_BOOLEAN'
  ];

  //var $translate = $filter('translate');

  for(var i=0, ln=errorMessages.length; i < ln; i++) {
    validationSummary.push({  
      field: i,
      message: $translate.instant(errorMessages[i])
    });
  }

  // attach public functions
  service.getValidationSummary = getValidationSummary;
  return service;

  // function declaration
  function getValidationSummary() {
    return validationSummary;
  }
}]);

Конфигурация $translateProvider

app.config(['$translateProvider', function ($translateProvider) {
  $translateProvider.useStaticFilesLoader({
    prefix: 'https://rawgit.com/ghiscoding/angular-validation/master/locales/validation/',
    suffix: '.json'
    });

    // load English ('en') table on startup
    $translateProvider.preferredLanguage('en').fallbackLanguage('en');
}]);

Вызовите мою службу через контроллер:

app.controller("TestController", function($scope, validationService) {
  var vm = this;
  vm.displayValidationSummary = true;

  vm.validationSummary = validationService.getValidationSummary();
});

и, наконец, HTML с использованием контроллера:

<div class="alert alert-danger alert-dismissable" ng-show="vm.displayValidationSummary">
  <button type="button" class="close" data-dismiss="alert" aria-hidden="true" ng-click="displayValidationSummary = false">&times;</button>
  <h4><strong>{{ 'ERRORS' | translate }}!</strong></h4>
  <ul>
      <li ng-repeat="item in vm.validationSummary">{{item.field }}: {{item.message}}</li>
  </ul>
</div>

Поскольку я использую AngularJS 1.3+, я также обнаружил, что $translate переводится только один раз, поэтому автор предлагает использовать translateFilter.$stateful = true;, и я попытался, но это, похоже, не помогает.

Опять же, вот Plunker

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

Пожалуйста помоги!!!

EDIT
Я понял, что мой вопрос не охватывает все, что связано с моей проблемой. Помимо проблемы с задержкой перевода, мне также приходится передавать дополнительные аргументы, и это огромная проблема с их передачей анонимной функции перевода. К тому времени, когда обещание закончено, состояние моих аргументов уже изменилось. Например:

$translate(validator.message).then(function(translation) {
    // only log the invalid message in the $validationSummary
    addToValidationSummary(formElmObj, translation);

    // error Display
    if(!isValid) {
      updateErrorMsg(translation, isValid);
    }else if(!!formElmObj && formElmObj.isValid) {
      addToValidationSummary(formElmObj, '');
    }
}, function(data) {
    throw 'Failed to translate' + data;
});

person ghiscoding    schedule 10.04.2015    source источник
comment
возможный дубликат Правильное использование angular-translate в контроллерах   -  person Dmitry Gonchar    schedule 23.04.2015


Ответы (3)


При работе с AngularJS или JavaScript вам действительно нужно принять асинхронную парадигму. Чтобы сделать работу с асинхронным кодом менее громоздкой, вы можете использовать промисы. Angular предоставляет вам сервис под названием $q, который делает всю тяжелую работу за вас.

https://docs.angularjs.org/api/ng/service/$q< /а>

Чтобы разобраться с Promises, может потребоваться время, но в долгосрочной перспективе оно того стоит.

По сути, то, что вам нужно сделать с вашим сервисом проверки, - это использовать обещание API $translate, которое даст вам требуемый перевод на основе предоставленного ключа, когда он будет в состоянии это сделать. Это сводится к тому, что вы запрашиваете $translate для всех translationId, для которых хотите получить перевод, и когда все они будут получены, вы заполняете массив validationSummary своими сообщениями.

app.factory('validationService', ['$q', '$translate', function ($q, $translate) {

  var translationsPromises = [], 
    validationSummary = [],
    errorMessages = [
      'INVALID_ALPHA',
      'INVALID_ALPHA_SPACE',
      'INVALID_ALPHA_NUM',
      'INVALID_BOOLEAN'
    ];


  angular.forEach(errorMessages, function(val, key) {
    translationsPromises.push($translate(val));
  });

  $q.all(translationsPromises)
    .then(function(translations) {
      angular.forEach(translations, function(val, key) {
        validationSummary.push({
          filed: key,
          message: val
        });
      });
    })
    .catch(function (err) {
      console.error('Failed to translate error messages for validation summary', err);  
    });

  // function declaration
  function getValidationSummary() {
    return validationSummary;
  }

  return {
    getValidationSummary: getValidationSummary
  };

}]);

Я разветвил ваш плункер и модифицировал его, включив приведенный выше образец.

http://plnkr.co/edit/7DCwvY9jloXwfetKtcDA?p=preview

Другое наблюдение заключается в том, что вы используете фильтр перевода в HTML. Имейте в виду, что это может оказаться дорогостоящим, если у вас большой DOM, поскольку Angular будет вызывать перевод каждого ключа при каждом дайджесте. Подход, который следует рассмотреть, состоит в том, чтобы предоставить вашей виртуальной машине объект labels и использовать службу $filter для их заполнения при создании экземпляра контроллера.

person Pat Nolan    schedule 10.04.2015
comment
Спасибо, я посмотрю на это, фильтр $ был там только как еще несколько изменений кода, которые я пробовал, но не могу заставить что-либо работать должным образом. - person ghiscoding; 10.04.2015
comment
Единственная проблема, с которой я столкнулся с этой реализацией, заключается в том, что я выполняю дополнительные манипуляции с моим переводом, например, у меня есть перевод с именем INVALID_MIN_CHAR, а текст выглядит как Должен быть не менее символов :param. ... Затем я заменяю :param в цикле for, к тому времени, когда обещание завершено, мой цикл уже выполнен, а параметры не определены, если только я не передам их в качестве аргумента анонимной функции внутри обещания, но я могу похоже, не нашел, как это сделать в обещании $translate. - person ghiscoding; 11.04.2015
comment
Почему бы вам не использовать встроенную интерполяцию Angular? Параметр :param в вашем файле перевода можно заменить, скажем, на {{vm.minChars}} и указать это значение в файле vm. Очевидно, что у вас будет больше работы, если вы собираетесь иметь в форме несколько сообщений разной минимальной длины. С предоставленной реализацией у вас есть доступ к каждому переводу в angular.forEach(translations, function(val, key) { validationSummary.push({ filed: key, message: val }); где вы можете обработать :param, если продолжите с этой конкретной реализацией. - person Pat Nolan; 11.04.2015
comment
Да, я обнаружил, что на самом деле интерполяция также обрабатывается $translate, вы передаете перевод в качестве второго аргумента, например $translate('INVALID_MIN_CHAR', { value: 5 });, но даже несмотря на то, что он полностью нарушает весь мой код, я основывал весь свой код на предположении, что у меня есть текст, доступный на месте, идет с обещанием, требуется много перекодирования на моей стороне, что довольно печально. Я бы предпочел использовать решение, которое запускает мой код только после загрузки файла JSON, к сожалению, $translateProvider.useStaticFilesLoader не возвращает обещание. - person ghiscoding; 11.04.2015

Я обнаружил, что решение моей проблемы с передачей дополнительных аргументов анонимной функции обещания заключается в использовании Замыкания, таким образом, переменные одинаковы перед промисом и внутри него. Поэтому мне в основном нужно обернуть вызов $translate в замыкание, что-то вроде следующего:

(function(formElmObj, isValid, validator) {
    $translate(validator.message).then(function(translation) {
        message = message.trim();

        // only log the invalid message in the $validationSummary
        addToValidationSummary(formElmObj, message);

        // error Display
        if(!isValid) {
          updateErrorMsg(message, isValid);
        }else if(!!formElmObj && formElmObj.isValid) {
          addToValidationSummary(formElmObj, '');
        }
    }, function(data) {
        throw 'Failed to translate' + data;
    });
})(formElmObj, isValid, validator);

и теперь, наконец, мои переменные верны и сохраняют значение на тот момент времени :)

person ghiscoding    schedule 24.04.2015

Хотя это правда, что $translateProvider.useStaticFilesLoader не возвращает промис, я заглянул внутрь сервиса $translate и обнаружил, что он предоставляет удобный обратный вызов onReady(), который действительно возвращает промис. Этот обратный вызов вызывается, когда служба $translate завершает загрузку текущего выбранного языка, и он полезен для обеспечения того, чтобы мгновенные переводы работали должным образом после инициализации страницы:

$translate.onReady(function () {
    // perform your instant translations here
    var translatedMsg = $translate.instant('INVALID_ALPHA');
});
person Max GamEx    schedule 10.04.2018