Tetto Obata

Tetto Obata

Data Scientist

Next.js × react-i18next で始める Web アプリの多言語対応

i18n

はじめに

データサイエンティストの小畑です。

MCD3 では、 Tachyon 生成AI をはじめ、様々な Web アプリの開発を行っております。非日本語話者にも Web アプリをご利用いただく場合、多言語化対応が一つの重要な課題となります。

本記事では特に Next.js を用いて開発されている Web アプリについて、 react-i18next を用いた多言語化対応について紹介します。


使用するライブラリについて

本記事では、以下のライブラリについて説明をします。

ライブラリ用途使用バージョン
react-i18next多言語化ライブラリである i18nextを React で扱うためのバインディング14.1.3
i18next-browser-languagedetectorブラウザの言語設定の検出、及びその結果の LocalStorage への保存8.0.0
i18next-parserコードを元にした辞書ファイルの自動生成9.0.1

多言語化に必要な設定ファイル群について

ディレクトリ構成の一例を示します。

frontend/
├── src/
│   ├── i18n
│   │   ├── locales
│   │   │   ├── en.json
│   │   │   └── ja.json
│   │   └── config.ts
│   ├── app
│   └── …
└── …

config.ts ファイルについて

config.ts ファイルの一例を示します。

import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import { initReactI18next } from "react-i18next";

import translationEn from "i18n/locales/en.json";
import translationJa from "i18n/locales/ja.json";
i18n
  .use(
    new LanguageDetector(null, {
      order: ['querystring', 'cookie', 'localStorage', 'sessionStorage', 'navigator', 'htmlTag'],
    }),
  )
  .use(initReactI18next)
  .init({
    fallbackLng: "en",
    returnEmptyString: false,
    resources: {
      en: {
        translation: translationEn,
      },
      ja: {
        translation: translationJa,
      },
    },
  });

export { i18n };

これを layout ファイルで import します。

...
import "i18n/config";
...

order

