iT邦幫忙

2022 iThome 鐵人賽

DAY 25
2
Modern Web

Nuxt 3 學習筆記系列 第 25

[Day 25] Nuxt 3 邁向國際化 - 使用 Nuxt I18n 實作多國語系

  • 分享至 

  • xImage
  •  

前言

當一個網站需要面向不同國家或不同語言的使用者,我們就需要做國際化 (Internationalization),將網站內容進行翻譯或語言的對應,讓使用者能理解網頁上的操作或內容,I18n 意謂著「Internationalization」這個單字中,I 和 n 之間有 18 個字母,也正是各個框架的實作多國語系套件的一個常用名稱,例如 Vue 生態就有 vue-i18n,而這篇文章將講述如何在 Nuxt 3 中整合 Vue I18n 來協助實作多國語系。

Nuxt 3 使用 Vue I18n

想要在 Vue 3 使用 Vue I18n 獲得比較好的支援度需要使用正處於 Beta 階段的 v9 版本,因此有幾種方式可以在 Nuxt 整合 Vue I18n,分別是依照 Vue I18n 官方指引、使用 Nuxt Community提供的 @nuxtjs/i18n@next 模組及 @intlify/nuxt3 模組。

比較早以前,Nuxt 3 社群模組還沒有支援比較新的 Vue I18n,所以需要自己額外的製作插件來安裝,後來也有一些相容性的問題,所以 Vue I18n v9 的作者 kazupon 發布了 @intlify/nuxt3 模組來方便 Nuxt 3 整合vue-i18n-next (Vue I18n v9),目前仍然可以使用 @intlify/nuxt3 來進行整合,但我最後選擇使用@nuxtjs/i18n@next 模組,除了看好是由Nuxt Community 提供的支援及未來的發展外,也能使用到 @nuxtjs/i18n 提供的一些組合函數等。

Nuxt 3 整合 @nuxtjs/i18n

目前 @nuxtjs/i18n 模組的穩定版本在 v7.3.0,而使用 Vue 3 和 Vue I18n v9 的模組則是下一個迭代版本 v8,所以使用 NPM 安裝的時候需要稍微注意一下。

下圖於 2022/10/10 擷取自 NPM - @nuxyjs/i18n

https://ithelp.ithome.com.tw/upload/images/20221010/20152617sc9Y8e13Ro.png

Step 1. 安裝 @nuxtjs/i18n

使用 next 標籤來安裝 v8 版本

npm install -D @nuxtjs/i18n@next

Step 2. 添加模組

nuxt.config.ts 中的 modules 屬性添加 '@nuxtjs/i18n',參考如下:

export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n']
})

Step 3. 設定 i18n 參數

當添加完模組後,我們可以先設置 @nuxhjs/i18n 模組的選項,來測試是否能正常運作,於 nuxt.config.ts 中,添加 i18n 屬性,用以設置**@nuxhjs/i18n** 模組;i18n.vueI18n 設置的選項也可以參考 Vue I18n v9 官方文件

export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],
  i18n: {
    vueI18n: {
      legacy: false,
      locale: 'zh',
      messages: {
        en: {
          hello: 'Hello!',
          language: 'Language'
        },
        zh: {
          hello: '你好!',
          language: '語言'
        }
      }
    }
  }
})

記得設置 i18n.vueI18n.legacyfalse 來關閉使用較舊的 API 模式。

Step 4. 確認專案下的 pages 目錄

當完成 Step 3. 的模組設置後,就可以啟動伺服器來看看效果;不過若是 Nuxt 3 專案目錄下缺少了 pages 目錄,可能會無法啟用 Vue Router 自動產生路由,而出現下圖的錯誤。
https://ithelp.ithome.com.tw/upload/images/20221010/20152617c94NOwSSE9.png

我們只需要新增 ./pages/ingex.vue 路由頁面,就可以解決錯誤囉!

建立英語和繁體中文的語系切換

