在 Nuxtjs 設置並使用多國語言

Stephen Lai
10 min readMar 10, 2021

Nuxtjs: i18n + Vuex + 下拉選單

選擇語言後, 切換頁面仍然維持語言狀態

我正在開發個人部落格網站, 要有多國語言切換的功能 (中文/英文), 看了網路上許多的技術文 (中英文皆有), 多數是針對 vue-cli 專案設置的, 我沒有看到一篇中文文章 (有簡體中文的, 但是沒有詳細說明) 談到 Nuxtjs + i18n 這個主題的, 英文的倒是不少, 不過要做不少修正才能達到我要的需求和功能. 以下就來談談我是怎麼在 Nuxtjs 專案使用下拉式選單做到中英文切換網頁語言並且在切換頁面的情形下仍然保持狀態.

相信有訪問過 wiki 的人都會發現當我們切換語言選項 (繁體中文 <=> 簡體中文), 假設我們選擇了繁體中文, 當我們切換到另一個頁面 (比如左邊的 "討論" 選項), 字體會自動恢復到 default 值 (簡體中文), 很明顯這個網站並沒有紀錄我們在下拉選單選擇的項目. 這是很不好的使用者體驗. 我的網站要使用者選擇合適的語言後, 不論切換到哪個頁面, 或是重整網頁 (選擇性), 仍然維持設定, 提供最佳的使用者體驗.

Tech Stack

  • NuxtJS (功能最強大的 VueJS 框架)
  • 下拉式選單 (select+option 標籤)
  • Vuex (狀態管理系統, 所以的組件都可以直接存取資料)
  • (選擇性) Local Storage (瀏覽器的內存, 儲存語言選項 (英文/中文)

專案架構

VSCode 專案結構

步驟

安裝 vue-i18n 套件 npm i vue-i18n

components目錄下建立 LangSwitcher.vueNavbar.vue

LangSwitcher.vue (下拉選單組件)

<template>
<select
v-model="$store.state.lang"
@change="setLang($store.state.lang)"
><option
v-for="item in optionLang"
:value="item.value"
:key="item.id"
>{{ item.text }}
</option>
</select>
</template>
<script>
export default {
data() {
return {
optionLang: [
{ text: 'English', value: 'en', id: 1 },
     { text: '中文', value: 'zh', id: 2 }
    ]
   }
  },
 methods: {
  setLang(value) {
   this.$store.commit('setLang', value)
   this.$i18n.locale = value
  }
 },
}
</script>

說明:

  • 下拉選單由 selectoption 標籤組成.
  • v-model 綁定 storelang state
  • 選單改變時會觸發 setLang 函式, setLang 做兩件事:
  1. 把選單值 (en/zh) 賦值給 store 的 lang
  2. 把選單值賦值給 i18n.locale

Navbar.vue (菜單組件)

<template>
<div class="main-header">
<div class="navbar">
<NuxtLink to="/">
<div class="logo">{{ $t('title') }}</div>
</NuxtLink>
<div class="menu-right">
<div class="menu">
<NuxtLink to="/">{{ $t('navbarHome') }}</NuxtLink>
<NuxtLink to="/blog">{{ $t('navbarBlog') }}</NuxtLink>
<NuxtLink to="/about">{{ $t('navbarAbout') }}</NuxtLink>
<NuxtLink to="/work">{{ $t('navbarWork') }}</NuxtLink>
<LangSwitcher />
</div>
</div>
</div>
</div>
</template>
<style scoped>
.menu-right {
display: flex;
align-items: center;
}
.main-header {
border-bottom: var(--bottom);
}
.navbar {
height: 112px;
padding: 0 15px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.logo {
font-weight: 400;
font-size: 38px;
color: var(--primary);
text-transform: uppercase;
}
.menu {
width: 525px;
display: flex;
justify-content: space-between;
}
@media (max-width: 992px) {
.navbar {
height: 90px;
}
.logo {
font-size: 32px;
}
}
a {
text-decoration: none;
color: var(--item);
font-size: 22px;
font-weight: 300;
}
a.nuxt-link-exact-active {
color: var(--primary);
font-weight: 400;
}
</style>

說明:

i18n 套件會根據 i18n.locale物件的內容 (中文或英文) 把值設定給 store 的 langstate, 然後我們在組件內使用 {{$t(variable)}} 把值動態渲染出來

layouts\default.vue 貼上下列內容

<template>
<div>
<Navbar />
<Nuxt class="style" />
</div>
</template>
<style>
@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;600;700&display=swap');
:root {
--primary: #278f6e;
--item: #707070;
--subtitle: #313131;
--bottom: 1px solid #efefef;
--brDefault: 30px;
}
.style {
display: flex;
height: 30vh;
justify-content: center;
align-items: center;
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
font-family: 'Rubik', sans-serif;
background: var(--bg-color);
}
</style>

建立 locales 資料夾, 新增 en.jsonzh.json

en.json 內容

{
"title": "LOGO",
"navbarHome": "Home",
"navbarBlog": "Blog",
"navbarAbout": "About",
"navbarWork": "My Work"
}

zh.json

{
"title": "標識",
"navbarHome": "首頁",
"navbarBlog": "部落格",
"navbarAbout": "關於我",
"navbarWork": "作品集"
}

說明:

語言的資料存放在 locales 資料夾內, 如果要新增其他語言, 比如法文, 就新增一個 fr.json 檔案, 然後加入法文資料.

pages 資料夾下建立 about.vue , blog.vue , work.vue

index.vue 內容

<template>
<div class="home-page">
<h1>{{ $t('homepage') }}</h1>
</div>
</template>

about.vue 內容

<template>
<div class="about-page">
<h1>{{ $t('navbarAbout') }}</h1>
</div>
</template>

blog.vue 內容

<template>
<div class="blog-page">
<h1>{{ $t('navbarBlog') }}</h1>
</div>
</template>

work.vue 內容

<template>
<div class="work-page">
<h1>{{ $t('navbarWork') }}</h1>
</div>
</template>

plugins 資料夾建立 i18n.js

i18n.js 內容

import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)export default ({ app, store }) => {
app.i18n = new VueI18n({
locale: store.state.locale,
fallbackLocale: 'en',
messages: {
en: require('~/locales/en.json'),
zh: require('~/locales/zh.json'),
},
})
app.i18n.path = (link) => {
if (app.i18n.locale === app.i18n.fallbackLocale) {
return `/${link}`
}
return `/${app.i18n.locale}/${link}`
}
}

說明:

i18n 插件的檔案, 1. 把 i18n.locale 和 store 做綁定; 2. 引入 語言資料 (en.json/zh.json)

在 store 資料夾建立 index.js , state.js , mutations.js

index.js 內容

import Vuex from 'vuex'
import { state } from './state.js'
import { mutations } from './mutations.js'
const createStore = () => {
return new Vuex.Store({
state,
mutations,
})
}
export default createStore

state.js 內容

export const state = {
lang: 'en'
}

mutations.js 內容

export const mutations = {
setLang(state, data) {
state.lang = data
}
}

說明:

  • index.js 設定並且註冊 vuex
  • state.js 設置 lang 的初始值為英文

nuxt.config.js 註冊 i18n

export default {
plugins: [
'~/plugins/i18n.js'
]
}

我們也可以把 lang 存到 localStorage, 這樣網頁重整語言設定也不會跑掉, 不過我覺得沒有這個必要, 因為現在是多語言時代, 英文/中文交替使用, 不需要說只要選擇了某個語言網頁就永久記住, 只要切換頁面維持狀態即可.

--

--