以下の言語検出方法について、使用する優先順位を付けることができます。(i18next-browser-languageDetector README より引用)

  • cookie (set cookie i18next=LANGUAGE)
  • sessionStorage (set key i18nextLng=LANGUAGE)
  • localStorage (set key i18nextLng=LANGUAGE)
  • navigator (set browser language)
  • querystring (append ?lng=LANGUAGE to URL)
  • htmlTag (add html language tag <html lang="LANGUAGE” …)
  • path (http://my.site.com/LANGUAGE/…)
  • subdomain (http://LANGUAGE.site.com/…)
  • hash (append #lng=LANGUAGE or #/LANGUAGE to URL)

デフォルトの設定では、一度設定された言語情報は localStorage に保存されます。

fallbackLng

LanguageDetector による言語検出に失敗した場合に使用される言語を指定できます。

returnEmptyString

false の場合、後に記載する辞書ファイルにて value が空文字列であった場合に、key の値がそのまま value として使用されます。 value の値を設定し忘れた場合に気付きやすくなる為、 false にすることを推奨します。

その他

i18next-browser-languageDetector のオプションについては READMEを、 react-i18next のオプションについてはi18next documentationをご参照ください。

辞書ファイルについて

ja.json, en.json の一例を示します。

{
  "message": {
    "loadingFiles": "{{fileNames}}を読み込み中…",
  },
  "UserButton": {
    "language": "言語",
    "logout": "ログアウト"
  }
}
{
  "message": {
    "loadingFiles": "Loading {{fileNames}}...",
  },
  "UserButton": {
    "language": "Language",
    "logout": "Logout"
  }
}

上記のように適当にネストさせることで、コンポーネント毎に要素をグルーピングするような定義が可能です。 また、modal.loadingFiles に示すように、適当な文字列をコード内で与えて、それを埋め込むことも可能です。

尚、後に紹介する i18next-parser にて雛形は自動生成される為、 value 以外の部分を書く必要はありません。

多言語化の記法について

react-i18next が提供する useTranslation フックから t関数 を取得し、翻訳キーを渡します。

import { Button } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';

export const UserButton = () => {
  const { t } = useTranslation();

  const handleLogout = () => {};

  return (
    <Button onClick={handleLogout}>
      {t('UserButton.logout')}
    </Button>
  );
};

また、次に示すように、適当な文字列を渡し、それを埋め込むようにして翻訳処理を行うことも可能です。

<Text>
  {t("message.loadingFiles", {
    fileNames: "sample1, sample2, sample3",
  })}
</Text>

この場合、日本語における表示は以下の通りとなります。

sample1, sample2, sample3を読み込み中…

言語設定の切り替えについて

useTranslation フックから i18n オブジェクトを取得し、i18n.changeLanguageを利用することで、言語設定を切り替えることが出来ます。

const languageMenuItems: Array<{
  value: string;
  label: string;
}> = [
  {
    value: "ja",
    label: "日本語",
  },
  {
    value: "en",
    label: "English",
  },
];
import { FC } from "react";
import { languageMenuItems } from "@/languages";
import { Select } from "@chakra-ui/react";

export const ChangeLanguage: FC<{
  selectDefaultLanguage: string;
}> = ({ selectDefaultLanguage }) => {
  const { i18n } = useTranslation();
  const onChangeLanguage = (language: string) => {
    i18n.changeLanguage(language);
  };

  return (
    <Select
      defaultValue={selectDefaultLanguage}
      onChange={(e) => onChangeLanguage(e.target.value)}
    >
      {languageMenuItems.map(({ value, label }) => (
        <option key={value} value={value}>
          {label}
        </option>
      ))}
    </Select>
  );
};

i18next-parser について

i18next-parser を用いることで、以下を行うことが出来ます。

  • コード内に存在する t 関数で使用されている key の集合を key に持つ辞書ファイルの生成
    • 新規追加された key に対応する要素の追加
    • 削除された key に対応する要素の削除
  • コード内に存在する t 関数で使用されている key の集合と、辞書ファイル内に存在する key の集合が一致していることのチェック

更に、本チェックを CI に導入することで、辞書ファイルの key の追加・削除忘れを防ぐことができます。

i18next-parser.config.js ファイルについて

新しく、 i18next-parser.config.js ファイルを配置します。

frontend/
├── src/
│   ├── i18n
│   │   ├── locales
│   │   │   ├── en.json
│   │   │   └── ja.json
│   │   └── config.ts
│   ├── app
│   └── …
├── i18next-parser.config.js
└── …

i18next-parser.config.js の一例を示します。

module.exports = {
  locales: ["ja", "en"],
  sort: true,
  createOldCatalogs: false,
  output: "i18n/locales/$LOCALE.json",
};

locales, output は、 config.ts やディレクトリ構成に合わせてください。

sort

true にすると、辞書ファイルの key が辞書式順序でソートされます。

createOldCatalogs

true の場合、削除された key の情報が別ファイルにエクスポートされます。

その他

その他のオプションについては、READMEをご参照ください。

i18next-parser を用いた、辞書ファイルの自動生成について

以下のコマンドにより、辞書ファイルの自動生成を行うことが出来ます。(対象ファイルは一例です。ディレクトリ構成に合わせて、適切に設定してください。)

i18next 'src/**/*.{tsx,ts}'

i18next-parser を用いた、辞書ファイルのチェックについて

以下のコマンドにより、辞書ファイルの key の集合が、コード内に現れる t 関数の key の集合と一致しているか、確かめることが出来ます。(対象ファイルは一例です。ディレクトリ構成に合わせて、適切に設定してください。)

i18next 'src/**/*.{tsx,ts}' --fail-on-update

react-i18next の Storybook 対応について 1

Storybook とは、様々なコンポーネントを独立してチェックする「UIカタログ」を作成するライブラリです。 適切に設定を行うことで、 Storybook にて他言語でのコンポーネントの表示を確認することが出来ます。

preview.tsx を適切に編集することで、ツールバーから locale を設定できるようにし、それに基づいて言語設定を行います。


export const globalTypes = {
  locale: {
    name: "Locale",
    description: "Internationalization locale",
    toolbar: {
      icon: "globe",
      items: [
        { value: "ja", title: "日本語" },
        { value: "en", title: "English" },
      ],
      showName: true,
    },
  },
};

...

const i18nextDecorator = (Story, context) => {
  const { locale } = context.globals;

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

  return (
    <Suspense fallback={<div>loading translations...</div>}>
      <I18nextProvider i18n={i18n}>
        <Story />
      </I18nextProvider>
    </Suspense>
  );
};

...

const preview: Preview = {
  ...

  decorators: [i18nextDecorator] // i18nextDecorator を追加する

  ...
}

以上の設定により、Storybook 内のツールバーから言語設定を行うことができます。 Storybook

まとめ

本記事では、 react-i18next, i18next-browser-languagedetector, i18next-parser を用いて、 Next.js で開発された Web アプリを多言語化対応する方法について解説しました。 本記事の内容が皆様の助けになれば幸いです。

MCD3 では、技術力向上のためのイベントや勉強会なども定期的に実施しています。 もし MCD3 で働くことに興味を持っていただいた方がいらっしゃいましたら、カジュアル面談も受け付けておりますので、お気軽にお声掛けください! 採用情報や面談申込みはこちらから

参考文献


  1. Storybookでnext-i18next対応

RSS

Tags

Next

Tatsuya Sumiya

Tatsuya Sumiya

長時間処理を実行するための Streamlit アプリ設計

はじめに データサイエンティストの住谷です。 エムシーデジタルでは、数理最適化を活用したプロジェクトに取り組んでおり、これまでもテックブログで最適化のアルゴリズム開発に関する記事をいく

  • #TechBlog
  • #Python