4

你真的认为Google翻译不影响"前端"页面功能吗?

 2 years ago
source link: https://segmentfault.com/a/1190000041067692
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

你真的认为Google翻译不影响"前端"页面功能吗?

     又是一个时光飞逝的工作日, QA同学突然提出bug, 文本输入框的"计数"功能失效, 经过大家多方查找最后发现竟然是因为测试同学开启了"谷歌翻译"造成的无法"计数", 这就引起了我的浓厚兴趣, 到底这个看起来"人畜无害"的翻译功能是如何影响了我的输入框? 魏蜀吴争霸从此揭开序幕,啊~额(中箭)。

     这篇文章是按我的探究过程编写的, 所以并不是一下就找到正确的方向, 跟着我当时的思路一起探索吧。

image.png

一、dom结构的改变 (以react代码为例)

     想要知道为什么"翻译功能"会影响页面, 要从观察每次"翻译"功能生效时页面结构的变化开始, 最为简单的一段代码:

return ( <div>你好</div> )

翻译前:
image.png

翻译后:
image.png

     查询一下font标签的特性, 竟然已经不建议使用了:
image.png

     尝试一下是否可以获取到这个font标签元素, 将代码改装一下

  function handleShowDom() {
    console.log(document.getElementById("box").children);
  }

 return( 
   <div id="box" onClick={handleShowDom}>
    你好
   </div>
 )

image.png

     可以获取到这个元素就有意思了, 如果某些情况下代码里面存在通过获取子元素进行操作的逻辑, 那么理论上就会出问题啊。

     难道是由于dom结构的改变, 导致的文本输入框的计数功能会消失吗?

二、对比vue与react各类ui库

     通过查看element ui的源码没有发现特殊的代码逻辑, 那究竟是怎么回事? 满心的疑问让我萌生一种想法, 难道跟vue与react框架本身的原理有关? 那么接下来我会分别实验4种ui框架的效果:

1: react -> antd (无bug 👍🏻)

antd官网
image.png

2: react -> arco (有bug)

arco 官网

image.png

3: vue -> element(有bug)

     这个不用演示了, 开局就是靠它。

4: vue -> iview(有bug)

iview 官网

image.png

     上述只有 antd是完美解决了这个bug (必须: respect), 那必须要研究一下它是如何"力压群芳"的呢?

三、深究textarea

     既然是这个输入框元素发生的问题, 那么我们就从它开始吧, 一起看一下element-uiantd分别产生的textarea元素长什么样子:

image.png

image.png

     区别还是比较明显的, 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>

image.png

     很奇妙, 上述三种写法都会产生同样的dom结构, 那么就比较好奇是什么样的写法可以隐藏标签内的文字? 我就尝试了一下js指定value的方式:

  useEffect(() => {
    document.getElementById("wrap").value = "你好世界";
  }, []);

  return (
    <div className="App">
      <textarea id="wrap" rows="5" cols="40">
        内容4
      </textarea>
   </div>
 )

image.png

     果然问题出在这里, dom结构内保存的居然是初始值, 外界显示的是正确的值, 除了这些还有什么区别那? 我们把相关的值都打印出来吧:

image.png

image.png

     真是不研究不知道, 原来同样的组件效果不同的库实现出来差这么多, 感觉属性值差的还挺多的。

     但是通过实验发现, 这些属性并不是这个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>

image.png

     上面可以看出vue的变量已经变化, 只是由于dom结构的改变导致了页面无法被正确更新。

     这是一个很危险的隐患, 因为有的用户可能会选择"总是翻译", 这就导致页面功能完全乱掉了啊!

image.png

五、谷歌翻译是否影响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;

image.png

如果改成则不会受影响:

<div onClick={handleClick}>{n}</div>;

image.png

六、受影响范围 (以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;
}

image.png

所以antd很可能是因为伪元素才没有出bug, 如果antd不是无意之举那就太细节了。

3: 属性(非常特殊)

     之所以说它非常特殊, 是因为属性是否被翻译需要分两种情况讨论, 是否有文本元素, 并且这个文本不能是input内部的文本。

第一种情况: 页面只有一个输入框

    <div>
      <span>
        <input
          type="Please enter content"
          value={value}
          onChange={change}
          placeholder={"Please enter content"}
        />
      </span>
    </div>

显而易见, 这个输入框没有被翻译:

image.png

第二种情况: 随意添加一个字符串

    <div>
      <span>你好</span>
      <span>
        <input
          type="Please enter content"
          value={value}
          onChange={change}
          placeholder={"Please enter content"}
        />
      </span>
    </div>

image.png

     成功进行了翻译并且像placeholder这种可能会展示给用户的属性也被翻译了, 其余功能性的属性并不会被翻译。

<span xxx={"xxxxxxxxx hello"}>你好</span>

image.png

七、如何屏蔽翻译

     为防止因谷歌翻译引起不良体验, 只需要在html标签上添加translate="no"属性即可屏蔽掉翻译功能:

image.png

image.png

八、检测被翻译成了什么

     其实存在一部分用户默认就开启谷歌翻译功能的, 更有甚者你做的是国际化项目, 不同语言的人使用你的网站更有可能使用默认的谷歌翻译, 那么不编直接屏蔽的情况下, 我们要至少要监听一下用户都将我们的网站翻译成了什么语言? 这样也方便我们日后对网站的i18n进行优化。

     比如我们网站自己没有韩语的翻译, 但是经常被用过翻译成韩语, 那么我们可以考虑增加韩语呢?

     再比如我们虽然提供了韩语的翻译, 但是用户仍然主动将网站翻译成韩语, 那就要考虑是不是我们可以将已有的翻译功能, 更明确的提示给用户使用? 毕竟我们自带的翻译更准确体验也更好。

1: MutationObserver 简介

     可以监控dom元素本身的所有改变, MutationObserver实例化后要传入一个配置项, 这个配置项可以自定义需要监听dom的哪些变化, 下面列出主要的几个属性:

  1. attributes 布尔值, 监测dom的属性变化
  2. attributeFilter 数组, 监测dom的具体某个属性的变化, 填写这个参数就不用每次判断哪个属性变化了
  3. childList布尔值, 子元素的变化
  4. 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">禁止使用翻译功能, 上述方法仍能检测到用户想要用谷歌翻译翻译成什么语言。

image.png

我整理了一份谷歌翻译的对照表, 有需要的同学请自取, 里面并不全面但罗列出比较常用的国家:

[
  {
    "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"
  }
]

     看似"人畜无害"的翻译功能, 背后竟然隐藏着各种深浅不一的"坑井", 假设在某些"金额结算页"用户使用了谷歌翻译, 那么是否会造成不小的问题, 所以最好的办法还是提供完善的"语言切换"功能才是王道啊。

     这次就是这样, 希望与你一起进步。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK