Next.jsにおける i18n 対応において next-i18next から Rosetta へ移行した話
By kkoudev
Next.jsにおけるi18n対応についてです。
公式サイトを見ると i18n 対応についてはいくつかライブラリが紹介されていますが、
今回は私が最初に選定した、 isaachinman/next-i18next を使ったことによる失敗談と、
代わりに採用した lukeed/rosetta を選定した理由についてお話したいと思います。
next-i18next について
react-i18nextをNext.jsで使えるようにラップしたライブラリです。
これについては単純にGitHubのスターの数を見て採用を決めました。
ただし、Next.jsのDiscussionを見ているとこのライブラリはあまり評判が良くなく、
代わりのライブラリが紹介されているといった点が少し気になってはいました。
とはいえ、スターの多いライブラリだし、なんとかなるだろうと思ってそのまま採用をしました。
古い依存関係があったり、Storybookで使おうとするとcore-jsが必要になる
next-i18nextはreact-i18nextのラッパーライブラリで、
それを Next.js で使えるように対応したものです。
更に react-i18next は、 i18nextというReactのi18n対応ライブラリをラップしたもので、
何かと依存関係が多いライブラリです。
まずこの時点でちょっと面倒くさい感じはしつつも特に気にせず勧めましたが、
Storybookで自分が作っていた構成だと core-js がないと動かないようになっていました。
更にいうと自分がこのライブラリを採用した当時は core-js のバージョン2系でないと動かないという感じで、
依存しているライブラリがメンテされていないことで古いcore-jsを採用しなければならないという問題にぶち当たりました。
もっとも、作っているブラウザアプリはモダンブラウザ向けだったということもあり、2系でも動けば問題なかったのでそのまま2系を入れることとなりました。
これは後にバージョンアップで core-js の3系にも対応していましたが、モダンブラウザ向けでもcore-jsが必要になるというのはちょっと疑問な感じです。
SSR対応する際にはカスタムサーバーが必要で、かつ getServerSidePropsやgetStaticPropsに未対応
一番の決め手はここで、Next.jsの9.3から導入された getServerSideProps や getStaticProps に未だに対応していません。
next-i18nextは getInitialProps を使う必要があり、かつカスタムサーバーを導入してそこで next-i18next の初期化処理と Express へのミドルウェアの設定をする必要があります。
i18n対応をするだけでここまで大掛かりのことをしなければいけないのか・・・というところです。
Cachebusterに未対応
極めつけは、Cachebusterに対応していないため、言語定義したJSONファイルを読み込む際に、
自前でファイルの末尾に日時などのCachebusterとなるクエリを付与する必要があります。
これはライブラリとして機能提供されていないため、非常に面倒なところです。
next-i18nextはreact-i18nextを上手くラップしていくつかの設定を隠蔽化(デフォルトで自動設定)しているのですが、
Cachebusterをつけるためにはその利点を享受できなくなる感じになります。
言語テーブルのJSONファイルをCDNに通している場合は尚更Cachebusterを導入しなければ新しいファイルをデプロイした際にいつまでも古い内容を参照してしまいます。
更に余計なことに、CDNキャッシュを削除したところでnext-i18nextもキャッシュをするのでCDNキャッシュの削除をしてもキャッシュが消せないという超面倒くさい感じです。
こんな面倒なことをしてまでi18n対応をしたところで、getInitialPropsにいつまでも縛られてNext.jsの最新機能に追従しづらくなるばかりか、そもそもこのライブラリにHoC前提なコードがまだ随所に残っていて使い勝手も良くないので使うことをやめました。
Rosettaについて
Rosettaはスターは少ないものの、非常にシンプルで扱いやすいライブラリです。
考え方としては、ハッシュ定義された言語テーブルから言語情報を抜き出すだけというただそれだけのシンプルな実装となっています。
getServerSidePropsやgetStaticPropsにも簡単に対応できる
RosettaをNext.jsで扱いやすくした flayyer/next-rosetta がRosettaの利点を上手く表現しています。
next-i18nextのようにSSR対応時にgetInitialPropsを使うことを強制させられるということもなければ、
getServerSidePropsやgetStaticPropsで使うことも考慮した作りになっています。
next-i18nextのようにJSONファイルに言語テーブルを定義することも可能です。
(ただその際はCachebusterを考慮しなければならないので、個人的にはTypeScriptコードにしてDynamic Importする形が一番手間がかからないと考えています)
言語テーブルはある程度大きなシステムになると画面ごとに分ける必要が出てくる
1つ欠点があるとすれば、SSR対応する場合はPropsにDynamic Importして言語テーブルを渡す必要があるので、
レンダリングされたページにその言語テーブルの情報がすべて含まれる形になってしまいます。
数十KB程度ならgzip圧縮していれば問題にはなりませんが、システムが肥大化すればするほどページサイズの肥大化につながり、サイズによってはLCPのスコアを下げる結果につながってしまいます。
そのため、システムの規模次第ではある程度機能単位で言語テーブルを分割し、getServerSidePropsやgetStaticPropsで読み込む際は利用する文言だけを含めた言語テーブルをDynamic Importする形が必要になってきます。
これは言語テーブルの分割度合いをどうするかというくらいの話なので、それほど大したことではない感じです。
まとめ
next-i18nextの個人的な不満ポイントが多くなってしまいましたが、
i18n対応についてはアプリケーション全体に関わる部分であるため、一度実装してから移行するのは非常にコストのかかる作業となります。
そのため最初にどれを選択するのかが肝心になってくるのですが、単にスターの数だけで判断してしまうと痛い目をみるという自分の失敗談でした。
i18n対応時のライブラリ選定は慎重に行うことをおすすめします。