Spring Boot(Spring) i18n 설정 시 주의사항

장장 네시간(?)의 삽질 후에 혹시나 다른 누군가가 비슷한 곤란한 상황에 빠졌을 때 도움이 되길 바라면서 포스트를 써본다.

사내에서 솔루션 관련 내부 프로젝트 구성 중에 간단하게 배포해야하는 서비스 구조가 있어서 그 동안 눈으로만 훑어봤던 Spring Boot를 사용하기로 맘 먹고 레퍼런스 보면서 하나씩 붙여나가는 도중에 예기치 않게 i18n 구성 중에 메시지 파일을 계속 못 불러오는 불상사가 일어났다.

결론부터 말하자면..

결론적으로 말하자면 messages 파일의 이름에 . 이 들어가면 로딩이 안된다.

비극의 시작

레퍼런스를 보면서 차근히 하라는 데로 시작했다.

spring:
  messages:
    always-use-message-format: false
    cache-seconds: -1
    basename: i18n/message, i18n/error.message # 이 부분
    encoding: UTF-8

레퍼런스에 따르면 Spring Boot의 MessageSourceAutoConfiguration을 사용한 INTERNATIONALIZATION 설정은 아래와 같이 진행할 수 있다.

# INTERNATIONALIZATION (MessageSourceAutoConfiguration)
spring.messages.always-use-message-format=false # Set whether to always apply the MessageFormat rules, parsing even messages without arguments.
spring.messages.basename=messages # Comma-separated list of basenames, each following the ResourceBundle convention.
spring.messages.cache-seconds=-1 # Loaded resource bundle files cache expiration, in seconds. When set to -1, bundles are cached forever.
spring.messages.encoding=UTF-8 # Message bundles encoding.
spring.messages.fallback-to-system-locale=true # Set whether to fall back to the system Locale if no files for a specific Locale have been found.

message 파일을 하나로 가져가지 않고 여러개 사용할 경우 comma로 구분해서 다수의 basename을 설정하여 사용할 수 있다.

해서 위 yaml 설정 처럼 당당하게 i18n/error.message을 추가하였다.

    basename: i18n/message, i18n/error.message

죽어도 파일 로딩을 못하는 것이다. --debug 설정을 걸어도 항상 맨 앞에 있는 basename만 읽어오는 것 처럼 보인다.

   MessageSourceAutoConfiguration matched:
      - ResourceBundle found bundle URL [file:/C:/app/workspace/xstream-portal/src/main/resources/i18n/message.properties] (MessageSourceAutoConfiguration.ResourceBundleCondition)
      - @ConditionalOnMissingBean (types: org.springframework.context.MessageSource; SearchStrategy: current) did not find any beans (OnBeanCondition)

WebMvcConfigurerAdapter 으로 설정이 추가되면 혹여나 MessageSourceAutoConfiguration이 무시되고 문제가 되는 것인가?

혹시나 해서 AutoConfiguration 문제인가 해서 MessageSourceAutoConfiguration을 exclude하고 직접 아래처럼 설정해봐도

@EnableAutoConfiguration(exclude = {MessageSourceAutoConfiguration.class})
...
@Value("${spring.messages.basename}")
String messagesBasename = null
...
@Bean
public ReloadableResourceBundleMessageSource messageSource(){
   ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
   messageSource.setBasenames(messagesBasename.split(","));
   messageSource.setDefaultEncoding(messagesEncoding);
   messageSource.setCacheSeconds(messagesCacheSeconds);

   return messageSource;
}

여전히 못불러오고 basename의 convention 문제인가 해서 아래처럼 다 해봐도..

spring:
  messages:
    basename: i18n/message, i18n/error.message
    basename: classpath:i18n/message, classpath:i18n/error.message
    basename: classpath:/i18n/message, classpath:/i18n/error.message
    basename: classpath*:i18n/message, classpath*:i18n/error.message
    basename: classpath*:/i18n/message, classpath*:/i18n/error.message

여전히 못불러오는 것이다 ㅎㅎㅎㅎㅎㅎㅎㅎ 이쯤되면 정신이 혼란해지고 구글은 더 이상 조력자가 되지 않는 상황이 발생한다.

아예 처음부터 다시 시작해봐도. 잘되어 있는 sample 프로젝트르 받아서 변경해봐도.. 안된다..

마음을 다시 한번 가다듬고.. 근데.. 설마.. 혹시나 해서 관련된 소스를 까보기 시작하니..

final String resourceName = toResourceName(bundleName, "properties");

ResourceBundleMessageSource에서 Bundle 파일 생성 시 toResourceName 메소드가 호출된다. 따라가보니...

public final String toResourceName(String arg0, String arg1) {
    StringBuilder arg2 = new StringBuilder(arg0.length() + 1 + arg1.length());
    arg2.append(arg0.replace('.', '/')).append('.').append(arg1);
    return arg2.toString();
}

쉣!! .을 path 구분자로 변환한다 ㅠㅠㅠㅠㅠㅠ

마음을 가다 듬고 .이 들어가지 않는 basename으로 변경하니 잘 안다. 아주 잘 읽어온다.

spring:
  messages:
    basename: i18n/message, i18n/error, # 열받아서 삼십개 정도 더 붙여봤는데 잘 된다 ㅋㅋㅋ
└── resources
    ├── application.yml
    ├── banner.txt
    ├── i18n
    │   ├── error.properties
    │   ├── error_ko_KR.properties
    │   ├── message.properties
    │   └── message_ko_KR.properties
    ├── logback-spring.xml
    ....

i18n 설정 시 주의사항

  • basename의 resource 이름에는 .를 넣지 않는다. 앞으로 다른 파일 만들때도 안 넣을 것 같다. ㅜㅜ
  • Spring과 Spring Boot의 basename Convention은 약간 상이하다. classpath prefix가 붙지 않는다.
  • 위처럼 아무 내용이 없더라고 basename.properties 파일을 생성한다.
  • --debug 옵션의 autoconfiguration report는 모든 정보가 다 나오지 않는다.
  • 삽질하지 말고 안될때는 기본부터 천천히..