專案國際化的難點痛點是什麼

請叫我大蘇發表於2024-01-03

如果有做過專案國際化的應該瞭解, 國際化的工作項大概包括以下幾項:

【詞條相關工作】

  • 文字包裹翻譯函式,如 $t
  • 提取翻譯詞條到 json 檔案裡
  • 翻譯並更新 json 檔案

【三方庫相關工作】

  • 元件庫的國際化配置,如 element-ui 元件庫
  • 其他有詞條場景的三方庫的國際化配置

【圖片、檔案相關工作】

  • 圖片裡出現中文時,需要準備國際化的圖片資源
  • 檔案裡出現中文時,需要準備國際化的檔案資源
    • 通常是表格匯入匯出的 excel 檔案、使用者宣告和軟體協議等 pdf 文件或靜態 html 檔案

【樣式相關工作】

  • 國際化後的文字可能會出現顯示溢位、截斷、亂換行、排版錯亂、未對齊等場景,
    • 需要針對不同語言處理不同樣式,互不影響

如果沒有相關經驗的,經常會以為國際化只有詞條相關工作項,這就是第一個坑點:工作量的評估過於樂觀,遺漏其他工作項

但當你真正去開發一個國際化專案後,你會發現,國際化的難點、痛點、坑點遠不止表面看到的這些,尤其是後期維護,痛點更大

相反,詞條工作可能都是最輕鬆的工作了,因為圈子裡有各種各樣的自動化指令碼工具來輔助你完成

下面我們就來聊一聊國際化裡的各種痛點

如果你經歷過,歡迎一起來吐槽補充

如果你沒經歷過,希望這些痛點可以幫助你在將來如果遇到國際化工作時,可以更有準備的做好評估工作

詞條相關工作的痛點

痛點:詞條出現在各種各樣的地方

這裡以 vue2.x 專案為例,詞條有可能存在於 vue 檔案的 template 裡,script 裡,甚至 style 裡;也可能存在於 js 檔案裡,html 檔案裡。出現在不同地方,需要使用的翻譯函式可能都不一樣,如:

<template>
  <div>
    <div>{{ $t("這裡是中文") }}</div>
    <div :label="$t('這裡也是中文')"></div>
  </div>
</template>
<script>
const ZH_KEY = window.$t("這裡也可能有中文,還用不了this上下文");
export default {
  props: {
    label: {
      type: String,
      default: window.$t("這裡的中文也用不了this上下文"),
    },
  },
  data() {
    return {
      label2: this.$t("這裡中文就用得上this.$t全域性函式"),
    };
  },
  mounted() {
    setTimeout(function () {
      // 非同步回撥函式不使用箭頭函式,導致 this 指向丟失,內部也無法訪問 this
      this.$t(
        "這裡使用 this.$t() 會拋異常,因為沒有使用箭頭函式,this指向不是當前vue元件例項物件"
      );
    });
  },
  methods: {},
};
</script>
<style lang="scss" scoped>
.main {
  &:empty {
    content: "暫無資料";

    // 這裡的中文只能透過樣式優先順序來覆蓋掉,用不了翻譯函式
    [lang="en"] & {
      content: "No Data";
    }
  }
}
</style>

如果你沒仔細看上述程式碼裡的中文詞條出現的場景的話,可能你會下意識的覺得,vue 裡面出現中文的不就 template 模板程式碼和 js 程式碼裡嗎,給 vue 掛個全域性翻譯函式如 $t,包裹下出現的詞條不就好了嗎

正常場景下的確是這樣沒錯,但畢竟前端太靈活了,每個人能力水平和習慣不同,如果團隊有規範要求可能還可控點,如果沒有規範要求,或者又是個歷史久遠經手 N 多人的專案的話,你沒法保證程式碼裡會出現什麼樣的寫法

比如,有人甚至透過 :empty 偽類選擇器來填充文字,那這種場景你要麼改造掉,要麼就只能用 css 優先順序覆蓋來解決國際化問題,因為 css 裡用不了 js 的函式

比如,有人傳給一些非同步操作的回撥函式就不使用箭頭函式,非使用原始的 function() {} 宣告,這就導致回撥函式內部的 this 指向不是當前 vue 元件例項,所以你在裡面使用 this.$t() 的話會導致程式異常。這時候要麼改造成箭頭函式,要麼就是舊時代還沒有箭頭函式時的解法(在函式宣告前先把 this 儲存下來 const self = this,函式內部再透過 self 當前 this 使用)

