Spring Message | JeongKeepsCalm

Spring Message

메시지 관리 기능 사용

1
2
3
4
5
6
7
@Bean
public MessageSource messageSource() {
  ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
  ms.setBasenames("messages", "errors");
  ms.setDefaultEncoding("utf-8");
  return ms;
}

basenames
설정 파일 이름 지정
messages로 지정하면, messages.properties 파일을 읽어 사용
파일 위치: /resources/messages.properties
여러 파일을 한번에 지정 가능(ms.setBasenames(“messages”, “errors”);)


  • 스프링 부트는 MessageSource를 자동으로 스프링 빈으로 등록한다.
  • 스프링 부트 메시지 소스 설정
    • application.properties
    • spring.messages.basename=messages,config.i18n.messages
  • 스프링 부트 메시지 소스 기본 값
    • spring.messages.basename=messages
    • 기본값이 messages이므로 messages_en.properties, messages_kr.properties, messages.properties 파일을 등록하면 자동으로 인식한다.



Message Source Test

1
2
3
4
5
6
7
# messages.properties
hello=안녕
hello.name=안녕 {0}

# messages_en.properties
hello=hello
hello.name=hello {0}
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
49
50
51
52
53
54
@SpringBootTest
public class MessageSourceTest {

  @Autowired
  MessageSource ms;

  @Test
  void helloMessage() {
    /* locale 정보가 없으면 basename 에서 설정한 기본 이름 메시지 파일을 조회 */
    String result = ms.getMessage("hello", null, null);
    Assertions.assertThat(result).isEqualTo("안녕");
  }

  @Test
  void helloMessageArgs() {
    /* 매개변수 사용 */
    String result = ms.getMessage("hello.name", new Object[]{new String("김춘배")}, null);
    Assertions.assertThat(result).isEqualTo("안녕 김춘배");
  }

  @Test
  void notFoundMessageCode() {
    /* 메시지가 없는 경우 NoSuchMessageException 발생 */
    Assertions.assertThatThrownBy(() -> ms.getMessage("no_code", null, null))
            .isInstanceOf(NoSuchMessageException.class);
  }

  @Test
  void notFoundMessageCodeDefaultMessage() {
    /* 지정된 메시지가 없어도 기본 메시지를 설정하여 활용 가능 */
    String result = ms.getMessage("no_code", null, "Default Message :::", null);
    Assertions.assertThat(result).isEqualTo("Default Message :::");
  }

   @Test
  void defaultLanguage() {
    /*
    * locale 정보가 없음(null)
    *   -> 시스템 기본 locale이 ko_KR 이므로 messages_ko.properties 조회
    *   -> 조회 실패
    *   -> messages.properties
    * */
    Assertions.assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");

    /* locale 정보가 있지만, message_ko 가 없으므로 messages 를 사용 */
    Assertions.assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
  }

  @Test
  void EnglishLanguage() {
    Assertions.assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
  }

}



Thymeleaf Message 적용: #{…}

  • 렌더링 전
    • <div th:text="#{label.item}"></h2>
  • 렌더링 후
    • <div>상품</h2>
  • 파라미터 추가
    • hello.name=안녕 {0}
    • <p th:text="#{hello.name(${item.itemName})}"></p>



웹 브라우저의 언어 설정 값을 변경

  • 크롬 브라우저 → 설정 → 언어 변경
    • HTTP 요청 헤더 내 Accept-Language 속성의 값이 변경된다.



LocaleResolver

  • 스프링은 Locale 선택 방식 변경할 수 있도록 LocaleResolver 인터페이스 제공
  • 스프링 부트는 AcceptHeaderLocaleResolver 인터페이스를 사용해서 Accept-Language를 활용한다.
1
2
3
4
5
6
7
8
9
public interface LocaleResolver {

  Locale resolveLocale(HttpServletRequest request);

  void setLocale(HttpServletRequest request
    , @Nullable HttpServletResponseresponse
    , @Nullable Locale locale);

}



MessageCodesResolver Test

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
49
public class MessageCodesResolverTest {

  /**
   * MessageCodesResolver:          인터페이스
   * DefaultMessageCodesResolver:   구현체
   *
   */
  MessageCodesResolver codesResolver = new DefaultMessageCodesResolver();

  @Test
  void messageCodesResolverObject() {
    String[] messageCodes = codesResolver.resolveMessageCodes("required", "item");
    Assertions.assertThat(messageCodes).containsExactly("required.item", "required");
  }

  @Test
  void messageCodesResolverField() {
    String[] messageCodes = codesResolver.resolveMessageCodes("required", "item", "itemName", String.class);
    Assertions.assertThat(messageCodes).containsExactly(
            "required.item.itemName",
            "required.itemName",
            "required.java.lang.String",
            "required"
    );
  }

  /**
   * DefaultMessageCodesResolver의 기본 메시지 생성 규칙
   *
   * 객체 오류의 경우 다음 순서로 2가지 생성
   *    1.: code + "." + object name
   *    2.: code
   *    예) 오류 코드: required, object name: item
   *    1.: required.item
   *    2.: required
   *
   *  필드 오류의 경우 다음 순서로 4가지 메시지 코드 생성
   *    1.: code + "." + object name + "." + field
   *    2.: code + "." + field
   *    3.: code + "." + field type
   *    4.: code
   *    예) 오류 코드: typeMismatch, object name "user", field "age", field type: int
   *    1. "typeMismatch.user.age"
   *    2. "typeMismatch.age"
   *    3. "typeMismatch.int"
   *    4. "typeMismatch"
   */

}