Spring message, 국제화

강정우·2023년 12월 13일
0

Spring-boot

목록 보기
39/73

메시지

  • 만약 기획자가 결과물을 보더니 컬럼 명칭이 마음에 들지 않는다고 바꿔달라고 하면 어떻게 할까?
    만약 따로 메시지를 관리하는 객체를 만들지 않고 일일이 html 파일에 적어넣었다면 그 개수만큼 고쳐야할 것이다.
    하지만 따로 아래처럼 명칭을 관리하는 메시지 객체를 만들고

messages_ko.properties

item=상품
item.id=상품 ID
item.itemName=상품명
item.price=가격
item.quantity=수량
  • 이를 각각의 html 파일에서 불러다 쓴다면 컬럼 이름을 바꿀 때 n개 만큼 바꾸는 것이 아닌 1개만 바꿔도 되는 아주 편리한 상황이 오는 것이다.
    이렇게 어떠한 컬럼의 명칭들을 메시지라고 한다.
<label for="itemName" th:text="#{item.itemName}"></label>

국제화

  • 이를 발전시키면 이제 국제화도 가능하다. 이런 메시지를 여러 문자로 객체를 만들어두고

messages_en.properties

item=Item
item.id=Item ID
item.itemName=Item Name
item.price=price
item.quantity=quantity
  • 사용자 요청의 HTTP accept-language 해더 를 확인하여 요구사항에 맞게 메시지 객체를 넣어주면 된다.
    물론 사용자가 직접 언어를 선택하게 할 수 있도록 하면 더 좋다.

  • 하지만 스프링은 이러한 메시지, 국제화 기능을 모두 제공한다. 심지어 thymeleaf오 스프링이 제공하는 메시지, 국제화 기능을 편리하게 통합하여 제공한다.

스프링의 메시지 등록

@Bean
public MessageSource messageSource() {
 ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
 messageSource.setBasenames("messages", "errors");
 messageSource.setDefaultEncoding("utf-8");
 return messageSource;
}
  • 원래라면 사용자가 이 메시지를 관리해주는, 스프링이 제공하는 MessageSource빈을 등록을 해줘야한다. 하지만 스프링이 알아서 등록을 해둔다.
    참고록 MessageSource인터페이스라 이를 구현한 구현체인 ResourceBundleMessageSource를 등록해줘야한다.

  • basenames : 설정 파일의 이름을 지정한다.

    • messages 로 지정하면 messages.properties 파일을 읽어서 사용한다.
      위 코드같은 경우는 error도 추가 등록해놔서 errors.properties 파일도 읽어올 것 이다.
    • 추가로 국제화 기능을 적용하려면 messages_en.properties , messages_ko.properties 와 같
    • 이 파일명 마지막에 언어 정보를 주면된다. 만약 찾을 수 있는 국제화 파일이 없으면
    • messages.properties (언어정보가 없는 파일명)를 기본으로 사용한다.
    • 파일의 위치는 /resources/messages.properties 에 두면 된다.
    • 여러 파일을 한번에 지정할 수 있다. 여기서는 messages , errors 둘을 지정했다.
  • defaultEncoding : 인코딩 정보를 지정한다. utf-8 을 사용하면 된다.

  • spring.messages.basename=messages,config.i18n.messages 코드를 application.properties에 추가하면 메시지 소스를 설정할 수 있다.
    참고로 default는 spring.messages.basename=messages이다.
    더 자세한 옵션들은 공식문서를 확인하면 된다.

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

properties 작성하기

  • 위치는 resources밑에 messages.properties 파일을 만들어주면 이게 default로 나가고 추가적으로 messages_en.properties을 만들면 IDE가 알아서 bundle 폴더를 만들어준다.

messages.properties :기본 값으로 사용(한글)
messages_en.properties : 영어 국제화 사용
  • 참고로 파일명은 메시지가 아니라 메시지쓰 다!

  • 그리고 얘가 앞서 작성한 @Bean 으로 등록되기 때문에 이제 우리는 얘를 갖다가 쓰는 방법에 대해 알아보자.

test

@SpringBootTest
public class MessageSourceTest {
    @Autowired
    MessageSource messageSource;

