Post

Spring Boot와 JabRef에서 배운 다국어 설정 & 국제화 (i18n)

Spring Boot와 JabRef에서 배운 다국어 설정 & 국제화 (i18n)

안녕하세요!

이번 포스트에서는 Spring 프레임워크와 오픈소스 프로젝트 JabRef에서 직접 경험한 다국어 웹 애플리케이션 개발의 핵심 기술인 국제화(i18n: Internationalization) 설정과 구현 방법을 자세히 알아보겠습니다. 특히 최근 제가 참여한 JabRef PR #12582를 통해 배운 실전 경험을 공유합니다.


1. 국제화(i18n)란 무엇일까요?

국제화(Internationalization, i18n)는 웹 서비스나 소프트웨어가 특정 언어나 지역에 종속되지 않고, 다양한 언어와 문화권 사용자에게 맞춤형 경험을 제공할 수 있도록 설계하는 과정을 의미합니다. i18n은 ‘I’와 ‘n’ 사이에 18개의 글자가 생략되었다는 의미를 담고 있는 약어입니다.

국제화의 핵심 목표는 사용자의 로케일(Locale), 즉 언어, 국가, 지역 설정에 따라 콘텐츠를 유연하게 제공하는 데 있습니다. 예를 들어, 같은 웹 페이지라도 한국 사용자에게는 한국어로, 미국 사용자에게는 영어로 메시지를 표시하는 것이죠. 이를 통해 사용자 친화적인 웹 환경을 구축하고, 글로벌 사용자층을 확보할 수 있습니다.

  • i18n: Internationalization의 약어 (I와 n 사이 18글자 생략)
  • 지역화(Localization, l10n): 국제화된 소프트웨어를 특정 로케일에 맞게 번역하고 조정하는 과정 (l과 n 사이 10글자 생략)

Spring Framework와 JabRef는 이러한 국제화 및 지역화 과정을 효율적으로 관리할 수 있도록 강력한 도구들을 제공합니다.


2. Locale 객체 완벽 이해: 언어와 지역 정보의 핵심

java.util.Locale 클래스는 Java에서 언어, 국가, 지역에 대한 정보를 담는 핵심 클래스입니다. 국제화된 애플리케이션 개발에서 사용자의 로케일 정보를 정확히 파악하고 활용하는 것은 매우 중요합니다.

2.1 Locale의 역할: 언어와 국가 코드

Locale 객체는 크게 두 가지 주요 정보를 포함합니다.

  • 언어(language): 사용자의 언어를 나타내는 코드입니다. ISO 639 표준에 따른 2글자 또는 3글자 언어 코드가 사용됩니다. (예: 한국어 ko, 영어 en, 일본어 ja)
  • 국가(country): 사용자의 국가 또는 지역을 나타내는 코드입니다. ISO 3166 표준에 따른 2글자 또는 3글자 국가 코드가 사용됩니다. (예: 대한민국 KR, 미국 US, 캐나다 CA)

이 두 가지 정보를 조합하여 특정 로케일을 표현합니다. 예를 들어, Locale.KOREA 상수는 한국(KR)과 한국어(ko)를 조합한 ko_KR 로케일을 나타냅니다.

2.2 Java에서 제공하는 미리 정의된 Locale 상수

Java는 개발자들이 자주 사용하는 로케일을 Locale 클래스의 상수로 미리 정의하여 제공합니다. 이러한 상수를 활용하면 편리하게 Locale 객체를 생성할 수 있습니다.

1
2
3
Locale.KOREAN  // 언어: 한국어 (ko)
Locale.KOREA   // 국가: 대한민국, 언어: 한국어 (ko_KR)
Locale.US      // 국가: 미국, 언어: 영어 (en_US)

주의: Locale 상수는 자주 사용되는 로케일에 대해 편의를 제공하지만, 모든 로케일을 포함하지는 않습니다. 따라서 다양한 로케일을 지원해야 하는 경우에는 상수보다는 생성자를 사용하는 것이 더 유연합니다.

2.3 Locale 생성자를 활용한 다양한 로케일 정의

Locale 클래스는 다양한 생성자를 제공하여 개발자가 필요에 따라 로케일을 직접 정의할 수 있도록 합니다.

