（React + TypeScript）UseContextとi18nextを使ってi18n対応してみる

下の記事に感化されて、i18n対応をやってみました。

[日本語で開発しつつ、いつでも多言語対応できる環境を react-i18next で維持する](https://qiita.com/suzukalight/items/54860fdda35e6ce983d9)

i18n対応の部分は上の記事を見ていただく（辞書ファイルの自動生成とかもありますが、この記事ではスキップしてるので是非読んでください）として、useContextを利用してグローバルstateに言語情報を持たせて、パッと切り替えられる様にしました。

### トップコンポーネント
まずは大元のApp.tsxにstateを持たせます。

```tsx:App.tsx
import React, { FC, useEffect, createContext, useReducer } from 'react';
import { appReducer, StateType, ActionType } from './reducer'
import { useTranslation, initReactI18next } from 'react-i18next';
import i18n from 'i18next';
import enJson from './locales/en.json';
import jaJson from './locales/ja.json';
import MainContents from './View/MainContents';

type ContextType = {
  state: StateType;
  dispatch: React.Dispatch<ActionType>;
};

// 初期ステートを作成
const initialState = { lang: 'ja' };

// コンテキストを作成する
export const AppContext = createContext({} as ContextType);

//i18nextの初期化
i18n.use(initReactI18next).init({
  debug: true,
  resources: {
    en: { translation: enJson },
    ja: { translation: jaJson },
  },
  lng: 'ja',
  fallbackLng: false,
  returnEmptyString: false, 
});

const App: FC = () => {
  const { i18n } = useTranslation();
  const [state, dispatch] = useReducer(appReducer, initialState);

  useEffect(() => {
    i18n.changeLanguage(state.lang);
  }, [i18n, state.lang]);

  return (
      <AppContext.Provider value={{ state, dispatch }}>
        <MainContents />
      </AppContext.Provider>
  );
};
```

### Reducer
Reducerは下記の様な感じです。

```ts:reducer.ts
export const CHANGE_LANG = 'CHANGE_LANG' as const;

export type StateType = {
  lang: string;
};

export const changeLang = (lang: string) => ({
  type: CHANGE_LANG,
  payload: { lang },
});

export type ActionType = ReturnType<typeof changeLang>;

export const appReducer = (state: StateType, action: ActionType) => {
  switch (action.type) {
    case CHANGE_LANG:
      return { ...state, lang: action.payload.lang };
    default:
      return state;
  }
};
```
この辺のreducerの書き方は[こちらの記事](https://qiita.com/taqm/items/4d6c5c12296fd1300086)を参考にさせていただきました。

### 言語切り替えコンポーネント
```tsx:SubSub.tsx
import React, { useContext } from 'react'
import { AppContext } from '../App';
import { CHANGE_LANG } from '../reducer';

export default const SubSub = () {
  const { state, dispatch } = useContext(AppContext);
  
  return (
    <button
      onClick={() =>
        dispatch({
          type: CHANGE_LANG,
          payload: { lang: state.lang == 'ja' ? 'en' : 'ja' },
        })
      }
    >
     言語切り替え
   </button>
  );
}
```

※SubSub.tsxはMain.tsxの孫くらいのコンポーネントだとしてください。Main.tsx配下の子孫コンポーネントであればなんでも良いです。

言語切り替えのところは3カ国以上なら適宜別のロジックでstateを変更してください。

### 言語が切り替わるコンポーネント
下記は実際に言語が切り替わるコンポーネントです。

```tsx:AnotherSubSub.tsx
import React from 'react';
import { useTranslation } from 'react-i18next'

export default const AnotherSubSub = () => {
  const { t } = useTranslation();

  return (
    <p>
      {t('ここの言語が切り替わります')}
    </p>
  );
}

```
こちらもApp以下の子孫コンポーネントでしたら、どこでも構いません。
`ここの言語が切り替わります`というのをキーとしてstateにセットしたlocaleに合わせて日本語、英語が切り替えます。

### 辞書設定

```json:ja.json
{
  "ここの言語が切り替わります": "ここの言語が切り替わります"
}
```
```json:en.json
{
  "ここの言語が切り替わります": "The language will change right here"
}
```
これで言語切り替え設定が完了しました。

### まとめ
あとは言語の切り替えが発生するコンポーネントに上記の様な設定をすれば、言語切り替えボタンを押した時に全てのコンポーネントに言語切り替えが設定されます。

ただ、initReactI18nextを初期化する時に全ての辞書ファイルを読み込むので、辞書ファイルが非常に大きい場合はパフォーマンスが落ちるかもしれません（この辺は未検証）。

また、初期化時に常に日本語で表示しているため、そこは適宜初期の言語設定を加えてください。（アメリカからアクセスあった場合は初期設定で英語にする等）

（日本語をキーにするのは便利だなぁ）