我們安裝與配置好 @nuxtjs/i18n 後,可以嘗試建立頁面來看看效果,以下以 enzh 作為英語和繁體中文兩個語系的使用範例。

新增 ./pages/index.vue 內容如下:

<template>
  <div class="flex flex-col items-center bg-white">
    <h1 class="mt-48 text-8xl font-medium text-blue-500">
      {{ $t('hello') }}
    </h1>
  </div>
</template>

我們就可以使用 $t() 來傳入我們在 nuxt.config.ts 中的 i18n.vueI18n.messages 定義語系與對應的文字,而我們預設是 zh 繁體中文語系,畫面上就出現 hello: '你好!' 對應的「你好!」文字囉!
https://ithelp.ithome.com.tw/upload/images/20221010/20152617iVMhJzdiz6.png

新增按鈕來實現切換不同的語系,完整的程式碼如下:

<template>
  <div class="flex flex-col items-center bg-white">
    <h1 class="mt-48 text-8xl font-medium text-blue-500">
      {{ $t('hello') }}
    </h1>
    <div class="mt-24 space-x-4">
      <button
        type="button"
        class="inline-flex items-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
        @click="setLocale('en')"
      >
        English
      </button>

      <button
        type="button"
        class="inline-flex items-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
        @click="setLocale('zh')"
      >
        繁體中文
      </button>
    </div>
    <div class="mt-4 flex flex-row justify-center">
      <label class="text-gray-600">{{ $t('language') }}</label>
      <span class="ml-4 font-bold text-gray-800">{{ locale }}</span>
    </div>
  </div>
</template>

<script setup>
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
</script>

若沒有特殊需求,切換語系你可以選擇直接設定 locale 來自動響應或使用 setLocale() 來設定語系。

<script setup>
import { useI18n } from 'vue-i18n'
const { locale, setLocale } = useI18n()

locale.value = 'en'
// or
setLocale('en')
</script>

完成後我們就可以切換 enzh 語系,當然你也可以製作成選單來放置更多的語系來支援切換。
https://i.imgur.com/p5bMTIt.gif

獨立存放語系檔案

隨著專案增大,語系的翻譯數量也會增多,而全部放在 nuxt.config.ts 中也實在太過冗長,所以我習慣獨立放置在一個資料夾內做處理。

稍微調整一下 nuxt.config.ts 如下:

