Skip to content

Instantly share code, notes, and snippets.

@developit
Last active July 25, 2023 12:45
Show Gist options
  • Save developit/3e807962630ddbd4977cbd07b597f24f to your computer and use it in GitHub Desktop.
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);
}
};
@lf94
Copy link

lf94 commented May 3, 2021

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, and insertBefore, it will most likely be bugged!

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);
};

@oshell
Copy link

oshell commented Nov 18, 2021

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);
  }
};

@sicknarlo
Copy link

sicknarlo commented Aug 9, 2022

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 custom insertBefore prototype doesn't seem to actually be doing anything. Simply early returning and preventing something from happening in insertBefore 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment