1.6K Views
May 22, 26
スライド概要
TSKaigi 2026 登壇資料
型で頑張るプロダクト国際化 © Techtouch, Inc. TSKaigi 2026 #TSKaigi #tskaigi_leverages
トーク概要 [前提] Reactベースのフロントエンドアプリケーションでの国際化対応 国際化対応に用いられるフレームワークのうち、i18next/react-i18next を対象 [話すこと] 1. プロダクトの国際化を進める上でぶつかった課題を型の仕組みで解消しようとした話 2. react-i18next の最新の型機能の紹介 2 / 24
発表者紹介 名前: Shotaro Ozawa Twitter (現X): @shzawa テックタッチ株式会社でフロントエンドエンジニアをやってます DAPサービスの開発に携わってます 大阪出身です 趣味は野球観戦です 今シーズンからDAZNの年間契約をし始めました 3 / 24
本編に入ります 4 / 24
課題発見までの経緯 プロダクトがある程度成熟している状態で、国際化対応 (日英の二言語) プロジェクト がプロダクトロードマップに載った。 一応プロダクト開発初期から i18next/react-i18next が導入されてはいた。 lng: ['ja'] 固定 辞書データの整備はおざなり。割れ窓状態 プロジェクトを進めるうちに、そんな状態ならではの課題が見つかった。 5 / 24
開発中の課題 1. t 関数のラップ (= i18n化) が漏れている文字列の検出問題 a. 日本語テキストを定数化しているパターン、など 2. 辞書データ登録漏れ問題 a. 未登録な翻訳キーがあってもビルドできてしまう (型チェックが通ってしまう) 6 / 24
開発中の課題 1. t 関数のラップ (= i18n化) が漏れている文字列の検出問題 a. 日本語テキストを定数化しているパターン、など 2. 辞書データ登録漏れ問題 a. 未登録な翻訳キーがあってもビルドできてしまう (型チェックが通ってしまう) 今回は時間の都合により課題2にフォーカスしてお話しします。 7 / 24
開発中の課題 2. 辞書データ登録漏れ問題 a. 未登録な翻訳キーがあってもビルドできてしまう (型チェックが通ってしまう) react-i18next の場合... TypeScript用の設定ファイル i18next.d.ts を用意することで検出機能を有効化できる らしい strictKeyChecks オプションの有効化 8 / 24
strictKeyChecks オプション 9 / 24
strictKeyChecks オプション このオプションを有効にすることで、辞書データにあるすべてのキーを取りうる Union 型が合成され、 t 関数の引数型として注入されます。 10 / 24
strictKeyChecks 設定方法↓ オプション // i18next.d.ts import 'i18next' import type jaJson from './locales/ja.json' import type enJson from './locales/en.json' // パイプラインで繋ぐことで、キーが全ての辞書データに存在するかがチェックできます。 type Translation = typeof jaJson | typeof enJson declare module 'i18next' { interface CustomTypeOptions { defaultNS: 'translation' resources: { translation: Translation } strictKeyChecks: true } } ※ tsconfig.json で `compilerOptions.resolveJsonModule: true` を指定する必要があります。 11 / 24
strictKeyChecks オプションを適用してみた 12 / 24
strictKeyChecks オプションを適用してみた 辞書データが2,000件程度のプロダクトに適用したところ、実際に合成された型が エディター上から確認できました (情報量多い・・・) 13 / 24
strictKeyChecks オプションを適用してみた:終わり また、適用する前と後とで tsc の実行時間が大きく変わらないことも確認できました。 プロジェクトが完了してしばらくした後、 strictKeyChecks オプション を置き換える 目的で作られた新機能 "Selector API" がリリースされたことを知り、調査を進めてみま した。 キーから翻訳テキストを辿るために一々 grep しないといけない 14 / 24
Selector API 15 / 24
Selector API この機能の開発にあたって t 関数のAPIレベルで設計が見直されており、 キーの指定方法が「既存のキー文字列を与える」形から 「オブジェクトプロパティを参照するコールバック関数を与える」形に置き換えられま す。 // before t('key') // after t($ => $.key) 16 / 24
Selector API 設定方法↓ // i18next.d.ts ... declare module 'i18next' { interface CustomTypeOptions { defaultNS: 'translation' resources: { translation: Translation } enableSelector: true } } ※ パフォーマンスに支障をきたす環境では `enableSelector: 'optimize'` の指定が推奨されています。(トレー ドオフ機能あり) 17 / 24
Selector API 翻訳キーの存在判定ロジックは strictKeyChecks 有効時とで以下のように異なります。 before: i. typeof 翻訳データ を再帰的に走査して文字列 Union 型で結びつける ii. t(key) の引数の型として Union 型を注入 after: i. t($ => $.key) のコールバック引数の型として typeof 翻訳データ を ほぼそのまま注入 18 / 24
Selector API プロパティアクセサーの形なので、キーから翻訳テキストを辿るのに エディターの「定義へ移動」機能 が利用できます。 ← これ地味に求めてたやつ strictKeyChecks 有効時から、型検査のパフォーマンスが最大で約1,700倍も向上す るようです。 https://github.com/i18next/i18next/pull/2322 より また、APIに破壊的変更が加わることから、非Selector API環境で構築済みのコードベ ースのマイグレーション用に codemod が提供されています。 https://www.npmjs.com/package/i18next-selector-codemod 19 / 24
Selector API 実践編 感触としては良い感じ。 これは今すぐにも Selector API 利用に乗り換えるべきでは (影響範囲が広くなるから動作確認が大変なのはさておき) というわけで実際にプロジェクトへの適用を試みたところ… 20 / 24
Selector API 実践編 キーが未登録かは爆速で判定されるようになったけど、出力されるエラーが 思うてたんと違う... 21 / 24
Selector API 実践編 こうなった原因は以下の2つでした。 1. キーがほぼすべて日本語文字で並べられている 2. キーがすべてフラット なので、機能有効化と並行して辞書データの構造見直しも進めていこうと思います。 辞書データ構造の指針について説明されている記事 (自分調べ) "Translation key naming conventions: 11 best practices for developers" - Ilya Krukowski氏 https://lokalise.com/blog/translation-keys-naming-and-organizing/ "TypeScriptで実現する型安全な多言語化(i18n)設計 - アーキテクチャから運用まで" - 日向 一樹氏 https://zenn.dev/nexx_hinata/articles/38635095faab70#翻訳キー設計のベストプラクティス 22 / 24
まとめ 辞書データは適切に構造化しておくべし react-i18next をこれから導入するプロダクトでは Selector API 利用がおすすめ react-i18next 導入済みのプロダクトには、まずは strictKeyChecks: true 適用が おすすめ マイグレーションが可能な状況であれば Selector API 利用がおすすめ 23 / 24
まとめ 辞書データは適切に構造化しておくべし react-i18next をこれから導入するプロダクトでは Selector API 利用がおすすめ react-i18next 導入済みのプロダクトには、まずは strictKeyChecks: true 適用が おすすめ マイグレーションが可能な状況であれば Selector API 利用がおすすめ ご清聴ありがとうございました! 24 / 24