你真的認為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
這次就是這樣, 希望與你一起進步。