Created
April 30, 2021 10:00
-
-
Save jpedroribeiro/76ac1910df9bc7b9325396bc49ff4341 to your computer and use it in GitHub Desktop.
Pseudo-Localization script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Quick copy and paste solution for pseudo-localization | |
Based on from https://github.com/tryggvigy/pseudo-localization, go there for options and customisations | |
1 - Open dev tools | |
2 - Paste this code | |
3 - Text on the page should be pseudo-localized | |
*/ | |
const ACCENTED_MAP = { | |
a: 'ȧ', | |
A: 'Ȧ', | |
b: 'ƀ', | |
B: 'Ɓ', | |
c: 'ƈ', | |
C: 'Ƈ', | |
d: 'ḓ', | |
D: 'Ḓ', | |
e: 'ḗ', | |
E: 'Ḗ', | |
f: 'ƒ', | |
F: 'Ƒ', | |
g: 'ɠ', | |
G: 'Ɠ', | |
h: 'ħ', | |
H: 'Ħ', | |
i: 'ī', | |
I: 'Ī', | |
j: 'ĵ', | |
J: 'Ĵ', | |
k: 'ķ', | |
K: 'Ķ', | |
l: 'ŀ', | |
L: 'Ŀ', | |
m: 'ḿ', | |
M: 'Ḿ', | |
n: 'ƞ', | |
N: 'Ƞ', | |
o: 'ǿ', | |
O: 'Ǿ', | |
p: 'ƥ', | |
P: 'Ƥ', | |
q: 'ɋ', | |
Q: 'Ɋ', | |
r: 'ř', | |
R: 'Ř', | |
s: 'ş', | |
S: 'Ş', | |
t: 'ŧ', | |
T: 'Ŧ', | |
v: 'ṽ', | |
V: 'Ṽ', | |
u: 'ŭ', | |
U: 'Ŭ', | |
w: 'ẇ', | |
W: 'Ẇ', | |
x: 'ẋ', | |
X: 'Ẋ', | |
y: 'ẏ', | |
Y: 'Ẏ', | |
z: 'ẑ', | |
Z: 'Ẑ', | |
}; | |
const BIDI_MAP = { | |
a: 'ɐ', | |
A: '∀', | |
b: 'q', | |
B: 'Ԑ', | |
c: 'ɔ', | |
C: 'Ↄ', | |
d: 'p', | |
D: 'ᗡ', | |
e: 'ǝ', | |
E: 'Ǝ', | |
f: 'ɟ', | |
F: 'Ⅎ', | |
g: 'ƃ', | |
G: '⅁', | |
h: 'ɥ', | |
H: 'H', | |
i: 'ı', | |
I: 'I', | |
j: 'ɾ', | |
J: 'ſ', | |
k: 'ʞ', | |
K: 'Ӽ', | |
l: 'ʅ', | |
L: '⅂', | |
m: 'ɯ', | |
M: 'W', | |
n: 'u', | |
N: 'N', | |
o: 'o', | |
O: 'O', | |
p: 'd', | |
P: 'Ԁ', | |
q: 'b', | |
Q: 'Ò', | |
r: 'ɹ', | |
R: 'ᴚ', | |
s: 's', | |
S: 'S', | |
t: 'ʇ', | |
T: '⊥', | |
u: 'n', | |
U: '∩', | |
v: 'ʌ', | |
V: 'Ʌ', | |
w: 'ʍ', | |
W: 'M', | |
x: 'x', | |
X: 'X', | |
y: 'ʎ', | |
Y: '⅄', | |
z: 'z', | |
Z: 'Z', | |
}; | |
const strategies = { | |
accented: { | |
prefix: '', | |
postfix: '', | |
map: ACCENTED_MAP, | |
elongate: true, | |
}, | |
bidi: { | |
// Surround words with Unicode formatting marks forcing | |
// right-to-left directionality of characters | |
prefix: '\u202e', | |
postfix: '\u202c', | |
map: BIDI_MAP, | |
elongate: false, | |
}, | |
}; | |
const pseudoLocalizeString = (string, { strategy = 'accented' } = {}) => { | |
let opts = strategies[strategy]; | |
let pseudoLocalizedText = ''; | |
for (let character of string) { | |
if (opts.map[character]) { | |
const cl = character.toLowerCase(); | |
// duplicate "a", "e", "o" and "u" to emulate ~30% longer text | |
if ( | |
opts.elongate && | |
(cl === 'a' || cl === 'e' || cl === 'o' || cl === 'u') | |
) { | |
pseudoLocalizedText += opts.map[character] + opts.map[character]; | |
} else pseudoLocalizedText += opts.map[character]; | |
} else pseudoLocalizedText += character; | |
} | |
// If this string is from the DOM, it should already contain the pre- and postfix | |
if ( | |
pseudoLocalizedText.startsWith(opts.prefix) && | |
pseudoLocalizedText.endsWith(opts.postfix) | |
) { | |
return pseudoLocalizedText; | |
} | |
return opts.prefix + pseudoLocalizedText + opts.postfix; | |
}; | |
const pseudoLocalization = (() => { | |
const opts = { | |
blacklistedNodeNames: ['STYLE'], | |
}; | |
// Observer for dom updates. Initialization is defered to make parts | |
// of the API safe to use in non-browser environments like nodejs | |
let observer = null; | |
const observerConfig = { | |
characterData: true, | |
childList: true, | |
subtree: true, | |
}; | |
const textNodesUnder = element => { | |
const walker = document.createTreeWalker( | |
element, | |
NodeFilter.SHOW_TEXT, | |
node => { | |
const isAllWhitespace = !/[^\s]/.test(node.nodeValue); | |
if (isAllWhitespace) return NodeFilter.FILTER_REJECT; | |
const isBlacklistedNode = opts.blacklistedNodeNames.includes( | |
node.parentElement.nodeName | |
); | |
if (isBlacklistedNode) return NodeFilter.FILTER_REJECT; | |
return NodeFilter.FILTER_ACCEPT; | |
} | |
); | |
let currNode; | |
const textNodes = []; | |
while ((currNode = walker.nextNode())) textNodes.push(currNode); | |
return textNodes; | |
}; | |
const isNonEmptyString = str => str && typeof str === 'string'; | |
const pseudoLocalize = element => { | |
const textNodesUnderElement = textNodesUnder(element); | |
for (let textNode of textNodesUnderElement) { | |
const nodeValue = textNode.nodeValue; | |
if (isNonEmptyString(nodeValue)) { | |
textNode.nodeValue = pseudoLocalizeString(nodeValue, opts); | |
} | |
} | |
}; | |
const domMutationCallback = mutationsList => { | |
for (let mutation of mutationsList) { | |
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { | |
// Turn the observer off while performing dom manipulation to prevent | |
// infinite dom mutation callback loops | |
observer.disconnect(); | |
// For every node added, recurse down it's subtree and convert | |
// all children as well | |
mutation.addedNodes.forEach(pseudoLocalize); | |
observer.observe(document.body, observerConfig); | |
} else if (mutation.type === 'characterData') { | |
const nodeValue = mutation.target.nodeValue; | |
const isBlacklistedNode = opts.blacklistedNodeNames.includes( | |
mutation.target.parentElement.nodeName | |
); | |
if (isNonEmptyString(nodeValue) && !isBlacklistedNode) { | |
// Turn the observer off while performing dom manipulation to prevent | |
// infinite dom mutation callback loops | |
observer.disconnect(); | |
// The target will always be a text node so it can be converted | |
// directly | |
mutation.target.nodeValue = pseudoLocalizeString(nodeValue, opts); | |
observer.observe(document.body, observerConfig); | |
} | |
} | |
} | |
}; | |
const start = ({ | |
strategy = 'accented', | |
blacklistedNodeNames = opts.blacklistedNodeNames, | |
} = {}) => { | |
opts.blacklistedNodeNames = blacklistedNodeNames; | |
opts.strategy = strategy; | |
// Pseudo localize the DOM | |
pseudoLocalize(document.body); | |
// Start observing the DOM for changes and run | |
// pseudo localization on any added text nodes | |
observer = new MutationObserver(domMutationCallback); | |
observer.observe(document.body, observerConfig); | |
}; | |
const stop = () => { | |
observer && observer.disconnect(); | |
}; | |
return { | |
start, | |
stop, | |
localize: pseudoLocalizeString, | |
}; | |
})(); | |
pseudoLocalization.start({ | |
strategy: 'accented', | |
blacklistedNodeNames: ['STYLE', 'text', 'PRE'], | |
}); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment