스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 : Message 국제화

jkky98·2024년 7월 30일
0

Spring

목록 보기
16/77

메세지

<div class="menu__nav-home">
   <a href="./index.html#card-matching" class="menu__nav-home--button" id="home-button">
      <span class="sr-only">메인 페이지로 이동</span>
   </a>
</div>

span 태그 안에 하드코딩된 "메인 페이지로 이동"과 같은 텍스트는 작은 프로젝트에서는 직접 수정하면 큰 문제가 없을 수 있다. 그러나 큰 프로젝트에서는 이러한 텍스트가 여러 템플릿 파일에서 중복되어 있을 경우, 수정 요구사항이 생기면 많은 작업이 필요하게 된다.

이런 상황을 효율적으로 관리하기 위해 다양한 메시지를 한 곳에서 관리하고 필요한 템플릿이나 다른 영역에서 가져다 사용하는 방식을 사용하면 유지보수 관점에서 큰 이점이 있다. 이로 인해 수정 작업이 간소화되고, 텍스트 관리의 책임 분리가 가능해진다. 이를 통해 프로젝트의 확장성과 유지보수성이 크게 향상된다.

국제화

메시지는 스프링에서 보통 messages.properties를 기준으로 관리된다. 이러한 파일을 각 나라별로 관리한다면 서비스를 국제화할 수 있다.

만약 한국어와 영어를 지원하는 프로그램이라면
영어 이용자에 대해messages_en.properties 를 사용하고,
한국어 이용자에 대해서는 messages_ko.properties 를 사용하게 개발하면 된다.

이용자 구별은 HTTP accept-language 헤더 값을 사용하여 구별하거나 이용자가 직접 언어를 선택하도록 할 수 있다(쿠키 처리).

스프링 메시지 소스 설정

스프링 메세지 관리 기능을 사용하기 위해서는 스프링이 제공하는 MessageSource를 빈에 등록하면 되는데, 스프링 부트는 이를 프로젝트에 미리 구성해준다.

직접등록을 위해서는 다음과 같이 작업하면 된다.

@Bean
public MessageSource messageSource() {
	ResourceBundleMessageSource messageSource = new
	ResourceBundleMessageSource();
	messageSource.setBasenames("messages", "errors");
	messageSource.setDefaultEncoding("utf-8");
	return messageSource;
}
`// 이 내용을 부트는 자동적으로 등록해준다.

테스트 코드로 사용해보기

// ./resource

// messages.properties
hello=안녕
hello.name=안녕 {0}

// messages_en.properties
hello=hello
hello.name=hello {0}

위와 같이 메세지 설정 파일을 구성해준다.

@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 message = ms.getMessage("no_code", null, "기본 메세지", null);
        assertThat(message).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");
    }
}

빈에 등록되어있는 MessageSource 객체를 받아온다.

ms.getMessage()

ms.getMessage("no_code", null, "기본 메세지", null)는 스프링의 메시지 소스를 사용하여 메시지를 조회하는 코드이다. 여기서 "no_code"는 메시지 설정 파일에서 키(key)를 의미하며, 해당 키에 매핑된 메시지를 조회한다.

이 코드의 동작을 설명하면:
1. "no_code"라는 키를 메시지 파일에서 검색한다.
2. 만약 메시지 파일에 "no_code" 키가 존재하지 않으면 기본적으로 에러가 발생해야 한다.
3. 그러나 세 번째 인자인 "기본 메세지"를 통해 디폴트 메시지를 설정할 수 있다.

  • "기본 메세지""no_code" 키가 메시지 파일에서 발견되지 않을 경우 반환될 값이다.
  • 이를 통해 키가 없을 때 발생하는 에러를 방지하고, 대신 기본 메시지를 제공할 수 있다.

예를 들어, 메시지 파일에 "hello" 키만 존재한다고 가정하면 "no_code" 키를 찾을 수 없으므로 "기본 메세지"가 반환된다. 디폴트 메시지를 사용함으로써 키가 없을 때 발생할 수 있는 오류를 유연하게 처리할 수 있다.

두번째 인자의 null은 변수 전달을 위함이다. 만약 ms.getMessage("hello.name", new Object[]{"Spring"}, null);와 같이 전달한다면 설정 파일의 hello.name에 대응될 것이다. 전달된 Object배열 객체에 순서대로 {0}에 대응되어 출력되는 메세지는 hello Spring이 될 것이다. 동적으로도 구성가능하다는 것이다.

마지막 인자의 null은 Locale설정에 관한 것이다. Locale 인자를 null로 준다면 시스템 기본 locale이 반영된다. 만약 시스템 기본 locale의 메세지 파일이 존재하지 않을 경우 가장 기본 메세지 파일을 조회한다.

ms.getMessage("hello", null, Locale.ENGLISH))의 경우 Locale.ENGLISH의 적용으로 messages_en.properties를 우선 조회한다.

Spring MVC 프로젝트에 적용

메세지 설정 파일에 템플릿의 메세지에 대응 될 내용을 추가한다.

// ./resource

// messages.properties
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=취소

// messages_en.properties
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

템플릿의 메세지 부분을 th: 속성으로 하여금 #{label.item}과 같이 수정해주어야 한다.
#{...}은 메세지 파일 내부를 탐색한다.

<!-- 이전 --> <h2>상품 등록 폼</h2>
<!-- 메세지 파일 적용 --><h2 th:text="#{page.addItem}">상품 등록 폼</h2>

messages_en.properties의 내용을 적용하기 위해 크롬의 설정에서 언어 우선순위를 변경해준다. 아래는 현재 상태이다.

위와 같이 브라우저 언어 우선순위를 수정한다면

다음과 같이 en메세지가 적용되는 것을 볼 수 있다.

브라우저의 언어 우선순위 설정을 변경하여 요청시 Accept-Language의 우선순위가 en-US, en이 ko, ko-KR보다 값이 높은 것을 확인할 수 있다. 스프링은 이러한 요청 Accept-Language 헤더를 파악하여 어떠한 메세지 파일을 적용할 지 결정하는 것을 단순화하여 개발자들에게 제공하고 있다.

LocaleResolver

MessageSource를 가지고 테스트 코드를 짰을 때 언어를 구분해주는 것은 결국 Locale 헤더 정보였다. 스프링도 역시 Locale정보로 메세지 파일을 구분하며 기본으로 Accept-Language 헤더의 값을 사용한다.

LocaleResolver가 이를 해석하는 인터페이스이며 Accept-Language 헤더의 값을 사용하는 AcceptHeaderLocaleResolver가 기본 구현체가 된다. 만약 쿠키나 세션 기반의 Locale선택 기능을 사용하기 위해서는(고객이 직접 Locale을 설정하도록) LocaleResolver의 쿠키관련 구현체를 설정할 수 있다.

profile
자바집사의 거북이 수련법

0개의 댓글