你真的認為Google翻譯不影響"前端"頁面功能嗎?

lulu_up發表於2021-12-06

你真的認為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

vs?

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

九、總結

     看似"人畜無害"的翻譯功能, 背後竟然隱藏著各種深淺不一的"坑井", 假設在某些"金額結算頁"使用者使用了谷歌翻譯, 那麼是否會造成不小的問題, 所以最好的辦法還是提供完善的"語言切換"功能才是王道啊。

end

     這次就是這樣, 希望與你一起進步。

相關文章