已发表
Everything about Google Translate crashing React (and other web apps)
关于谷歌翻译崩溃 React(和其他网络应用程序)的一切
Google Translate, the built-in extension of Google Chrome, is a machine translator that provides users with an easy way of translating webpages from within their browser tab. This allows webpages to be used by users from all over the world, regardless of their native language.
谷歌翻译是谷歌浏览器的内置扩展,是一种机器翻译器,为用户提供了一种在浏览器选项卡中翻译网页的简单方法。这使得来自世界各地的用户可以使用网页,无论他们的母语是什么。
But this convenience comes at a cost, as it interferes with the workings of many modern sites. This is because Google Translate manipulates the DOM in such a way that it breaks the base apps. This interference often manifest as crashes caused by the DOM element's native removeChild
method, resulting in errors like NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
, but it affects a lot more. Not all issues are as obvious as a crash.
但这种便利是有代价的,因为它会干扰许多现代网站的运作。这是因为Google 翻译操作 DOM 的方式破坏了基本应用程序。这种干扰通常表现为由 DOM 元素的本机引起的崩溃 removeChild
方法,导致类似的错误 NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
,但影响更大。并非所有问题都像崩溃一样明显。
The focus of this article will be on Google Translate's interference of React, but it's important to note that these issues are not unique to React; they affect most machine translators and can disrupt any large and complex web app.
本文的重点将放在谷歌翻译对 React 的干扰上,但值得注意的是,这些问题并不是 React 所独有的;它们会影响大多数机器翻译,并可能破坏任何大型且复杂的网络应用程序。
In this article, we will explore:
在本文中,我们将探讨:
- How Google Translate works
- Google Translate's interference
- The interference of browser extensions in general
一般浏览器扩展的干扰 - The impact on regular JavaScript code
- Possible workarounds and alternatives
可能的解决方法和替代方案
Additionally, at the end of the article you will find an addendum in which I share my views on whether web apps should even claim full and exclusive control of the DOM.
此外,在文章末尾,您会发现一个附录,其中我分享了我对 Web 应用程序是否应该声明对 DOM 的完全且独占控制的看法。
But first, let's take a look at how Google Translate works.
但首先,我们来看看谷歌翻译是如何工作的。
How Google Translate works
To understand what Google Translate does, we need to take a close look at the DOM structure before and after translation.
为了理解谷歌翻译的作用,我们需要仔细观察翻译前后的 DOM 结构。
All HTML that is rendered in the browser is represented by the DOM in JavaScript. This is a tree-like structure where each element is a node. HTML elements are represented by Element
-nodes and text is represented by a TextNode
.
所有在浏览器中呈现的 HTML 都由 JavaScript 中的DOM表示。这是一个树状结构,其中每个元素都是一个节点。 HTML 元素表示为 Element
-节点和文本由a表示 TextNode
。
Let's take a simple piece of HTML:
让我们来看一段简单的 HTML:
<p>There are 4 lights!</p>
In JavaScript, this is represented in the DOM by a structure like this:
在 JavaScript 中,这在 DOM 中通过如下结构表示:
将鼠标悬停或点击 TextNode 可查看其内容。
When Google Translate activates, it looks for TextNode
s to translate. These nodes are then replaced with FontElement
elements with the new, translated, strings inside. This results in the following HTML (assuming we're translating to Dutch):
当谷歌翻译激活时,它会寻找 TextNode
s 进行翻译。然后将这些节点替换为 FontElement
内部包含新的、已翻译的字符串的元素。这会生成以下 HTML(假设我们要翻译为荷兰语):
<p><font>Er zijn 4 lampen!</font></p>
More importantly, the DOM structure becomes this:
更重要的是,DOM结构变成了这样:
已安装的 DOM(现已翻译)
将鼠标悬停或点击 TextNode 可查看其内容。
What this shows is that the original TextNode
is unmounted and replaced with a new FontElement
with the translated text inside.
这说明原来的 TextNode
已卸载并更换为新的 FontElement
里面有翻译后的文字。
This is the gist of how Google Translate impacts the DOM and an important piece of why Google Translate causes problems (i.e. interferes) with JavaScript apps doing DOM manipulation.
这是 Google 翻译如何影响 DOM 的要点,也是 Google 翻译导致执行 DOM 操作的 JavaScript 应用程序出现问题(即干扰)的重要原因。
Simulating Google Translate
Now that we know how Google Translate works, we can simulate it being applied to a part of a page. This will allow us to reproduce the issues caused by Google Translate more easily.
现在我们知道了谷歌翻译的工作原理,我们可以模拟它应用于页面的一部分。这将使我们能够更轻松地重现谷歌翻译引起的问题。
The snippet below will search for an element with the id "translateme" and replace all direct TextNode
children with FontElement
s similar to how Google Translate operates. To make it more obvious which text has the Google Translate simulation applied, any text affected is surrounded with square brackets (“There are 4 lights!” becomes “[There are 4 lights!]”).
下面的代码片段将搜索 id 为“translateme”的元素并替换所有直接 TextNode
儿童与 FontElement
与谷歌翻译的运作方式类似。为了使应用了 Google 翻译模拟的文本更加明显,任何受影响的文本都用方括号括起来(“There are 4 个灯!”变为“[There are 4 个灯!]”)。
useEffect(() => { document.getElementById('translateme').childNodes.forEach((child) => { if (child.nodeType === Node.TEXT_NODE) { const fontElem = document.createElement('font') fontElem.textContent = `[${child.textContent}]`
child.parentElement.insertBefore(fontElem, child) child.parentElement.removeChild(child) } })})
The reproduction examples below all use this method to simulate Google Translate.
下面的复现例子均使用该方法来模拟谷歌翻译。
Manually testing Google Translate
If you want to validate the issues caused by Google Translate yourself, you can do so by manually testing it. This will help you understand the impact of Google Translate on your app.
如果您想自己验证谷歌翻译引起的问题,可以通过手动测试来实现。这将帮助您了解 Google 翻译对您的应用的影响。
The easiest way I found to test Google Translate, is to translate English to some other language. To get Google Chrome to do this, you will need to change your Preferred languages in the settings like so:
我发现测试谷歌翻译的最简单方法是将英语翻译成其他语言。要让 Google Chrome 执行此操作,您需要在设置中更改您的首选语言,如下所示:
Next, go to the webpage you want to test. If the webpage is set up correctly (and it's in English), it should have lang="en"
in its html
tag. This allows Google Translate to reliably detect its language and translate it. If it doesn't suggest it by itself, click the translation icon in the address bar.
接下来,转到您要测试的网页。如果网页设置正确(并且是英文),则应该有 lang="en"
在其 html
标签。这使得谷歌翻译能够可靠地检测其语言并进行翻译。如果它本身没有建议,请单击地址栏中的翻译图标。
The interference issues 干扰问题
Now that we know how Google Translate manipulates the DOM, we can explore the interference issues it causes. The most common issues are:
现在我们知道了 Google 翻译如何操作 DOM,我们可以探讨它引起的干扰问题。最常见的问题是:
Issue: Translated text not updating
问题:翻译文本未更新
When Google Translate unmounts DOM nodes and places its own new ones in their place, the original DOM nodes will continue to exist in-memory. Any changes then made to the original DOM nodes will not show up in the user's browser in any way. The changes will remain in-memory.
当 Google Translate 卸载 DOM 节点并将其自己的新节点放置在其位置时,原始 DOM 节点将继续存在于内存中。对原始 DOM 节点所做的任何更改都不会以任何方式显示在用户的浏览器中。更改将保留在内存中。
This is an issue for systems like React that work with a Virtual DOM. One of the main reasons behind using the Virtual DOM is performance, and a key part of that is, whenever possible, updating the values of DOM nodes instead of replacing them. Replacing DOM nodes is more computationally expensive.
对于像 React 这样使用虚拟 DOM 的系统来说,这是一个问题。使用 Virtual DOM 的主要原因之一是性能,其中一个关键部分是尽可能更新 DOM 节点的值而不是替换它们。替换 DOM 节点的计算成本更高。
The consequence of this is that, in React, any text or number that might change alongside another string is affected. When Google Translate is applied, values shown on your page may never update again.
这样做的结果是,在 React 中,任何可能与另一个字符串一起更改的文本或数字都会受到影响。应用 Google 翻译后,页面上显示的值可能永远不会再次更新。
This is a big problem for any app that shows users important data, which probably means every big React app. Showing the wrong data could be misleading and even dangerous. A dashboard showing the wrong number could lead to users making the wrong decisions, your app showing invalid prices could be a legal issue, while showing a user the wrong dosage of medicine could have much more dire consequences. How big of a risk this is, depends on your app and your business.
对于任何向用户显示重要数据的应用程序来说,这都是一个大问题,这可能意味着每个大型 React 应用程序。显示错误的数据可能会产生误导,甚至是危险的。显示错误数字的仪表板可能会导致用户做出错误的决定,您的应用程序显示无效价格可能是一个法律问题,而向用户显示错误的药物剂量可能会产生更可怕的后果。这种风险有多大,取决于您的应用程序和您的业务。
This issue is hard to discover since it doesn't lead to a crash or any error.
此问题很难发现,因为它不会导致崩溃或任何错误。
Reproduction 生殖
In the below reproduction, we have a simple counter tracking the number of lights (a number in a useState
). The button increments the number of lights by one every time it's pressed. The marked label directly next to it is no more than There are {lights} lights!
- no conditions or anything.
在下面的再现中,我们有一个简单的计数器,用于跟踪灯光的数量(a 中的数字) useState
)。每次按下该按钮时,灯光数量都会增加一。紧邻其旁边的标记标签不超过 There are {lights} lights!
- 没有任何条件或任何东西。
We simulate Google Translate using the method described above. The Google Translate simulation adds square brackets around the text to indicate it's active. The value shown in green underneath the button is the actual value, which is unaffected by Google Translate.
我们使用上面描述的方法来模拟Google Translate。 Google 翻译模拟会在文本周围添加方括号以指示其处于活动状态。按钮下方显示为绿色的值是实际值,不受 Google 翻译的影响。
When you click the button a few times, you will see the state is updating and the component is rerendering, but the translated text is never updated to reflect the new value.
当您单击该按钮几次时,您将看到状态正在更新并且组件正在重新渲染,但翻译的文本永远不会更新以反映新值。
TextNode
for each variable in a string. The real Google Translate would normalize the text nodes, merging them together, but our simulation doesn't do this to keep it simpler. This makes the reproduction slightly different from Google Translate, but the result is the same.
复制品在文本周围显示了三组括号。这是因为 React 做了一个单独的 TextNode
对于字符串中的每个变量。真正的谷歌翻译会标准化文本节点,将它们合并在一起,但我们的模拟并没有这样做,以使其更简单。这使得再现与谷歌翻译略有不同,但结果是相同的。
Issue: Crashes
If you're running an error monitoring tool like Sentry or tried manually testing Google Translate, you've probably seen these before. In React, the following errors are common due to interference from Google Translate:
如果您正在运行像 Sentry 这样的错误监控工具或尝试手动测试 Google Translate,您以前可能已经见过这些。在 React 中,由于 Google Translate 的干扰,常见以下错误:
NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
When one of those errors occurs, React will unmount your tree to the closest error boundary. But if you have no error boundary (which is common on websites), your entire app will crash.
当这些错误之一发生时,React 会将你的树卸载到最近的错误边界。但如果你没有错误边界(这在网站上很常见),你的整个应用程序将会崩溃。
The removeChild
error usually happens because your app was trying to remove a conditionally rendered TextNode
from the DOM that Google Translate unmounted. The insertBefore
error is less common; this usually occurs because something conditionally rendered is trying to appear before a TextNode
that was unmounted by Google Translate.
这 removeChild
错误通常发生是因为您的应用程序试图删除有条件渲染的 TextNode
来自 Google Translate 卸载的 DOM。这 insertBefore
错误不太常见;这通常是因为有条件渲染的东西试图出现在 TextNode
已被谷歌翻译卸载。
I think in many cases these crashes might be less important than the translated text not updating. Text not updating is less predictable than not showing anything at all. It may mislead users, which would be a worse outcome than not showing anything at all.
我认为在很多情况下,这些崩溃可能没有翻译文本不更新那么重要。文本不更新比不显示任何内容更难以预测。它可能会误导用户,这比根本不显示任何内容更糟糕。
Reproduction 生殖
The button below toggles whether the lights are on by simply flipping a boolean in a useState
. When the lights are turned off, the “There are 4 lights!” text will no longer be rendered through the conditional expression {lightsOn && 'There are 4 lights!'}
. React tries to reconsolidate this render by removing the TextNode
from the parent that it added it to. When it does this with Google Translate active, the TextNode
is no longer a child of the parent, which results in a crash.
下面的按钮通过简单地翻转布尔值来切换灯是否打开 useState
。当灯关闭时,“有 4 盏灯!”文本将不再通过条件表达式呈现 {lightsOn && 'There are 4 lights!'}
。 React 尝试通过删除 TextNode
来自将其添加到的父级。当它在 Google 翻译处于活动状态时执行此操作时, TextNode
不再是父级的子级,这会导致崩溃。
To reproduce it, the conditional TextNode
needs to have a sibling of any kind. In React nearly every node that's conditionally rendered will have a sibling, so this is a common situation.
要重现它,有条件 TextNode
需要有任何类型的兄弟姐妹。在 React 中,几乎每个有条件渲染的节点都会有一个兄弟节点,所以这是一种常见的情况。
Another way of reproducing this crash is by rendering a different amount of TextNode
s within a ternary. The reproduction below also toggles the lights, but instead of rendering nothing when the lights are off, it tries to render the text "The lights are off" through a ternary: {lightsOn ? <>There are {lights} lights!</> : <>The lights are off</>}
.
重现此崩溃的另一种方法是渲染不同数量的 TextNode
s 在三元中。下面的再现也切换了灯光,但是当灯光关闭时它不会渲染任何内容,而是尝试通过三元组渲染文本“灯光关闭”: {lightsOn ? <>There are {lights} lights!</> : <>The lights are off</>}
。
The important part of this reproduction is that the sides of the ternary have a different amount of TextNode
s. While it might not be obvious, this is the case here, as React produces three TextNode
s for the <>There are {lights} lights!</>
expression.
这种复制的重要部分是三元的侧面有不同数量的 TextNode
s。虽然可能并不明显,但这里就是这种情况,因为 React 生成了三个 TextNode
s 为 <>There are {lights} lights!</>
表达。
This reproduction is a simplified version of what you might have in your app. In the example code, we could have used a single template string for both sides of the ternary. In the real world, these expressions tend to be more complex, making it hard to turn it into a template string.
此复制品是您的应用程序中可能拥有的内容的简化版本。在示例代码中,我们可以对三元数的两侧使用单个模板字符串。在现实世界中,这些表达式往往更加复杂,因此很难将其转换为模板字符串。
As there are more ways to vary the amount of TextNode
s rendered, I'm sure there are more ways of reproducing this crash. This makes it hard to find a workaround that solves all cases.
由于有更多方法可以改变数量 TextNode
渲染后,我确信有更多方法可以重现此崩溃。这使得很难找到解决所有情况的解决方法。
Workarounds
React's crashes have been reported in this issue on GitHub. Several workarounds have been posted, but unfortunately, none of the workarounds provide a quick fix. Some just make things worse.
React 的崩溃已在 GitHub 上的本期中报告。已经发布了几个解决方法,但不幸的是,没有一个解决方法提供快速修复。有些只会让事情变得更糟。
The below workarounds only focus on the crashes and have absolutely no impact on translated text not updating.
以下解决方法仅关注崩溃问题,对翻译文本不更新完全没有影响。
1. Monkey patching removeChild and insertBefore
1. 猴子修补removeChild和insertBefore
Gaearon, a member of the React Core team, posted a workaround that monkey patches removeChild
and insertBefore
to fail silently when they're called with invalid arguments.
Gaearon是 React Core 团队的成员,发布了一个猴子补丁的解决方法 removeChild
和 insertBefore
当使用无效参数调用它们时,会默默地失败。
While this monkey patch succeeds at preventing the crashes, it doesn't solve the underlying issue at all. Instead of React crashing when it tries to remove a TextNode
through removeChild
, it does nothing and the translated text will remain in the DOM until its parent is removed. And when the insertBefore
error is triggered, the newly rendered text won't appear for your user.
虽然这个猴子补丁成功地防止了崩溃,但它根本没有解决根本问题。而不是 React 在尝试删除时崩溃 TextNode
通过 removeChild
,它不执行任何操作,翻译后的文本将保留在 DOM 中,直到其父级被删除。而当 insertBefore
触发错误,新呈现的文本将不会向您的用户显示。
Unless the user is aware of the behavior, both issues make an app almost as unusable as when it would crash.
除非用户意识到这种行为,否则这两个问题都会使应用程序几乎像崩溃一样无法使用。
Watch this monkey patch in action:
观看这个猴子补丁的实际操作:
You can toggle the Google Translate simulation to see how the component behaves with and without its interference. It also serves as a great way of resetting the component to its initial state.
您可以切换 Google Translate 模拟来查看组件在有干扰和无干扰的情况下的行为方式。它也是将组件重置为其初始状态的好方法。
2. Surrounding TextNodes with spans
2. 用span包围TextNode
GitHub user Shuhei proposed a workaround of surrounding all conditionally rendered and adjacent text in span
elements. This avoids the crashes by ensuring React doesn't try to remove or insert a TextNode
directly.
GitHub 用户Shuhei提出了一种解决方法,将所有有条件渲染的文本和相邻文本包围起来 span
元素。这通过确保 React 不会尝试删除或插入来避免崩溃 TextNode
直接地。
This fixes some of the most common crashes, but not all of them. The crashes caused by conditionally rendered TextNode
s like the {lightsOn && 'There are 4 lights!'}
expression in the first reproduction above, can be fixed by this workaround. But crashes caused by other conditionally rendered TextNode
s, like those in the ternary expression reproduction, are not.
这修复了一些最常见的崩溃,但不是全部。条件渲染导致的崩溃 TextNode
就像 {lightsOn && 'There are 4 lights!'}
上面第一个再现中的表达式可以通过此解决方法进行修复。但是其他条件渲染导致的崩溃 TextNode
s,就像三元表达式复制中的那些一样,不是。
Implementing this workaround does require mangling a lot of existing, regular, code. Without an ESLint rule to enforce this, it is going take a lot of pleading in PRs to get your entire team to consistently apply this workaround. And for many the honest truth is that it's not worth the effort and code quality sacrifice for them.
实现此解决方法确实需要修改大量现有的常规代码。如果没有 ESLint 规则来强制执行此操作,则需要在 PR 中进行大量恳求才能让整个团队一致应用此解决方法。对于许多人来说,诚实的事实是,为他们付出努力和牺牲代码质量是不值得的。
eslint-plugin-sayari has a rule that requires TextNode
s that share a common parent with other elements to be wrapped in a <span>
tag. While this probably catches the problematic expressions, this rule has an extremely high false-positive rate and will require you to wrap nearly all TextNode
s in your app. The ternary crashes are also not solved by this rule.
ESLint 插件eslint-plugin-sayari有一个规则,要求 TextNode
与要包装在 a 中的其他元素共享公共父级的 s <span>
标签。虽然这可能会捕获有问题的表达式,但该规则具有极高的误报率,并且需要您包装几乎所有 TextNode
在您的应用程序中。此规则也无法解决三元崩溃问题。
3. Self re-rendering error boundaries
An error boundary that just renders the same children again when it runs into an error is a good idea by GitHub user Sorahn, but unfortunately, any components in the subtree will lose their state in the process. While this could work for some of the instances, it's not a general solution and if you're going to be adapting your code anyway, you're probably better off surrounding your TextNode
s with spans.
GitHub 用户 Sorahn 提出了一个好主意,即在遇到错误时再次渲染相同的子组件,但不幸的是,子树中的任何组件都会在此过程中丢失其状态。虽然这可能适用于某些情况,但它不是一个通用的解决方案,如果您无论如何都要调整您的代码,那么您可能最好围绕您的 TextNode
s 带有跨度。
Issue: Inconsistent event.target
问题:不一致 event.target
When Google Translate is active, the value of event.target
becomes unpredictable, as users are likely to click on one of Google Translate's font
elements instead of the underlying element that you, as the developer, created and could reasonably expect. In some instances, such as inside overlays, this could lead to click events not working correctly.
当 Google 翻译处于活动状态时,值 event.target
变得不可预测,因为用户可能会点击谷歌翻译的其中一个 font
元素,而不是您作为开发人员创建的并且可以合理预期的底层元素。在某些情况下,例如内部覆盖层,这可能会导致单击事件无法正常工作。
While this issue is very specific and can be worked around with relative ease, very few developers will even be aware of the issue or think to test it.
虽然这个问题非常具体并且可以相对轻松地解决,但很少有开发人员会意识到这个问题或考虑对其进行测试。
Reproduction 生殖
In the reproduction below, the text of the button gets translated by the Google Translate simulator. When you click anywhere within the reproduction, the element type of the event.target
(the element you clicked on) will appear in the text underneath the button. Normally when you click the button, event.target
would refer to the button
, but with Google Translate, it will be a font
element instead.
在下面的复制中,按钮的文本由 Google 翻译模拟器翻译。当您单击复制品中的任意位置时,复制品的元素类型 event.target
(您单击的元素)将出现在按钮下方的文本中。通常,当您单击按钮时, event.target
会提到 button
,但有了谷歌翻译,这将是 font
元素代替。
Click the button. Toggle Google Translate simulation. Click again. Compare results.
单击按钮。切换谷歌翻译模拟。再次单击。比较结果。
Not just React 不仅仅是反应
Google Translate's interference affects not just React apps.
谷歌翻译的干扰不仅仅影响React应用程序。
Any JavaScript code that manipulates the DOM in a similar fasion is affected. This includes operations such as updating a value of a TextNode
, adding or removing children, or using event.target
. These operations are not specific to React.
任何以类似方式操作 DOM 的 JavaScript 代码都会受到影响。这包括诸如更新 a 的值之类的操作 TextNode
,添加或删除子项,或使用 event.target
。这些操作并非特定于 React。
However, these issues are more commonly observed in React applications since React is a prominent user of the “Virtual DOM”. The Virtual DOM keeps references to all DOM nodes so it only has to update parts of the DOM that are actually changed (through a process called reconciliation). This allows for high-performance apps, as it's more efficient than replacing DOM nodes. Because of this, React's use of a Virtual DOM to reuse and update nodes rather than constantly replacing them is a natural evolution for frameworks.
然而,这些问题在 React 应用程序中更常见,因为 React 是“虚拟 DOM ”的重要用户。 Virtual DOM 保留对所有 DOM 节点的引用,因此它只需更新 DOM 中实际更改的部分(通过称为协调的过程)。这允许高性能应用程序,因为它比替换 DOM 节点更有效。因此,React 使用 Virtual DOM 来重用和更新节点而不是不断替换它们是框架的自然演变。
Not just Google Translate
不仅仅是谷歌翻译
Most machine translators work pretty much the same way as Google Translate, so the issue is not just limited to it. But the issue is even bigger than that: any browser extensions that manipulate the DOM can interfere. Some other examples are:
大多数机器翻译器的工作方式与谷歌翻译几乎相同,因此问题不仅仅限于它。但问题比这更严重:任何操纵 DOM 的浏览器扩展都可能会干扰.其他一些例子是:
- Password managers manipulating forms to show prefill dropdowns
密码管理器操作表单以显示预填充下拉列表 - Extensions that inject alternative prices on competing webshops (*)
为竞争性网上商店注入替代价格的扩展(*) - An adblocker removing an element
- AutocardAnywhere: Displays card image popups for collectible card games (*)
AutocardAnywhere :显示收藏式纸牌游戏的纸牌图像弹出窗口(*)
I want to stress that I do not think the team behind Google Translate deserves any blame for the issues. It's a great tool that helps people worldwide and makes the web usable for many more people. Google Translate was originally architected at a time when the web was very different from what it is today. The issues are a result of the web evolving; websites aren't almost exclusively static websites anymore as many of the popular websites today are actually large and complex web apps.
我想强调的是,我认为谷歌翻译背后的团队不应该对这些问题承担任何责任。它是一个很棒的工具,可以帮助全世界的人们并使网络可供更多的人使用。谷歌翻译最初是在网络与今天有很大不同的时候构建的。这些问题是网络不断发展的结果;网站不再几乎完全是静态网站,因为当今许多流行的网站实际上都是大型且复杂的网络应用程序。
Fixing the issues is also not trivial. For many translations, Google Translate needs to be able to restructure sentences to make them work in the target language. That's nearly impossible to do without interfering with the DOM.
解决这些问题也并非易事。对于许多翻译,谷歌翻译需要能够重组句子,使其适用于目标语言。如果不干扰 DOM,这几乎是不可能做到的。
There is no real solution (yet)
目前还没有真正的解决方案
At the time of writing, there is, unfortunately, no solution yet that can make Google Translate work well enough with React for a large React app. As mentioned above, the workarounds for the crashes introduce a new set of issues, and they still leave any complex app barely usable after translation by Google Translate.
不幸的是,在撰写本文时,还没有任何解决方案可以使 Google Translate 与 React 很好地配合用于大型 React 应用程序。如上所述,崩溃的解决方法引入了一系列新问题,并且在谷歌翻译翻译后,它们仍然使任何复杂的应用程序几乎无法使用。
There are a couple of things you can do, but I don't think you're gonna like them.
有几件事你可以做,但我认为你不会喜欢它们。
The regrettable “fix” 令人遗憾的“修复”
When I first ran into this issue back in 2017, I posted in the React issue tracker that I had ”fixed” my app by blocking translation entirely. Now, 7 years later, I am sad to have to report that this still appears to be the only quick way of avoiding all of the issues caused by Google Translate.
当我在 2017 年第一次遇到这个问题时,我在 React 问题跟踪器中发布说,我已经通过完全阻止翻译来“修复”了我的应用程序。现在,七年后,我很遗憾地不得不报告,这似乎仍然是避免谷歌翻译引起的所有问题的唯一快速方法。
I don't like solving it this way. It makes apps less accessible to people worldwide. But for some complex apps, it beats serving Google Translate users a broken app that barely works.
我不喜欢这样解决问题。它使世界各地的人们更难访问应用程序。但对于一些复杂的应用程序来说,它胜过为谷歌翻译用户提供一个几乎无法运行的破损应用程序。
If you're willing to put in the time and effort, wrapping conditional TextNode
s in span
s will solve a large chunk of the crashes (but not the other issues). This will usually be good enough for a simple website like this as a typical website isn't very reactive, has a small codebase, has few developers working on it, and doesn't show any computed numbers that are critical.
如果你愿意投入时间和精力,包装条件 TextNode
进入 span
s 将解决大部分崩溃问题(但不能解决其他问题)。对于像这样的简单网站来说,这通常已经足够了,因为典型的网站反应性不是很强,代码库很小,开发人员很少,并且不显示任何关键的计算数字。
You will have to carefully consider which of these solutions is the right fit for your app. Leaving Google Translate available will be a big help for some of your users, but it will take some debugging to get it to work well enough and ensure you're not showing users incorrect data.
您必须仔细考虑哪些解决方案最适合您的应用程序。保持 Google 翻译可用将对某些用户有很大帮助,但需要进行一些调试才能使其正常工作并确保不会向用户显示错误的数据。
How to detect Google Translate and other machine translation for a way to detect when Google Translate is active.
无论您做什么,告知用户在使用 Google 翻译时可能遇到的问题可能会有所帮助。请参阅如何检测 Google 翻译和其他机器翻译的要点,了解检测 Google 翻译何时处于活动状态的方法。
Alternatives
The only alternative solution that I can think of, is to implement your own localization within your app (i.e. internationalization). This makes machine translation unnecessary and provides international users with the best possible experience. But this has a couple of downsides:
我能想到的唯一替代解决方案是在您的应用程序中实现您自己的本地化(即国际化)。这使得机器翻译变得不必要,并为国际用户提供最佳的体验。但这有两个缺点:
- It takes a lot of effort to do at all
完全需要付出很大的努力才能做到 - It's hard to get right
很难做对 - It slows down development
- Good translators are expensive
- It's infeasible to cover as many languages as Google Translate covers
谷歌翻译不可能覆盖尽可能多的语言
All things considered, this isn't the most practical solution for most apps. Do you know of any other alternatives?
考虑到所有因素,这对于大多数应用程序来说并不是最实用的解决方案。您知道还有其他选择吗?
TextNode
s that are changed. However, I looked into the feature's code and that is part of a >4500 LOC file so it seemed more involved than I bargained for. Maybe someone else can take a look at it.
React 中可能有一个可能的(外部)解决方法,它使用与 React Dev Tools 的“渲染突出显示”类似的机制来触发整个父级的重新挂载(通过 React) TextNode
已更改的内容。然而,我查看了该功能的代码,它是 >4500 LOC 文件的一部分,因此它似乎比我预想的更复杂。也许其他人可以看一下。
Conclusion
That's the gist of Google Translate crashing React apps (and other web apps). Or, as we've discovered, the gist of third-party browser extension DOM manipulation interfering with complex JavaScript app reactivity, often leading to crashes and other issues.
这就是谷歌翻译导致 React 应用程序(和其他网络应用程序)崩溃的要点。或者,正如我们发现的,第三方浏览器扩展 DOM 操作的要点会干扰复杂的 JavaScript 应用程序反应性,通常会导致崩溃和其他问题。
I hope this article will help you understand the issues and help you choose the right way to deal with them in your app.
我希望本文能够帮助您了解这些问题,并帮助您选择正确的方法来在应用程序中处理这些问题。
Please help bring attention to this issue by upvoting its bug report on the Chromium project: https://issues.chromium.org/issues/41407169. The increased attention can help the chances of the issues being addressed.
请通过对 Chromium 项目的错误报告进行投票来帮助引起人们对这个问题的关注: https://issues.chromium.org/issues/41407169 。增加关注可以帮助解决问题的机会。
So what do you think? Can you think of any other workarounds? Or do you know a machine translator that doesn't have these issues? Please share your insights through the links below.
那么你觉得怎么样?您还能想到其他解决方法吗?或者您知道没有这些问题的机器翻译器吗?请通过下面的链接分享您的见解。
ps. In the addendum below, I discuss whether it is reasonable for an app to claim full and exclusive control of the DOM, as React does with its Virtual DOM.
附:在下面的附录中,我讨论了应用程序声明对 DOM 的完全且独占的控制是否合理,就像 React 对其虚拟 DOM 所做的那样。
Addendum: Should an app claim full-control of its DOM?
A developer of the translation project Localize posted their thoughts on the issue in the Chromium report for this issue. In it, they wrote:
翻译项目Localize的开发人员在 Chromium 对此问题的报告中发表了他们对此问题的想法。他们在其中写道:
The problem is introduced when a javascript library assumes that it has full and exclusive control over the DOM (such as a VDOM library) without accounting for the fact that the DOM is inherently mutable by design.
当 javascript 库假设它对 DOM(例如VDOM库)具有完全且独占的控制权而不考虑 DOM 在设计上本质上是可变的这一事实时,就会出现问题。
Even if a website owner wanted to give full and exclusive control of the DOM to a VDOM library, it's not practical if you also have end-users with DOM-modifying Chrome extensions (ie. Grammarly, password managers, etc) or if you have users with browsers that modify the DOM (ie. Chrome's built-in translation functionality). There's a large ecosystem dependent on DOM manipulation - that ecosystem is an unintended victim of the adoption of VDOM frameworks. I have nothing against React or VDOM (I personally use and like them), but entertaining the idea of changing chromium in response to compatibility issues that arise with React is setting a curious precedent.
即使网站所有者想要将 DOM 的完全且独占的控制权交给 VDOM 库,如果您还有使用 DOM 修改 Chrome 扩展程序(即 Grammarly、密码管理器等)的最终用户,或者如果您有使用可修改 DOM 的浏览器的用户(即 Chrome 的内置翻译功能)。有一个大型生态系统依赖于 DOM 操作 - 该生态系统是 VDOM 框架采用的意外受害者。我并不反对 React 或 VDOM(我个人使用并喜欢它们),但是考虑通过更改 chromium 来应对 React 出现的兼容性问题的想法正在开创一个奇怪的先例。
This raises an important question: should an app claim full-control over the DOM?
这就提出了一个重要的问题:应用程序是否应该声明对 DOM 的完全控制?
As I mentioned earlier, third-party extensions interfere with more than just apps that claim full and exclusive control over the DOM. Extensions like Google Translate touch so much of the DOM, that it can break any DOM manipulation, even small pieces of code not built on top of a library. The core of the issue is in third-party DOM manipulation. It's not just limited to frameworks using the Virtual DOM.
正如我之前提到的,第三方扩展不仅仅干扰那些声称对 DOM 具有完全且独占控制权的应用程序。像 Google Translate 这样的扩展涉及 DOM 的太多,以至于它可以破坏任何DOM 操作,甚至是不是构建在库之上的小代码片段。问题的核心在于第三方 DOM 操作。它不仅限于使用虚拟 DOM 的框架。
The nature of browser extensions (and modding in general) involves patching other people's work, and a big part of that is ensuring you don't break things in the process. I don't think it would be reasonable to expect web developers to consider and solve third-party (browser) extension interference. These extensions are beyond the web developer's control and should be designed with caution to minimize potential disruptions. The responsibility lies with the creators of these extensions to ensure compatibility and avoid negatively impacting web applications.
浏览器扩展(以及一般的改装)的本质涉及修补其他人的工作,其中很大一部分是确保您不会在此过程中破坏东西。我认为期望 Web 开发人员考虑并解决第三方(浏览器)扩展干扰是不合理的。这些扩展超出了 Web 开发人员的控制范围,应谨慎设计,以尽量减少潜在的干扰。这些扩展的创建者有责任确保兼容性并避免对 Web 应用程序产生负面影响。
React's use of a Virtual DOM to reuse and update nodes instead of constantly replacing them is a natural evolution for frameworks. After all, there are notable performance benefits to doing it this way. Therefore, I don't think it's unreasonable for React to apply this to allDOM nodes, claiming full and exclusive control over the DOM in the process.
React 使用虚拟 DOM 来重用和更新节点,而不是不断替换它们,这是框架的自然演变。毕竟,这样做可以带来显着的性能优势。因此,我认为 React 将其应用于所有DOM 节点,并声称在此过程中对 DOM 具有完全且独占的控制权,这并不是不合理的。
All things considered, third-party extensions are best equipped to assess their potential impact and address any interference they might cause. They should be the first to resolve any interference with the proper functioning of web apps.
考虑到所有因素,第三方扩展最适合评估其潜在影响并解决它们可能造成的任何干扰。他们应该是第一个解决对网络应用程序正常运行的任何干扰的人。
But it might also be unreasonable to expect extensions to consider all possible interference.
但期望扩展考虑所有可能的干扰也可能是不合理的。
In the end, the only reasonable solution might be to solve this within the platform; inside the browsers that inject those third-party extensions. The issues probably aren't a big enough problem for that to happen any time soon, so for now extension developers will have to take care of it themselves.
最终,唯一合理的解决方案可能是在平台内部解决这个问题;在注入这些第三方扩展的浏览器内部。这些问题可能还不是一个足够大的问题,不会很快发生,所以现在扩展开发人员必须自己解决这个问题。