🌎

多言語化ライブラリを開発して得た気付きと失敗

2022/12/12に公開

フロントエンドに感度の高い皆さんならご存じの方も多いと思いますが、今Javascript/Typescript界隈で一番勢いのあるバリデーションライブラリといえばZodです。(異論は認めます)

https://github.com/colinhacks/zod

Zodは各スキーマ一つ一つにエラーメッセージを設定すれば、エラーメッセージを多言語化することが可能ですが、デフォルトのエラーメッセージの多言語化はかなり手間です。
翻訳ファイルを用意してimportするだけで翻訳できるみたいな簡単な口は用意されていません。

https://zenn.dev/hisho/articles/e8f809db5415e8

私は今年の夏より、Zodのデフォルトのエラーメッセージの多言語化の標準化を行うための3rdパーティライブラリをメンテナンスしています。

https://github.com/aiji42/zod-i18n

パブリッシュしてからまだ3,4ヶ月しか経っていませんが、多言語化ライブラリのメンテナンスをする上での、気付きや失敗に関してノウハウが溜まってきたので、改めてアウトプットしたいと思います。

zod-i18n-mapの紹介

若干宣伝ぽくなってしまいますが、前提となるライブラリの紹介です。

https://github.com/aiji42/zod-i18n

このライブラリでは、Zodとi18nextを組み合わせて多言語化を行っています。

Zodのエラーメッセージをi18nextで解釈可能テンプレートに変換し、

https://github.com/aiji42/zod-i18n/blob/main/packages/core/src/index.ts#L65-L73

言語ごとに用意した翻訳ファイルと組み合わせることで翻訳されたエラーメッセージを出力します。

https://github.com/aiji42/zod-i18n/blob/main/packages/core/locales/ja/zod.json#L1-L11

import i18next from 'i18next'
import { z } from 'zod'
import { zodI18nMap } from 'zod-i18n-map'
import translation from 'zod-i18n-map/locales/ja/zod.json'

i18next.init({
  lng: 'ja',
  resources: {
    ja: { zod: translation },
  },
  interpolation: {
    skipOnVariables: false
  }
});
z.setErrorMap(zodI18nMap)

const schema = z.string().email()
schema.parse('foo') // => メールアドレスの形式で入力してください。

気づきと工夫

英語圏の人はあまり多言語化に興味ない?

まず、ZodのリポジトリにあるDiscussionで多言語化についてのトピックを開きました。
そのときに返ってきた返答が、「なんでエラーメッセージの翻訳してるの?」というものでした。

(なぜって、そりゃ英語話者の人じゃなきゃ、エラーメッセージが英語というのはわかりにくいんですが...)

正直なところ「なんで?」と問われるのは予想していませんでした。

あくまで、私に返信したその人だけなのかもしれませんが、英語話者の人からしてみると、案外多言語化のニーズというのはそこまで高くないのかもしれません。
(当たり前といえば当たり前ですが、リポジトリの多言語化自体に興味がないのは意外でした。)

ただ一方で、実際にパッケージのスター数やDL数を見てみると、私がオーナーを務めているほかのいくつかのライブラリと比較したときに、対期間での伸び率は高い方です。
Zodという比較的に人気のあるライブラリの、3rdパーティーライブラリであるというのものありますが、やはり多言語化のニーズは一定あるのだと実際にパブリッシュしてみて実感しました。

コントリビューションが集まりやすい

翻訳ファイル(json)をライブラリ内で管理しているため、翻訳言語を増やす際にはPRが必要です。
対応言語を増やす場合には、テンプレートとなる英語版のjsonファイルとテストファイルをコピーして翻訳するだけで良いので、比較的コントリビューションのハードルは低くなっています。
コントリビューションの数やスター数はオーナーにとってライブラリをメンテンスする上での最大のモチベーションになりますので、「コントリビューションが集まりやすい」というのはとてもありがたいことです。

感謝のメッセージくらいは相手の言語で書く

気づきというか、私が工夫していることなのですが、コントリビューターに対して感謝を伝える時は、相手の言語でメッセージを書くということを心がけています。
これは純粋に自分がそうされたときに嬉しかったから真似をしているというだけですが、まさに多言語化ライブラリならではの工夫だと思います。


失敗

翻訳ファイルはレビューできない

前述の通り、対応言語を追加する際、または翻訳を修正する場合にはPRが必要です。
PRが必要であるということは、本来マージのためにはレビューが必要ですが、翻訳内容の正しさはその言語の話者でなければ評価できません。
「コントリビューションが集まりやすい」というのはオーナーである私にとって、モチベーションにつながる大切なことですが、多言語であるゆえに内容のレビューができないというのが欠点です。


この問題に関しては、パブリッシュする際に予め想定されていた問題であったため、内容の評価とは異なる観点でのポリシーを設けました。

  • 翻訳ファイルのフォーマット
  • テストファイルの存在と通過
  • サンプルページへのユースケース追加

これらに対応する作業手順をCONTRIBUTING.mdに記載しておくことでコントリビューターは戸惑いなく作業を行えます。
テストに関しても、ベースとなる英語版のテストファイルをコピーして翻訳してもらうだけで済みますので、予めカバレッジを高く置いておくことで、言語数の増加に伴うリグレッションを予防することができました。

実際のユースケースベースのサンプルプロジェクトを用意したのも、動作確認の上でもそして、実際のパッケージ利用者がイメージを掴むためにも良かったと思います。

翻訳ファイルをパッケージに含めるべではなかった

大本のZodに新しいバリデーションメソッドが増えて、エラーメッセージが増えた場合など、当たり前ですがこちらの多言語化ライブラリの方でも対応が必要です。
コアロジックを変更し、更に各言語の翻訳ファイルとテストファイルにメッセージを追加したり変更したり、という作業が発生します。
私一人で日本語と英語のファイルはメンテナンスできますが、他の言語は各メンテナに毎度まいど協力を仰ぐ必要があります。

現状コアロジックと言語ファイルを一つのパッケージで配布しているので、すべての言語の足並みが揃わなければリリースができません。
一応フォールバック機構を用意していて、部分的にメッセージが足りなければデフォルトの英語で出力される仕組みになっているので、リリースしようと思えばできるのですが、リリースルールを設けていないので、現状はしっかりと足並みを揃えるという感じになっています。

上記の「レビューできない」という点に関しては、予想ができたので対策が打てましたが、こちらの問題に関してはフォールバックを用意していたこと以外は無策でした。
(もうすこし事前に考えていれば十分に対処できたと思いますが...)

今後はコアロジックと翻訳ファイルとをモノレポ等で分割し、異なるバージョニングができるようにすることを考えています。
とはいえ、言語によっては翻訳が不十分な状態でパッケージが進捗していくことが予想されますので、コアロジックに変更があった際に、各言語のメンテナに自動的にメッセージを送って対応をお願いする(強制ではない)ような仕組みを整えたいと考えています。

まとめ

  • 比較的コントリビューションが集まりやすいのでモチベーションに繋がる
    • 人気ライブラリのi18nプロジェクトならなおさら
  • テストとサンプルを充実させておくとスムーズにマージできる
    • 「テンプレをコピーして翻訳するだけ」くらいのかんたんなものにして、コントリビューションのハードルを下げる
  • フォールバック機構を用意しておくとよい
    • 翻訳が不十分でもリリースが滞らないように
  • 言語ファイルとコアロジックは別パッケージにしておくのがベター
    • リリースを分けられるようにするため

以上が多言語化ライブラリを作る上のノウハウです。

GitHubで編集を提案

Discussion