<commit_before>import { MessageContext } from '../intl/context';
import { Localization } from '../lib/localization';

import { getDirection } from './shims';
import {
  initMutationObserver, translateRoots, observe, disconnect
} from './observer';
import {
  setAttributes, getAttributes, translateFragment
} from './dom';

const properties = new WeakMap();
const contexts = new WeakMap();

export class HTMLLocalization extends Localization {
  constructor(doc, requestBundles) {
    super();
    this.interactive = requestBundles();
    this.ready = this.interactive
      .then(bundles => fetchFirstBundle(bundles))
      .then(bundles => translateDocument(this, bundles));

    properties.set(this, { doc, requestBundles, ready: false });
    initMutationObserver(this);
    this.observeRoot(doc.documentElement);
  }

  requestLanguages(requestedLangs) {
    return this.ready = this.interactive.then(
      bundles => changeLanguages(this, bundles, requestedLangs)
    );
  }

  handleEvent() {
    return this.requestLanguages();
  }

  formatEntities(...keys) {
    // XXX add async fallback
    return this.interactive.then(
      ([bundle]) => this._formatKeysFromContext(
        contexts.get(bundle), keys, this._formatEntityFromContext
      )
    );
  }

  formatValues(...keys) {
    return this.interactive.then(
      ([bundle]) => this._formatKeysFromContext(
        contexts.get(bundle), keys, this._formatValueFromContext
      )
    );
  }

  formatValue(id, args) {
    return this.formatValues([id, args]).then(
      ([val]) => val
    );
  }

  translateFragment(frag) {
    return translateFragment(this, frag);
  }

  observeRoot(root) {
    observe(this, root);
  }

  disconnectRoot(root) {
    disconnect(this, root);
  }
}

HTMLLocalization.prototype.setAttributes = setAttributes;
HTMLLocalization.prototype.getAttributes = getAttributes;

function createContextFromBundle(bundle) {
  return bundle.fetch().then(resources => {
    const ctx = new MessageContext(bundle.lang);
    resources.forEach(res => ctx.addMessages(res));
    contexts.set(bundle, ctx);
    return ctx;
  });
}

function fetchFirstBundle(bundles) {
  const [bundle] = bundles;
  return createContextFromBundle(bundle).then(
    () => bundles
  );
}

function changeLanguages(l10n, oldBundles, requestedLangs) {
  const { requestBundles } = properties.get(l10n);

  l10n.interactive = requestBundles(requestedLangs).then(
    newBundles => equal(oldBundles, newBundles) ?
      oldBundles : fetchFirstBundle(newBundles)
  );

  return l10n.interactive.then(
    bundles => translateDocument(l10n, bundles)
  );
}

function equal(bundles1, bundles2) {
  return bundles1.length === bundles2.length &&
    bundles1.every(({lang}, i) => lang === bundles2[i].lang);
}

function translateDocument(l10n, bundles) {
  const langs = bundles.map(bundle => bundle.lang);
  const props = properties.get(l10n);
  const html = props.doc.documentElement;

  if (props.ready) {
    return translateRoots(l10n).then(
      () => setAllAndEmit(html, langs)
    );
  }

  const translated = translateRoots(l10n).then(
    () => setLangDir(html, langs)
  );

  return translated.then(() => {
    setLangs(html, langs);
    props.ready = true;
  });
}

function setLangs(html, langs) {
  html.setAttribute('langs', langs.join(' '));
}

function setLangDir(html, [lang]) {
  html.setAttribute('lang', lang);
  html.setAttribute('dir', getDirection(lang));
}

function setAllAndEmit(html, langs) {
  setLangDir(html, langs);
  setLangs(html, langs);
  html.parentNode.dispatchEvent(new CustomEvent('DOMRetranslated', {
    bubbles: false,
    cancelable: false,
  }));
}<commit_msg>Refactor translateDocument<commit_after>import { MessageContext } from '../intl/context';
import { Localization } from '../lib/localization';

import { getDirection } from './shims';
import {
  initMutationObserver, translateRoots, observe, disconnect
} from './observer';
import {
  setAttributes, getAttributes, translateFragment
} from './dom';

const properties = new WeakMap();
const contexts = new WeakMap();

export class HTMLLocalization extends Localization {
  constructor(doc, requestBundles) {
    super();
    this.interactive = requestBundles();
    this.ready = this.interactive
      .then(bundles => fetchFirstBundle(bundles))
      .then(bundles => translateDocument(this, bundles));

    properties.set(this, { doc, requestBundles, ready: false });
    initMutationObserver(this);
    this.observeRoot(doc.documentElement);
  }

  requestLanguages(requestedLangs) {
    return this.ready = this.interactive.then(
      bundles => changeLanguages(this, bundles, requestedLangs)
    );
  }

  handleEvent() {
    return this.requestLanguages();
  }

  formatEntities(...keys) {
    // XXX add async fallback
    return this.interactive.then(
      ([bundle]) => this._formatKeysFromContext(
        contexts.get(bundle), keys, this._formatEntityFromContext
      )
    );
  }

  formatValues(...keys) {
    return this.interactive.then(
      ([bundle]) => this._formatKeysFromContext(
        contexts.get(bundle), keys, this._formatValueFromContext
      )
    );
  }

  formatValue(id, args) {
    return this.formatValues([id, args]).then(
      ([val]) => val
    );
  }

  translateFragment(frag) {
    return translateFragment(this, frag);
  }

  observeRoot(root) {
    observe(this, root);
  }

  disconnectRoot(root) {
    disconnect(this, root);
  }
}

HTMLLocalization.prototype.setAttributes = setAttributes;
HTMLLocalization.prototype.getAttributes = getAttributes;

function createContextFromBundle(bundle) {
  return bundle.fetch().then(resources => {
    const ctx = new MessageContext(bundle.lang);
    resources.forEach(res => ctx.addMessages(res));
    contexts.set(bundle, ctx);
    return ctx;
  });
}

function fetchFirstBundle(bundles) {
  const [bundle] = bundles;
  return createContextFromBundle(bundle).then(
    () => bundles
  );
}

function changeLanguages(l10n, oldBundles, requestedLangs) {
  const { requestBundles } = properties.get(l10n);

  l10n.interactive = requestBundles(requestedLangs).then(
    newBundles => equal(oldBundles, newBundles) ?
      oldBundles : fetchFirstBundle(newBundles)
  );

  return l10n.interactive.then(
    bundles => translateDocument(l10n, bundles)
  );
}

function equal(bundles1, bundles2) {
  return bundles1.length === bundles2.length &&
    bundles1.every(({lang}, i) => lang === bundles2[i].lang);
}

function translateDocument(l10n, bundles) {
  const langs = bundles.map(bundle => bundle.lang);
  const props = properties.get(l10n);
  const html = props.doc.documentElement;

  function setLangs() {
    html.setAttribute('langs', langs.join(' '));
    html.setAttribute('lang', langs[0]);
    html.setAttribute('dir', getDirection(langs[0]));
  }

  function emit() {
    html.parentNode.dispatchEvent(new CustomEvent('DOMRetranslated', {
      bubbles: false,
      cancelable: false,
    }));
  }

  const next = props.ready ?
    emit : () => props.ready = true;

  return translateRoots(l10n)
    .then(setLangs)
    .then(next);
}
{'fraction_non_alphanumeric': 0.13590864232341746, 'fraction_numerical': 0.006767235302410827, 'mean_word_length': 3.899171270718232, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3177997', 'n_tokens_mistral': 2282, 'n_tokens_neox': 2224, 'n_words': 415}