比如,有人習慣把表單的校驗函式,或者一些靜態變數宣告在 script 標籤內,這裡的 js 是執行在模組作用域內,this 指向也不是當前 vue 元件例項。這時候要麼把程式碼下沉至 vue 內部,要麼就使用 window 全域性函式

比如,即使是在 vue 內部裡的一些地方,也訪問不了 this,比如 props 裡面宣告的元件輸入引數的預設值,如果是中文,這裡也訪問不了 this.$t,比如 beforeRouteEnter 生命週期內也訪問不了 this。這種場景只能使用掛載在 window 全域性上的翻譯函式了

所以你看,只是在 vue 元件內的程式碼,中文詞條就有可能出現在各種各樣的地方,不同地方的上下文還都不一樣,還得分場景處理,得注意是否可以訪問 this 等等問題

更何況,還有 .js.html 檔案的場景

js 檔案場景可能還好說,無非就是使用 window 上的全域性翻譯函式,或者手動 import 進來一個翻譯函式給當前 js 模組程式碼使用

html 檔案裡,純原生的 html 裡你怎麼搞,這裡又不是 vue,沒有模板語法可以讓你在 html 裡呼叫 js 函式,那麼你只能使用 jQuery 時代的那種思想,手動去操作 dom 進行修改了,舉個例子:

<!DOCTYPE html>
<html>
  <head>
    <title>這裡是中文標題</title>
  </head>
</html>

這裡的中文,你只能透過 js 來操作 dom,如 document.title = 'xxx',如果不是 title 標籤,而是其他標籤,得先獲取到對應 dom,再做相對應的處理。雖然這種場景不多,但你沒法保證沒有,誰知道這個老專案以前的前輩會怎麼寫

所以,給詞條包裹個翻譯函式的工作,也不輕鬆,坑也很多。即使是各個大佬在推崇的各種自動化外掛、指令碼工具,這些場景也仍舊需要去關注、小心

痛點:動態拼接的詞條

中文詞條有可能是固定的詞條,也有可能是動態拼接而成的詞條,舉個例子:

<template>
  <div>
    本次批次操作{{ total }}條,其中,成功{{ success }}條,失敗{{ error }}條
  </div>
</template>
<script>
export default {
  data() {
    return {
      total: 10,
      success: 3,
      error: 7,
    };
  },
  mounted() {},
  methods: {
    removeItem(item) {
      this.$confirm("確認是否要刪除【" + item.name + "】嗎?");
    },
  },
};
</script>

動態拼接的場景其實也非常常見,比如在一些表格的敏感操作提示、批次操作結果顯示、或者介面報錯提示原因等等場景,都需要根據使用者的行為來動態的拼接上一些業務資料來呈現

那麼,這種動態拼接場景要怎麼解決?

注意:各種推崇的國際化的自動化外掛或指令碼,侷限之一就是無法解決這類動態拼接的場景,基本都只能人為處理

如果每個中文詞條片段都各自獨立包裹翻譯函式,如 this.$t("確認是否要刪除【") + item.name + this.$t("】嗎?"),這樣翻譯出來很容易會翻譯錯誤,而這種解法又基本都是自動化工具指令碼的解法,因為指令碼無法區分一句話是否結束了

這種場景目前我能想到的就是人為去處理,有經驗了之後,或許編寫程式碼就會下意識的避免寫出這種程式碼

人為的處理就是利用翻譯函式的佔位符替換功能,給翻譯函式動態傳參方式,如:

  • <div>{{ $t("本次批次操作{0}條,其中,成功{1}條,失敗{2}條", [total, success, error]) }}</div>
  • this.$confirm(this.$t("確認是否要刪除【{0}】嗎?", [item.name]));

所以,動態拼接詞條的場景,處理不難,但工作量大,基本沒法靠自動化指令碼完成

痛點:詞條非得標紅加粗關鍵字顯示

關鍵詞高亮這種場景其實跟動態拼接詞條場景類似,一句完整詞條被其他東西被迫拆分成多個片段組成。

常見的場景就是搜尋結果裡對關鍵詞高亮處理,如百度的搜尋結果:

實現方式上,無非就是把需要標紅加粗高亮的關鍵詞用其他標籤包裹起來,單獨設定樣式,如:

  • <div>這句話裡<span style="color: red">我</span>要標紅顯示</div>

注意:跟動態拼接詞條相同,這種關鍵詞高亮場景也是自動化外掛或指令碼的侷限之一,需要靠人為處理

