🐕

Reactの多言語対応を効率化するための辞書管理ベストプラクティス

2024/08/28に公開

※言語ごとのオブジェクト(json)のことをこの記事では辞書と呼びます。

はじめに

Reactでreact-i18nextを使用して多言語対応をしています。
そこで使用している辞書運用のベストプラクティスを模索しました。
導入時の初期設定などは省きます🙇‍♂

よくある実装(工夫前)

例えば、日本語と英語対応したいと思ったときに、以下のような実装がよくあると思います。

ja.json 日本語の辞書
{
  "profile": {
    "name": "名前",
    "email": "メールアドレス",
    "password": "パスワード"
  },
  "confirmPassword": "パスワード確認",
  "login": "ログイン",
  "logout": "ログアウト",
  "register": "登録",
  "forgotPassword": "パスワードを忘れた",
  "resetPassword": "パスワードをリセット",
  "changePassword": "パスワードを変更"
}
en.json 英語の辞書
{
  "profile": {
    "name": "名前",
    "email": "メールアドレス",
    "password": "パスワード"
  },
  "confirmPassword": "パスワード確認",
  "login": "ログイン",
  "logout": "ログアウト",
  "register": "登録",
  "forgotPassword": "パスワードを忘れた",
  "resetPassword": "パスワードをリセット",
  "changePassword": "パスワードを変更"
}

使用する側でのtsx

App.tsx
<p>{t(‘profile.name’)}</p>
<p>{t(‘profile.email’)}</p>
<p>{t(‘profile.login’)}</p>

改善点

この実装で改善したいと思った点は以下です。

  • key名を考えるのが手間
    各要素に対応するkey名を毎回考えるのは手間がかかり、ミスの原因になることもある。

  • tsxで表示内容がイメージしにくい
    コードを見ただけでは、表示される内容が直感的にわかりにくい。

  • 型がないため、タイポに気づかない
    型がついていないと、文字列の誤りに気づきにくく、バグの原因になりやすい。

改善してみる

上記の課題を解決するために、①辞書のデータ形式を変える②辞書に型をつけるの2点の対応をしました。

①辞書のデータ形式

データ形式をフラットにして、日本語をkey名にしました。
②で辞書に型をつける都合上、TypeScriptで定義し直しています。

en.ts 英語の辞書
export const en = {
  "名前": "name",
  "メールアドレス": "email",
  "パスワード": "password",
  "パスワード確認": "confirmPassword",
  "ログイン": "login",
  "ログアウト": "logout",
  "登録": "register",
  "パスワードを忘れた": "forgotPassword",
  "パスワードをリセット": "resetPassword",
  "パスワードを変更": "changePassword"
}
ja.ts 日本語の辞書
export const ja = {
  "名前": "名前",
  "メールアドレス": "メールアドレス",
  "パスワード": "パスワード",
  "パスワード確認": "パスワード確認",
  "ログイン": "ログイン",
  "ログアウト": "ログアウト",
  "登録": "登録",
  "パスワードを忘れた": "パスワードを忘れた",
  "パスワードをリセット": "パスワードをリセット",
  "パスワードを変更": "パスワードを変更"
}

使用する側では以下のようになります

App.tsx
<p>{t(‘名前’)}</p>
<p>{t(‘メールアドレス’)}</p>
<p>{t(‘ログイン’)}</p>

これにより、以下の課題を解決したと思います。

  • key名を考えるのが手間
  • tsxで表示内容がイメージしにくい

②辞書に型をつける

I18nextにアンビエント宣言をして、型定義を拡張することにより、実現しました。
アンビエント宣言について説明は省きますが、i18nextに対して、型定義をするイメージです。

i18-next.d.ts
import { ja } from '@/assets/lang/ja';
import 'i18next';

declare module 'i18next' {
  interface CustomTypeOptions {
    resources: {
      translation: typeof ja;
    };
  }
}

これで型補完が出るようになり、間違えていた場合エラーが起こるので、以下の課題を解決しました。

  • 型がないため、タイポに気づかない

keyに型をつける

これらの実装では、jaで定義したkeyをenで定義し忘れたときに、エラーが出ません。
なので、jaを基準にしてkeyに型を付けたいと思います。

以下のように定義して、enとi18-next.d.tsのtranslationに型定義したいと思います。

export type TranslationKeys = keyof typeof ja;

これらをファイルに適用すると、以下のようになります。

i18-next.d.ts
import { ja } from '@/assets/lang/ja';
import 'i18next';

export type TranslationKeys = keyof typeof ja;

declare module 'i18next' {
  interface CustomTypeOptions {
    resources: {
      translation: Record<TranslationKeys, string>
    };
  }
}
en.ts 英語の辞書
import { TranslationKeys } from '@/types/i18next';

export const en: Record<TranslationKeys, string> = {
  "名前": "name",
  "メールアドレス": "email",
  "パスワード": "password",
  "パスワード確認": "confirmPassword",
  "ログイン": "login",
  "ログアウト": "logout",
  "登録": "register",
  "パスワードを忘れた": "forgotPassword",
  "パスワードをリセット": "resetPassword",
  "パスワードを変更": "changePassword"
}
ja.ts 日本語の辞書
export const ja = {
  "名前": "名前",
  "メールアドレス": "メールアドレス",
  "パスワード": "パスワード",
  "パスワード確認": "パスワード確認",
  "ログイン": "ログイン",
  "ログアウト": "ログアウト",
  "登録": "登録",
  "パスワードを忘れた": "パスワードを忘れた",
  "パスワードをリセット": "パスワードをリセット",
  "パスワードを変更": "パスワードを変更"
}

これで、jaで定義したけどenで定義し忘れるということがなくなると思います。

最後に

今回の対応により、通常の日本語での実装にかなり近づき、開発に集中しやすくなったと思います。
より効率的な方法があれば、ブラッシュアップしたいと思います。
(追記もしていきたいと思います)

エックスポイントワン技術ブログ

Discussion