見出し画像

Now in REALITY Tech #86 サーバサイドの多言語対応の話

こんにちは、サーバエンジニアの abe です。久々のブログ投稿!
最近はルーム機能のバックエンドなどをやっているんですが、日々すごい勢いで家具が作られていくのですごいなーと思いながら見ています。マスタデータもどんどん増えているのでどう管理していくか悩ましい!

さて、つい先日 GREE Tech Conference 2023 が開催され、REALITY からも多くのメンバーが登壇しました。その中でチュイさんが多言語対応について発表していたのですが、そういえば REALITY の「サーバの」多言語対応の話ってあまりしてこなかったなぁと思ったので、今回はサーバサイドの多言語対応の話をしたいと思います。

翻訳文言の管理

REALITY では Lokalise という LMP (Localization Management Platform) を利用し、翻訳文言を一元管理しています。サービス名がローカライズ自体を指す "Localize" とややこしく、今でもたまに混乱します 😇

Lokalise 導入以前は文言をスプレッドシートで管理していましたが、対応言語を12言語に増やしたことで管理対象の文言が膨大になったため徐々に移行を進めました。移行してハッピーになれるのか不安な面もありましたが、移行にあわせて運用フローを整えた結果

  • Lokalise CLI を活用し、文言に差分が出たら自動で PR が作成され直接 base branch にマージするフローを整備できた

  • スプシ管理時代に発生していたようなコンフリクトやシートの更新漏れなどが起きなくなり、Single Source of Truth が実現できた

  • API も公開されているので社内での運用体制に合わせたツールを実装でき、様々な人が利用しやすい環境を構築できた

など様々なメリットがあり、だいぶ快適になりました!

API サーバの実装

REALITY では言語設定をアプリ内では持たず、OS 設定の言語を利用しています。API をコールする際にクライアント側の言語を Accept-Language ヘッダで送り、サーバ側ではその言語のデータを返すというのが基本的な仕組みです。
この方式だとリクエストユーザの言語しか取れないため、最後にアクセスした際の各ユーザの言語を保持しておき、リクエストユーザ以外の言語が必要になった場合や非同期処理を行う際などはその言語を参照しています。

API サーバ内でのローカライズ処理には go-i18n を採用し、各マイクロサービスから利用する共通ライブラリに専用のパッケージを用意してローカライズ関連の実装をまとめています。
翻訳リソースは Lokalise から json で出力してそのまま配置していますが、キーを定数として参照できるよう json からキーを抜き出した以下のような Go ファイルを自動生成し、json と一緒に更新したりもしています。

type MessageID string

const (
  ErrorDefault MessageID = "ErrorDefault"
  ...
)

定数として参照することで typo によるバグを防げますし、使用されているキーが誤って削除されてしまってもコンパイルエラーで検知できるので安心。文言の利用箇所を検索する際にも便利です。キーを文字列として動的に組み立てたりしてると、利用箇所の検索が大変なんですよね……。

go:embed による埋め込み

Go では json のような静的ファイルは通常バイナリに含まれません。コンテナ内に直接配置すれば読み込めますが、ローカライズ関連処理は各マイクロサービスから共通で使うため共通ライブラリ内に実装をまとめており、ライブラリ内に配置した json を読み込めないという問題がありました。

そんな時に便利なのが、Go 1.16 から導入された go:embed です。json ファイルを置いて、コード上で

import _ "embed"

//go:embed resources/ja.json
var jaMessages []byte

このように指定しておくことで、json の内容を埋め込んだ状態でバイナリがビルドできます。埋め込んだコードは、go-i18n であれば

b := goi18n.NewBundle(language.Japanese)
b.MustParseMessageFileBytes(jaMessages, path)

のようにして読み込めます。

Lokalise 移行を進めている時期は Go 1.11 で動いているサーバがまだ残っていたためそのサーバでは go:embed が使えず、同等の機能を持つ statik のようなライブラリを導入するか、json を Go の map に変換するコードを書くか悩ましかったんですが、バージョンアップが間に合ったため無事に go:embed でスッキリ書くことができました。こまめなバージョンアップ大事!

入稿テキストの管理

アプリ内に表示する定型的なテキストについては前述の通り json で出力しビルドに組み込んでリリースしていますが、サーバ側ではそれ以外にも多言語対応された様々な文言を管理する必要があります。例えば

  • お知らせ

  • 各種マスタデータ

  • 言語ごとに異なる画像のURL

などです。このような文言は社内ツールから入稿できるようになっているため変更頻度も高くなっており、サーバ実装として柔軟に更新・検索・キャッシュできるようにするため、専用のデータベースで管理しています。

最初は配信情報を扱うサーバ・DBに相乗りしていたのですが、配信関連の負荷が高まった際に配信以外の機能にも影響が出たりしたため改善を進め、現在は翻訳専用のマイクロサービスに集約できました。負荷が高まった際も柔軟にスケールしやすくなり、安定して運用できています。

まとめ

REALITY のサーバサイドでの多言語対応について紹介しました。
今回は Go の API サーバを中心に紹介しましたが、Web フロントエンドや WebSocket サーバで利用する文言も同様の仕組みで管理しています。
多言語対応についてはまだまだ課題も多いため、運用・品質など引き続き改善していきたいと思います!