你真的认为Google翻译不影响"前端"页面功能吗?
背景
又是一个时光飞逝的工作日, QA同学突然提出bug, 文本输入框的"计数"功能失效, 经过大家多方查找最后发现竟然是因为测试同学开启了"谷歌翻译"造成的无法"计数", 这就引起了我的浓厚兴趣, 到底这个看起来"人畜无害"的翻译功能是如何影响了我的输入框? 魏蜀吴争霸从此揭开序幕,啊~额(中箭)。
这篇文章是按我的探究过程编写的, 所以并不是一下就找到正确的方向, 跟着我当时的思路一起探索吧。
一、dom结构的改变 (以react代码为例)
想要知道为什么"翻译功能"会影响页面, 要从观察每次"翻译"功能生效时页面结构的变化开始, 最为简单的一段代码:
return ( <div>你好</div> )
翻译前:
翻译后:
查询一下font标签的特性, 竟然已经不建议使用了:
尝试一下是否可以获取到这个font标签元素, 将代码改装一下
function handleShowDom() {
console.log(document.getElementById("box").children);
}
return(
<div id="box" onClick={handleShowDom}>
你好
</div>
)
可以获取到这个元素就有意思了, 如果某些情况下代码里面存在通过获取子元素进行操作的逻辑, 那么理论上就会出问题啊。
难道是由于dom结构的改变, 导致的文本输入框
的计数功能会消失吗?
二、对比vue与react各类ui库
通过查看element ui
的源码没有发现特殊的代码逻辑, 那究竟是怎么回事? 满心的疑问让我萌生一种想法, 难道跟vue与react框架本身的原理有关? 那么接下来我会分别实验4种ui框架的效果:
1: react -> antd (无bug 👍🏻)
2: react -> arco (有bug)
3: vue -> element(有bug)
这个不用演示了, 开局就是靠它。
4: vue -> iview(有bug)
上述只有 antd
是完美解决了这个bug (必须: respect), 那必须要研究一下它是如何"力压群芳"的呢?
三、深究textarea
既然是这个输入框元素发生的问题, 那么我们就从它开始吧, 一起看一下element-ui
与antd
分别产生的textarea
元素长什么样子:
区别还是比较明显的, element-ui
的内容信息不是放在标签内部的, 因为我们在dom结构的视角无法查看到内容, 但是antd
恰恰相反, 这也许可以成为一个突破口。
那我们把"火力"集中在<textarea>
身上, 看看它到底有多少种赋值写法:
return (
<div className="App">
<textarea rows="5" cols="40">
内容1
</textarea>
<br />
<textarea rows="5" cols="40" value="内容2"></textarea>
<br />
<textarea rows="5" cols="40" defaultValue="内容3"></textarea>
</div>
很奇妙, 上述三种写法都会产生同样的dom结构, 那么就比较好奇是什么样的写法可以隐藏标签内的文字? 我就尝试了一下js指定value的方式:
useEffect(() => {
document.getElementById("wrap").value = "你好世界";
}, []);
return (
<div className="App">
<textarea id="wrap" rows="5" cols="40">
内容4
</textarea>
</div>
)
果然问题出在这里, dom结构内保存的居然是初始值, 外界显示的是正确的值, 除了这些还有什么区别那? 我们把相关的值都打印出来吧:
vs🌩
真是不研究不知道, 原来同样的组件效果不同的库实现出来差这么多, 感觉属性值差的还挺多的。
但是通过实验发现, 这些属性并不是这个bug的直接原因, 我们还需要深入源码进行探究, 此时我意识到这个问题可能不是elemnet的责任。
四、谷歌翻译是否影响vue
经过多次尝试连自己都不敢相信, 竟然vue的计算属性等双向绑定的数据会在翻译后失效:
<template>
<div id="app">
<button @click="handleClick">你好: {{n}}</button>
</div>
</template>
<script>
export default {
name: 'App',
methods:{
handleClick(){
this.n += 1;
console.log(this.n)
}
},
data(){
return {
n:1
}
},
}
</script>
上面可以看出vue的变量已经变化, 只是由于dom结构的改变导致了页面无法被正确更新。
这是一个很危险的隐患, 因为有的用户可能会选择"总是翻译", 这就导致页面功能完全乱掉了啊!
五、谷歌翻译是否影响react
既然react的arco框架会出问题, 那么react框架也一定会被影响, 让我们尝试一下什么情况下会出bug, 翻译之后点击:
import { useState } from "react";
import "./App.css";
function App() {
const [n, setN] = useState(0);
function handleClick() {
setN(n + 1);
console.log(n + 1);
}
return <div onClick={handleClick}>你好:{n}</div>;
}
export default App;
如果改成则不会受影响:
<div onClick={handleClick}>{n}</div>;
六、受影响范围 (以react的写法为例)
通过观察antd与element-ui实现的不同我发现, antd是使用after
伪类类做的, 难道此事与伪类有关? 这也证明了某些情况是不受影响的, 让我们实验一下谷歌翻译都影响哪些写法:
1: 拼接文本
不受影响的写法, 甚至这种写法每次n发生变化, 会移除font标签的dom结构。
<div>{n}</div>;
受影响的写法主要是拼接的文本。
<div>你好 {n}</div>;
<div>{n} {n}</div>;
2: 伪元素(不会被翻译)
空空的dom
<div className={"box"}>.</div>
定义伪元素
.box {
height: 10px;
width: 10px;
border: 1px solid red;
}
.box::after {
content: 'hello';
display: inline-block;
}
所以antd很可能是因为伪元素才没有出bug, 如果antd不是无意之举那就太细节了。
3: 属性(非常特殊)
之所以说它非常特殊, 是因为属性是否被翻译需要分两种情况讨论, 是否有文本元素, 并且这个文本不能是input内部的文本。
第一种情况: 页面只有一个输入框
<div>
<span>
<input
type="Please enter content"
value={value}
onChange={change}
placeholder={"Please enter content"}
/>
</span>
</div>
显而易见, 这个输入框没有被翻译:
第二种情况: 随意添加一个字符串
<div>
<span>你好</span>
<span>
<input
type="Please enter content"
value={value}
onChange={change}
placeholder={"Please enter content"}
/>
</span>
</div>
成功进行了翻译并且像placeholder
这种可能会展示给用户的属性也被翻译了, 其余功能性的属性并不会被翻译。
<span xxx={"xxxxxxxxx hello"}>你好</span>
七、如何屏蔽翻译
为防止因谷歌翻译引起不良体验, 只需要在html
标签上添加translate="no"
属性即可屏蔽掉翻译功能:
八、检测被翻译成了什么
其实存在一部分用户默认就开启谷歌翻译功能的, 更有甚者你做的是国际化项目, 不同语言的人使用你的网站更有可能使用默认的谷歌翻译, 那么不编直接屏蔽的情况下, 我们要至少要监听一下用户都将我们的网站翻译成了什么语言? 这样也方便我们日后对网站的i18n进行优化。
比如我们网站自己没有韩语
的翻译, 但是经常被用过翻译成韩语
, 那么我们可以考虑增加韩语
呢?
再比如我们虽然提供了韩语
的翻译, 但是用户仍然主动将网站翻译成韩语
, 那就要考虑是不是我们可以将已有的翻译功能, 更明确的提示给用户使用? 毕竟我们自带的翻译更准确体验也更好。
1: MutationObserver 简介
可以监控dom元素本身的所有改变, MutationObserver
实例化后要传入一个配置项, 这个配置项可以自定义需要监听dom的哪些变化, 下面列出主要的几个属性:
attributes
布尔值, 监测dom的属性变化attributeFilter
数组, 监测dom的具体某个属性的变化, 填写这个参数就不用每次判断哪个属性变化了childList
布尔值, 子元素的变化characterData
布尔值, 监视指定目标节点或子节点树中节点所包含的字符数据的变化
需要注意的是: childList,attributes 或者 characterData 三个属性之中,至少有一个必须为 true,否则会抛出 TypeError 异常。
2: 监控翻译的思路
由于每次谷歌翻译都会修改我们的<html lang="en">
标签的lang
属性, 所以我们就监控这个属性的变化, 并且由于哪怕当前是lang="en"
谷歌翻译成英语还是lang="en"
, 也是会被检测到的 , 所以这个技术方案也是可行的。
新建一个listenerI18n.js
文件, 已插件的形式引入即可:
(function () {
const oHtml = document.getElementsByTagName("html")[0];
const observer = new window.MutationObserver((mutations) => {
console.log(`上报: 翻译为 ${oHtml.getAttribute("lang")}`);
});
observer.observe(oHtml, { attributes: true, attributeFilter: ["lang"] });
})();
比较幸运的是就算你设置了<html lang="en" translate="no">
禁止使用翻译功能, 上述方法仍能检测到用户想要用谷歌翻译翻译成什么语言。
我整理了一份谷歌翻译的对照表, 有需要的同学请自取, 里面并不全面但罗列出比较常用的国家:
[
{
"name": "简体中文",
"lang": "zh-CN"
},
{
"name": "英语",
"lang": "en"
},
{
"name": "阿拉伯语",
"lang": "ar"
},
{
"name": "爱尔兰语",
"lang": "ga"
},
{
"name": "白俄罗斯语",
"lang": "be"
},
{
"name": "保加利亚语",
"lang": "bg"
},
{
"name": "繁体中文",
"lang": "zh-TW"
},
{
"name": "波兰语",
"lang": "pl"
},
{
"name": "波斯语",
"lang": "fa"
},
{
"name": "丹麦语",
"lang": "da"
},
{
"name": "德语",
"lang": "de"
},
{
"name": "俄语",
"lang": "ru"
},
{
"name": "法语",
"lang": "fr"
},
{
"name": "菲律宾语",
"lang": "tl"
},
{
"name": "芬兰语",
"lang": "fi"
},
{
"name": "高棉语",
"lang": "km"
},
{
"name": "格鲁吉亚语",
"lang": "ka"
},
{
"name": "哈萨克语",
"lang": "kk"
},
{
"name": "韩语",
"lang": "ko"
},
{
"name": "荷兰语",
"lang": "nl"
},
{
"name": "老挝语",
"lang": "lo"
},
{
"name": "罗马尼亚语",
"lang": "ro"
},
{
"name": "马来语",
"lang": "ms"
},
{
"name": "蒙古语",
"lang": "mn"
},
{
"name": "孟加拉语",
"lang": "bn"
},
{
"name": "缅甸语",
"lang": "my"
},
{
"name": "尼泊尔语",
"lang": "ne"
},
{
"name": "挪威语",
"lang": "no"
},
{
"name": "葡头牙语",
"lang": "pt"
},
{
"name": "日语",
"lang": "ja"
},
{
"name": "瑞典语",
"lang": "sv"
},
{
"name": "世界语",
"lang": "eo"
},
{
"name": "泰语",
"lang": "th"
},
{
"name": "土耳其语",
"lang": "tr"
},
{
"name": "乌克兰语",
"lang": "uk"
},
{
"name": "西班牙语",
"lang": "es"
},
{
"name": "希腊语",
"lang": "el"
},
{
"name": "匈牙利语",
"lang": "hu"
},
{
"name": "意大利语",
"lang": "it"
},
{
"name": "印度尼西亚语",
"lang": "id"
},
{
"name": "越南语",
"lang": "vi"
},
{
"name": "爪哇语",
"lang": "jw"
}
]
九、总结
看似"人畜无害"的翻译功能, 背后竟然隐藏着各种深浅不一的"坑井", 假设在某些"金额结算页"用户使用了谷歌翻译, 那么是否会造成不小的问题, 所以最好的办法还是提供完善的"语言切换"功能才是王道啊。
end
这次就是这样, 希望与你一起进步。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。