1
2
3
Locale locale1 = new Locale("en");          // 영어 (언어 코드만 지정)
Locale locale2 = new Locale("en", "CA");    // 영어 (캐나다) - 언어 코드와 국가 코드 지정
Locale locale3 = new Locale("en", "US", "SiliconValley"); // 영어 (미국, 실리콘 밸리) - 언어, 국가, 지역 세분화
  • language: 필수 파라미터이며, ISO 639-1 또는 ISO 639-2 표준에 따른 2글자(en, ko) 또는 3글자(eng, kor) 언어 코드를 사용합니다.
  • country: 선택적 파라미터이며, ISO 3166-1 alpha-2 표준에 따른 2글자(US, KR) 또는 ISO 3166-1 alpha-3 표준에 따른 3글자 숫자 국가 코드를 사용할 수 있습니다.
  • variant: 선택적 파라미터이며, 특정 국가 내에서의 지역, 방언, 또는 기타 변형을 세분화하기 위해 사용됩니다. 실무에서는 자주 사용되지는 않습니다.

3. 국제화의 핵심: 메시지(Message) 관리 전략

국제화의 핵심은 메시지를 효율적으로 관리하는 것입니다. 웹 애플리케이션 내에서 사용되는 텍스트 문자열을 코드에 직접 하드코딩하는 대신, 키(Key)-값(Value) 쌍으로 관리하여 로케일에 따라 동적으로 메시지를 불러오는 방식을 사용합니다.

3.1 로케일별 메시지 파일 구성 예시

일반적으로 메시지 파일은 properties 형식으로 관리하며, 각 언어별로 별도의 파일을 생성합니다. 파일명은 messages_{언어코드}.properties 규칙을 따릅니다.

  • messages_ko.properties (한국어 메시지 파일):
    1
    2
    3
    4
    5
    
    greeting=안녕하세요
    welcome.message=환영합니다!
    product.name=제품 이름
    product.price=가격
    button.submit=확인
    
  • messages_en.properties (영어 메시지 파일):
    1
    2
    3
    4
    5
    
    greeting=Hello
    welcome.message=Welcome!
    product.name=Product Name
    product.price=Price
    button.submit=Submit
    

이처럼 메시지를 파일로 분리하여 관리하면, 코드 수정 없이 메시지 파일만 변경하여 다국어 지원을 쉽게 확장할 수 있습니다.


4. Spring에서 다국어 설정 완벽 가이드

Spring Framework는 다국어 지원을 위한 강력한 MessageSource 인터페이스와 다양한 구현체를 제공합니다. Spring Boot 환경에서는 기본적으로 MessageSource가 자동 설정되지만, 필요에 따라 커스터마이징할 수 있습니다.

4.1 MessageSource 커스터마이징 설정

Spring Boot 프로젝트에서 기본 MessageSource 설정을 변경하거나, 추가 설정을 하고 싶다면 다음과 같이 MessageSource Bean을 직접 정의할 수 있습니다.

1
2
3
4
5
6
7
@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasenames("messages"); // messages.properties 기본 이름 설정
    messageSource.setDefaultEncoding("UTF-8"); // 파일 인코딩 설정
    return messageSource;
}
  • ResourceBundleMessageSource: properties 파일 기반 메시지 관리를 위한 MessageSource 구현체입니다.
  • setBasenames("messages"): 메시지 파일의 기본 이름(basename)을 설정합니다. messages 로 설정하면 messages.properties, messages_ko.properties, messages_en.properties 등의 파일을 메시지 파일로 인식합니다. 확장자 .properties는 생략합니다.
  • setDefaultEncoding("UTF-8"): 메시지 파일의 인코딩을 설정합니다. 다국어 지원을 위해 UTF-8 인코딩을 사용하는 것이 일반적입니다.

4.2 메시지 파일 준비: src/main/resources

메시지 파일은 Spring Boot 프로젝트의 기본 리소스 경로인 src/main/resources 폴더 아래에 위치해야 합니다. 앞서 예시로 든 한국어(messages_ko.properties)와 영어(messages_en.properties) 메시지 파일을 src/main/resources 폴더에 생성합니다.

  • src/main/resources/messages_ko.properties:
    1
    2
    
    welcome.message=환영합니다!
    hello.user=안녕하세요, {0}님!
    
  • src/main/resources/messages_en.properties:
    1
    2
    
    welcome.message=Welcome!
    hello.user=Hello, {0}!
    

4.3 Spring에서 메시지 사용법

Spring에서 설정한 MessageSource를 통해 메시지를 사용하는 방법은 크게 두 가지입니다.

4.3.1 컨트롤러에서 메시지 사용

컨트롤러에서 MessageSource를 주입받아 getMessage() 메서드를 사용하여 메시지를 가져올 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Controller
public class WelcomeController {

