メディアサイトを多言語対応する【Nuxt.js】

## はじめに
こちらの記事は[グロービスアドベントカレンダー2021](https://qiita.com/advent-calendar/2021/globis)の15日目の記事になります。

こんにちは！グロービス・デジタル・プラットフォームのエンジニアの[@couragenki](https://twitter.com/couragenki)です。
グロービスでは[GLOBIS Unlimited](https://globisunlimited.com/)のフロントエンドをReact.jsで開発したり[グロービス経営大学院 ナノ単科](https://nano-mba.hodai.globis.co.jp/) の学習アプリケーションの開発をFlutterで開発しております。

https://globisunlimited.com/

https://nano-mba.hodai.globis.co.jp/

## 今回作成したサイト
今回は個人開発で作成したNuxt.jsのメディアサイトの多言語対応を実装したお話になります。
下記の画像のように言語切り替えを行うとページと記事の言語が切り替わる実装を行いました。
今回作成した多言語サイトのデモはこちらになります。

![画面収録_2021-12-05_7_52_46_SparkVideo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199085/a5701af5-8f96-c4d0-979a-375d17cd4767.gif)

https://hobby-site-510c5.firebaseapp.com/

### 用途
多言語対応が必要で記事が多いサイトに向いています。

- コーポレートサイト
- メディアサイト
- FAQサイト

サイトのヘッダーやフッター以外にもお知らせや投稿なども多言語化して管理したい場合に向いています。
SSRで各言語ごとにページを生成するのでSEO的にもメリットがあるので検索流入を狙うケースでも使えます。

## Nuxt.jsでのサイトの基本的な実装について
多言語化するサイトのベースの実装はこちらのNuxt/Contentを利用したサイトです。
こちらでのサイトの立ち上げが個人的に楽なのでよく使っています。
今回はこちらで作成したサイトを多言語に対応させていく内容になります。

https://qiita.com/couragenki/items/0e66d0b0a97bb7749bee

## サイトを多言語化する

多言語化するために`i18n`対応していきます。
i18nとは？という方にはこちらの記事がわかりやすくておすすめです。

https://mabots.hatenablog.com/entry/20120214/1329225300

## nuxt-i18nを使った多言語化

nuxt-i18nを使って多言語対応していきます。

https://i18n.nuxtjs.org/

### nuxt-i18nを導入

まずはインストールする

```
npm i nuxt-i18n
```

### 言語のデータを保存するjsonを追加する

jsonに変換する言語データを保存する

#### 日本語のデータ
```ja.json
{
  "HELLO_WORLD": "こんにちは、世界",
  "FOODS": "食べ物",
  "INTORO": "こちらは多言語で作られたサイトのデモです。",
  "SITE_TITLE": "サイトのタイトル",
  "ARTICLES": "投稿",
  "LINKS": "リンク",
  "NEWS": "お知らせ"
}
```
#### 英語のデータ
```en.json
{
  "HELLO_WORLD": "Hello World",
  "FOODS": "FOODS",
  "INTORO": "Here is a demo of a site created in multiple languages.",
  "SITE_TITLE": "SITE TITLE",
  "ARTICLES": "Articles",
  "LINKS": "Links",
  "NEWS": "News"
}

```

このようにデータを作成し`/lang`以下のディレクトリに置きます。

### 言語設定を追加する
`nuxt.config.js`に言語設定を追加します。
今回はデフォルトの言語を日本語にしていますが例えば英語をデフォルトにしたい場合などは`defaultLocale`と`fallbackLocale`を`en`に変更すればOKです。

日本語の言語コードは`ja`で国コードは`jp`なのでどちらのコードを使うべきか迷ったので調べたのですが、i18nの場合は`ja`を使うようでした。

https://www.kanzaki.com/docs/html/lang.html

```js
i18n: {
  locales: [
    {
      code: 'ja',
      iso: 'ja',
      file: 'ja.json',
    },
    {
      code: 'en',
      iso: 'en',
      file: 'en.json',
    },
    {
      code: 'cn',
      iso: 'cn',
      file: 'cn.json',
    },
  ],
  langDir: 'lang/',
  fallbackLocale: ['ja'],
  defaultLocale: 'ja',
```


### データを呼び出す
`$t()`関数の中に呼び出したいjsonのキーを入れて呼び出すことで言語を切り替えて表示することが可能です。

```vue
<span>{{ $t('HELLO_WORLD') }}</span>
```

### 言語切り替えの実装
今回はこのように言語ごとにリンクを設置して切り替えたい言語を選択する形式にしました。

```SetLang.vue
<template>
  <div class="setlang">
    <nuxt-link
      :class="{ selected: $i18n.locale === 'cn' }"
      :to="switchLocalePath('cn')"
      >中国語</nuxt-link
    >
    <nuxt-link
      :class="{ selected: $i18n.locale === 'en' }"
      :to="switchLocalePath('en')"
      >English</nuxt-link
    >
    <nuxt-link
      :class="{ selected: $i18n.locale === 'jp' }"
      :to="switchLocalePath('jp')"
      >日本語</nuxt-link
    >
  </div>
</template>
```

### Linkコンポーネントを作る
必須ではないですが`localePath`の挙動を一括管理するならコンポーネント化した方が楽なのでコンポーネントを作る。

```components/Link.vue
<template>
  <n-link :to="localePath(setPath)">
    <slot />
  </n-link>
</template>
```

ここまでが一般的なi18nの対応になります。

## nuxt-contentとnuxt-i18nを組み合わせた多言語化対応

今回のやりたいことのメインはここからになります。
ここからは`nuxt/content`で追加したお知らせや投稿などを言語切り替えできるように実装していきます。
`nuxt/content`を使うことで記事ごとに翻訳したマークダウンを用意できるのでページ単位で翻訳を管理できます。

### まずはcontentに言語ごとに記事を追加する
日本語で記事を追加

```content/foods/articles/1.md
---
title: 美味しいハンバーガー
description: 今回は美味しい美味しいハンバーガーについて語っていきたいと思います。
keyword: ハンバーガー
language: jp
image: /articles/1.jpeg
tags:
  - 食べ物
  - ハンバーガー
---

## 美味しいハンバーガーについて

今回は美味しい美味しいハンバーガーについて語っていきたいと思います。
```


続いて同じ記事の英語版を追加

```content/foods/articles/en/1.md
---
title: Delicious burgers
description: In this article, I would like to talk about delicious and tasty hamburgers.
keyword: hamburger
language: en
image: /articles/1.jpeg
tags:
  - foods
  - hamburger
---

## Delicious burgers

In this article, I would like to talk about delicious and tasty hamburgers.

```

### pagesにて記事の言語切り替えを行う
記事を表示する`_slug.vue`にて`i18n`で現在表示している言語キーを取得できるのでそちらを使って記事の切り替えを行なっています。
基本的にcontentの中の言語切り替えは他の箇所でもこのやり方で行いました。

```/pages/foods/articles/_slug.vue
<template>
  <PostTemplate :post-data="post" />
</template>

<script>
import PostTemplate from '~/components/PostTemplate.vue'
export default {
  components: { PostTemplate },
  async asyncData({ $content, params, i18n }) {
    if (i18n.locale === 'cn') {
      const post = await $content(
        'foods/articles/cn',
        params.slug || 'index'
      ).fetch()

      return { post }
    } else if (i18n.locale === 'en') {
      const post = await $content(
        'foods/articles/en',
        params.slug || 'index'
      ).fetch()

      return { post }
    } else {
      const post = await $content(
        'foods/articles',
        params.slug || 'index'
      ).fetch()
      return { post }
    }
  },
}
</script>

```

こうすることで記事のページでも言語の切り替えがこんな感じでできます。
ページの中身も言語が同じタイミングで切り替わっていることが分かります。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199085/8eb1062f-f5bc-a0a5-1f35-f1215f8615e7.gif">

### タグのページも実装する
contentに追加したマークダウンのデータから現在選択されている言語のタグだけを取得するメソッドを追加する。

```/pages/foods/tags/index.vue
<template>
  <DefaultTemplate>
    <div class="wrapper">
      <h1 class="title">
        <div>
          <span>{{ $t('FOODS') }}</span>
        </div>
      </h1>

      <h2>Tags</h2>
      <ul v-for="(item, index) in setTags(viewArticlesData)" :key="index">
        <li>
          <Link :path="'/foods/tags/' + item">
            {{ item }}
          </Link>
        </li>
      </ul>
    </div>
  </DefaultTemplate>
</template>

<script>
import DefaultTemplate from '~/components/DefaultTemplate.vue'
export default {
  components: {
    DefaultTemplate,
  },
  async asyncData({ $content }) {
    const articlesQuery = $content('foods/articles', { deep: true }).sortBy(
      'date',
      'desc'
    )
    const articlesData = await articlesQuery.fetch()
    const viewArticlesData = articlesData.sort(function (a, b) {
      return new Date(b.date) - new Date(a.date)
    })

    return { viewArticlesData }
  },
  methods: {
    setTags(arr) {
      const muchTags = []
      for (let i = 0; i < arr.length; i++) {
        if (this.$i18n.locale === arr[i].language) {
          for (let ii = 0; ii < arr[i].tags.length; ii++) {
            if (!muchTags.includes(arr[i].tags[ii])) {
              muchTags.push(arr[i].tags[ii])
            }
          }
        }
      }

      return muchTags
    },
  },
}
</script>
```

こんな感じで言語切り替えを行なった際にタグの言語も切り替わりタグによる絞り込みもできるようになりました。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199085/f42f34e1-6258-5523-01af-8e50b9251406.gif">

## まとめ
今回は多言語化対応の勉強とキャッチアップも兼ねてNuxt.jsで多言語サイトを作成しました。
またメディアサイトやFAQサイトは汎用が高いので今後サイトを作る際のテンプレートとして実装したので活躍の機会があればいいなと思います。

Nuxt.jsは現在は業務で触っていませんがとにかくお手軽で気に入っているので個人開発ではよく使っています。
今後も定期的にNuxtのアウトプットをしていきたいです！

以上になります。最後までお読みいただきありがとうございました。