📘

Next.js + Strapi(HeadlessCMS)で多言語サイトのテンプレートを作成しました。

2022/01/17に公開

2022/07/12 TypeScript対応しました
IoT-Arduino/nextjs-strapi-i18n-frontend

Next.js + Strapi(HeadlessCMS)で多言語サイトのテンプレートを作成しました。

ネット上にNext.jsの多言語対応についての記事がたくさん上げられていますが、これらは、以下1)か2)のどちらか一方に対応したものしかありませんでした。今回は両方に対応する為の実装を自分でおこなって、記事にしてみました。

実現した機能

1)トップページの言語切り替えメニューの指定によって、メニューやコンテンツが切り替わる。

トップページの多言語対応
トップページの多言語対応

2)ニュース記事、ブログ記事などCMSと連携して表示する部分も、1)の言語切り替えを反映して、連携するコンテンツが切り替わる。

CMSコンテンツの多言語対応
CMSコンテンツの多言語対応

実装プロセス

Next.js V10から多言語対応が標準化されたので、今回は、これを使って、多言語サイトの実装をします。ところが、Next.js V10の標準で対応しているのはルーティング機能のみです。
https://nextjs.org/docs/advanced-features/i18n-routing
実際にやりたいことを実現する為に、追加で対応しました。以下解説していきます。

1)言語選択メニュー(Next.js)

ヘッダーに設置された、ドロップダウンリストにより、ナビゲーションやページの内容(CMSと連携しない部分)の言語を切り替える。この部分についてはnext/translate等のライブラリを使う方法が、ネット記事等で紹介されていたりしますが、今回はライブラリを使わない方法を紹介します。
(尚、Next.jsのi18n標準機能によって、ブラウザの言語設定に基づいて、表示言語は自動的に切り替わりますので、通常はメニューでの言語切り替え操作は不要です。)

言語切り替えメニュー
言語切り替えメニュー

多言語対応基本設定

今回は日本語、英語、中国語(簡体字)の設定を行います。

*Next.jsの設定 Next.config.js
*Next.jsの設定 Next.config.js

言語翻訳ファイル設定
Localesフォルダ以下に言語翻訳ファイルを設定します。

翻訳ファイル
翻訳ファイル

中国語(簡体字)の翻訳ファイル

/locales/cn.js
/locales/cn.js

各ページでの翻訳設定の反映

翻訳ファイルのインポート

/pages/index.js"のインポート文
/pages/index.js"のインポート文

レンダリング部分
・Locale情報に基づき、使用する翻訳ファイルを切り替える条件式(水色枠部分)
・指定された翻訳ファイル情報に基づき、表示するコンテンツを切り替え表示する部分(赤枠部分)

/pages/index.js
/pages/index.js

言語切り替え機能のコーディング

ドロップダウンメニュー設定

/components/Header.js
/components/Header.js

上記ドロップダウンメニューで指定された値をもとに、localeが切り替わる。(以下のコード赤枠部分)

"https://example.com/"
"https://example.com/en-US"
"https://example.com/zh-CN"

/components/Header.js
/components/Header.js

2)CMS多言語連携設定(Strapi-HeadlessCMSとの連携)

前のセクションの内容は、メニュー情報等、静的なデータの言語切り替え対応となります。このセクションでは、CMSと連携した動的データの言語切り替えの実装について紹介します。
まず、CMS側の設定について紹介します。今回は多言語対応機能が標準で実装されているStapi(V3.6)を使用しました。

Strapi(CMS)の設定

・多言語基本設定
設定->Internationalizationの画面で、使用したい言語とデフォルト言語を指定します。

Strapi多言語設定画面
Strapi多言語設定画面

・コンテントタイプの設定
タイトル、内容等のフィールド毎に多言語設定を使用するか、デフォルト言語のみの対応とするか等きめ細かく設定することができます。

Content-TypeBuilder 、該当するコンテントタイプの設定から、表示されたダイアログボックスで「高度な設定」タブをクリック。最後に「Enable localization for this field」をチェックします。

さらに、多言語に設定したいフィールドをクリック。表示されたダイアログボックスで「高度な設定」タブをクリック。最後に「Enable localization for this field」をチェックします。

Content-TypeBuilderからコンテントタイプとフィールドの多言語設定
Content-TypeBuilderからコンテントタイプとフィールドの多言語設定

コンテンツを追加します。(各言語ごとにコンテンツを作成します)

日本語コンテンツの入力画面
日本語コンテンツの入力画面

中国語と英語の入力画面
中国語と英語の入力画面

フロントエンド側の実装(Next.jsとStrapiAPIとの連携)

今回の実装で一番難しかった部分になります。

一般的なイメージとして、APIのレスポンスとして以下のような構成をイメージしていました。
*記事のidが同じで、localeのパラメータが各言語ごとに異なるエンドポイントから記事に関するデータを取得できると考えていました。

例)
pages/5
pages/5?locale=en-US
pages/5?locale=zh-CN

ところがStapiのAPIレスポンスは、現在表示されている言語のレスポンスデータのlocalizationsキー配下に、他の翻訳の情報について、言語ごと個別の記事IDが格納されていました。また、情報はIDのみでタイトルや記事内容は格納されていませんでした。

StrapiAPIからのレスポンスデータ
StrapiAPIからのレスポンスデータ

*2022/2/2 追記 URLパラメータによる、言語の切り替えはGoogle非推奨とのことです。Strapiの対応はGoogleの推奨に準拠したものになります。Google検索セントラル:多地域、多言語のサイトを管理する

したがって、別の言語の表示内容については、上記のレスポンスデータから、IDを一旦取得し、その取得したIDをもとに、API連携を改めて行い、記事のタイトルや記事内容のデータを取得する必要があります。

このような2段階の処理を行う為、通常の非同期処理(async/await)ではうまくデータが取得できない問題に突き当たりました。最終的にはasync/awaitとPromiseAllを組み合わせることで、うまくデータを取得することができました。

./page/index.tsx getServerSidePropsの一部
    const translationId = initial.map((item: NewsResponse) => {
      return item.localizations[localeNum].id
    })

    await Promise.all(
      translationId.map(async (item: number) => {
        const translationRes = await fetch(`${API_URL}/pages/${item}`)
        const translationData = await translationRes.json()
        translation = [...translation, translationData]

        return translation
      })
    )

他の言語の記事IDを取得して、そのIDをもとに記事データを取得するコード

WordPressの多言語対応との比較

WordPressの場合は、Bogoというプラグインがあり、こちらのプラグインを使うことで、今回の記事で説明したことと同様のことが実現できそうです。
https://ja.wordpress.org/plugins/bogo/

WordPress+プラグインと比較して、今回の、NextJS+HeadlessCMSで構築するメリットは、サイトの高速表示や拡張性にあると思います。例えば、バックエンドにHeadlessCMSに加えて、ShopifyのAPIとの連携も加えて、多言語EC機能も組み込んだマルチソースサイト構成が実現できます。

以上長々と書きましたが、以上になります、ご覧いただきありがとうございました。

参考情報

ソースコードはGitHubにて公開しています。
https://github.com/IoT-Arduino/nextjs-strapi-i18n-frontend

その他参考情報(英語)

Next.js i18n公式

Strapi i18n公式

Strapi i18nブログ

Internationalization (i18n) with Next.js!
*トップページのコンテンツの切り替えを言語メニュー切り替えからできる、ライブラリをつかわず、Localの翻訳定義を使っている。

Next.js + Strapi - #Internationalization (i18n)
*Strapiをつかって、ブログページの翻訳コンテンツ切り替えができる

Discussion