    @Autowired
    private MessageSource messageSource;

    @GetMapping("/welcome")
    public String welcome(Model model, Locale locale) {
        // 1. 기본 메시지 가져오기 (locale 정보 활용)
        String welcomeMessage = messageSource.getMessage("welcome.message", null, locale);
        model.addAttribute("welcomeMessage", welcomeMessage);

        // 2. 파라미터 메시지 가져오기 (locale, arguments 활용)
        String userName = "홍길동";
        String helloMessage = messageSource.getMessage("hello.user", new Object[]{userName}, locale);
        model.addAttribute("helloMessage", helloMessage);

        return "welcome"; // welcome.html 뷰 반환
    }
}
  • messageSource.getMessage(String code, Object[] args, Locale locale): 메시지를 가져오는 핵심 메서드입니다.
    • code: 메시지 키 (예: welcome.message, hello.user)
    • args: 메시지 내에 포함될 파라미터 배열 (선택 사항). {0}, {1}… 와 같이 메시지 파일에 정의된 placeholders를 순서대로 채웁니다.
    • locale: 사용자 로케일 정보 (Locale 객체). 이 로케일에 맞는 메시지 파일을 찾아서 메시지를 반환합니다.

4.3.2 Thymeleaf 템플릿에서 메시지 사용

Thymeleaf 템플릿 엔진을 사용하는 경우, #{...} 메시지 표현식을 사용하여 메시지 파일에 정의된 메시지를 간편하게 출력할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Welcome</title>
</head>
<body>
    <h1 th:text="#{welcome.message}"></h1>

    <p th:text="#{hello.user(${userName})}"></p>
</body>
</html>
  • #{message.key}: messages.properties 파일에서 message.key에 해당하는 메시지를 가져와서 출력합니다. Thymeleaf는 현재 로케일에 따라 자동으로 해당 언어의 메시지를 선택합니다.
  • #{message.key(param1, param2, ...)}: 파라미터 메시지를 출력할 때 사용합니다. messages.properties 파일에 {0}, {1}… 와 같은 placeholders가 정의되어 있다면, param1, param2… 값이 순서대로 placeholders를 대체합니다.

5. LocaleResolver를 이용한 로케일 관리

LocaleResolver는 웹 요청(request)에서 사용자의 로케일을 결정하는 역할을 담당하는 Spring Framework의 인터페이스입니다. Spring MVC는 LocaleResolver를 통해 현재 요청에 대한 로케일을 확인하고, 이를 기반으로 국제화된 기능을 제공합니다.

Spring Boot는 기본적으로 AcceptHeaderLocaleResolverLocaleResolver로 사용합니다.

5.1 주요 LocaleResolver 구현체

Spring Framework는 다양한 LocaleResolver 구현체를 제공하여 개발자가 로케일 결정 방식을 선택할 수 있도록 합니다.

  • AcceptHeaderLocaleResolver: Accept-Language HTTP 헤더를 기반으로 로케일을 결정합니다. 웹 브라우저는 Accept-Language 헤더를 통해 사용자가 선호하는 언어 정보를 서버에 전달하며, AcceptHeaderLocaleResolver는 이 정보를 활용합니다. Spring Boot의 기본 LocaleResolver입니다.

  • SessionLocaleResolver: HTTP 세션에 로케일 정보를 저장하고 관리합니다. 사용자가 명시적으로 로케일을 변경하는 경우, 세션에 변경된 로케일 값을 저장하여 이후 요청부터는 세션에 저장된 로케일을 사용합니다.

  • CookieLocaleResolver: 쿠키에 로케일 정보를 저장하고 관리합니다. SessionLocaleResolver와 유사하게, 사용자가 로케일을 변경하면 쿠키에 저장하여 클라이언트 측에서 로케일 정보를 유지합니다. 세션 기반 방식에 비해 서버 부하를 줄일 수 있다는 장점이 있습니다.

5.2 SessionLocaleResolver 설정 예시 (세션 기반 로케일 관리)

SessionLocaleResolverLocaleResolver로 사용하도록 설정하고, 기본 로케일을 한국어로 설정하는 예시입니다.

