메세지, 국제화

2025. 9. 7. 17:20·Spring

메세지, 국제화 소개

기획자가 화면에 보이는 문구가 마음에 들지 않는다고 상품명이라는 단어를 모두 상품 이름으로 변경해달라고 한다.

이럴 때 상품명, 가격, 수량 등 label에 있는 단어를 변경하려면 여러 화면들을 다 찾아가면서 모두 변경해야한다.

지금처럼 그 수가 적다면 괜찮지만 많다면 모두 고치기 매우 힘들다.

(addForm.html, editForm.html 등등..)

 

왜냐하면 해당 HTML 파일들이 다 하드코딩 되어있기 때문이다.

 

이런 다양한 메세지를 한 곳에서 관리하도록 하는 기능을 메세지 기능이라고 한다.

 

예를들어 messages.properties라는 메세지 관리용 파일을 만들고

item=상품

item.id=상품 ID

item.itemName=상품명

등등등

 

각 HTML들은 다음과 같은 해당 데이터를 key 값으로 불러서 사용하는 것이다.

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

 

(물론 이렇게 사용하려면 추가적인 코드를 작성해야 한다)

 

국제화

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

(messages_en.properties)

item=Item

item.itemName=Item Name 

...

 

(messages_ko.properties)

item=상품

item.id=상품 ID

item.itemName=상품명

...

 

이처럼 사이트를 국제화 할 수 있다.

 

위와같이 직접 구현하는 방법도 있지만, 스프링은 이러한 기본적인 메세지와 국제화 기능을 모두 제공한다.

 

messages.properties

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

 

messages_en.properties

hello=hello
hello.name=hello {0}

 

스프링이 제공하는 메세지 관리 기능을 사용하려면 MessageSource를 스프링 빈으로 등록하면 되는데, 스프링 부트에선 자동으로 스프링 빈으로 등록한다. 따라서 별도 설정이 굳이 필요없다는 말임

스프링 부트 메세지 소스 기본값은 (application.properties에서) spring.messages.basename=messages 이다.

(궁금하면 application.properties 관련 공식문서 더 찾아보기)

 

MessageSource의 인터페이스

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.context;

import java.util.Locale;
import org.springframework.lang.Nullable;

public interface MessageSource {
    @Nullable
    String getMessage(String var1, @Nullable Object[] var2, @Nullable String var3, Locale var4);

    String getMessage(String var1, @Nullable Object[] var2, Locale var3) throws NoSuchMessageException;

    String getMessage(MessageSourceResolvable var1, Locale var2) throws NoSuchMessageException;
}

 

인터페이스를 보면 코드를 포함한 일부 파라미터로 메세지를 읽어오는 기능도 제공한다.

 

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

    @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 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");
    }
}

 

별거 없다. 현재 내 IDE 기본값은 한국어이기 때문에 (정확히는 UTF-8, Transparent native-to-ascii conversion 옵션 사용) 

null이어도 한국어가 default로 설정되어있다. 코드를 천천히 읽어보면 이해될것임

 

 

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

실제 웹 애플리케이션에 메세지를 적용해보자

 

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

 

타임리프의 메시지 표현식 #{...}를 사용하면 스프링의 메세지를 편리하게 조회할 수 있다.

예를 들어서 방금 등록한 상품이라는 이름을 조회하려면 #{label.item}이라고 한다.

 

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>

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

 

이렇게 사용하면 된다. 그럼 나중에 하드코딩된 코드를 변경하지 않아도 일괄적으로 코드를 수정할 수 있게 되는 것이다.

참고로 파라미터는 다음과 같이 사용할 수 있다.

hello.name=안녕 {0}

<p th:text="#{hello.name(${item.itemName})}"></p>

 

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

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

 

이렇게만 해주면 국제화는 완료되었다. 왜냐면 템플릿 파일에 모두 #{...}를 통해서 메세지를 사용하도록 적용했기때문이다.

 

현재 방식은 Accept-language의 값을 통해 언어가 설정된다.

스프링의 MessageSource는 결국 Locale 정보를 알아야 언어를 선택할 수 있다. 

accept-language 헤더값을 사용하거나, LocalResolver로 쿠키나 세션 기반의 Locale을 통해 언어를 선택하는 방법도 있다.

 

'Spring' 카테고리의 다른 글

Bean Validation  (0) 2025.09.12
오류 코드와 메세지처리, Validator 분리  (0) 2025.09.11
타임리프 - 스프링 통합과 폼  (1) 2025.09.06
타임리프 - 기본 기능  (1) 2025.08.31
상품 도메인 개발 - 뷰 템플릿, 타임리프  (1) 2025.08.28
'Spring' 카테고리의 다른 글
  • Bean Validation
  • 오류 코드와 메세지처리, Validator 분리
  • 타임리프 - 스프링 통합과 폼
  • 타임리프 - 기본 기능
공부처음하는사람
공부처음하는사람
  • 공부처음하는사람
    lazzzykim
    공부처음하는사람
  • 전체
    오늘
    어제
    • 분류 전체보기 (159)
      • Kotlin (31)
      • Java (56)
      • Spring (44)
      • JPA (6)
      • Algorithm (3)
      • TroubleShooting (1)
      • 내일배움캠프 프로젝트 (14)
      • Setting (2)
      • ... (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 인기 글

  • 태그

    트랜잭션
    빈 생명주기
    예외처리
    김영한의 실전 자바
    언체크예외
    제네릭
    OCP
    Di
    kotlin
    내일배움캠프
    java
    jpa
    김영한의 실전자바
    다형성
    캡슐화
    중첩클래스
    배열
    싱글톤
    김영한
    spring
  • hELLO· Designed By정상우.v4.10.3
공부처음하는사람
메세지, 국제화
상단으로

티스토리툴바