VNDB优先原文和中文化

优先显示原文(title->value),以及中文化(mainMap[value]->value)

目前为 2022-06-08 提交的版本。查看 最新版本

// ==UserScript==
// @name         VNDB优先原文和中文化
// @namespace    http://tampermonkey.net/
// @version      4.3.1
// @description  优先显示原文(title->value),以及中文化(mainMap[value]->value)
// @author       aotmd
// @match        https://vndb.org/*
// @noframes
// @license MIT
// @run-at document-body
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @require https://gf.qytechs.cn/scripts/445990-vndbtranslatorlib/code/VNDBTranslatorLib.js?version=1058974
// ==/UserScript==

/**-----------------------------业务逻辑部分[300行]----------------------------------*/
/** ---------------------------map处理---------------------------*/
let pathname = window.location.pathname;
otherPageRules.forEach((item) => {
    //当regular是正则才执行
    if (item.regular !== undefined && item.regular instanceof RegExp) {
        if (item.regular.test(pathname)) {
            //添加到主map,若存在重复项则覆盖主map
            Object.assign(mainMap, item.map);
            //添加特殊map
            Object.assign(specialMap, item.specialMap);
            //添加titleMap
            Object.assign(titleMap, item.titleMap);
            console.log(item.name + ',规则匹配:' + pathname + '->' + item.regular);
        }
    }
});
/*object转Map, 正则new效率原因,先new出来*/
(function () {
    let tempMap = new Map();
    let k = Object.getOwnPropertyNames(specialMap);
    for (let i = 0, len = k.length; i < len; i++) {
        try {
            tempMap.set(new RegExp(k[i]), specialMap[k[i]]);
        } catch (e) {
            console.log('"' + k[i] + '"不是一个合法正则表达式');
        }
    }
    specialMap = tempMap;
})();
/** ----------------------------END----------------------------*/


/**
 * 递归节点
 * @param el   要处理的节点
 * @param func 调用的函数
 */
function 递归(el, func) {
    const nodeList = el.childNodes;
    /*先处理自己*/
    数据归一化(el,false);
    for (let i = 0; i < nodeList.length; i++) {
        const node = nodeList[i];
        数据归一化(node);
    }
    function 数据归一化(el,recursion=true) {
        if (el.nodeType === 1) {
            //为元素则递归
            if (recursion){
                递归(el, func);
            }
            let attribute, value, flag = false;
            if (el.nodeName === 'INPUT') {
                value = el.getAttribute('value');
                attribute = 'value';
                if (value == null || value.trim().length === 0) {
                    value = el.getAttribute('placeholder');
                    attribute = 'placeholder';
                }
                flag = true;
            } else if (el.nodeName === 'TEXTAREA') {
                value = el.getAttribute('placeholder');
                attribute = 'placeholder';
                flag = true;
            } else if (el.getAttribute('title')!==null&&
                el.title.length!==0) {
                /*过判断用*/
                value = 'title用过判断value值';
                attribute = 'title';
                flag = true;
            }
            if (!flag) return;
            func(el, value, attribute);
        } else if (el.nodeType === 3) {
            //为文本节点则处理数据
            func(el, el.nodeValue);
        }
    }
}
recordsList = [];
let observerMap = new Map();
/**
 * dom修改事件,包括属性,内容,节点修改
 * @param document 侦听对象
 * @param func  执行函数,可选参数(records),表示更改的节点
 */
