티스토리 뷰
Spring Basic : Spring MVC Internationalization i18n 설정
Korean Eagle 2020. 8. 9. 22:240. 스프링 MVC 기본적으로 request 헤더에 설정되어 있는 accept-language라는 항목을 보고 어떤 언어인지를 판단한다.
0-1 ko-KR은 한국 en-US는 미국 같은 식이다. 이건 ISO 639에 언어, ISO 3166에 국가 코드가 설정되어 있다.
1. 지역 설정은 사용자의 시스템의 설정을 따르는 방식, 쿠키를 사용하는 방식, 사용자가 임의로 설정하는 방식이 있다.
1-1 사용자에게 설정가능하도록 하려면 Custome Parameter를 사용할 수 있다.
1-2 스프링 MVC는 LocaleChangeInterceptor를 제공하여 지역변경할 수 있도록 custom parameter를 설정할 수 있다.
2. 스프링에서 기본적으로 사용하는 지역 설정자는 AcceptHeaderLocaleResolver이다.
2-1 이것은 request 지정된 accept-language를 사용한다.
3. FixedLocaleResolver는 jvm에 설정된 지역설정을 따르는데 application.properties에서 활성화할 수 있다.
3-1 일반적으로 jvm 지역설정은 system parameter를 통하여 지정할 수 있다.
4. 아래는 자동설정을 지정하는 WebMvcAutoConfiguration.class 파일의 코드의 일부분이다.
4-1 LocaleResolver를 생성하는 부분인데 @ConditionalOnMissingBean은 Bean이 정의되어 있지 않는 경우 실행하고
4-2 application.properties 파일에 spring.mvc.local 설정이 없는 경우 AcceptHeaderLocaleResolver를 생성한다.
4-2-1 spring.mvc.locale-resolver=fixed 가 지정되면 FixedLocaleResolver를 생성한다.
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
5. CookieLocaleResolver, SessionLocaleResolver 방식도 지원하고 있다.
6. 이런 설정들은 Recource Bundle이라는 message.properties 파일을 통하여 지정할 수 있다.
6-1 한국의 경우는 messages_ko_KO.properties로 지정하면 스프링에서 인식하는 지역과 일치할 경우 사용한다.
6-2 지역과 일치하는 파일이 없는 경우 언어가 일치하는 지 확인한다. 영어는 message_en.properties를 검색하게 된다.
6-3 없으면 messages.propertes를 사용한다.
7. 간단한 예제로 아래와 같은 한글용 messages_ko_KO.properties를 만들었다.
7-1 이 파일을 resources 폴더 아래 생성한다.
recipe.description= 레시피 설명
recipe.categories=분류
recipe.prepTime=준비시간
recipe.cookTime=요리시간
recipe.difficulty=난이도
recipe.servings=몇인분
recipe.source=소스
recipe.url=웹링크
NotBlank.recipe.description=값이 있어야 한다
Size.recipe.description={0}은 {2} 보다 크고 {1} 작아야 한다
Max.recipe.cookTime={0}은 {1} 보다 작아야 한다
Min.recipe.prepTime={0}은 {1} 보다 커야 한다
URL.recipe.url=제대로 된 거 좀 넣어라
Max.recipe.servings={0}은 {1} 보다 작아야 해
7-1-1 validation 메시지 설정에 대한 부분은 아래 링크 참조
Spring Advanced : Web MVC + Form Validation - Custom message 설정 (spring boot 포함)
0. 이 포스트는 Spring : Web MVC + Form Validation - with InitBinder의 연속이다. Spring : Form Validation with @InitBinder -1. Form Validation은 시스템에서 원하는 값이 입력되도록 제한하고 검증하는 기..
kogle.tistory.com
7-2 이 Resource Bundle을 사용한 결과
7-3 messages_ko_KR.properties가 없는 경우 결과
7-3 template은 아래와 같다.
7-3-1 messages.properties의 값을 사용하려면 th:text="#{recipe.description}" 형식의 spEL을 사용하면 된다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"
th:href="@{/webjars/bootstrap/4.5.0/css/bootstrap.min.css}">
<title>Recipe Homer</title>
</head>
<body>
<!--/*@thymesVar id="recipe" type="guru.springframework.domain.Recipe"*/-->
<div class="container-fluid" style="margin-top: 20px">
<div class="row">
<div class="col-md-6 offset-md-3">
<form th:object="${recipe}" th:action="@{/recipe/}" method="post">
<div th:if="${#fields.hasErrors('*')}" class="alert alert-danger pb-0">
<p>Please Correct Errors Below</p>
</div>
<input type="hidden" th:field="*{id}" />
<div class="card text-white bg-primary">
<div class="card-body p-0">
<div class="card-title">
<p class="m-2 font-weight-bold">Edit Recipe Information</p>
</div>
<div class="card-text bg-white text-dark m-0 p-2">
<div class="row form-group"
th:class="${#fields.hasErrors('description')} ? 'row form-group text-danger' : 'row form-group'">
<label th:text="#{recipe.description}" class="col-lg-4 col-xl-3 mx-0 pr-0 col-form-label">Recipe Description:</label>
<div class="col-lg-8 col-xl-9">
<input type="text" class="form-control" th:field="*{description}" th:errorclass="is-invalid" />
<span class="small form-text text-muted invalid-feedback" th:if="${#fields.hasErrors('description')}">
<ul>
<li th:each="err : ${#fields.errors('description')}" th:text="${err}"></li>
</ul>
</span>
</div>
</div>
<div class="row form-group">
<div class="col-form-label col-md-3">
<label th:text="#{recipe.categories}">Categories:</label>
</div>
<div class="col-md-9">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" />
<label class="form-check-label">
Cat 1
</label>
</div>
<div class="form-check" th:remove="all">
<input class="form-check-input" type="checkbox" value="" />
<label class="form-check-label">
Cat 2
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4 form-group"
th:class="${#fields.hasErrors('prepTime')} ? 'col-md-4 form-group text-danger' : 'col-md-4 form-group'">
<label th:text="#{recipe.prepTime}" class="col-form-label">Prep Time:</label>
<input type="text" class="form-control" th:field="*{prepTime}" th:errorclass="is-invalid" />
<span class="small form-text text-muted invalid-feedback" th:if="${#fields.hasErrors('prepTime')}">
<ul>
<li th:each="err : ${#fields.errors('prepTime')}" th:text="${err}"></li>
</ul>
</span>
</div>
<div class="col-md-4 form-group"
th:class="${#fields.hasErrors('cookTime')} ? 'col-md-4 form-group text-danger' : 'col-md-4 form-group'">
<label th:text="#{recipe.cookTime}" class="col-form-label">Cooktime:</label>
<input type="text" class="form-control" th:field="*{cookTime}" th:errorclass="is-invalid" />
<span class="small form-text text-muted invalid-feedback" th:if="${#fields.hasErrors('cookTime')}">
<ul>
<li th:each="err : ${#fields.errors('cookTime')}" th:text="${err}"></li>
</ul>
</span>
</div>
<div class="col-md-4 form-group">
<label th:text="#{recipe.difficulty}" class="col-form-label">Difficulty:</label>
<select class="form-control" th:field="*{difficulty}">
<option th:each="difficultyValue : ${T(pe.pilseong.recipe.domain.Difficulty).values()}"
th:value="${difficultyValue.name()}"
th:text="${difficultyValue.name()}">
val
</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-4 form-group"
th:class="${#fields.hasErrors('servings')} ? 'col-md-4 form-group text-danger' : 'col-md-4 form-group'">
<label th:text="#{recipe.servings}" class="col-form-label">Servings:</label>
<input type="text" class="form-control" th:field="*{servings}" th:errorclass="is-invalid" />
<span class="small form-text text-muted invalid-feedback" th:if="${#fields.hasErrors('servings')}">
<ul>
<li th:each="err : ${#fields.errors('servings')}" th:text="${err}"></li>
</ul>
</span>
</div>
<div class="col-md-4 form-group">
<label th:text="#{recipe.source}" class="col-form-label">Source:</label>
<input type="text" class="form-control" th:field="*{source}" />
</div>
<div class="col-md-4 form-group"
th:class="${#fields.hasErrors('url')} ? 'col-md-4 form-group text-danger' : 'col-md-4 form-group'">
<label th:text="#{recipe.url}" class="col-form-label">URL:</label>
<input type="text" class="form-control" th:field="*{url}" th:errorclass="is-invalid" />
<span class="small form-text text-muted invalid-feedback" th:if="${#fields.hasErrors('url')}">
<ul>
<li th:each="err : ${#fields.errors('url')}" th:text="${err}"></li>
</ul>
</span>
</div>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="card-title mb-0">
<div class="row m-0">
<div class="col-md-10 p-0">
<p class="m-2 font-weight-bold">Ingredients</p>
</div>
<div class="col-md-2">
<a class="btn btn-success font-weight-bold" href="#" role="button">View</a>
</div>
</div>
</div>
<div class="card-text bg-white text-dark m-0 p-2">
<div class="row">
<div class="col-md-12">
<ul th:if="${not #lists.isEmpty(recipe.ingredients)}">
<li th:remove="all">1 Cup of milk</li>
<li th:remove="all">1 Teaspoon of chocolate</li>
<li th:remove="all">1 Doggy of SAXU</li>
<li th:each="ingredient : ${recipe.ingredients}" th:text="${(ingredient.getAmount() +
' ' + ingredient.uom.getDescription() +
' - ' + ingredient.getDescription())}">1 Teaspoon of Sugar
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="card text-white bg-primary">
<div class="card-body p-0">
<div class="card-title">
<p class="m-2 font-weight-bold">Directions</p>
</div>
<div class="card-text bg-white text-dark m-0 p-2">
<div class="row">
<div class="col-md-12 form-group">
<textarea class="form-control" rows="3" th:field="*{directions}"></textarea>
</div>
</div>
</div>
</div>
</div>
<div class="card text-white bg-primary">
<div class="card-body p-0">
<div class="card-title">
<p class="m-2 font-weight-bold">Notes</p>
</div>
<div class="card-text bg-white text-dark m-0 p-2">
<div class="row">
<div class="col-md-12 form-group">
<textarea class="form-control" rows="3" th:field="*{note.recipeNote}"></textarea>
</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"
th:src="@{/webjars/jquery/3.5.1/jquery.min.js}">
</script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"
th:src="@{/webjars/popper.js/1.16.0/popper.min.js}">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"
th:src="@{/webjars/bootstrap/4.5.0/js/bootstrap.min.js}">
</script>
</body>
</html>
'Spring > Spring Basic' 카테고리의 다른 글
설정 : Java + Spring + Console + JDBC + Gradle 테스트 용 설정 1 (0) | 2021.11.28 |
---|---|
Spring Basic : profile 사용하기 - 2 (0) | 2020.08.19 |
Spring Basic : Spring MVC Formatter 사용하기 (0) | 2020.08.07 |
Spring Basic : 외부 설정 파일에서 읽어오기 (정리) (0) | 2020.07.24 |
Spring Basic : Interface Segregation 원칙 (0) | 2020.07.21 |
- Total
- Today
- Yesterday
- 도커 개발환경 참고
- AWS ARN 구조
- Immuability에 관한 설명
- 자바스크립트 멀티 비동기 함수 호출 참고
- WSDL 참고
- SOAP 컨슈머 참고
- MySql dump 사용법
- AWS Lambda with Addon
- NFC 드라이버 linux 설치
- electron IPC
- mifare classic 강의
- go module 관련 상세한 정보
- C 메모리 찍어보기
- C++ Addon 마이그레이션
- JAX WS Header 관련 stackoverflow
- SOAP Custom Header 설정 참고
- SOAP Custom Header
- SOAP BindingProvider
- dispatcher 사용하여 설정
- vagrant kvm으로 사용하기
- git fork, pull request to the …
- vagrant libvirt bridge network
- python, js의 async, await의 차이
- go JSON struct 생성
- Netflix Kinesis 활용 분석
- docker credential problem
- private subnet에서 outbound IP 확…
- 안드로이드 coroutine
- kotlin with, apply, also 등
- 안드로이드 초기로딩이 안되는 경우
- navigation 데이터 보내기
- 레이스 컨디션 navController
- raylib
- mapping
- 외부파일
- Angular
- 자바
- Spring Security
- XML
- 상속
- spring boot
- WebMvc
- Security
- Spring
- form
- one-to-many
- 스프링
- Validation
- RestTemplate
- jsp
- 설정
- crud
- Many-To-Many
- 로그인
- login
- 설정하기
- 스프링부트
- hibernate
- one-to-one
- Rest
- MYSQL
- 하이버네이트
- 매핑