赛马娘官网中文翻译

为赛马娘官网内容提供中文翻译。

// ==UserScript==
// @name         赛马娘官网中文翻译
// @namespace    http://umamusume.jp/
// @version      1.1.0
// @description  为赛马娘官网内容提供中文翻译。
// @author       yingyingyingqwq
// @match        *://umamusume.jp/*
// @icon         https://umamusume.jp/favicon.ico
// @grant        GM_getResourceText
// @resource     Replacements https://umasite.yingqwq.cn/Replacements.json
// ==/UserScript==

(function () {
    'use strict';
    const Replacements = JSON.parse(GM_getResourceText("Replacements"));

    const IMGreplace = Replacements[3].images

    function replaceImageSource() {
        const path = window.location.pathname;
        if (IMGreplace[path]) {
            const imgConfig = IMGreplace[path];

            for (const altValue in imgConfig) {
                const images = document.querySelectorAll(`img[alt="${altValue}"]`);
                const replacements = imgConfig[altValue];

                images.forEach((img, index) => {
                    if (replacements[index + 1]) {
                        img.src = replacements[index + 1];
                    }
                });
            }
        }
    }

    function escapeRegExp(string) {
        return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }

    // 预编译所有替换规则
    const compiledReplacements = Replacements.map(replacement => {
        const partialPatterns = Object.keys(replacement.partial || {}).flatMap(key => {
            if (replacement.type === 'url' || replacement.type === 'element') {
                return Object.keys(replacement.partial[key]).map(subKey => ({
                    regex: new RegExp(escapeRegExp(subKey), 'g'),
                    value: replacement.partial[key][subKey],
                    matchType: "partial",
                    context: key
                }));
            } else {
                return {
                    regex: new RegExp(escapeRegExp(key), 'g'),
                    value: replacement.partial[key],
                    matchType: "partial"
                };
            }
        });
        const fullPatterns = Object.keys(replacement.full || {}).flatMap(key => {
            if (replacement.type === 'url' || replacement.type === 'element') {
                return Object.keys(replacement.full[key]).map(subKey => ({
                    regex: new RegExp(escapeRegExp(subKey)),
                    value: replacement.full[key][subKey],
                    matchType: "full",
                    context: key
                }));
            } else {
                return {
                    regex: new RegExp(escapeRegExp(key)),
                    value: replacement.full[key],
                    matchType: "full"
                };
            }
        });
        return { ...replacement, patterns: [...partialPatterns, ...fullPatterns] };
    });

    function getReplacementsForURL() {
        const path = window.location.pathname;
        return compiledReplacements.filter(replacement =>
            replacement.type === "global" ||
            (replacement.type === "url" && replacement.patterns.some(pattern => pattern.context === path))
        );
    }

    function getReplacementsForElement(element) {
        const dataAttributes = Array.from(element.attributes).map(attr => attr.name);
        return compiledReplacements.filter(replacement =>
            replacement.type === "global" ||
            (replacement.type === "element" && replacement.patterns.some(pattern => dataAttributes.includes(pattern.context)))
        );
    }

    function ReplaceText(node) {
        const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, null, false);
        let currentNode = walker.nextNode();
        const urlReplacements = getReplacementsForURL();

        while (currentNode) {
            const elementReplacements = getReplacementsForElement(currentNode.parentElement);
            const allReplacements = [...urlReplacements, ...elementReplacements];

            let text = currentNode.nodeValue;
            allReplacements.forEach(({ patterns }) => {
                patterns.forEach(({ regex, value, matchType, context }) => {
                    if (context && window.location.pathname !== context && !currentNode.parentElement.hasAttribute(context)) return;
                    if (matchType === "partial") {
                        text = text.replace(regex, value);
                    } else if (matchType === "full" && text === regex.source) {
                        text = value;
                    }
                });
            });
            if (text !== currentNode.nodeValue) {
                currentNode.nodeValue = text;
            }
            currentNode = walker.nextNode();
        }
    }

    function ReplaceTitle() {
        let title = document.title;
        const urlReplacements = getReplacementsForURL();

        urlReplacements.forEach(({ patterns }) => {
            patterns.forEach(({ regex, value, matchType, context }) => {
                if (context && window.location.pathname !== context) return;
                if (matchType === "partial") {
                    title = title.replace(regex, value);
                } else if (matchType === "full" && title === regex.source) {
                    title = value;
                }
            });
        });
        document.title = title;
    }
    

    function replaceYoutubeVideo() {
        const videoMapping = Replacements[4].videos
    
        const youtubeIframes = document.querySelectorAll('iframe[src*="youtube-nocookie.com/embed/"]');
    
        youtubeIframes.forEach(iframe => {
            const youtubeUrl = new URL(iframe.src);
            const videoId = youtubeUrl.pathname.split('/').pop();
    
            const bilibiliId = videoMapping[videoId];
            if (bilibiliId) {
                const bilibiliUrl = `https://player.bilibili.com/player.html?bvid=${bilibiliId}&high_quality=1`;
    
                const bilibiliIframe = document.createElement('iframe');
                bilibiliIframe.src = bilibiliUrl;
                bilibiliIframe.width = iframe.width;
                bilibiliIframe.height = iframe.height;
                bilibiliIframe.frameBorder = "0";
                bilibiliIframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture";
                bilibiliIframe.allowFullscreen = true;
    
                iframe.parentNode.replaceChild(bilibiliIframe, iframe);
            }
        });
    }

    function changeFont() {
        if (!document.querySelector('#customFontStyle')) {
            const styleElement = document.createElement('style');
            styleElement.id = 'customFontStyle';
            document.head.appendChild(styleElement);
            styleElement.textContent = `
                body,label,.character-detail__visual-catch,.font-style-italic,.catch-text  {
                    font-family: Misans,YakuHanJP,Roboto,Zen Kaku Gothic New,sans-serif,微软雅黑 !important;
                }
                .mainstory-part1-section[data-v-dbd344ea],.font-weight-regular {
                    font-family: Noto Serif SC,Noto Serif JP,serif !important;
                }
            `;
        }
    }

    function observeDOMChanges() {
        const observer = new MutationObserver((mutations) => {
            ReplaceText(document.body);
            ReplaceTitle();
            replaceImageSource();
            replaceYoutubeVideo()
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    function loadMiSansFont() {
        var link = document.createElement('link');
        link.rel = 'stylesheet';
        link.crossOrigin = 'anonymous';
        link.href = 'https://cdn.jsdelivr.net/npm/[email protected]/lib/Normal/MiSans-Medium.min.css';

        document.head.appendChild(link);
    }

    function loadSongFont() {
        var link = document.createElement('link');
        link.rel = 'stylesheet';
        link.crossOrigin = 'anonymous';
        link.href = 'https://fonts.font.im/css2?family=Noto+Serif+SC:[email protected]&display=swap';

        document.head.appendChild(link);
    }

    loadMiSansFont();
    loadSongFont();
    ReplaceText(document.body);
    ReplaceTitle();
    changeFont();
    observeDOMChanges();
    replaceImageSource();
    replaceYoutubeVideo()
})();