export default defineNuxtConfig({
  modules: [@nuxtjs/i18n'],
  i18n: {
    langDir: 'locales',
    locales: [
      {
        code: 'en',
        iso: 'en-US',
        file: 'en.json'
      },
      {
        code: 'zh',
        iso: 'zh-TW',
        file: 'zh.json'
      }
    ],
    defaultLocale: 'zh',
    strategy: 'no_prefix'
  }
})

接著我們就可以建立各個語系放置的資料夾對應著 i18n.langDir 屬性,目錄名稱為 locales,再依序建立 en.jsonzh.json 檔案,最終目錄的結構大概會長這樣:

nuxt-app/
├── ...
├── locales/
│   ├── en.json
│   └── zh.json
├── ...
└── nuxt.config.ts

./locales/en.json 內容如下:

{
  "hello": "Hello!",
  "language": "Language"
}

./locales/zh.json 內容如下:

{
  "hello": "你好!",
  "language": "語言"
}

翻譯的語系檔案,也可以在根據需求使用 .jsyamljson 格式的檔案,來搭配接受參數來產生翻譯文字。

持久化語系設定

當使用者切換語系之後,我們希望儲存這個語系設定,讓使用者下次瀏覽網站時,能套用儲存的語系,而不用再次切換,我們可以藉由瀏覽器的 LocalStorageCookie 來持久化。

Cookie 來儲存語系設定

Nuxt 3 的伺服器渲染,會導致第一次的請求無法在伺服器端取得 LocalStorage 的設定。

我們可以透過使用 cookie 的方式來儲存語系,這樣伺服器端在接受第一次請求時也能解析出使用者的偏好設定,回傳正確的語系翻譯文字。

你可以自己使用 Nuxt 3 提供 useCookie 等方式操作 cookie 來實現這套流程,不過呢,@nuxtjs/i18n 模組預設是啟用 cookie 相關的設定,提供的 setLocaleCookie()getLocaleCookie() 可以來協助儲存語系設定至 cookie。

調整 ./pages/index.vue 程式碼如下,在我們自訂的 changeLanguage() 方法內,同時呼叫 setLocaleCookie(localeCode) 來儲存語系。

<template>
  <div class="flex flex-col items-center bg-white">
    <h1 class="mt-48 text-8xl font-medium text-blue-500">
      {{ $t('hello') }}
    </h1>
    <div class="mt-24 space-x-4">
      <button
        v-for="localeItem in locales"
        :key="localeItem.code"
        type="button"
        class="inline-flex items-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
        @click="changeLanguage(localeItem.code)"
      >
        {{ localeItem.name }}
      </button>
    </div>
    <div class="mt-4 flex flex-row justify-center">
      <label class="text-gray-600">{{ $t('language') }}</label>
      <span class="ml-4 font-bold text-gray-800">{{ locale }}</span>
    </div>
  </div>
</template>

<script setup>
import { useI18n } from 'vue-i18n'
const { locale, locales, setLocaleCookie } = useI18n()

const changeLanguage = (localeCode) => {
  locale.value = localeCode
  setLocaleCookie(localeCode)
}
</script>

調整 app.vue 呼叫 getLocaleCookie() 從 cookie 解析的設定值,並再進行套用。

<template>
  <div>
    <NuxtPage />
  </div>
</template>

<script setup>
import { useI18n } from 'vue-i18n'
const { locale, getLocaleCookie } = useI18n()

const defaultLocaleCode = locale.value

locale.value = getLocaleCookie() ?? defaultLocaleCode
</script>

當我們使用 setLocaleCookie() 儲存語系至 cookie,會將 localeCode 儲存在 cookie 的 i18n_redirected 值上,當我們重新進入網頁,cookie 也將自動夾帶了這個 cookie 過去給伺服器端,並由 getLocaleCookie() 解析再由我們進行語系套用。
https://i.imgur.com/eHBkAAq.gif

其實 @nuxtjs/i18n 因為已將 detectBrowserLanguage.useCookie 預設為 true 等啟用了 cookie 儲存的相關設定,當使用者進入網站時,就會嘗試檢查瀏覽器的 cookie 來將使用者儲存的偏好語言進行套用或重定向。

因此,我們在 app.vue 也不需要特別從 cookie 取出再進行套用,@nuxtjs/i18n 會自動幫我們完成。

<template>
  <div>
    <NuxtPage />
  </div>
</template>

<script setup>
// 除非你有特需的需求,不然這邊都不需要自己來了

// import { useI18n } from 'vue-i18n'
// const { locale, getLocaleCookie } = useI18n()

// const defaultLocaleCode = locale.value

// locale.value = getLocaleCookie() ?? defaultLocaleCode
</script>

而當使用者調整語系時,在第一個範例有提到,如果沒有特別需求,我們可以直接使用 locale 來設定語系,來自動響應變更語系,再使用 setLocaleCookie(localeCode) 將語系保存至 cookie 之內。

<script setup>
import { useI18n } from 'vue-i18n'
const { locale, locales, setLocaleCookie } = useI18n()

const changeLanguage = (localeCode) => {
  locale.value = localeCode
  setLocaleCookie(localeCode)
}
</script>

但我們只要使用 setLocal() 方法來套用語系,就會一同更新至 cookie 中進行儲存,非常方便。最後我們將 ./pages/index.vue 中的 調整如下,一樣可以達到在 cookie 中儲存使用者語系並在下次瀏覽網頁時自動套用。

<script setup>
import { useI18n } from 'vue-i18n'
const { locale, locales, setLocale } = useI18n()

const changeLanguage = (localeCode) => {
  setLocale(localeCode)
}
</script>

使用 setLocaleCookie()getLocaleCookie() 操作的預設設定,我們可以透過 nuxt.config.ts 中的 i18n.detectBrowserLanguage 進行調整,例如,調整 cookieKey 來變更儲存的名稱 i18n_redirected 等,更多選項可以參考這裡

export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],
  i18n: {
    // ...
    detectBrowserLanguage: {
      useCookie: true,
      cookieKey: 'i18n_redirected'
    },
    // ...
  }
})

