[Spring]MessageSourceでメッセージをYAMLファイルから読み出す

#MessageResourceでメッセージをYAMLファイルから読み出す

##動機
`messages.properties`のメンテ面倒くさいんですけおお！！
Eclipseで触ると勝手に`native2ascii`してくれちゃって困るし！！１１！！[^1]

だから`YAML`で書こうね

##目的
SpringFrameworkを使ったアプリケーションでi18n対応をする場合、`messages.properties`のようなリソースファイル（拡張子が`.properties`なプロパティファイル）を使用することになる。

こういうやつだ

```properties:message_ja.properties
#階層構造で書けないんですか？やだー！
site.title=サイトタイトル
site.description=サイトの説明
page.header.links.index=サイトトップ
page.header.links.gettingstart=はじめる
page.header.links.document=ドキュメント
page.header.links.community=コミュニティ
```

これをこう書きたい。[^2]

```yaml:message_ja.yaml
#階層構造で書けるんですか？native2asciiしなくていいんですか？やったー！
site:
  title: "サイトタイトル"
  description: "サイトの説明"
page:
  header:
    links:
      index: "サイトトップ"
      gettingstart: "はじめる"
      document: "ドキュメント"
      community: "コミュニティ"
```

ググってもそのものズバリの実装例が見つからなかったのでこうやって記事にしておく。

##プロジェクト構成例
メッセージリソースをYAML対応させるためのプロジェクトの構成例。[^3]
Yamlファイルからの読み出しには[YamlResourceBundle](https://github.com/akihyro/yaml-resource-bundle)を使用している。[^4]

![screenshot-00.png](https://qiita-image-store.s3.amazonaws.com/0/83175/035ca06b-0bbd-1622-d4a6-2cda1eea52e0.png)

各ファイルの内容は以下の通り。
やってる事は単純なので読めば分かると思う。なので細かい解説は省略。

```gradle:build.gradle
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.0.2.RELEASE'
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
	baseName = 'exercise1'
	version =  '0.0.1-SNAPSHOT'
}

repositories {
	mavenCentral()
}

dependencies {
    compile 'org.springframework.boot:spring-boot-devtools'
	compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
    compile 'net.rakugakibox.util:yaml-resource-bundle:1.1'
}

sourceCompatibility = '1.8'
targetCompatibility = '1.8'
ext['mainClass'] = 'pkg.exercise1.App'
ext['thymeleaf.version'] = '3.0.9.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.3.0'
```

```yaml:config/application.yml
spring:
  thymeleaf:
    mode: "HTML"
  messages:
    basename: "i18n/messages"
    encoding: "UTF-8"
```

```yaml:i18n/messages.yml
site:
  title: "Excercise"
  description: "このメッセージはYAMLリソースファイルから出力していますよ"
```

```html:templates/index.html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<title data-th-text="#{site.title}">Title</title>
	</head>
	<body>
		<div>
			<article>
				<h1 data-th-text="#{site.title}">Title</h1>
				<p data-th-text="#{site.description}">This is Index page. Yeah!</p>
				<hr />
				<h2>API</h2>
				<ul>
					<li><a href="#" data-th-href="@{/api/hello}">/api/hello</a></li>
				</ul>
			</article>
		</div>
	</body>
</html>
```

```java:pkg/exercise1/App.java
package pkg.exercise1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
	public static void main(String... args) {
		SpringApplication.run(App.class, args);
	}
}
```

```java:pkg/exercise1/configurations/MessageSourceConfig.java
package pkg.exercise1.configurations;

import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;

import net.rakugakibox.util.YamlResourceBundle;

@Configuration
public class MessageSourceConfig {

    @Bean("messageSource")
    public MessageSource messageSource(
            @Value("${spring.messages.basename}") String basename,
            @Value("${spring.messages.encoding}") String encoding
    ) {
        YamlMessageSource ms = new YamlMessageSource();
        ms.setBasename(basename);
        ms.setDefaultEncoding(encoding);
        ms.setAlwaysUseMessageFormat(true);
        ms.setUseCodeAsDefaultMessage(true);
        ms.setFallbackToSystemLocale(true);
        return ms;
    }
}

class YamlMessageSource extends ResourceBundleMessageSource {
    @Override
    protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
        return ResourceBundle.getBundle(basename, locale, YamlResourceBundle.Control.INSTANCE);
    }
}
```

```java:pkg/exercise1/controllers/IndexController.java
package pkg.exercise1.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {

	@RequestMapping("/")
	String home() {
		return "index";
	}
}
```

##ブラウザ表示
SpringBootアプリケーションを起動して[http://localhost:8080](http://localhost:8080)にアクセスしてみる。

![screenshot-01.png](https://qiita-image-store.s3.amazonaws.com/0/83175/9b5c1ae9-1f4c-b723-98fe-86418f49474d.png)

やったぜ。[^5]

[^1]: Netbeansは多言語を並べて表示してくれるので対訳しやすいし`native2ascii`もしない。話の分かる奴だ。でも色々と機能が足りないので開発環境として使いやすいかというと正直微妙だと言わざるを得ない…。
[^2]: みんなもこう書きたいよね。書きたくないのにこのページ開かないもんね。（名推理）
[^3]: 余計なパッケージも写っているが気にしないで欲しい。
[^4]: 今回の用途では`YamlResourceBundle.Control`クラスだけ使えばいい。
[^5]: 英語(en)のロケールでの表示も確認済み。エビデンス？ねーよそんなの:smiling_imp: