스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - sec03
출처 : 스프링 MVC 2편
우리는 여태까지 화면에 보이는 문구를 일일히 하나씩 다 쳐주었다. 넘나 귀찮
만약에 이름을 다 바꿔야하는 일이 생기면? 우리는 label에 있는 단어들을 몇 십개의 파일을 일일히 찾아가서 고쳐줘야함 WHY? 우리가 하드코딩 해놔서
이런 메시지를 한 곳에서 관리하도록 하는 것을 메시지 기능이라고 한다.
주로 messages.properties
라는 메시지 관리용 파일을 만들어서 다음과 같이 작성해둔다.
item=상품
item.id=상품 ID
item.itemName=상품명
그래서 우리는 이렇게 된 것을 어떻게 불러오나?!
각 HTML에서 th:text="#{}"안에 key값을 불러와서 사용함
<label for="itemName" th:text="#{item.itemName}"></label>
보통 웹사이트에는 한국인만 들어오는 것이 아니라, 외국인들도 들어오는 경우 생김
그럴 때 메시지를 영어로 전달하기 위해 국제화를 진행한다.
보통 영어가 많이 쓰이니깐, 영어로 지정한다고 했을 때 우리는 messages_en.properties
라는 파일을 따로 하나 생성한다
item=Item
item.id=Item ID
item.itemName=Item Name
어떤 언어권에서 접근한 것인지는 인식하는 방법은 HTTP accept-language
해더 값을 사용하거나 사용자가 직접 언어를 선택하도록 하고, 쿠키 등을 사용해서 처리하면 됨
메시지와 국제화 기능을 직접 구현할 수도 있겠지만, 스프링은 기본적인 메시지와 국제화 기능을 모두 제공해주고 타임리프도 스프링이 제공하는 메시지와 국제화 기능을 편리하게 통합해서 제공해줌
메시지 관리 기능을 사용하려면 스프링이 제공하는 MessageSource
를 스프링 빈으로 등록하면 되는데, MessageSource
는 인터페이스이기 때문에 구현체인 ResourceBundleMessageSource
를 스프링 빈으로 등록하면 됨
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("messages", "errors");
messageSource.setDefaultEncoding("utf-8");
return messageSource;
}
basenames
: 설정 파일의 이름을 지정messages
로 지정하면 messages.properties 파일을 읽어서 사용=> 이건 직접 설정하는 방법이고, 스프링 부트를 사용하면 자동으로 스트링 빈으로 등록됨
스프링 부트를 사용하면 appllication.properties
부분에 한 마디만 적어주면 됨
spring.messages.basename=messages,config.i18n.messages
얘도 디폴트로 messages.properties가 자동으로 인식됨
그리고 메시지 부분에 매개변수를 받을 수 있는데 hello.name=hello {0}
이렇게 {}를 사용해주면 됨
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)
=> 코드, 매개변수, 언어 순
언어 정보가 null이니깐 디폴트인 messages.properties가 조회됨
메시지가 없는 경우
@Test
void notFoundMessageCode() {
assertThatThrownBy(() -> ms.getMessage("no_code", null, null))
.isInstanceOf(NoSuchMessageException.class);
}
@Test
void notFoundMessageCodeDefaultMessage() {
String result = ms.getMessage("no_code", null, "기본 메시지", null);
assertThat(result).isEqualTo("기본 메시지");
}
만약에 메시지가 없는 경우에는 NoSuchMessageException
발생함
+) 예외가 발생하는 코드를 테스트할 때는 asserThatThrownBy를 쓰고 isInstanceOf를 통해 검사
메시지가 없어도 기본 메시지(defaultMessage)를 사용하면 기본 메시지가 반환됨
매개변수를 사용 할 경우
@Test
void argumentMessage() {
String result = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
assertThat(result).isEqualTo("안녕 Spring");
}
{}부분안에 매개변수를 전달해서 치환이 가능하다
위와 같은 경우는 Spring을 넣어줌으로써 "안녕 Spring"이 출력되게 된다
locale
정보를 기반으로 국제화 파일을 선택한다.
ex) Locale이 en_US 의 경우 messages_en_US messages_en messages 순서로 찾는다.
=> Locale 에 맞추어 구체적인 것이 있으면 구체적인 것을 찾고, 없으면 디폴트를 찾는다고 이해하면 된다.
@Test
void defaultLang() {
assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");
//locale 정보가 없으므로 messages 를 사용
assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
//정보가 있지만, message_ko 가 없으므로 messages 를 사용
assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
//정보가 Locale.ENGLISH 이므로 messages_en 을 찾아서 사용
}
총 정리를 해보자면, 메시지는 messages.properties
파일을 만들고 안에 메시지 내용을 적고 타임리프에서는 이를 사용하기 위해 #{...}를 사용한다.
label.item=상품
label.item.id=상품 ID
label.item.itemName=상품명
page.items=상품 목록
page.item=상품 상세
button.save=저장
button.cancel=취소
메시지의 이름을 작성할 때 page, label, button을 구분해서 작성을 했다. 그래서 각 구분별로 적용하면 됨
파라미터를 사용하고자 하면
hello.name=안녕 {0}
=><p th:text="#{hello.name(${item.itemName})}"></p>
근데 국제화 적용은 어떻게 해?
우리가 타임리프에서 #{...}를 통해 가져오도록 해두었기 때문에 일일히 만들어야할 필요는 없다!
스프링의 국제화 메시지 선택
앞서 MessageSource 테스트에서 보았듯이 메시지 기능은 Locale 정보를 알아야 언어를 선택할 수 있음
결국 스프링도 Locale 정보를 알아야 언어를 선택할 수 있는데, 스프링은 언어 선택시 기본으로 Accept-Language
헤더의 값을 사용함
LocaleResolver
스프링은 Locale 선택 방식을 변경할 수 있도록 LocaleResolver 라는 인터페이스를 제공하는데, 스프링 부트는 기본으로 Accept-Language 를 활용하는 AcceptHeaderLocaleResolver
를 사용함
public interface LocaleResolver {
Locale resolveLocale(HttpServletRequest request);
void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}
LocaleResolver 변경
만약 Locale 선택 방식을 변경하려면 LocaleResolver 의 구현체를 변경해서 쿠키나 세션 기반의 Locale 선택 기능을 사용할 수 있음
=> ex) 고객이 직접 Locale 을 선택하도록 하는 것이다.
근데 보통 국제화는 잘 안쓴다고 하더이다...