@nuxtjs/i18n 模組提供的 cookie 操作語系持久話,甚至能做到一些重導向等行為,較 Nuxt 3 提供 useCookie 等方式自己來實現這套流程能更擁有更多功能。

小結

這篇太深入沒有講述 Vue I18n 的功能,而是針對 Nuxt 3 的整合與常見的設定做一個分享,如果你已經在 Vue 3 上熟悉 Vue I18n,根據這篇可以加快與 Nuxt 3 整合的速度,更多 Vue I18n v9@nuxtjs/i18n 功能大家可以參照著官方文件,也常注意正式發布前的一些改版訊息,期待 Vue I18n 與 Nuxt Community 提供的 @nuxtjs/i18n 能越來越穩定,讓網站邁向國際化的過程能更順利,大家都能方便的實現多國語系。


感謝大家的閱讀,這是我第一次參加 iThome 鐵人賽,請鞭小力一些,也歡迎大家給予建議 :)
如果對這個 Nuxt 3 系列感興趣,可以訂閱接收通知,也歡迎分享給喜歡或正在學習 Nuxt 3 的夥伴。

範例程式碼

參考資料


上一篇
[Day 24] Nuxt 3 搜尋引擎最佳化 (SEO) 與 HTML Meta Tag
下一篇
[Day 26] Nuxt 3 Public 與 Assets 資源目錄
系列文
Nuxt 3 學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
Wolke
iT邦研究生 5 級 ‧ 2022-10-17 20:14:33

動態改變文字
會不會對於 SEO 不好

Ryan iT邦新手 1 級 ‧ 2022-10-17 23:44:26 檢舉

如果指的動態,是同一個頁面或元件,因為取得不同的資料而動態渲染的文字,對 SEO 來說還是以首次所顯示的或爬取到的資訊為主。

也就是說,一個部落格文章的頁面元件,當請求進來時,如果是後端渲染,則是後端打 API 取得文章資料,動態的渲染出 SEO 要的 Title 或 Meta,那麼爬蟲蒐集到的基本上沒什麼大問題。

倘若是有在客戶端渲染再進行加料的動態文字,雖然網頁標題也會更改,但是搜尋引擎收錄的文字,多不會涵蓋這邊動態修改的文字。

0
wilbur825882
iT邦新手 5 級 ‧ 2023-05-18 14:08:00

https://ithelp.ithome.com.tw/upload/images/20230518/20121190BeYUhYOEIK.jpg

請問您有遇到這個型別問題嗎?
我看官方文件也都沒特別說明...
實在無法解決

補充: 語系有正常顯示

Ryan iT邦新手 1 級 ‧ 2023-05-19 12:39:46 檢舉

如果運作正常,比較大的可能性應該是編輯器(VS Code)需要重新載入。

建議您先移除 @nuxtjs/i18n

npm uninstall @nuxtjs/i18n

同時也建議您可以嘗試完整移除專案下的 node_modules

重新安裝一次最新的版本

npm install -D @nuxtjs/i18n@next

重新開啟編輯器(VS Code),看看原本錯誤的提示是否還存在


另外不確定您所有的套件的版本跟設定,想詢問您在使用本篇文章的範例專案也會有一樣的情況嗎?

其他可能性也可以建議您檢查看看,例如

  1. tsconfig.json 是否有做一些額外的設定
  2. 其他套件的版本是否有衝突等等
0
碼農
iT邦新手 4 級 ‧ 2024-03-06 11:44:24

留言內容已刪除

我要留言

立即登入留言