|  | @@ -0,0 +1,230 @@
 | 
	
		
			
				|  |  | +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;
 |