[ Spring ]/SpringMVC 2편

[Spring] 메시지, 국제화

쿠릉쿠릉 쾅쾅 2022. 3. 6. 18:40
728x90

 

 

 

메시지, 국제화

  • 스프링은 메시지와 국제화 기능을 통합해서 제공한다.

 

🔍 메시지

  • 메시지 기능이란, 화면에서 공통으로 사용되는 다양한 메시지를 한 곳에서 관리하는 기능이다.
  • 메시지 기능을 사용하면 메시지를 변수화하여 key값으로 불러서 사용할 수 있다.
  • 그렇게하면 HTML 문서의 메시지 내용을 수정할 때 유지보수가 간편해진다.
  • 스프링은 메시지 기능을 제공한다.

 

💡 사용예시

📌 메시지 관리용 파일 (messages.properties)

item=상품
item.id=상품 ID
item.itemName=상품명
item.price=가격
item.quantity=수량
  • 사용할 메시지들을 파일 한 곳에 모아서 key, value 형식으로 다룬다.

 

📌 HTML (해당 메시지를 사용할 HTML)

<label for="itemName" th:text="#{item.itemName}"></label>
  • HTML 파일에 사용할 메시지를 key값으로 value값을 호출한다.

 

🔍 국제화

  • 국제화 기능이란, 메시지 내용을 서비스를 지원하는 나라 언어로 제공하는 것이다. 
  • 메시지에서 설명한 메시지 파일(messages.properties)을 각 나라별로 별도로 관리하면 서비스를 국제화할 수 있다.
  • HTTP  accept-language  헤더 값을 사용하거나 또는 사용자가 직접 언어를 선택할 수 있도록 선택권을 주고, 쿠키를 사용해서 각 국가별로 맞는 언어를 제공하면 된다.

 

💡 사용 예시

📌 messages_ko.properties

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

📌 messages_en.properties

item=Item
item.id=Item ID
item.itemName=Item Name
item.price=price
item.quantity=quantity
  • 이렇게 영어를 사용하는 사람이면 messages_en.properties 파일을 사용하고, 한국어를 사용하는 사람이면 messages_ko.properties 파일을 사용하면 된다.
  • 이처럼 메시지와 국제화 기능을 직접 구현할 수 있지만, 스프링은 기본적으로 메시지와 국제화 기능을 모두 제공한다.

 

🧷 정리

  • 스프링은 properties 파일에 공통 메시지를 변수화하여 제공할 수 있다.
  • 여러 언어에 맞게 메시지를 제공할 수 있으며 이를 국제화 기능이라고 한다.
  • 스프링은 메시지와 국제화 기능을 통합으로 제공한다.

 

 

 


 

 

스프링 메시지 소스 설정

  • 스프링은 기본적으로 메시지 관리 기능을 제공한다.
  • 스프링 부트는 프레임워크 자체적으로 MessageSource를 자동으로 스프링 빈으로 등록해준다.
  • 참고로 MessageSource는 인터페이스다. 따라서 구현체인 ResourceBundleMessageSource를 스프링 빈으로 등록하면 된다. (스프링 부트에서는 자동으로 해준다.)

 

🔍 스프링일 경우 MessageSource 빈 등록 방법

스프링 Bean 직접 등록

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasenames("messages", "errors");
    messageSource.setDefaultEncoding("utf-8");
    return messageSource;
}
  • 메시지 관리 기능을 사용하려면 스프링이 제공하는 MessageSource를 스프링 빈으로 등록해야 한다.
  •  setBasenames 
    • 파라미터로 설정 파일의 이름을 넣으면 된다. 그러면 설정 파일로 지정된다.
    •  messages 로 지정하면  messages.properties  파일을 찾아 읽어 사용한다.
    • 추가로 국제화 기능을 적용하려면 message_en.properties, message_ko.properties 와 같이 파일명 마지막에 언어 정보를 주면 된다.
    • 만약 국제화 파일이 없으면 messages.properties (언어 정보가 없는 파일명)를 기본으로 사용한다.
    • 파일 위치는 /resources/messages.properties 에 두면 된다.
    • 한 번에 여러 설정 파일을 지정할 수 있다. 여기서는 messages, errors 둘을 지정 햇다.
  •  setDefaultEncoding 
    • 인코딩 정보를 지정한다.
    • 보통 utf-8을 사용한다.

 

 🔍 스프링부트일 경우 MessageSource 빈 등록 방법 

메시지 소스 설정

📌 application.properties

spring.messages.basename=messages
  • 스프링 부트와 관련된 별도의 설정을 하지 않으면, message라는 이름으로 기본 등록 된다. (기본 설정)
  • 국제화 기능을 사용하려면 기본 이름 뒤에 사용할 국가명을 붙이면 자동으로 인식된다.
    • 예)
      • messages_en.properties
      • messages_ko.properties
      • messages.properties
    • 적절한 국가 설정을 찾지 못하면 기본 파일인 messages.properties를 사용한다.
  • 설정 파일이 하나일 경우 설정파일 지정을 생략해도 된다.

 

