text: angular.module('pascalprecht.translate')
  /**
   * @ngdoc directive
   * @name pascalprecht.translate.directive:translate
   * @requires $compile
   * @requires $filter
   * @requires $interpolate
   * @restrict A
   *
   * @description
   * Translates given translation id either through attribute or DOM content.
   * Internally it uses `translate` filter to translate translation id. It possible to
   * pass an optional `translate-values` object literal as string into translation id.
   *
   * @param {string=} translate Translation id which could be either string or interpolated string.
   * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
   * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
   * @param {string=} translate-default will be used unless translation was successful
   * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translate#usePostCompiling}
   *
   * @example
     <example module="ngView">
      <file name="index.html">
        <div ng-controller="TranslateCtrl">
  
          <pre translate="TRANSLATION_ID"></pre>
          <pre translate>TRANSLATION_ID</pre>
          <pre translate translate-attr-title="TRANSLATION_ID"></pre>
          <pre translate="{{translationId}}"></pre>
          <pre translate>{{translationId}}</pre>
          <pre translate="WITH_VALUES" translate-values="{value: 5}"></pre>
          <pre translate translate-values="{value: 5}">WITH_VALUES</pre>
          <pre translate="WITH_VALUES" translate-values="{{values}}"></pre>
          <pre translate translate-values="{{values}}">WITH_VALUES</pre>
          <pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre>
  
        </div>
      </file>
      <file name="script.js">
        angular.module('ngView', ['pascalprecht.translate'])
  
        .config(function ($translateProvider) {
  
          $translateProvider.translations('en',{
            'TRANSLATION_ID': 'Hello there!',
            'WITH_VALUES': 'The following value is dynamic: {{value}}'
          }).preferredLanguage('en');
  
        });
  
        angular.module('ngView').controller('TranslateCtrl', function ($scope) {
          $scope.translationId = 'TRANSLATION_ID';
  
          $scope.values = {
            value: 78
          };
        });
      </file>
      <file name="scenario.js">
        it('should translate', function () {
          inject(function ($rootScope, $compile) {
            $rootScope.translationId = 'TRANSLATION_ID';
  
            element = $compile('<p translate="TRANSLATION_ID"></p>')($rootScope);
            $rootScope.$digest();
            expect(element.text()).toBe('Hello there!');
  
            element = $compile('<p translate="{{translationId}}"></p>')($rootScope);
            $rootScope.$digest();
            expect(element.text()).toBe('Hello there!');
  
            element = $compile('<p translate>TRANSLATION_ID</p>')($rootScope);
            $rootScope.$digest();
            expect(element.text()).toBe('Hello there!');
  
            element = $compile('<p translate>{{translationId}}</p>')($rootScope);
            $rootScope.$digest();
            expect(element.text()).toBe('Hello there!');
  
            element = $compile('<p translate translate-attr-title="TRANSLATION_ID"></p>')($rootScope);
            $rootScope.$digest();
            expect(element.attr('title')).toBe('Hello there!');
          });
        });
      </file>
     </example>
   */
  .directive('translate', ['$translate', '$q', '$interpolate', '$compile', '$parse', '$rootScope', function ($translate, $q, $interpolate, $compile, $parse, $rootScope) {
  
    return {
      restrict: 'AE',
      scope: true,
      compile: function (tElement, tAttr) {
  
        var translateValuesExist = (tAttr.translateValues) ?
          tAttr.translateValues : undefined;
  
        var translateInterpolation = (tAttr.translateInterpolation) ?
          tAttr.translateInterpolation : undefined;
  
        var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);
  
        var interpolateRegExp = "^(.*)(" + $interpolate.startSymbol() + ".*" + $interpolate.endSymbol() + ")(.*)",
            watcherRegExp = "^(.*)" + $interpolate.startSymbol() + "(.*)" + $interpolate.endSymbol() + "(.*)";
  
        return function linkFn(scope, iElement, iAttr) {
  
          scope.interpolateParams = {};
          scope.preText = "";
          scope.postText = "";
          var translationIds = {};
  
          // Ensures any change of the attribute "translate" containing the id will
          // be re-stored to the scope's "translationId".
          // If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
          var observeElementTranslation = function (translationId) {
            if (angular.equals(translationId , '') || !angular.isDefined(translationId)) {
              // Resolve translation id by inner html if required
              var interpolateMatches = iElement.text().match(interpolateRegExp);
              // Interpolate translation id if required
              if (angular.isArray(interpolateMatches)) {
                scope.preText = interpolateMatches[1];
                scope.postText = interpolateMatches[3];
                translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent);
                watcherMatches = iElement.text().match(watcherRegExp);
                if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) {
                  scope.$watch(watcherMatches[2], function (newValue) {
                    translationIds.translate = newValue;
                    updateTranslations();
                  });
                }
              } else {
                translationIds.translate = iElement.text().replace(/^\s+|\s+$/g,'');
              }
            } else {
              translationIds.translate = translationId;
            }
            updateTranslations();
          };
  
          var observeAttributeTranslation = function (translateAttr) {
            iAttr.$observe(translateAttr, function (translationId) {
              translationIds[translateAttr] = translationId;
              updateTranslations();
            });
          };
  
          iAttr.$observe('translate', function (translationId) {
            observeElementTranslation(translationId);
          });
  
          for (var translateAttr in iAttr) {
            if(iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr') {
              observeAttributeTranslation(translateAttr);
            }
          }
  
          iAttr.$observe('translateDefault', function (value) {
            scope.defaultText = value;
          });
  
          if (translateValuesExist) {
            iAttr.$observe('translateValues', function (interpolateParams) {
              if (interpolateParams) {
                scope.$parent.$watch(function () {
                  angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
                });
              }
            });
          }
  
          if (translateValueExist) {
            var observeValueAttribute = function (attrName) {
              iAttr.$observe(attrName, function (value) {
                var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
                scope.interpolateParams[attributeName] = value;
              });
            };
            for (var attr in iAttr) {
              if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
                observeValueAttribute(attr);
              }
            }
          }
  
          // Master update function
          var updateTranslations = function () {
            for (var key in translationIds) {
              if (translationIds.hasOwnProperty(key) && translationIds[key]) {
                updateTranslation(key, translationIds[key], scope, scope.interpolateParams);
              }
            }
          };
  
          // Put translation processing function outside loop
          var updateTranslation = function(translateAttr, translationId, scope, interpolateParams) {
            $translate(translationId, interpolateParams, translateInterpolation)
              .then(function (translation) {
                applyTranslation(translation, scope, true, translateAttr);
              }, function (translationId) {
                applyTranslation(translationId, scope, false, translateAttr);
              });
          };
  
          var applyTranslation = function (value, scope, successful, translateAttr) {
            if (translateAttr === 'translate') {
              // default translate into innerHTML
              if (!successful && typeof scope.defaultText !== 'undefined') {
                value = scope.defaultText;
              }
              iElement.html(scope.preText + value + scope.postText);
              var globallyEnabled = $translate.isPostCompilingEnabled();
              var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
              var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
              if ((globallyEnabled && !locallyDefined) || locallyEnabled) {
                $compile(iElement.contents())(scope);
              }
            } else {
              // translate attribute
              if (!successful && typeof scope.defaultText !== 'undefined') {
                value = scope.defaultText;
              }
              var attributeName = iAttr.$attr[translateAttr].substr(15);
              iElement.attr(attributeName, value);
            }
          };
  
          scope.$watch('interpolateParams', updateTranslations, true);
  
          // Ensures the text will be refreshed after the current language was changed
          // w/ $translate.use(...)
          var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
  
          // ensure translation will be looked up at least one
          if (iElement.text().length) {
            observeElementTranslation('');
          }
          updateTranslations();
          scope.$on('$destroy', unbind);
        };
      }
    };
  }]);
  