검색결과 리스트
국제화에 해당되는 글 1건
- 2022.06.12 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 7
글
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 7
인프런 강의 46일차.
- 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 1 (김영한 강사님)
- 1편에서 배운 MVC를 활용할 수 있는 기술 습득
- 타입 컨버터, 파일 업로드, 활용, 쿠키, 세션, 필터, 인터셉터, 예외 처리, 타임리프, 메시지, 국제화, 검증 등등
3. 메시지, 국제화
3.1 프로젝트 설정
- 스프링 통합과 폼에서 개발한 상품 관리 프로젝트를 일부 수정해서 message-start 라는 프로젝트로 변환
- 샘플소스의 message-start 의 프로젝트를 message로 변경 후 import
3.2 메시지, 국제화 소개
- '상품명'이라는 단어를 모두 '상품이름'으로 고쳐달라는 요구사항이 접수된 경우 여러 화면에 보이는 상품명, 가격, 수량 등, label 에 있는 단어를 변경하려면 다음 화면들을 다 찾아가면서 모두 변경해야 한다.(하드코딩)
- 메시지 : 다양한 메시지를 한 곳에서 관리하도록 하는 기능
<!-- 메시지 관리용 파일 (messages.properties) -->
item=상품
item.id=상품 ID
item.itemName=상품명
item.price=가격
item.quantity=수량
- 메시지 관리용 properties 파일 생성.
- 각 HTML에서는 해당 데이터를 key값으로 불러서 사용 가능하다.
* addForm.html 내에서 사용
<label for="itemName" th:text="#{item.itemName}"></label>
* editForm.html 내에서 사용
<label for="itemName" th:text="#{item.itemName}"></label>
3.3 국제화
- 국제화 : 메시지에서 설명한 메시지 파일( messages.properties )을 각 나라별로 별도로 관리하는 것
* 영어용 메시지 관리 파일
<!-- 영어용 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=수량
* 접근 국가 인식 방법
- HTTP accept-language 헤더 값 or 사용자가 직접 언어 선택 후 쿠키 등을 사용해서 처리하면 된다.
3.4 스프링 메시지 소스 설정
- 스프링은 기본적인 메시지 관리 기능을 제공한다
- 메시지 관리 기능을 사용하려면 스프링이 제공하는 MessageSource 를 스프링 빈으로 등록하면 되는데, MessageSource 는 인터페이스이다. 따라서 구현체인 ResourceBundleMessageSource 를 스프링 빈으로 등록하면 된다.
* 직접 등록하는 방법
/**
* 국제화 기능을 적용하려면 messages_en.properties , messages_ko.properties 와 같이
* 파일명 마지막에 언어 정보를 주면된다. 만약 찾을 수 있는 국제화 파일이 없으면
* messages.properties (언어정보가 없는 파일명)를 기본으로 사용한다
*/
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
//basenames : 설정 파일의 이름을 지정한다.
//파일의 위치는 /resources/messages.properties 에 두면 된다.
//여러 파일을 한번에 지정할 수 있다. 여기서는 messages , errors 둘을 지정했다.
messageSource.setBasenames("messages", "errors");
//defaultEncoding : 인코딩 정보를 지정한다. utf-8 을 사용하면 된다.
messageSource.setDefaultEncoding("utf-8");
return messageSource;
}
* 스프링 부트
- 스프링 부트를 사용하면 스프링 부트가 MessageSource 를 자동으로 스프링 빈으로 등록한다
* 스프링 부트
- 메시지 소스 설정 스프링 부트를 사용하면 메시지 소스를 설정할 수 있다.
spring.messages.basename=messages,config.i18n.messages
- application.properties
* 스프링 부트 메시지 소스 기본 값
spring.messages.basename=messages
- MessageSource 를 스프링 빈으로 등록하지 않고, 스프링 부트와 관련된 별도의 설정을 하지 않으면 messages 라는 이름으로 기본 등록된다.
- 따라서 messages_en.properties , messages_ko.properties , messages.properties 파일만 등록하면 자동으로 인식된다.
* 메시지 파일 만들기
- messages.properties :기본 값으로 사용(한글)
- messages_en.properties : 영어 국제화 사용
hello=안녕
hello.name=안녕 {0}
- resources/messages.properties
hello=hello
hello.name=hello {0}
- resources/messages_en.properties
3.5 스프링 메시지 소스 사용
- 스프링은 기본적인 메시지 관리 기능을 제공한다
package hello.itemservice.domain.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 static org.assertj.core.api.Assertions.*;
@SpringBootTest
public class MessageSourceTest {
@Autowired
MessageSource ms;
@Test
void helloMessage() {
//가장 단순한 테스트
String result = ms.getMessage("hello", null, null);
assertThat(result).isEqualTo("안녕");
}
}
- java/hello/itemservice/domain/message/MessageSourceTest.java
- 메시지 테스트를 위한 java
- ms.getMessage("hello", null, null)
> code: hello
> args: null
> locale: null
- locale 정보가 없으면 basename 에서 설정한 기본 이름 메시지 파일을 조회한다.
- basename 으로 messages 를 지정 했으므로 messages.properties 파일에서 데이터 조회한다.
@Test
void notFoundMessageCode() {
//no code는 messages.properties에 정의된 코드가 아니므로 NoSuchMethodException 에러가 발생함.
assertThatThrownBy(() -> ms.getMessage("no code", null, null)).isInstanceOf(NoSuchMessageException.class);
}
@Test
void notFoundMessageCodeDefaultMessage() {
//3번째 파라미터는 찾는 메시지가 없는 경우 (NoSuchMessageException) 표시할 디폴트 메시지를 설정한다.
String result = ms.getMessage("no code", null, "기본 메시지", null);
assertThat(result).isEqualTo("기본 메시지");
}
- java/hello/itemservice/domain/message/MessageSourceTest.java
- 해당하는 메시지가 없는 케이스 테스트에 추가
@Test
void argumentMessage() {
//messages.properties에서 정의한 {0} 부분은 메소드 호출 시 매개변수를 전달해서 치환할 수 있다.
//hello.name=안녕 {0}
//Spring 단어를 매개변수로 전달 시 결과물 : 안녕 Spring
//매개변수는 Object Array로 선언해서 전달해야한다.
String result = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
assertThat(result).isEqualTo("안녕 Spring");
}
- java/hello/itemservice/domain/message/MessageSourceTest.java
- 파라미터로 값을 전달받는 케이스 테스트에 추가
* 국제화 파일 선택
- locale 정보를 기반으로 국제화 파일을 선택한다
- Locale이 en_US 의 경우 messages_en_US -> messages_en -> messages 순서로 찾는다.
- Locale 에 맞추어 정의된 것이 있으면 정의된 것을 찾고, 없으면 디폴트를 적용
@Test
void defaultLang() {
//locale 파라미터가 null 이므로 디폴트인 messages.properties 정보 사용
assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");
//locale 파라미터가 Locale.KOREA 이지만 messages_ko.properties가 없으므로 messages.properties 정보 사용
assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
//locale 파라미터가 Locale.ENGLISH 이므로 messages_en.properties 정보 사용
assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
}
- java/hello/itemservice/domain/message/MessageSourceTest.java
- 국제화 케이스 테스트에 추가
3.6 웹 애플리케이션에 메시지 적용하기
- 실제 웹 애플리케이션에 메시지를 적용해보자
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=취소
- resources/messages.properties
- 상품 코드에 적용할 메시지 messages.properties에 추가
* 타임리프 메시지 적용
- 타임리프의 메시지 표현식 #{...} 를 사용하면 스프링의 메시지를 편리하게 조회할 수 있다.
- 예를 들어서 방금 등록한 상품이라는 이름을 조회하려면 #{label.item} 이라고 하면 된다.
<!-- 렌더링 전 HTML -->
<div th:text="#{label.item}"></h2>
<!-- 렌더링 후 HTML -->
<div>상품</h2>
- 메시지 코드 타임리프 적용
* 타임리프 템플릿 파일에 메시지를 적용해보자.
<!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>
<h4 class="mb-3">상품 입력</h4>
<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="formcontrol" 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>
- 메시지 로직을 적용한 addForm.html
* 페이지 이름에 적용
<!-- as-is -->
<h2>상품 등록 폼</h2> ->
<!-- to-be -->
<h2 th:text="#{page.addItem}">상품 등록</h2>
* 레이블에 적용
<!-- as-is -->
<label for="itemName">상품명</label>
<!-- to-be -->
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<label for="price" th:text="#{label.item.price}">가격</label>
<label for="quantity" th:text="#{label.item.quantity}">수량</label>
* 버튼에 적용
<!-- as-is -->
<button type="submit">상품 등록</button>
<!-- to-be -->
<button type="submit" th:text="#{button.save}">저장</button>
<button type="button" th:text="#{button.cancel}">취소</button>
<!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.updateItem}">상품 수정</h2>
</div>
<form action="item.html" th:action th:object="${item}" method="post">
<div>
<label for="id" th:text="#{label.item.id}">상품 ID</label>
<input type="text" id="id" th:field="*{id}" class="form-control" readonly>
</div>
<div>
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text" id="itemName" th:field="*{itemName}" class="form-control">
</div>
<div>
<label for="price" th:text="#{label.item.price}">가격</label>
<input type="text" id="price" th:field="*{price}" class="formcontrol">
</div>
<div>
<label for="quantity" th:text="#{label.item.quantity}">수량</label>
<input type="text" id="quantity" th:field="*{quantity}" class="form-control">
</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='item.html'"
th:onclick="|location.href='@{/message/items/{itemId}(itemId=${item.id})}'|"
type="button" th:text="#{button.cancel}">취소</button>
</div>
</div>
</form>
</div> <!-- /container -->
</body>
</html>
- 메시지 로직을 적용한 editForm.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.item}">상품 상세</h2>
</div>
<!-- 추가 -->
<h2 th:if="${param.status}" th:text="'저장 완료'"></h2>
<div>
<label for="itemId" th:text="#{label.item.id}">상품 ID</label>
<input type="text" id="itemId" name="itemId" class="form-control" value="1" th:value="${item.id}" readonly>
</div>
<div>
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text" id="itemName" name="itemName" class="form-control" value="상품A" th:value="${item.itemName}" readonly>
</div>
<div>
<label for="price" th:text="#{label.item.price}">가격</label>
<input type="text" id="price" name="price" class="form-control" value="10000" th:value="${item.price}" readonly>
</div>
<div>
<label for="quantity" th:text="#{label.item.quantity}">수량</label>
<input type="text" id="quantity" name="quantity" class="form-control" value="10" th:value="${item.quantity}" readonly>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-primary btn-lg"
onclick="location.href='editForm.html'"
th:onclick="|location.href='@{/message/items/{itemId}/edit(itemId=${item.id})}'|"
type="button" th:text="#{page.updateItem}">상품 수정</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="#{page.items}">목록으로</button>
</div>
</div>
</div> <!-- /container -->
</body>
</html>
- 메시지 로직을 적용한 item.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">
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="py-5 text-center">
<h2 th:text="#{page.items}">상품 목록</h2>
</div>
<div class="row">
<div class="col">
<button class="btn btn-primary float-end"
onclick="location.href='addForm.html'"
th:onclick="|location.href='@{/message/items/add}'|"
type="button" th:text="#{page.addItem}">상품 등록</button>
</div>
</div>
<hr class="my-4">
<div>
<table class="table">
<thead>
<tr>
<th th:text="#{label.item.id}">ID</th>
<th th:text="#{label.item.itemName}">상품명</th>
<th th:text="#{label.item.price}">가격</th>
<th th:text="#{label.item.quantity}">수량</th>
</tr>
</thead>
<tbody>
<tr th:each="item : ${items}">
<td><a href="item.html" th:href="@{/message/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
<td><a href="item.html" th:href="@{|/message/items/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
<td th:text="${item.price}">10000</td>
<td th:text="${item.quantity}">10</td>
</tr>
</tbody>
</table>
</div>
</div> <!-- /container -->
</body>
</html>
- 메시지 로직을 적용한 items.html
* 파라미터는 다음과 같이 사용할 수 있다.
hello.name=안녕 {0}
<p th:text="#{hello.name(${item.itemName})}"></p
- "안녕 itemName" 으로 변경됨
3.7 웹 애플리케이션에 국제화 적용하기
- 실제 웹 애플리케이션에 국제화를 적용해보자
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
- resources/messages_en.properties
- 상품 코드에 적용할 국제화 messages_en.properties에 추가
- 메시지를 적용하면서 #{...} 로 템플릿을 변경해두었기 때문에 국제화는 바로 적용이 되었다.
* 웹으로 확인하기
- 웹 브라우저의 언어 설정 값을 변경하면서 국제화 적용을 확인해보자.
- 크롬 브라우저 설정 언어를 검색하고, 우선 순위를 변경하면 된다.
- 우선순위를 영어로 변경하고 테스트해보자.
- 웹 브라우저의 언어 설정 값을 변경하면 요청시 Accept-Language 의 값이 변경된다
- 메시지 기능은 Locale 정보를 알아야 언어를 선택할 수 있다. 결국 스프링도 Locale 정보를 알아야 언어를 선택할 수 있는데, 스프링은 언어 선택시 기본으로 AcceptLanguage 헤더의 값을 사용한다
* LocaleResolver
- 스프링은 Locale 선택 방식을 변경할 수 있도록 LocaleResolver 라는 인터페이스를 제공하는데, 스프링 부트는 기본으로 Accept-Language 를 활용하는 AcceptHeaderLocaleResolver 를 사용한다.
* LocaleResolver 변경
- 만약 Locale 선택 방식을 변경하려면 LocaleResolver 의 구현체를 변경해서 쿠키나 세션 기반의 Locale 선택 기능을 사용할 수 있다.
- 예를 들어서 고객이 직접 Locale 을 선택하도록 하는 것이다. 관련해서 LocaleResolver 를 검색하면 된다.
'Spring 정리' 카테고리의 다른 글
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 9 (0) | 2022.06.26 |
---|---|
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 8 (0) | 2022.06.12 |
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 6 (0) | 2022.06.12 |
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 5 (0) | 2022.06.08 |
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 4 (0) | 2022.06.03 |