한 번에 설정 파일을 여러개 지정하기

spring.messages.basename=messages, config.i18n.messages
  •  여러 파일을   (콤마)를 통해 지정할 수 있다. 
  • config.i18n.messages
    • /resources/config/i18n/messages/ 경로를 의미한다.

 

 

🔍 메시지 파일 만들기 (스프링 부트 이용)

📌 application.properties

spring.messages.basename=messages

 

📌 /resources/messages.properties

hello=안녕
hello.name=안녕 {0}
  • {0} 처럼 파라미터 지정 가능

 

📌 /resources/messages_en.properties

hello=hello
hello.name=hello {0}
  • {0} 처럼 파라미터 지정 가능

 

참고로 인털레J를 사용한다면 여러 messages 설정이 묶여서 Resource Bundle 'messages'로 뭉쳐진다.

 

 


 

 

스프링 메시지 소스 사용

 

🔍 MessageSource 기능

  • MessageSource를 스프링 빈으로 등록하면 메시지 기능을 사용할 수 있다.

📌 MessageSource 인터페이스

public interface MessageSource {
    String getMessage(String code, @Nullable Object[] args, @Nullable String
            defaultMessage, Locale locale);

    String getMessage(String code, @Nullable Object[] args, Locale locale) throws
            NoSuchMessageException;
}
  • code : 메시지에서 불러올 값의 key
  • args : 메시지에 배열이 있을 경우 배열을 전달할 수 있다. 그러면 메시지 설정 파일에서 해당 배열의 인덱스 값을 출력한다. 
    • 예) new Object[]{"Spring"}
  • defaultMessage : 인수로 전달한 key에 해당하는 값이 없는 경우 반환할 기본 값
  • locale : Locale 정보를 기반으로 국제화 파일을 선택한다. 
    • 예)
      • Locale.KOREA
      • Locale.ENGLISH

 

🔍 MessageSource 기능 사용 (테스트 코드로 알아보기)

📌 MessageSourceTest.java

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.AssertionsForClassTypes.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 result = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
        assertThat(result).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");
    }

}
  •  @SpringBootTest  애노테이션을 통해 테스트 파일에 스프링 부트를 실행시킨다.
    • 즉, MessageSource 빈을 자동으로 등록해준다.
  • MessageSource 객체를  @Autowired  를 통해 자동 의존 주입을 해줬다.

 

📌 messages.properties

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

📌 messages_en.properties

hello=hello
hello.name=hello {0}

 

💡 getMessage() 기본 기능

@Test
void helloMessage() {
    String result = ms.getMessage("hello", null, null);
    assertThat(result).isEqualTo("안녕");
}
  •  ms.getMessage("hello", null, null)  
    • code : hello
    • args : null
    • locale : null
      • locale 정보가 없으면 application,.properties파일의 basename에서 기본 메시지 설정 파일을 조회한다.

 

➰ 인텔리제이 한글 호환 인코딩 에러 해결

더보기

 

인텔리제이 한글 호환 인코딩 에러

  • 위 사진처럼 "안녕" 값이 아닌 "??" 값이 나와야한다고 적혀있다.
  • 한글 인코딩 문제로 발생한 에러다.

 

한글 인코딩 문제 해결

  • 인텔리제이의 인코딩을 UTF-8로 설정하면 된다. 
  • File → Settings... → Editor → File Encodings → Global Encoding → UTF-8 선택
  • File → Settings... → Editor → File Encodings → Project Encoding → UTF-8 선택
  • File → Settings... → Editor → File Encodings → Properties Files(*.properties) → UTF-8 선택 → 체크박스 체크

 

 

💡 해당 메시지가 없을 경우 - 예외 확인

@Test
void notFoundMessageCode() {
    assertThatThrownBy(() -> ms.getMessage("no_code", null, null))
            .isInstanceOf(NoSuchMessageException.class);
}

 

  • 해당 메시지가 없을 경우에도 잘 작동하는지 테스트 코드를 작성해야한다.
  •  assertThatThrownBy() 를 통해 예외처리를 가독성 있게 테스트할 수 있다.
  • 메시지 설정 파일에 일치하는 메시지가 없을 경우  NoSuchMessageException  예외가 발생한다.

 

💡 해당 메시지가 없을 경우 - 기본 메시지 설정

