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

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는 모든 정보가 다 나오지 않는다.
  • 삽질하지 말고 안될때는 기본부터 천천히..

Read more

나의 프로그래밍 폰트 사용 일대기

나의 프로그래밍 폰트 사용 일대기

시작은 2003년 이제 막 프로그래머로써 첫발을 내딛을 때부터 나는 프로그래밍 폰트에 대해서 관심이 많은 편이었다. 화면 붙잡고 매일 글자들과 씨름하는 직업이다보니 당연하게도 좀더 눈에 잘 보이고, 보기에 좀더 미려하고 조화스러운 폰트를 찾는 것이 어찌보면 약간 본능(?)적으로 관심을 가졌던게 아닌가 싶기도 하고 말이다. 최근까지도 이 주체할 수 없는 본능에 따라

By Kevin H. Kwon
Istio 를 통한 path(url) 기반 Local Rate Limit 적용

Istio 를 통한 path(url) 기반 Local Rate Limit 적용

몇 년 전인지는 기억나진 않지만 Rate Limit 적용은 항상 애플리케이션 쪽에서 처리하는 것이 당연하다는 것이 주된 의견이었다. 그래서 그때 당시 Bucket4J 를 통해서 Spring 쪽에서 처리하고 했던 기억이 있다. 이제는 당연하게도 Istio와 같은 Service Mesh쪽에서 처리하는 것이 응당 맞다고 생각되는 것이 개발 세상이 이제 점점 더 클라우드향으로 이동된다는 느낌이다. 강력한

By Kevin H. Kwon
Istio를 통한 header기반 API 라우팅/호출 시 cors preflight request 이슈 트러블슈팅 기록

Istio를 통한 header기반 API 라우팅/호출 시 cors preflight request 이슈 트러블슈팅 기록

현재 개발하고 있는 일부 컨테이너 기반의 서비스들을 Istio를 통해 서비스들을 구성하고 트래픽을 관리하고 있다. 이때 컨테이너 서비스가 같은 규격이 여러개가 같은 url과 port를 할당 받아서 사용해야는 애로 사항이 있어 Istio에서 header 기반으로 특별한 헤더가 있는 경우에만 라우팅이 될 수 있도록 구성하고 테스트를 진행했었다. Istio Request Routing 예제와 같이 header

By Kevin H. Kwon