React で i18next を使った国際化対応したアプリを開発していて、言語切り替えボタンを押しても言語が切り替わらない問題にハマったのでメモ
環境
- i18next
23.4.1
- react-i18next
13.0.3
- react
18.2.0
- typescript
5.0.2
setup i18next
$ npm install i18next $ npm install react-i18next
ユーザーの環境からいい感じに言語設定を取得する i18next-browser-languagedetector
というパッケージもあるが今回の例では使用しない
i18next config
i18next を使う設定ファイルを作成する
/src/i18n/config.ts
import i18n from "i18next"; import { initReactI18next } from 'react-i18next'; i18n .use(initReactI18next) .init({ debug: true, // for development fallbackLng: 'en', resources: { en: { translation: { "Learn": "Learn i18next + React" }, }, ja: { translation: { "Learn": "i18next + React を学ぶ" }, }, }, });
cf.
- Caching - i18next documentation
- Best React Localization - Internationalize with i18next (a complete guide to react-i18next)
設定ファイル React のアプリで読み込む
/src/main.tsx
import './i18n/config';
※ App.tsx
など開発中にホットリロードされるコンポーネントで i18next の設定ファイルをインポートしていると i18next: init: i18next is already initialized. You should call init just once!
という警告が発生するので変更が入らないファイルでインポートするのが良さそう
🐞 i18next から import した t
では changeLanguage
で言語を切り替えてもテキストが切り替わらない
import i18next, { changeLanguage, t } from 'i18next'; const langs = { en: { nativeName: 'English' }, ja: { nativeName: '日本語' }, } as const; function App() { return ( <div> <h1>{t('Learn')}</h1> <div> {Object.keys(langs).map((lang) => ( <button key={lang} onClick={() => changeLanguage(lang)} disabled={i18next.language === lang} > {langs[lang as typeof keyof langs].nativeName} </button> ))} </div> </div> ); } export default App;
※ Object.keys(langs).map
内のの箇所で型エラーが発生するが、長くなるので本エントリーでは割愛し as keyof typeof langs
で対応
この App コンポーネントではボタンを押した際に changeLanguage(lang)
で言語が変更されるが t('Learn')
で表示されるテキストは切り替わらない。useState
などで state を持たせると state が変更されコンポーネントが再レンダリングされると t('Learn')
のテキストも変更される。
つまり i18next
から直接 import した t
は再レンダリングをしない。(state や props が変更されるわけではないのでそれはそう…)
i18next-react
の useTranslation
hook を使う
import { useTranslation } from 'react-i18next'; function App() { const {t, i18n: {changeLanguage, resolvedLanguage} } = useTranslation(); return ( <div> <h1>{t('Learn')}</h1> <div> {Object.keys(langs).map((lang) => ( <button key={lang} onClick={() => changeLanguage(lang)} disabled={resolvedLanguage === lang} > {langs[lang as typeof keyof langs].nativeName} </button> ))} </div> </div> ); } export default App;
useTranslation()
から取得した t
を利用すると changeLanguage()
で言語が切り替えられた際にコンポーネントが再レンダリングされテキストが切り替わるようになる
※ useTranslation()
で取得できる i18n
は内部的に使われている i18next
のオブジェクト
設定で i18n.use(initReactI18next)
していたらよしなにやってくれるんだろうと思いこんでてハマりました。ドキュメントのデザインが似ていて気づかなかったけど i18next-react
のドキュメントの方にちゃんと書いてあった…
思い込みはエンジニアの敵!!!!反省!!!!!!!
おわり
[参考]