Spring Boot での国際化(i18n)実装:メッセージ表示の多言語対応

Spring Boot の国際化(i18n)機能は、Spring Framework の MessageSource インターフェースと LocaleResolver を基盤としており、ユーザーのロケールに応じて動的にテキストを切り替えることが可能です。以下では、その動作原理と具体的な実装手順を解説します。

1. 動作原理

主要コンポーネント

コンポーネント役割
MessageSource ロケールに対応したメッセージを提供するインターフェース。デフォルト実装は ResourceBundleMessageSource で、.properties ファイルからメッセージを読み込む。
LocaleResolver 現在のリクエストにおけるロケール(例: ja_JP, en_US)を決定する。デフォルトでは HTTP ヘッダーの Accept-Language を使用。
LocaleChangeInterceptor(オプション) URL パラメータ(例: ?lang=ja_JP)による動的な言語切り替えを可能にするインターセプター。

処理フロー

  1. アプリ起動時:Spring Boot が自動的に classpath:i18n/messages*.properties をスキャンし、MessageSource を設定。
  2. リクエスト受信時
    • LocaleResolver がヘッダーやパラメーターから現在のロケールを取得。
    • コントローラーが messageSource.getMessage(key, args, locale) を呼び出し。
    • MessageSourcemessages_{locale}.properties から該当メッセージを返却。
  3. マッチしない場合:デフォルトファイル(messages.properties)またはシステムロケールにフォールバック(設定次第)。

2. 実装手順

ステップ1:リソースファイルの作成

src/main/resources/i18n/ 配下に以下のファイルを配置:

resources/
└── i18n/
    ├── messages.properties          # デフォルト
    ├── messages_ja_JP.properties    # 日本語
    ├── messages_zh_CN.properties    # 簡体字中国語
    └── messages_en_US.properties    # 英語(米国)

messages_ja_JP.properties の例

greeting=こんにちは、{0}さん!
user.login=ログイン

⚠️ .properties ファイルは **UTF-8** で保存し、IDE(例:IntelliJ IDEA)で「Transparent native-to-ascii conversion」を有効にしてください。これにより日本語などの非ASCII文字が正しく扱われます。

ステップ2:application.yml の設定

spring:
  messages:
    basename: classpath:i18n/messages
    encoding: UTF-8
    fallback-to-system-locale: false
    default-locale: ja_JP
    cache-duration: 3600s

ステップ3:URLパラメーターによる言語切り替えの有効化

デフォルトではヘッダーのみ対応のため、?lang=ja_JP 形式で切り替えられるようにカスタマイズします。

// config/LocaleConfig.java
@Configuration
public class LocaleConfig implements WebMvcConfigurer {

    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver resolver = new SessionLocaleResolver();
        resolver.setDefaultLocale(Locale.JAPAN);
        return resolver;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang");
        return interceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}

ステップ4:コントローラーでの利用

@RestController
public class GreetingController {

    private final MessageSource msgSrc;

    public GreetingController(MessageSource msgSrc) {
        this.msgSrc = msgSrc;
    }

    @GetMapping("/greet")
    public String greet(@RequestParam String name) {
        Locale current = LocaleContextHolder.getLocale();
        return msgSrc.getMessage("greeting", new Object[]{name}, current);
    }

    @GetMapping("/login-label")
    public String loginLabel() {
        return msgSrc.getMessage("user.login", null, LocaleContextHolder.getLocale());
    }
}

ステップ5:動作確認

  • GET /greet?name=太郎 → 「こんにちは、太郎さん!」
  • GET /greet?name=Alice&lang=en_US → 「Hello, Alice!」
  • GET /login-label?lang=zh_CN → 「登录」

3. 応用パターン

データベース駆動の動的国際化

MessageSource を独自実装し、DBからメッセージを取得。CaffeineやRedisでキャッシュすることでパフォーマンスを確保。

バリデーションメッセージの国際化

@NotBlank(message = "{field.name.required}")
private String name;

対応する ValidationMessages_ja_JP.properties にメッセージを定義。

フロントエンド連携(SPA対応)

API経由でJSON形式の言語リソースを提供し、Vue.jsやReact側で動的に読み込み。

4. よくある問題と対処

問題解決策
日本語が文字化けする プロパティファイルをUTF-8保存 + IDEのネイティブ変換設定を有効化
言語が切り替わらない LocaleChangeInterceptor が登録され、パラメーター名(例: lang)が一致しているか確認
リソースファイルが見つからない basename のパスが classpath:i18n/messages で、実際のファイルが resources/i18n/ 配下にあるか確認
本番環境で更新が反映されない spring.messages.cache-duration を適切に設定し、キャッシュを無効化または短縮

タグ: Spring Boot i18n MessageSource LocaleResolver internationalization

6月7日 19:03 投稿