Japanize.mod.core

Japanize Core Library

Dette scriptet burde ikke installeres direkte. Det er et bibliotek for andre script å inkludere med det nye metadirektivet // @require https://update.greasyfork.org/scripts/24109/153163/Japanizemodcore.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

var MYLINGUAL = {
    
    VERSION: "0.7.1",
    UID: "[email protected]",
    
    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);
})();