コンテンツにスキップ

メッセージ管理機能の設定⚓︎

SSR アプリケーションのメッセージ管理方針に関するアーキテクチャについては、こちら をご確認ください。

メッセージは用途に応じて以下の 2 種類に分類します。

  • 業務メッセージ

    業務ロジックで使用されるエラーメッセージや警告文など。

  • UI メッセージ

    ラベル、ボタン名、画面固有の文言といった画面表示用のメッセージ。

設定方法⚓︎

本設定で利用するフォルダーの構成は以下の通りです。

root/ -------------------------------------------------- root フォルダー
 ├ a-function/src/main/resources
 │ └ i18n/a-function ----------------------------------- a 機能の業務メッセージのプロパティファイルを一括管理するフォルダー
 │    ├ messages_en.properties ------------------------- a 機能の業務メッセージのプロパティファイル(英語)
 │    └ messages_ja.properties ------------------------- a 機能の業務メッセージのプロパティファイル(日本語)
 └ web/src/main/resources
   ├ i18n/ --------------------------------------------- UI メッセージのプロパティファイルを一括管理するフォルダー
   │  └ a-function/ ------------------------------------ a 機能 の UI メッセージのプロパティファイルを管理するフォルダー
   |     └ register/
   |        ├ register_en.properties ------------------- UI メッセージ(登録画面)のプロパティファイル(英語)
   |        └ register_ja.properties ------------------- UI メッセージ(登録画面)のプロパティファイル(日本語)
   │     
   └ templates/ ---------------------------------------- HTML ファイルを配置するフォルダー
      └ a-function/
         └ register/
            └ register.html ---------------------------- 登録画面の HTML ファイル

プロパティファイルの作成⚓︎

業務メッセージ⚓︎

業務メッセージのプロパティファイルは機能モジュールのプロジェクトの /src/main/resources/i18n フォルダーに作成します。 以下のように、メッセージコードとメッセージ文字列本体を格納するプロパティファイルを言語ごとに作成します。

messages_ja.properties
1
2
systemError=想定外のシステムエラーが発生しました
businessError=想定外の業務エラーが発生しました

UI メッセージ⚓︎

UI メッセージのプロパティファイルは web サブプロジェクトの /src/main/resources/i18n フォルダー配下に、画面ごとにフォルダーを作成して管理します。 メッセージコードは、アプリケーション内で重複しないように設定する必要があるため、以下のように <機能名>.<画面名>.<項目名> で設定します。

register_ja.properties
1
announcement.register.message1=お知らせ登録画面のメッセージ文字列

プロパティファイルの読込⚓︎

Spring Framework で提供されている PathMatchingResourcePatternResolver を利用して、プロパティファイルを読込みます。

また、 MessageSource で提供されている機能を利用して、プロパティファイルの末尾に _ja_en のような接尾辞を付与します。 これにより、ブラウザーの言語設定に応じて読み込むプロパティファイルを切り替えます。

サンプルアプリケーションの I18nConfig.java

以下のように、プレゼンテーション層を担うサブプロジェクトやシステム共通のサブプロジェクトの設定クラスにプロパティファイルを読み込む設定を記載します。

I18nConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.dressca.cms.systemcommon.config;

import java.io.IOException;
import java.util.Arrays;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

/**
 * 国際化対応を行う i18n の設定クラスです。
 */
@Configuration
public class I18nConfig {

