-
-
Save developit/3e807962630ddbd4977cbd07b597f24f to your computer and use it in GitHub Desktop.
import { options } from 'preact'; | |
// Fix Preact rendering when Google Translate breaks the DOM | |
const FONT_AS_TEXT = { | |
localName: { | |
value: null | |
}, | |
nodeType: { | |
value: 3 | |
}, | |
data: { | |
get() { | |
return this._data; | |
}, | |
set(v) { | |
v += ''; | |
if (v === this._data) return; | |
const t = document.createTextNode(this._data = v); | |
this.parentNode.replaceChild(t, this); | |
this.__v.__e = t; | |
} | |
} | |
}; | |
function check(child, ref) { | |
if (ref && ref.nodeType === 3 && child) { | |
let intercept = child.localName === 'msreadoutspan'; | |
if (!intercept && child.localName === 'font') { | |
for (let i in child) { | |
if (i.startsWith('_gt_')) { | |
intercept = true; | |
break; | |
} | |
} | |
} | |
if (intercept) { | |
ref._font = child; | |
child._data = ref.data; | |
Object.defineProperties(child, FONT_AS_TEXT); | |
} | |
} | |
} | |
const ib = Element.prototype.insertBefore; | |
Element.prototype.insertBefore = function(child, ref) { | |
check(child, ref); | |
return ib.apply(this, arguments); | |
}; | |
const rc = Element.prototype.replaceChild; | |
Element.prototype.replaceChild = function(child, ref) { | |
check(child, ref); | |
return rc.apply(this, arguments); | |
}; | |
const oldDiffedHook = options.diffed; | |
options.diffed = vnode => { | |
if (vnode.type === null && vnode.__e) { | |
const font = vnode.__e._font; | |
if (font) { | |
vnode.__e = font; | |
font.__v = vnode; | |
font.data = vnode.props; | |
} | |
} | |
if (oldDiffedHook) { | |
oldDiffedHook(vnode); | |
} | |
}; |
both solutions did not work for me. so I used the existing code and modified by inserting translated text into real node and removing nodes from google translate. it's not perfect, since original text is flashing, but the app is usable. just import this fix in your main index.js.
import { options } from 'preact';
const ib = Element.prototype.insertBefore;
Element.prototype.insertBefore = function(child, ref) {
if (ref && ref.localName === 'font') {
child.innerText = ref.innerText;
return;
}
return ib.apply(this, arguments);
};
const rc = Element.prototype.replaceChild;
Element.prototype.replaceChild = function(child, ref) {
if (ref && ref.localName === 'font') {
child.innerText = ref.innerText;
return;
}
return rc.apply(this, arguments);
};
const oldDiffedHook = options.diffed;
options.diffed = vnode => {
if (vnode.type === null && vnode.__e && vnode.__e.previousSibling && vnode.__e.previousSibling.childNodes) {
const font = vnode.__e.previousSibling.childNodes[0];
if (font && font.nodeName === 'FONT') {
vnode.__e.data = font.innerText;
font.remove()
}
}
if (oldDiffedHook) {
oldDiffedHook(vnode);
}
};
both solutions did not work for me. so I used the existing code and modified by inserting translated text into real node and removing nodes from google translate. it's not perfect, since original text is flashing, but the app is usable. just import this fix in your main index.js.
import { options } from 'preact'; const ib = Element.prototype.insertBefore; Element.prototype.insertBefore = function(child, ref) { if (ref && ref.localName === 'font') { child.innerText = ref.innerText; return; } return ib.apply(this, arguments); }; const rc = Element.prototype.replaceChild; Element.prototype.replaceChild = function(child, ref) { if (ref && ref.localName === 'font') { child.innerText = ref.innerText; return; } return rc.apply(this, arguments); }; const oldDiffedHook = options.diffed; options.diffed = vnode => { if (vnode.type === null && vnode.__e && vnode.__e.previousSibling && vnode.__e.previousSibling.childNodes) { const font = vnode.__e.previousSibling.childNodes[0]; if (font && font.nodeName === 'FONT') { vnode.__e.data = font.innerText; font.remove() } } if (oldDiffedHook) { oldDiffedHook(vnode); } };
@oshell This worked for us, but after spending a lot of time debugging this is the approach I found worked for Google translate:
const insertBefore = Element.prototype.insertBefore;
Element.prototype.insertBefore = function <T extends Node>(
newNode: T,
referenceNode: Node | null,
): T {
if (
newNode instanceof Text &&
referenceNode instanceof HTMLElement &&
referenceNode?.localName === 'font'
) {
return newNode;
}
return insertBefore.apply<Element, [T, Node | null], T>(this, [
newNode,
referenceNode,
]);
};
Some of my observations:
child.innerText = ref.innerText;
inside the custominsertBefore
prototype doesn't seem to actually be doing anything. Simply early returning and preventing something from happening ininsertBefore
was enough for the duplication to go away- I couldn't get your
replaceChild
function to trigger at all using default Chrome Google Translate. Was that included in yours for a different translation tool? - The
diffed
top level if statement was never being triggered
Obviously none of that is reassuring so I was curious what you ran into that caused you to add those so I can be sure I'm not missing anything.
Here is a version which works; note: This is not meant to work in all contexts. If you are using anything with a
<font>
tag, andinsertBefore
, it will most likely be bugged!