🌏

react-i18nextを使って、React環境に1から多言語翻訳の仕組みを構築してみる

2023/12/18に公開

こんにちは!アルダグラムでエンジニアをしている柴田です。

本記事は株式会社アルダグラム Advent Calendar 2023 18日目の記事です。

はじめに

直近2週間ほど前に、英語とタイ語用のランディングページ(LP)を作成する機会がありました。その際に英語・タイ語への翻訳の仕組みを1から構築しました。
今回は自身の備忘録も兼ねて、react-i18next を使って、多言語翻訳の仕組みを1から構築する手順をご紹介したいと思います。

react-i18next

react-i18nextは、Reactアプリケーションの多言語対応を柔軟かつ簡単に実現するためのライブラリです。react-i18nextの特徴としては、t関数を使うことで、サクッと変換できることが特徴です。今回はこちらのライブラリを使用します。

https://react.i18next.com/

使い方

具体的な設定を記述する前に、react-i18nextの使い方を軽くご説明します。

翻訳の仕組み

翻訳対象の指定はt関数を使って、t('key') のように指定します。この場合、keyは日本語でも英語、他の言語でも問題ないです。

import React, { useState, useEffect, FC } from 'react'
import { useTranslation } from 'react-i18next'

const App: FC = () => {
  const { t } = useTranslation()

  return (
    <div>
      <h1>{t('こんにちは!')}</h1>
    </div>
  )
}

言語切り替え

i18n.changeLanguage(lang)を使うと、指定した言語に切り替えることができます。

import React, { useState, useEffect, FC } from 'react'
import { useTranslation } from 'react-i18next'

const App: FC = () => {
  const { t, i18n } = useTranslation()

  return (
    <div>
       {/* 英語の場合は、"Hello"に切り替わる */}
      <h1>{t('こんにちは!')}</h1>
      <div>選択言語:{i18n.language === 'en' ? 'English' : '日本語'}</div>
      <button
        type='button'
        title='change language'
        onClick={() => i18n.changeLanguage('en')}
      >
        英語
      </button>
      <button
        type='button'
        title='change language'
        onClick={() => i18n.changeLanguage('ja')}
      >
        日本語
      </button>
    </div>
  )
}

change_language


セットアップ

react-i18nextと関連ライブラリをインストールします。
インストールするライブラリは下記の通りです。

  • react-i18next
  • i18next : 辞書設定の初期化に必要なパッケージです
  • i18next-browser-languagedetector: ユーザーのブラウザで利用可能な言語を検出し、i18next がその情報を使用して適切な言語の翻訳を提供するライブラリです。
npm i --save react-i18next i18next i18next-browser-languagedetector 

辞書設定

i18nextの辞書設定を書いていきましょう。
まず最初に、i18nextを利用して、辞書設定の初期化の設定を記述します。

i18nディレクトリを作成し、その直下にconfigファイルを作成します。
作成したconfigファイルに下記を記述します。

// i18n/config.ts
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'

i18n
  .use(initReactI18next)
  .use(LanguageDetector) // ユーザーの言語設定を検知するために必要
  .init({
    fallbackLng: 'ja', // デフォルトの言語を設定
    returnEmptyString: false, // 空文字での定義を許可に
    resources: { 
      // 辞書情報
      // 用意した翻訳ファイルを読み込む
      en: {
        translation: require('./locales/en.json')
      },
      ja: {
        translation: require('./locales/ja.json')
      }
    },
    interpolation: {
           // 翻訳された文字列内のHTMLやReactコンポーネントをエスケープすることを無効に
      escapeValue: false
    },
    react: {
      // 指定したHTMLノードを翻訳時にそのまま保持して表示するための設定
      transKeepBasicHtmlNodesFor: ['br', 'strong', 'i', 'span']
    }
  })

export default i18n

辞書ファイルの指定

辞書ファイルは外部JSONファイルで管理することにします。
理由としては、テキストやメッセージの文言の翻訳の変換情報をアプリケーションから切り離すことができ、管理しやすくするためです。

辞書ファイルを作成

日本語と英語のjsonファイルを作成します。
翻訳キーの生成は後述で対応するため、現時点でのファイルの中身は空Objectで問題ないです。

  • 英語
// ./i18n/locales/en.json
{}
  • 日本語
// ./i18n/locales/ja.json
{}

翻訳キーを自動で抽出するための設定

t関数で指定されている翻訳キーを抽出するためにbabel-plugin-i18next-extractのプラグインを使用します。babel-plugin-i18next-extract と必要な関連プラグインをインストールします

npm i --save-dev babel-plugin-i18next-extract @babel/cli @babel/preset-react @babel/preset-typescript

babel.config.json

Componentで定義した翻訳キーを抽出し、外部 JSONファイルへ書き出す機能があります。
こちらの機能を利用するために、必要な情報を記述します。

