Spring Frameworkの多国語対応

# はじめに
Spring Frameworkでの多国語対応について調べていたがいくつか詰まったところがあったのでメモ。
なおSpring Bootのバージョンは2.1.2。

# 基本
基本的には`org.springframework.context.MessageSource`を使う。

## 設定ファイル例
`src/main/resources`の直下に以下のようなファイルを作成する。

```messages.properties
hello=hello
```

```messages_ja.properties
hello=こんにちは
```

`messages.properties`がデフォルトのファイルで、`messages_ja.properties`が日本語用のファイル（アンダーバー以降でロケールを指定）。

## 呼び出し例

```java:MessageController.java
@CrossOrigin
@RestController
@RequestMapping("/messages")
public class MessageController {
	@Autowired
	private MessageSource messageSource;

	@GetMapping("/hello")
	public String hello(Locale locale) {
		return messageSource.getMessage("hello", new String[] {}, locale);
	}
}

```
`MessageSource`をインジェクションしてメッセージを呼び出す。上記サンプルでは基本的なやつを使っているが、[公式](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/MessageSource.html)ではデフォルトメッセージを指定するメソッドなんかもある。

## 実行例
`Accept-Language`を`ja`にして`GET`リクエストを送信してやれば`こんにちは`が、それ以外の言語（`en-US`や`zh-CN`等）でリクエストを送信してやれば`hello`が返ってくる。
ただし環境によっては`ja`以外にしても`hello`が返ってこない場合がある（[後述](#常に日本語でメッセージが返る場合)）。

# 応用
## 設定ファイルの場所や名前を変更したい
デフォルトだと`messages.properties`を参照するが、ファイルの場所を変更したり複数用意したりしたい場合がある。
その場合はインジェクションされる`MessageSource`を修正してやればいい。

### 設定ファイル例
`messages.properties`の内容はそのままで、保存場所を`src/main/resources/i18n`にしたとする。
また、新規で以下のファイルを`src/main/resources/i18n/hoge`に保存したとする。

```hoge.properties
hoge=hoge
```

```hoge_ja.properties
hoge=ほげ
```

### Spring Framework側での設定例

```java:MessageSourceConfig.java
@Configuration
public class MessageSourceConfig {
	@Bean
	public MessageSource messageSource() {
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		messageSource.setBasenames("i18n/messages", "i18n/hoge/hoge");
		messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
		return messageSource;
	}
}
```
`ResourceBundleMessageSource`が`MessageSource`を実装したクラス。他にもメッセージファイルをリロードできる`ReloadableResourceBundleMessageSource`なんかがあるらしい。
`setBasenames`でメッセージファイルの場所や名前を指定する。
`application.properties`でメッセージファイルを指定する方法もあるらしいが未確認。

### 呼び出し例

```java:MessageController.java
@CrossOrigin
@RestController
@RequestMapping("/messages")
public class MessageController {
	@Autowired
	private MessageSource messageSource;

	@GetMapping("/hello")
	public String hello(Locale locale) {
		return messageSource.getMessage("hello", new String[] {}, locale);
	}

	@GetMapping("/hoge")
	public String hoge(Locale locale) {
		return messageSource.getMessage("hoge", new String[] {}, locale);
	}
}
```

### 実行例
[基本](#実行例)のときと同じで`Accept-Language`の値によって日本語（`こんにちは`や`ほげ`）が返ってきたり、`hello`や`hoge`が返ってくる。

# 補足
## 常に日本語でメッセージが返る場合
大抵どのサイトで`MessageSource`のことを調べても指定されたロケールがなければデフォルトファイルのメッセージが返されるとあるが、ローカルPCで確認すると常に日本語でメッセージが返ってきた。
調べた限りロケールが存在しなかった場合デフォルトだとシステムロケールにフォールバックされるらしい（[参考](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/support/AbstractResourceBasedMessageSource.html#setFallbackToSystemLocale-boolean-)）。ローカルPCで確認したときはシステムロケールが日本語で、かつ日本語のプロパティファイルを用意していたため常に日本語でメッセージが返ってきたのだと予想。

### 対策
```java:MessageSourceConfig.java
@Configuration
public class MessageSourceConfig {
	@Bean
	public MessageSource messageSource() {
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		messageSource.setBasenames("i18n/messages", "i18n/hoge/hoge");
		messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
		messageSource.setFallbackToSystemLocale(false);
		return messageSource;
	}
}
```

`setFallbackToSystemLocale`を`false`にしてやれば指定されたロケールのファイルが存在しなかった場合（システムロケール等関係なく）デフォルトのファイルのメッセージが返ってくるようになる。

# おわりに
`ja`だと日本語で返ってくるが`ja_JP`だと日本語で返ってこなかったり等まだいじれそうなところはあったのでどこかで調査したい。