Greasy Fork

Greasy Fork is available in English.

Tâi-gí「教典」TL ⇄ POJ

予代管當局 ROC 教育部 Tâi-gí 常用詞詞典網站呈現出 POJ!(對臺羅換過來、換轉去)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Tâi-gí「教典」TL ⇄ POJ
// @namespace    aiuanyu
// @version      2.2.1
// @description  予代管當局 ROC 教育部 Tâi-gí 常用詞詞典網站呈現出 POJ!(對臺羅換過來、換轉去)
// @author       Aiuanyu 愛灣語, TongcyDai
// @match        http*://sutian.moe.edu.tw/*
// @grant        none
// @license      GNU GPLv3
// ==/UserScript==

(function() {
    'use strict';

    // 預設使用 POJ
    let usePOJ = localStorage.getItem('sutianUsePOJ') !== 'false';

    // Add a flag for Wiktionary mode (default to off)
    let useWiktionaryMode = localStorage.getItem('sutianUseWiktionaryMode') === 'true';

    // 囥原始文字的 mapping table
    const originalTextMap = new WeakMap();

    // Create Wiktionary mode toggle
    function createWiktionaryToggle() {
        const toggle = document.createElement('button');
        toggle.id = 'wiktionary-toggle-btn';
        toggle.textContent = useWiktionaryMode ? 'Wikt Mode: ON' : 'Wikt Mode: OFF';
        toggle.style.position = 'fixed';
        toggle.style.bottom = '80px';
        toggle.style.left = '20px';
        toggle.style.zIndex = '9999';
        toggle.style.padding = '8px 12px';
        toggle.style.backgroundColor = useWiktionaryMode ? '#27ae60' : '#95a5a6';
        toggle.style.color = 'white';
        toggle.style.border = 'none';
        toggle.style.borderRadius = '4px';
        toggle.style.cursor = 'pointer';
        toggle.style.fontWeight = 'bold';
        toggle.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
        toggle.style.fontSize = '14px';

        // Hover effects
        toggle.addEventListener('mouseover', function() {
            this.style.backgroundColor = useWiktionaryMode ? '#219653' : '#7f8c8d';
        });

        toggle.addEventListener('mouseout', function() {
            this.style.backgroundColor = useWiktionaryMode ? '#27ae60' : '#95a5a6';
        });

        // Toggle Wiktionary mode
        toggle.addEventListener('click', function() {
            useWiktionaryMode = !useWiktionaryMode;
            localStorage.setItem('sutianUseWiktionaryMode', useWiktionaryMode);

            this.textContent = useWiktionaryMode ? 'Wikt Mode: ON' : 'Wikt Mode: OFF';
            this.style.backgroundColor = useWiktionaryMode ? '#27ae60' : '#95a5a6';

            // Refresh the current view if we're in POJ mode
            if (usePOJ) {
                restoreOriginalText(document.body);
                applyPOJTransformation(document.body);
            }
        });

        document.body.appendChild(toggle);
    }

    // 切換揤鈕
    function createToggleButton() {
        const button = document.createElement('button');
        button.id = 'poj-toggle-btn';
        button.textContent = usePOJ ? '用臺羅 (TL)' : '用白話字 (POJ)';
        button.style.position = 'fixed';
        button.style.bottom = '20px';
        button.style.left = '20px';
        button.style.zIndex = '9999';
        button.style.padding = '8px 12px';
        button.style.backgroundColor = '#3498db';
        button.style.color = 'white';
        button.style.border = 'none';
        button.style.borderRadius = '4px';
        button.style.cursor = 'pointer';
        button.style.fontWeight = 'bold';
        button.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';

        // 鼠仔滑過的效果
        button.addEventListener('mouseover', function() {
            this.style.backgroundColor = '#2980b9';
        });

        button.addEventListener('mouseout', function() {
            this.style.backgroundColor = '#3498db';
        });

        // 「揤」事件:切換羅馬字
        button.addEventListener('click', toggleRomanization);

        document.body.appendChild(button);
    }

    // 切換羅馬字
    function toggleRomanization() {
        usePOJ = !usePOJ;
        localStorage.setItem('sutianUsePOJ', usePOJ);

        const button = document.getElementById('poj-toggle-btn');
        if (button) {
            button.textContent = usePOJ ? '用臺羅 (TL)' : '用白話字 (POJ)';
        }

        if (usePOJ) {
            applyPOJTransformation(document.body);
        } else {
            restoreOriginalText(document.body);
        }
    }

    // 共文字對臺羅轉做白話字
    function applyPOJTransformation(node) {
        if (node.nodeType === Node.TEXT_NODE) {
            let parent = node.parentNode;
            while (parent) {
                if (parent.id === 'poj-toggle-btn' || parent.id === 'wiktionary-toggle-btn') {
                    return; // 跳過揤鈕文字
                }
                parent = parent.parentNode;
            }
            let text = node.nodeValue;
            const originalText = text;
            const exceptions = [];
            const regex = /\(([a-zA-Z]+)\)/g;
            let match;

            while ((match = regex.exec(text)) !== null) {
                exceptions.push({
                    start: match.index,
                    end: match.index + match[0].length,
                    original: match[0]
                });
            }

            // 先共欲排除个部份用一个暫時个字串取代,才袂予轉換
            let tempText = text;
            const tempMarkers = {};
            exceptions.forEach((exception, index) => {
                const marker = `__TEMP_EXCEPTION_${index}__`;
                tempMarkers[marker] = exception.original;
                tempText = tempText.substring(0, exception.start) + marker + tempText.substring(exception.end);
            });

            // 收囥原始文字 (排除例外以後)
            if (!originalTextMap.has(node)) {
                originalTextMap.set(node, originalText);
            }

            text = tempText.normalize('NFD').normalize('NFC');

            // ts → ch
            text = text.replace(/(?<=^|\W)ts/gi, function(match) { return (match[0] === 'T' ? 'Ch' : 'ch'); });

            // ua → oa
            // 處理 ua 後面接 i、n、t、h 的情形 (調符徙去 a 頂懸);注意 ua̍ ua̋ 是組合字元愛特別處理
            text = text.replace(/u(?:(a(?:[\u030d\u030b]))|([aáàâǎā]))([inth](?:\W|$))/gi, function(match, combinedA, precomposedA, end) {
              const theVowel = combinedA || precomposedA;
              const prefix = (match[0] === 'U' ? 'O' : 'o');
              return prefix + theVowel + end;
            });

            // 處理孤一个 ua 抑是後壁接 nn 的情形 (調符徙去 o 頂懸,nn 上尾才換)
            text = text.replace(/ua(?=nn\W|nn$|\W|$)/gi, function(match) { return (match[0] === 'U' ? 'O' : 'o') + 'a'; });
            text = text.replace(/uá(?=nn\W|nn$|\W|$)/gi, function(match) { return (match[0] === 'U' ? 'Ó' : 'ó') + 'a'; });
            text = text.replace(/uà(?=nn\W|nn$|\W|$)/gi, function(match) { return (match[0] === 'U' ? 'Ò' : 'ò') + 'a'; });
            text = text.replace(/uâ(?=nn\W|nn$|\W|$)/gi, function(match) { return (match[0] === 'U' ? 'Ô' : 'ô') + 'a'; });
            text = text.replace(/uǎ(?=nn\W|nn$|\W|$)/gi, function(match) { return (match[0] === 'U' ? 'Õ' : 'õ') + 'a'; });
            text = text.replace(/uā(?=nn\W|nn$|\W|$)/gi, function(match) { return (match[0] === 'U' ? 'Ō' : 'ō') + 'a'; });
            text = text.replace(/ua̍(?=nn\W|nn$|\W|$)/gi, function(match) { return (match[0] === 'U' ? 'O̍' : 'o̍') + 'a'; });
            text = text.replace(/ua̋(?=nn\W|nn$|\W|$)/gi, function(match) { return (match[0] === 'U' ? 'Ŏ' : 'ŏ') + 'a'; });

            // ue → oe (除了 ueh 以外,調符攏徙去 o 頂懸)
            text = text.replace(/ue/gi, function(match) { return (match[0] === 'U' ? 'O' : 'o') + 'e'; });
            text = text.replace(/ué/gi, function(match) { return (match[0] === 'U' ? 'Ó' : 'ó') + 'e'; });
            text = text.replace(/uè/gi, function(match) { return (match[0] === 'U' ? 'Ò' : 'ò') + 'e'; });
            text = text.replace(/uê/gi, function(match) { return (match[0] === 'U' ? 'Ô' : 'ô') + 'e'; });
            text = text.replace(/uě/gi, function(match) { return (match[0] === 'U' ? 'Õ' : 'õ') + 'e'; });
            text = text.replace(/uē/gi, function(match) { return (match[0] === 'U' ? 'Ō' : 'ō') + 'e'; });
            text = text.replace(/ue̍/gi, function(match) { return (match[0] === 'U' ? 'Oe̍' : 'oe̍'); });
            text = text.replace(/ue̋/gi, function(match) { return (match[0] === 'U' ? 'Ŏ' : 'ŏ') + 'e'; });

            // ui → ui (調符徙去 u 頂懸)
            // text = text.replace(/ui/gi, function(match) { return (match[0] === 'U' ? 'U' : 'u') + 'i'; });
            text = text.replace(/uí/gi, function(match) { return (match[0] === 'U' ? 'Ú' : 'ú') + 'i'; });
            text = text.replace(/uì/gi, function(match) { return (match[0] === 'U' ? 'Ù' : 'ù') + 'i'; });
            text = text.replace(/uî/gi, function(match) { return (match[0] === 'U' ? 'Û' : 'û') + 'i'; });
            text = text.replace(/uǐ/gi, function(match) { return (match[0] === 'U' ? 'Ũ' : 'ũ') + 'i'; });
            text = text.replace(/uī/gi, function(match) { return (match[0] === 'U' ? 'Ū' : 'ū') + 'i'; });
            text = text.replace(/ui̍/gi, function(match) { return (match[0] === 'U' ? 'U̍' : 'u̍') + 'i'; });
            text = text.replace(/ui̋/gi, function(match) { return (match[0] === 'U' ? 'Ŭ' : 'ŭ') + 'i'; });

            // oo → o͘
            text = text.replace(/oo(?=\W|$)/gi, function(match) { return (match[0] === 'O' ? 'O͘' : 'o͘'); });
            text = text.replace(/óo(?=\W|$)/gi, function(match) { return (match[0] === 'Ó' ? 'Ó͘' : 'ó͘'); });
            text = text.replace(/òo(?=\W|$)/gi, function(match) { return (match[0] === 'Ò' ? 'Ò͘' : 'ò͘'); });
            text = text.replace(/ôo(?=\W|$)/gi, function(match) { return (match[0] === 'Ô' ? 'Ô͘' : 'ô͘'); });
            text = text.replace(/ǒo(?=\W|$)/gi, function(match) { return (match[0] === 'Ǒ' ? 'Õ͘' : 'õ͘'); });
            text = text.replace(/ōo(?=\W|$)/gi, function(match) { return (match[0] === 'Ō' ? 'Ō͘' : 'ō͘'); });
            text = text.replace(/ooh(?=\W|$)/gi, function(match) { return (match[0] === 'O' ? 'O͘h' : 'o͘h'); }); // 替代處理,才袂換過頭
            text = text.replace(/o̍oh(?=\W|$)/gi, function(match) { return (match[0] === 'O' ? 'O̍͘h' : 'o̍͘h'); });
            text = text.replace(/őo(?=\W|$)/gi, function(match) { return (match[0] === 'Ő' ? 'Ŏ͘' : 'ŏ͘'); });

            // The following conversions should only run if Wiktionary mode is OFF
            if (!useWiktionaryMode) {
                // ee → e͘
                text = text.replace(/ee/gi, function(match) { return (match[0] === 'E' ? 'E͘' : 'e͘'); });
                text = text.replace(/ée/gi, function(match) { return (match[0] === 'É' ? 'É͘' : 'é͘'); });
                text = text.replace(/èe/gi, function(match) { return (match[0] === 'È' ? 'È͘' : 'è͘'); });
                text = text.replace(/êe/gi, function(match) { return (match[0] === 'Ê' ? 'Ê͘' : 'ê͘'); });
                text = text.replace(/ěe/gi, function(match) { return (match[0] === 'Ě' ? 'Ẽ͘' : 'ẽ͘'); });
                text = text.replace(/ēe/gi, function(match) { return (match[0] === 'Ē' ? 'Ē͘' : 'ē͘'); });
                text = text.replace(/e̍e/gi, function(match) { return (match[0] === 'E' ? 'E̍͘' : 'e̍͘'); });
                text = text.replace(/e̋e/gi, function(match) { return (match[0] === 'E' ? 'Ĕ͘' : 'ĕ͘'); });

                // er → o̤
                text = text.replace(/er/gi, function(match) { return (match[0] === 'E' ? 'O̤' : 'o̤'); });
                text = text.replace(/ér/gi, function(match) { return (match[0] === 'É' ? 'Ó̤' : 'ó̤'); });
                text = text.replace(/èr/gi, function(match) { return (match[0] === 'È' ? 'Ò̤' : 'ò̤'); });
                text = text.replace(/êr/gi, function(match) { return (match[0] === 'Ê' ? 'Ô̤' : 'ô̤'); });
                text = text.replace(/ěr/gi, function(match) { return (match[0] === 'Ě' ? 'Õ̤' : 'õ̤'); });
                text = text.replace(/ēr/gi, function(match) { return (match[0] === 'Ē' ? 'Ō̤' : 'ō̤'); });
                text = text.replace(/e̍r/gi, function(match) { return (match[0] === 'E' ? 'O̤̍' : 'o̤̍'); });
                text = text.replace(/e̋r/gi, function(match) { return (match[0] === 'E' ? 'Ŏ̤' : 'ŏ̤'); });

                // ir → ṳ
                text = text.replace(/ir/gi, function(match) { return (match[0] === 'I' ? 'Ṳ' : 'ṳ'); });
                text = text.replace(/ír/gi, function(match) { return (match[0] === 'Í' ? 'Ṳ́' : 'ṳ́'); });
                text = text.replace(/ìr/gi, function(match) { return (match[0] === 'Ì' ? 'Ṳ̀' : 'ṳ̀'); });
                text = text.replace(/îr/gi, function(match) { return (match[0] === 'Î' ? 'Ṳ̂' : 'ṳ̂'); });
                text = text.replace(/ǐr/gi, function(match) { return (match[0] === 'Ǐ' ? 'Ṳ̃' : 'ṳ̃'); });
                text = text.replace(/īr/gi, function(match) { return (match[0] === 'Ī' ? 'Ṳ̄' : 'ṳ̄'); });
                text = text.replace(/i̍r/gi, function(match) { return (match[0] === 'I' ? 'Ṳ̍' : 'ṳ̍'); });
                text = text.replace(/i̋r/gi, function(match) { return (match[0] === 'I' ? 'Ṳ̆' : 'ṳ̆'); });
            }

            // or → o (白話字無分 o、or)
            text = text.replace(/or/gi, function(match) { return (match[0] === 'O' ? 'O' : 'o'); });
            text = text.replace(/ór/gi, function(match) { return (match[0] === 'Ó' ? 'Ó' : 'ó'); });
            text = text.replace(/òr/gi, function(match) { return (match[0] === 'Ò' ? 'Ò' : 'ò'); });
            text = text.replace(/ôr/gi, function(match) { return (match[0] === 'Ô' ? 'Ô' : 'ô'); });
            text = text.replace(/ǒr/gi, function(match) { return (match[0] === 'Ǒ' ? 'Õ' : 'õ'); });
            text = text.replace(/ōr/gi, function(match) { return (match[0] === 'Ō' ? 'Ō' : 'ō'); });
            text = text.replace(/o̍r/gi, function(match) { return (match[0] === 'O' ? 'O̍' : 'o̍'); });
            text = text.replace(/őr/gi, function(match) { return (match[0] === 'Ő' ? 'Ŏ' : 'ŏ'); });

            // nn → ⁿ (干焦 nn 後壁是空隙抑 - hyphen 時才取代)
            text = text.replace(/nn(?=[ \-,\.!\?/]|\s*$)/gi, 'ⁿ');

            // nnh → hⁿ (干焦 nnh 佇音節尾的時才會換)
            text = text.replace(/nnh(?=\W|$)/gi, 'hⁿ');

            // ing → eng
            text = text.replace(/ing/gi, function(match) { return (match[0] === 'I' ? 'E' : 'e') + 'ng'; });
            text = text.replace(/íng/gi, function(match) { return (match[0] === 'Í' ? 'É' : 'é') + 'ng'; });
            text = text.replace(/ìng/gi, function(match) { return (match[0] === 'Ì' ? 'È' : 'è') + 'ng'; });
            text = text.replace(/îng/gi, function(match) { return (match[0] === 'Î' ? 'Ê' : 'ê') + 'ng'; });
            text = text.replace(/ǐng/gi, function(match) { return (match[0] === 'Ǐ' ? 'Ẽ' : 'ẽ') + 'ng'; });
            text = text.replace(/īng/gi, function(match) { return (match[0] === 'Ī' ? 'Ē' : 'ē') + 'ng'; });
            text = text.replace(/i̋ng/gi, function(match) { return (match[0] === 'I' ? 'Ĕ' : 'ĕ') + 'ng'; });

            // ik → ek
            text = text.replace(/ik/gi, function(match) { return (match[0] === 'I' ? 'E' : 'e') + 'k'; });
            text = text.replace(/i̍k/gi, function(match) { return (match[0] === 'I' ? 'E̍' : 'e̍') + 'k'; });

            text = text.replace(/[Aa]̋/g, function(match) { return match[0] === 'A' ? 'Ă' : 'ă'; });
            text = text.replace(/[Ee]̋/g, function(match) { return match[0] === 'E' ? 'Ĕ' : 'ĕ'; });
            text = text.replace(/[Ii]̋/g, function(match) { return match[0] === 'I' ? 'Ĭ' : 'ĭ'; });
            text = text.replace(/[Őő]/g, function(match) { return match[0] === 'Ő' ? 'Ŏ' : 'ŏ'; });
            text = text.replace(/[Űű]/g, function(match) { return match[0] === 'Ű' ? 'Ŭ' : 'ŭ'; });
            text = text.replace(/[Mm]̋/g, function(match) { return match[0] === 'M' ? 'M̆' : 'm̆'; });
            text = text.replace(/[Nn]̋g/g, function(match) { return match[0] === 'N' ? 'N̆g' : 'n̆g'; });

            // Only convert sixth tone if not in Wiktionary mode
            if (!useWiktionaryMode) {
                text = text.replace(/[Ǎǎ]/g, function(match) { return match[0] === 'Ǎ' ? 'Ã' : 'ã'; });
                text = text.replace(/[Ěě]/g, function(match) { return match[0] === 'Ě' ? 'Ẽ' : 'ẽ'; });
                text = text.replace(/[Ǐǐ]/g, function(match) { return match[0] === 'Ǐ' ? 'Ĩ' : 'ĩ'; });
                text = text.replace(/[Ǒǒ]/g, function(match) { return match[0] === 'Ǒ' ? 'Õ' : 'õ'; });
                text = text.replace(/[Ǔǔ]/g, function(match) { return match[0] === 'Ǔ' ? 'Ũ' : 'ũ'; });
                text = text.replace(/[Mm]̌/g, function(match) { return match[0] === 'M' ? 'M̃' : 'm̃'; });
                text = text.replace(/[Ňň]g/g, function(match) { return match[0] === 'Ň' ? 'Ñg' : 'ñg'; });
            }

            // 共排除个例外囥轉原底个位置
            Object.keys(tempMarkers).forEach(marker => {
                const regexReplace = new RegExp(marker.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g');
                text = text.replace(regexReplace, tempMarkers[marker]);
            });

            node.nodeValue = text;
        } else if (node.nodeType === Node.ELEMENT_NODE) {
            // 排除切換揤鈕
            if (node.id !== 'poj-toggle-btn' && node.id !== 'wiktionary-toggle-btn') {
                for (let i = 0; i < node.childNodes.length; i++) {
                    applyPOJTransformation(node.childNodes[i]);
                }
            }
        }
    }

    // 共原始文字囥倒轉去
    function restoreOriginalText(node) {
        if (node.nodeType === Node.TEXT_NODE) {
            if (originalTextMap.has(node)) {
                node.nodeValue = originalTextMap.get(node);
            }
        } else if (node.nodeType === Node.ELEMENT_NODE) {
            // 共切換按鈕跳過
            if (node.id !== 'poj-toggle-btn' && node.id !== 'wiktionary-toggle-btn') {
                for (let i = 0; i < node.childNodes.length; i++) {
                    restoreOriginalText(node.childNodes[i]);
                }
            }
        }
    }

    // 處理動態載入的內容
    function setupMutationObserver() {
        const observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                if (mutation.addedNodes && mutation.addedNodes.length > 0) {
                    for (let i = 0; i < mutation.addedNodes.length; i++) {
                        const node = mutation.addedNodes[i];
                        // 共切換按鈕跳過
                        if (node.id === 'poj-toggle-btn' || node.id === 'wiktionary-toggle-btn') continue;

                        // 若這馬咧用 POJ,就使用轉換
                        if (usePOJ) {
                            applyPOJTransformation(node);
                        }
                    }
                }
            });
        });

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

    // 頁面載入的時陣原初化
    window.addEventListener('load', function() {
        createToggleButton();
        createWiktionaryToggle(); // Create the new Wiktionary mode toggle
        setupMutationObserver();

        // 根據使用者的選擇來決定頭起先顯示的方式
        if (usePOJ) {
            applyPOJTransformation(document.body);
        }
    });
})();