catsridingCATSRIDING|OCEANWAVES
Dev

Spring i18n 및 l10n을 활용한 글로벌 서비스 다국어 지원 구현하기

jin@catsriding.com
Oct 28, 2023
Published byJin
999
Spring i18n 및 l10n을 활용한 글로벌 서비스 다국어 지원 구현하기

Implementing Internationalization and Localization for Global Services with Spring

글로벌 서비스를 제공하려면 국제화(i18n, Internationalization)와 현지화(l10n, Localization)가 필수적입니다. Spring은 메시지 프로퍼티 파일과 로케일 리졸버(Locale Resolver)를 활용한 다국어 설정을 지원하며, 이를 기반으로 데이터베이스 기반 다국어 관리도 설계할 수 있습니다. 이러한 기능이 없으면 코드가 복잡해지고 유지보수가 어려우며, 글로벌 확장도 제한될 것입니다.

이 글에서는 Spring의 i18n 및 l10n을 활용한 다국어 지원 방법을 다루며, 언어 설정 방식과 API 응답 및 예외 처리에서의 적용 전략을 살펴봅니다.

Understanding i18n and l10n

국제화와 현지화는 글로벌 애플리케이션을 구축하는 핵심 개념입니다. 국제화는 애플리케이션이 여러 언어와 지역을 지원할 수 있도록 설계하는 과정이며, 현지화는 특정 언어와 문화에 맞게 콘텐츠와 인터페이스를 최적화하는 과정입니다. 이 두 개념을 효과적으로 구현하면 사용자가 자신의 언어와 문화에 맞는 환경에서 서비스를 이용할 수 있습니다.

i18n

국제화(i18n, Internationalization)는 애플리케이션이 다양한 언어와 지역을 지원할 수 있도록 설계하는 과정입니다. 즉, 코드 변경 없이 새로운 언어를 쉽게 추가할 수 있도록 구조를 유연하게 설계하는 것이 핵심입니다.

i18n
이 용어는 “Internationalization”에서 첫 글자 ‘I’와 마지막 글자 ‘N’ 사이에 있는 18개의 문자를 축약하여 표현한 방식입니다.

이 개념이 필요한 대표적인 경우는 다음과 같습니다:

  • 코드 수정 없이 새로운 언어를 추가할 수 있어야 하는 경우
  • 여러 국가에서 동일한 서비스가 제공되지만, 언어 및 숫자 형식이 다른 경우
  • 사용자 환경에 따라 UI 요소의 크기와 레이아웃을 자동으로 조정해야 하는 경우

이를 위해 국제화된 애플리케이션에서는 다음과 같은 설계를 적용합니다.

  • 메시지를 코드에 직접 작성하지 않고, messages.properties 등의 외부 파일에서 관리하여 다국어 확장성을 확보
  • 날짜, 통화, 숫자 형식을 LocaleNumberFormat 클래스를 활용하여 동적으로 처리, 사용자 환경에 맞는 형식 적용
  • UI 요소의 크기와 레이아웃을 언어별로 조정 가능하도록 설계, 텍스트 길이가 다른 언어에서도 적절히 표시되도록 대응

l10n

현지화(l10n, Localization)는 국제화된 애플리케이션을 특정 지역과 문화에 맞게 번역된 콘텐츠와 형식을 적용하여 사용자 경험을 최적화하는 과정입니다. 국제화된 애플리케이션이 다양한 지역에서 원활하게 작동하려면 현지화가 필수적으로 적용되어야 합니다.

l10n
이 용어는 “Localization”에서 첫 글자 ‘L’과 마지막 글자 ‘N’ 사이의 10개 문자를 축약하여 표현한 방식입니다.

이 개념이 필요한 대표적인 경우는 다음과 같습니다.

  • 동일한 서비스라도 각 국가별로 다른 법적 요구 사항이 있는 경우
  • 사용자가 익숙한 날짜, 통화, 숫자 형식을 제공해야 하는 경우
  • 각 언어별 번역 파일을 유지보수하고, UI에서 자연스럽게 적용해야 하는 경우

