RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

【多言語対応】Spring Boot+Java - 動的に言語を切り替る方法 -

f:id:tech-rakus:20211122151839p:plain

はじめに

はじめまして、バックエンドエンジニアのryrkssです。
今回、担当する開発業務にて、Javaフレームワークの中でも有名なSpring FrameworkにあるSpring Bootを使用して、多言語対応しましたのでそのお話をさせていただきたく思います。
多言語対応で調べたときの記事で動的ではない言語対応方法(ユーザの言語設定に左右されない)の記事が多い印象でした。
たしかにサンプルとしてはいいかもしれないんですが、実際のアプリケーションでは動的に言語を切り替えたいはずです。
それをSpring Boot + Javaで実現する1つの方法を記載したいと思います。

今回のゴールはブラウザ設定に応じた言語でJsonレスポンスを返却するというところにおきます。

Spring Bootについてはこちらのブログでも紹介されておりますので参考までに。
【入門】Spring Bootとは~実践まで - RAKUS Developers Blog | ラクス エンジニアブログ

多言語対応とは

その名前の通り、様々な言語に対応することです。
※念のため補足ですが、ここでいう言語は話し言葉の言語であり、プログラミング言語ではありません。

さっそく環境準備から

こちらは本記事の本題ではないので、さくっと説明します。
以下に手順を書いていきますが、皆さんのやりやすい方法で実行環境を作成してください。

  1. Spring Initializrを使ってプロジェクトを作成
  2. 依存関係にorg.springframework.boot:spring-boot-starter-webを追加
    lombokはお好みで。本記事では使用しています。
  3. Controllerとレスポンスオブジェクトを作成
  4. アプリケーションを実行する

 

  • Controller
@RestController
public class MultilingualController {

  @GetMapping("/message")
  public ResponseData message() {
    return new ResponseData("多言語対応");
  }
}
  • レスポンスオブジェクト
@Value
public class ResponseData {
  String message;
}

これでPostmanなどからAPIを叩けば、以下のようなJson形式でボディレスポンスが返ってくると思います。

  • レスポンス
{
    "message": "多言語対応"
}

ただ見ての通り、固定値の多言語対応の文字列を返しているだけなので、
当たり前ですが今のままでは他の言語で返してはくれません。
次からいよいよ多言語対応の実装に入っていきましょう!
本題に入るので順序立てて説明していきます。

message.propertiesを作成する

実際にJsonへ返すメッセージをpropertiesファイルで定義します。
yamlでも可能ですのでこちらもお好みで。
本記事はpropertiesの例のみ記載しますので、yamlの場合は適宜変換してお読みください。

Springでメッセージ管理を行いたい場合、基本的にorg.springframework.context.MessageSourceを使用します。
このクラスをDIし、使用することでpropertiesファイルから指定されたキーに紐づくメッセージが取得できるようになります。

まずはメッセージの出力元となるpropertiesファイルから作成します。
src/main/resources/に以下2ファイルを配置し、それぞれ key=valueの形でメッセージを記載します。

messages_ja.properties(日本語)

application.success=成功
application.error=失敗

messages_en.properties(英語)

application.success=succeeded
application.error=failed

デフォルトはmessages.propertiesが使用されますが、messages_{ロケール}.propertiesのように末に対応したい言語のロケールをファイル名に使用することでリクエストにより使用するmessagesファイルを動的に振り分けることができます。
※propertiesで用意していないロケールがリクエストで飛んできた場合にmessages.propertiesファイルが使用されます。ここでは分かりやすいようにja, enのみの用意で話を進めます。

ここでのポイントはmessagesプロパティ間でのキーは必ず同一にすることです。
MessageSourceを使用するとき、propertiesファイルにあるキー名を指定してそれに紐づくValueを取得する動きとなります。
propertiesファイルの書き方は色々あるので気になる方は調べてください。

これで、出力するメッセージの元は作成できました。
続いてMessageSourceの設定をConfigファイルに定義していきたいと思います。

Configを設定する

xmlベースで設定もできますが、今回はJavaベースで設定を記述していきます。
※個人的にはJavaConfigの方が分かりやすくて好きです。

ここではMessageSourceのベース設定をしていきます。

@Configuration
public class AppConfig {
  @Bean
  public MultiMessageSource messageSource() {
    MultiMessageSource messageSource = new MultiMessageSource(); // ①
    messageSource.setBasenames("messages"); // ②
    messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name()); // ③
    return messageSource;
  }
}

MessageSourceの実装クラスResourceBundleMessageSourceを継承してカスタマイズする独自クラス
簡単に言うとここでリクエストヘッダーからAccept-Languageを読み取り、それに応じたmessages_xx.propertiesの取得結果を返します。
※詳細は後述します。

どのpropertiesファイルを取得対象とするかresourcesからの相対パスかつprefix(_まで)で定義
※例: resources/sample/messages_ja.propertiessample/messages また、messageSource.setBasenames("messages", "sample/paths") のように複数定義することも可能となっており、
自由に定義できるのでアプリケーションの設計により柔軟に対応できると思います。

messages.propertiesを解釈する文字コードをセット

ResourceBundleMessageSourceを継承した独自クラスを作成する

ここでは実際にリクエストの情報を元に動的にmessages_xx.propertiesからメッセージを取得する部分を作っていきます。
といってもこちらで実装する部分は少なく、Springに用意されているAcceptHeaderLocaleResolverというクラスがロケールの判定を行い、returnしているgetMessageメソッドで振り分けを担ってくれるので、その設定を定義してあげるくらいの実装になります。

public class MultiMessageSource extends ResourceBundleMessageSource {

