4 분 소요

이전 프로젝트에 이어서 메시지, 국제화 기능을 학습해보자.

메시지, 국제화 소개

메시지

악덕? 기획자가 화면에 보이는 문구가 마음에 들지 않는다고, 상품명이라는 단어를 모두 상품이름으로 고쳐달라고 하면 어떻게 해야할까?
여러 화면에 보이는 상품명, 가격, 수량 등, label 에 있는 단어를 변경하려면 다음 화면들을 다 찾아가면서 모두 변경해야 한다.
지금처럼 화면 수가 적으면 문제가 되지 않지만 화면이 수십개 이상이라면 수십개의 파일을 모두 고쳐야 한다.

왜냐하면 해당 HTML 파일에 메시지가 하드코딩 되어 있기 때문이다.
이런 다양한 메시지를 한 곳에서 관리하도록 하는 기능을 메시지 기능이라 한다.

예를 들어서 messages.properties 라는 메시지 관리용 파일을 만들고

item=상품 
item.id=상품 ID 
item.itemName=상품명 
item.price=가격 
item.quantity=수량

각 HTML들은 다음과 같이 해당 데이터를 key 값으로 불러서 사용하는 것이다.

addForm.html
<label for="itemName" th:text="#{item.itemName}"></label>

editForm.html
<label for="itemName" th:text="#{item.itemName}"></label>

국제화

메시지에서 한 발 더 나가보자.
메시지에서 설명한 메시지 파일( messages.properties )을 각 나라별로 별도로 관리하면 서비스를 국제화 할 수 있다.
예를 들어서 다음과 같이 2개의 파일을 만들어서 분류한다.

messages_en.properties

item=Item
item.id=Item ID
item.itemName=Item Name
item.price=price
item.quantity=quantity

messages_ko.properties

item=상품 
item.id=상품 ID 
item.itemName=상품명 
item.price=가격 
item.quantity=수량


영어를 사용하는 사람이면 messages_en.properties 를 사용하고, 한국어를 사용하는 사람이면 messages_ko.properties 를 사용하게 개발하면 된다.
이렇게 하면 사이트를 국제화 할 수 있다.

한국에서 접근한 것인지 영어에서 접근한 것인지는 인식하는 방법은 HTTP accept-language 해더 값을 사용하거나 사용자가 직접 언어를 선택하도록 하고, 쿠키 등을 사용해서 처리하면 된다.

메시지와 국제화 기능을 직접 구현할 수도 있겠지만, 스프링은 기본적인 메시지와 국제화 기능을 모두 제공한다.
그리고 타임리프도 스프링이 제공하는 메시지와 국제화 기능을 편리하게 통합해서 제공한다.
지금부터 스프링이 제공하는 메시지와 국제화 기능을 알아보자.

메시지 소스 설정

스프링

스프링은 기본적인 메시지 관리 기능을 제공한다.
메시지 관리 기능을 사용하려면 스프링이 제공하는 MessageSource 를 스프링 빈으로 등록하면 되는데, MessageSource 는 인터페이스이다. 따라서 구현체인 ResourceBundleMessageSource 를 스프링 빈으로 등록하면 된다.
하지만, 스프링 부트를 사용하면 스프링 부트가 MessageSource 를 자동으로 스프링 빈으로 등록하기 때문에 편리하게 사용할 수 있다! 😲

스프링 부트

스프링 부트를 사용하면 다음과 같이 메시지 소스를 설정할 수 있다.
/resources/application.properties

spring.messages.basename=messages,config.i18n.messages

1. 스프링 부트 메시지 소스 기본 값

/resources/application.properties

spring.messages.basename=messages

(위 코드는 default이기 때문에 직접 등록하지 않아도 자동으로 등록된다.)

MessageSource 를 스프링 빈으로 등록하지 않고, 스프링 부트와 관련된 별도의 설정을 하지 않으면 messages 라는 이름으로 기본 등록된다.
따라서 messages_en.properties , messages_ko.properties , messages.properties 파일만 등록하면 자동으로 인식된다.