이를 위해 현지화된 애플리케이션에서는 다음과 같은 방식이 적용됩니다.

  • 각 언어별 번역 파일 제공 (messages_ko.properties, messages_en.properties 등), UI 텍스트 및 메시지를 문화권에 맞게 변환
  • 날짜 및 시간 형식 지역화 (예: 한국에서는 YYYY-MM-DD, 미국에서는 MM/DD/YYYY 적용)
  • 통화 및 숫자 단위 변환 (예: 한국에서는 KRW(₩), 미국에서는 USD($), 유럽에서는 EUR(€))
  • 문화 및 규칙 반영 (예: 우측 정렬이 필요한 아랍어 UI, 좌측에서 우측으로 읽는 영어 UI, 번역된 단어의 길이에 맞는 UI 조정)

이처럼 국제화(i18n)는 다국어 지원을 고려한 애플리케이션 설계 과정이고, 현지화(l10n)는 실제로 해당 언어와 문화에 맞게 조정하는 과정입니다. 두 개념은 상호 보완적으로 동작하며, 글로벌 서비스를 제공하는 데 필수적인 요소입니다.

Key Components i18n and l10n in Spring

Spring 애플리케이션에서 i18n과 l10n을 구현하려면, 이를 지원하는 주요 컴포넌트를 이해해야 합니다. Spring은 다양한 인터페이스와 클래스를 제공하여 언어 설정과 로케일 감지를 처리할 수 있도록 돕습니다.

1. Locale

Java의 Locale 클래스는 특정 언어, 국가 및 지역을 정의하는 핵심 클래스입니다. 애플리케이션에서 다국어 지원을 구현할 때, Locale 객체를 활용하여 사용자의 언어 설정을 관리할 수 있습니다.

Locale.java
package java.util;

public final class Locale implements Cloneable, Serializable {

    public static final Locale ENGLISH;
    public static final Locale FRENCH;
    public static final Locale GERMAN;
    public static final Locale ITALIAN;
    public static final Locale JAPANESE;
    public static final Locale KOREAN;
    public static final Locale CHINESE;
    ...

    public static Locale of(String language, String country, String variant) {...}
    public static Locale getDefault() {...}
    public String getLanguage() {...}
    public String getScript() {...}
    public String getCountry() {...}
    public String getVariant() {...}
    public final String getDisplayName() {...}
    ...
}
  • Locale.of(): 언어 및 국가 코드를 기반으로 로케일을 생성합니다. 예를 들어, "ko", "KR"을 입력하면 한국어와 한국을 나타내는 로케일이 생성됩니다.
  • Locale.getDefault(): 시스템의 기본 로케일을 반환하며, 사용자의 운영체제 언어 및 지역 설정에 따라 다르게 반환됩니다.
  • Locale.forLanguageTag(): RFC 5646에 정의된 언어 태그 형식을 기반으로 로케일을 생성합니다. 예를 들어, "en-US" 또는 "fr-CA" 등의 태그를 입력받아 해당 로케일을 반환합니다.
  • Locale.getLanguage(): 로케일에서 언어 코드를 반환합니다. 예를 들어, Locale("en", "US")에서 getLanguage()는 "en"을 반환합니다.
  • Locale.getCountry(): 로케일에서 국가 코드를 반환합니다. 예를 들어, Locale("en", "US")에서 getCountry()는 "US"를 반환합니다.
  • Locale.getDisplayName(): 로케일을 사람이 읽을 수 있는 형태로 반환합니다. 예를 들어, "en-US" 로케일은 "English (United States)"로 표시됩니다.

2. LocaleResolver

LocaleResolver는 사용자의 언어 및 지역 정보를 감지하고, 해당 정보를 기반으로 적절한 로케일을 설정하는 컴포넌트입니다.

LocaleResolver.java
package org.springframework.web.servlet;

public interface LocaleResolver {

	Locale resolveLocale(HttpServletRequest request);

	void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);

}

Spring에서는 다양한 LocaleResolver 구현체를 제공하며, 애플리케이션의 요구 사항에 맞게 선택할 수 있습니다:

  • AcceptHeaderLocaleResolver: HTTP 요청 헤더의 Accept-Language 값을 사용하여 클라이언트의 로케일을 자동으로 감지합니다. 기본값으로 많이 사용되며, 클라이언트가 설정한 언어를 따릅니다.
  • SessionLocaleResolver: 세션에 로케일 정보를 저장하고, 이후 요청에 대해 세션 정보를 기반으로 로케일을 적용합니다. 사용자가 언어를 변경할 때 세션을 통해 지속적인 언어 환경을 제공합니다.
  • CookieLocaleResolver: 사용자의 로케일을 쿠키에 저장하여, 이후 요청에서 해당 언어를 참조하여 로케일을 설정합니다.