@Test
void notFoundMessageCodeDefaultMessage() {
    String result = ms.getMessage("no_code", null, "기본 메시지", null);
    assertThat(result).isEqualTo("기본 메시지");
}
  • 메시지 설정 파일에 일치하는 메시지가 없어도 기본 메시지 기능을 사용하면 기본 메시지 값이 반환된다.
  •  ms.getMessage("no_code", null, "기본 메시지", null) 
    • code : "no_code"
    • args : null
    • defaultMessage : "기본 메시지"
      • 일치하는 메시지가 없을 경우 해당 파라미터 값이 반환된다.
    • locale : null

 

💡 추가 매개변수 사용

@Test
void argumentMessage() {
    String result = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
    assertThat(result).isEqualTo("안녕 Spring");
}
  • 메시지 설정 파일에 {0} 과 같은 포멧이 있을 경우 인덱스 번째에 해당하는 배열의 값이 적용된다.
  •  hello.name=안녕 {0} 
    • messages.properties 파일에서 {0}값에 new Object{"Spring"} 배열의 0번째 인덱스인 "Spring" 값이 들어간다.
    • 출력 : 안녕 Spring 
  •  ms.getMessage("hello.name", new Object[]{"Spring"}, null)   
    • code : "hello.name"
    • args : new Object[]{"Spring"}
      • 파라미터로 Object 배열 타입이 와야한다.
      • 해당 배열을 통해 메시지에 인덱스에 맞는 배열 값을 전달할 수 있다.
    • locale : null

 

💡 국제화 파일 선택 - 기본 / 한국어

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

 

  • locale 정보를 기반으로 국제화 파일을 선택한다.
  •  ms.getMessage("hello", null, Locale.KOREA)  
    • code : "hello"
    • args : null
    • locale : Locale.KOREA
      • 한국어 선택
      • messages_ko 메시지 설정 파일이 없으면 messages 기본 메시지 설정 파일이 사용된다.

 

💡 국제화 파일 선택 - 영어

@Test
void enLang() {
    assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
}

 

 


 

 

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

 

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

 

🔍 타임리프에서 메시지 적용하기

  • 사용법 :  #{...} 

💡 랜더링 전

<div th:text="#{label.item}"> <div>

 

💡 렌더링 후

<div>상품<div>

 

🔍 동적인 메시지

  • 메시지의 값이 상황에 따라 동적으로 변하는 경우 파라미터를 사용하면 된다.
  • 메시지 : hell.name=안녕 {0}
<p th:text="#{hello.name(${item.itemName})}"></p>

 

 


 

 

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

  • 메시지 기능을 통해 웹 애플리케이션을 유연하게 메시지를 추가했고, 국제화 기능을 적용해 다른 언어의 서비스를 제공할 수 있다.

 

📌 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

 

🔍 국제화 기능 적용하기

  • 구글 설정 → '언어'에 들어가서 원하는 언어를 맨위로 올려서 우선권을 주면 된다.
  • 그러면 가장 우선권이 높은 언어를 기반으로 국제화 기능이 알아서 적용된다.
  • 위 사진처럼 영어를 가장 우선권을 높게 설정하면 HTTP 헤더의 Accept-Language가 영어로 바뀐다.

 

  • 영어를 우선권을 주자 메시지가 자동으로 영어로 바뀌었다.

 

🔍 스프링의 국제화 메시지 선택

  • 스프링의 메시지 기능을 사용하기 위해서는 Locale 정보가 필요하다.
  • 스프링은 언어 선택시 기본으로 HTTP 헤더의 Accept-Language 값을 기반으로 메시지 설정 파일을 선택한다.

 

🔍 LocaleResolver

  • LocaleResolver는 인터페이스이며, Locale 선택 방식을 변경할 수 있도록 제공한다. (스프링 기능)
  • 스프링 부트는 기본으로 Accept-Language를 활용하는 AcceptHeaderLocaleResolver를 사용한다.
  • 만약에 Locale 선택 방식을 변경하려면 LocaleResolver의 구현제를 변경해서 쿠키나 세션 기반의 Locale 선택 기능을 사용할 수 있다.
    • 사용 예) 고객이 직접 Locale을 선택하도록 선택권을 준다.

 

 

 

 

 


👀 참고 자료

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

 

https://pjh3749.tistory.com/241

 

[AssertJ] JUnit과 같이 쓰기 좋은 AssertJ 필수 부분 정리

AssertJ가 core document를 새로운 github.io로 이전했네요 :) . 본 글은 AssertJ 공식 문서를 핵심 챕터를 선정하여 번역하며 정리한 글 입니다. http://joel-costigliola.github.io/assertj/assertj-core.html A..

pjh3749.tistory.com

 

https://inf.run/ok17

 

한글 인코딩 관련 질문입니다. - 인프런 | 질문 & 답변

ms.getMessage('hello', null, null)을 넣고 테스트를 돌렸을 때 아래와 같이 뜨면서 테스트가 실패합니다. Expecting: <'??'> to be equal to: <'안녕'> but was not. org.opentest4...

www.inflearn.com

 

728x90