<commit_before>'use strict';

const allowed = {
  elements: [
    'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data',
    'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u',
    'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr'
  ],
  attributes: {
    global: [ 'title', 'aria-label', 'aria-valuetext', 'aria-moz-hint' ],
    a: [ 'download' ],
    area: [ 'download', 'alt' ],
    // value is special-cased in isAttrAllowed
    input: [ 'alt', 'placeholder' ],
    menuitem: [ 'label' ],
    menu: [ 'label' ],
    optgroup: [ 'label' ],
    option: [ 'label' ],
    track: [ 'label' ],
    img: [ 'alt' ],
    textarea: [ 'placeholder' ],
    th: [ 'abbr']
  }
};

export function setAttributes(element, id, args) {
  element.setAttribute('data-l10n-id', id);
  if (args) {
    element.setAttribute('data-l10n-args', JSON.stringify(args));
  }
}

export function getAttributes(element) {
  return {
    id: element.getAttribute('data-l10n-id'),
    args: JSON.parse(element.getAttribute('data-l10n-args'))
  };
}

function getTranslatables(element) {
  var nodes = [];

  if (typeof element.hasAttribute === 'function' &&
      element.hasAttribute('data-l10n-id')) {
    nodes.push(element);
  }

  return nodes.concat.apply(
    nodes, element.querySelectorAll('*[data-l10n-id]'));
}

export function translateDocument(view, langs, doc) {
  let setDOMLocalized = function() {
    doc.localized = true;
    dispatchEvent(doc, 'DOMLocalized', langs);
  };

  if (langs[0].code === doc.documentElement.getAttribute('lang')) {
    return Promise.resolve(setDOMLocalized());
  }

  return translateFragment(view, langs, doc.documentElement).then(
    () => {
      doc.documentElement.lang = langs[0].code;
      doc.documentElement.dir = langs[0].dir;
      setDOMLocalized();
    });
}

export function translateMutations(view, langs, mutations) {
  let targets = new Set();

  for (let mutation of mutations) {
    switch (mutation.type) {
      case 'attributes':
        translateElement(view, langs, mutation.target);
        break;
      case 'childList':
        for (let addedNode of mutation.addedNodes) {
          if (addedNode.nodeType === Node.ELEMENT_NODE) {
            targets.add(addedNode);
          }
        }
    }
  }

  targets.forEach(
    target => target.childElementCount ?
      translateFragment(view, langs, target) :
      translateElement(view, langs, target));
}

export function translateFragment(view, langs, frag) {
  return Promise.all(
    getTranslatables(frag).map(
      elem => translateElement(view, langs, elem)));
}

function camelCaseToDashed(string) {
  // XXX workaround for https://bugzil.la/1141934
  if (string === 'ariaValueText') {
    return 'aria-valuetext';
  }

  return string
    .replace(/[A-Z]/g, function (match) {
      return '-' + match.toLowerCase();
    })
    .replace(/^-/, '');
}

export function translateElement(view, langs, elem) {
  var l10n = getAttributes(elem);

  if (!l10n.id) {
    return false;
  }

  return view.ctx.formatEntity(langs, l10n.id, l10n.args).then(
    translation => applyTranslation(view, elem, translation));
}

function applyTranslation(view, element, translation) {
  view.disconnect();

  var value;
  if (translation.attrs && translation.attrs.innerHTML) {
    // XXX innerHTML is treated as value (https://bugzil.la/1142526)
    value = translation.attrs.innerHTML;
    console.warn(
      'L10n Deprecation Warning: using innerHTML in translations is unsafe ' +
      'and will not be supported in future versions of l10n.js. ' +
      'See https://bugzil.la/1027117');
  } else {
    value = translation.value;
  }

  if (typeof value === 'string') {
    if (!translation.overlay) {
      element.textContent = value;
    } else {
      // start with an inert template element and move its children into
      // `element` but such that `element`'s own children are not replaced
      var tmpl = element.ownerDocument.createElement('template');
      tmpl.innerHTML = value;
      // overlay the node with the DocumentFragment
      overlayElement(element, tmpl.content);
    }
  }

  for (var key in translation.attrs) {
    var attrName = camelCaseToDashed(key);
    if (isAttrAllowed({ name: attrName }, element)) {
      element.setAttribute(attrName, translation.attrs[key]);
    }
  }

  view.observe();
}

// The goal of overlayElement is to move the children of `translationElement`
// into `sourceElement` such that `sourceElement`'s own children are not
// replaced, but onle have their text nodes and their attributes modified.
//
// We want to make it possible for localizers to apply text-level semantics to
// the translations and make use of HTML entities. At the same time, we<commit_msg>Turn off MutationObserver while translating DOM<commit_after>'use strict';

const allowed = {
  elements: [
    'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data',
    'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u',
    'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr'
  ],
  attributes: {
    global: [ 'title', 'aria-label', 'aria-valuetext', 'aria-moz-hint' ],
    a: [ 'download' ],
    area: [ 'download', 'alt' ],
    // value is special-cased in isAttrAllowed
    input: [ 'alt', 'placeholder' ],
    menuitem: [ 'label' ],
    menu: [ 'label' ],
    optgroup: [ 'label' ],
    option: [ 'label' ],
    track: [ 'label' ],
    img: [ 'alt' ],
    textarea: [ 'placeholder' ],
    th: [ 'abbr']
  }
};