3. LocaleContextHolder

LocaleContextHolder는 현재 스레드에서 실행되는 요청의 로케일을 관리하고, 애플리케이션 전반에서 이 정보를 쉽게 접근할 수 있도록 돕는 클래스입니다. 이는 로케일 관련 정보를 컨텍스트에 저장하여, 별도의 상태 관리 없이 다양한 서비스나 컴포넌트에서 로케일 정보를 사용할 수 있도록 합니다.

ProductService.java
Locale currentLocale = LocaleContextHolder.getLocale();
System.out.println("Current Locale: " + currentLocale);
  • LocaleContextHolder.getLocale(): 현재 HTTP 요청을 처리하는 스레드에서 설정된 로케일을 반환합니다.
  • LocaleContextHolder.setLocale(): 애플리케이션에서 현재 스레드의 로케일을 직접 설정하거나 변경할 수 있습니다.

4. MessageSource

MessageSource는 Spring에서 다국어 메시지를 관리하고 반환하는 핵심 인터페이스로, 애플리케이션이 다양한 언어 환경에서 동적으로 메시지를 처리할 수 있도록 도와줍니다. 이를 활용하면 프로퍼티 파일에 저장된 메시지를 로케일(Locale)에 맞춰 조회할 수 있으며, 필요한 경우 동적으로 값을 삽입하여 응답을 구성할 수도 있습니다.

MessageSource.java
package org.springframework.context;

public interface MessageSource {

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

	String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

	String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

}

Spring에서는 MessageSource 인터페이스의 여러 구현체를 제공하며, 가장 많이 사용되는 두 구현체는 다음과 같습니다:

  • ResourceBundleMessageSource:
    • 메시지를 프로퍼티 파일에서 로드하여 로케일별로 메시지를 제공합니다.
    • 주로 고정된 메시지들을 처리할 때 사용합니다.
    • 예시: messages.properties, messages_ko.properties, messages_en.properties 등의 파일에서 메시지를 로드하여 각 로케일에 맞는 값을 반환합니다.
  • ReloadableResourceBundleMessageSource
    • ResourceBundleMessageSource의 확장형으로, UTF-8 인코딩을 지원하며 메시지 변경을 자동으로 반영할 수 있습니다.
    • cacheSeconds 값을 설정하면 주기적으로 메시지 파일을 다시 로드하여 실시간 변경 사항을 적용할 수 있습니다.
    • 주로 운영 중에도 메시지를 변경해야 하는 경우 유용하게 사용됩니다.
4-1. Static Messages

MessageSource를 활용하면 정적인 메시지를 로케일에 맞게 반환할 수 있습니다. 메시지는 프로퍼티 파일에서 정의되며, 각 언어별로 별도 파일을 만들면 클라이언트의 로케일 요청에 따라 자동으로 적절한 메시지를 제공합니다.

예를 들어, 다음과 같은 메시지 파일을 구성한 경우:

messages.properties
welcome.message=환영합니다!
error.notfound=요청하신 페이지를 찾을 수 없습니다.

이 메시지를 getMessage() 메서드를 사용하여 로드할 수 있습니다:

StaticMessagesSample.java
String welcomeMessage = messageSource.getMessage("welcome.message", null, Locale.KOREA);
String errorMessage = messageSource.getMessage("error.notfound", null, Locale.KOREA);

System.out.println(welcomeMessage);
System.out.println(errorMessage);

위 코드를 실행하면 다음과 같은 결과가 출력됩니다.

console
환영합니다!
요청하신 페이지를 찾을 수 없습니다.

이처럼 getMessage()를 사용하면 정적인 메시지를 효율적으로 관리할 수 있으며, 다국어 지원이 필요한 애플리케이션에서 로케일을 기반으로 적절한 메시지를 제공할 수 있습니다.

4-2. Dynamic Messages

