【Vue】Vue-i18n 變量使用以及採坑總結
前言
筆者目前在 Shopee 工作,我們公司主要業務是跨境電商,業務涉及到多個國家,所以我們各個系統都會涉及到國際化翻譯。我們 Vue 項目技術上採用了 Vue-i18n 這個庫。
今天就聊聊這個庫的一個功能,在國際化時候使用變量。在翻譯中使用變量是一個非常常見的場景,最簡單的例子,比如以下的文案要國際化
I am Gopal.I am from China
但其中 Gopal 和 China 是需要變量傳入的,這個時候我們怎麼辦呢?
簡單的傳參
首先我們定義要翻譯的字符如下
"introTips": "I am {name}.I am from {region}"
然後在模板中使用
$t('introTips', { name: 'Gopal一號', region: 'China' })
就可以渲染出 I am Gopal 一號. I am from China
需要給變量加個顏色
假如說我們 Gopal 不僅僅是一個文案,還需要變成紅色?我們該怎麼辦?我們可以直接使用 v-html 渲染 html。這個時候我們就要修改翻譯的字符如下
introTipsTwo: "I am <span class='name'>{name}</span>.I am from {region}"
使用 v-html 直接渲染
<div
class="text"
v-html="$t('introTipsTwo', { name: 'Gopal一號', region: 'China' })"
></div>
渲染結果如下
<div data-v-763db97b="" class="text">
I am <span class="name">Gopal一號</span>.I am from China
</div>
image-20210430112350433
注意:這個方法很可能引發 XSS 攻擊,所以不推薦使用該方法
使用 place 屬性
首先翻譯的文案先改回最開始變量的版本
introTips: "I am {name}.I am from {region}"
直接使用 i18n 組件以及 place 屬性,其中 path 指的是上面的 key,place 指定變量
<i18n path="introTips" tag="div">
<span class="name" place="name">Gopal 二號</span>
<span place="region">China</span>
</i18n>
渲染結果如下:
<div data-v-763db97b="">
I am <span data-v-763db97b="" place="name" class="name">Gopal 二號</span>.I am from <span data-v-763db97b="" place="region">China</span>
</div>
image-20210430113555745
我們已經實現了我們的方案,但這個方法會在下個版本中被淘汰,仔細看,這不是 Vue 中的插槽麼?沒錯,官方推薦的最終的解決方案是 Slot,用法跟上面的非常相似
最終的方案 ——Slot
直接上代碼:
<i18n path="introTips" tag="div">
<template v-slot:name>
<span class="name">Gopal 三號</span>
</template>
<template v-slot:region>
<span>China</span>
</template>
</i18n>
渲染的結果跟上面的類似
<div data-v-763db97b="">
I am <span data-v-763db97b="" class="name">Gopal 三號</span>.I am from <span data-v-763db97b="">China</span></div>
問題
在項目中使用的時候,卻報錯了...
我的辦法跟上面的可謂是一模一樣...
報錯
<i18n path="introTips" tag="div">
<template v-slot:name>
<span class="name">Gopal 三號</span>
</template>
<template v-slot:region>
<span>China</span>
</template>
</i18n>
vue.esm.js:628 [Vue warn]: Error in nextTick: "TypeError: Cannot create property 'isRootInsert' on string 'You submit '"
我感覺我又要掉頭髮了...。
斷點查看
Google 了一下這個報錯以及看了一下報錯的堆棧信息,看起來像是 vNode 渲染的問題,然後我在報錯的地方打了一個斷點,可以看到下面 children 中數組的各項並不都是 vnode,第一項就是字符串,這應該就是導致報錯的罪魁禍首
閱讀源碼
我看了一下 node_modules 中 vue-i18n 的源碼,這裏注意我目前使用的版本是 8.15.0
發現在 i18n 這個函數式組件的源碼中有兩句非常奇怪的代碼,這個函數式組件源碼見鏈接 [1]
export default {
name: 'i18n',
functional: true,
props: {
// 留意這裏
tag: {
type: String
},
// ...
},
render (h: Function, { data, parent, props, slots }: Object) {
const { $i18n } = parent
// ...
const { path, locale, places } = props
const params = slots()
const children = $i18n.i(
path,
locale,
onlyHasDefaultPlace(params) || places
? useLegacyPlaces(params.default, places)
: params
)
const tag = props.tag || 'span'
return tag ? h(tag, data, children) : children
}
}
留意最後兩行代碼
const tag = props.tag || 'span'
return tag ? h(tag, data, children) : children
啊,這。。。children 是不是永遠沒有機會執行了?
我嘗試直接 return 回 children。額成功了...
嘗試升級版本
我想了想,升級到較新的版本試下?
果然,這個組件修改了...,簡化了一下代碼,源碼可見鏈接 [2]
export default {
name: 'i18n',
functional: true,
props: {
// 留意這裏
tag: {
type: [String, Boolean, Object],
default: 'span'
}
// ...
},
render (h: Function, { data, parent, props, slots }: Object) {
// ....
// 留意這裏
const tag = (!!props.tag && props.tag !== true) || props.tag === false ? props.tag : 'span'
return tag ? h(tag, data, children) : children
}
}
這裏的 tag 可以傳 String,Boolean 和 Object
看了一下官方文檔
You can choose the root container's node type by specifying a
tagprop. If omitted, it defaults to'span'. You can also set it to the boolean valuefalseto insert the child nodes directly without creating a root element.
簡單來說 tag 可以傳 false,這樣就不需要在翻譯的外層再多加一個節點了
我再去找了一下修改這個 PR[3] 以及對應的 Issue[4]
解決問題
雖然看起來爲了解決不需要配置根節點的問題的,但我感覺也可以解決我的問題,以下升級版本後,我修改了一下我的代碼
<div class="test-container">
<i18n path="introTips" :tag="false">
<template v-slot:name>
<span class="name">Gopal 三號</span>
</template>
<template v-slot:region>
<span>China</span>
</template>
</i18n>
</div>
這回真的成功了,非常開心,最終它的渲染如下
<div data-v-0b89d11d="" class="test-container">
<!-- 這裏外層沒有節點了 -->
I am <span data-v-0b89d11d="" class="name">Gopal 三號</span>.I am from <span data-v-0b89d11d="">China</span></div>
可以看到這個時候渲染出來就沒有最外層的 tag 了
總結
本文介紹了 vue-i18n 變量的使用方法,幾種方法都較爲簡單易懂。主要是記錄了自己踩過的一個坑,也算是學到了一些經驗
-
當沒有思路的時候,可以看看源碼。可以直接 node_modules 中查看,甚至在 dist 文件中找到相對應的代碼,斷點調試
-
源碼調試不僅僅對定位問題有所幫助,也可以擴寬視野。比如上面提到的 i18n 函數式組件的實現。我當時看的時候,假如讓我來寫,我可以寫出來麼?
-
留意官方的 ISSUE,不過這次前期我都找過,只是都不是我這個錯誤...
-
官方的文檔英文比較全,中文版和英文版差異較大(應該是翻譯滯後了)
最後
之前提到了,筆者在 Shopee 工作,有需要換工作的同學可以找我內推哈~
五月份專場非常多機會,具體可以查看 鏈接 [5]。一線大廠薪資,少加班(公司提倡高效工作),團隊氛圍好,晉升機會多,感興趣的同學請抓緊時間,發送簡歷到我郵箱 15521091584@163.com
參考資料
[1]
鏈接: https://github.com/kazupon/vue-i18n/blob/v8.15.0/src/components/interpolation.js#L42
[2]
鏈接: https://github.com/kazupon/vue-i18n/blob/v8.22.0/src/components/interpolation.js
[3]
PR: https://github.com/kazupon/vue-i18n/pull/878
[4]
Issue: https://github.com/kazupon/vue-i18n/issues/876
[5]
鏈接: https://app.mokahr.com/apply/shopee/2963#/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/F-9OvxCoXCB4fyPSZZ-WMg