世界を旅して暮らしたい放浪エンジニアブログ

Nuxt.jsの国際化(i18n)対応

Nuxt.jsで英語、日本語の多言語対応したサイトを構築します。

[ 目次 ]

はじめに

こんにちは、香港に住んでいるWEBデベロッパーのなかむ(@nakanakamu0828)です。

この記事は過去に運用していたブログからの移行記事になります。
Nuxt.jsのリポジトリの国際化対応をしていきます。

■ 前回の投稿

言語の切り替えをサブディレクトリで行えるように実装していきます。
http://[domain]/が英語ページ、http://[domain]/ja/が日本語ページになるイメージです。

以下のNuxt.jsの公式ページを参考に進めていきます。
https://ja.nuxtjs.org/examples/i18n/

vue-i18nのインストール

$ npm install vue-i18n --save

package.jsonを確認するとdependenciesvue-i18nが追加されます。

・・・
"dependencies": {
  "nuxt": "^1.0.0",
  "vue-i18n": "^7.6.0",
  "vuetify": "^1.0.0"
},
・・・

各種ファイルの作成・更新

コマンドにてディレクトリ・ファイルを作成する

テンプレートとして作成されているpages/index.vueの内容を修正して、多言語化を試します。

pages/inspire.vueは削除します

_langを作成するのは、URL内に言語を埋め込む為です。

$ mkdir -p pages/_lang
$ cp pages/index.vue pages/_lang/index.vue
$ rm -f pages/inspire.vue

翻訳ファイルはjsonで管理します

$ mkdir -p locales
$ touch locales/{en.json,ja.json}

$ touch plugins/i18n.js
$ touch middleware/i18n.js

pages/_lang/index.vue

タイトルを言語ごとに切り替えられるようにします

<template>
  <v-layout column justify-center align-center>
    <v-flex xs12 sm8 md6>
      <h1>{{ $t('welcome.title') }}</h1>
    </v-flex>
  </v-layout>
</template>

pages/index.vue

<script>
import Index from '~/pages/_lang/index'
export default Index
</script>

locales/en.json

英語用の翻訳ファイル

{
    "welcome": {
        "title": "Welcome"
    }
}

locales/ja.json

日本語用の翻訳ファイル

{
    "welcome": {
        "title": "ようこそ"
    }
}

plugins/i18n.js

pluginにてi18nライブラリの読み込みと設定を行います。

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'),
      'ja': require('~/locales/ja.json')
    }
  })

  app.i18n.path = (link) => {
    if (app.i18n.locale === app.i18n.fallbackLocale) {
      return `/${link}`
    }
    return `/${app.i18n.locale}/${link}`
  }
}

middleware/i18n.js

HTTPリクエスト毎に現在の言語を設定します

export default function ({ isHMR, app, store, route, params, error, redirect }) {
  const defaultLocale = app.i18n.fallbackLocale
  if (isHMR) return
  const locale = params.lang || defaultLocale
  if (store.state.locales.indexOf(locale) === -1) {
    return error({ message: 'This page could not be found.', statusCode: 404 })
  }
  store.commit('SET_LANG', locale)
  app.i18n.locale = store.state.locale
  if (locale === defaultLocale && route.fullPath.indexOf('/' + defaultLocale) === 0) {
    const toReplace = '^/' + defaultLocale
    const re = new RegExp(toReplace)
    return redirect(
      route.fullPath.replace(re, '/')
    )
  }
}

store/index.js

ストアに言語を設定して共通利用します

export const state = () => ({
  sidebar: false,
  locales: ['en', 'ja'],
  locale: 'en'
})

export const mutations = {
  toggleSidebar (state) {
    state.sidebar = !state.sidebar
  },
  SET_LANG (state, locale) {
    if (state.locales.indexOf(locale) !== -1) {
      state.locale = locale
    }
  }
}

nuxt.config.js

nuxt.config.jsにプラグイン、ライブラリの設定を追加

・・・
plugins: [
  '~/plugins/vuetify.js',
  '~/plugins/i18n.js'     <-- 追加
],
・・・
vendor: [
  '~/plugins/vuetify.js',
  'vue-i18n'   <-- 追加
],
・・・
以下を追加

router: {
  middleware: 'i18n'
},
generate: {
  routes: ['/', '/ja']
}

layouts/default.vue

ヘッダーに言語切り替え用のdropdownを用意します。
inspireページのリンクは不要なので削除します。

