Transliteration of Georgian

Adds transliteration to all text nodes containing Georgian letters. Press a button in the bottom right corner of the page, or use a command in the Tampermonkey menu. Cyrillic transliteration is supported in addition to Latin.

当前为 2023-02-18 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Transliteration of Georgian
// @namespace    https://greasyfork.org/users/1029228
// @version      0.6
// @description  Adds transliteration to all text nodes containing Georgian letters. Press a button in the bottom right corner of the page, or use a command in the Tampermonkey menu. Cyrillic transliteration is supported in addition to Latin.
// @author       watxum
// @match        http*://*/*
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAbJJREFUOE+tk7GrQXEUx7+39CwoYcAiC4MMMiiDTcog3YRdyiClTK9elrsYFOIPMJmklJEiJZMkA1kMBrPkiu7rd8RzS1ev985y657z+/y+53t+h5Mk6QvAJ4AP/C7OAAROkiTx1eHxeAybzQar1aqEPTOA9KoiEAggk8kgkUgo6vo/wPF4RL1ex2q1QiQSQblcRjqdlimoVCqUY63d46GAJVg3qVQK3W4X7XabgM8txONxGAwGNBoNOeBwOECn02G/38NoNOJyucBisaBarcoAu90Ofr8f2WwW+XyeIKRAFEV4PB5MJhNotVpK3AuZAtaW3W6HSqXCer2G1+vFaDSC2+2+AZSmEAqFoNfrcTqdoFarqTQcDiMYDCKXy70HRKNRAkynU7hcLlyvV7q5WCwiFovdAL1eD61WC81m8yHm+R0w90ulEt26XC5hMpnQ6XSoJQLUajUMBgNy/h4+nw+FQgE8z9Ov7XaL2WwGs9lMHnAc92PicDgEGxEzSKPR0DSYafP5nL5K8TCRPRrmLHO/3+8jmUxCEIS36yVbpsVigc1mA6fTCYfD8fYwAFqmP63zNzNe4MPEsD5EAAAAAElFTkSuQmCC
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    function isInline(node) {
        if (node.nodeType !== Node.ELEMENT_NODE) {
            return null;
        }

        if (inlineTags.includes(node.tagName)) {
            return true;
        } else if (blockTags.includes(node.tagName)) {
            return false;
        }

        return null;
    }

    function doesBlockHaveMoreText(node) {
        for (let currentNode = node.parentNode; currentNode; currentNode = currentNode.parentNode) {
            if (currentNode.textContent.trim() !== node.textContent.trim()) {
                return true;
            }
            if (!isInline(currentNode)) {
                return false;
            }
        }
        return false;
    }

    function getAllTextNodes() {
        let treeWalker = document.createNodeIterator(document.body, NodeFilter.SHOW_TEXT);
        let nodes = [];
        let node;
        while ((node = treeWalker.nextNode())) {
            nodes.push(node);
        }
        return nodes.filter((node) => !wrapper.contains(node));
    }

    function initSettings() {
        if (!GM_getValue('target')) {
            GM_setValue('target', 'latin');
            GM_setValue('separator', 'auto');
            GM_setValue('showButton', 'always');
            GM_setValue('runOnLoad', false);
        }

        // Remove in April 2023
        if (!GM_getValue('showButton')) {
            GM_setValue('showButton', GM_getValue('showLink'));
        }
    }

    function removeTransliteration() {
        [...document.querySelectorAll('.transliterationOfGeorgian-transliteration')]
            .forEach((el) => {
                el.remove();
            });
    }

    function convert() {
        removeTransliteration();
        getAllTextNodes()
            .filter((node) => node.textContent.match(georgianRegexp))
            .forEach((node) => {
                let newNode = document.createElement('span');
                newNode.className = 'transliterationOfGeorgian-transliteration';

                let prefix = '';
                let postfix = '';
                if (
                    GM_getValue('separator') === 'br'
                    || (
                        GM_getValue('separator') === 'auto'
                        && !doesBlockHaveMoreText(node)
                        // && node.parentNode.tagName !== 'A'
                    )
               ) {
                    prefix = document.createElement('br');
                } else {
                    prefix = ' [';
                    postfix = ']';
                }

                newNode.append(
                    prefix,
                    convertString(node.textContent.trim(), GM_getValue('target')),
                    postfix
                );

                // Add a space if the converted node ends with spaces.
                if (postfix && node.textContent.match(/\s+$/)) {
                    newNode.append(' ');
                }

                node.after(newNode);
            });
    }

    function toggleTransliteration() {
        if (document.querySelector('.transliterationOfGeorgian-transliteration')) {
            removeTransliteration();
        } else {
            convert();
        }
    }

    function saveSettings() {
        [...document.querySelectorAll('.transliterationOfGeorgian-setting')].forEach((input) => {
            if (input.type === 'checkbox' || input.checked) {
                GM_setValue(
                    input.name.split('-').slice(-1)[0],
                    input.type === 'checkbox' ? input.checked : input.value
                );
            }
        });
        if (document.querySelector('.transliterationOfGeorgian-transliteration')) {
            convert();
        }
    }

    let blockTags = [
        'BLOCKQUOTE', 'DD', 'DIV', 'DL', 'DT', 'FIGURE', 'FIGCAPTION', 'FORM', 'H1', 'H2', 'H3',
        'H4', 'H5', 'H6', 'HR', 'INPUT', 'LI', 'OL', 'P', 'PRE', 'TABLE', 'TBODY', 'TR', 'TH', 'TD',
        'UL',
    ];
    let inlineTags = [
        'A', 'ABBR', 'B', 'BIG', 'BR', 'CENTER', 'CITE', 'CODE', 'DEL', 'EM', 'FONT', 'I', 'IMG',
        'INS', 'KBD', 'Q', 'S', 'SAMP', 'SMALL', 'SPAN', 'STRIKE', 'STRONG', 'SUB', 'SUP', 'TIME',
        'TT', 'U', 'VAR',

        'OPTION', // 'OPTION' is a block element, but it doesn't support line breaks
    ];

    let conversions = {
        latin: {
            "ა": "a",
            "ბ": "b",
            "გ": "g",
            "დ": "d",
            "ე": "e",
            "ვ": "v",
            "ზ": "z",
            "თ": "t",
            "ი": "i",
            "კ": "k'",
            "ლ": "l",
            "მ": "m",
            "ნ": "n",
            "ო": "o",
            "პ": "p'",
            "ჟ": "zh",
            "რ": "r",
            "ს": "s",
            "ტ": "t'",
            "უ": "u",
            "ფ": "p",
            "ქ": "k",
            "ღ": "gh",
            "ყ": "q'",
            "შ": "sh",
            "ჩ": "ch",
            "ც": "ts",
            "ძ": "dz",
            "წ": "ts'",
            "ჭ": "ch'",
            "ხ": "kh",
            "ჯ": "j",
            "ჰ": "h",
        },
        cyrillic: {
            "ა": "а",
            "ბ": "б",
            "გ": "г",
            "დ": "д",
            "ე": "э",
            "ვ": "в",
            "ზ": "з",
            "თ": "т",
            "ი": "и",
            "კ": "к'",
            "ლ": "л",
            "მ": "м",
            "ნ": "н",
            "ო": "o",
            "პ": "п'",
            "ჟ": "ж",
            "რ": "р",
            "ს": "с",
            "ტ": "т'",
            "უ": "у",
            "ფ": "п",
            "ქ": "к",
            "ღ": "гх",
            "ყ": "q'",
            "შ": "ш",
            "ჩ": "ч",
            "ც": "ц",
            "ძ": "дз",
            "წ": "ц'",
            "ჭ": "ч'",
            "ხ": "х",
            "ჯ": "дж",
            "ჰ": "h",
        },
    };

    let convertString = (georgian, target) => (
        georgian
            .split('')
            .map((letter) => conversions[target][letter] || letter)
            .join('')
    );
    let georgianRegexp = new RegExp('[' + Object.keys(conversions.latin).join('') + ']');

    // Always add menu items, even if there is no Georgian text in sight - it might be loaded
    // asynchronically.
    GM_registerMenuCommand('Toggle transliteration', () => {
        toggleTransliteration();
    }, 'a');
    GM_registerMenuCommand('Toggle settings', () => {
        toggleSettings();
    }, 'a');

    if (!document.body.innerHTML.match(georgianRegexp)) return;

    initSettings();

    function toggleSettings() {
        settings.style.display = settings.style.display === '' ? 'none' : '';
    }

    let settings = document.createElement('div');
    settings.className = 'transliterationOfGeorgian-settings';
    toggleSettings();

    settings.innerHTML = `
<div class="transliterationOfGeorgian-settings-group">
  Target script<br>
  <input
    type="radio"
    id="transliterationOfGeorgian-setting-target-latin"
    value="latin"
    name="transliterationOfGeorgian-setting-target"
    class="transliterationOfGeorgian-setting"
  > <label for="transliterationOfGeorgian-setting-target-latin">Latin (national system)</label><br>
  <input
    type="radio"
    id="transliterationOfGeorgian-setting-target-cyrillic"
    value="cyrillic"
    name="transliterationOfGeorgian-setting-target"
    class="transliterationOfGeorgian-setting"
  > <label for="transliterationOfGeorgian-setting-target-cyrillic">Cyrillic (but Ჰ = h and ყ = q')</label><br>
  <div class="transliterationOfGeorgian-setting-helpText">Note that ejective consonants have ' (for example, ტ = t') while aspirated consonants have no ' (for example, თ = t). See <a href="https://www.georgian-alphabet.com/en/lesson10.php" target="_blank">the table</a> for more correspondences.</div>
</div>
<div class="transliterationOfGeorgian-settings-group">
  Transliteration separator<br>
  <input
    type="radio"
    id="transliterationOfGeorgian-setting-separator-br"
    value="br"
    name="transliterationOfGeorgian-setting-separator"
    class="transliterationOfGeorgian-setting"
  > <label for="transliterationOfGeorgian-setting-separator-br">Line break</label><br>
  <input
    type="radio"
    id="transliterationOfGeorgian-setting-separator-brackets"
    value="brackets"
    name="transliterationOfGeorgian-setting-separator"
    class="transliterationOfGeorgian-setting"
  > <label for="transliterationOfGeorgian-setting-separator-brackets">Brackets []</label><br>
  <input
    type="radio"
    id="transliterationOfGeorgian-setting-separator-auto"
    value="auto"
    name="transliterationOfGeorgian-setting-separator"
    class="transliterationOfGeorgian-setting"
  > <label for="transliterationOfGeorgian-setting-separator-auto">Line break for blocks of text, brackets for in-line elements</label><br>
</div>
<div class="transliterationOfGeorgian-settings-group">
  Show the "Toggle transliteration" button<br>
  <input
    type="radio"
    id="transliterationOfGeorgian-setting-showButton-always"
    value="always"
    name="transliterationOfGeorgian-setting-showButton"
    class="transliterationOfGeorgian-setting"
  > <label for="transliterationOfGeorgian-setting-showButton-always">On all sites with Georgian letters</label><br>
  <input
    type="radio"
    id="transliterationOfGeorgian-setting-showButton-dotGe"
    value="dotGe"
    name="transliterationOfGeorgian-setting-showButton"
    class="transliterationOfGeorgian-setting"
  > <label for="transliterationOfGeorgian-setting-showButton-dotGe">On .ge sites</label><br>
  <input
    type="radio"
    id="transliterationOfGeorgian-setting-showButton-never"
    value="never"
    name="transliterationOfGeorgian-setting-showButton"
    class="transliterationOfGeorgian-setting"
  > <label for="transliterationOfGeorgian-setting-showButton-never">Nowhere</label><br>
  <div class="transliterationOfGeorgian-setting-helpText">You can always use a command in the Tampermonkey menu.</div>
</div>
<div class="transliterationOfGeorgian-settings-group">
  <input
    type="checkbox"
    id="transliterationOfGeorgian-setting-runOnLoad"
    value="runOnLoad"
    name="transliterationOfGeorgian-setting-runOnLoad"
    class="transliterationOfGeorgian-setting"
  > <label for="transliterationOfGeorgian-setting-runOnLoad">Transliterate on page load</label><br>
  <div class="transliterationOfGeorgian-setting-helpText">If the button is hidden, transliteration will not be performed.</div>
</div>
`;

    let button = document.createElement('a');
    button.textContent = 'Toggle transliteration';
    button.className = 'transliterationOfGeorgian-button';
    button.onclick = toggleTransliteration;

    let settingsButton = document.createElement('a');
    settingsButton.textContent = '⚙️';
    settingsButton.className = 'transliterationOfGeorgian-button';
    settingsButton.onclick = toggleSettings;

    let buttonWrapper;
    if (
        GM_getValue('showButton') === 'always' ||
        (GM_getValue('showButton') === 'dotGe' && location.hostname.endsWith('.ge'))
    ) {
        buttonWrapper = document.createElement('div');
        buttonWrapper.className = 'transliterationOfGeorgian-buttonWrapper';
        buttonWrapper.append(button, settingsButton);
    }

    let wrapper = document.createElement('div');
    wrapper.id = 'transliterationOfGeorgian-wrapper';
    wrapper.append(settings);
    if (buttonWrapper) {
        wrapper.append(buttonWrapper);
    }
    document.body.append(wrapper);

    ['target', 'separator', 'showButton', 'runOnLoad'].forEach((setting) => {
        [...document.querySelectorAll(`[name="transliterationOfGeorgian-setting-${setting}"]`)]
            .forEach((input) => {
                if (
                    (input.type === 'radio' && GM_getValue(setting) === input.value)
                    || (input.type === 'checkbox' && GM_getValue(setting))
                ) {
                    input.checked = true;
                }
            });
    });

    [...document.querySelectorAll('.transliterationOfGeorgian-setting')].forEach((input) => {
        input.onclick = saveSettings;
    });

    if (GM_getValue('runOnLoad') && buttonWrapper) {
        convert();
    }

    GM_addStyle(`
#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper {
    all: revert;
    position: fixed;
    z-index: 999999;
    bottom: 0.5em;
    right: 0.5em;
    font-size: 14px;
    line-height: normal !important;
    font-family: sans-serif !important;
    text-align: left;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper * {
    all: revert;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-settings {
    width: 400px;
    color: #222;
    background-color: #f8f8f8;
    padding: 0.75em 1em;
    border: 1px solid #ccc;
    border-radius: 3px;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-settings-group {
    margin: 0.5em 0;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-settings-group:first-child {
    margin-top: 0;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-settings-group:last-child {
    margin-bottom: 0;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-setting-helpText {
    font-size: 85%;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-buttonWrapper {
    width: max-content;
    margin: 0 0 0 auto;
    border: 1px solid #ccc;
    background-color: #f4f4f4;
    opacity: 0.67;
    border-radius: 3px;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-buttonWrapper:hover {
    opacity: 1;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-settings + .transliterationOfGeorgian-buttonWrapper {
    margin-top: 0.5em;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper a {
    cursor: pointer;
    font-family: sans-serif !important;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-button {
    display: inline-block;
    padding: 0.25em 0.5em;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-button + .transliterationOfGeorgian-button {
    padding-left: 0;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-button {
    color: #666;
}

#transliterationOfGeorgian-wrapper#transliterationOfGeorgian-wrapper .transliterationOfGeorgian-button:hover {
    color: #222;
}
`);
})();