  /**
   * メッセージプロパティファイルが読み込まれた、 {@link MessageSource} オブジェクトを Bean 登録します。
   * 
   * @return {@link MessageSource} オブジェクト。
   * @throws IOException 正常にプロパティファイルを読み込めなかった場合。
   */
  @Bean
  public MessageSource messageSource() throws IOException {
    ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
    ms.setDefaultEncoding("UTF-8");
    // キャッシュ時間(開発時は 0、運用時は適宜)
    ms.setCacheSeconds(3600);
    // ── i18n 以下をスキャン ──
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    // /i18n/messages_ja.properties や
    // /i18n/announcement/register/register_en.properties を拾う
    Resource[] resources = resolver.getResources("classpath*:i18n/**/*.properties");
    String[] baseNames = Arrays.stream(resources).map(resource -> {
      try {
        String uriStr = resource.getURI().toString();
        // "classpath:"付きのベース名を抽出
        return "classpath:" + uriStr.replaceAll("^.*?/i18n/", "i18n/")
            .replaceAll("(_[a-z]{2}(_[A-Z]{2})?)?\\.properties$", "");
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }).distinct().toArray(String[]::new);
    ms.setBasenames(baseNames);
    return ms;
  }
}

読み込むプロパティファイルは classpath: 配下の i18n/<フォルダー名>/<ファイル名> で指定します。 プロパティファイルが複数ある場合は、ファイルの間をカンマで区切ります。

多言語対応⚓︎

Spring Framework で提供されている AcceptHeaderLocaleResolver を利用して、ブラウザーの言語設定を取得します。

対応していない言語の場合は、 AcceptHeaderLocaleResolversetDefaultLocale メソッドを利用して日本語を使用するようにします。

サンプルアプリケーションの LocaleConfig.java

以下のように、プレゼンテーション層を担うサブプロジェクトの設定クラスにデフォルトのロケール設定を記載します。 言語コードを定数で管理しない場合、ハイライト部を java.util.Locale パッケージの Locale.JAPANESE に置き換えます。

LocaleConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.dressca.cms.web.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import com.dressca.cms.systemcommon.constant.LanguageCodeConstants;

/**
 * Locale の設定クラスです。
 */
@Configuration
public class LocaleConfig {

  /**
   * Accept-Language ヘッダをそのまま使う {@link LocaleResolver} オブジェクトを Bean 登録します。
   * クッキーや URL パラメータによる切り替えは不要です。
   * 
   * 
   * @return {@link LocaleResolver} オブジェクト。
   */
  @Bean
  public LocaleResolver localeResolver() {
    AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
    resolver.setDefaultLocale(LanguageCodeConstants.LOCALE_JA);
    return resolver;
  }
}

メッセージの取得⚓︎

読み込んだプロパティファイルのメッセージを取得するためには、 MessageSource インターフェースを利用します。

また、 @Service@Controller@Component といった Bean 登録されたクラス内で MessageSource を利用する場合は、 @Autowired やコンストラクタインジェクションによる DI で実装します。

以下は、ユーザー名やメールアドレスなどの識別情報からユーザーを取得する UserDetailsServiceImpl.java クラスの例です。

サンプルアプリケーションの UserDetailsServiceImpl.java
UserDetailsServiceImpl.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.dressca.cms.authentication.applicationcore;

import com.dressca.cms.authentication.applicationcore.constant.ExceptionIdConstants;
import com.dressca.cms.authentication.applicationcore.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import java.util.Locale;
import org.springframework.context.MessageSource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * {@link UserDetailsService} を継承するクラス。 指定された名前のユーザー情報を返します。
 */
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

  private final UserRepository userRepository;
  private final MessageSource messages;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // Dressca CMS では、ログイン時にメールアドレスを使用するため、
    // UserRepository を使用してメールアドレスに対応するユーザー情報を取得します。
    UserDetails userDetails = userRepository.findByEmail(username);
    if (userDetails == null) {
      throw new UsernameNotFoundException(messages.getMessage(ExceptionIdConstants.E_USER_NOT_FOUND,
          new Object[] {username}, Locale.getDefault()));
    }
    return userDetails;
  }
}

HTML とのバインディング⚓︎

テンプレートエンジンである Thymeleaf の機能を利用して、 #{} 構文によってメッセージを参照します。 以下のように、構文内にはプロパティファイルで定義したメッセージコードを記述します。

register.html
1
<h1 th:text="#{announcement.register.message1}"></h1>