i18nディレクトリ配下にbabel.config.jsonを作成します。

  • locales: 外部JSONに書き出す言語の定義
  • outputPath: 抽出された翻訳キーを書き出すファイルの指定
  • discardOldKeys: ビルド時に既存のキー(メッセージ)が見つからない場合に、それらを破棄するかどうかのフラグ
  • keySeparator
    • キーを分割するために使用したい文字列の設定方法です。
      defaultは'.'です。
      キーを分割したくない場合やキーをそのまま値として使用したい場合は、nullに設定します。
    • 英文を翻訳キーにしているときに有効な設定です。
      行末のカンマで翻訳キーが分割されることを防ぎます。
// i18n/babel.config.json
{
  "presets": ["@babel/preset-typescript", "@babel/preset-react"],
  "plugins": [
    [
      "i18next-extract",
      {
        "locales": ["en", "ja"],
        "outputPath": "./i18n/locales/{{locale}}.json",
        "discardOldKeys": false,
        "keySeparator": null. // defaultは'.'
      }
    ]
  ]
}

翻訳キー生成のスクリプトをpackage.jsonに追加します。

スクリプトの内容としては、babel --config-file i18next-extractのconfigファイルのパス 抽出対象のファイルのパス になります。

// package.json
{
  "scripts": {
    ...
    // ↓追加する
    "i18next-extract": "babel --config-file ./i18n/babel.config.json './src/**/*.{js,jsx,ts,tsx}'"
  },
}

Gatsbyの場合の追加設定

React環境にGatsbyを導入しているプロジェクトもあるかと思います。
Gatsbyの場合は、別途追加で下記の設定が必要になります。

  1. ルートディレクトリ直下にgatsby-wrapper.jsgatsby-browser.jsgatsby-ssr.jsを作成します。

  2. gatsby-wrapper.jsにて、i18nextProviderを記述します

    // gatsby-wrapper.js
    import React from 'react'
    import { I18nextProvider } from 'react-i18next'
    import i18n from './i18n/config'
    
    export const root = ({ element }) => (
      <>
        <I18nextProvider i18n={i18n}>{element}</I18nextProvider>
      </>
    )
    
  3. gatsby-wrapper.jsで定義したrootをgatsby-browser.jsgatsby-ssr.jsそれぞれでexportします。

    const React = require('react')
    const { root } = require('./gatsby-wrapper')
    
    exports.wrapRootElement = root
    
    • wrapRootElementについて
      • Gatsby ページ全体をラップする React 要素を追加できる Gatsby の API フックです。
        Gatsby ページがレンダリングされる際にラップする要素を指定のために使用します。
      • 今回はi18nextProviderをラップ要素として指定しました。他にもRedux ストアのプロバイダーやuiライブラリのプロバイダーなどもラップ要素として指定できます。
      • 参考
        https://www.gatsbyjs.com/docs/reference/config-files/gatsby-browser/#wrapRootElement

最終的なディレクトリ構成

今までに作成したフォルダ・ファイルの構成としては下記に通りになります。


├── i18n
│   ├── babel.config.json
│   ├── config.ts
│   └── locales
│       ├── en.json
│       └── ja.json
...
 // (Gatsbyを採用している場合は追加で)
├── gatsby-browser.js
├── gatsby-ssr.js
├── gatsby-wrapper.js

Componentを作成

翻訳されているかを確認するために、Componentを作成します。

import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'

const App: FC = () => {
  const { t, i18n } = useTranslation()

  return (
    <div>
      <h1>{t('こんにちは!')}</h1>
      <p>{t('早起きは三文の徳')}</p>
      <div>{i18n.language === 'en' ? '英語' : '日本語'}</div>
      <button
        type='button'
        title='change to English.'
        onClick={() => i18n.changeLanguage('en')}
      >
        英語
      </button>
      <button
        type='button'
        title='Change to Japanese'
        onClick={() => i18n.changeLanguage('ja')}
      >
        日本語
      </button>
    </div>
  )
}

export default App

翻訳キーを生成

コマンドを実行して、翻訳キーを生成します

npm run i18next-extract

実行すると、作成したen.jsonファイル、ja.jsonファイルそれぞれにキーが追加されていることが確認できます。翻訳ファイルに未定義であった翻訳キーの場合は、keyのみ値を追加され、valueは空になります。

// ./i18n/locales/en.json
{
  "こんにちは!": "",
  "早起きは三文の徳": ""
}
// ./i18n/locales/ja.json
{
  "こんにちは!": "",
  "早起きは三文の徳": ""
}

未定義の翻訳キーに翻訳を追加して、再度ローカルを起動して、確認してみます。

// ./i18n/locales/en.json
{
  "こんにちは!": "Hello!",
  "早起きは三文の徳": "The early bird catches the worm."
}
// ./i18n/locales/ja.json
{
  "こんにちは!": "こんにちは!",
  "早起きは三文の徳": "早起きは三文の徳"
}

実際に確認

無事翻訳されていることが確認できましたね。言語切り替えも問題なく、動いてますね。

finish_complete.gif

最後に

翻訳仕組みを1から導入する手順をご紹介しました。
react-i18nextを利用することで簡単に多言語対応を実現することができ、翻訳対応は後追いで対応とかもできたりします。多言語対応を一気に進めて、グローバル化を推し進めていきたいですね。


もっとアルダグラムエンジニア組織を知りたい人、ぜひ下記の情報をチェックしてみてください!

アルダグラム Tech Blog

Discussion