2. 메시지 파일 만들기

  • messages.properties :기본 값으로 사용(한글)
  • messages_en.properties : 영어 국제화 사용

/resources/messages.properties

hello=안녕 
hello.name=안녕 {0}

/resources/messages_en.properties

hello=hello 
hello.name=hello {0}

3. 스프링 메시지 소스 사용

스프링이 제공하는 메시지 소스를 어떻게 사용하는지 테스트 코드를 통해서 학습해보자.
test/java/hello/itemservice/message.MessageSourceTest.java 퍼알울 생성한다.

1) messages.properties에 메시지가 있는 경우

@SpringBootTest // 스프링 부트가 자동으로 MessageSource를 등록해준다.
public class MessageSourceTest {

  @Autowired
  MessageSource ms;

  @Test
  void helloMessage(){
      String result = ms.getMessage("hello", null, null);
      assertThat(result).isEqualTo("안녕");
  }
}
  • ms.getMessage("hello", null, null)
    - code: hello
    - args: null
    - locale: null

가장 단순한 테스트는 메시지 코드로 hello 를 입력하고 나머지 값은 null 을 입력했다.
locale 정보가 없으면 basename 에서 설정한 기본 이름 메시지 파일을 조회한다.
basename 으로 messages 를 지정 했으므로 messages.properties 파일에서 데이터 조회한다.

한글 인코딩 오류가 발생한다면?

스크린샷 2022-06-30 오후 4 41 29

  1. 인텔리제이에서 Preference에 들어간다.
  2. [Editor] -> [File Encodings] 메뉴에서
    Global Encodeing, Project Encoding, Default encoding for properties files를 모두 UTF-8로 설정하고 인텔리제이를 재시작한다.
  3. 그 후 messages.properties 를 다시 열어보면 한글이 깨진상태로 기록되어있는데,
    이것을 다시 한글로 입력해서 저장한 후 테스트를 다시 실행한다.

스크린샷 2022-06-30 오후 4 43 51

2) messages.properties에 메시지가 없는 경우 & 기본 메시지

@SpringBootTest // 스프링 부트가 자동으로 MessageSource를 등록해준다.
public class MessageSourceTest {

  @Autowired
  MessageSource ms;

  @Test
  void notFountMessageCode(){
      assertThatThrownBy(() -> ms.getMessage("no_code", null, null))
              .isInstanceOf(NoSuchMessageException.class);
  }

  @Test
  void notFountMessageCodeDefaultMessage(){
      String result = ms.getMessage("no_code", null, "기본 메시지", null);
      assertThat(result).isEqualTo("기본 메시지");
  }
}
  • 메시지가 없는 경우에는 NoSuchMessageException 이 발생한다.
  • 메시지가 없어도 기본 메시지( defaultMessage )를 사용하면 기본 메시지가 반환된다.

3) 매개변수 사용

@SpringBootTest // 스프링 부트가 자동으로 MessageSource를 등록해준다.
public class MessageSourceTest {

  @Autowired
  MessageSource ms;

  @Test
  void defaultLang(){
      assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");
      assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
  }
}

다음 메시지의 {0} 부분은 매개변수를 전달해서 치환할 수 있다.
hello.name=안녕 {0} -> Spring 단어를 매개변수로 전달 -> 안녕 Spring

4) 국제화 파일 선택

locale 정보를 기반으로 국제화 파일을 선택한다.

  • Localeen_US 의 경우 messages_en_US -> messages_en -> messages 순서로 찾는다.
  • Locale에 맞추어 구체적인 것이 있으면 구체적인 것을 찾고, 없으면 디폴트를 찾는다고 이해하면 된다.
@SpringBootTest // 스프링 부트가 자동으로 MessageSource를 등록해준다.
public class MessageSourceTest {

  @Autowired
  MessageSource ms;

  @Test
  void defaultLang(){
      assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");
      assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
  }

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

4. 웹 어플리케이션에 메시지 적용하기

실제 웹 애플리케이션에 메시지를 적용해보자.

1) 메시지 추가 등록