至於解決方案,其實有兩種,一種是直接把帶有 html 標籤程式碼的一整句話當作詞條,丟給翻譯組去翻譯,但需要跟人家解釋說明清楚,畢竟她們不懂程式碼
另一種是參考動態拼接詞條的解法和 v-html 來解決,因為要讓 span 標籤被正確當前 html 程式碼解析而不是字串顯示,如

  • <div v-html="$t('這句話裡{0}我{1}要標紅顯示', [`<span style='color: red'>`, '</span>'])"></div>

所以,關鍵詞加粗高亮的場景,處理起來更麻煩,能懟掉這需求就懟掉吧,實在不行,跟翻譯人員解釋下

幸好這種場景在專案裡應該不多,比動態詞條拼接場景會少很多

痛點:後端介面返回的未翻譯詞條

理論上,前端的詞條前端翻譯,後端的詞條後端翻譯。介面返回的詞條理應由後端去翻譯就好了

遇到這種場景,能懟回去就懟回去吧

真的由於各種原因,後端就是改不了,非得前端來翻譯,那就專門準備一個 json 檔案來維護後端沒翻譯的這類詞條場景吧

然後找到使用介面返回欄位的地方,在呈現前,先用 $t 包裹翻譯處理下,主要是找程式碼的工作量,其他都還好

痛點:中文做 key 值怎麼辦

還是那句話,每個人的能力水平習慣不同,老專案經手 N 多人,什麼牛鬼神蛇的程式碼都有可能存在

