Japanize.mod.core

Japanize Core Library

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://greasyfork.org/scripts/24109-japanize-mod-core/code/Japanizemodcore.js?version=153163

var MYLINGUAL = {
    
    VERSION: "0.7.1",
    UID: "kazuho-japanize2@labs.cybozu.co.jp",
    
    getPromptService: function () {
        return Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
            .getService(Components.interfaces.nsIPromptService);
    },
    
    notifyUser: function (message) {
        this.getPromptService().alert(
            window, this._s('prompt.title'), message);
    },
    
    getLang: function () {
        return this.getPreference('lang', this._s('defaults.lang'));
    },
    
    setLang: function (l) {
        this.setPreference('lang', l);
    },
    
    getStoreBaseDir: function () {
        try {
            var profileDir =
                Components.classes["@mozilla.org/file/directory_service;1"]
                .getService(Components.interfaces.nsIProperties)
                .get("ProfD", Components.interfaces.nsILocalFile);
            // return the extension directory
            var dir = profileDir.clone();
            dir.append("extensions");
            if (dir.exists() && dir.isDirectory()) {
               dir.append(this.UID);
               if (dir.exists() && dir.isDirectory()) {
                   dir.append("data");
                   return dir;
               }
            }
            // if failed, create our own directory below profile dir
            dir = profileDir.clone();
            dir.append("mylingual"); // only used by developers
            if (! dir.exists()) {
                dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
            }
            return dir;
        } catch (e) {
            alert("Japanize: " + e.toString());
        }
    },
    
    getStoreDir: function () {
        try {
            var dir = this.getStoreBaseDir();
            dir.append(this.getLang());
            if (! dir.exists()) {
                dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
            }
            return dir;
        } catch (e) {
            alert("Mylingual: " + e.toString());
        }
    },
    
    saveToStore: function (file, text) {
        text = "\ufeff" + text.toString();
        
        var conv =
            Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
            .getService(Components.interfaces.nsIScriptableUnicodeConverter);
        conv.charset = "UTF-8";
        
        var temppath = this.getStoreDir();
        temppath.append("t" + Math.floor(Math.random() * 10000000) + ".tmp");
        
        var os = Components.classes["@mozilla.org/network/file-output-stream;1"]
            .createInstance(Components.interfaces.nsIFileOutputStream);
        os.init(temppath, 0x2a, 0644, -1);
        for (var offset = 0; offset < text.length; offset += 1024) {
            var data =
                String.fromCharCode.apply(
                    null,
                    conv.convertToByteArray(
                        text.substring(offset, offset + 1024),
                        new Number(0)));
            os.write(data, data.length);
        }
        os.close();
        
        try {
            temppath.moveTo(this.getStoreDir(), file);
        } catch (e) {
            try {
                temppath.remove(false);
            } catch (e2) {
            }
            throw e;
        }
    },
    
    getStoreURI: function (file) {
        var path = this.getStoreDir();
        path.append(file);
        return Components.classes["@mozilla.org/network/io-service;1"]
            .getService(Components.interfaces.nsIIOService)
            .newFileURI(path);
    },
    
    getPreferenceService: function () {
        return Components.classes["@mozilla.org/preferences-service;1"]
            .getService(Components.interfaces.nsIPrefService)
                .getBranch("");
    },

    getPreference: function (name, defaultValue) {
        var value = null;
        try {
            value =
                this.getPreferenceService().getCharPref(
                    "extensions.japanize." + name);
        } catch (e) {
        }         
        if (value == null) {
            value = defaultValue;
        }
        return value;
    },
    
    setPreference: function (name, value) {
        this.getPreferenceService().setCharPref(
            "extensions.japanize." + name, value.toString());
    },
    
    debugAlert: function () {
        if (! this.getPreference('debug', '')) {
            return;
        }
        alert.apply(null, arguments);
    },
    
    PREF_UPDATEMODE: "updatemode",
    UPDATEMODE_PERIODICALLY: "periodically",
    UPDATEMODE_EVERYTIME: "everytime",
    
    getUpdateMode: function () {
        return this.getPreference(
            this.PREF_UPDATEMODE, this.UPDATEMODE_PERIODICALLY);
    },
    
    setUpdateMode: function (newMode) {
        this.setPreference(this.PREF_UPDATEMODE, newMode);
    },
    
    getBaseURL: function () {
        return this.getPreference("baseURL", "http://japanize.mylingual.net/");
    },
    
    normalizeHost: function (host) {
        return host.toString().toLowerCase().replace(/^www\./, "");
    },
    
    buildTranslationURL: function (host) {
        var url =
            this.getBaseURL() + "data/" + this.normalizeHost(host)
            + "/current.txt";
        return url;
    },
    
    loadLocalizationData: function (doc) {
        doc.__MYLINGUAL_DONE = true;
        var browser = this.findBrowser(doc);
        if (! this.getMode()) {
            this.updateStatus(browser, "");
            return;
        }
        if (doc.location.protocol != "http:") {
            this.updateStatus(browser, "");
            return;
        }
        if (this.getUpdateMode() == this.UPDATEMODE_PERIODICALLY
            && typeof this.localTranslationTable != "undefined") {
            if (this.localTranslationTable["$builddate"]
                == this.getPreference("$builddate", -1)) {
                setTimeout(
                    function () {
                        MYLINGUAL.localizeWithLocalData(doc, browser);
                    },
                    1);
                return;
            }
            this.localTranslationTable = undefined;
        }
        if (this.getUpdateMode() == this.UPDATEMODE_EVERYTIME) {
            try {
                var xhr = new XMLHttpRequest();
                xhr.open(
                    "get",
                    this.buildTranslationURL(doc.location.host),
                    true);
                xhr.onreadystatechange = function () {
                    if (xhr.readyState == 4) {
                        MYLINGUAL.localizeWithData(
                            MYLINGUAL.parseJSON(xhr.responseText),
                            doc,
                            browser);
                    }
                };
                xhr.send(null);
            } catch (e) {
                alert("Japanize: " + e.toString());
            }
        } else {
            try {
                var xhr = new XMLHttpRequest();
                xhr.open(
                    "get",
                    this.getStoreURI("all.txt").spec,
                    true);
                xhr.onreadystatechange = function () {
                    if (xhr.readyState == 4) {
                        var json = MYLINGUAL.parseJSON(xhr.responseText);
                        MYLINGUAL.localTranslationTable = json;
                        MYLINGUAL.localizeWithLocalData(doc, browser);
                    }
                };
                xhr.send(null);
            } catch (e) {
                alert("Japanize: " + e.toString());
            }
        }
    },
    
    localizeWithLocalData: function (doc, browser) {
        var table;
        if (typeof this.localTranslationTable == 'object') {
            table = this.localTranslationTable[
                MYLINGUAL.normalizeHost(doc.location.host)];
            if (typeof table == 'undefined'
                && doc.location.host.match(/^[^\.]*\./)) {
                table = this.localTranslationTable['*.' + RegExp.rightContext];
            }
        }
        this.localizeWithData(table, doc, browser);
    },
    
    canTranslateHost: function (host) {
        var list = this.getPreference("hosts", "").split(",");
        if (list.length == 0) {
            return true;
        }
        for (var i = 0; i < list.length; i++) {
            var flag = list[i].charAt(0);
            var pat = MYLINGUAL._gsub(
                list[i].substring(1),
                /[^A-Za-z0-9\-]/g,
                function (m) {
                    return m == '*' ? '.*' : '\\' + m;
                });
            pat = new RegExp(pat);
            if (typeof pat != 'undefined' && host.match(new RegExp(pat))) {
                if (flag == '-') {
                    return false;
                } else if (flag == '+') {
                    return true;
                }
            }
        }
        return true;
    },
    
    localizeWithData: function (json, doc, browser) {
        this.updateStatus(browser, "");
        // do nothing unless translation is available
        if (typeof json != "object") {
            return;
        }
        // check preferences
        if (! this.canTranslateHost(doc.location.host)) {
            this.updateStatus(browser, this._s('status.original'));
            return;
        }
        // convert json to internal representation
        var mappings = {
            text: {},
            re: []
        };
        this.initCommandMappings(mappings);
        for (var n in json) {
            if (n.charAt(0) == '$') {
                this.compileCommand(mappings, n, json[n]);
            } else if (n.match(/^\/(\^.*\$)\/$/)) {
                var v = json[n];
                try {
                    n = new RegExp(RegExp.$1);
                } catch (e) {
                    this.debugAlert(
                        this._f('mylingual.error.recompile', [ n ]));
                    continue;
                }
                mappings.re.push([ n, v ]);
            } else {
                mappings.text[n] = json[n];
            }
        }
        this.postProcessCommandMappings(mappings);
        // check url patterns
        if (this.ifSkipURL(mappings, doc.location)) {
            this.updateStatus(browser, this._s('status.bannedpage'));
            return;
        }
        // setup logger
        var log = ! ! this.getPreference("log", "");
        if (log && typeof FireBug == 'undefined' && ! this.noFireBugAlert) {
            alert("Japanize: Install FireBug to view translation logs.");
            this.noFireBugAlert = true;
            log = false;
        }
        if (log) {
            log = function (s) {
                FireBug.console.log(s);
            };
            log.log = true;
        } else {
            log = function () {
            };
            log.log = false;
        }
        // convert
        log("Japanize: translating: " + doc.location);
        this.localizeElement(doc.body, mappings, true);
        this.updateStatus(browser, this._s('status.translated'));
        
        // build handler for handilng DHTML modifs.
        var handler = function (evt) {
            if (handler.underOperation) {
                return;
            }
            if (log.log) {
                var msg = (function (t) {
                    if (t.id) {
                        return "id='" + t.id + "'";
                    } else if (t.className) {
                        return "class='" + t.className + "'";
                    } else if (t.parentNode) {
                        return arguments.callee(t.parentNode);
                    } else if (t.nodeType == 9) {
                        return "no identifier at root";
                    } else {
                        return "not within document";
                    }
                }).call(null, evt.target);
                msg += (function (t) {
                    while (typeof t == 'object' && t.nodeType != 9) {
                        t = t.parentNode;
                    }
                    if (! t) {
                        return '';
                    }
                    return ", " + t.location;
                }).call(null, evt.target);
                log("Japanize: " + msg);
            }
            setTimeout(
                function () {
                    handler.underOperation = true;
                    MYLINGUAL.localizeElement(
                        evt.target,
                        mappings,
                        MYLINGUAL.getElementTranslationMode(
                            mappings, doc.body, evt.target.parentNode));
                    handler.underOperation = false;
                },
                1);
        };
        if (doc.addEventListener
            && ! (navigator.userAgent.toString().match(/\sAppleWebKit\/([0-9]+)/i) && RegExp.$1 < 522)) {
            doc.addEventListener("DOMNodeInserted", handler, false);
            doc.addEventListener("DOMCharacterDataModified", handler, false);
            doc.addEventListener(
                "DOMAttrModified",
                function (evt) {
                    if (evt.attrName == 'style') {
                        var iframes = evt.target.getElementsByTagName('iframe');
                        for (var i = 0; i < iframes.length; i++) {
                            var doc = iframes[i].contentDocument;
                            if (! doc.__MYLINGUAL_DONE) {
                                MYLINGUAL.loadLocalizationData(doc);
                            }
                        }
                    } else if (evt.target.tagName == 'INPUT'
                        || evt.target.tagName == 'OPTION') {
                        handler(evt);
                    }
                },
                false);
        }
    },
    
    translateText: function (orig, mappings) {
        // direct match
        if (typeof mappings.text[orig] != 'undefined') {
            return mappings.text[orig];
        }
        // match (while taking care of surrounding spaces)
        if (orig.match(/^([ \r\n\t\xa0]*)(.+?)([ \r\n\t\xa0]*)$/)
            && (RegExp.$1 != '' || RegExp.$3 != '')) {
            if (typeof mappings.text[RegExp.$2] != 'undefined') {
                return RegExp.$1 + mappings.text[RegExp.$2] + RegExp.$3;
            }
        }
        // regexp
        for (var i = 0; i < mappings.re.length; i++) {
            var m = null;
            if (m = orig.match(mappings.re[i][0])) {
                return this._gsub(
                    mappings.re[i][1],
                    /\$(R?)([1-9])/g,
                    function (_dummy, rerun, digit) {
                        var t = m[digit - 0];
                        if (rerun) {
                            var t2 =
                                MYLINGUAL.translateText(t, mappings);
                            if (t2 != null) {
                                t = t2;
                            }
                        }
                        return t;
                    });
            }
        }
        return null;
    },
    
    // patch required for safari
    _gsub: function (str, re, func) {
        return str.replace(re, func);
    },
    
    initCommandMappings: function (mappings) {
        var f0 = function () {
            return {
                 re: [],
                 reCaseless: [],
                 text: {}
            }
        };
        var f1 = function () {
            return {
                'class': f0(),
                id:      f0(),
                path:    f0(),
                tag:     f0(),
                url:     f0()
            };
        };
        mappings.skip = f1();
        mappings.translate = f1();
    },
    
    postProcessCommandMappings: function (mappings) {
        var f0 = function (t, n, f) {
            t[n] = t[n].length != 0 ? new RegExp(t[n].join('|'), f) : undefined;
        };
        var f1 = function (t) {
            f0(t, 're', '');
            f0(t, 'reCaseless', 'i');
        };
        var f2 = function (t) {
            f1(t['class']);
            f1(t.id);
            f1(t.path);
            f1(t.tag);
            f1(t.url);
        };
        f2(mappings.skip);
        f2(mappings.translate);
    },
    
    compileCommand: function (mappings, name, value) {
        if (! name.match(/^\$(.*?)\(\s*(~{0,2})\s*(.*)\s*\)$/)) {
            return;
        }
        var type = RegExp.$1;
        var re = RegExp.$2 ? RegExp.$2.length : 0;
        var match = RegExp.$3;
        var store = mappings
            [value[0] == 'skip' || value[0] == '' ? 'skip' : 'translate']
            [type];
        if (typeof store != 'object') {
            return;
        }
        if (re) {
            if (! new RegExp(match, re == 2 ? 'i' : '')) {
                this.debugAlert('Syntax error: ' + name);
                return;
            }
            store[re == 2 ? 'reCaseless' : 're'].push(match);
        } else {
            if (type == 'tag') {
                match = match.toUpperCase();
            }
            store.text[match] = 1;
        }
    },
    
    ifSkipURL: function (mappings, loc) {
        return this.translateOrSkip(mappings.skip.path, loc.pathname)
            || this.translateOrSkip(mappings.skip.url, loc.toString());
    },
    
    translateOrSkipElement: function (mappings, e, current) {
        var table = mappings[current ? 'skip' : 'translate'];
        if (this.translateOrSkip(table.tag, e.tagName)
            || e.className && this.translateOrSkip(table['class'], e.className)
            || e.id && this.translateOrSkip(table.id, e.id)) {
            return ! current;
        }
        return current;
    },
    
    translateOrSkip: function (table, value) {
        value = value.toString();
        return typeof table.text[value] != 'undefined'
            || (table.re && value.match(table.re))
            || (table.reCaseless && value.match(table.reCaseless));
    },
    
    getElementTranslationMode: function (mappings, body, element) {
        var path = [];
        for (var p = (element.nodeType == 1 ? element : element.parentNode);
             p != body;
             p = p.parentNode) {
           path.push(p);
        }
        var translate = true;
        while (path.length != 0) {
            translate =
                this.translateOrSkipElement(mappings, path.pop(), translate);
        }
        return translate;
    },
    
    localizeElement: function (node, mappings, translate) {
        if (node.nodeType == 1) {
            translate = this.translateOrSkipElement(mappings, node, translate);
            if (node.nodeName == "SCRIPT" || node.nodeName == "STYLE") {
                // nothing to do
                return;
            } else if (node.nodeName == "INPUT") {
                if (! translate) {
                    return;
                }
                if (node.type == "button" || node.type == "reset") {
                    var translated = this.translateText(node.value, mappings);
                    if (translated != null) {
                        node.value = translated;
                    }
                }
                return;
            } else if (node.nodeName == "OPTION") {
                if (! translate) {
                    return;
                }
                var translated = this.translateText(node.text, mappings);
                if (translated != null) {
                    node.value = node.value.toString();
                    node.text = translated;
                }
                return;
            }
            var children = node.childNodes;
            for (var i = 0; i < children.length; i++) {
                this.localizeElement(children.item(i), mappings, translate);
            }
        } else if (translate && node.nodeType == 3) {
            var translated =
                this.translateText(node.nodeValue, mappings);
            if (translated != null) {
                node.nodeValue = translated;
            }
        }
    },
    
    parseJSON: function (text) {
        var json = undefined;
        try {
            var s = Components.utils.Sandbox(
                "http://sandbox.japanize.31tools.com/");
            Components.utils.evalInSandbox("json = " + text, s);
            json = s.json;
        } catch (e) {
            this.debugAlert(e);
        }
        return json;
    },
    
    findBrowser: function (doc) {
        var tb = getBrowser();
        for (var i = 0; i < tb.browsers.length; ++i) {
            var b = tb.getBrowserAtIndex(i);
            if (b.contentDocument == doc) {
                return b;
            }
        }
        return null;
    },
    
    updateStatus: function (browser, text) {
        if (browser == null) return;
        browser.contentDocument.JAPANIZED_status = text;
        this.redrawStatus();
    },
    
    redrawStatus: function () {
        var status =
            getBrowser().selectedBrowser.contentDocument.JAPANIZED_status;
        if (typeof status == "undefined") {
             status = "";
        }
        var label = document.getElementById("japanize-status-label");
        if (status == "") {
            label.style.display = "none";
        } else {
            label.value = status;
            label.style.display = "inline";
        }
    },
    
    getMode: function () {
        var img = document.getElementById("japanize-status-icon");
        return ! ! img.src.toString().match(/icon_on(_message)?\.gif$/);
    },
    
    setMode: function (on) {
        // setup icon
        var s = on ? "on" : "off";
        var img = document.getElementById("japanize-status-icon");
        img.src = img.src.toString().replace(/icon_o(n|ff)/, "icon_" + s);
        document.getElementById("japanize-status-main").value =
            this._s('tooltip.is' + s);
    },
    
    getMenuItem: function (suffix) {
        return document.getElementById("japanize-popup-" + suffix);
    },
    
    getBundle: function () {
        return document.getElementById("japanize-bundle");
    },
    
    _s: function (n) {
        try {
            return this.getBundle().getString('mylingual.' + n);
        } catch (e) {
            this.debugAlert(
                "Could not find string resource: " + n + "\n\n" + e.toString());
        }
    },
    
    _f: function (n, a) {
        return this.getBundle().getFormattedString('mylingual.' + n, a);
    },
    
    showPopup: function (evt) {
        this.getMenuItem("enabled").setAttribute(
            "checked", this.getMode().toString());
        if (this.getUpdateMode() == this.UPDATEMODE_EVERYTIME) {
            this.getMenuItem("updateeverytime").setAttribute(
                "checked", "true");
            this.getMenuItem("updateperiodically").setAttribute(
                "checked", "false");
            this.getMenuItem("updatenow").setAttribute(
                "disabled", "true");
        } else {
            this.getMenuItem("updateeverytime").setAttribute(
                "checked", "false");
            this.getMenuItem("updateperiodically").setAttribute(
                "checked", "true");
            this.getMenuItem("updatenow").setAttribute(
                "disabled", "false");
        }
    },
    
    handlePopup: function (evt) {
        if (! evt.target.id.toString().match(/^japanize-popup-/)) {
            return;
        }
        var cmd = RegExp.rightContext;
        if (cmd == "enabled") {
            this.setMode(! this.getMode());
        } else if (cmd == "updateeverytime") {
            this.setUpdateMode(this.UPDATEMODE_EVERYTIME);
        } else if (cmd == "updateperiodically") {
            this.setUpdateMode(this.UPDATEMODE_PERIODICALLY);
            if (this.needsUpdate()) {
                if (this.getPromptService().confirm(
                        window,
                        this._s('prompt.title'),
                        this._s('prompt.recommendupdate'))) {
                    this.downloadAllTable(true);
                }
            }
        } else if (cmd == "updatenow") {
            this.downloadAllTable(true);
        }
    },
    
    saveAllTable: function (content, buildDate, retryCount, notifyUser) {
        try {
            this.saveToStore("all.txt", content);
            this.setPreference("$builddate", buildDate);
            if (notifyUser) {
                this.notifyUser(this._s('prompt.defupdated'));
            }
        } catch (e) {
            if (retryCount == 0) {
                this.notifyUser(
                    this._s('prompt.defsavefailed')
                    + "\n\n" + e.toString());
            } else {
                setTimeout(
                    function () {
                        MYLINGUAL.saveAllTable(
                            content, buildDate, retryCount - 1, notifyUser);
                    },
                    100);
            }
        }
    },
    
    onAllTableDownload: function (xhr, notifyUser) {
        var json = this.parseJSON(xhr.responseText);
        if (typeof json != "object") {
            if (notifyUser) {
                this.notifyUser(this._s('prompt.defdownloadfailed'));
            }
            return;
        }
        if (json["$builddate"] == this.getPreference("$builddate", -1)) {
            if (notifyUser) {
                this.notifyUser(this._s('prompt.defnochanges'));
            }
            return;
        }
        this.saveAllTable(xhr.responseText, json["$builddate"], 10, notifyUser);
    },
    
    downloadAllTable: function (notifyUser) {
        try {
            var xhr = new XMLHttpRequest();
            xhr.open(
                "get",
                this.getBaseURL() + "alldata/all.txt",
                true);
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4) {
                    MYLINGUAL.onAllTableDownload(xhr, notifyUser);
                }
            };
            xhr.send(null);
        } catch (e) {
            alert("Japanize : " + e.toString());
        }
    },
    
    needsUpdate: function () {
        // every 3 hours by default
        var interval = this.getPreference("updateinterval", 3 * 3600) * 1000;
        var lastUpdateAt = this.getPreference("lastupdateat", 0) * 1;
        if (new Date().getTime() < lastUpdateAt + interval) {
            return false;
        }
        return true;
    },
    
    periodicalTasks: function () {
        if (! this.needsUpdate()) {
            return;
        }
        this.setPreference("lastupdateat", new Date().getTime());
        
        // update alltable if in periodical mode
        if (this.getUpdateMode() == this.UPDATEMODE_PERIODICALLY) {
            this.downloadAllTable(false);
        }
    }
};