1
2
3
4
5
6
@Bean
public LocaleResolver localeResolver() {
    SessionLocaleResolver resolver = new SessionLocaleResolver();
    resolver.setDefaultLocale(Locale.KOREA); // 기본 로케일: 한국 (ko_KR)
    return resolver;
}
  • setDefaultLocale(Locale.KOREA): 기본 로케일을 설정합니다. Accept-Language 헤더 정보가 없거나, 로케일 결정에 실패한 경우 기본적으로 사용될 로케일입니다. 위 예시에서는 Locale.KOREA (한국) 로케일을 기본값으로 설정했습니다.

5.3 LocaleResolver를 이용한 동적인 로케일 변경

LocaleResolver를 사용하면 런타임에 동적으로 사용자 로케일을 변경할 수 있습니다. 예를 들어, 사용자가 웹 페이지에서 언어 선택 드롭다운 메뉴를 통해 언어를 변경하면, LocaleResolver를 통해 현재 로케일을 변경하고, 변경된 로케일에 맞춰 웹 페이지를 다시 렌더링할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
@Controller
public class LanguageController {

    @GetMapping("/change-lang")
    public String changeLanguage(@RequestParam("lang") String lang, HttpServletRequest request, HttpServletResponse response) {
        LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request); // LocaleResolver 획득
        localeResolver.setLocale(request, response, new Locale(lang)); // 로케일 변경
        return "redirect:/welcome"; // welcome 페이지로 리다이렉트
    }
}
  • RequestContextUtils.getLocaleResolver(request): HttpServletRequest에서 현재 설정된 LocaleResolver를 획득합니다.
  • localeResolver.setLocale(request, response, new Locale(lang)): LocaleResolver를 통해 로케일을 변경합니다. 첫 번째 파라미터는 HttpServletRequest, 두 번째 파라미터는 HttpServletResponse, 세 번째 파라미터는 변경할 Locale 객체입니다. lang 파라미터는 사용자가 선택한 언어 코드 (예: ko, en)를 전달받습니다.
  • return "redirect:/welcome": 로케일 변경 후, /welcome 페이지로 리다이렉트하여 변경된 로케일이 적용된 웹 페이지를 사용자에게 보여줍니다.

6. 실습: Spring Boot 다국어 웹 페이지 만들기

지금까지 학습한 내용을 바탕으로 간단한 다국어 웹 페이지를 만들어 보겠습니다.

6.1 메시지 파일 준비

src/main/resources 폴더에 다음 두 개의 메시지 파일을 생성합니다.

  • src/main/resources/messages_ko.properties:
    1
    
    welcome.message=환영합니다!
    
  • src/main/resources/messages_en.properties:
    1
    
    welcome.message=Welcome!
    

6.2 컨트롤러 (WelcomeController.java)

1
2
3
4
5
6
7
8
9
10
11
12
@Controller
public class WelcomeController {
    @Autowired
    private MessageSource messageSource;

    @GetMapping("/welcome")
    public String welcome(Model model, Locale locale) {
        String message = messageSource.getMessage("welcome.message", null, locale);
        model.addAttribute("message", message);
        return "welcome"; // welcome.html 뷰 반환
    }
}

6.3 뷰 템플릿 (welcome.html)

src/main/resources/templates 폴더에 welcome.html 파일을 생성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Welcome</title>
</head>
<body>
    <h1 th:text="#{welcome.message}"></h1>

    <p th:text="#{hello.user(${userName})}"></p>
</body>
</html>

6.4 실행 및 확인

Spring Boot 애플리케이션을 실행하고, 웹 브라우저에서 /welcome URL로 접속합니다.

  • 브라우저 언어 설정이 한국어인 경우: “환영합니다!” 메시지가 표시됩니다.
  • 브라우저 언어 설정이 영어인 경우: “Welcome!” 메시지가 표시됩니다.

브라우저의 언어 설정에 따라 메시지가 다르게 출력되는 것을 확인할 수 있습니다.


7. JabRef에서 배운 실전 다국어 구현 사례

JabRef는 Java/JavaFX 기반의 오픈소스 참고문헌 관리 프로그램입니다. 최근 제가 참여한 PR #12582를 통해 실제 프로덕션 레벨의 다국어 구현 방식을 학습할 수 있었습니다.

7.1 Spring과 JabRef의 다국어 처리 비교

특성SpringJabRef
기본 메커니즘MessageSource 인터페이스ResourceBundle 기반 Localization 클래스
메시지 파일 위치src/main/resources/messages_*.propertiessrc/main/resources/l10n/JabRef_*.properties
메시지 접근 방식messageSource.getMessage(key, args, locale)Localization.lang(key, args)
UI 통합Thymeleaf #{key} 표현식JavaFX FXML %key 접두사
로케일 결정LocaleResolver 구현체Locale.getDefault() 또는 사용자 설정
파라미터 처리{0}, {1} 등의 인덱스%0, %1 등의 인덱스

