// ==UserScript==
// @name Transliteration of Georgian
// @namespace https://greasyfork.org/users/1029228
// @version 0.12
// @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('showButtonInFrames', false);
GM_setValue('runOnLoad', false);
}
if (GM_getValue('showButtonInFrames') === undefined) {
GM_setValue('showButtonInFrames', false);
}
}
function removeTransliteration() {
[...document.querySelectorAll('.transliterationOfGeorgian-transliteration')]
.forEach((el) => {
el.remove();
});
}
function convertString(georgian, target) {
return georgian
.split('')
.map((letter) => conversions[target][letter] || letter)
.join('');
}
function convert() {
removeTransliteration();
if (!wrapper || !wrapper.parentNode) {
addToDom();
}
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();
}
}
function toggleSettings() {
if (!wrapper || !wrapper.parentNode) {
addToDom();
}
settings.style.display = settings.style.display === '' ? 'none' : '';
}
function addToDom() {
settings = document.createElement('div');
settings.className = 'transliterationOfGeorgian-settings';
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 (<a href="https://en.wikipedia.org/wiki/Romanization_of_Georgian#Transliteration_table" target="_blank">national system</a>)</label><br>
<input
type="radio"
id="transliterationOfGeorgian-setting-target-fahnrich"
value="fahnrich"
name="transliterationOfGeorgian-setting-target"
class="transliterationOfGeorgian-setting"
> <label for="transliterationOfGeorgian-setting-target-fahnrich">Latin (<a href="https://en.wiktionary.org/wiki/Project:Georgian_transliteration" target="_blank">Fähnrich</a>)</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 ' or dot (for example, ტ = t' or ṭ) while aspirated consonants have no ' or dot (for example, თ = t). See <a href="https://www.georgian-alphabet.com/en/lesson10.php" target="_blank">the table of ejective and aspirated consonants</a>.</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-showButtonInFrames"
value="showButtonInFrames"
name="transliterationOfGeorgian-setting-showButtonInFrames"
class="transliterationOfGeorgian-setting"
> <label for="transliterationOfGeorgian-setting-showButtonInFrames">Show the "Toggle transliteration" button in frames (pages inside pages)</label><br>
</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'))
)
&& (
window.self === window.top
|| (
GM_getValue('showButtonInFrames')
// Never show in small frames like Facebook's share button
&& window.innerWidth * window.innerHeight < 10000
)
)
) {
buttonWrapper = document.createElement('div');
buttonWrapper.className = 'transliterationOfGeorgian-buttonWrapper';
buttonWrapper.append(button, settingsButton);
}
wrapper = document.createElement('div');
wrapper.id = 'transliterationOfGeorgian-wrapper';
wrapper.append(settings);
if (buttonWrapper) {
wrapper.append(buttonWrapper);
}
document.body.append(wrapper);
toggleSettings();
['target', 'separator', 'showButton', 'showButtonInFrames', '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: 9999999;
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;
}
`);
}
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",
},
fahnrich: {
"ა": "a",
"ბ": "b",
"გ": "g",
"დ": "d",
"ე": "e",
"ვ": "v",
"ზ": "z",
"თ": "t",
"ი": "i",
"კ": "ḳ",
"ლ": "l",
"მ": "m",
"ნ": "n",
"ო": "o",
"პ": "ṗ",
"ჟ": "ž",
"რ": "r",
"ს": "s",
"ტ": "ṭ",
"უ": "u",
"ფ": "p",
"ქ": "k",
"ღ": "ɣ",
"ყ": "q̇",
"შ": "š",
"ჩ": "č",
"ც": "с",
"ძ": "ʒ",
"წ": "c̣",
"ჭ": "č̣",
"ხ": "x",
"ჯ": "ǯ",
"ჰ": "h",
},
cyrillic: {
"ა": "а",
"ბ": "б",
"გ": "г",
"დ": "д",
"ე": "э",
"ვ": "в",
"ზ": "з",
"თ": "т",
"ი": "и",
"კ": "к'",
"ლ": "л",
"მ": "м",
"ნ": "н",
"ო": "o",
"პ": "п'",
"ჟ": "ж",
"რ": "р",
"ს": "с",
"ტ": "т'",
"უ": "у",
"ფ": "п",
"ქ": "к",
"ღ": "гх",
"ყ": "q'",
"შ": "ш",
"ჩ": "ч",
"ც": "ц",
"ძ": "дз",
"წ": "ц'",
"ჭ": "ч'",
"ხ": "х",
"ჯ": "дж",
"ჰ": "h",
},
};
let georgianRegexp = new RegExp('[' + Object.keys(conversions.latin).join('') + ']');
let settings;
let wrapper;
// 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');
initSettings();
if (!document.body.innerHTML.match(georgianRegexp)) return;
addToDom();
})();