const { phrasebook } = require("./phrasebook"); /** * This class is responsible for automatic translate site content of the * HTML elements. It could automatic translate content for all elements * which are in the "translate" class, and has "phrase" attribute. This * attrobite store phrase to translate, and insert into element innerText * or placeholder (for inputs). It could also observate HTML Node, and * automatic update transltions when any item had been changed. */ class autotranslate { /** * @var {?phrasebook} * This store phrasebook to get translates from, or store null, to get * translate content from global translate function. */ #phrasebook; /** * @var {?MutationObserver} * This store observer object, when it is connecter and waiting for * changaes, or store null, when observer currently not working. */ #observer; /** * This create new autotranslator. It require phrasebook, to loads * translations for phrases from. When null had been given, the it * use global translate function. * * @throws {Error} - When trying to use it in the NodeJS. * * @param {?phrasebook} phrasebook */ constructor(phrasebook = null) { NODE: throw new Error("It is not avairable in the NodeJS."); this.#observer = null; this.#phrasebook = phrasebook; } /** * It return class name for elements, which would be translated by * autotranslator. * * @returns {string} - Class name for autotranslating elements. */ static get_class_name() { return "translate"; } /** * This return selector for choose elements which must be autotranslated. * * @returns {string} - Selector of the elements to translate. */ get #class_selector() { return "." + autotranslate.get_class_name(); } /** * This return name of attribute which store phrase to translate. * * @returns {string} - Name of attribute which store phrase. */ static get_attribute_name() { return "phrase"; } /** * This check that autotranslator is connected and waiting to changes. * * @returns {bool} - True when observer is connected, fakse when not. */ get is_connected() { return this.#observer !== null; } /** * This search elements which could be translated in the element given * in the parameter. When null given, then it search elements in the * all document. * * @param {?HTMLElement} where - Item to load items from or null. * @returns {Array} - Array of elements to translate. */ #get_all_items(where = null) { if (where === null) { where = document; } return Array.from( where.querySelectorAll(this.#class_selector) ); } /** * It translate given phrase, baseed on loaded phrasebook, or when not * loaded any, then use global translate function. When it also not * exists, then throws error in debug mode, or return not translated * phrase on production. * * @throws {Error} - When any option to translate not exists. * * @param {string} content - Phrase to translate. * @returns {string} - Translated content. */ #translate(content) { if (this.#phrasebook !== null) { return this.#phrasebook.translate(content); } if (_ === undefined) { DEBUG: throw new Error("All translate options are unavairable."); return content; } return _(content); } /** * This add mutable observer to the body. It wait for DOM modifications, * and when any new node had been adder, or any phrase attribute had * been changed, then it trying to translate it. * * @returns {autotranslate} - This object to chain load. */ connect() { if (this.is_connected) { return this; } const body = document.querySelector("body"); const callback = (targets) => { this.#process(targets); }; const options = { childList: true, attributes: true, characterData: false, subtree: true, attributeFilter: [ autotranslate.get_attribute_name() ], attributeOldValue: false, characterDataOldValue: false }; this.#observer = new MutationObserver(callback); this.#observer.observe(body, options); return this; } /** * This prcoess all given in the array mutable records. * * @param {Array} targets - Array with mutable records. */ #process(targets) { targets.forEach(count => { if (count.type === "attributes") { this.#update_single(count.target); return; } this.#get_all_items(count.target).forEach(count => { this.#update_single(count); }); }); } /** * This disconnect observer, and remove it. * * @returns {autotranslate} - This object to chain loading. */ disconnect() { if (!this.is_connected) { return this; } this.#observer.disconnect(); this.#observer = null; return this; } /** * This update single element, based on phrase attribute. When element * is standard HTMLElement, then it place translated content into * innerText, but when element is input, like HTMLInputElement or * HTMLTextAreaElement, then it place result into placeholder. When * input is button, or submit, then it put content into value. * * @param {HTMLElement} target - Element to translate */ #update_single(target) { const attrobute_name = autotranslate.get_attribute_name(); const phrase = target.getAttribute(attrobute_name); const result = this.#translate(phrase); if (target instanceof HTMLInputElement) { if (target.type === "button" || target.type === "submit") { target.value = result; return; } target.placeholder = result; return; } if (target instanceof HTMLTextAreaElement) { target.placeholder = result; return; } target.innerText = result } /** * This update translation of all elements in the document. It is useable * when new autotranslator is created. * * @returns {autotranslate} - Instance of object to chain loading. */ update() { this.#get_all_items().forEach(count => { this.#update_single(count); }); return this; } } exports.autotranslate = autotranslate;