Slide 1

Slide 1 text

nabeliwo 2024/05/11 @TSKaigi 2024 多言語化対応における TypeScript の型定義を 通して開発のしやすさについて考えた

Slide 2

Slide 2 text

自己紹介 •id: nabeliwo •株式会社 SmartHR •プロダクトエンジニア

Slide 3

Slide 3 text

今日話すこと react-intl というライブラリを使って React アプリケーションの多言 語化対応をする中で型定義を工夫しまくったら堅牢でありつつ開発者 体験も担保できたという話 ※こちらのブログ記事の LT 版です 「多言語化対応における TypeScript の型定義を通して開発のしやすさについて考えた - SmartHR Tech Blog」 https://tech.smarthr.jp/entry/2023/11/14/145748

Slide 4

Slide 4 text

react-intl の使い方 3ステップ 1. 言語ごとの翻訳ファイルを用意する オブジェクトの key を一致させる必要がある

Slide 5

Slide 5 text

react-intl の使い方 3ステップ 2. アプリケーションのルートを IntlProvider でラップする 選択した言語の 翻訳オブジェクトを渡す

Slide 6

Slide 6 text

react-intl の使い方 3ステップ 3. 翻訳対象の文言を FormattedMessage に置き換える 翻訳オブジェクトの key を id に渡す defaultMessage は フォールバック

Slide 7

Slide 7 text

react-intl を使うと簡単に多言語化対応ができる ただ現状の書き方だとメンテナンスを考えたときにいくつか問題がある

Slide 8

Slide 8 text

問題1 存在しない id を FormattedMessage に渡してしまう可能性がある • 存在しない id を渡しても defaultMessage によって日本語は表示されるの で、日本語でしか動作確認をしていないと翻訳できないことに気づかずリリ ースしてしまう可能性がある 翻訳オブジェクトに存在しない Key を渡しちゃってる!! フォールバックで 「キャンセル」が表示される

Slide 9

Slide 9 text

問題2 日本語の翻訳ファイルと FormattedMessage の defaultMessage とで日本語 が二重管理になる • 文言変更時の修正箇所が2箇所になる • 翻訳ファイルと defaultMessage の日本語で乖離が起きてしまう可能性 同じ日本語を2箇所で管理してる〜〜

Slide 10

Slide 10 text

まずこれらを型定義の工夫で解決する

Slide 11

Slide 11 text

対応1 翻訳ファイルを型で縛る 型注釈!!

Slide 12

Slide 12 text

対応2 FormattedMessage をラップして id の型を変更する keyof Messages は翻訳オブジェクトの key のユニオン型

Slide 13

Slide 13 text

対応3 defaultMessage を自動で渡すようにする 翻訳オブジェクトに id を渡して value を取り出す defaultMessage を渡さなくて 良くなった!!

Slide 14

Slide 14 text

結果 Messages に存在しない key は FormattedMessage の id として渡せなくなっ た 翻訳オブジェクトに存在しない keyを 渡すとエラーが出る!!

Slide 15

Slide 15 text

結果 日本語の二重管理がなくなった 日本語が1箇所にしかない

Slide 16

Slide 16 text

結果 副産物として、FormattedMessage の id にエディタの補完が効くようになって 開発体験が良くなった 翻訳オブジェクトの key の一覧が 補完されるので手打ちする必要なし

Slide 17

Slide 17 text

メンテナンスしやすい多言語化対応ができた! めでたしめでたし…

Slide 18

Slide 18 text

と思いきや

Slide 19

Slide 19 text

チームメンバーからフィードバックがきた 「コンポーネントから日本語がなくなるとコードが見 づらくなるので defaultMessage は残してほしい」

Slide 20

Slide 20 text

なぜ defaultMessage を残すべきなのか • 前提として、フィードバックはコードを書くデザイナーから来ている • コンポーネントから日本語が消えることで直感的に検討ができなくなる • 例えば、見出し・ボタンのラベル・説明文など、主な表示言語である日本 語の文字量を考慮してコンポーネントを選定したり、レイアウトを調整し ており、これらが近接する場合は、その組み合わせ全体で文字量が多くな りすぎないようにバランスを調整することもあるため、これらがコード上 から読みづらくなってしまう • id で検索すればそこに入る日本語はわかるが、その1ステップの影響は少な くない

Slide 21

Slide 21 text

なるほど、たしかに… よし、対応しよう!

Slide 22

Slide 22 text

FormattedMessage に defaultMessage を必須で渡す ようにしつつも、日本語の翻訳ファイルと defaultMessage とで二重管理になってしまう問題は 起こらないようにしたい

Slide 23

Slide 23 text

対応1 as const satis fi es を使う • 値が string ではなく文字列リテラル型になる

Slide 24

Slide 24 text

対応2 ジェネリック型を使う • FormattedMessage に渡された id props の値を元に defaultMessage の型 を絞れるようになる 日本語の翻訳オブジェクトから 文字列リテラル型を取り出す

Slide 25

Slide 25 text

結果 defaultMessage に日本語の翻訳と違う文言を渡せなくなった id と defaultMessage が 日本語の翻訳オブジェクトの key と value に 一致しない場合はエラーになる

Slide 26

Slide 26 text

結果 id が確定していれば defaultMessage は補完されるので手打ちする必要がなく なった id に渡した値によってdefaultMessage が補完される ※日本語自体は翻訳ファイルと defaultMessage で2つ存在するが、  型で縛られるので乖離が起き得ない

Slide 27

Slide 27 text

FormattedMessage に defaultMessage を必須で渡すようにしつ つも、日本語の翻訳ファイルと defaultMessage とで二重管理に なってしまう問題が起こらないようにできた 結果、コードのメンテナンス性を担保しつつチームメンバーの開 発のしやすさも担保することもできた

Slide 28

Slide 28 text

これで本当に、めでたしめでたし…

Slide 29

Slide 29 text

型定義で堅牢なコードを書くということ以外にも、チ ームで開発のしやすさを考える良い機会になった

Slide 30

Slide 30 text

おしまい