function dom修改事件(document, func) {
    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;//浏览器兼容
    const config = {attributes: true, childList: true, characterData: true, subtree: true};//配置对象
    const observer = new MutationObserver(function (records, itself) {
        recordsList.push(records);
        //进入后停止侦听
        let flag = false;
        let obsArr = [];
        let selfIndex = -1;
        let doc = -1;
        //找到当前对象对应的value,和索引,以及k
        for (let key of observerMap.keys()) {
            let t = observerMap.get(key);
            for (let i = 0; i < t.length; i++) {
                if (itself === t[i][0]) {
                    obsArr = t;
                    selfIndex = i;
                    doc = key;
                    flag = true;
                    break;
                }
            }
            if (flag) {
                break;
            }
        }
        if (selfIndex === -1) {
            console.error('没有找到obs的v');
            return;
        }
        /*停止与之相同config的obs*/
        for (let i = 0; i < obsArr.length; i++) {
            if (JSON.stringify(obsArr[i][1]) === JSON.stringify(obsArr[selfIndex][1])) {
                obsArr[i][0].disconnect()
            }
        }
        /*调用与之相同config的obs*/
        try {
            for (let i = 0; i < obsArr.length; i++) {
                if (JSON.stringify(obsArr[i][1]) === JSON.stringify(obsArr[selfIndex][1])) {
                    obsArr[i][2](records);
                }
            }
        } catch (e) {
            console.error('执行错误')
        }
        //启用与之相同config的obs
        for (let i = 0; i < obsArr.length; i++) {
            if (JSON.stringify(obsArr[i][1]) === JSON.stringify(obsArr[selfIndex][1])) {
                obsArr[i][0].observe(doc, obsArr[i][1]);
            }
        }
    });
    if (observerMap.get(document) !== undefined) {
        let v = observerMap.get(document);
        v.push([observer, config, func]);
        observerMap.set(document, v);
    } else {
        observerMap.set(document, [[observer, config, func]]);
    }
    /*开始侦听*/
    observer.observe(document, config);
}

