스프링과 타임리프에서는 messages를 편리하게 사용할 수 있도록 여러기능을 제공합니다.
2021.08.20 - [Web/MVC2] - EP2. 메세지와 국제화
EP2. 메세지와 국제화
메시지 현재 HTML의 상품명, 가격, 수량 등 'label'에 있는 단어들을 하드코딩 되어있습니다. 만약에 '상품명'이라는 단어를 '상품이름'으로 바꾸려면 모든 HTML상의 단어들을 바꾸어주어야합니다.
ksabs.tistory.com
제 프로젝트의 HTML에서 하드코딩 되어있는 글자들을 messages 기능을 이용해 리팩토링했습니다.
다수의 label 또는 버튼이나 여러 text에 해당하는 부분을 th:text="#{}" 를 이용해 messages.properties에 미리 설정한 글자를 가져올 수 있도록 수정했습니다.
이로 인해 만약 "강의"가 들어가는 text들을 "수업"으로 바꾸어 주어야할 때
HTML 파일들을 다 찾아서 고치는 것이 아닌, messages.properties의 "강의"글자만 바꿔도 됩니다.
또한 나중에 국제화를 적용시키기에도 편리합니다.
messages.properties
label.search.siteName = 사이트이름
label.search.lectureName = 강의 이름
label.search.teacherName = 강의자 이름
label.search.starRating = 별 점
label.review.member = 회원선택
label.review.starRating = 별점
label.review.content = 후기:
label.member.name = 이름
label.member.age = 나이
label.member.gender = 성별
label.member.career = 직업
label.lecture.name = 강의제목
label.lecture.teacher = 강의자 이름
label.lecture.content = 강의 내용 소개
label.lecture.representPicture = 강의 대표사진
label.lecture.siteName = 강의 사이트 이름
label.lecture.siteUrl = 강의 사이트 주소
button.save = 등록
button.edit = 수정
button.delete = 삭제
page.lecture = 강의소개
page.review.member = 회원이름
page.review.starRating = 별점
page.review.content = 후기
page.addMember = 회원가입
page.addLecture = 강의등록
lecture.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<style>
.star_rating {font-size:0; letter-spacing:-4px;}
.star_rating a {
font-size:22px;
letter-spacing:0;
display:inline-block;
margin-left:5px;
color:#ccc;
text-decoration:none;
}
.star_rating a:first-child {margin-left:0;}
.star_rating a.on {color:#E1BC3F;}
</style>
<body>
<div th:replace="fragments/bodyHeader :: bodyHeader"/>
<div class="container">
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">
<div class="panel-title" th:text="#{page.lecture}">강의소개</div>
</div>
<div class="panel-body">
<div class="media">
<div class="col-md-6" >
<div class="media-left">
<a href="">
<img th:src="${lectureDto.getImagePath()}" src="/images/springInstroduction.png" class="img-responsive" alt="">
</a>
</div>
</div>
<div class="col-md-6">
<div class="media-body">
<h4 th:text="${lectureDto.getLectureName()}">강의이름</h4>
<h4 th:text="${lectureDto.getTeacherName()}">강의자</h4>
<p class="star_rating">
<a th:if="${lectureDto.getAverageRating() != 0}" class="on" th:each="num : ${#numbers.sequence(0, lectureDto.getAverageRating()-1)}">★</a>
<a th:unless="${lectureDto.getAverageRating() != 0}">★★★★★</a>
</p>
<h4 th:text="|${lectureDto.getReviewNum()}개의 수강평|">n개의 수강평</h4>
<a th:href="@{${lectureDto.getUri()}}" th:text="${lectureDto.getSiteName()}" target='_blank'>원래사이트</a>
</div>
</div>
</div>
</div>
<div>
<hr>
<div th:text="${lectureDto.getContent()}">content </div>
</div>
</div>
</div>
</div> <!-- /container -->
<!--리뷰조회-->
<div class="container">
<div>
<table class="table table-striped">
<thead>
<tr>
<th th:text="#{page.review.member}">회원이름</th>
<th th:text="#{page.review.starRating}">별점</th>
<th th:text="#{page.review.content}">후기</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr th:if="${not #strings.isEmpty(reviewDtos)}" th:each="reviewDto : ${reviewDtos}">
<td th:text="${reviewDto.getMemberName()}"></td>
<td>
<p class="star_rating">
<a th:if="${reviewDto.getRating() != 0}" class="on" th:each="num : ${#numbers.sequence(0, reviewDto.getRating()-1)}">★</a>
</p>
</td>
<td th:text="${reviewDto.getContent()}"></td>
<td>
<a class="btn btn-primary" role="button"
th:onclick="setReviewEdit([[${reviewDto.getReviewId()}]], [[${reviewDto.getContent()}]]);"
data-target="#modal" data-toggle="modal" th:text="#{button.edit}">수정</a>
</td>
<td>
<form id="reviewRemoveForm" th:action="@{/lectures/{lectureId}/removeReview/{reviewId}(lectureId=${lectureId}, reviewId=${reviewDto.getReviewId()})}" method="post">
<button type="button" onclick="removeCheck()" class="btn btn-danger" role="button" th:text="#{button.delete}">삭제</button>
</form>
</td>
</tr>
</table>
</div>
<!-- 리뷰등록-->
<hr>
<form role="form" method="post" th:object="${reviewForm}">
<div class="form-group col-md-3">
<label for="formMemberSelect" th:text="#{label.review.member}">회원선택</label>
<select id="formMemberSelect" th:field="*{memberName}" class="form-control">
<option value="" disabled>==회원선택==</option>
<option th:each="memberDto : ${memberDtos}" th:text="${memberDto.getUserName()}" th:value="${memberDto.getUserName()}">동호</option>
</select>
</div>
<div class="form-group col-md-4">
<label for="formStarRating" th:text="#{label.review.starRating}">별점</label>
<select id="formStarRating" th:field="*{rating}" class="form-control">
<option th:each="rating : ${ratings}" th:value="${rating.key}" th:text="${rating.value}"></option>
</select>
</div>
<div class="form-group col-md-12">
<label th:text="#{label.review.content}">후기: </label>
<textarea class="form-control" rows="5" id="commentContent" placeholder="후기를 입력해 주세요"
name="commentContent" maxlength="255" th:field="*{content}"></textarea>
</div>
<button type="submit" class="btn btn-primary pull-right" role="button" th:text="#{button.save}">등록</button>
</form>
</div>
<br>
<br>
<div th:replace="fragments/footer :: footer" />
<div class="row">
<div class="modal" id="modal" tabindex="-1">
<div class="modal-dialog">
<div class="model-content" style="background-color: white;">
<div class="modal-header">
리뷰 수정
</div>
<div class="modal-body">
<form name = "reviewEdit" th:object="${reviewForm}">
<label>별점</label>
<select th:field="*{rating}">
<option th:each="rating : ${ratings}" th:value="${rating.key}" th:text="${rating.value}"></option>
</select>
<label>후기: </label>
<textarea id="reviewEdit" class="form-control" rows="5" placeholder="최대 50자까지 입력 가능합니다."
name="commentContent" maxlength="255" th:field="*{content}"></textarea>
<button onclick="reviewEditForm()" type="submit" class="btn btn-primary" role="button">수정완료</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="/js/bootstrap.js"></script>
<script th:inline="javascript" language="JavaScript">
var updateReviewId;
var lectureId = [[${lectureId}]];
function setReviewEdit(id, content) {
updateReviewId = id;
document.getElementById('reviewEdit').value = content;
}
function reviewEditForm() {
const theForm = document.reviewEdit;
theForm.method = "post";
theForm.action = lectureId+"/updateReview/"+updateReviewId;
theForm.submit();
}
function removeCheck() {
if (confirm("정말 삭제하시겠습니까?") === true){ //확인
document.getElementById("reviewRemoveForm").submit();
}else{ //취소
return false;
}
}
</script>
</body>
</html>
lectureForm.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<style>
.fieldError {
border-color: #bd2130;
}
.container-center {
max-width: 560px;
}
</style>
<body>
<div th:replace="fragments/bodyHeader :: bodyHeader"/>
<div class="container container-center">
<div class="page-header">
<h1 class="text-center" th:text="#{page.addLecture}">강의등록</h1>
</div>
<form role="form" action="/lectures/new" th:object="${lectureForm}"
method="post", enctype="multipart/form-data">
<div class="form-group">
<label th:for="lectureName" th:text="#{label.lecture.name}">강의제목</label>
<input type="text" th:field="*{lectureName}" class="form-control" placeholder="강의제목을 입력하세요"
th:class="${#fields.hasErrors('lectureName')}? 'form-control fieldError' : 'form-control'">
<p th:if="${#fields.hasErrors('lectureName')}" th:errors="*{lectureName}">Incorrect date</p>
</div>
<div class="form-group">
<label th:for="teacherName" th:text="#{label.lecture.teacher}">강의자 이름</label>
<input type="text" th:field="*{teacherName}" class="form-control" placeholder="강의자를 입력하세요"
th:class="${#fields.hasErrors('teacherName')}? 'form-control fieldError' : 'form-control'">
<p th:if="${#fields.hasErrors('teacherName')}" th:errors="*{teacherName}">Incorrect date</p>
</div>
<div class="form-group">
<label th:for="content" th:text="#{label.lecture.content}">강의 내용 소개</label>
<textarea th:field="*{content}" class="form-control" placeholder="강의내용을 입력하세요"> </textarea>
</div>
<div class="form-group">
<label th:for="representImage" th:text="#{label.lecture.representPicture}">강의 대표사진 업로드</label>
<input type="file" multiple="true" th:field="*{image}" accept="image/*" class="form-control" placeholder="강의 대표사진을 업로드해주세요">
</div>
<div class="form-group">
<label th:for="siteName" th:text="#{label.lecture.siteName}">강의 사이트 이름</label>
<input type="text" th:field="*{siteName}" class="form-control" placeholder="강의 사이트의 이름을 입력해주세요">
</div>
<div class="form-group">
<label th:for="uri" th:text="#{label.lecture.siteUrl}">강의 사이트 주소</label>
<input type="url" th:field="*{uri}" class="form-control" placeholder="강의 사이트의 주소를 입력해주세요">
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary" th:text="#{button.save}">등록하기</button>
</div>
</form>
<br/>
</div> <!-- /container -->
<div th:replace="fragments/footer :: footer" />
</body>
</html>
memberForm.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<style>
.fieldError {
border-color: #bd2130;
}
.container-center {
max-width: 360px;
}
</style>
<body>
<div th:replace="fragments/bodyHeader :: bodyHeader"/>
<div class="container container-center">
<div class="page-header">
<h1 class="text-center" th:text="#{page.addMember}">회원가입</h1>
</div>
<form role="form" action="/members/new" th:object="${memberForm}" method="post">
<div class="form-group">
<label th:for="name" th:text="#{label.member.name}">이름</label>
<input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력하세요"
th:class="${#fields.hasErrors('name')}? 'form-control fieldError' : 'form-control'">
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Incorrect date</p>
</div>
<div class="form-group">
<label th:for="age" th:text="#{label.member.age}">나이</label>
<input type="text" th:field="*{age}" class="form-control" placeholder="나이를 입력하세요">
</div>
<div>
<div><b th:text="#{label.member.gender}">성별</b></div>
<div class="radio" th:each="gender : ${genderTypes}">
<input type="radio" th:field="*{gender}" th:value="${gender.name()}">
<label th:for="${#ids.prev('gender')}" th:text="${gender.description}"></label>
</div>
</div>
<div class="form-group">
<label th:for="career" th:text="#{label.member.career}">직업</label>
<input type="text" th:field="*{career}" class="form-control" placeholder="직업을 입력하세요">
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary" th:text="#{button.save}">등록하기</button>
</div>
</form>
<br/>
</div> <!-- /container -->
<div th:replace="fragments/footer :: footer" />
</body>
</html>
home.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header">
</head>
<body>
<style type="text/css">
.jumbotron {
text-shadow: black 0.2em 0.2em 0.2em;
color: red;
}
.lecture-images {
width: 128px;
height: 83px;
}
.panel-heading {
font-weight: bold;
background-color: white;
}
.lecture-title {
font-weight: bold;
}
.star_rating {font-size:0; letter-spacing:-4px;}
.star_rating a {
font-size:22px;
letter-spacing:0;
display:inline-block;
margin-left:5px;
color:#ccc;
text-decoration:none;
}
.star_rating a:first-child {margin-left:0;}
.star_rating a.on {color:#E1BC3F;}
</style>
<div th:replace="fragments/bodyHeader :: bodyHeader"></div>
<div class="container">
<div class="jumbotron well">
<h1 class="text-center">CLASS FLIX</h1>
<p class="text-center" style="text-shadow: none">인강 추천좀</p>
</div>
<div class="card card-7 text-center">
<div class="card-body">
<div class="input-group input--medium">
<label th:for="${condition.siteName}" class="label" th:text="#{label.search.siteName}">사이트 이름</label>
<input class="input--style-1" type="text" th:field="${condition.siteName}">
</div>
<div class="input-group input--large">
<label th:for="${condition.lectureName}" class="label" th:text="#{label.search.lectureName}">강의 이름</label>
<input class="input--style-1" type="text" th:field="${condition.lectureName}">
</div>
<div class="input-group input--medium">
<label th:for="${condition.teacherName}" class="label" th:text="#{label.search.teacherName}">강의자 이름</label>
<input class="input--style-1" type="text" th:field="${condition.teacherName}">
</div>
<div class="input-group input--medium">
<label th:for="${condition.ratingGoe}" class="label" th:text="#{label.search.starRating}">별 점</label>
<div class="input-group-icon js-number-input">
<div class="icon-con">
<span class="plus">+</span>
<span class="minus">-</span>
</div>
<input class="input--style-1 quantity" type="text" id="ratingGoe" th:value="|${condition.ratingGoe}점 이상만|" disabled>
</div>
</div>
<button th:onclick="goSearch([[${page.sortParam}]]);" class="btn-submit" type="submit">search</button>
</div>
</div>
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">IT 분야 인기 강의</div>
<div class="panel-body" th:unless="${#lists.size(lectures) != 0}">
검색결과가 없습니다.
</div>
<div class="panel-body" th:if="${#lists.size(lectures) != 0}">
<div class="text-right">
<th:block th:each="sortParam : ${sortParams}">
<a th:text="${sortParam.displayName}" th:unless="${#strings.equals(page.sortParam, sortParam.code)}"
th:href="@{/(page=${page.curPage},sort=${sortParam.code})}" class="btn btn-sm btn-default" role="button"></a>
<a th:text="${sortParam.displayName}" th:if="${#strings.equals(page.sortParam, sortParam.code)}"
class="btn btn-sm btn-default disabled" role="button"></a>
</th:block>
</div>
<div class="col-md-3" th:each="lecture : ${lectures}">
<a th:href="@{/lectures/{lectureId}(lectureId=${lecture.id})}">
<img class="lecture-images" th:src="${lecture.representImagePath}"
src="/images/springInstroduction.png" alt="사진없음">
<h5 th:text="${lecture.lectureName}" class="lecture-title">강의제목</h5>
<p class="star_rating">
<a th:if="${lecture.averageRating != 0}" class="on"
th:each="num : ${#numbers.sequence(0, lecture.averageRating-1)}">★</a>
<a th:unless="${lecture.averageRating != 0}">★★★★★</a>
</p>
</a>
</div>
</div>
<div class="text-center" th:if="${#lists.size(lectures) != 0}">
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm">
<li th:if="${page.isPrev()}" class="page-item"><a th:onclick="goPage([[${page.curPage-1}]],[[${page.sortParam}]]);" class="page-link">Prev</a></li>
<li th:unless="${page.isPrev()}" class="page-item disabled"><a class="page-link">Prev</a></li>
<li class="page-item" th:each="num, index : ${#numbers.sequence(page.startPage, page.endPage)}">
<a th:if="${#strings.equals(page.curPage+1, num)}" th:text="${num}" class="page-link" style="background-color: #eeeeee">1</a>
<a th:unless="${#strings.equals(page.curPage+1, num)}" th:onclick="goPage([[${index.current-1}]],[[${page.sortParam}]]);" th:text="${num}" class="page-link">1</a>
</li>
<li th:if="${page.isNext()}" class="page-item"><a th:onclick="goPage([[${page.curPage+1}]],[[${page.sortParam}]]);" class="page-link">Next</a></li>
<li th:unless="${page.isNext()}" class="page-item disabled"><a class="page-link">Next</a></li>
</ul>
</nav>
</div>
</div>
</div>
</div>
<div th:replace="fragments/footer :: footer" />
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<!-- Jquery JS-->
<script src="/vendor/jquery/jquery.min.js"></script>
<!-- Vendor JS-->
<script src="/vendor/select2/select2.min.js"></script>
<script src="/vendor/jquery-validate/jquery.validate.min.js"></script>
<script src="/vendor/bootstrap-wizard/bootstrap.min.js"></script>
<script src="/vendor/bootstrap-wizard/jquery.bootstrap.wizard.min.js"></script>
<script src="/vendor/datepicker/moment.min.js"></script>
<script src="/vendor/datepicker/daterangepicker.js"></script>
<!-- Main JS-->
<script src="/js/search.js"></script>
<script>
function getSearchParam(searchParam) {
var siteName = document.getElementById('siteName').value;
var lectureName = document.getElementById('lectureName').value;
var teacherName = document.getElementById('teacherName').value;
var ratingGoe = document.getElementById('ratingGoe').value.substring(0,1);
if (siteName !== "") {
searchParam += "&siteName=" + siteName;
}
if (lectureName !== "") {
searchParam += "&lectureName=" + lectureName;
}
if (teacherName !== "") {
searchParam += "&teacherName=" + teacherName;
}
if (ratingGoe !== "") {
searchParam += "&ratingGoe=" + ratingGoe;
}
return searchParam;
}
function goSearch(sortParam) {
var param = "/?";
param += "sort=" + sortParam;
param = getSearchParam(param);
location.replace(param);
// return searchParam;
}
function goPage(page, sortParam) {
var param = "/?";
param += "page=" + page;
param += "&sort=" + sortParam;
param = getSearchParam(param);
location.replace(param);
}
</script>
</body>
</html>
'Project > ClassFlix' 카테고리의 다른 글
[Class Flix] EP 21. Bean Validator 적용 (0) | 2021.10.22 |
---|---|
[Class Flix] EP 19. 타임리프 리팩토링 (0) | 2021.08.09 |
[ClassFlix] EP 18. QueryDSL 도입 (검색기능) (0) | 2021.07.23 |
[ClassFlix] EP 17. QueryDSL 도입 (페이징, 정렬) (0) | 2021.07.14 |
[ClassFlix] EP 16. 리팩토링 계획 (0) | 2021.06.17 |