【React】react-i18nextでメッセージ文言を管理する

## はじめに
こんにちは。
画面のヘッダーやラベル、プルダウンメニュー項目など様々な箇所で使うメッセージ文言、皆さんはどのように管理していますでしょうか？
ここ最近 `react-i18next`を使ってみて便利に感じたので、今回は`react-i18next`を使ってメッセージ文言管理を行うやり方を紹介したいと思います。

今回のサンプルは [こちら](https://github.com/mytysoldier/portfolios/tree/main/web-apps/sale_notification/frontend) にあげています。


## react-i18nextとは

https://react.i18next.com/

>react-i18next is a powerful internationalization framework for React / React Native which is based on i18next.
>
>react-i18nextは、i18nextを基盤とした、React/React Native向けの強力な国際化フレームワークです。(Geminiで和訳)

多言語対応のフレームワークになります。本来の使い方とはズレますが、メッセージ文言管理にも活用できるので、以下でコード例を見ていきたいと思います。

## 実行環境について
以下の環境で試しています。
- react@18.3.1
- next@14.2.15


## 事前準備
react-i18nextをプロジェクトに導入。
```shell
npm install react-i18next i18next --save
```


## react-i18nextを使った実装サンプル
それではいくつかファイルを準備して実行をしていきます。

### メッセージ管理用ファイル

メッセージ管理用のファイルにメッセージをまとめます。

```json:ja.json
{
  "header": {
    "title": {
      "list": "セール一覧",
      "regist": "セール登録",
      "detail": "セール詳細"
    }
  },
  "form": {
    "button": {
      "addSale": "セール追加",
      "search": "検索",
      "regist": "登録",
      "update": "更新",
      "back": "戻る"
    },
    "label": {
      "saleName": "セール名",
      "saleStatus": "ステータス",
      "itemCategory": "商品カテゴリ",
      "startDate": "開始日",
      "endDate": "終了日"
    },
    "selectbox": {
      "itemCategory": {
        "digitalEquipment": "デジタル機器",
        "sportsOutdoors": "スポーツ用品",
        "books": "本",
        "dailyNecessities": "日用品",
        "kitchenItems": "キッチン用品",
        "foodBeverageAlcohol": "食品・飲料・アルコール",
        "cosmeticsAndBeautySupplies": "化粧品・美容用品"
      }
    }
  }
}
```


### react-i18nextの初期化設定

続いて上記メッセージファイルを読み込む設定を追加します。

```typescript:configs.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";

import translation_ja from "./ja.json";

const resources = {
  ja: {
    translation: translation_ja,
  },
};

i18n
  .use(initReactI18next) // passes i18n down to react-i18next
  .init({
    resources,
    lng: "ja",
    interpolation: {
      escapeValue: false, // react already safes from xss
    },
  });

export default i18n;
```

- `translation` 内に「キー：メッセージ」のJSON形式でメッセージ定義できます
- メッセージ定義はソースコードと分けたほうが使い勝手良いので、ここではJSON形式で定義した `ja.json` を `import` する形を取っています

### プロジェクトにインポートする

先ほどの `configs.ts` を読み込み、各コンポーネントでメッセージ文言を扱えるようにします。
ここではappルート直下の `layout.tsx` で読み込みを行っています。

```typescript:app/layout.tsx
"use client";
// import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
import { Header } from "@/components/Header";
import "../i18n/configs"; // <-これを追加
・・・（略）
```

- import を追加しただけですが、これで配下のコンポーネントでメッセージを扱うことができるようになります


### メッセージを表示する

```typescript:SaleListForm.tsx
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; // <-これを追加
import { Form, formSchema } from "./yupForm";
import { yupResolver } from "@hookform/resolvers/yup";
import { InputControl } from "@/components/form/input/InputControl";
import { SelectBoxControl } from "@/components/form/selectbox/SelectBoxControl";
import { CustomDatePickerControl } from "@/components/form/datepicker/CustomDatePickerControl";
import { Button, ButtonType } from "@/components/Button";

export const SaleListForm = () => {
  const { t } = useTranslation(); // <-これを追加

  const methods = useForm<Form>({
    mode: "onBlur",
    defaultValues: {
      saleName: "",
    },
    resolver: yupResolver(formSchema),
  });

  const onSubmit = (data: Form) => console.log(JSON.stringify(data));

  return (
    <FormProvider {...methods}>
      <form onSubmit={methods.handleSubmit(onSubmit)} noValidate>
        <div className="flex items-center gap-4 mb-4 px-16">
          <label htmlFor="saleName" className="w-32">
            {t("form.label.saleName")} {/* <-これを追加 */}
          </label>
          <InputControl<Form>
            fieldName="saleName"
            className="text-black flex-1 border h-10 rounded leading-10"
          />
        </div>
・・・（略）
```

- `useTranslation` フックで取得される `t` ファンクションを経由して、i18next の翻訳コンテンツにアクセスできます
- 「t({対象メッセージ文言のキー})」の形式でメッセージを読み込むことができます

これを実行すると以下のように読み込んだメッセージを表示されます。

![スクリーンショット 2024-12-15 18.49.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519408/d6d16fd7-57ad-66b2-d828-f550d449d6d8.png)


## 終わりに
これまでメッセージ文言をソースコードに直書きしたりしていましたが（これで辛みを見たことが何度もあって・・）、一見管理できていると修正の負担も大分軽減できて嬉しい、積極的に活用していきたいと思いました。
それでは〜