<template>
  <v-app dark>
    <v-navigation-drawer
      :mini-variant.sync="miniVariant"
      :clipped="clipped"
      v-model="drawer"
      fixed
      app
    >
      <v-list>
        <v-list-tile
          router
          :to="item.to"
          :key="i"
          v-for="(item, i) in items"
          exact
        >
          <v-list-tile-action>
            <v-icon v-html="item.icon"></v-icon>
          </v-list-tile-action>
          <v-list-tile-content>
            <v-list-tile-title v-text="item.title"></v-list-tile-title>
          </v-list-tile-content>
        </v-list-tile>
      </v-list>
    </v-navigation-drawer>
    <v-toolbar fixed app :clipped-left="clipped">
      <v-toolbar-side-icon @click="drawer = !drawer"></v-toolbar-side-icon>
      <v-btn
        icon
        @click.stop="miniVariant = !miniVariant"
      >
        <v-icon v-html="miniVariant ? 'chevron_right' : 'chevron_left'"></v-icon>
      </v-btn>
      <v-btn
        icon
        @click.stop="clipped = !clipped"
      >
        <v-icon>web</v-icon>
      </v-btn>
      <v-btn
        icon
        @click.stop="fixed = !fixed"
      >
        <v-icon>remove</v-icon>
      </v-btn>
      <v-toolbar-title v-text="title"></v-toolbar-title>
      <v-spacer></v-spacer>
      <v-menu bottom left>
        <v-btn icon slot="activator" dark>
          <v-icon>language</v-icon>
        </v-btn>
        <v-list>
          <v-list-tile v-for="(language, i) in languages" :key="i" :to="language.to">
            <v-list-tile-title>{{ language.title }}</v-list-tile-title>
          </v-list-tile>
        </v-list>
      </v-menu>
      <v-btn
        icon
        @click.stop="rightDrawer = !rightDrawer"
      >
        <v-icon>menu</v-icon>
      </v-btn>
    </v-toolbar>
    <v-content>
      <v-container>
        <nuxt />
      </v-container>
    </v-content>
    <v-navigation-drawer
      temporary
      :right="right"
      v-model="rightDrawer"
      fixed
    >
      <v-list>
        <v-list-tile @click.native="right = !right">
          <v-list-tile-action>
            <v-icon light>compare_arrows</v-icon>
          </v-list-tile-action>
          <v-list-tile-title>Switch drawer (click me)</v-list-tile-title>
        </v-list-tile>
      </v-list>
    </v-navigation-drawer>
    <v-footer :fixed="fixed" app>
      <span>&copy; 2017</span>
    </v-footer>
  </v-app>
</template>

<script>
  export default {
    data () {
      return {
        clipped: false,
        drawer: true,
        fixed: false,
        items: [
          { icon: 'apps', title: 'Welcome', to: '/' }
        ],
        languages: [
          { title: 'English', to: this.$route.fullPath.replace(/^\/[^/]+/, '') },
          { title: '日本語', to: '/ja' + this.$route.fullPath.replace(/^\/[^/]+/, '') }
        ],
        miniVariant: false,
        right: true,
        rightDrawer: false,
        title: 'Vuetify.js'
      }
    }
  }
</script>

plugins/vuetify.js

言語切り替えで利用した、v-menuのコンポーネントを読み込む為、plugins/vuetify.jsに設定を追加します。

import Vue from 'vue'
import {
  Vuetify,
  VApp,
  VCard,
  VNavigationDrawer,
  VFooter,
  VList,
  VBtn,
  VIcon,
  VGrid,
  VToolbar,
  VMenu <-- 追加
} from 'vuetify'

Vue.use(Vuetify, {
  components: {
    VApp,
    VCard,
    VNavigationDrawer,
    VFooter,
    VList,
    VBtn,
    VIcon,
    VGrid,
    VToolbar,
    VMenu <-- 追加
  }
})

サーバー起動

開発環境としてサーバーを起動してみましょう。

$ npm run dev

ブラウザから http://localhost:3000 を開いて確認しましょう。
ヘッダーメニューの言語メニューから英語・日本語を切り替えてみてください。

今回の成果物は こちら になります。
Netlifyでデモ環境を用意しています。以下のURLにてご確認ください。

https://goofy-feynman-3a61d1.netlify.com/

修正(2018/10/24)

デモページに関しては、サンプルで作ったものなので閉鎖しました。

前のページ

次のページ

Profile

なかむ🇭🇰Webデベロッパー

なかむ🇭🇰Webデベロッパー

香港在住4年目になるWEBエンジニアのなかむです。 現在は、LaravelやRailsを利用したWEB開発を中心にエンジニアをしています。 顧客は全て日本の企業になります。リモート開発にて各企業様の支援を行なっております

プロフィール詳細はこちら

Latest Posts