동적으로 데이터를 삽입해야 하는 경우에는 메시지에서 {0}, {1} 등의 자리 표시자를 활용하여 데이터를 삽입할 수 있습니다. 이를 통해 사용자 이름, 날짜, 특정 값 등을 메시지에 포함할 수 있습니다.

예를 들어, 다음과 같은 메시지 파일이 있다고 가정해 보겠습니다:

messages.properties
welcome.message=안녕하세요 {0}님, {1}에 오신 것을 환영합니다!

이 메시지에서 {0}{1}은 동적으로 변경될 값을 위한 자리 표시자입니다. 이를 코드에서 사용할 때는 getMessage() 메서드를 통해 값을 주입할 수 있습니다.

DynamicMessagesSample.java
String message = messageSource.getMessage(
    "welcome.message", 
    new Object[]{"옹이", "catsriding | OCEAN WAVES"}, 
    Locale.KOREA
);

위 코드를 실행하면 {0}에는 "옹이", {1}에는 "catsriding | OCEAN WAVES"가 삽입되어 다음과 같은 결과가 출력됩니다.

console
안녕하세요 옹이님, catsriding | OCEAN WAVES에 오신 것을 환영합니다!

이처럼 자리 표시자를 활용하면 동일한 메시지 키를 재사용하면서도 동적인 데이터를 삽입할 수 있어, 다국어 지원과 유지보수에 큰 도움이 됩니다.

Configuring i18n and l10n in Spring Boot

Spring Boot에서는 application.yml을 활용하여 다국어 환경을 쉽게 구성할 수 있습니다. 이 설정을 통해 애플리케이션이 여러 언어를 지원할 수 있도록 메시지 파일을 관리하고, 로케일을 자동으로 감지하여 적절한 언어를 적용할 수 있습니다.

application.yml
spring:
  messages:
    basename:
      - messages
      - errors
    encoding: UTF-8
    cache-duration: -1
    always-use-message-format: true
    fallback-to-system-locale: true
    use-code-as-default-message: true
  web:
    locale: ko
    locale-resolver: accept_header
  • spring.messages.basename: 다국어 메시지를 관리할 메시지 파일의 기본 이름을 지정합니다. 예를 들어, messages.propertieserrors.properties를 설정하면, 해당 파일에서 각 로케일에 맞는 번역된 문자열을 불러옵니다.
  • spring.messages.encoding: 메시지 파일의 문자 인코딩 방식을 지정합니다. UTF-8을 권장하며, 다국어 지원 시 올바른 인코딩을 설정하지 않으면 문자열이 깨질 수 있습니다.
  • spring.messages.cache-duration: 메시지 파일의 캐시 지속 시간을 설정합니다. -1로 설정하면 캐싱이 비활성화되며, 메시지 파일이 변경될 때마다 즉시 반영됩니다.
  • spring.messages.always-use-message-format: 메시지의 형식을 항상 적용할지를 설정합니다. true로 설정하면 메시지가 특정 형식으로 변환될 수 있도록 지원합니다.
  • spring.messages.fallback-to-system-locale: 요청된 로케일의 메시지가 존재하지 않을 경우, 시스템 기본 로케일을 사용할지 여부를 지정합니다.
  • spring.messages.use-code-as-default-message: 지정된 메시지 키에 해당하는 번역이 존재하지 않을 경우, 해당 키 자체를 메시지로 사용할지를 설정합니다.
  • spring.web.locale: 애플리케이션의 기본 로케일을 설정합니다. 예를 들어, ko로 설정하면 기본 언어가 한국어로 지정됩니다.
  • spring.web.locale-resolver: 사용할 LocaleResolver의 유형을 지정합니다. 기본적으로 accept_header가 지원되며, 요청의 Accept-Language 헤더를 기반으로 로케일을 감지합니다. fixed를 설정하면, 모든 요청에서 동일한 고정된 로케일이 사용됩니다.

Spring Boot에서는 위와 같이 application.yml을 활용하여 i18n과 l10n을 설정할 수 있지만, 보다 세부적인 설정이 필요한 경우 직접 MessageSourceLocaleResolver를 빈으로 등록하여 구성할 수도 있습니다.

