[스프링 MVC 2편 - 백엔드 웹 개발 활용 기술] 03. 메시지, 국제화

Turtle·2024년 7월 15일
0
post-thumbnail

🙄메시지

  • 하드코딩❌
  • 메시지 관리용 파일 도입⭕

✔️messages.properties

item=상품
item.id=상품ID
item.itemName=상품명
item.price=가격
item.quantity=수량

✔️적용

<label for="itemName" th:text="#{item.itemName}"></label>

🙄국제화

메시지에서 설정한 메시지 파일을 각 나라별로 별도 관리하면 서비스를 국제화 할 수 있다.
예를 들어 다음과 같이 한국어, 영어 버전 메시지 파일로 별도 관리할 수 있다.

// messages_ko.properties

item=상품
item.id=상품ID
item.itemName=상품명
item.price=가격
item.quantity=수량
// messages_en.properties

item=Item
item.id=Item ID
item.itemName=Item Name
item.price=price
item.quantity=quantity

한국어로 접근한 것인지 영어로 접근한 것인지를 인식하려면 HTTP 헤더에 있는 Accept-Language 헤더 값을 사용하거나 사용자가 직접 언어를 선택하도록 하고 쿠키 등을 사용해서 처리하면 된다.

HTTP Header - Accept-Language

🙄스프링 메시지 소스 설정

직접 등록하는 방법도 있으나 스프링 부트를 사용하면 스프링 부트가 MessageSource를 자동으로 스프링 빈으로 등록한다.

spring.messages.basename=messages,config.i18n.messages

✔️테스트 코드를 통해서 확인

@SpringBootTest
public class MessageSourceTest {

	private final MessageSource messageSource;

	@Autowired
	public MessageSourceTest(MessageSource messageSource) {
		this.messageSource = messageSource;
	}

	@Test
	@DisplayName("한국어")
	void helloMessage() {
		String hello = messageSource.getMessage("hello", null, Locale.KOREA);
		Assertions.assertThat(hello).isEqualTo("안녕");
	}

	@Test
	@DisplayName("영어")
	void helloMessage2() {
		String hello = messageSource.getMessage("hello", null, Locale.ENGLISH);
		Assertions.assertThat(hello).isEqualTo("hello");
	}

	@Test
	@DisplayName("못 찾는 경우")
	void notFoundMessageCode() {
		Assertions.assertThatThrownBy(() -> messageSource.getMessage("no_code", null, null))
				.isInstanceOf(NoSuchMessageException.class);
	}

	@Test
	@DisplayName("인자 추가")
	void argumentMessage() {
		String message = messageSource.getMessage("hello.name", new Object[]{"스프링"}, Locale.KOREA);
		Assertions.assertThat(message).isEqualTo("안녕 스프링");
	}
}

🙄웹 애플리케이션에서 메시지 적용하기

<div class="py-5 text-center">
	<h2 th:text="#{page.items}"></h2>
</div>

🙄웹 애플리케이션에 국제화 적용하기

크롬의 언어 설정을 변경해서 테스트 할 수 있다.

🙄스프링의 국제화 메시지 선택

메시지 기능은 Locale 정보를 알아야 언어를 선택할 수 있다.
스프링은 언어 선택 시 기본으로 HTTP의 Accept-Language 헤더의 값을 사용한다.

✔️LocaleResolver
스프링은 Locale 선택 방식을 변경할 수 있도록 LocaleResolver라는 인터페이스를 지원하는데 스프링 부트는 기본적으로 Accept-Language를 활용하는 AcceptHeaderLocaleResolver를 사용한다.

public class AcceptHeaderLocaleResolver implements LocaleResolver {

	private final List<Locale> supportedLocales = new ArrayList<>(4);

	@Nullable
	private Locale defaultLocale;


	/**
	 * Configure supported locales to check against the requested locales
	 * determined via {@link HttpServletRequest#getLocales()}. If this is not
	 * configured then {@link HttpServletRequest#getLocale()} is used instead.
	 * @param locales the supported locales
	 * @since 4.3
	 */
	public void setSupportedLocales(List<Locale> locales) {
		this.supportedLocales.clear();
		this.supportedLocales.addAll(locales);
	}

	/**
	 * Return the configured list of supported locales.
	 * @since 4.3
	 */
	public List<Locale> getSupportedLocales() {
		return this.supportedLocales;
	}

	/**
	 * Configure a fixed default locale to fall back on if the request does not
	 * have an "Accept-Language" header.
	 * <p>By default this is not set in which case when there is "Accept-Language"
	 * header, the default locale for the server is used as defined in
	 * {@link HttpServletRequest#getLocale()}.
	 * @param defaultLocale the default locale to use
	 * @since 4.3
	 */
	public void setDefaultLocale(@Nullable Locale defaultLocale) {
		this.defaultLocale = defaultLocale;
	}

	/**
	 * The configured default locale, if any.
	 * <p>This method may be overridden in subclasses.
	 * @since 4.3
	 */
	@Nullable
	public Locale getDefaultLocale() {
		return this.defaultLocale;
	}


	@Override
	public Locale resolveLocale(HttpServletRequest request) {
		Locale defaultLocale = getDefaultLocale();
		if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
			return defaultLocale;
		}
		Locale requestLocale = request.getLocale();
		List<Locale> supportedLocales = getSupportedLocales();
		if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
			return requestLocale;
		}
		Locale supportedLocale = findSupportedLocale(request, supportedLocales);
		if (supportedLocale != null) {
			return supportedLocale;
		}
		return (defaultLocale != null ? defaultLocale : requestLocale);
	}

	@Nullable
	private Locale findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales) {
		Enumeration<Locale> requestLocales = request.getLocales();
		Locale languageMatch = null;
		while (requestLocales.hasMoreElements()) {
			Locale locale = requestLocales.nextElement();
			if (supportedLocales.contains(locale)) {
				if (languageMatch == null || languageMatch.getLanguage().equals(locale.getLanguage())) {
					// Full match: language + country, possibly narrowed from earlier language-only match
					return locale;
				}
			}
			else if (languageMatch == null) {
				// Let's try to find a language-only match as a fallback
				for (Locale candidate : supportedLocales) {
					if (!StringUtils.hasLength(candidate.getCountry()) &&
							candidate.getLanguage().equals(locale.getLanguage())) {
						languageMatch = candidate;
						break;
					}
				}
			}
		}
		return languageMatch;
	}

	@Override
	public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
		throw new UnsupportedOperationException(
				"Cannot change HTTP accept header - use a different locale resolution strategy");
	}

}

0개의 댓글