アットウィキロゴ
アサルトリリィwiki
ページ検索 ページ検索 メニュー メニュー
「Translation_javascript」の編集履歴(バックアップ)一覧に戻る

Translation_javascript - (2026/04/08 (水) 22:36:42) のソース

#javascript(){{
    function IstranslationOn() {
        return (localStorage["translationCheck"] || "") === "true";
    }

    // 連続する同一リンクの統合(content 内で処理)
    function mergeAdjacentSameHrefAnchors(content, separator = ' ') {
        if (!content) return;
        // 先に全アンカーを配列化しておく(DOM操作で要素が削除されても安全)
        const anchors = Array.from(content.querySelectorAll('a'));
        for (let i = 0; i < anchors.length; i++) {
            const a = anchors[i];
            if (!a.isConnected) continue; // 既に削除済みならスキップ
            let hrefA = a.getAttribute('href');
            if (!hrefA) continue;
            // 正規化(相対URL対応)
            try { hrefA = new URL(hrefA, document.baseURI).href; } catch (e) { /* そのまま比較 */ }

            let next = a.nextElementSibling;
            while (next && next.tagName === 'A') {
                let hrefNext = next.getAttribute('href');
                if (!hrefNext) break;
                try { hrefNext = new URL(hrefNext, document.baseURI).href; } catch (e) { /* そのまま比較 */ }

                if (hrefA === hrefNext) {
                    // 本文を結合(innerHTML を結合する。必要なら textContent に変更)
                    // 末尾・先頭の空白を整理して余分なスペースを作らない
                    const left = a.innerHTML.trim();
                    const right = next.innerHTML.trim();
                    a.innerHTML = left + separator + right;
                    const toRemove = next;
                    next = next.nextElementSibling;
                    toRemove.remove();
                } else {
                    break;
                }
            }
        }
    }

    // 連続するテキストノードを結合する
    function mergeAdjacentTextNodes(root) {
        const walker = document.createTreeWalker(
            root,
            NodeFilter.SHOW_TEXT,
            null
        );

        let node;
        while (node = walker.nextNode()) {
            let next = node.nextSibling;

            // 連続する textNode をまとめる
            while (next && next.nodeType === Node.TEXT_NODE) {
                node.textContent += next.textContent;
                const toRemove = next;
                next = next.nextSibling;
                toRemove.remove();
            }
        }
    }

    async function loadDictionary() {
        const res = await fetch("https://tgr55al.github.io/assault_lily-ga4-graph/terms.json");
        return await res.json();
    }

    async function translationFunc() {
        if (!IstranslationOn()) {
            return;
        }

        // 対象コンテント(優先順)
        const content = document.getElementById("content")
                     || document.querySelector(".atwiki-content")
                     || document.body;

        // ページ内のすべてのルビを削除し、平文に変える
        document.querySelectorAll("ruby").forEach(ruby => {
            ruby.querySelectorAll("rt, rp").forEach(el => el.remove());
            ruby.querySelectorAll("rb").forEach(rb => {
                while(rb.firstChild) { rb.parentNode.insertBefore(rb.firstChild, rb); }
                rb.remove();
            });
            while(ruby.firstChild) { ruby.parentNode.insertBefore(ruby.firstChild, ruby); }
            ruby.remove();
        });
        document.querySelectorAll("rb, rt").forEach(el => {
            while(el.firstChild) { el.parentNode.insertBefore(el.firstChild, el); }
            el.remove();
        });

        // ここで連続する同一リンクを統合しておく(ルビ削除後にDOMが安定しているため)
        mergeAdjacentSameHrefAnchors(content, ' ');

        // テキストノードを結合
        mergeAdjacentTextNodes(content);

        // 和英置換辞書
        const replaceDict = await loadDictionary();

        // 長い文字列優先でソートして処理
        const sortedEntries = Object.entries(replaceDict)
            .sort((a, b) => b[0].length - a[0].length);

        // テキスト置換
        const walker = document.createTreeWalker(
            content,
            NodeFilter.SHOW_TEXT,
            {
                acceptNode: function(node) {
                    const tag = node.parentNode.nodeName;
                    if (tag === "SCRIPT" || tag === "STYLE" || tag === "NOSCRIPT") {
                        return NodeFilter.FILTER_REJECT;
                    }
                    return NodeFilter.FILTER_ACCEPT;
                }
            }
        );

        let node;
        while (node = walker.nextNode()) {
            let text = node.textContent;
            let changed = false;

            for (let [ja, en] of sortedEntries) {
                const regex = new RegExp(ja, "g");
                if (regex.test(text)) {
                    text = text.replace(regex, " " + en + " ");
                    changed = true;
                }
                else if (regex.test(text.replace(/\s/g, ''))) {
                    text = text.replace(/\s/g, '').replace(regex, " " + en + " ");
                    changed = true;
                }
            }

            if (changed) {
                node.textContent = text;
            }
        }
    }

    // ページ読み込み後に実行
    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", translationFunc);
    } else {
        translationFunc();
    }
}}
記事メニュー
ウィキ募集バナー