export function setAttributes(element, id, args) {
  element.setAttribute('data-l10n-id', id);
  if (args) {
    element.setAttribute('data-l10n-args', JSON.stringify(args));
  }
}

export function getAttributes(element) {
  return {
    id: element.getAttribute('data-l10n-id'),
    args: JSON.parse(element.getAttribute('data-l10n-args'))
  };
}

function getTranslatables(element) {
  var nodes = [];

  if (typeof element.hasAttribute === 'function' &&
      element.hasAttribute('data-l10n-id')) {
    nodes.push(element);
  }

  return nodes.concat.apply(
    nodes, element.querySelectorAll('*[data-l10n-id]'));
}

export function translateDocument(view, langs, doc) {
  let setDOMLocalized = function() {
    doc.localized = true;
    dispatchEvent(doc, 'DOMLocalized', langs);
  };

  if (langs[0].code === doc.documentElement.getAttribute('lang')) {
    return Promise.resolve(setDOMLocalized());
  }

  return translateFragment(view, langs, doc.documentElement).then(
    () => {
      doc.documentElement.lang = langs[0].code;
      doc.documentElement.dir = langs[0].dir;
      setDOMLocalized();
    });
}

export function translateMutations(view, langs, mutations) {
  let targets = new Set();

  for (let mutation of mutations) {
    switch (mutation.type) {
      case 'attributes':
        targets.add(mutation.target);
        break;
      case 'childList':
        for (let addedNode of mutation.addedNodes) {
          if (addedNode.nodeType === Node.ELEMENT_NODE) {
            targets.add(addedNode);
          }
        }
        break;
    }
  }

  if (targets.size === 0) {
    return;
  }

  let elements = [];

  targets.forEach(target => target.childElementCount ?
      elements.concat(getTranslatables(target)) : elements.push(target));

  Promise.all(
    elements.map(elem => getElementTranslation(view, langs, elem))).then(
      translations => applyTranslations(view, elements, translations));
}

export function translateFragment(view, langs, frag) {
  let elements = getTranslatables(frag);
  return Promise.all(
    elements.map(elem => getElementTranslation(view, langs, elem))).then(
      translations => applyTranslations(view, elements, translations));
}

function camelCaseToDashed(string) {
  // XXX workaround for https://bugzil.la/1141934
  if (string === 'ariaValueText') {
    return 'aria-valuetext';
  }

  return string
    .replace(/[A-Z]/g, function (match) {
      return '-' + match.toLowerCase();
    })
    .replace(/^-/, '');
}

function getElementTranslation(view, langs, elem) {
  var l10n = getAttributes(elem);

  return l10n.id ?
    view.ctx.formatEntity(langs, l10n.id, l10n.args) : false;
}

export function translateElement(view, langs, elem) {
  return getElementTranslation(view, langs, elem).then(translation => {
    if (!translation) {
      return false;
    }

    view.disconnect();
    applyTranslation(view, elem, translation);
    view.observe();
  });
}

function applyTranslations(view, elements, translations) {
  view.disconnect();
  for (let i = 0; i < elements.length; i++) {
    if (translations[i] === false) {
      continue;
    }
    applyTranslation(view, elements[i], translations[i]);
  }
  view.observe();
}

function applyTranslation(view, element, translation) {
  var value;
  if (translation.attrs && translation.attrs.innerHTML) {
    // XXX innerHTML is treated as value (https://bugzil.la/1142526)
    value = translation.attrs.innerHTML;
    console.warn(
      'L10n Deprecation Warning: using innerHTML in translations is unsafe ' +
      'and will not be supported in future versions of l10n.js. ' +
      'See https://bugzil.la/1027117');
  } else {
    value = translation.value;
  }

  if (typeof value === 'string') {
    if (!translation.overlay) {
      element.textContent = value;
    } else {
      // start with an inert template element and move its children into
      // `element` but such that `element`'s own children are not replaced
      var tmpl = element.ownerDocument.createElement('template');
      tmpl.innerHTML = value;
      // overlay the node with the DocumentFragment
      overlayElement(element, tmpl.content);
    }
  }

  for (var key in translation.attrs) {
    var attrName = camelCaseToDashed(key);
    if (isAttrAllowed({ name: attrName }, element)) {
      element.setAttribute(attrName, translation.attrs[key]);
    }
  }
}

// The goal of overlayElement is to move the children of `translationElement`
// into `sourceElement` such that `sourceElement`'s own children are not
// replaced, but onle have their text nodes and their attributes modified.
//
// We want to make it possible for localizers to apply text-level semantics to
// the translations and make use of HTML entities. At the same time, we
{'fraction_non_alphanumeric': 0.1497857421114141, 'fraction_numerical': 0.009544215037008181, 'mean_word_length': 3.271630615640599, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 14, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '780123', 'n_tokens_mistral': 3316, 'n_tokens_neox': 3211, 'n_words': 812}