본문 바로가기
야미스터디/Spring

나는야 다국어 전문가(호소인)! - feat. Locale

by 의정부핵꿀밤 2024. 10. 28.
728x90

 

🙌 개요

최근에 회사에서 다국어 번역 관련 업무를 처리하게 되면서 Locale에 대해 다루는 일이 많아졌습니다.

저는 사이드 플젝할 때 대략적으로만 사용만 해봤지 정확히 어떻게 사용해야 좋은 코드인지 고민하며 작성해보는 것이 이번이 처음이라, 이왕이면 이에 대해 공부하며 기록하고 좋은 코드를 작성해보고자 합니다 :-)

 

🧐 Locale에 대해 알아보쟈!

Locale 이란?

  • 나라, 언어 등 지역들에 대한 정보를 담고 있는 자바 객체이다
  • 이는 Accept-Language 헤더, request params, Cookie/Session 등의 방법을 통해 Locale을 사용할 수 있다

 

Locale 형식

language[_territory][.codeset][@modifier]
  • 위와 같이 언어, 지역, 코드셋의 3개의 값을 묶어 Locale이 구성된다

 

LocaleResolver

  • 스프링 프레임워크에서 국제화(i18n)를 구현하기 위한 핵심 인터페이스
  • LocaleResolver 인터페이스를 사용하면 요청, 세션, 쿠키 등을 기반으로 구현할 수 있다
💡 i18n 이란?
internationalization을 줄인 것으로 국제화를 의미한다

 

LocaleResolver 종류

  • AcceptHeaderLocaleResolver
    • default 구현체 설정
    • Accept-Language 헤더를 통해 Locale을 선택한다
    • setLocale 메서드를 지원하지 않아, 중간에 서버에서 언어를 바꿀 수 없다
  • CookieLocaleResolver
    • Cookie를 이용해 Locale을 선택한다
    • setLocale 메서드를 사용할 경우, 브라우저의 Cookie에 Locale 정보가 저장된다
    • Set-Cookie: en_US
  • SessionLocaleResolver
    • Session을 이용해 Locale을 선택한다
    • setLocale 메서드를 사용할 경우, 서버의 Session에 Locale 정보가 저장된다
  • FixedLocaleResolver
    • 웹 요청에 상관없이 특정한 Locale을 선택한다
    • 초기 설정에서 사용한 Locale값 만을 사용하며, setLocale 메서드를 지원하지 않는다

 

LocaleContextHolder

  • 애플리케이션의 현재 지역화 정보를 관리하고 접근할 수 있도록 제공하는 Spring 프레임워크 유틸리티 클래스
  • 현재의 Locale 정보를 thread-local에 저장하여 애플리케이션의 어디서든지 쉽게 접근할 수 있다
    • LocaleContextHolder.getLocale()

 

주의 사항

  • LocaleContextHolder는 thread-local 기반으로 동작하므로, 같은 thread 내에서만 로케일 정보를 공유할 수 있다
  • 따라서 만약 비동기 프로그래밍을 하거나 별도의 스레드로 작업을 실행하면 ThreadLocal 값인 LocaleContextHolder를 참조할 수 없다
💡 xxxContextHolder
- spring에서 ContextHolder로 끝나는 클래스는 내부적으로 ThreadLocal을 사용하도록 구현되어 있다
- LocaleContextHolder의 생성과 삭제는 RequestContextFilter에서 수행한다

 

 

MessageSource

  • Locale에 따라 메시지를 제공하는 인터페이스
  • 다국어 메시지 출력을 위해 LocalContextHolder와 함께 자주 사용된다

 

🫡 코드 리팩토링 해부쟈!

내가 작성했던 코드와 받은 코드리뷰, 개선된 코드까지 같이부댜!

Before

실제 코드를 보여드리기는 좀 그래서 대략적으로 보여드릴게요!

 

xxxController

public MyOutput getMyList(User user, Locale locale) {
	...
	
	return myService.getMyList(user, locale);
}

 

