【Vue】Vue-i18n 變量使用以及采坑總結(jié)
前言
筆者目前在 Shopee 工作,我們公司主要業(yè)務(wù)是跨境電商,業(yè)務(wù)涉及到多個(gè)國家,所以我們各個(gè)系統(tǒng)都會(huì)涉及到國際化翻譯。我們 Vue 項(xiàng)目技術(shù)上采用了 Vue-i18n 這個(gè)庫。
今天就聊聊這個(gè)庫的一個(gè)功能,在國際化時(shí)候使用變量。在翻譯中使用變量是一個(gè)非常常見的場景,最簡單的例子,比如以下的文案要國際化
I am Gopal.I am from China
但其中 Gopal 和 China 是需要變量傳入的,這個(gè)時(shí)候我們?cè)趺崔k呢?
簡單的傳參
首先我們定義要翻譯的字符如下
"introTips": "I am {name}.I am from {region}"
然后在模板中使用
$t('introTips', { name: 'Gopal一號(hào)', region: 'China' })
就可以渲染出 I am Gopal 一號(hào).I am from China
需要給變量加個(gè)顏色
假如說我們 Gopal 不僅僅是一個(gè)文案,還需要變成紅色?我們?cè)撛趺崔k?我們可以直接使用 v-html 渲染 html。這個(gè)時(shí)候我們就要修改翻譯的字符如下
introTipsTwo: "I am <span class='name'>{name}</span>.I am from {region}"
使用 v-html 直接渲染
<div
class="text"
v-html="$t('introTipsTwo', { name: 'Gopal一號(hào)', region: 'China' })"
></div>
渲染結(jié)果如下
<div data-v-763db97b="" class="text">
I am <span class="name">Gopal一號(hào)</span>.I am from China
</div>

注意:這個(gè)方法很可能引發(fā) 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 二號(hào)</span>
<span place="region">China</span>
</i18n>
渲染結(jié)果如下:
<div data-v-763db97b="">
I am <span data-v-763db97b="" place="name" class="name">Gopal 二號(hào)</span>.I am from <span data-v-763db97b="" place="region">China</span>
</div>

我們已經(jīng)實(shí)現(xiàn)了我們的方案,但這個(gè)方法會(huì)在下個(gè)版本中被淘汰,仔細(xì)看,這不是 Vue 中的插槽么?沒錯(cuò),官方推薦的最終的解決方案是 Slot,用法跟上面的非常相似
最終的方案 ——Slot
直接上代碼:
<i18n path="introTips" tag="div">
<template v-slot:name>
<span class="name">Gopal 三號(hào)</span>
</template>
<template v-slot:region>
<span>China</span>
</template>
</i18n>
渲染的結(jié)果跟上面的類似
<div data-v-763db97b="">
I am <span data-v-763db97b="" class="name">Gopal 三號(hào)</span>.I am from <span data-v-763db97b="">China</span></div>
問題
在項(xiàng)目中使用的時(shí)候,卻報(bào)錯(cuò)了...
我的辦法跟上面的可謂是一模一樣...
報(bào)錯(cuò)
<i18n path="introTips" tag="div">
<template v-slot:name>
<span class="name">Gopal 三號(hào)</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 '"

我感覺我又要掉頭發(fā)了...。
斷點(diǎn)查看
Google 了一下這個(gè)報(bào)錯(cuò)以及看了一下報(bào)錯(cuò)的堆棧信息,看起來像是 vNode 渲染的問題,然后我在報(bào)錯(cuò)的地方打了一個(gè)斷點(diǎn),可以看到下面 children 中數(shù)組的各項(xiàng)并不都是 vnode,第一項(xiàng)就是字符串,這應(yīng)該就是導(dǎo)致報(bào)錯(cuò)的罪魁禍?zhǔn)?/p>
閱讀源碼
我看了一下 node_modules 中 vue-i18n 的源碼,這里注意我目前使用的版本是 8.15.0
發(fā)現(xiàn)在 i18n 這個(gè)函數(shù)式組件的源碼中有兩句非常奇怪的代碼,這個(gè)函數(shù)式組件源碼見鏈接 [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 是不是永遠(yuǎn)沒有機(jī)會(huì)執(zhí)行了?
我嘗試直接 return 回 children。額成功了...
嘗試升級(jí)版本
我想了想,升級(jí)到較新的版本試下?
果然,這個(gè)組件修改了...,簡化了一下代碼,源碼可見鏈接 [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,這樣就不需要在翻譯的外層再多加一個(gè)節(jié)點(diǎn)了
我再去找了一下修改這個(gè) PR[3] 以及對(duì)應(yīng)的 Issue[4]
解決問題
雖然看起來為了解決不需要配置根節(jié)點(diǎn)的問題的,但我感覺也可以解決我的問題,以下升級(jí)版本后,我修改了一下我的代碼
<div class="test-container">
<i18n path="introTips" :tag="false">
<template v-slot:name>
<span class="name">Gopal 三號(hào)</span>
</template>
<template v-slot:region>
<span>China</span>
</template>
</i18n>
</div>
這回真的成功了,非常開心,最終它的渲染如下
<div data-v-0b89d11d="" class="test-container">
<!-- 這里外層沒有節(jié)點(diǎn)了 -->
I am <span data-v-0b89d11d="" class="name">Gopal 三號(hào)</span>.I am from <span data-v-0b89d11d="">China</span></div>
可以看到這個(gè)時(shí)候渲染出來就沒有最外層的 tag 了
總結(jié)
本文介紹了 vue-i18n 變量的使用方法,幾種方法都較為簡單易懂。主要是記錄了自己踩過的一個(gè)坑,也算是學(xué)到了一些經(jīng)驗(yàn)
當(dāng)沒有思路的時(shí)候,可以看看源碼。可以直接 node_modules 中查看,甚至在 dist 文件中找到相對(duì)應(yīng)的代碼,斷點(diǎn)調(diào)試 源碼調(diào)試不僅僅對(duì)定位問題有所幫助,也可以擴(kuò)寬視野。比如上面提到的 i18n 函數(shù)式組件的實(shí)現(xiàn)。我當(dāng)時(shí)看的時(shí)候,假如讓我來寫,我可以寫出來么? 留意官方的 ISSUE,不過這次前期我都找過,只是都不是我這個(gè)錯(cuò)誤... 官方的文檔英文比較全,中文版和英文版差異較大(應(yīng)該是翻譯滯后了)
最后
之前提到了,筆者在 Shopee 工作,有需要換工作的同學(xué)可以找我內(nèi)推哈~
五月份專場非常多機(jī)會(huì),具體可以查看 鏈接 [5]。一線大廠薪資,少加班(公司提倡高效工作),團(tuán)隊(duì)氛圍好,晉升機(jī)會(huì)多,感興趣的同學(xué)請(qǐng)抓緊時(shí)間,發(fā)送簡歷到我郵箱 [email protected]

參考資料
鏈接: 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#/
