diff --git a/src/main/java/com/backend/elearning/configuration/AppLocaleResolver.java b/src/main/java/com/backend/elearning/configuration/AppLocaleResolver.java new file mode 100644 index 00000000..89aa4523 --- /dev/null +++ b/src/main/java/com/backend/elearning/configuration/AppLocaleResolver.java @@ -0,0 +1,37 @@ +package com.backend.elearning.configuration; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; + +import java.util.List; +import java.util.Locale; + +@Configuration +public class AppLocaleResolver extends AcceptHeaderLocaleResolver implements WebMvcConfigurer { + + private static final List SUPPORTED = List.of(Locale.ENGLISH, new Locale("vi")); + + @Override + public Locale resolveLocale(HttpServletRequest request) { + String header = request.getHeader("Accept-Language"); + if (header == null || header.isBlank()) return Locale.ENGLISH; + Locale matched = Locale.lookup(Locale.LanguageRange.parse(header), SUPPORTED); + return matched != null ? matched : Locale.ENGLISH; + } + + @Bean + public MessageSource messageSource() { + var rs = new ResourceBundleMessageSource(); + rs.setBasenames("messages/messages"); // -> resources/messages/messages*.properties + rs.setDefaultEncoding("UTF-8"); + rs.setUseCodeAsDefaultMessage(true); + rs.setFallbackToSystemLocale(false); + return rs; + } +} \ No newline at end of file diff --git a/src/main/java/com/backend/elearning/domain/auth/AuthenticationService.java b/src/main/java/com/backend/elearning/domain/auth/AuthenticationService.java index 0f2516dd..f0e9ada5 100644 --- a/src/main/java/com/backend/elearning/domain/auth/AuthenticationService.java +++ b/src/main/java/com/backend/elearning/domain/auth/AuthenticationService.java @@ -14,7 +14,6 @@ import com.backend.elearning.security.JWTUtil; import com.backend.elearning.utils.Constants; import com.backend.elearning.utils.EmailEncryptionUtil; -import com.backend.elearning.utils.RandomString; import jakarta.mail.MessagingException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -26,7 +25,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; @@ -80,7 +78,7 @@ public AuthenticationVm login(AuthenticationPostVm request) { Optional user = userRepository.findByEmail(request.email()); Optional student = studentRepository.findByEmail(request.email()); if (!user.isPresent() && !student.isPresent()) { - throw new BadRequestException("Email or password is not corrected"); + throw new BadRequestException(Constants.ERROR_CODE.AUTH_ERROR); } authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( diff --git a/src/main/java/com/backend/elearning/domain/course/CourseController.java b/src/main/java/com/backend/elearning/domain/course/CourseController.java index 39ffcbb9..ade801d0 100644 --- a/src/main/java/com/backend/elearning/domain/course/CourseController.java +++ b/src/main/java/com/backend/elearning/domain/course/CourseController.java @@ -38,11 +38,13 @@ public ResponseEntity> getPageableCourse ( return ResponseEntity.ok().body(pageableCourses); } - @GetMapping("/courses/category/{categoryId}") + @GetMapping("/courses/category/{categoryName}") public ResponseEntity> getPageableCourse ( - @PathVariable("categoryId") Integer categoryId + @PathVariable("categoryName") String categoryName, + @RequestParam(value = "pageNum", defaultValue = Constants.PageableConstant.DEFAULT_PAGE_NUMBER) int pageNum, + @RequestParam(value = "pageSize", defaultValue = Constants.PageableConstant.DEFAULT_PAGE_SIZE) int pageSize ) { - List courseListGetVMS = courseService.getCoursesByCategoryId(categoryId); + List courseListGetVMS = courseService.getCoursesByCategory(categoryName, pageNum, pageSize); return ResponseEntity.ok().body(courseListGetVMS); } @@ -62,21 +64,6 @@ public ResponseEntity> getCoursesByMultiQueryWithP return ResponseEntity.ok().body(pageableCourses); } -// @GetMapping("/courses/search") -// public ResponseEntity> getCoursesByMultiQuery ( -// @RequestParam(value = "pageNum", defaultValue = Constants.PageableConstant.DEFAULT_PAGE_NUMBER, required = false) int pageNum, -// @RequestParam(value = "pageSize", defaultValue = Constants.PageableConstant.DEFAULT_PAGE_SIZE, required = false) int pageSize, -// @RequestParam(value = "keyword", required = false) String keyword, -// @RequestParam(value = "ratingStar", required = false) Float rating, -// @RequestParam(value = "level", required = false) String[] level, -// @RequestParam(value = "free", required = false) Boolean[] free, -// @RequestParam(value = "categoryName", required = false) String categoryName, -// @RequestParam(value = "topicId", required = false) Integer topicId -// ) { -// List coursesByMultiQueryReturnList = courseService.getCoursesByMultiQueryReturnList(pageNum, pageSize, keyword, rating, level, free, categoryName, topicId); -// return ResponseEntity.ok().body(coursesByMultiQueryReturnList); -// } - @PostMapping("/admin/courses") @ApiResponses(value = { @ApiResponse(responseCode = "201", description = "Created", content = diff --git a/src/main/java/com/backend/elearning/domain/course/CourseRepository.java b/src/main/java/com/backend/elearning/domain/course/CourseRepository.java index 948677bf..b49385f5 100644 --- a/src/main/java/com/backend/elearning/domain/course/CourseRepository.java +++ b/src/main/java/com/backend/elearning/domain/course/CourseRepository.java @@ -271,7 +271,6 @@ List findByMultiQuery( AND (:freeFlags IS NULL OR c.free IN :freeFlags) AND (:categoryName IS NULL OR cat.name = :categoryName OR p.name = :categoryName) AND (:topicId IS NULL OR t.id = :topicId) - AND c.status = 'PUBLISHED' AND ( :ratingStar IS NULL OR (SELECT COALESCE(AVG(r.ratingStar), 0) @@ -292,7 +291,6 @@ SELECT COUNT(c) AND (:freeFlags IS NULL OR c.free IN :freeFlags) AND (:categoryName IS NULL OR cat.name = :categoryName OR p.name = :categoryName) AND (:topicId IS NULL OR t.id = :topicId) - AND c.status = 'PUBLISHED' AND ( :ratingStar IS NULL OR (SELECT COALESCE(AVG(r.ratingStar), 0) diff --git a/src/main/java/com/backend/elearning/domain/course/CourseService.java b/src/main/java/com/backend/elearning/domain/course/CourseService.java index ac4302ae..a5f549bc 100644 --- a/src/main/java/com/backend/elearning/domain/course/CourseService.java +++ b/src/main/java/com/backend/elearning/domain/course/CourseService.java @@ -28,22 +28,11 @@ PageableData getCoursesByMultiQuery(int pageNum, Integer topicId ); - - List getCoursesByMultiQueryReturnList(int pageNum, - int pageSize, - String title, - Float rating, - String[] level, - Boolean[] free, - String categoryName, - Integer topicId - ); - - List getCoursesByCategoryId(Integer categoryId); - void delete(Long id); void updateStatusCourse(CourseStatusPostVM courseStatusPostVM, Long courseId); List getByPromotionId(Long promotionId); + + List getCoursesByCategory(String categoryName, int pageNum, int pageSize); } diff --git a/src/main/java/com/backend/elearning/domain/course/CourseServiceImpl.java b/src/main/java/com/backend/elearning/domain/course/CourseServiceImpl.java index bd2aaf32..3a1ff53a 100644 --- a/src/main/java/com/backend/elearning/domain/course/CourseServiceImpl.java +++ b/src/main/java/com/backend/elearning/domain/course/CourseServiceImpl.java @@ -356,83 +356,15 @@ public PageableData getCoursesByMultiQuery(int pageNum, Pageable pageable = PageRequest.of(pageNum, pageSize); Page coursePage = courseRepository.findByMultiQueryWithKeyword(pageable, title, rating, level, free, categoryName, topicId) ; List courses = coursePage.getContent(); - List courseListGetVMS = courses.stream().map(course -> { - course = courseRepository.findByIdWithPromotions(course).orElseThrow(() -> new NotFoundException(Constants.ERROR_CODE.COURSE_NOT_FOUND)); - List reviews = course.getReviews(); - int ratingCount = reviews.size(); - Double averageRating = reviews.stream().map(review -> review.getRatingStar()).mapToDouble(Integer::doubleValue).average().orElse(0.0); - double roundedAverageRating = Math.round(averageRating * 10) / 10.0; - AtomicInteger totalCurriculumCourse = new AtomicInteger(); - AtomicInteger totalDurationCourse = new AtomicInteger(); - List
sections = sectionService.findByCourseId(course.getId()); - sections.forEach(section -> { - List lectures = section.getLectures(); - List quizzes = section.getQuizzes(); - totalCurriculumCourse.addAndGet(lectures.size()); - totalCurriculumCourse.addAndGet(quizzes.size()); - int totalSeconds = lectures.stream() - .mapToInt(lecture -> lecture.getDuration()) - .sum(); - totalDurationCourse.addAndGet(totalSeconds); - }); - String formattedHours = convertSeconds(totalDurationCourse.get()); - - - Set promotions = course.getPromotions(); - Long discountedPrice = getDiscountedPriceByCourse(promotions, course); - return CourseListGetVM.fromModel(course, formattedHours, totalCurriculumCourse.get(), roundedAverageRating, ratingCount, discountedPrice); - }).toList(); + List courseListGetVMS = courses.stream().map(course -> getCourseListGetVMById(course.getId())).toList(); return new PageableData( pageNum, pageSize, - coursePage.getTotalElements(),coursePage.getTotalPages() - , + coursePage.getTotalElements(),coursePage.getTotalPages(), courseListGetVMS ); } - @Override - public List getCoursesByMultiQueryReturnList(int pageNum, int pageSize, String title, Float rating, String[] level, Boolean[] free, String categoryName, Integer topicId) { - log.info("received pageNum: {}, pageSize: {}, title: {}, rating: {}, level: {}, free: {}, categoryName: {}, " + - "topicId: {}", pageNum, pageSize, title, rating, level, free, categoryName, topicId); - List courses = title != null ? courseRepository.findByMultiQueryWithKeyword(title, rating, level, free, categoryName, topicId) : - courseRepository.findByMultiQuery(rating, level, free, categoryName, topicId); - List courseListGetVMS = courses.stream().map(course -> { - course = courseRepository.findByIdWithPromotions(course).orElseThrow(() -> new NotFoundException(Constants.ERROR_CODE.COURSE_NOT_FOUND)); - List reviews = course.getReviews(); - int ratingCount = reviews.size(); - Double averageRating = reviews.stream().map(review -> review.getRatingStar()).mapToDouble(Integer::doubleValue).average().orElse(0.0); - double roundedAverageRating = Math.round(averageRating * 10) / 10.0; - AtomicInteger totalCurriculumCourse = new AtomicInteger(); - AtomicInteger totalDurationCourse = new AtomicInteger(); - List
sections = sectionService.findByCourseId(course.getId()); - sections.forEach(section -> { - List lectures = section.getLectures(); - List quizzes = section.getQuizzes(); - totalCurriculumCourse.addAndGet(lectures.size()); - totalCurriculumCourse.addAndGet(quizzes.size()); - int totalSeconds = lectures.stream() - .mapToInt(lecture -> lecture.getDuration()) - .sum(); - totalDurationCourse.addAndGet(totalSeconds); - - }); - Set promotions = course.getPromotions(); - Long discountedPrice = getDiscountedPriceByCourse(promotions, course); - String formattedHours = convertSeconds(totalDurationCourse.get()); - return CourseListGetVM.fromModel(course, formattedHours, totalCurriculumCourse.get(), roundedAverageRating, ratingCount, discountedPrice); - }).toList(); - return courseListGetVMS; - } - - @Override - public List getCoursesByCategoryId(Integer categoryId) { - log.info("received categoryId: {}", categoryId); - List courses = courseRepository.findByCategoryIdWithStatus(categoryId); - List courseListGetVMS = courses.stream().map(course -> getCourseListGetVMById(course.getId())).toList(); - return courseListGetVMS; - } - @Override public void delete(Long id) { log.info("received courseId: {}", id); @@ -485,6 +417,15 @@ public List getByPromotionId(Long promotionId) { return courseAssignPromotions; } + @Override + public List getCoursesByCategory(String categoryName, int pageNum, int pageSize) { + Pageable pageable = PageRequest.of(pageNum, pageSize); + Page coursePage = courseRepository.findByMultiQueryWithKeyword(pageable, null, null, null, null, categoryName, null) ; + List courses = coursePage.getContent(); + List courseListGetVMS = courses.stream().map(course -> getCourseListGetVMById(course.getId())).toList(); + return courseListGetVMS; + } + private String convertSeconds(int seconds) { if (seconds < 3600) { int minutes = seconds / 60; diff --git a/src/main/java/com/backend/elearning/exception/ApiExceptionHandler.java b/src/main/java/com/backend/elearning/exception/ApiExceptionHandler.java index bf67d6c2..63ec3e82 100644 --- a/src/main/java/com/backend/elearning/exception/ApiExceptionHandler.java +++ b/src/main/java/com/backend/elearning/exception/ApiExceptionHandler.java @@ -1,5 +1,7 @@ package com.backend.elearning.exception; +import com.backend.elearning.utils.Constants; +import com.backend.elearning.utils.MessageUtil; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; @@ -40,7 +42,7 @@ public ResponseEntity handleBadRequestException(BadRequestException ex, @ExceptionHandler(BadCredentialsException.class) public ResponseEntity handleBadRequestException(BadCredentialsException ex, WebRequest request) { - String message = "Email hoặc mật khẩu không chính xác"; + String message = Constants.ERROR_CODE.AUTH_ERROR; ErrorVm errorVm = new ErrorVm(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase(), message); return ResponseEntity.badRequest().body(errorVm); } @@ -67,8 +69,8 @@ protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotV .stream() .map(error -> error.getField() + " " + error.getDefaultMessage()) .toList(); - - ErrorVm errorVm = new ErrorVm(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase(), "Request information is not valid", errors); + String detail = Constants.ERROR_CODE.REQUEST_NOT_VALID; + ErrorVm errorVm = new ErrorVm(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase(), detail, errors); return ResponseEntity.badRequest().body(errorVm); } @@ -79,7 +81,8 @@ public ResponseEntity handleConstraintViolation(ConstraintViolationExcep errors.add(violation.getRootBeanClass().getName() + " " + violation.getPropertyPath() + ": " + violation.getMessage()); } - ErrorVm errorVm = new ErrorVm(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase(), "Request information is not valid", errors); + String detail = Constants.ERROR_CODE.REQUEST_NOT_VALID; + ErrorVm errorVm = new ErrorVm(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase(), detail, errors); return ResponseEntity.badRequest().body(errorVm); } @@ -93,7 +96,6 @@ private String getServletPath(WebRequest webRequest) { public ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex) { Throwable cause = ex.getCause(); - // Handle Enum Deserialization specifically if (cause instanceof InvalidFormatException ife && ife.getTargetType().isEnum()) { String field = ife.getPath().isEmpty() ? "unknown" : ife.getPath().get(0).getFieldName(); String rejectedValue = String.valueOf(ife.getValue()); @@ -101,17 +103,16 @@ public ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadab Object[] allowed = ife.getTargetType().getEnumConstants(); List fieldErrors = new ArrayList<>(); - fieldErrors.add( - String.format("Field '%s': invalid value '%s'. Expected one of: %s", - field, - rejectedValue, - String.join(", ", java.util.Arrays.stream(allowed).map(Object::toString).toList())) - ); + String message = MessageUtil.getMessage(Constants.ERROR_CODE.FIELD_INVALID_VALUE,field, + rejectedValue, + String.join(", ", java.util.Arrays.stream(allowed).map(Object::toString).toList())); + fieldErrors.add(message); + String detail = MessageUtil.getMessage(Constants.ERROR_CODE.ENUM_VALUE_NOT_VALID,field); ErrorVm error = new ErrorVm( String.valueOf(HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST.getReasonPhrase(), - "Invalid enum value for field '" + field + "'", + detail, fieldErrors ); diff --git a/src/main/java/com/backend/elearning/exception/BadRequestException.java b/src/main/java/com/backend/elearning/exception/BadRequestException.java index 6bc86219..3fa2aca5 100644 --- a/src/main/java/com/backend/elearning/exception/BadRequestException.java +++ b/src/main/java/com/backend/elearning/exception/BadRequestException.java @@ -4,6 +4,8 @@ public class BadRequestException extends RuntimeException{ private String message; + + public BadRequestException(String errorCode, Object... var2) { this.message = MessageUtil.getMessage(errorCode, var2); } diff --git a/src/main/java/com/backend/elearning/utils/Constants.java b/src/main/java/com/backend/elearning/utils/Constants.java index cf757a5c..419f0bb5 100644 --- a/src/main/java/com/backend/elearning/utils/Constants.java +++ b/src/main/java/com/backend/elearning/utils/Constants.java @@ -10,10 +10,9 @@ public final class AUTH { } public final class ERROR_CODE { - + public static final String REQUEST_NOT_VALID = "REQUEST_NOT_VALID"; public static final String USER_NOT_FOUND = "USER_NOT_FOUND"; public static final String CART_NOT_FOUND = "CART_NOT_FOUND"; - public static final String ORDER_NOT_FOUND = "ORDER_NOT_FOUND"; public static final String CATEGORY_NOT_FOUND = "CATEGORY_NOT_FOUND"; public static final String QUESTION_NOT_FOUND = "QUESTION_NOT_FOUND"; @@ -23,7 +22,6 @@ public final class ERROR_CODE { public static final String COURSE_HAD_CART = "COURSE_HAD_CART"; public static final String COURSE_HAD_BEEN_BOUGHT = "COURSE_HAD_BEEN_BOUGHT"; public static final String NOTE_NOT_FOUND = "NOTE_NOT_FOUND"; - public static final String LECTURE_NOT_FOUND = "LECTURE_NOT_FOUND"; public static final String SECTION_NOT_FOUND = "SECTION_NOT_FOUND"; public static final String QUIZ_NOT_FOUND = "QUIZ_NOT_FOUND"; @@ -35,22 +33,20 @@ public final class ERROR_CODE { public static final String TOPIC_NAME_DUPLICATED = "TOPIC_NAME_DUPLICATED"; public static final String COURSE_TITLE_DUPLICATED = "COURSE_TITLE_DUPLICATED"; public static final String USER_EMAIL_DUPLICATED = "USER_EMAIL_DUPLICATED"; - public static final String STUDENT_EMAIL_DUPLICATED = "STUDENT_EMAIL_DUPLICATED"; - public static final String COUPON_CODE_DUPLICATED = "COUPON_CODE_DUPLICATED"; - public static final String LECTURE_SECTION_NOT_SAME = "LECTURE_SECTION_NOT_SAME"; - public static final String COUPON_NOT_FOUND = "COUPON_NOT_FOUND"; public static final String COUPON_IS_EXPIRED = "COUPON_IS_EXPIRED"; - + public static final String AUTH_ERROR = "AUTH_ERROR"; + public static final String FIELD_INVALID_VALUE = "FIELD_INVALID_VALUE"; + public static final String ENUM_VALUE_NOT_VALID = "ENUM_VALUE_NOT_VALID"; } public final class PageableConstant { - public static final String DEFAULT_PAGE_SIZE = "5"; + public static final String DEFAULT_PAGE_SIZE = "10"; public static final String DEFAULT_COURSE_PAGE_SIZE = "5"; public static final String DEFAULT_PAGE_SIZE_REVIEW = "4"; diff --git a/src/main/java/com/backend/elearning/utils/MessageUtil.java b/src/main/java/com/backend/elearning/utils/MessageUtil.java index f99ef9be..d53b20bc 100644 --- a/src/main/java/com/backend/elearning/utils/MessageUtil.java +++ b/src/main/java/com/backend/elearning/utils/MessageUtil.java @@ -1,24 +1,18 @@ package com.backend.elearning.utils; -import org.slf4j.helpers.FormattingTuple; -import org.slf4j.helpers.MessageFormatter; - -import java.util.Locale; -import java.util.MissingResourceException; -import java.util.ResourceBundle; +import org.springframework.context.MessageSource; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.stereotype.Component; +@Component public class MessageUtil { - static ResourceBundle messageBundle = ResourceBundle.getBundle("messages.messages", Locale.getDefault()); + private static MessageSourceAccessor accessor; + + public MessageUtil(MessageSource messageSource) { + MessageUtil.accessor = new MessageSourceAccessor(messageSource); + } - public static String getMessage(String errorCode, Object... var2) { - String message; - try { - message = messageBundle.getString(errorCode); - } catch (MissingResourceException ex) { - // case message_code is not defined. - message = errorCode; - } - FormattingTuple formattingTuple = MessageFormatter.arrayFormat(message, var2); - return formattingTuple.getMessage(); + public static String getMessage(String code, Object... args) { + return accessor.getMessage(code, args, code); } -} +} \ No newline at end of file diff --git a/src/main/resources/messages/messages.properties b/src/main/resources/messages/messages.properties index dbd7e719..fb7c19bf 100644 --- a/src/main/resources/messages/messages.properties +++ b/src/main/resources/messages/messages.properties @@ -13,12 +13,16 @@ CATEGORY_NAME_DUPLICATED= Category with name {} is duplicated TOPIC_NAME_DUPLICATED= Topic with name {} is duplicated PARENT_CATEGORY_NOT_FOUND=Parent category {} is not found COURSE_TITLE_DUPLICATED=Course {} is duplicated -LECTURE_SECTION_NOT_SAME=section of lecture {} is not same -COUPON_IS_EXPIRED = coupon with code {} is expired -COUPON_NOT_FOUND = coupon with code {} not found +LECTURE_SECTION_NOT_SAME=Section of lecture {} is not same +COUPON_IS_EXPIRED = Coupon with code {} is expired +COUPON_NOT_FOUND = Coupon with code {} not found COUPON_CODE_DUPLICATED = Coupon with code {} is duplicated COURSE_HAD_SECTION= Course {} had section already COURSE_HAD_STUDENT= Course {} had section already COURSE_HAD_CART= Course {} had cart COURSE_HAD_BEEN_BOUGHT= Course {} had been bought -NOTE_NOT_FOUND= Note {} is not found \ No newline at end of file +NOTE_NOT_FOUND= Note {} is not found +AUTH_ERROR= Email or password is not correct +REQUEST_NOT_VALID=Request information is not valid +FIELD_INVALID_VALUE= Field '%s': invalid value '%s'. Expected one of: %s +ENUM_VALUE_NOT_VALID=Giá trị enum không hợp lệ cho trường '%s' \ No newline at end of file diff --git a/src/main/resources/messages/messages_vi.properties b/src/main/resources/messages/messages_vi.properties new file mode 100644 index 00000000..367524a2 --- /dev/null +++ b/src/main/resources/messages/messages_vi.properties @@ -0,0 +1,27 @@ +USER_EMAIL_DUPLICATED=Người dùng với email {} đã tồn tại +STUDENT_EMAIL_DUPLICATED=Học viên với email {} đã tồn tại +USER_NOT_FOUND=Không tìm thấy người dùng {} +CART_NOT_FOUND=Không tìm thấy giỏ hàng {} +ORDER_NOT_FOUND=Không tìm thấy đơn hàng {} +COURSE_NOT_FOUND=Không tìm thấy khóa học {} +CATEGORY_NOT_FOUND=Không tìm thấy danh mục {} +QUESTION_NOT_FOUND=Không tìm thấy câu hỏi {} +QUIZ_NOT_FOUND=Không tìm thấy bài kiểm tra {} +TOPIC_NOT_FOUND=Không tìm thấy chủ đề {} +PARENT_CATEGORY_CANNOT_BE_ITSELF=Danh mục cha không thể là chính nó hoặc con của nó +CATEGORY_NAME_DUPLICATED=Danh mục với tên {} đã tồn tại +TOPIC_NAME_DUPLICATED=Chủ đề với tên {} đã tồn tại +PARENT_CATEGORY_NOT_FOUND=Không tìm thấy danh mục cha {} +COURSE_TITLE_DUPLICATED=Khóa học {} đã tồn tại +LECTURE_SECTION_NOT_SAME=Phần của bài giảng {} không trùng khớp +COUPON_IS_EXPIRED=Mã giảm giá {} đã hết hạn +COUPON_NOT_FOUND=Không tìm thấy mã giảm giá {} +COUPON_CODE_DUPLICATED=Mã giảm giá {} đã tồn tại +COURSE_HAD_SECTION=Khóa học {} đã có phần học +COURSE_HAD_STUDENT=Khóa học {} đã có học viên +COURSE_HAD_CART=Khóa học {} đã có trong giỏ hàng +COURSE_HAD_BEEN_BOUGHT=Khóa học {} đã được mua +NOTE_NOT_FOUND=Không tìm thấy ghi chú {} +AUTH_ERROR= Email hoặc mật khẩu không chính xác +REQUEST_NOT_VALID=Yêu cầu không hợp lệ +FIELD_INVALID_VALUE='%s': giá trị không hợp lệ '%s'. Kỳ vọng một trong các giá trị: %s \ No newline at end of file