用中文做 key 值也就不奇怪了,這裡說幾種場景:

  • if (type === '其他') { // ... }
    • 用中文做判斷
  • const map = { 折線圖: 'line', 餅圖: 'pie' }
    • 用中文做物件的 key 值

用中文做判斷的話,如果確保國際化下 type 的賦值也能正確被翻譯的話,那其實應該還好,因為兩邊都翻譯了,只要翻譯是一樣的,那判斷邏輯還是能夠正常執行,但怕就怕翻譯不一致,或者 type 根本沒翻譯

畢竟你只有去確認過邏輯才能保證有沒有問題,那確認邏輯這個工作量就特別大了。或者也許可以這麼處理:

  • if (this.$t(type) === this.$t('其他'))
    • 兩邊都翻譯了再進行判斷,可能某些場景下會出問題,比如誤翻譯
  • if (type === '其他' || type === this.$t('其他'))
    • 多加個判斷條件,這樣總有一個判斷能滿足,但也怕會誤傷,不過應該還好

至於用中文做物件 key 值的場景,就要去區分這個中文能不能被翻譯了,萬一不能被翻譯但卻給翻譯了,就會引起取數匹配不到,導致業務功能異常,如果可以翻譯,那麼加個 [] 就能呼叫翻譯函式,如:

  • const map = { [this.$t('折線圖')]: 'line', [this.$t('餅圖')]: 'pie' }

所以,中文做 key 值,最大的問題就是要去梳理確認邏輯,到底這個中文能不能被翻譯處理,而且這種場景很難主動發現,因為不好找,通常發現時已經是被測出功能故障來了

不同技術棧專案的痛點

痛點:jQuery 老專案的國際化

vue 專案通常是用 vue-i18n 作為國際化方案基礎,那非 vue 專案呢,比如以前的 jQuery 專案呢?

不同專案都有各自的國際化框架,雖然框架不一樣,但本質上基本都是一樣的,無非就是翻譯函式和詞條檔案

區別可能是翻譯函式名不一樣,詞條檔案不一樣

比如 vue-i18n 是用 json 來維護詞條檔案,而 jquery.i18n 是用 properties 來維護詞條檔案

你可以不同專案直接用不同方案去實現、維護國際化,但這個可能對能力有些要求,有些新人可能轉不過來,因為出現過帶的一些新人平時不關注國際化底層實現原理,只會用,導致換了個不同技術棧的老專案就完全不知道怎麼搞了,教了就忘

針對這種場景,我們實踐出來的解決方案就是開發個抹平不同框架的自動化 node 指令碼,雖然框架不同,但大家都是基於 node

當然,對於一些老專案,還需要擴充套件下原國際化框架的能力,儘可能讓它在使用、維護上跟其他框架保持一致

比如擴充套件下 jquery.i18n 框架能力,讓它也支援用 json 檔案來維護詞條檔案

自動化指令碼我會再寫篇文章介紹,本篇主要是講痛點和解決方案思路

樣式相關工作的痛點

痛點:相互影響,修復完中文樣式、英文出異常

樣式的工作經常是會被遺漏掉的工作項,不同語言的對齊、寬度、間距、換行等是有可能需要不同的處理的,尤其是在表單的 label 寬度上,通常需要單獨設定

而且樣式的處理上,影響點其實很大的,很容易不經意間就相互影響了

而測試又預設不影響,所以可能會導致測試沒覆蓋到而引發生產口碑問題了

比如你改了一個英文樣式問題,但卻影響到了中文時的呈現,但測試關 BUG 時又只驗證了英文的,這就容易出問題了

純 css 程式碼樣式問題修復就還好,加個作用域,再配合語言切換時往 body 上掛個屬性上去,就能限制影響範圍,如:

.input {
  width: "220px";
  work-break: break-all;
  // 加個作用域,限制生效範圍,非 en 語言下就不生效。
  [lang="en"] & {
    width: "300px";
    work-break: break-word;
  }
}

但如果是模板程式碼或者 js 程式碼裡,就需要使用到判斷邏輯來分場景處理了,這裡建議是用物件取值方式替換掉三元運算子,這樣方便後續再擴充套件其他語言,如:

<template>
  <!-- 推薦 -->
  <el-form :label-width="{ en: '150px' }[lang] || '80px'"></el-form>

  <!-- 不推薦 -->
  <!-- <el-form :label-width="lang === 'en' ? '150px' : '80px'"></el-form> -->
</template>

所以,樣式工作主要是影響點,注意宣講到位,測試到位,避免將問題遺漏到生產上

三方庫相關工作的痛點

專案裡通常會使用到一些三方的基礎元件庫,國際化就需要按照對應元件庫的國際化方案來進行相對應配置

這個難度不大,主要問題也是容易被遺漏

痛點:三方庫不支援國際化怎麼辦

但如果專案裡使用到了不支援國際化的三方庫,這時候,沒辦法了

只能是魔改原始碼,改造成直接引入 js 的方式替換掉 package.json 裡的依賴構建模式了

圖片、檔案相關工作

這個場景也是經常容易被遺漏的工作項,有時甚至都不知道原來國際化還要處理圖片、檔案這類場景

經歷多了後,以後在評審高保設計圖時,就儘量讓設計人員不要設計帶中文文案的圖片了,如果非要帶,就連同其他語言的圖片一起出了,省得後期找不到人出圖

至於檔案場景,現在基本都是後端維護,交給後端去處理就行

有些老專案是把檔案放前端資源裡直接下載的,注意下也有這種場景就像

維護相關工作的痛點

除了開發階段有一堆痛點外,其實後續的迭代維護,也是一個大痛點

痛點:經常有遺漏的未翻譯詞條

當你的專案已經完成了國際化了,然後又經歷了一次新的需求迭代開發,有多個人一起參與,新增了很多功能,也在原有功能上做了很多改動。

好,問題來了。

你如何確保你們這麼多人在這次迭代的改動裡,新增或修改的程式碼裡的詞條都進行了國際化處理呢?

相互 review? 自測一輪?

這也是種解決方案,但需要投入資源成本,而且本身這次迭代開發裡除了正常需求開發工作量外,也需要投入國際化處理的工作量
注:國際化事項就是文章開頭列出的事項,每次迭代基本都需要處理

最完美的解決方案應該是自動化指令碼,讓指令碼來解決這種問題,下篇會介紹下團隊大佬開發的自動化指令碼

痛點:如何在 json 裡增量式撈取未翻譯的詞條

跟上一個痛點是一樣的背景,在一次迭代裡新增或修改的程式碼裡多少會引入、修改、刪除中文詞條,那麼如果增量式的更新到 json 檔案中去呢?

靠人工手動去更新,工作量大,而不靠譜穩定

而且,我們詞條翻譯不是透過機翻,而是需求把詞條撈出來提供給翻譯團隊進行翻譯

那我怎麼在上萬條詞條裡面,把那些未翻譯的撈出來呢?

一條條過嗎,太不現實了,還不如在迭代開發寫程式碼過程中就一條條記錄下來

但仍舊是需要耗費大量工作,而且一旦這個步驟忘記,後續再想手工撈取工作量只會更大

而且就算你是機翻,難道每次都把所有詞條,包括已經翻譯好的詞條都丟給機器嗎,嫌錢不夠花嘛

最完美的解決方案還是自動化指令碼,一切重複、耗時的工作都可以讓指令碼來替代

痛點:如何把翻譯好的詞條更新回 json 檔案裡

還是跟上一個痛點是一樣的背景,當從翻譯組拿到了這次迭代裡那些詞條的翻譯後,怎麼更新回 json 檔案裡呢

尤其跟翻譯組的往來檔案有可能是 excel 檔案,並不是 json 檔案

所以,完美的解決方案還是自動化指令碼,指令碼去解析 excel,然後回填到 json 檔案裡,工作效率提升百分百,一鍵式就搞定

痛點:json 越來越龐大,甚至導致編譯時撐爆記憶體

專案只會越來越大,如果把整個專案的翻譯詞條都放到一個 json 檔案裡維護,那這份 json 檔案只會越來越大,萬級別,甚至百萬千萬級別,那到時就非常考研裝置效能,開發維護都是個問題,因為我們已經遇到過一些老專案上構建時直接撐爆了記憶體,導致構建失敗,都沒辦法進行熱更新開發除錯了

所以,json 還是要按模組,拆分成多份維護

而這個工作,仍舊可以交給自動化指令碼來處理

痛點:多人多分支時,合併時的大量衝突

這也是國際化專案容易出現的問題,不同分支如果都進行了國際化,就會導致基本每個檔案每行程式碼都發生變更,如果兩個分支並行了,那合併時就會是個災難

我今年經過過 N 次這種場景,領導根本不關注是不是國際化,只關注說幾個月前某個分支不是已經國際化做完了,現在合併到 xx 分支上就好了,為什麼還需要這麼多天的工作量

但其實這個合併工作量巨大,而且風險很大,因為是人為一個個解決衝突,程式碼還不是就一個人開發,但合併就一個人合併

至於解決方案,懟吧,這種分支管理不合理

要國際化就儘量不要並行

要麼就是分支就只單純國際化,不要做其他需求開發了,這樣藉助指令碼,還能直接在新分支上跑下指令碼,然後同步下樣式或者動態詞條處理這些場景的程式碼變更就行

總之,這個場景沒有想到好的解決方案,只能從管理上,從規範流程上儘量去避免

痛點:翻譯函式的 key 值如果不是中文詞條,維護程式碼成本可能會增大

有些國際化方案裡會單獨為每個詞條定義一個 code,然後程式碼裡是使用這個 code,而非中文詞條,在根據不同翻譯檔案對每個 code 進行翻譯

element-ui 元件庫的國際化就是這種方案,它提供了一份內部所有詞條的 code,然後我們根據需要,傳入不同 code 語言的翻譯檔案就行

這種方案不是說有問題,而是其實不適用到每個專案裡,元件庫這種是比較穩定不怎麼變更的專案,而且沒有業務性質的專案,可以使用這種方案

但在真實的業務專案裡,如果把每個業務頁面裡的中文詞條都換成一個唯一的 code 值,這其實是非常降低閱讀性的

而且你想想,一個專案上百個頁面,上千個程式碼檔案,我不可能對每個程式碼檔案都很熟悉,很多時候的迭代開發或者故障排查,都是基於特定頁面開始在專案裡找程式碼,因為我也不知道在哪裡

那通常都是根據介面上的中文詞條或者路由等資訊找到程式碼檔案後,開始梳理邏輯

中文作為我們的母語,自然是直接看到夾帶著中文的程式碼會更容易閱讀和理解,如果是 code 的話,還得特意去轉換一遍

效率非常低下

至少我們有個老專案就是用 code 這種方式,導致我們閱讀、維護都非常費勁

而且,都是 code 的話,也非常不利於自動化指令碼的工作,因為自動化指令碼需要根據一定的規則來撈取詞條,本來中文就是最好撈取規則了,現在整成 code,還得定義系列規範跟程式碼含義區分開

綜上,我們團隊一致建議翻譯詞條就直接用中文做 key 值,就像文章開頭給出的例項程式碼


最後

網上好多關於國際化的文章不是介紹類似 vue-i18n 框架的使用,就是推崇下一些自動化工具指令碼

但當經歷過國際化工作後,尤其是一些老專案,才發現,國際化工作裡,除了詞條相關工作外,還有其他很多方面的工作項

而且就算是詞條工作,也存在各自各樣的場景要處理,坑很多,痛點也很多

不是一個自動化指令碼就能完全搞定的,指令碼只能幫忙把重複、低效的手工工作替換掉,但指令碼沒法完成的仍舊需要我們自行去完成

所以本篇才想彙總來聊一聊國際化工作中,我所遇到的各種痛點

但是啊,自動化指令碼還是不能少的哈,它至少能提效 50% 以上的效率

曾經它幫我把兩週的工作量直接節省到 1 天內搞定

所以,下篇就想來聊一聊國際化的自動化指令碼

相關文章