タイトル: メッセージ式
SEOタイトル: Thymeleaf メッセージ式と国際化 (i18n) 完全ガイド
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><strong>メッセージ式 <code>#{...}</code></strong> は <code>messages.properties</code> 等の<strong>外部ファイル</strong>から翻訳テキストを読み込む仕組み</li><li>基本: <code>messages.properties</code> に <code>home.title=ホーム</code> を書き、テンプレートで <code><h1 th:text="#{home.title}"></h1></code></li><li>多言語対応: <code>messages_ja.properties</code> / <code>messages_en.properties</code> を用意。<code>Accept-Language</code> ヘッダや URL パラメータでロケール切替</li><li>引数つき: <code>greeting=こんにちは {0} さん ({1} 件のメッセージ)</code> → <code>#{greeting(${user.name}, ${count})}</code></li><li>Spring Boot は <code>MessageSource</code> 経由で自動連携。<code>spring.messages.basename=messages</code> がデフォルト</li></ul></td></tr></table>
<h2>メッセージ式とは</h2>
<p>Thymeleaf の <code>#{...}</code> は、テンプレート内に直接文字列を書くのではなく、<strong>外部のプロパティファイル</strong>から読み込む仕組みです。これにより <strong>(1) 多言語対応 (i18n)</strong>、<strong>(2) 翻訳作業をデザイナと分業</strong>、<strong>(3) 文言変更時にテンプレート再ビルド不要</strong>といった利点があります。</p>
<h2>最小サンプル</h2>
<h3>1. messages.properties (デフォルト)</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-properties"># src/main/resources/messages.properties
home.title=ホーム
home.welcome=ようこそ
home.button.login=ログイン
home.button.signup=新規登録
article.title=記事タイトル
article.body=本文
article.created_at=作成日時
error.required={0} は必須です
error.minLength={0} は {1} 文字以上で入力してください</code></pre>
<h3>2. テンプレート</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{home.title}">ホーム</title>
</head>
<body>
<h1 th:text="#{home.welcome}">ようこそ</h1>
<button th:text="#{home.button.login}">ログイン</button>
<button th:text="#{home.button.signup}">新規登録</button>
</body>
</html></code></pre>
<h2>多言語対応 (i18n)</h2>
<h3>ファイル命名規約</h3>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>ファイル名</th><th>適用条件</th></tr></thead>
<tbody>
<tr><td><code>messages.properties</code></td><td>デフォルト (どのロケールにも該当しない時)</td></tr>
<tr><td><code>messages_ja.properties</code></td><td>日本語</td></tr>
<tr><td><code>messages_en.properties</code></td><td>英語</td></tr>
<tr><td><code>messages_en_US.properties</code></td><td>アメリカ英語</td></tr>
<tr><td><code>messages_zh_CN.properties</code></td><td>簡体字中国語</td></tr>
</tbody>
</table>
<h3>サンプル</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-properties"># messages_ja.properties
home.title=ホーム
home.welcome=ようこそ
home.button.login=ログイン
# messages_en.properties
home.title=Home
home.welcome=Welcome
home.button.login=Login
# messages_zh_CN.properties
home.title=主页
home.welcome=欢迎
home.button.login=登录</code></pre>
<h3>application.properties</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-properties"># 既定で messages という basename を読みます。明示も可
spring.messages.basename=messages
# 複数の basename を読む場合
# spring.messages.basename=messages,errors,validation
spring.messages.encoding=UTF-8
spring.messages.fallback-to-system-locale=false
spring.messages.use-code-as-default-message=true</code></pre>
<h3>ロケール切替の方法</h3>
<p>Spring MVC は<strong>ロケール解決</strong>を 3 つの方法で行えます:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-java">// 1. URL パラメータ ?lang=en で切替
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
resolver.setDefaultLocale(Locale.JAPANESE);
return resolver;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang"); // ?lang=en
return interceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
// 2. Accept-Language HTTP ヘッダ
// AcceptHeaderLocaleResolver (Spring Boot のデフォルト)
// 3. クッキー
// CookieLocaleResolver</code></pre>
<h2>引数つきメッセージ</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-properties"># messages.properties
greeting=こんにちは {0} さん、{1} 件の通知があります
error.required={0} は必須項目です
error.range={0} は {1} 以上 {2} 以下で入力してください</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- テンプレート -->
<p th:text="#{greeting(${user.name}, ${notificationCount})}">
こんにちは ゲスト さん
</p>
<p th:text="#{error.required('メールアドレス')}">
必須エラー
</p>
<p th:text="#{error.range('年齢', 0, 120)}">
範囲エラー
</p></code></pre>
<h2>属性へのメッセージ展開</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- placeholder にメッセージ -->
<input type="text"
th:placeholder="#{form.name.placeholder}"
th:title="#{form.name.title}">
<!-- 文字列連結 -->
<a th:text="|#{home.welcome}, ${user.name}!|">Welcome, taro!</a>
<!-- 複数属性の一括 -->
<input type="text"
th:attr="placeholder=#{form.placeholder}, title=#{form.title}"></code></pre>
<h2>テキスト中に直接埋め込み</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- [[ ]] = th:text、[( )] = th:utext と同義 -->
<p>[[#{home.welcome}]] さん</p>
<!-- 条件・繰り返しと組み合わせ -->
<th:block th:if="${count > 0}">
<p th:text="#{notifications.summary(${count})}">通知あり</p>
</th:block></code></pre>
<h2>Controller で MessageSource を直接使う</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-java">@RestController
public class ApiController {
private final MessageSource messageSource;
public ApiController(MessageSource messageSource) {
this.messageSource = messageSource;
}
@GetMapping("/api/hello")
public String hello(Locale locale) {
return messageSource.getMessage("home.welcome", null, locale);
}
@GetMapping("/api/error")
public String error(Locale locale) {
return messageSource.getMessage(
"error.required",
new Object[]{"メールアドレス"},
locale
);
}
}</code></pre>
<h2>UTF-8 設定 (文字化け対策)</h2>
<p>古い Spring では <code>messages_ja.properties</code> を Unicode エスケープ (<code>こん...</code>) する必要がありました。Spring Boot 1.4+ では UTF-8 がデフォルトですが、明示推奨:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-properties"># application.properties
spring.messages.encoding=UTF-8
spring.thymeleaf.encoding=UTF-8
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true</code></pre>
<h2>FAQ</h2>
<p><strong>Q: <code>??home.welcome_ja_JP??</code> と表示される</strong><br />
A: メッセージキー未定義。<code>messages.properties</code> に定義漏れか、basename 設定ミス。</p>
<p><strong>Q: HTML タグを含むメッセージを出したい</strong><br />
A: <code>th:utext="#{key}"</code> でエスケープ無効化。XSS 注意。</p>
<p><strong>Q: 改行をメッセージに入れたい</strong><br />
A: properties に <code>\n</code> と書き、テンプレートで <code>style="white-space:pre-line"</code> で改行を表示。</p>