(function () {
    /*立即执行*/
    console.time('初始原文化 ,时间');
    递归(document.body, 原文化);
    console.timeEnd('初始原文化 ,时间');
    console.time('初始字典翻译,时间');
    递归(document.body, 字典翻译);
    console.timeEnd('初始字典翻译,时间');
    /*当body发生变化时执行*/
    dom修改事件(document.body, (records) => {
        console.time('原文化 ,时间');
        for (let i = 0, len = records.length; i < len; i++) {
            递归(records[i].target, 原文化);
        }
        console.timeEnd('原文化 ,时间');
        console.time('字典翻译,时间');
        for (let i = 0, len = records.length; i < len; i++) {
            递归(records[i].target, 字典翻译);
        }
        console.timeEnd('字典翻译,时间');
    });

    function 原文化(node, value, attribute = 'Text') {
        if (value == null || value.trim().length === 0) return;
        value = value.trim();

        if (attribute === 'Text') {
            let title = node.parentNode.getAttribute("title");
            if (内容判定(title, value)) {
                node.parentNode.setAttribute("title", value);
                node.nodeValue = title;
                // console.log(value+'->'+title)
            }
        } else {
            let title = node.getAttribute("title");
            if (内容判定(title, value)) {
                //若为通常节点则正常设置属性
                node.setAttribute('title', value);
                node.setAttribute(attribute, title);
                // console.log(value+'->'+title)
            }
        }

        /**
         * 显示的部分不为中文或日文,并且交换的部分为中文或日文
         * 且不应只有空格和> (标签链接浏览器页vn匹配)
         * 并且value没有对应翻译值,title没有翻译过[通过查找' \t\n'标记判断]
         * @param title
         * @param value
         * @returns {boolean}
         */
        function 内容判定(title, value) {
            return title != null
                && !/[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(value)
                && !/^[> ]+$/.test(value)
                && /[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(title)
                && mainMap[value]===undefined
                && title.indexOf(' \t\n')===-1;
        }
    }

    function 字典翻译(node, value, attribute = 'Text') {
        if (value == null || value.trim().length === 0) return;
        value = value.trim();
        /** titleMap翻译*/
        if (attribute==='title'){
            if(mainMap[value] === undefined
                &&node.nodeType === 1&&node.title
                ){
                /*如果为节点类型,value没有翻译,且存在title*/
                let flag=true;
                /*判断子节点文本,若文本为中文日文或匹配mainMap或与title相等,则执行后续操作*/
                let nodelist=node.childNodes;
                for (let i=0;i<nodelist.length;++i){
                    if (nodelist[i].nodeType===3&&
                        (/[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(nodelist[i].nodeValue)
                            ||mainMap[nodelist[i].nodeValue] !== undefined
                            ||node.title===nodelist[i].nodeValue)
                    ){
                        flag=false;
                        break;
                    }
                }
                if (flag){/*加上翻译信息*/
                    if (titleMap[node.title] !== undefined) {
                        node.title=titleMap[node.title]+" \t\n"+node.title;
                    }else if (mainMap[node.title]!==undefined){
                        node.title=mainMap[node.title]+" \t\n"+node.title;
                    }
                }
            }
            return;
        }
        /** mainMap翻译*/
        if (mainMap[value] !== undefined) {
            if (attribute === 'Text') {
                //若为文本节点则追加父节点title属性
                let title = node.parentNode.getAttribute('title');
                if (title != null && title.trim() !== value) {
                    node.parentNode.setAttribute('title', title + ' ' + value);
                } else {
                    node.parentNode.setAttribute('title', value);
                }
                node.nodeValue = mainMap[value];
            } else {
                //若为通常节点则正常设置属性
                node.setAttribute('title', value);
                node.setAttribute(attribute, mainMap[value])
            }
        }else {
            /** specialMap正则翻译*/
            //遍历specialMap,正则替换
            for (let key of specialMap.keys()) {
                /*正则匹配*/
                if (key.test(value)) {
                    /*正则替换*/
                    let newValue = value.replace(key, specialMap.get(key));

                    /*若有循环替换符,则进行替换*/
                    let nvs = newValue.split('%%');

                    /*如果map的值没有中文,且带%%%%,则设置flag为true*/
                    let flag = false;
                    if (!/[\u4E00-\u9FA5]+/.test(specialMap.get(key))
                        && nvs.length !== 1 && nvs.length % 2 === 1) {
                        flag = true;
                    }
                    if (nvs.length !== 1 && nvs.length % 2 === 1) {
                        for (let i = 1; i < nvs.length; i += 2) {
                            /*转小写*/
                            let low = nvs[i].split('@@');
                            if (low.length === 3) {
                                nvs[i] = low[1].toLowerCase();
                            }
                            /*匹配mainMap*/
                            if (mainMap[nvs[i]] !== undefined) {
                                nvs[i] = mainMap[nvs[i]];
                                /*若找到map,则重新置flag为false*/
                                flag = false;
                            }
                        }
                        newValue = nvs.join('')
                    }
                    if (flag) {/*如果替换式没有中文,且%%%%也没有匹配,则跳过*/
                        continue;
                    }
                    if (attribute === 'Text') {
                        //若为文本节点则追加父节点title属性
                        let title = node.parentNode.getAttribute('title');
                        if (title != null && title.trim() !== value) {
                            node.parentNode.setAttribute('title', title + ' ' + value);
                        } else {
                            node.parentNode.setAttribute('title', value);
                        }
                        node.nodeValue = newValue;
                    } else {
                        //若为通常节点则正常设置属性
                        node.setAttribute('title', value);
                        node.setAttribute(attribute, newValue)
                    }
                    // console.log(value+'->'+newValue);
                    /*替换后结束遍历*/
                    break;
                }
            }
        }
    }
})();


/**-----------------------------开发用函数部分[350行]----------------------------------*/
/** 开启后通过控制台调用函数即可*/
let 开发者模式 = false;
if (开发者模式) {
    /*exportMap已弃用*/
    /**
     * 导出新的已被翻译的内容到控制台显示
     * <br>即手动在网页上改文本,注意:
     * <br>先在要翻译的文本中间写入翻译后的内容
     * <br>然后用del和backspace删除前后内容
     * <br>开启编辑模式:
     * <br>document.body.contentEditable='true';
     * <br>document.designMode='on';
     */
    exportMap = function () {
        let addMap = {};
        递归(document.body, 数据处理);
        /*导出到控制台处理*/
        console.log(JSON.stringify(addMap));

        function 数据处理(node, value, attribute = 'Text') {
            if (value == null || value.trim().length === 0) return;
            value = value.trim();
            //没有在map中找到翻译
            if (mainMap[value] === undefined) {
                //是中文、不是日文
                if (/[\u4E00-\u9FA5]+/.test(value) &&
                    !/[ぁ-んァ-ヶ]+/.test(value)) {
                    if (attribute === 'Text') {
                        node = node.parentNode;
                    }
                    let title = node.getAttribute('title');
                    //如果title没有翻译,则记录
                    if (title !== null && mainMap[title] === undefined) {
                        addMap[title] = value;
                    }
                }
            }
        }
    };
    /*** 记录所有满足条件的未翻译内容<br>缺点为找不到上下文*/
    noMap = {};
    /*** 记录所有满足条件的未翻译提示信息<br>缺点为找不到上下文*/
    noTitleMap={};
    /*** <s>用以复制value到title[已弃用]</s>
     * <br>现用来导出未翻译的title和value,map->控制台
     * <br>若出现新元素,请手动通过控制台重新调用
     * <br>若干扰项太多,可以通过删除干扰元素,再重新调用
     * */
    copyToTitle = () => {
        //清空
        noMap = {};
        noTitleMap={};
        递归(document.body, 数据处理);
        console.log(JSON.stringify(noMap));
        console.log(JSON.stringify(noTitleMap));

        function 数据处理(node, value, attribute = 'Text') {
            if (value == null || value.trim().length === 0) return;
            value = value.trim();
            //没有在map中找到翻译
            if (mainMap[value] === undefined) {
                //1<长度<300,不为中文、日文,不是纯数字
                if (1 < value.length && value.length < 300
                    && !/[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(value)
                    && !/^[\d]+$/.test(value)) {
                    //归一化处理
                    if (attribute === 'Text') {
                        node = node.parentNode;
                    }
                    let title = node.getAttribute('title');
                    //title属性为中文或日文时不执行后续操作
                    if (title != null && /[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(title)) {
                        return;
                    }
                    //未翻译的节点title
                    if(title!= null&&titleMap[title]===undefined){
                        let flag=true;
                        /*判断子节点文本,若文本为中文日文或匹配mainMap或与title相等,则不添加到未翻译title*/
                        let nodelist=node.childNodes;
                        for (let i=0;i<nodelist.length;++i){
                            if (nodelist[i].nodeType===3&&
                                (/[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(nodelist[i].nodeValue)
                                ||mainMap[nodelist[i].nodeValue] !== undefined
                                ||title===nodelist[i].nodeValue)
                            ){
                                flag=false;
                                break;
                            }
                        }
                        /*如果存在title,且title没有定义在mainMap*/
                        /*那么添加到待翻译title信息*/
                        if (flag){
                            noTitleMap[title] = title.toLowerCase();
                        }
                    }
                    //复制value->title
                    // if (title != null && title.trim() !== value) {
                    //     node.setAttribute('title', title + ' ' + value);
                    // } else {
                    //     node.setAttribute('title', value);
                    // }
                    //设置没有翻译的map标记
                    noMap[value] = value.toLowerCase();
                }
            }
        }
    };
    /*立即执行*/
    copyToTitle();
    /**
     *统计不应该匹配,但可以匹配的k->v与正则,用以将局部map升级到主map
     * @type {{Object}}
     */
    otherLog = GM_getValue('otherLog') || {};
    console.log(otherLog);
    delotherLog = () => {
        GM_deleteValue('otherLog');
    };
    /**
     * 按降序显示otherLog数组
     */
    showotherLog=()=>{
        //复制一份
        let temp=JSON.parse(JSON.stringify(otherLog));
        let k = Object.getOwnPropertyNames(temp);
        let otherLogList=[];
        for (let i = 0, len = k.length; i < len; i++) {
            temp[k[i]].unshift(k[i]);
            otherLogList.push(temp[k[i]]);
        }
        /*排序*/
        otherLogList.sort(function (obj1, obj2) {
            return obj2[1] - obj1[1];
        });
        console.log(otherLogList);

        let sb='匹配项\t匹配数\t匹配时机\t匹配结果\n';
        for (let i=0,len=otherLogList.length;i<len;i++){
            sb+=otherLogList[i].join('\t')+'\n'
        }
        console.log(sb)
    };
    /*未生效规则匹配测试,用以筛选常用规则手动上移至主规则*/
    (() => {
        /*object转Map,将其他没有生效的map合起来*/
        let otherMap = {};
        let otherTitleMap={};
        let otherSpecialMap = new Map();
        otherPageRules.forEach((item) => {
            let pathname = window.location.pathname;
            if (item.regular !== undefined && item.regular instanceof RegExp && !item.regular.test(pathname)) {
                let k = Object.getOwnPropertyNames(item.specialMap);
                for (let i = 0, len = k.length; i < len; i++) {
                    try {
                        otherSpecialMap.set(new RegExp(k[i]), item.specialMap[k[i]]);
                    } catch (e) {
                        console.log('"' + k[i] + '"不是一个合法正则表达式');
                    }
                }
                Object.assign(otherMap, item.map);
                Object.assign(otherTitleMap, item.titleMap);
            }
        });
        /*立即执行*/
        console.time('初始其他规则,调试');
        递归(document.body, 未生效规则匹配测试);
        console.timeEnd('初始其他规则,调试');
        /*当body发生变化时执行*/
        dom修改事件(document.body, (records) => {
            console.time('其他规则,调试');
            for (let i = 0, len = records.length; i < len; i++) {
                递归(records[i].target, 未生效规则匹配测试);
            }
            console.timeEnd('其他规则,调试');
            /*若不相等则更新并输出*/
            if (JSON.stringify(otherLog) !== JSON.stringify(GM_getValue('otherLog') || {})) {
                GM_setValue('otherLog', otherLog);
                console.log(otherLog);
            }
        });

        /**
         * 统计不应该匹配,但可以匹配的k->v与正则,用以将局部map升级到主map
         * @param key
         * @param value
         */
        function otherLogAdd(key, value) {
            if (otherLog[key] === undefined) {
                otherLog[key] = [1, value[0], value[1]];
            } else {
                let item = otherLog[key];
                item[0]++;
                /*去重*/
                let a1 = item[1].split('$$');
                a1.push(value[0]);
                let mySet = new Set(a1);
                a1 = [...mySet];
                item[1] = a1.join('$$');

                a1 = item[2].split('$$');
                a1.push(value[1]);
                mySet = new Set(a1);
                a1 = [...mySet];
                item[2] = a1.join('$$');
            }
        }

        function 未生效规则匹配测试(node, value, attribute = 'Text') {
            if (value == null || value.trim().length === 0) return;
            value = value.trim();
            /*不被mainMap和specialMap匹配*/
            /*由于执行顺序的原因,该判断基本没有意义*/
            if (mainMap[value] !== undefined) {
                return;
            }
            /*因为正则涉及匹配可能太广,所以不排除*/
            // for (let key of specialMap.keys()) {
            //     if (key.test(value)) {
            //         return;
            //     }
            // }

            /*1<长度<300,不为中文、日文,不是纯数字,降低缩进*/
            let f = true;
            if (1 < value.length && value.length < 300
                && !/[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(value)
                && !/^[\d]+$/.test(value)) {
                f = false;
            }
            if (f) return;
            /*title翻译*/
            /** titleMap翻译*/
            if (attribute==='title'){
                if(otherMap[value] === undefined
                    &&node.nodeType === 1&&node.title
                ){
                    /*如果为节点类型,value没有翻译,且存在title*/
                    let flag=true;
                    /*判断子节点文本,若文本为中文日文或匹配mainMap或与title相等,则执行后续操作*/
                    let nodelist=node.childNodes;
                    for (let i=0;i<nodelist.length;++i){
                        if (nodelist[i].nodeType===3&&
                            (/[\u4E00-\u9FA5ぁ-んァ-ヶ]+/.test(nodelist[i].nodeValue)
                                ||otherMap[nodelist[i].nodeValue] !== undefined
                                ||node.title===nodelist[i].nodeValue)
                        ){
                            flag=false;
                            break;
                        }
                    }
                    if (flag){/*加上翻译信息*/
                        let title=node.title;
                        if (otherTitleMap[title] !== undefined) {
                            node.title=otherTitleMap[title]+" \t\n"+title;
                            otherLogAdd(title, ['otherTitleMap匹配', otherTitleMap[title]]);
                        }else if (otherMap[node.title]!==undefined){
                            node.title=otherMap[title]+" \t\n"+title;
                            otherLogAdd(title, ['otherTitleMap->otherMap匹配', otherMap[title]]);
                        }
                    }
                }
                return;
            }


            if (otherMap[value] !== undefined) {
                if (attribute === 'Text') {
                    //若为文本节点则追加父节点title属性
                    let title = node.parentNode.getAttribute('title');
                    if (title != null && title.trim() !== value) {
                        node.parentNode.setAttribute('title', title + ' ' + value);
                    } else {
                        node.parentNode.setAttribute('title', value);
                    }
                    node.nodeValue = otherMap[value];
                    otherLogAdd(value, ['otherMap匹配,Text', otherMap[value]]);

                } else {
                    //若为通常节点则正常设置属性
                    node.setAttribute('title', value);
                    node.setAttribute(attribute, otherMap[value]);
                    otherLogAdd(value, ['otherMap匹配,节点', otherMap[value]]);
                }
            } else {
                //遍历specialMap,正则替换
                for (let key of otherSpecialMap.keys()) {
                    /*正则匹配,降低缩进*/
                    if (!key.test(value)) {continue;}
                    let info = 'otherSpecialMap匹配,正则:' + key + ',';
                    /*正则替换*/
                    let newValue = value.replace(key, otherSpecialMap.get(key));
                    /*若有循环替换符,则进行替换*/
                    let nvs = newValue.split('%%');
                    /*如果map的值没有中文,且带%%%%,则设置flag为true*/
                    let flag = false;
                    if (!/[\u4E00-\u9FA5]+/.test(otherSpecialMap.get(key))
                        && nvs.length !== 1 && nvs.length % 2 === 1) {
                        flag = true;
                    }
                    if (nvs.length !== 1 && nvs.length % 2 === 1) {
                        for (let i = 1; i < nvs.length; i += 2) {
                            /*转小写*/
                            let low = nvs[i].split('@@');
                            if (low.length === 3) {
                                nvs[i] = low[1].toLowerCase();
                            }
                            /*匹配otherMap*/
                            if (otherMap[nvs[i]] !== undefined) {
                                nvs[i] = otherMap[nvs[i]];
                                info += '在otherMap找到%%%%(额外匹配),';
                                /*若找到map,则重新置flag为false*/
                                flag = false;
                            }
                            /*匹配匹配mainMap*/
                            if (mainMap[nvs[i]] !== undefined) {
                                nvs[i] = mainMap[nvs[i]];
                                info += '在mainMap找到%%%%(额外匹配),';
                                /*若找到map,则重新置flag为false*/
                                flag = false;
                            }
                        }
                        newValue = nvs.join('')
                    }
                    /*如果替换式没有中文,且%%%%也没有匹配,则跳过*/
                    if (flag) {continue;}
                    if (attribute === 'Text') {
                        //若为文本节点则追加父节点title属性
                        let title = node.parentNode.getAttribute('title');
                        if (title != null && title.trim() !== value) {
                            node.parentNode.setAttribute('title', title + ' ' + value);
                        } else {
                            node.parentNode.setAttribute('title', value);
                        }
                        node.nodeValue = newValue;
                        info += 'Text';
                    } else {
                        //若为通常节点则正常设置属性
                        node.setAttribute('title', value);
                        node.setAttribute(attribute, newValue);
                        info += '节点';
                    }
                    otherLogAdd(value, [info, newValue]);
                    // console.log(value + '->' + newValue);
                    /*替换后结束遍历*/
                    break;
                }
            }
        }
    })();
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址