두 시스템 모두 유사한 원칙을 따르지만 구현 방식에 차이가 있습니다. Spring은 웹 애플리케이션에 최적화된 반면, JabRef는 데스크톱 애플리케이션에 맞게 설계되었습니다.

7.2 JabRef의 다국어 처리 아키텍처

JabRef는 Java의 ResourceBundle을 활용하여 다국어를 구현합니다. 각 언어별 메시지는 properties 파일에 key-value 쌍으로 저장되며, 다음과 같은 구조를 가집니다:

1
2
3
4
5
# src/main/resources/l10n/JabRef_en.properties (영어)
preference.downloadLinkedFiles=Download referenced files (PDFs, ...)

# src/main/resources/l10n/JabRef_ko.properties (한국어) 
preference.downloadLinkedFiles=참조된 파일 다운로드 (PDF 등...)

7.3 JavaFX FXML에서의 다국어 처리

UI 컴포넌트의 텍스트를 다국어로 처리할 때는 FXML 파일에서 % 접두사를 사용합니다:

1
2
3
4
5
6
<HBox alignment="CENTER_LEFT">
    <Label text="%Want to help?" wrapText="true"/>
    <Hyperlink text="%Make a donation"/>
    <Label text="%or" wrapText="true"/>
    <Hyperlink text="%get involved"/>
</HBox>

7.4 다국어 처리 모범 사례 (JabRef에서 배운 점)

  1. 직접 문자열 사용: 변수에 저장하지 않고 직접 Localization.lang() 메서드에 문자열을 전달합니다.
    1
    2
    3
    4
    5
    6
    
    // 좋은 예시
    Localization.lang("Translate me");
       
    // 피해야 할 예시
    String text = "Translate me";
    Localization.lang(text);
    
  2. 파라미터 활용: 문자열 연결 대신 파라미터를 활용합니다.
    1
    2
    3
    4
    5
    
    // 좋은 예시
    Localization.lang("Exported %0 entry(s).", number);
       
    // 피해야 할 예시
    Localization.lang("Exported ") + number + Localization.lang(" entry(s).");
    
  3. 문장 종료: 완전한 문장은 마침표로 종료합니다.

  4. 복수형 처리: 단수/복수를 하나의 메시지로 처리합니다.
    1
    
    Localization.lang("checked %0 entry(s)");
    

7.5 새로운 다국어 키 추가 프로세스

  1. Java 코드에 새로운 Localization.lang("KEY") 추가
  2. LocalizationConsistencyTest 실행
  3. 테스트 출력의 스니펫을 영어 번역 파일(src/main/resources/l10n/JabRef_en.properties)에 추가
  4. Crowdin을 통한 다른 언어 번역 관리

7.6 새로운 언어 추가 프로세스

  1. Language enum에 새 언어 추가 (src/main/java/org/jabref/logic/l10n/Language.java)
  2. <locale code>.properties 파일 생성
  3. Crowdin에서 새 언어 설정
  4. 필요한 경우 crowdin.yml에 언어 매핑 추가 (예: zh_CN, pt_BR)

7.7 품질 관리

JabRef는 LocalizationConsistencyTest를 통해 다국어 처리의 일관성을 검증합니다:

  • 모든 키가 기본 영어 파일에 존재하는지 확인
  • 사용되지 않는 키 검출
  • 번역 파일의 형식 검증

8. 실제 PR 경험 (#12582)

제가 참여한 PR #12582에서는 기존의 “Download linked online files” 텍스트를 “Download referenced files (PDFs, …)”로 변경하는 작업을 수행했습니다. 이 과정에서 배운 점들입니다:

  1. 명확한 메시지: 사용자가 이해하기 쉽도록 구체적인 표현 사용
  2. 일관성: 다른 UI 요소들과의 일관성 유지
  3. 테스트 중요성: LocalizationConsistencyTest를 통한 품질 보장
  4. 문서화: 사용자 문서도 함께 업데이트하여 일관성 유지

이러한 경험을 통해 실제 프로덕션 환경에서의 다국어 처리가 단순한 번역 이상의 것임을 배웠습니다. 사용자 경험, 코드 품질, 유지보수성을 모두 고려한 통합적인 접근이 필요합니다.


참고 자료

This post is licensed under CC BY 4.0 by the author.