Project/ClassFlix

[Class Flix] EP 20. messages 적용

 

 

스프링과 타임리프에서는 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>