기존 상품 등록 프로젝트를 그대로 가져온다. 강의에서 받을 수 있는 파일로 받는데, 이전 시간에 다룬 체크박스 등의 내용은 제외된다.
1. 개요
현재 프로젝트는 만약 html 상 하드코딩된 단어들을 변경해야 된다면, 일일이 찾아서 고쳐야하는 상황이다. 아래 사진에서 상품명, 가격, 수량 등을 바꿔야 한다면 직접 찾아가며 바꿔야되는 것이다.
이런 어려움을 해결해줄 수 있는 것이 메시지 이다. 단어들을 하드코딩하지 않고, 별도의 파일(messages.properties)에 놔두고 관리하는 것이다. 국제화 도 메시지에서 영어버전 파일은 messages_en.properties, 한글 버전 파일은 message_ko.properties로 두면 될 것이다. 그리고 HTTP의 accept-language 헤더 값을 보고 그에 맞춰서 메시지가 나가도록 하거나, 사용자의 쿠키 값에 따라서, 아니면 사용자가 선택할 수 있도록 하면 된다.
(예시)
item = 상품
item.id = 상품 ID
item.itemName=상품명
item.price=가격
...
2. 기초 세팅 및 테스트
스프링 메시지 소스 설정
원래는 스프링이 제공하는 MessageSource의 구현체인 ResourceBundleMessageSource를 빈으로 등록해야한다.
1
2
3
4
5
6
7
|
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messagesource = new ResourceBundleMessageSource();
messageSource.setBasenames("meesages", "errors");
messageSource.setDefaultEncoding("utf-8");
return messageSource;
}
|
cs |
basenaems 는 설정 파일의 이름을 지정하는데, 위 코드에서는 messages, errors 2개의 파일을 지정했다. messages.properties, errors.properties 파일을 읽어들이도록 설정하는 것이다. /resources/messages.properties와 같은 위치에 파일을 만들어두면 된다. 위 개요에서 살펴본대로, messages_en.properties와 같은 파일로 지정하면 국제화 처리를 할 수 있다.
defaultEncoding은 인코딩 정보를 저장한다. 여기서는 utf-8을 사용했다.
스프링 부트를 사용하면 MessageSource를 자동으로 스프링 빈으로 등록한다. 이를 위해 application.properties 안에 다음과 같은 코드를 작성해주면 된다.
spring.messages.basename=messages,config.i18n.messages
별도로 작성하지 않으면 spring.messages.basename=messages로 messages만 읽어들이도록 기본 설정이 되어있다.
메시지 파일 작성
resources 폴더 아래에 일반 파일로 messages.properties라고 만들면 된다. 기본 파일이 messages.properties이고, 영어 파일은 messages_en.properties로 만들면 된다. 그러면 이 messages 파일들을 자동으로 Bundle로 묶어준다.
그리고 메시지를 적용할 내용을 작성한다. 여기서 {0}은 나중에 파라미터로 받아올 배열의 인덱스를 표현하는 것이다. 테스트를 통해 이 파라미터가 어떻게 사용되는지 알아보자.
resources/messages.properties
1
2
|
hello=안녕
hello.name=안녕 {0}
|
cs |
resources/messages_en.properties
1
2
|
hello=hello
hello.name=hello {0}
|
cs |
테스트
테스트를 통해 기본적인 메시지 사용방법을 알아본다. 다만, 테스트 전에 인코딩 설정을 해주도록한다. File -> Settings -. File Encodings에 들어가서 아래 그림과 같이 인코딩 설정들을 UTF-8으로 변경해준다.
만약 설정을 바꾸었다면, 설정 변경 전 저장된 캐시를 날리기 위해 File-> Invalidate Caches...에 들어가서 캐시를 날리고 intelliJ를 재실행한다.
테스트 코드는 다음과 같다. @SpringBootTest 어노테이션을 써주면, MessageSource 클래스를 스프링부트가 알아서 찾아오기 때문에, @Autowired를 적용할 수 있다. 이렇게 되면 이전에 application.properties에서 설정해준 파일들을 찾아오게 된다.
application.properties
spring.messages.basename=messages,config.i18n.messages
getMessage의 인자는 4개이다(기본은 3개). 첫 번째는 파일 내부에 지정한 경로를 의미한다. 두 번째 인자는 {0}과 같이 지정한 배열 속의 요소로, 불러올때는 Object의 배열을 만들어서 불러와야 한다(argumentMessage 메서드 참조). 세 번째 인자는 추가로 지정할 수 있는 인자로, messages로 지정한 파일들 중에 인자로 불러오는 경로에 해당하는 파일이 없으면 NoSuchMessageException을 던지게 되는데 그때 출력할 기본 메시지를 설정한다. 마지막으로 네 번째 인자는 언어 설정에 관한 것으로 Locale을 지정하여 사용자에 따라 다른 언어가 출력되도록 할 수 있다. 뒷 부분 실습에서 살펴볼 것이다.
java/hello/itemservice/message/MessageSourceTest.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
|
@SpringBootTest
public class MessageSourceTest {
@Autowired
MessageSource ms;
@Test
void helloMessage() {
assertThat(ms.getMessage("hello", null, null))
.isEqualTo("안녕");
}
@Test
void notFoundMessageCode() {
assertThatThrownBy(() -> ms.getMessage("kkk", null, null))
.isInstanceOf(NoSuchMessageException.class);
}
@Test
void notFoundMessageCodeDefaultMessage() {
assertThat(ms.getMessage("kkk", null, "기본 메시지", null))
.isEqualTo("기본 메시지");
}
@Test
void argumentMessage() {
String message = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
assertThat(message).isEqualTo("안녕 Spring");
}
@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");
}
}
|
cs |
3. 예제에 적용해보기
만들어 놓은 프로젝트에 메시지를 적용해본다. 타임리프의 th:text=#{} 문법을 활용하면 된다. 우선 messages.properties 파일에 내용을 작성해준다. (messages_en.properties도 알맞게 작성해준다.)
resources/messages.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
hello=안녕
hello.name=안녕 {0}
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=취소
|
cs |
그리고 각 페이지 View단에서 하드코딩이 되어있던 부분에 타임리프 문법을 아래와 같이 모두 적용해주면 된다.
이제 서버를 실행시키면, messages.properties에 적용된대로 내용이 출력되는 것을 확인할 수 있다. 국제화를 위해서 messages_en.properties에 내용을 추가한다.
resources/messages_en.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
hello=hello
hello.name=hello {0}
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
|
cs |
그리고나서 크롬 브라우저에서는 설정 -> 고급 -> 언어 -> 화살표를 눌러서 영어를 우선순위로 두자. 그 다음 프로젝트의 웹페이지를 새로고침하면 전부 영어로 바뀌는 것을 확인할 수 있다. 이것은 View단에서 타임리프로 미리 지정해뒀기 때문이다. 이제 만약 "상품 이름"이라는 문자를 "상품명" 이라고 바꾸고 싶다면, messages.properties 파일에서 수정만 하면 전체 View에 적용될 것이다.
4. 추가 내용
실무에서는 'i18n -> messages -> 패키지들' 로 구성해놓고, 도메인별로 properties 파일들을 만들었다.
그리고 사진과 같이 CommonMessageException이 발생했을 때의 에러 메시지로 사용하거나, 또는 클라이언트로부터 전달받는 파라미터 오브젝트가 null 일때 에러를 던지는 상황에서 메시지를 활용했다.
참조
1. 인프런_스프링 MVC 2편 - 백엔드 웹개발 핵심 기술_김영한 님 강의
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2