MessageConfig.java
@Configuration
public class MessageConfig {

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages,errors");
        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setCacheMillis(3600000);
        messageSource.setUseCodeAsDefaultMessage(true);
        return messageSource;
    }

    @Bean
    public LocaleResolver localeResolver() {
        AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
        localeResolver.setDefaultLocale(Locale.KOREA);
        localeResolver.setSupportedLocales(List.of(Locale.KOREA, Locale.US, Locale.JAPAN));
        return localeResolver;
    }
}

이와 같이 직접 MessageSourceLocaleResolver를 구성하면 더 세부적인 설정을 적용할 수 있으며, 애플리케이션의 요구사항에 맞춰 유연하게 조정할 수 있습니다.

Using Message Properties for API Responses

Spring Boot에서는 메시지 프로퍼티를 활용하여 다국어 API 응답을 구성할 수 있습니다. 이 섹션에서는 메시지 프로퍼티 설정과 이를 API 응답에 적용하는 방법을 살펴봅니다.

Successful Message Handling with Message Properties

먼저, 기본적인 Spring Boot 프로젝트를 설정하고, 필요한 의존성을 추가합니다.

build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

이제 다국어 메시지를 지원하기 위한 설정을 application.yml 파일에서 진행합니다. 여기서 messages.basename을 설정하여 사용할 메시지 파일의 기본 이름을 지정하고, encoding을 UTF-8로 설정하여 다양한 언어를 지원할 수 있도록 합니다. 이때, 일반적인 응답 메시지와 예외 메시지를 분리하여 관리하기 위해 messageserrors 두 개의 프로퍼티 파일을 지정하였습니다.

  • messages.properties: 정상적인 응답 메시지를 저장하는 파일입니다. API에서 일반적인 메시지를 반환할 때 사용됩니다.
  • errors.properties: 예외 발생 시 반환할 오류 메시지를 저장하는 파일입니다. 컨트롤러에서 예외가 발생하면 컨트롤러 어드바이스를 통해 해당 파일에서 적절한 메시지를 찾아 응답하게 됩니다.

이를 통해 메시지를 논리적으로 분리함으로써 유지보수성을 높이고, 응답 메시지와 예외 메시지를 독립적으로 관리할 수 있습니다.

application.yml
spring:
  messages:
    basename:
      - messages
      - errors
    encoding: UTF-8
    cache-duration: -1
    always-use-message-format: true
    fallback-to-system-locale: true
    use-code-as-default-message: false
  web:
    locale: ko
    locale-resolver: accept_header

그런 다음, 󰝰 resources 디렉토리 하위에 각 언어별 메시지 프로퍼티 파일을 추가합니다. 이 파일들은 특정 언어에 맞는 메시지를 저장하는 역할을 합니다.

기본적으로 제공되는 메시지는 messages.properties 파일에 key-value 형식으로 작성합니다. 별도의 언어 코드가 없는 경우 기본적으로 이 파일의 메시지가 사용됩니다.

messages.properties
i18n.greeting=🇰🇷안녕하세요, 저희 비공식 기술 블로그에 오신 것을 환영합니다. 다양한 기술 정보를 제공하며, 개발과 관련된 유익한 내용을 공유합니다.

영어 버전의 메시지는 messages_en.properties 파일을 추가하여 작성합니다.

messages_en.properties
i18n.greeting=🇺🇸Hello, welcome to our unofficial tech blog. We provide various technical insights and share valuable content related to development.

일본어 버전의 메시지도 마찬가지로 messages_ja.properties를 생성하여 작성합니다.

messages_ja.properties
i18n.greeting=🇯🇵こんにちは、私たちの非公式技術ブログへようこそ!さまざまな技術情報を提供し、開発に関する有益な内容を共有します。

마지막으로, API 컨트롤러에서 LocaleContextHolder를 사용하여 현재 요청의 로케일을 추출하고, 이를 기반으로 적절한 메시지를 반환하는 로직을 구현합니다:

GreetingController.java
@Slf4j
@RestController
public class GreetingController {

    private final MessageSource messageSource;