먼저 메시지를 추가 등록하자.
messages.properties

label.item=상품
label.item.id=상품 ID
label.item.itemName=상품명
label.item.price=가격
label.item.quantity=수량

page.items=상품 목록
page.item=상품 상세
page.addItem=상품 등록
page.updateItem=상품 수정

button.save=저장
button.cancel=취소

2) 타임리프 템플릿 파일에 메시지 적용

타임리프의 메시지 표현식 #{...} 를 사용하면 스프링의 메시지를 편리하게 조회할 수 있다.
예를 들어서 방금 등록한 상품이라는 이름을 조회하려면 #{label.item} 이라고 하면 된다.

렌더링 전
<div th:text="#{label.item}"></h2>

렌더링 후
<div>상품</h2>

적용 대상

  • addForm.html
  • editForm.html
  • item.html
  • items.html

모두 비슷하니 addForm.html만 확인하자.

addForm.html
...
<div class="py-5 text-center">
      <h2 th:text="#{page.addItem}">상품 등록</h2>
  </div>

  <form action="item.html" th:action th:object="${item}" method="post">
      <div>
          <label for="itemName" th:text="#{label.item.itemName}">상품명</label>
          <input type="text" id="itemName" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
      </div>
      <div>
          <label for="price" th:text="#{label.item.price}">가격</label>
          <input type="text" id="price" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
      </div>
      <div>
          <label for="quantity" th:text="#{label.item.quantity}">수량</label>
          <input type="text" id="quantity" th:field="*{quantity}" class="form-control" placeholder="수량을 입력하세요">
      </div>

      <hr class="my-4">

      <div class="row">
          <div class="col">
              <button class="w-100 btn btn-primary btn-lg" type="submit" th:text="#{button.save}">저장</button>
          </div>
          <div class="col">
              <button class="w-100 btn btn-secondary btn-lg"
                      onclick="location.href='items.html'"
                      th:onclick="|location.href='@{/message/items}'|"
                      type="button" th:text="#{button.cancel}">취소</button>
          </div>
      </div>

  </form>
...
  • 이전 코드와의 차이점 스크린샷 2022-06-30 오후 5 35 47
파라미터 사용

참고로 파라미터는 다음과 같이 사용할 수 있다.
hello.name=안녕 {0}
<p th:text="#{hello.name(${item.itemName})}"></p>

5. 웹 어플리케이션에 국제화 적용하기

이번에는 웹 애플리케이션에 국제화를 적용해보자. 먼저 영어 메시지를 추가하자.

1) 영어 메시지 추가 등록

먼저 메시지를 추가 등록하자.
messages_en.properties

label.item=Item
label.item.id=Item ID
label.item.itemName=Item Name
label.item.price=price
label.item.quantity=quantity

page.items=Item List
page.item=Item Detail
page.addItem=Item Add
page.updateItem=Item Update

button.save=Save
button.cancel=Cancel

더 이상 할게 없다!

이것으로 국제화 작업은 거의 끝났다.
앞에서 템플릿 파일에는 모두 #{...} 를 통해서 메시지를 사용하도록 적용해두었기 때문이다.
웹 브라우저의 언어 설정 값을 변경하면서 국제화 적용을 확인해보자.
웹 브라우저의 언어 설정 값을 변경하면 요청시 Accept-Language 의 값이 변경된다.
(Accept-Language 는 클라이언트가 서버에 기대하는 언어 정보를 담아서 요청하는 HTTP 요청 헤더이다.)

  1. 크롬 브라우저의 우측 상단에 점 세개 버튼을 클릭하여 “설정”에 들어간다.
  2. 검색창에 “언어”를 검색한다.
  3. 영어를 가장 위로 이동시킨다.
    스크린샷 2022-06-30 오후 5 55 57
  4. 아래와 같이 모든 속성이 영어로 변경된 것을 확인할 수 있다.
    스크린샷 2022-06-30 오후 6 00 11


💛 개인 공부 기록용 블로그입니다. 👻

맨 위로 이동하기