메세지, 국제화 소개
기획자가 화면에 보이는 문구가 마음에 들지 않는다고 상품명이라는 단어를 모두 상품 이름으로 변경해달라고 한다.
이럴 때 상품명, 가격, 수량 등 label에 있는 단어를 변경하려면 여러 화면들을 다 찾아가면서 모두 변경해야한다.
지금처럼 그 수가 적다면 괜찮지만 많다면 모두 고치기 매우 힘들다.
(addForm.html, editForm.html 등등..)
왜냐하면 해당 HTML 파일들이 다 하드코딩 되어있기 때문이다.
이런 다양한 메세지를 한 곳에서 관리하도록 하는 기능을 메세지 기능이라고 한다.
예를들어 messages.properties라는 메세지 관리용 파일을 만들고
item=상품
item.id=상품 ID
item.itemName=상품명
등등등
각 HTML들은 다음과 같은 해당 데이터를 key 값으로 불러서 사용하는 것이다.
<label for="itemName" th:text="#{item.itemName}"></label>
(물론 이렇게 사용하려면 추가적인 코드를 작성해야 한다)
국제화
메세지에서 설명한 메세지 파일 (messages.properties)을 각 나라별로 별도로 관리하면 서비스를 국제화 할 수 있다.
(messages_en.properties)
item=Item
item.itemName=Item Name
...
(messages_ko.properties)
item=상품
item.id=상품 ID
item.itemName=상품명
...
이처럼 사이트를 국제화 할 수 있다.
위와같이 직접 구현하는 방법도 있지만, 스프링은 이러한 기본적인 메세지와 국제화 기능을 모두 제공한다.
messages.properties
hello=안녕
hello.name=안녕 {0}
messages_en.properties
hello=hello
hello.name=hello {0}
스프링이 제공하는 메세지 관리 기능을 사용하려면 MessageSource를 스프링 빈으로 등록하면 되는데, 스프링 부트에선 자동으로 스프링 빈으로 등록한다. 따라서 별도 설정이 굳이 필요없다는 말임
스프링 부트 메세지 소스 기본값은 (application.properties에서) spring.messages.basename=messages 이다.
(궁금하면 application.properties 관련 공식문서 더 찾아보기)
MessageSource의 인터페이스
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context;
import java.util.Locale;
import org.springframework.lang.Nullable;
public interface MessageSource {
@Nullable
String getMessage(String var1, @Nullable Object[] var2, @Nullable String var3, Locale var4);
String getMessage(String var1, @Nullable Object[] var2, Locale var3) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable var1, Locale var2) throws NoSuchMessageException;
}
인터페이스를 보면 코드를 포함한 일부 파라미터로 메세지를 읽어오는 기능도 제공한다.
package hello.itemservice.message;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import java.util.Locale;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@SpringBootTest
public class MessageSourceTest {
@Autowired
MessageSource ms;
@Test
void helloMessage() {
String result = ms.getMessage("hello", null, null);
assertThat(result).isEqualTo("안녕");
}
@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("기본 메세지");
}
@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");
}
}
별거 없다. 현재 내 IDE 기본값은 한국어이기 때문에 (정확히는 UTF-8, Transparent native-to-ascii conversion 옵션 사용)
null이어도 한국어가 default로 설정되어있다. 코드를 천천히 읽어보면 이해될것임
웹 애플리케이션 메세지 적용하기
실제 웹 애플리케이션에 메세지를 적용해보자
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=취소
타임리프의 메시지 표현식 #{...}를 사용하면 스프링의 메세지를 편리하게 조회할 수 있다.
예를 들어서 방금 등록한 상품이라는 이름을 조회하려면 #{label.item}이라고 한다.
addForm.html로 예시코드 작성
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.container {
max-width: 560px;
}
</style>
</head>
<body>
<div class="container">
<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>
</div> <!-- /container -->
</body>
</html>
이렇게 사용하면 된다. 그럼 나중에 하드코딩된 코드를 변경하지 않아도 일괄적으로 코드를 수정할 수 있게 되는 것이다.
참고로 파라미터는 다음과 같이 사용할 수 있다.
hello.name=안녕 {0}
<p th:text="#{hello.name(${item.itemName})}"></p>
웹 애플리케이션에 국제화 적용
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
이렇게만 해주면 국제화는 완료되었다. 왜냐면 템플릿 파일에 모두 #{...}를 통해서 메세지를 사용하도록 적용했기때문이다.
현재 방식은 Accept-language의 값을 통해 언어가 설정된다.
스프링의 MessageSource는 결국 Locale 정보를 알아야 언어를 선택할 수 있다.
accept-language 헤더값을 사용하거나, LocalResolver로 쿠키나 세션 기반의 Locale을 통해 언어를 선택하는 방법도 있다.
'Spring' 카테고리의 다른 글
| Bean Validation (0) | 2025.09.12 |
|---|---|
| 오류 코드와 메세지처리, Validator 분리 (0) | 2025.09.11 |
| 타임리프 - 스프링 통합과 폼 (1) | 2025.09.06 |
| 타임리프 - 기본 기능 (1) | 2025.08.31 |
| 상품 도메인 개발 - 뷰 템플릿, 타임리프 (1) | 2025.08.28 |