    public GreetingController(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @GetMapping("/greetings")
    public ResponseEntity<?> greeting(
    ) {
        Locale locale = LocaleContextHolder.getLocale();
        String greeting = messageSource.getMessage("i18n.greeting", null, locale);
        return ResponseEntity
                .ok(greeting);
    }
}

이제 클라이언트가 API를 호출할 때 Accept-Language 헤더에 원하는 언어 코드를 추가하면, 서버는 해당 언어에 맞는 메시지를 반환합니다.

예를 들어, 클라이언트가 Accept-Language 헤더에 ko를 설정하여 한국어로 요청을 보내면:

api-korean.http
GET /greetings HTTP/1.1
Content-Type: application/json
Accept-Language: ko
Host: localhost:8080
Connection: close
User-Agent: RapidAPI/4.2.8 (Macintosh; OS X/15.3.1) GCDHTTPRequest

서버는 이에 대한 응답으로 한국어로 작성된 메시지를 반환하게 됩니다:

successful-in-korean.txt
🇰🇷안녕하세요, 저희 비공식 기술 블로그에 오신 것을 환영합니다. 다양한 기술 정보를 제공하며, 개발과 관련된 유익한 내용을 공유합니다.

이와 같은 방식으로 Accept-Language 헤더에 en을 설정하면 영어 응답이, ja를 설정하면 일본어 응답이 반환됩니다. 이를 통해 간단하면서도 효과적으로 다국어 API 응답을 구현할 수 있습니다.

successful-in-english.txt
🇺🇸Hello, welcome to our unofficial tech blog. We provide various technical insights and share valuable content related to development.

Failed Messages Handling with Message Properties

API에서 특정 조건에 따라 예외가 발생하는 경우, 해당 예외 메시지도 다국어로 제공될 수 있도록 메시지 프로퍼티를 활용할 수 있습니다. 이를 위해 오류 메시지를 별도의 프로퍼티 파일에 정의합니다.

기본 오류 메시지는 errors.properties에 정의되며, 기본적으로 사용됩니다.

errors.properties
i18n.greeting=🇰🇷안녕하세요, 저희 비공식 기술 블로그에 오신 것을 환영합니다. 다양한 기술 정보를 제공하며, 개발과 관련된 유익한 내용을 공유합니다.

영어 메시지는 errors_en.properties에 작성합니다.

errors_en.properties
i18n.errors.rejection=🇺🇸This resource is only accessible to authenticated users. Please log in and try again.

마찬가지로 일본어 메시지는 errors_ja.properties에 작성합니다.

errors_ko.properties
i18n.errors.rejection=🇯🇵このリソースには認証されたユーザーのみがアクセスできます。ログインして再試行してください。

다음은 컨트롤러에서 요청 파라미터를 추가하여 예외를 발생시키고,

GreetingController.java
@Slf4j
@RestController
public class GreetingController {
    ...

    @GetMapping("/greetings")
    public ResponseEntity<?> greeting(
+           @RequestParam boolean isRejected
    ) {
+       if (isRejected) throw new GreetingFailureException("i18n.errors.rejection", HttpStatus.FORBIDDEN);

        Locale locale = LocaleContextHolder.getLocale();
        String greeting = messageSource.getMessage("i18n.greeting", null, locale);
        return ResponseEntity
                .ok(greeting);
    }
}

컨트롤러 어드바이스를 통해 해당 예외를 다국어 메시지로 변환합니다.

GreetingControllerAdvice.java
@Slf4j
@RestControllerAdvice
public class GreetingControllerAdvice {

    private final MessageSource messageSource;

