콘텐츠 메시지 다국어 처리
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user