Spring MVC v2 - 메시지, 국제화

Kwon Yongho·2023년 4월 26일
0

Spring-MVC-v2

목록 보기
3/13
post-thumbnail

메시지, 국제화

  1. 메시지, 국제화 소개
  2. 스프링 메시지 소스 설정
  3. 스프링 메시지 소스 사용
  4. 웹 애플리케이션에 메시지 적용하기
  5. 웹 애플리케이션에 국제화 적용하기

1. 메시지, 국제화 소개

메시지

  • 기획자가 상품명이라는 단어를 모두 상품이름으로 고쳐달라고 하면 어떨까?
  • 상품명, 가격, 수량 등, label에 있는 단어를 변경하려면 다음 화면들을 다 찾아가면서 모두 변경해야 한다. 왜냐하면 해당 HTML 파일에 메시지가 하드코딩 되어 있기 때문이다.
  • 이런 다양한 메시지를 한 곳에서 관리하도록 하는 기능메시지 기능이라 한다.

Ex) messages.properties

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

국제화

  • 메시지에서 설명한 메시지 파일( messages.properties )을 각 나라별로 별도로 관리하면 서비스를 국제화 할 수 있다.

Ex) messages_en.properties

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

Ex) messages_ko.properties

item=상품
item.id=상품 ID
item.itemName=상품명
item.price=가격
item.quantity=수량
  • 한국에서 접근한 것인지 영어에서 접근한 것인지는 인식하는 방법은 HTTP accept-language 해더 값을 사용하거나 사용자가 직접 언어를 선택하도록 하고, 쿠키 등을 사용해서 처리하면 된다.
  • 스프링은 기본적인 메시지와 국제화 기능을 모두 제공한다.
  • 타임리프도 스프링이 제공하는 메시지와 국제화 기능을 편리하게 통합해서 제공한다.

2. 스프링 메시지 소스 설정

  • 메시지 관리 기능을 사용하려면 스프링이 제공하는 MessageSource를 스프링 빈으로 등록하면 된다.
  • MessageSource는 인터페이스이다. 따라서 구현체인 ResourceBundleMessageSource를 스프링 빈으로 등록하면 된다.

직접 등록

@Bean
public MessageSource messageSource() {
   ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
   messageSource.setBasenames("messages", "errors");
   messageSource.setDefaultEncoding("utf-8");
   return messageSource;
}

스프링 부트

  • 스프링 부트를 사용하면 스프링 부트가 MessageSource 를 자동으로 스프링 빈으로 등록한다.

스프링 부트 메시지 소스 설정

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

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

messages.properties

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

messages_en.properties

hello=hello
hello.name=hello {0}

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

MessageSourceTest

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("안녕");
    }
}

오류해결

messages.properties의 위치를 resourse 아래 위치 시키지 않아서 MessageSourceTest에서 messages.properties를 인식하지 못하는 오류가 있었다.
  • locale 정보가 없으면 basename에서 설정한 기본 이름 메시지 파일을 조회한다. basename으로 messages를 지정 했으므로 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이 발생한다.
  • 메시지가 없어도 기본 메시지(defaultMessage)를 사용하면 기본 메시지가 반환된다.

매개변수 사용 추가

    // 매개변수 사용
    @Test
    void argumentMessage() {
        String result = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
        assertThat(result).isEqualTo("안녕 Spring");
    }
  • 다음 메시지의 {0} 부분은 매개변수를 전달해서 치환할 수 있다.

국제화 추가

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

    // 국제화 2
    @Test
    void enLang() {
        assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
    }
  • ms.getMessage("hello", null, null): locale 정보가 없으므로 messages를 사용
  • ms.getMessage("hello", null, Locale.ENGLISH): locale 정보가 Locale.ENGLISH 이므로 messages_en 을 찾아서 사용

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

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=취소

타임리프 메시지 적용

  • #{...} 를 사용
  • addForm, editForm, item, items 모두 변경

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>
    <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="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>

페이지 이름에 적용

  • <h2 th:text="#{page.addItem}">상품 등록</h2>

레이블에 적용

  • <label for="itemName" th:text="#{label.item.itemName}">상품명</label>

버튼에 적용

  • <button type="submit" th:text="#{button.save}">저장</button>

이렇게 바꿔서 저장하면 messages.properties의 내용을 변경하면 등록, 수정, 상세, 목록 모두 한번에 내용수정이 가능하다.

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

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

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 요청 헤더이다.
  • 스프링은 언어 선택시 기본으로 Accept-Language 헤더 값을 사용한다.

LocaleResolver

  • 스프링은 Locale 선택 방식을 변경할 수 있도록 LocaleResolver라는 인터페이스를 제공하는데, 스프링 부트는 기본으로 Accept-Language를 활용하는 AcceptHeaderLocaleResolver를 사용한다.

참고
김영한: 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술(인프런)
Github - https://github.com/b2b2004/Spring_MVC

0개의 댓글