콘텐츠 메시지 다국어 처리

This commit is contained in:
2025-12-23 19:03:38 +09:00
parent 9d619450ef
commit e987a56544
10 changed files with 303 additions and 76 deletions

View File

@@ -36,7 +36,7 @@ class AudioContentController(private val service: AudioContentService) {
@RequestPart("request") requestString: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.createAudioContent(
@@ -57,7 +57,7 @@ class AudioContentController(private val service: AudioContentService) {
@RequestPart("request") requestString: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.modifyAudioContent(
@@ -74,7 +74,7 @@ class AudioContentController(private val service: AudioContentService) {
@RequestBody request: UploadCompleteRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.uploadComplete(
@@ -91,7 +91,7 @@ class AudioContentController(private val service: AudioContentService) {
@PathVariable id: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.deleteAudioContent(
@@ -111,7 +111,7 @@ class AudioContentController(private val service: AudioContentService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getAudioContentList(
@@ -134,7 +134,7 @@ class AudioContentController(private val service: AudioContentService) {
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getDetail(
@@ -151,7 +151,7 @@ class AudioContentController(private val service: AudioContentService) {
@PathVariable id: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.generateUrl(contentId = id, member = member))
}
@@ -160,7 +160,7 @@ class AudioContentController(private val service: AudioContentService) {
@RequestBody request: AddAllPlaybackTrackingRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.addAllPlaybackTracking(request, member))
}
@@ -170,7 +170,7 @@ class AudioContentController(private val service: AudioContentService) {
@RequestBody request: PutAudioContentLikeRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.audioContentLike(request, member))
}
@@ -179,7 +179,7 @@ class AudioContentController(private val service: AudioContentService) {
fun getAudioContentRankingSort(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getContentRankingSortTypeList())
}
@@ -221,7 +221,7 @@ class AudioContentController(private val service: AudioContentService) {
@PathVariable id: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.pinToTheTop(contentId = id, member = member))
}
@@ -232,7 +232,7 @@ class AudioContentController(private val service: AudioContentService) {
@PathVariable id: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.unpinAtTheTop(contentId = id, member = member))
}
@@ -248,7 +248,7 @@ class AudioContentController(private val service: AudioContentService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getLatestContentByTheme(

View File

@@ -31,6 +31,7 @@ import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
import kr.co.vividnext.sodalive.fcm.FcmEvent
import kr.co.vividnext.sodalive.fcm.FcmEventType
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationEvent
import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationTargetType
import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService
@@ -74,6 +75,7 @@ class AudioContentService(
private val audioContentCloudFront: AudioContentCloudFront,
private val applicationEventPublisher: ApplicationEventPublisher,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val contentThemeTranslationRepository: ContentThemeTranslationRepository,
@@ -117,7 +119,7 @@ class AudioContentService(
val request = objectMapper.readValue(requestString, ModifyAudioContentRequest::class.java)
val audioContent = repository.findByIdAndCreatorId(request.contentId, member.id!!)
?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
if (request.title != null) audioContent.title = request.title
if (request.detail != null) audioContent.detail = request.detail
@@ -189,7 +191,7 @@ class AudioContentService(
@Transactional
fun deleteAudioContent(audioContentId: Long, member: Member) {
val audioContent = repository.findByIdAndCreatorId(audioContentId, member.id!!)
?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
audioContent.isActive = false
audioContent.releaseDate = null
@@ -203,7 +205,7 @@ class AudioContentService(
member: Member
): CreateAudioContentResponse {
// coverImage 체크
if (coverImage == null) throw SodaException("커버이미지를 선택해 주세요.")
if (coverImage == null) throw SodaException(messageKey = "content.error.cover_image_required")
// request 내용 파싱
val request = objectMapper.readValue(requestString, CreateAudioContentRequest::class.java)
@@ -222,18 +224,18 @@ class AudioContentService(
// contentFile 체크
if (contentFile == null) {
throw SodaException("콘텐츠를 선택해 주세요.")
throw SodaException(messageKey = "content.error.content_required")
}
// 테마 체크
val theme = themeQueryRepository.findThemeByIdAndActive(id = request.themeId)
?: throw SodaException("잘못된 테마입니다. 다시 선택해 주세요.")
?: throw SodaException(messageKey = "content.error.invalid_theme")
if ((request.themeId == 12L || request.themeId == 13L || request.themeId == 14L) && request.price < 5) {
throw SodaException("알람, 모닝콜, 슬립콜 테마의 콘텐츠는 5캔 이상의 유료콘텐츠로 등록이 가능합니다.")
throw SodaException(messageKey = "content.error.alarm_theme_price_min")
}
if (request.price in 1..4) throw SodaException("콘텐츠의 최소금액은 5캔 입니다.")
if (request.price in 1..4) throw SodaException(messageKey = "content.error.minimum_price")
val isFullDetailVisible = if (request.price >= 50) {
request.isFullDetailVisible
@@ -388,34 +390,34 @@ class AudioContentService(
if (previewStartTime != null && previewEndTime != null) {
val startTimeArray = previewStartTime.split(":")
if (startTimeArray.size != 3) {
throw SodaException("미리 듣기 시간 형식은 00:30:00 과 같아야 합니다")
throw SodaException(messageKey = "content.error.preview_time_format")
}
for (time in startTimeArray) {
if (time.length != 2) {
throw SodaException("미리 듣기 시간 형식은 00:30:00 과 같아야 합니다")
throw SodaException(messageKey = "content.error.preview_time_format")
}
}
val endTimeArray = previewEndTime.split(":")
if (endTimeArray.size != 3) {
throw SodaException("미리 듣기 시간 형식은 00:30:00 과 같아야 합니다")
throw SodaException(messageKey = "content.error.preview_time_format")
}
for (time in endTimeArray) {
if (time.length != 2) {
throw SodaException("미리 듣기 시간 형식은 00:30:00 과 같아야 합니다")
throw SodaException(messageKey = "content.error.preview_time_format")
}
}
val timeDifference = timeDifference(previewStartTime, previewEndTime)
if (timeDifference < 15000) {
throw SodaException("미리 듣기의 최소 시간은 15초 입니다.")
throw SodaException(messageKey = "content.error.preview_time_minimum")
}
} else {
if (previewStartTime != null || previewEndTime != null) {
throw SodaException("미리 듣기 시작 시간과 종료 시간 둘 다 입력을 하거나 둘 다 입력 하지 않아야 합니다.")
throw SodaException(messageKey = "content.error.preview_time_both_required")
}
}
}
@@ -445,10 +447,10 @@ class AudioContentService(
@Transactional
fun uploadComplete(contentId: Long, content: String, duration: String) {
val keyFileName = content.split("/").last()
if (!keyFileName.startsWith(contentId.toString())) throw SodaException("잘못된 요청입니다.")
if (!keyFileName.startsWith(contentId.toString())) throw SodaException(messageKey = "common.error.invalid_request")
val audioContent = repository.findByIdOrNull(contentId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
audioContent.content = content
audioContent.duration = duration
@@ -456,7 +458,7 @@ class AudioContentService(
applicationEventPublisher.publishEvent(
FcmEvent(
type = FcmEventType.INDIVIDUAL,
title = "콘텐츠 등록완료",
title = formatMessage("content.notification.upload_complete_title"),
message = audioContent.title,
recipients = listOf(audioContent.member!!.id!!),
isAuth = null,
@@ -471,7 +473,7 @@ class AudioContentService(
FcmEvent(
type = FcmEventType.UPLOAD_CONTENT,
title = audioContent.member!!.nickname,
message = "콘텐츠를 업로드 하였습니다. - ${audioContent.title}",
message = formatMessage("content.notification.uploaded_message", audioContent.title),
isAuth = audioContent.isAdult,
contentId = contentId,
creatorId = audioContent.member!!.id,
@@ -483,7 +485,7 @@ class AudioContentService(
FcmEvent(
type = FcmEventType.UPLOAD_CONTENT,
title = audioContent.member!!.nickname,
message = "콘텐츠를 업로드 하였습니다. - ${audioContent.title}",
message = formatMessage("content.notification.uploaded_message", audioContent.title),
isAuth = audioContent.isAdult,
contentId = contentId,
creatorId = audioContent.member!!.id,
@@ -505,7 +507,7 @@ class AudioContentService(
FcmEvent(
type = FcmEventType.UPLOAD_CONTENT,
title = audioContent.member!!.nickname,
message = "콘텐츠를 업로드 하였습니다. - ${audioContent.title}",
message = formatMessage("content.notification.uploaded_message", audioContent.title),
isAuth = audioContent.isAdult,
contentId = audioContent.id!!,
creatorId = audioContent.member!!.id,
@@ -517,7 +519,7 @@ class AudioContentService(
FcmEvent(
type = FcmEventType.UPLOAD_CONTENT,
title = audioContent.member!!.nickname,
message = "콘텐츠를 업로드 하였습니다. - ${audioContent.title}",
message = formatMessage("content.notification.uploaded_message", audioContent.title),
isAuth = audioContent.isAdult,
contentId = audioContent.id!!,
creatorId = audioContent.member!!.id,
@@ -538,12 +540,12 @@ class AudioContentService(
// 오디오 콘텐츠 조회 (content_id, 제목, 내용, 테마, 태그, 19여부, 이미지, 콘텐츠 PATH)
val audioContent = repository.findByIdOrNull(id)
?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
// 크리에이터(유저) 정보
val creatorId = audioContent.member!!.id!!
val creator = explorerQueryRepository.getMember(creatorId)
?: throw SodaException("없는 사용자 입니다.")
?: throw SodaException(messageKey = "content.error.user_not_found")
val creatorFollowing = explorerQueryRepository.getCreatorFollowing(
creatorId = creatorId,
@@ -557,7 +559,9 @@ class AudioContentService(
// 차단된 사용자 체크
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = creatorId)
if (isBlocked && !isExistsAudioContent) throw SodaException("${creator.nickname}님의 요청으로 콘텐츠 접근이 제한됩니다.")
if (isBlocked && !isExistsAudioContent) {
throw SodaException(formatMessage("content.error.access_restricted_by_creator", creator.nickname))
}
val orderSequence = if (isExistsAudioContent) {
limitedEditionOrderRepository.getOrderSequence(
@@ -595,7 +599,7 @@ class AudioContentService(
audioContent.releaseDate != null &&
audioContent.releaseDate!! < LocalDateTime.now()
) {
throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
throw SodaException(messageKey = "content.error.invalid_content_retry")
}
// 댓글
@@ -628,11 +632,13 @@ class AudioContentService(
audioContent.releaseDate != null &&
audioContent.releaseDate!! >= LocalDateTime.now()
) {
val releaseDatePattern = messageSource.getMessage("content.release_date.format", langContext.lang)
?: "yyyy년 MM월 dd일 HH시 mm분 오픈예정"
audioContent.releaseDate!!
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
.toLocalDateTime()
.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH시 mm분 오픈예정"))
.format(DateTimeFormatter.ofPattern(releaseDatePattern, langContext.lang.locale))
} else {
null
}
@@ -1114,8 +1120,13 @@ class AudioContentService(
limit: Long,
sortType: String = "매출"
): GetAudioContentRanking {
val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일")
val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일")
val normalizedSortType = normalizeRankingSortType(sortType)
val startDatePattern = messageSource.getMessage("content.ranking.date.start_format", langContext.lang)
?: "yyyy년 MM월 dd일"
val endDatePattern = messageSource.getMessage("content.ranking.date.end_format", langContext.lang)
?: "MM월 dd일"
val startDateFormatter = DateTimeFormatter.ofPattern(startDatePattern, langContext.lang.locale)
val endDateFormatter = DateTimeFormatter.ofPattern(endDatePattern, langContext.lang.locale)
val contentRankingItemList = repository
.getAudioContentRanking(
@@ -1126,7 +1137,7 @@ class AudioContentService(
contentType = contentType,
offset = offset,
limit = limit,
sortType = sortType
sortType = normalizedSortType
)
return GetAudioContentRanking(
@@ -1137,16 +1148,19 @@ class AudioContentService(
}
fun getContentRankingSortTypeList(): List<String> {
return listOf("매출", "댓글", "좋아요")
val salesLabel = messageSource.getMessage("content.ranking.sort_type.sales", langContext.lang) ?: "매출"
val commentLabel = messageSource.getMessage("content.ranking.sort_type.comment", langContext.lang) ?: "댓글"
val likeLabel = messageSource.getMessage("content.ranking.sort_type.like", langContext.lang) ?: "좋아요"
return listOf(salesLabel, commentLabel, likeLabel)
}
@Transactional
fun pinToTheTop(contentId: Long, member: Member) {
val audioContent = repository.findByIdAndCreatorId(contentId = contentId, creatorId = member.id!!)
?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
if (audioContent.releaseDate != null && audioContent.releaseDate!! >= LocalDateTime.now()) {
throw SodaException("콘텐츠 오픈 후 채널에 고정이 가능합니다.")
throw SodaException(messageKey = "content.error.pin_available_after_open")
}
var pinContent = pinContentRepository.findByContentIdAndMemberId(
@@ -1176,14 +1190,14 @@ class AudioContentService(
val pinContent = pinContentRepository.findByContentIdAndMemberId(
contentId = contentId,
memberId = member.id!!
) ?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
) ?: throw SodaException(messageKey = "content.error.invalid_content_retry")
pinContent.isActive = false
}
fun generateUrl(contentId: Long, member: Member): GenerateUrlResponse {
val audioContent = repository.findByIdOrNull(contentId)
?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
val isExistsAudioContent = orderRepository.isExistOrdered(
memberId = member.id!!,
@@ -1312,4 +1326,28 @@ class AudioContentService(
.distinct()
.toList()
}
private fun normalizeRankingSortType(sortType: String?): String {
val trimmed = sortType?.trim().orEmpty()
val internalTypes = setOf("매출", "댓글", "좋아요", "후원")
if (trimmed in internalTypes) return trimmed
val salesLabel = messageSource.getMessage("content.ranking.sort_type.sales", langContext.lang)
val commentLabel = messageSource.getMessage("content.ranking.sort_type.comment", langContext.lang)
val likeLabel = messageSource.getMessage("content.ranking.sort_type.like", langContext.lang)
val donationLabel = messageSource.getMessage("content.ranking.sort_type.donation", langContext.lang)
return when (trimmed) {
salesLabel -> "매출"
commentLabel -> "댓글"
likeLabel -> "좋아요"
donationLabel -> "후원"
else -> "매출"
}
}
private fun formatMessage(key: String, vararg args: Any): String {
val template = messageSource.getMessage(key, langContext.lang) ?: return ""
return String.format(template, *args)
}
}

View File

@@ -24,7 +24,7 @@ class CategoryController(private val service: CategoryService) {
@RequestBody request: CreateCategoryRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.createCategory(request = request, member = member))
}
@@ -35,7 +35,7 @@ class CategoryController(private val service: CategoryService) {
@RequestBody request: ModifyCategoryRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.modifyCategory(request = request, member = member))
}
@@ -46,7 +46,7 @@ class CategoryController(private val service: CategoryService) {
@RequestBody request: UpdateCategoryOrdersRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.updateCategoryOrders(request = request, member = member))
}
@@ -57,7 +57,7 @@ class CategoryController(private val service: CategoryService) {
@PathVariable("id") categoryId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.deleteCategory(categoryId = categoryId, member = member))
}
@@ -67,7 +67,7 @@ class CategoryController(private val service: CategoryService) {
@RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getCategoryList(creatorId = creatorId, memberId = member.id!!))
}

View File

@@ -66,7 +66,7 @@ class CategoryService(
@Transactional
fun modifyCategory(request: ModifyCategoryRequest, member: Member) {
val category = repository.findByIdAndMemberId(categoryId = request.categoryId, memberId = member.id!!)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (!request.title.isNullOrBlank()) {
validateTitle(title = request.title)
@@ -108,7 +108,7 @@ class CategoryService(
@Transactional
fun deleteCategory(categoryId: Long, member: Member) {
val category = repository.findByIdAndMemberId(categoryId = categoryId, memberId = member.id!!)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
category.isActive = false
categoryContentRepository.deleteByCategoryId(categoryId = categoryId)
@@ -128,7 +128,7 @@ class CategoryService(
@Transactional
fun getCategoryList(creatorId: Long, memberId: Long): List<GetCategoryListResponse> {
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = creatorId)
if (isBlocked) throw SodaException("잘못된 접근입니다.")
if (isBlocked) throw SodaException(messageKey = "category.error.invalid_access")
// 기본 카테고리 목록 조회 (원본 언어 기준)
val baseList = repository.findByCreatorId(creatorId = creatorId)
@@ -205,6 +205,6 @@ class CategoryService(
}
private fun validateTitle(title: String) {
if (title.length < 2) throw SodaException("카테고리명은 2글자 이상 입력하세요")
if (title.length < 2) throw SodaException(messageKey = "category.error.title_min_length")
}
}

View File

@@ -25,7 +25,7 @@ class AudioContentCommentController(
@RequestBody request: RegisterCommentRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val commentId = service.registerComment(
comment = request.comment,
@@ -62,7 +62,7 @@ class AudioContentCommentController(
@RequestBody request: ModifyCommentRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.modifyComment(request = request, member = member))
}
@@ -74,7 +74,7 @@ class AudioContentCommentController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getCommentList(
@@ -93,7 +93,7 @@ class AudioContentCommentController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
): ApiResponse<GetAudioContentCommentListResponse> {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
return ApiResponse.ok(
service.getCommentReplyList(

View File

@@ -7,6 +7,8 @@ import kr.co.vividnext.sodalive.content.LanguageDetectTargetType
import kr.co.vividnext.sodalive.content.order.OrderRepository
import kr.co.vividnext.sodalive.fcm.FcmEvent
import kr.co.vividnext.sodalive.fcm.FcmEventType
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import org.springframework.beans.factory.annotation.Value
@@ -24,6 +26,8 @@ class AudioContentCommentService(
private val audioContentRepository: AudioContentRepository,
private val applicationEventPublisher: ApplicationEventPublisher,
private val orderRepository: OrderRepository,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String
@@ -38,11 +42,13 @@ class AudioContentCommentService(
languageCode: String?
): Long {
val audioContent = audioContentRepository.findByIdOrNull(id = audioContentId)
?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
val creator = audioContent.member!!
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = creator.id!!)
if (isBlocked) throw SodaException("${creator.nickname}님의 요청으로 댓글쓰기가 제한됩니다.")
if (isBlocked) {
throw SodaException(formatMessage("content.comment.error.blocked_by_creator", creator.nickname))
}
val (isExistsAudioContent, _) = orderRepository.isExistOrderedAndOrderType(
memberId = member.id!!,
@@ -50,7 +56,7 @@ class AudioContentCommentService(
)
if (isSecret && !isExistsAudioContent) {
throw SodaException("콘텐츠 구매 후 비밀댓글을 등록할 수 있습니다.")
throw SodaException(messageKey = "content.comment.error.secret_requires_purchase")
}
val audioContentComment = AudioContentComment(comment = comment, languageCode = languageCode, isSecret = isSecret)
@@ -78,9 +84,9 @@ class AudioContentCommentService(
member.nickname
},
message = if (parent != null) {
"댓글에 답글을 달았습니다.: ${audioContent.title}"
formatMessage("content.comment.notification.reply", audioContent.title)
} else {
"콘텐츠에 댓글을 달았습니다.: ${audioContent.title}"
formatMessage("content.comment.notification.new", audioContent.title)
},
contentId = audioContentId,
commentParentId = parentId,
@@ -105,7 +111,7 @@ class AudioContentCommentService(
@Transactional
fun modifyComment(request: ModifyCommentRequest, member: Member) {
val audioContentComment = repository.findByIdOrNull(request.commentId)
?: throw SodaException("잘못된 접근 입니다.\n확인 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "content.comment.error.invalid_access_retry")
if (audioContentComment.member!!.id!! == member.id!!) {
if (request.comment != null) {
@@ -164,4 +170,9 @@ class AudioContentCommentService(
return GetAudioContentCommentListResponse(totalCount, commentList)
}
private fun formatMessage(key: String, vararg args: Any): String {
val template = messageSource.getMessage(key, langContext.lang) ?: return ""
return String.format(template, *args)
}
}

View File

@@ -18,7 +18,7 @@ class AudioContentDonationController(private val service: AudioContentDonationSe
@RequestBody request: AudioContentDonationRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.donation(request = request, member = member))
}

View File

@@ -22,11 +22,11 @@ class AudioContentDonationService(
) {
@Transactional
fun donation(request: AudioContentDonationRequest, member: Member) {
if (request.donationCan < 1) throw SodaException("1캔 이상 후원하실 수 있습니다.")
if (request.comment.isBlank()) throw SodaException("함께 보낼 메시지를 입력하세요.")
if (request.donationCan < 1) throw SodaException(messageKey = "content.donation.error.minimum_can")
if (request.comment.isBlank()) throw SodaException(messageKey = "content.donation.error.comment_required")
val audioContent = queryRepository.findByIdAndActive(request.contentId)
?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
canPaymentService.spendCan(
memberId = member.id!!,

View File

@@ -22,7 +22,7 @@ class AudioContentMainController(
fun newContentUploadCreatorList(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getNewContentUploadCreatorList(
@@ -36,7 +36,7 @@ class AudioContentMainController(
fun getMainBannerList(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getAudioContentMainBannerList(
@@ -50,7 +50,7 @@ class AudioContentMainController(
fun getMainOrderList(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
orderService.getAudioContentMainOrderList(
@@ -68,7 +68,7 @@ class AudioContentMainController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getNewContentByTheme(
@@ -87,7 +87,7 @@ class AudioContentMainController(
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getThemeList(
@@ -105,7 +105,7 @@ class AudioContentMainController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getNewContentFor2WeeksByTheme(
@@ -125,7 +125,7 @@ class AudioContentMainController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getAudioContentCurationListWithPaging(

View File

@@ -47,6 +47,177 @@ class SodaMessageSource {
)
)
private val contentErrorMessages = mapOf(
"content.error.invalid_content_retry" to mapOf(
Lang.KO to "잘못된 콘텐츠 입니다.\n다시 시도해 주세요.",
Lang.EN to "Invalid content.\nPlease try again.",
Lang.JA to "不正なコンテンツです。\nもう一度お試しください。"
),
"content.error.cover_image_required" to mapOf(
Lang.KO to "커버이미지를 선택해 주세요.",
Lang.EN to "Please select a cover image.",
Lang.JA to "カバー画像を選択してください。"
),
"content.error.content_required" to mapOf(
Lang.KO to "콘텐츠를 선택해 주세요.",
Lang.EN to "Please select content.",
Lang.JA to "コンテンツを選択してください。"
),
"content.error.invalid_theme" to mapOf(
Lang.KO to "잘못된 테마입니다. 다시 선택해 주세요.",
Lang.EN to "Invalid theme. Please select again.",
Lang.JA to "不正なテーマです。もう一度選択してください。"
),
"content.error.alarm_theme_price_min" to mapOf(
Lang.KO to "알람, 모닝콜, 슬립콜 테마의 콘텐츠는 5캔 이상의 유료콘텐츠로 등록이 가능합니다.",
Lang.EN to "Alarm, Morning Call, and Sleep Call themes require paid content of at least 5 cans.",
Lang.JA to "アラーム、モーニングコール、スリープコールのテーマは5缶以上の有料コンテンツのみ登録できます。"
),
"content.error.minimum_price" to mapOf(
Lang.KO to "콘텐츠의 최소금액은 5캔 입니다.",
Lang.EN to "The minimum price for content is 5 cans.",
Lang.JA to "コンテンツの最低価格は5缶です。"
),
"content.error.preview_time_format" to mapOf(
Lang.KO to "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다",
Lang.EN to "Preview time format must be like 00:30:00.",
Lang.JA to "プレビュー時間の形式は00:30:00のようにする必要があります。"
),
"content.error.preview_time_minimum" to mapOf(
Lang.KO to "미리 듣기의 최소 시간은 15초 입니다.",
Lang.EN to "The minimum preview time is 15 seconds.",
Lang.JA to "プレビューの最小時間は15秒です。"
),
"content.error.preview_time_both_required" to mapOf(
Lang.KO to "미리 듣기 시작 시간과 종료 시간 둘 다 입력을 하거나 둘 다 입력 하지 않아야 합니다.",
Lang.EN to "You must enter both preview start and end times, or neither.",
Lang.JA to "プレビューの開始時間と終了時間は両方入力するか、両方入力しないでください。"
),
"content.error.user_not_found" to mapOf(
Lang.KO to "없는 사용자 입니다.",
Lang.EN to "User not found.",
Lang.JA to "ユーザーが見つかりません。"
),
"content.error.access_restricted_by_creator" to mapOf(
Lang.KO to "%s님의 요청으로 콘텐츠 접근이 제한됩니다.",
Lang.EN to "Access to content is restricted at %s's request.",
Lang.JA to "%sさんの要請によりコンテンツへのアクセスが制限されています。"
),
"content.error.pin_available_after_open" to mapOf(
Lang.KO to "콘텐츠 오픈 후 채널에 고정이 가능합니다.",
Lang.EN to "You can pin it to the channel after the content is opened.",
Lang.JA to "コンテンツ公開後にチャンネルへ固定できます。"
)
)
private val contentNotificationMessages = mapOf(
"content.notification.upload_complete_title" to mapOf(
Lang.KO to "콘텐츠 등록완료",
Lang.EN to "Content registration complete",
Lang.JA to "コンテンツ登録完了"
),
"content.notification.uploaded_message" to mapOf(
Lang.KO to "콘텐츠를 업로드 하였습니다. - %s",
Lang.EN to "Content uploaded. - %s",
Lang.JA to "コンテンツをアップロードしました。- %s"
)
)
private val contentFormatMessages = mapOf(
"content.release_date.format" to mapOf(
Lang.KO to "yyyy년 MM월 dd일 HH시 mm분 오픈예정",
Lang.EN to "MMM dd, yyyy HH:mm 'Opens soon'",
Lang.JA to "yyyy年 MM月 dd日 HH時 mm分 公開予定"
),
"content.ranking.date.start_format" to mapOf(
Lang.KO to "yyyy년 MM월 dd일",
Lang.EN to "MMM dd, yyyy",
Lang.JA to "yyyy年 MM月 dd日"
),
"content.ranking.date.end_format" to mapOf(
Lang.KO to "MM월 dd일",
Lang.EN to "MMM dd",
Lang.JA to "MM月 dd日"
)
)
private val contentRankingMessages = mapOf(
"content.ranking.sort_type.sales" to mapOf(
Lang.KO to "매출",
Lang.EN to "Sales",
Lang.JA to "売上"
),
"content.ranking.sort_type.comment" to mapOf(
Lang.KO to "댓글",
Lang.EN to "Comments",
Lang.JA to "コメント"
),
"content.ranking.sort_type.like" to mapOf(
Lang.KO to "좋아요",
Lang.EN to "Likes",
Lang.JA to "いいね"
),
"content.ranking.sort_type.donation" to mapOf(
Lang.KO to "후원",
Lang.EN to "Donations",
Lang.JA to "支援"
)
)
private val contentCommentMessages = mapOf(
"content.comment.error.blocked_by_creator" to mapOf(
Lang.KO to "%s님의 요청으로 댓글쓰기가 제한됩니다.",
Lang.EN to "Commenting is restricted at %s's request.",
Lang.JA to "%sさんの要請によりコメントの投稿が制限されています。"
),
"content.comment.error.secret_requires_purchase" to mapOf(
Lang.KO to "콘텐츠 구매 후 비밀댓글을 등록할 수 있습니다.",
Lang.EN to "You can post a secret comment after purchasing the content.",
Lang.JA to "コンテンツ購入後に秘密コメントを登録できます。"
),
"content.comment.notification.reply" to mapOf(
Lang.KO to "댓글에 답글을 달았습니다.: %s",
Lang.EN to "Replied to a comment: %s",
Lang.JA to "コメントに返信しました: %s"
),
"content.comment.notification.new" to mapOf(
Lang.KO to "콘텐츠에 댓글을 달았습니다.: %s",
Lang.EN to "Commented on content: %s",
Lang.JA to "コンテンツにコメントしました: %s"
),
"content.comment.error.invalid_access_retry" to mapOf(
Lang.KO to "잘못된 접근 입니다.\n확인 후 다시 시도해 주세요.",
Lang.EN to "Invalid access.\nPlease check and try again.",
Lang.JA to "不正なアクセスです。\n確認して再度お試しください。"
)
)
private val contentDonationMessages = mapOf(
"content.donation.error.minimum_can" to mapOf(
Lang.KO to "1캔 이상 후원하실 수 있습니다.",
Lang.EN to "You can donate at least 1 can.",
Lang.JA to "1缶以上寄付できます。"
),
"content.donation.error.comment_required" to mapOf(
Lang.KO to "함께 보낼 메시지를 입력하세요.",
Lang.EN to "Please enter a message to send.",
Lang.JA to "一緒に送るメッセージを入力してください。"
)
)
private val categoryMessages = mapOf(
"category.error.invalid_access" to mapOf(
Lang.KO to "잘못된 접근입니다.",
Lang.EN to "Invalid access.",
Lang.JA to "不正なアクセスです。"
),
"category.error.title_min_length" to mapOf(
Lang.KO to "카테고리명은 2글자 이상 입력하세요",
Lang.EN to "Category name must be at least 2 characters.",
Lang.JA to "カテゴリ名は2文字以上入力してください。"
)
)
private val alarmMessages = mapOf(
"alarm.error.already_purchased" to mapOf(
Lang.KO to "이미 구매하셨습니다",
@@ -1952,6 +2123,13 @@ class SodaMessageSource {
fun getMessage(key: String, lang: Lang): String? {
val messageGroups = listOf(
commonMessages,
contentErrorMessages,
contentNotificationMessages,
contentFormatMessages,
contentRankingMessages,
contentCommentMessages,
contentDonationMessages,
categoryMessages,
alarmMessages,
auditionMessages,
auditionRequestMessages,