【Vue】Vue-i18n 變量使用以及採坑總結

前言

筆者目前在 Shopee 工作,我們公司主要業務是跨境電商,業務涉及到多個國家,所以我們各個系統都會涉及到國際化翻譯。我們 Vue 項目技術上採用了 Vue-i18n 這個庫。

今天就聊聊這個庫的一個功能,在國際化時候使用變量。在翻譯中使用變量是一個非常常見的場景,最簡單的例子,比如以下的文案要國際化

I am Gopal.I am from China

但其中 GopalChina 是需要變量傳入的,這個時候我們怎麼辦呢?

簡單的傳參

首先我們定義要翻譯的字符如下

"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 tag prop. If omitted, it defaults to 'span'. You can also set it to the boolean value false to 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 變量的使用方法,幾種方法都較爲簡單易懂。主要是記錄了自己踩過的一個坑,也算是學到了一些經驗

最後

之前提到了,筆者在 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