i18next.js は i18next という名前の Cookie に選択された言語を保存する。
HTTP Cookie は同一ドメインであればポート番号の区別なく送信される。
RFC 6265 - HTTP State Management Mechanism 1. Introduction より
Similarly, cookies for a given host are shared across all the ports on that host, even though the usual "same-origin policy" used by web browsers isolates content retrieved via different ports.
以下のようなことをしてる場合、ローカルマシン上とリモートホスト上(本番環境)で iframe で読み込まれる側のアプリの i18next.js の振る舞いに差が出る。
↓このコードは http://localhost:8080/ で動作しているとする。最初にアクセスしたときは i18n.lng() はデフォルト値の dev になる。ボタンをクリックすると i18n.lng() が en-US になり、 Cookie にも保存される。
<script> function onClick() { i18n.setLng('en-US'); location.reload(); } </script> <iframe src="http://localhost:3333/"></iframe> <button onclick="onClick">Change Language</button>
その後ブラウザがリロードされると Cookie の値に従い http://localhost:8080/ で動作しているアプリケーション内では i18n.lng() は en-US になる(期待した動作)
だが、同じ Cookie が http://localhost:3333/ にも送られるので、そっちで動作しているアプリケーション内でも i18n.lng() は en-US になる。
iframe で読み出される側のアプリケーションを別ポートで動かすのはおそらくローカル開発環境だけの話で、本番環境では別ドメインを割り振るのが一般的だろう。すると読み出す側のアプリケーション(http://app1.example.com/)と読み出される側のアプリケーション(http://app2.example.com/)でホストが異なるので Cookie が送られず、 app1 のほうで i18n.lng() を変更しても app2 のほうでは言語設定が変更されない、という事態がおこる。
localhost でずっと開発してきていざ本番環境にデプロイした段階で初めてちゃんと動作してないことに気づくが、原因を突き止めるのにやや手間取る。
本番環境でも同一ドメインの別ポートで app2 を動かすか、 iframe の src 属性に渡す URL を JavaScript で動的に書き換えてやり、 http://app2.example.com/?setLng=en-US のようにクエリストリングパラメータで渡してやれば、 i18next.js がそれも解釈してくれるのでうまく動作する。
HTTP Cookie がリクエストヘッダで送られる条件も Same Origin Policy と同じとばかり思っていたら違っていて勉強になった。