  @Autowired
  HttpServletRequest request; // ①

  public String getMessage(String key, Object... params) { // ②

    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); // ③

    localeResolver.setDefaultLocale(Locale.JAPANESE); // ④

    localeResolver.setSupportedLocales(List.of(Locale.JAPANESE, Locale.ENGLISH)); // ⑤

    return super.getMessage(key, params, localeResolver.resolveLocale(request)); // ⑥
  }
}

HttpServletRequestをDIする
動的にmessages_xx.propertiesを振り分けるための情報のメインがこの中にあります。
このリクエストヘッダー内のAccept-Languageに設定されている値を⑥で出てくるlocaleResolver.resolveLocale(request)で使用します。
このヘッダーにはブラウザの言語設定の情報が入ってくるので、言語に応じた情報を返却するメインの情報源となります。

呼び出すメソッドを定義
第一引数:messages_xx.propertiesに設定しているキー
第二引数~:messages_xx.propertiesにパラメータを設定している場合に使用
戻り値:Stringで返ります(messages.propertiesのValue値)

変換のメインクラスとなるAcceptHeaderLocaleResolverインスタンス

デフォルトロケールを設定
ここで名前の通りデフォルトで使用する言語情報をセットします。
デフォルトロケールがmessagesの振り分けに使用されるパターンは以下です。

  • リクエストヘッダー内のAccept-LanguageがNULLの場合
  • 後述するSupportedLocalesに値がセットされていない場合
  • リクエストヘッダーの言語設定とSupportedLocalesの値がマッチしない場合

サポートロケールを設定
ここでは作成するアプリケーションがサポートしている言語は何かを明示的に指定します。
※List型で渡すことに注意してください。
サポートロケールがmessagesの振り分けに使用されるパターンは以下です。

  • サポートロケールとリクエストヘッダーの言語設定がマッチする場合

親クラスResourceBundleMessageSourceの親の親....AbstractMessageSourceのgetMessageメソッドの結果を返却
第一引数:messages_xx.propertiesに設定しているキー
第二引数~:messages_xx.propertiesにパラメータを設定している場合に使用
第三引数:Locale{ロケール}を指定 ※こちらを使用してどのmessages_xx.propertiesからValueを引いてくるかを判定します

今回の多言語対応のメインである、第三引数で行っているlocaleResolver.resolveLocale(request)について説明します。
AcceptHeaderLocaleResolverクラスのresolveLocaleメソッドでリクエストヘッダーに応じたロケールを取得してくれます。
そのため、resolveLocaleメソッドには引数としてHttpServletRequestを渡してあげるだけでいわゆる動的な多言語対応が実現します。
resolveLocaleメソッドでやっていることは前述した④、⑤で設定した内容とリクエストを使用してロケール情報を返しています。

...ただ正直この中身の作りはいまいちな気がしています。。
個人的には想定外のAccept-Languageが来たときは一律デフォルトロケールを設定してくれればいいと思っているのですが、確実に想定外が発生しないような動きにするにはデフォルトロケールとサポートロケールを設定する必要があります。
気になる方はそんなに難しいことはしていないのでresolveLocaleの中身を見てみてください。
また、私が調べてみた限りの情報なので、もしかしたらもっとうまい方法があるかもしれません。

これで多言語対応の内部ロジックは終わりです。
あとはControllerを少し修正して結果を確認してみましょう!

Controllerから作成したMultiMessageSourceを呼び出す

冒頭で作成したレスポンスをべた書きしているMultilingualControllerを修正します。

@AllArgsConstructor
@RestController
public class MultilingualController {

  private final MultiMessageSource messageSource; // ①

  @GetMapping("/message")
  public ResponseData message() {
    String message = messageSource.getMessage("application.success"); // ②
    return new ResponseData(message);
  }
}

カスタマイズしたメッセージクラスMultiMessageSourceをDIする
getMessageメソッドをコールし、引数にmessages_xx.propertiesに定義されているキーを渡す
 ※ここではキーだけ渡してますが、パラメータを設定している場合はそれも渡すことが可能です。

実行してみる

実際に想定通りの言語で返ってくるかAPIを実行してみましょう。

  • Accept-Language=ja(日本語)
    指定した通り日本語でメッセージが返ります。
{
    "message": "成功"
}
  • Accept-Language=en(英語)
    指定した通り英語でメッセージが返ります。
{
    "message": "succeeded"
}
  • Accept-Language=de(ドイツ語)-アプリケーションで想定していない言語コード
    存在しない場合デフォルトロケールで指定した言語でメッセージが返ります。
{
    "message": "成功"
}
  • Accept-Languageヘッダーなし
    デフォルトロケールで指定した言語でメッセージが返ります。
{
    "message": "成功"
}

まとめ

長々と書いてきましたが、実際にコーディングするのはごくわずかです。
ほとんどはSpringの標準パッケージの中でよしなにやってくれるので非常に簡単に実装できました。
もちろん他にも作りこめば色々できると思います。
また、今回はブラウザの言語設定によってメッセージを切り替える例をご紹介しましたが、
他にもSpringで多言語対応をするやり方は調べれば出てきます。
例えばUI上で言語を選択して切り替えることもSpringの機能を使用して比較的簡単にできるようです。
気になる方は調べてみてください!


  • エンジニア中途採用サイト
    ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
    ご興味ありましたら是非ご確認をお願いします。
    20210916153018
    https://career-recruit.rakus.co.jp/career_engineer/

  • カジュアル面談お申込みフォーム
    どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
    以下フォームよりお申込みください。
    rakus.hubspotpagebuilder.com

  • イベント情報
    会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!
    rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.