// hacks for opera
MYLINGUAL.updateStatus = function (_dummy1, text) {
    if (text) {
        var t = document.createElement('div');
        t.id = '__mylingual_status';
        (function (o) {
            for (var i in o) {
                t.style[i] = o[i];
            }
        })({
            border: '1px solid #666',
            background: '#f88',
            padding: '0.3em',
            color: 'black',
            fontFamily: 'sans-serif',
            fontWeight: 'bold',
            position: window.innerWidth ? 'fixed' : 'absolute',
            left: '10px',
            bottom: '10px',
            zIndex: 100
        });
        document.body.appendChild(t);
        t.innerHTML = 'Japanize: ' + text;
        window.setTimeout(
            function () {
                t.parentNode.removeChild(t);
            },
            3000);
    }
};

MYLINGUAL._s = function (n) {
    return {
        'status.translated': '翻訳完了',
        'status.bannedpage': '翻訳しないページ'
    }[n];
};
MYLINGUAL._f = function () {};
MYLINGUAL.localizeOpera = function (json) {
    if (typeof json != 'object') return;
    this.localizeWithData(json, document, {});
};

// flickr uses customized String.prototype.replace
if ("0a1".replace(/[a-z]/g, function (m) { return "A"; }) != "0A1") {
    MYLINGUAL._gsub = function (str, re, func) {
        var out = "";
        var match;
        var start = re.lastIndex = 0;
        while (match = re.exec(str)) {
            out += str.slice(start, match.index);
            out += func.apply(null, match).toString();
            start = re.lastIndex;
        }
        out += str.substring(start);
        return out;
    };
}

(function () {
    var elem = document.createElement('script');
    elem.src =
        'http://japanize.31tools.com/data_jsonp/'
        + location.host
        + '?jsonp=MYLINGUAL.localizeOpera';
    document.body.appendChild(elem);
})();