Superscript Pinyin for Chinese Characters with API (Optimized)

Adds superscript pinyin to all Chinese characters on a webpage using an external API with accurate processing and placement. Includes a toggle shortcut. Uses superscript to show the pinyin next to the character. Toggles on and off with ctrl + shift + p. Defaults to off. Perfectly reversible.

目前為 2024-08-21 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Superscript Pinyin for Chinese Characters with API (Optimized)
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Adds superscript pinyin to all Chinese characters on a webpage using an external API with accurate processing and placement. Includes a toggle shortcut. Uses superscript to show the pinyin next to the character. Toggles on and off with ctrl + shift + p. Defaults to off. Perfectly reversible.
// @author       Louis
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Cache for storing pinyin results to avoid duplicate API calls
    const pinyinCache = new Map(Object.entries({'的':'de','一':'yī','是':'shì','不':'bù','了':'le','在':'zài','人':'rén','有':'yǒu','我':'wǒ','他':'tā','这':'zhè','个':'gè','们':'men','中':'zhōng','来':'lái','上':'shàng','大':'dà','为':'wèi','和':'hé','国':'guó','地':'de','到':'dào','以':'yǐ','说':'shuō','时':'shí','要':'yào','就':'jiù','出':'chū','会':'huì','可':'kě','也':'yě','你':'nǐ','对':'duì','生':'shēng','能':'néng','而':'ér','子':'zi','那':'nà','得':'dé','于':'yú','着':'zhe','下':'xià','自':'zì','之':'zhī','年':'nián','过':'guò','发':'fā','后':'hòu','作':'zuò','里':'lǐ','用':'yòng','道':'dào','行':'xíng','所':'suǒ','然':'rán','家':'jiā','种':'zhǒng','事':'shì','成':'chéng','方':'fāng','多':'duō','经':'jīng','么':'me','去':'qù','法':'fǎ','学':'xué','如':'rú','都':'dōu','同':'tóng','现':'xiàn','当':'dāng','没':'méi','动':'dòng','面':'miàn','起':'qǐ','看':'kàn','定':'dìng','天':'tiān','分':'fēn','还':'hái','进':'jìn','好':'hǎo','小':'xiǎo','部':'bù','其':'qí','些':'xiē','主':'zhǔ','样':'yàng','理':'lǐ','心':'xīn','她':'tā','本':'běn','前':'qián','开':'kāi','但':'dàn','因':'yīn','只':'zhǐ','从':'cóng','想':'xiǎng','实':'shí','日':'rì','军':'jūn','者':'zhě','意':'yì','无':'wú','力':'lì','它':'tā','与':'yǔ','长':'zhǎng','把':'bǎ','机':'jī','十':'shí','民':'mín','第':'dì','公':'gōng','此':'cǐ','已':'yǐ','工':'gōng','使':'shǐ','情':'qíng','明':'míng','性':'xìng','知':'zhī','全':'quán','三':'sān','又':'yòu','关':'guān','点':'diǎn','正':'zhèng','业':'yè','外':'wài','将':'jiāng','两':'liǎng','高':'gāo','间':'jiān','由':'yóu','问':'wèn','很':'hěn','最':'zuì','重':'zhòng','并':'bìng','物':'wù','手':'shǒu','应':'yīng','战':'zhàn','向':'xiàng','头':'tóu','文':'wén','体':'tǐ','政':'zhèng','美':'měi','相':'xiāng','见':'jiàn','被':'bèi','利':'lì','什':'shén','二':'èr','等':'děng','产':'chǎn','或':'huò','新':'xīn','己':'jǐ','制':'zhì','身':'shēn','果':'guǒ','加':'jiā','西':'xī','斯':'sī','月':'yuè','话':'huà','合':'hé','回':'huí','特':'tè','代':'dài','内':'nèi','信':'xìn','表':'biǎo','化':'huà','老':'lǎo','给':'gěi','世':'shì','位':'wèi','次':'cì','度':'dù','门':'mén','任':'rèn','常':'cháng','先':'xiān','海':'hǎi','通':'tōng','教':'jiào','儿':'ér','原':'yuán','东':'dōng','声':'shēng','提':'tí','立':'lì','及':'jí','比':'bǐ','员':'yuán','解':'jiě','水':'shuǐ','名':'míng','真':'zhēn','论':'lùn','处':'chù','走':'zǒu','义':'yì','各':'gè','入':'rù','几':'jǐ','口':'kǒu','认':'rèn','条':'tiáo','平':'píng','系':'xì','气':'qì','题':'tí','活':'huó','尔':'ěr','更':'gèng','别':'bié','打':'dǎ','女':'nǚ','变':'biàn','四':'sì','神':'shén','总':'zǒng','何':'hé','电':'diàn','数':'shù','安':'ān','少':'shǎo','报':'bào','才':'cái','结':'jié','反':'fǎn','受':'shòu','目':'mù','太':'tài','量':'liàng','再':'zài','感':'gǎn','建':'jiàn','务':'wù','做':'zuò','接':'jiē','必':'bì','场':'chǎng','件':'jiàn','计':'jì','管':'guǎn','期':'qī','市':'shì','直':'zhí','德':'dé','资':'zī','命':'mìng','山':'shān','金':'jīn','指':'zhǐ','克':'kè','许':'xǔ','统':'tǒng','区':'qū','保':'bǎo','至':'zhì','队':'duì','形':'xíng','社':'shè','便':'biàn','空':'kōng','决':'jué','治':'zhì','展':'zhǎn','马':'mǎ','科':'kē','司':'sī','五':'wǔ','基':'jī','眼':'yǎn','书':'shū','非':'fēi','则':'zé','听':'tīng','白':'bái','却':'què','界':'jiè','达':'dá','光':'guāng','放':'fàng','强':'qiáng','即':'jí','像':'xiàng','难':'nán','且':'qiě','权':'quán','思':'sī','王':'wáng','象':'xiàng','完':'wán','设':'shè','式':'shì','色':'sè','路':'lù','记':'jì','南':'nán','品':'pǐn','住':'zhù','告':'gào','类':'lèi','求':'qiú','据':'jù','程':'chéng','北':'běi','边':'biān','死':'sǐ','张':'zhāng','该':'gāi','交':'jiāo','规':'guī','万':'wàn','取':'qǔ','拉':'lā','格':'gé','望':'wàng','觉':'jué','术':'shù','领':'lǐng','共':'gòng','确':'què','传':'chuán','师':'shī','观':'guān','清':'qīng','今':'jīn','切':'qiè','院':'yuàn','让':'ràng','识':'shí','候':'hòu','带':'dài','导':'dǎo','争':'zhēng','运':'yùn','笑':'xiào','飞':'fēi','风':'fēng','步':'bù','改':'gǎi','收':'shōu','根':'gēn','干':'gàn','造':'zào','言':'yán','联':'lián','持':'chí','组':'zǔ','每':'měi','济':'jì','车':'chē','亲':'qīn','极':'jí','林':'lín','服':'fú','快':'kuài','办':'bàn','议':'yì','往':'wǎng','元':'yuán','英':'yīng','士':'shì','证':'zhèng','近':'jìn','失':'shī','转':'zhuǎn','夫':'fū','令':'lìng','准':'zhǔn','布':'bù','始':'shǐ','怎':'zěn','呢':'ne','存':'cún','未':'wèi','远':'yuǎn','叫':'jiào','台':'tái','单':'dān','影':'yǐng','具':'jù','罗':'luō','字':'zì','爱':'ài','击':'jī','流':'liú','备':'bèi','兵':'bīng','连':'lián','调':'diào','深':'shēn','商':'shāng','算':'suàn','质':'zhì','团':'tuán','集':'jí','百':'bǎi','需':'xū','价':'jià','花':'huā','党':'dǎng','华':'huá','城':'chéng','石':'shí','级':'jí','整':'zhěng','府':'fǔ','离':'lí','况':'kuàng','亚':'yà','请':'qǐng','技':'jì','际':'jì','约':'yuē','示':'shì','复':'fù','病':'bìng','息':'xī','究':'jiū','线':'xiàn','似':'shì','官':'guān','火':'huǒ','断':'duàn','精':'jīng','满':'mǎn','支':'zhī','视':'shì','消':'xiāo','越':'yuè','器':'qì','容':'róng','照':'zhào','须':'xū','九':'jiǔ','增':'zēng','研':'yán','写':'xiě','称':'chēng','企':'qǐ','八':'bā','功':'gōng','吗':'ma','包':'bāo','片':'piàn','史':'shǐ','委':'wěi','乎':'hū','查':'chá','轻':'qīng','易':'yì','早':'zǎo','曾':'céng','除':'chú','农':'nóng','找':'zhǎo','装':'zhuāng','广':'guǎng','显':'xiǎn','吧':'ba','阿':'ā','李':'lǐ','标':'biāo','谈':'tán','吃':'chī','图':'tú','念':'niàn','六':'liù','引':'yǐn','历':'lì','首':'shǒu','医':'yī','局':'jú','突':'tū','专':'zhuān','费':'fèi','号':'hào','尽':'jǐn','另':'lìng','周':'zhōu','较':'jiào','注':'zhù','语':'yǔ','仅':'jǐn','考':'kǎo','落':'luò','青':'qīng','随':'suí','选':'xuǎn','列':'liè','武':'wǔ','红':'hóng','响':'xiǎng','虽':'suī','推':'tuī','势':'shì','参':'cān','希':'xī','古':'gǔ','众':'zhòng','构':'gòu','房':'fáng','半':'bàn','节':'jié','土':'tǔ','投':'tóu','某':'mǒu','案':'àn','黑':'hēi','维':'wéi','革':'gé','划':'huà','敌':'dí','致':'zhì','陈':'chén','律':'lǜ','足':'zú','态':'tài','护':'hù','七':'qī','兴':'xìng','派':'pài','孩':'hái','验':'yàn','责':'zé','营':'yíng','星':'xīng','够':'gòu','章':'zhāng','音':'yīn','跟':'gēn','志':'zhì','底':'dǐ','站':'zhàn','严':'yán','巴':'bā','例':'lì','防':'fáng','族':'zú','供':'gōng','效':'xiào','续':'xù','施':'shī','留':'liú','讲':'jiǎng','型':'xíng','料':'liào','终':'zhōng','答':'dá','紧':'jǐn','黄':'huáng','绝':'jué','奇':'qí','察':'chá','母':'mǔ','京':'jīng','段':'duàn','依':'yī','批':'pī','群':'qún','项':'xiàng','故':'gù','按':'àn','河':'hé','米':'mǐ','围':'wéi','江':'jiāng','织':'zhī','害':'hài','斗':'dòu','双':'shuāng','境':'jìng','客':'kè','纪':'jì','采':'cǎi','举':'jǔ','杀':'shā','攻':'gōng','父':'fù','苏':'sū','密':'mì','低':'dī','朝':'cháo','友':'yǒu','诉':'sù','止':'zhǐ','细':'xì','愿':'yuàn','千':'qiān','值':'zhí','仍':'réng','男':'nán','钱':'qián','破':'pò','网':'wǎng','热':'rè','助':'zhù','倒':'dào','育':'yù','属':'shǔ','坐':'zuò','帝':'dì','限':'xiàn','船':'chuán','脸':'liǎn','职':'zhí','速':'sù','刻':'kè','乐':'lè','否':'fǒu','刚':'gāng','威':'wēi','毛':'máo','状':'zhuàng','率':'lǜ','甚':'shén','独':'dú','球':'qiú','般':'bān','普':'pǔ','怕':'pà','弹':'dàn','校':'xiào','苦':'kǔ','创':'chuàng','假':'jiǎ','久':'jiǔ','错':'cuò','承':'chéng','印':'yìn','晚':'wǎn','兰':'lán','试':'shì','股':'gǔ','拿':'ná','脑':'nǎo','预':'yù','谁':'shuí','益':'yì','阳':'yáng','若':'ruò','哪':'nǎ','微':'wēi','尼':'ní','继':'jì','送':'sòng','急':'jí','血':'xuè','惊':'jīng','伤':'shāng','素':'sù','药':'yào','适':'shì','波':'bō','夜':'yè','省':'shěng','初':'chū','喜':'xǐ','卫':'wèi','源':'yuán','食':'shí','险':'xiǎn','待':'dài','述':'shù','陆':'lù','习':'xí','置':'zhì','居':'jū','劳':'láo','财':'cái','环':'huán','排':'pái','福':'fú','纳':'nà','欢':'huān','雷':'léi','警':'jǐng','获':'huò','模':'mó','充':'chōng','负':'fù','云':'yún','停':'tíng','木':'mù','游':'yóu','龙':'lóng','树':'shù','疑':'yí','层':'céng','冷':'lěng','洲':'zhōu','冲':'chōng','射':'shè','略':'lüè','范':'fàn','竟':'jìng','句':'jù','室':'shì','异':'yì','激':'jī','汉':'hàn','村':'cūn','哈':'hā','策':'cè','演':'yǎn','简':'jiǎn','卡':'kǎ','罪':'zuì','判':'pàn','担':'dān','州':'zhōu','静':'jìng','退':'tuì','既':'jì','衣':'yī','您':'nín','宗':'zōng','积':'jī','余':'yú','痛':'tòng','检':'jiǎn','差':'chà','富':'fù','灵':'líng','协':'xié','角':'jiǎo','占':'zhàn','配':'pèi','征':'zhēng','修':'xiū','皮':'pí','挥':'huī','胜':'shèng','降':'jiàng','阶':'jiē','审':'shěn','沉':'chén','坚':'jiān','善':'shàn','妈':'mā','刘':'liú','读':'dú','啊':'a','超':'chāo','免':'miǎn','压':'yā','银':'yín','买':'mǎi','皇':'huáng','养':'yǎng','伊':'yī','怀':'huái','执':'zhí','副':'fù','乱':'luàn','抗':'kàng','犯':'fàn','追':'zhuī','帮':'bāng','宣':'xuān','佛':'fú','岁':'suì','航':'háng','优':'yōu','怪':'guài','香':'xiāng','著':'zhe','田':'tián','铁':'tiě','控':'kòng','税':'shuì','左':'zuǒ','右':'yòu','份':'fèn','穿':'chuān','艺':'yì','背':'bèi','阵':'zhèn','草':'cǎo','脚':'jiǎo','概':'gài','恶':'è','块':'kuài','顿':'dùn','敢':'gǎn','守':'shǒu','酒':'jiǔ','岛':'dǎo','托':'tuō','央':'yāng','户':'hù','烈':'liè','洋':'yáng','哥':'gē','索':'suǒ','胡':'hú','款':'kuǎn','靠':'kào','评':'píng','版':'bǎn','宝':'bǎo','座':'zuò','释':'shì','景':'jǐng','顾':'gù','弟':'dì','登':'dēng','货':'huò','互':'hù','付':'fù','伯':'bó','慢':'màn','欧':'ōu','换':'huàn','闻':'wén','危':'wēi','忙':'máng','核':'hé','暗':'àn','姐':'jiě','介':'jiè','坏':'huài','讨':'tǎo','丽':'lì','良':'liáng','序':'xù','升':'shēng','监':'jiān','临':'lín','亮':'liàng','露':'lù','永':'yǒng','呼':'hū','味':'wèi','野':'yě','架':'jià','域':'yù','沙':'shā','掉':'diào','括':'kuò','舰':'jiàn','鱼':'yú','杂':'zá','误':'wù','湾':'wān','吉':'jí','减':'jiǎn','编':'biān','楚':'chǔ','肯':'kěn','测':'cè','败':'bài','屋':'wū','跑':'pǎo','梦':'mèng','散':'sàn','温':'wēn','困':'kùn','剑':'jiàn','渐':'jiàn','封':'fēng','救':'jiù','贵':'guì','枪':'qiāng','缺':'quē','楼':'lóu','县':'xiàn','尚':'shàng','毫':'háo','移':'yí','娘':'niáng','朋':'péng','画':'huà','班':'bān','智':'zhì','亦':'yì','耳':'ěr','恩':'ēn','短':'duǎn','掌':'zhǎng','恐':'kǒng','遗':'yí','固':'gù','席':'xí','松':'sōng','秘':'mì','谢':'xiè','鲁':'lǔ','遇':'yù','康':'kāng','虑':'lǜ','幸':'xìng','均':'jūn','销':'xiāo','钟':'zhōng','诗':'shī','藏':'cáng','赶':'gǎn','剧':'jù','票':'piào','损':'sǔn','忽':'hū','巨':'jù','炮':'pào','旧':'jiù','端':'duān','探':'tàn','湖':'hú','录':'lù','叶':'yè','春':'chūn','乡':'xiāng','附':'fù','吸':'xī','予':'yǔ','礼':'lǐ','港':'gǎng','雨':'yǔ','呀':'ya','板':'bǎn','庭':'tíng','妇':'fù','归':'guī','睛':'jīng','饭':'fàn','额':'é','含':'hán','顺':'shùn','输':'shū','摇':'yáo','招':'zhāo','婚':'hūn','脱':'tuō','补':'bǔ','谓':'wèi','督':'dū','毒':'dú','油':'yóu','疗':'liáo','旅':'lǚ','泽':'zé','材':'cái','灭':'miè','逐':'zhú','莫':'mò','笔':'bǐ','亡':'wáng','鲜':'xiān','词':'cí','圣':'shèng','择':'zé','寻':'xún','厂':'chǎng','睡':'shuì','博':'bó','勒':'lēi','烟':'yān','授':'shòu','诺':'nuò','伦':'lún','岸':'àn','奥':'ào','唐':'táng','卖':'mài','俄':'é','炸':'zhà','载':'zài','洛':'luò','健':'jiàn','堂':'táng','旁':'páng','宫':'gōng','喝':'hē','借':'jiè','君':'jūn','禁':'jìn','阴':'yīn','园':'yuán','谋':'móu','宋':'sòng','避':'bì','抓':'zhuā','荣':'róng','姑':'gū','孙':'sūn','逃':'táo','牙':'yá','束':'shù','跳':'tiào','顶':'dǐng'}));

    // Variable to track whether pinyin superscript is enabled or disabled
    let pinyinEnabled = false;
    let mutationObserver = null;

    // Function to debounce processing to reduce excessive executions
    function debounce(func, wait) {
        let timeout;
        return function(...args) {
            const context = this;
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(context, args), wait);
        };
    }

    // Function to make a request to the pinyin API (offloaded to worker)
    async function fetchPinyin(chars) {
        const results = {};
        const uncachedChars = [];

        chars.forEach(char => {
            if (pinyinCache.has(char)) {
                results[char] = pinyinCache.get(char);
            } else {
                uncachedChars.push(char);
            }
        });

        if (uncachedChars.length === 0) return results;

        try {
            const response = await fetch(`https://12.yvelin.net/pinyin.php?text=${encodeURIComponent(uncachedChars.join(''))}`);
            const data = await response.json();

            // Cache the results and assign pinyin
            data.pinyin.forEach((pinyin, index) => {
                const char = uncachedChars[index];
                pinyinCache.set(char, pinyin);
                results[char] = pinyin;
            });

            return results;
        } catch (e) {
            console.error(`Failed to fetch pinyin for characters: "${uncachedChars.join('')}"`, e);
            return results;
        }
    }

    // Function to apply pinyin superscripts to a given text node
    async function applyPinyin(node) {
        if (node.nodeType === Node.TEXT_NODE && !node.parentNode.hasAttribute('data-pinyin-processed')) {
            const text = node.textContent;
            const chineseChars = [...text].filter(char => /[\u4e00-\u9fff]/.test(char));

            if (chineseChars.length > 0) {
                const pinyinResults = await fetchPinyin(chineseChars);
                const fragment = document.createDocumentFragment();

                for (const char of text) {
                    if (/[\u4e00-\u9fff]/.test(char)) {
                        const charNode = document.createTextNode(char);
                        const supNode = document.createElement('sup');
                        supNode.textContent = pinyinResults[char] || '';
                        supNode.setAttribute('data-custom-pinyin-superscript', 'true');

                        fragment.appendChild(charNode);
                        fragment.appendChild(supNode);
                    } else {
                        fragment.appendChild(document.createTextNode(char));
                    }
                }

                const parent = node.parentNode;
                parent.replaceChild(fragment, node);
                parent.setAttribute('data-pinyin-processed', 'true'); // Mark as processed
            }
        }
    }

    // Function to handle text nodes within an element
    async function processElement(element) {
        const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
        const nodes = [];
        while (walker.nextNode()) {
            nodes.push(walker.currentNode);
        }

        for (const node of nodes) {
            await applyPinyin(node);
        }
    }

    // Function to enable or disable the pinyin display
    async function togglePinyin() {
        if (pinyinEnabled) {
            // Remove all <sup> elements with the data attribute
            const superscripts = document.querySelectorAll('sup[data-custom-pinyin-superscript]');
            superscripts.forEach(sup => {
                const parent = sup.parentNode;
                if (parent) {
                    parent.removeChild(sup);
                    parent.removeAttribute('data-pinyin-processed');
                }
            });
            pinyinEnabled = false;

            if (mutationObserver) mutationObserver.disconnect();
        } else {
            await processElement(document.body);
            pinyinEnabled = true;

            // Observe for changes to dynamically added content
            mutationObserver = new MutationObserver(debounce(async (mutations) => {
                for (const mutation of mutations) {
                    if (mutation.type === 'childList') {
                        for (const node of mutation.addedNodes) {
                            if (node.nodeType === Node.ELEMENT_NODE && !node.hasAttribute('data-pinyin-processed')) {
                                await processElement(node);
                            }
                        }
                    }
                }
            }, 300)); // Adjust the debounce delay as needed
            mutationObserver.observe(document.body, { childList: true, subtree: true });
        }
    }

    // Add CSS for pinyin display (optional, you can style it differently)
    function addCSS() {
        const style = document.createElement('style');
        style.textContent = `
            sup[data-custom-pinyin-superscript] {
                font-size: 0.8em;
                vertical-align: super;
                color: #888;
            }
        `;
        document.head.appendChild(style);
    }

    // Initialize CSS
    addCSS();

    // Listen for keyboard shortcut to toggle pinyin
    document.addEventListener('keydown', (event) => {
        if (event.ctrlKey && event.shiftKey && event.key === 'P') {
            event.preventDefault();
            togglePinyin();
        }
    });

})();