xxxService

public MyOutput getMyList(User user, Locale locale) {
	MyOutput output = getMyList(user);
	output.setInfo(getMyInfo(locale);
	
	return output;
}

private MyInfo getMyInfo(Locale locale) {
	if (!locale.equals(Locale.KOREA) {
		...
	}
	
	return myInfo;
}

대략적으로 정리하면 이러하다

여기서 Locale 관련 부분만 보면 Locale 값이 사용되는 부분은 getMyInfo라는 내부 메소드지만, 해당 메소드로 Locale 값을 전달하기 위해서 client에게 요청을 받은 시점부터 Locale 적용이 완료될 때까지 관련 메소드가 해당 Locale 정보를 물고 다니게 된다..

위의 코드에서는 크게 중복되지 않지만, 실제 코드에서는 getMyInfo 메소드가 많은 곳에서 사용되며 꽤나 많은 곳에서 불필요한 Locale 정보를 물고 다닌다

 

내가 받은 코드 리뷰..!

메소드 매개변수에 Locale 정보를 물고 다니지말고, 아래 코드를 사용하면,
Locale locale = LocaleContextHolder.getLocale();
내부 ThreadLocal 변수에 Locale 값을 저장하고 있기 때문에 서블릿 기반의 스프링 웹 애플리케이션에서는 어디에서든지 사용 가능합니다.
단점은 아래와 같습니다.
ThreadLocal 에 저장된 값은 같은 스레드에 저장되므로 같은 스레드 안에서만 참조가 가능합니다.
따라서 비동기 프로그래밍을 하거나 별도의 스레드로 작업을 실행하면 ThreadLocal 값을 참조할 수 없습니다.

 

Wow.. 나는 LocaleContextHolder를 몰랐다..!

이런 방법이 있었다니..

다시 한번 제 무지함의 장벽과 부딪히며 이런게 존재하는지 조차 몰랐던 제가 참 어리바리 공주님 같다ㅠ

역시 스프링은.. 취미 삼아 까봐야 할만큼 움짱난 친구구나..

 

After

  • LocaleContextHolder는 ThreadLocal한 특성이 있어 사용 시 주의해야 하지만, 해당 프로젝트에는 동기식으로 동작하여 하나의 요청이 하나의 Thread로 동작하기 때문에 이를 사용해도 될 것으로 판단!

 

xxxController

public MyOutput getMyList(User user) {
	...
	
	return myService.getMyList(user);
}

 

xxxService

public MyOutput getMyList(User user) {
	MyOutput output = getMyList(user);
	output.setInfo(getMyInfo(locale);
	
	return output;
}

private MyInfo getMyInfo() {
	Locale locale = LocaleContextHolder.getLocale();
	if (!locale.equals(locale) {
		...
	}
	
	return myInfo;
}

짜잔!

여기저기 물고 다니던 Locale 파라미터 없이 LocaleContextHolder에 저장된 값을 필요한 곳에서만 꺼내 씀으로써 더욱 깔끔한 코드 작성이 가능해졌다!

 

😎 마무리

사실 별거는 아니지만 그래도 이런게 존재한다는 걸 알게 되어 너무 기뻐서 정리해본다!

앞으로 다국어 적용 시 많이 쓸거 같으며, Spring의 다양한 기능에 대해 더욱 공부해봐야겠다는 의지가 생기는 코드리뷰였다!

이제 미뤄뒀던 스프링 공부를 어여 다시 해봐야겠다..! (우선 강의부터 조진다.)

728x90

'야미스터디 > Spring' 카테고리의 다른 글

[💚] Spring Cloud ☁️  (3) 2024.09.06
[Spring] IoC, DI, AOP 📌 - 작성중  (0) 2022.11.29
[Spring] Swagger 📌  (0) 2022.10.06
[Spring] Dispatcher Servlet 📌  (0) 2022.09.20
[Spring] Spring vs EJB 📌  (1) 2022.09.20

댓글