    @Test
    void helloMessage() {
        String result = messageSource.getMessage("hello", null, null);
        assertThat(result).isEqualTo("hello");
    }
}
  • 간단한 테스트 작성을 통하여 확인할 수 있다 (참고로 시스템 설정의 언어가 이렇게 영어로 되어있다면 영어로 나올테니 그것만 인지하자.)

  • ms.getMessage(, , ) 메서드는 순서대로 code: hello, args: null, locale: null 를 받는다.

  • argshello=안녕 hello.name=안녕 {0} 에서 {0} 에 들어가는 값이다.

  • locale 정보가 없으면 basename 에서 설정한 기본 이름 메시지 파일을 조회한다.

  • 앞서 application.properties에서 basename 으로 messages 를 지정 했으므로 messages.properties 파일에서 데이터를 조회한다.

exception test

@Test
void notFoundMessageCode() {
    assertThatThrownBy(() -> messageSource.getMessage("no_code", null, null))
            .isInstanceOf(NoSuchMessageException.class);
}
  • 만약 해당 데이터가 없다면 이렇게 에러를 뿌려준다.

default message

  • messageSource의 getMessage 메서드는 사실 3번째에 optional로 default message 인자를 받는다.
@Test
void notFoundMessageCodeDefaultMessage() {
    String result = messageSource.getMessage("no_code", null, "default message", null);
    assertThat(result).isEqualTo("default message");
}

args

  • arguments 값으로 항상 Object 배열을 넘겨줘야한다.

messages.properties

hello=hello
hello.name=hello {0}

label.item=product
label.item.id=product ID
label.item.itemName=product name
label.item.price=prrice
label.item.quantity=quantity

page.items=product list
page.item=product detail
page.addItem=product register
page.updateItem=product edit

button.save=save
button.cancel=canle

messages_ko.properties

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

그래서 이렇게 각각의 속성 파일이 있다고 가정할 때

@Test
void argumentMessage() {
    String message = messageSource.getMessage("hello.name", new Object[]{"spring message"}, null);
    assertThat(message).isEqualTo("hello spring message");
}
  • 위 코드르처럼 테스트를 작성하면 해당 {0} 여기에 사용자가 작성한 "spring message"라는 문자열이 들어가기 때문에 위 테스트 코드는 성공적으로 실행된다.

locale

@Test
void defaultLang() {
    assertThat(messageSource.getMessage("hello", null, null)).isEqualTo("hello");
    assertThat(messageSource.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
}
  • 사실상 이제 이게 국제화이다. 이걸로 locale값을 넣어줘서 국제화를 진행하는 것이다.
    이때 컴파일 될 언어와 시스템 언어가 한국어로 잡혀있어야 정상적으로 동작한다.

message 적용

  • #{} 을 통하여 편리하게 메시지를 조회할 수 있다.
<div class="py-5 text-center">
    <h2 th:text="#{page.addItem}">상품 등록 폼</h2>
</div>
  • 참고로 파라미터는 다음과 같이 사용할 수 있다.
hello.name=안녕 {0}
<p th:text="#{hello.name(${item.itemName})}"></p>

국제화 적용

  • 사실 이렇게 작성해두면 chrome에서는 설정에 들어가서 사용자 언어를 영어를 1순위로 올리든지 한국어를 1순위로 올리든지 하면 언어가 자동으로 바뀐다. 하지만 사용자가 chrome 설정에 들어가서 바꾸라고 하면 너무 무책임 하기 때문에
    개발자는 쿠키나 세션에 사용자 언어 설정을 보관해두고 해당 언어로 locale을 지정해주면 된다.

  • 그리고 이를 도와주는 것이 바로 localeResolver이다.

LocaleResolver

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

LocaleResolver 인터페이스

public interface LocaleResolver {
	Locale resolveLocale(HttpServletRequest request);
	void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}

LocaleResolver 변경

  • 만약 Locale 선택 방식을 변경하려면 LocaleResolver 의 구현체를 변경해서 쿠키나 세션 기반의 Locale 선택
    기능을 사용할 수 있다.
    이는 고객이 직접 Locale 을 선택하도록 하는 방법이다.
profile
智(지)! 德(덕)! 體(체)!

0개의 댓글