    public GreetingControllerAdvice(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @ExceptionHandler(GreetingFailureException.class)
    public ResponseEntity<String> handleRejectionException(
            GreetingFailureException e,
            Locale locale
    ) {
        String message = messageSource.getMessage(e.messageKey(), null, locale);
        return ResponseEntity
                .status(e.status())
                .body(message);
    }
}

이제 클라이언트가 isRejected=true를 포함하여 요청을 보내면, 서버는 컨트롤러 어드바이스에서 Accept-Language 헤더의 값을 확인한 후, 해당하는 언어의 오류 메시지를 찾아 반환합니다.

Korean
GET /greetings?isRejected=true HTTP/1.1
Content-Type: application/json
Accept-Language: ko
Host: localhost:8080
Connection: close
User-Agent: RapidAPI/4.2.8 (Macintosh; OS X/15.3.1) GCDHTTPRequest

예를 들어, Accept-Language 값이 ko인 경우에는 다음과 같은 응답을 받게 됩니다.

exceptions-in-korean.txt
🇰🇷해당 리소스는 인증된 사용자만 접근이 가능합니다. 로그인 후 다시 시도해주세요.

이와 같은 방식으로 Accept-Language 헤더에 ja를 설정하면 일본어 응답이 반환됩니다. 이를 통해 간단하면서도 효과적으로 다국어 예외 응답을 구현할 수 있습니다.

exceptions-in-japanese.txt
🇯🇵このリソースには認証されたユーザーのみがアクセスできます。ログインして再試行してください。

IntelliJ IDEA Plugins

다국어 메시지를 효율적으로 관리하기 위해 IntelliJ IDEA의 Resource Bundle Editor 플러그인을 활용할 수 있습니다. 이 플러그인은 여러 개의 다국어 메시지 파일을 한눈에 비교하고, 보다 쉽게 편집할 수 있도록 도와줍니다.

1. Installing Resource Bundle Editor Plugin

플러그인을 사용하려면 Settings ▸ Plugins에서 Resource Bundle Editor를 검색하여 설치하면 됩니다.

implementing-internationalization-and-localization-for-global-services-with-spring-i18n-and-l10n_00.png

2. Multiple Language Message Files at a Glance

기본적으로 IntelliJ IDEA에서는 .properties 파일을 개별적으로 열어야 하지만, Resource Bundle Editor 플러그인을 사용하면 여러 언어의 메시지 파일을 하나의 테이블 형식으로 볼 수 있습니다.

implementing-internationalization-and-localization-for-global-services-with-spring-i18n-and-l10n_01.png

이제 메시지를 추가하거나 수정할 때 각 언어별 값을 동시에 확인하면서 편집할 수 있습니다. 또한, 이 플러그인은 자동 완성 기능을 제공하여 기존 키를 빠르게 검색할 수 있으며, 특정 키의 값을 여러 언어에서 동시에 수정할 수도 있습니다.

이렇게 메시지 프로퍼티를 활용한 다국어 지원 시, Resource Bundle Editor 플러그인은 정말 유용한 도구가 될 것입니다. 🧩

Closing Notes

이번 글에서는 Spring Boot에서 메시지 프로퍼티를 활용하여 다국어 API 응답을 구성하는 방법을 살펴보았습니다. 이를 통해 클라이언트가 요청하는 언어에 따라 적절한 메시지를 반환할 수 있도록 설정하는 과정을 이해할 수 있었습니다.

추가적으로, 메시지 프로퍼티에서 key-value 형식의 키를 어떻게 설정하는 것이 좋은지에 대한 고민도 필요합니다. 일반적으로 사용되는 몇 가지 방식은 다음과 같습니다.

  • 도메인 기반 키: user.login.success, order.payment.failed
    • 도메인(예: user, order)을 기준으로 그룹을 나누어 메시지를 구조화하는 방식
    • 관리가 용이하고, 관련 메시지를 한눈에 파악하기 쉬움
  • 숫자 기반 키: 1001, 2002
    • 에러 코드처럼 특정 규칙에 맞는 숫자 키를 사용하는 방식
    • 개발자 입장에서 직관적이지 않으며, 코드별 의미를 별도로 관리해야 함
  • 한글 키: 로그인 성공, 결제 실패, 로그인.성공, 결제.실패
    • 키 자체만으로 의미를 쉽게 파악할 수 있어 직관적이지만, 영어 기반 프로젝트에서는 활용하기 어려울 수도 있음
    • 🔗 Inflearn 기술 블로그 참고

어떤 방식을 선택할지는 프로젝트의 특성과 팀의 운영 방식에 따라 다르지만, 도메인 기반 키 방식이 가장 일반적이며 유지보수에 유리한 구조를 제공합니다.

지금까지 살펴본 내용은 비교적 간단한 API 응답 메시지와 예외 메시지의 다국어 처리 방법이었습니다. 그러나 실제 서비스에서는 상품명과 같은 데이터베이스에 저장된 다국어 데이터를 어떻게 처리할지에 대한 고민도 필요합니다.

다음 글로벌 서비스를 위한 다국어 데이터베이스 설계하기에서 데이터베이스에 저장된 다국어 데이터를 관리하는 방법과, 이를 API 응답에서 효과적으로 제공하는 방법에 대해 자세히 다뤄보겠습니다. 🛵 💨

  • Java
  • Spring