클라이언트 메시지 다국어 처리

This commit is contained in:
2025-12-23 19:22:06 +09:00
parent e987a56544
commit 60e654cda9
11 changed files with 132 additions and 47 deletions

View File

@@ -19,7 +19,7 @@ class OrderController(private val service: OrderService) {
@RequestBody request: OrderRequest,
@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.order(
@@ -36,7 +36,7 @@ class OrderController(private val service: OrderService) {
@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.getAudioContentOrderList(

View File

@@ -33,11 +33,11 @@ class OrderService(
@Transactional
fun order(contentId: Long, orderType: OrderType, container: String, member: Member) {
val content = audioContentRepository.findByIdAndActive(contentId)
?: throw SodaException("잘못된 콘텐츠 입니다\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
validateOrder(memberId = member.id!!, content = content, orderType = orderType)
val order = if (content.limited != null && content.remaining != null) {
if (content.remaining!! <= 0) throw SodaException("해당 콘텐츠가 매진되었습니다.")
if (content.remaining!! <= 0) throw SodaException(messageKey = "order.error.content_sold_out")
orderLimitedEditionContent(content, member)
} else {
orderContent(orderType, content, member)
@@ -93,16 +93,20 @@ class OrderService(
}
private fun validateOrder(memberId: Long, content: AudioContent, orderType: OrderType) {
if (memberId == content.member!!.id!!) throw SodaException("자신이 올린 콘텐츠는 구매할 수 없습니다.")
if (memberId == content.member!!.id!!) {
throw SodaException(messageKey = "order.error.cannot_purchase_own_content")
}
val existOrdered = repository.isExistOrdered(memberId = memberId, contentId = content.id!!)
if (existOrdered) throw SodaException("이미 구매한 콘텐츠 입니다.")
if (existOrdered) throw SodaException(messageKey = "order.error.already_purchased")
val isOnlyRental = content.purchaseOption == PurchaseOption.RENT_ONLY || content.isOnlyRental
if (isOnlyRental && orderType == OrderType.KEEP) throw SodaException("대여만 가능한 콘텐츠 입니다.")
if (isOnlyRental && orderType == OrderType.KEEP) {
throw SodaException(messageKey = "order.error.rental_only")
}
val isOnlyBuy = content.purchaseOption == PurchaseOption.BUY_ONLY && orderType == OrderType.RENTAL
if (isOnlyBuy) throw SodaException("소장만 가능한 콘텐츠 입니다.\n앱 업데이트 후 구매해 주세요.")
if (isOnlyBuy) throw SodaException(messageKey = "order.error.keep_only_update_required")
}
fun getAudioContentOrderList(

View File

@@ -21,7 +21,7 @@ class AudioContentPlaylistController(private val service: AudioContentPlaylistSe
@RequestBody request: CreatePlaylistRequest,
@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.createPlaylist(request, member))
}
@@ -32,7 +32,7 @@ class AudioContentPlaylistController(private val service: AudioContentPlaylistSe
@RequestBody request: UpdatePlaylistRequest,
@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.updatePlaylist(playlistId = id, request = request, member = member))
}
@@ -42,7 +42,7 @@ class AudioContentPlaylistController(private val service: AudioContentPlaylistSe
@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.deletePlaylist(playlistId = id, member))
}
@@ -51,7 +51,7 @@ class AudioContentPlaylistController(private val service: AudioContentPlaylistSe
fun getPlaylists(
@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.getPlaylists(member))
}
@@ -61,7 +61,7 @@ class AudioContentPlaylistController(private val service: AudioContentPlaylistSe
@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.getPlaylistDetail(playlistId = id, member = member))
}

View File

@@ -20,12 +20,12 @@ class AudioContentPlaylistService(
) {
fun createPlaylist(request: CreatePlaylistRequest, member: Member) {
if (request.contentIdAndOrderList.size >= 30) {
throw SodaException("플레이 리스트에는 최대 30개의 콘텐츠를 저장할 수 있습니다.")
throw SodaException(messageKey = "playlist.error.max_content_limit")
}
val playlistCount = redisRepository.findByMemberId(member.id!!).size
if (playlistCount >= 10) {
throw SodaException("플레이 리스트는 최대 10개까지 생성할 수 있습니다.")
throw SodaException(messageKey = "playlist.error.max_playlist_limit")
}
val contentIdAndOrderList = validateAndGetContentIdAndOrderList(
@@ -68,7 +68,7 @@ class AudioContentPlaylistService(
private fun validateContent(contentIdList: List<Long>, memberId: Long) {
if (contentIdList.isEmpty()) {
throw SodaException("콘텐츠를 1개 이상 추가하세요")
throw SodaException(messageKey = "playlist.error.content_required")
}
checkOrderedContent(
@@ -83,20 +83,20 @@ class AudioContentPlaylistService(
val notOrderedContentList = orderedContentMap.filterValues { !it }.keys
if (notOrderedContentList.isNotEmpty()) {
throw SodaException("대여/소장하지 않은 콘텐츠는 재생목록에 추가할 수 없습니다.")
throw SodaException(messageKey = "playlist.error.not_purchased_content")
}
}
fun updatePlaylist(playlistId: Long, request: UpdatePlaylistRequest, member: Member) {
if (request.contentIdAndOrderList.size >= 30) {
throw SodaException("플레이 리스트에는 최대 30개의 콘텐츠를 저장할 수 있습니다.")
throw SodaException(messageKey = "playlist.error.max_content_limit")
}
val playlist = redisRepository.findByIdOrNull(id = playlistId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (playlist.memberId != member.id) {
throw SodaException("잘못된 요청입니다.")
throw SodaException(messageKey = "common.error.invalid_request")
}
val contentIdAndOrderList = validateAndGetContentIdAndOrderList(
@@ -145,10 +145,10 @@ class AudioContentPlaylistService(
fun deletePlaylist(playlistId: Long, member: Member) {
val playlist = redisRepository.findByIdOrNull(id = playlistId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (playlist.memberId != member.id) {
throw SodaException("잘못된 요청입니다.")
throw SodaException(messageKey = "common.error.invalid_request")
}
redisRepository.delete(playlist)
@@ -156,10 +156,10 @@ class AudioContentPlaylistService(
fun getPlaylistDetail(playlistId: Long, member: Member): GetPlaylistDetailResponse {
val playlist = redisRepository.findByIdOrNull(id = playlistId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (playlist.memberId != member.id) {
throw SodaException("잘못된 요청입니다.")
throw SodaException(messageKey = "common.error.invalid_request")
}
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd")

View File

@@ -26,7 +26,7 @@ class ContentSeriesController(private val service: ContentSeriesService) {
@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.getSeriesList(
@@ -49,7 +49,7 @@ class ContentSeriesController(private val service: ContentSeriesService) {
@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.getSeriesDetail(
@@ -70,7 +70,7 @@ class ContentSeriesController(private val service: ContentSeriesService) {
@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.getSeriesContentList(
@@ -91,7 +91,7 @@ class ContentSeriesController(private val service: ContentSeriesService) {
@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.getRecommendSeriesList(

View File

@@ -210,11 +210,11 @@ class ContentSeriesService(
seriesId = seriesId,
isAuth = member.auth != null && isAdultContentVisible,
contentType = contentType
) ?: throw SodaException("잘못된 시리즈 입니다.\n다시 시도해 주세요")
) ?: throw SodaException(messageKey = "series.error.invalid_series_retry")
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = series.member!!.id!!)
if (isBlocked) {
throw SodaException("잘못된 시리즈 입니다.\n다시 시도해 주세요")
throw SodaException(messageKey = "series.error.invalid_series_retry")
}
val creatorFollowing = explorerQueryRepository.getCreatorFollowing(

View File

@@ -31,7 +31,7 @@ class SeriesMainController(
@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")
val banners = bannerService.getActiveBanners(PageRequest.of(0, 10))
.content
@@ -70,7 +70,7 @@ class SeriesMainController(
@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(
contentSeriesService.getRecommendSeriesList(
@@ -90,7 +90,7 @@ class SeriesMainController(
@RequestParam(defaultValue = "20") size: Int,
@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 pageable = PageRequest.of(page, size)
ApiResponse.ok(
@@ -111,7 +111,7 @@ class SeriesMainController(
@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")
val memberId = member.id!!
val isAdult = member.auth != null && (isAdultContentVisible ?: true)
@@ -134,7 +134,7 @@ class SeriesMainController(
@RequestParam(defaultValue = "20") size: Int,
@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 pageable = PageRequest.of(page, size)
ApiResponse.ok(

View File

@@ -18,13 +18,13 @@ class ContentSeriesBannerService(
fun getBannerById(bannerId: Long): SeriesBanner {
return bannerRepository.findById(bannerId)
.orElseThrow { SodaException("배너를 찾을 수 없습니다: $bannerId") }
.orElseThrow { SodaException(messageKey = "series.banner.error.not_found") }
}
@Transactional
fun registerBanner(seriesId: Long, imagePath: String): SeriesBanner {
val series = seriesRepository.findByIdAndActiveTrue(seriesId)
?: throw SodaException("시리즈를 찾을 수 없습니다: $seriesId")
?: throw SodaException(messageKey = "series.banner.error.series_not_found")
val finalSortOrder = (bannerRepository.findMaxSortOrder() ?: 0) + 1
@@ -43,14 +43,14 @@ class ContentSeriesBannerService(
seriesId: Long? = null
): SeriesBanner {
val banner = bannerRepository.findById(bannerId)
.orElseThrow { SodaException("배너를 찾을 수 없습니다: $bannerId") }
if (!banner.isActive) throw SodaException("비활성화된 배너는 수정할 수 없습니다: $bannerId")
.orElseThrow { SodaException(messageKey = "series.banner.error.not_found") }
if (!banner.isActive) throw SodaException(messageKey = "series.banner.error.inactive_cannot_update")
if (imagePath != null) banner.imagePath = imagePath
if (seriesId != null) {
val series = seriesRepository.findByIdAndActiveTrue(seriesId)
?: throw SodaException("시리즈를 찾을 수 없습니다: $seriesId")
?: throw SodaException(messageKey = "series.banner.error.series_not_found")
banner.series = series
}
@@ -60,7 +60,7 @@ class ContentSeriesBannerService(
@Transactional
fun deleteBanner(bannerId: Long) {
val banner = bannerRepository.findById(bannerId)
.orElseThrow { SodaException("배너를 찾을 수 없습니다: $bannerId") }
.orElseThrow { SodaException(messageKey = "series.banner.error.not_found") }
banner.isActive = false
bannerRepository.save(banner)
}
@@ -70,8 +70,8 @@ class ContentSeriesBannerService(
val updated = mutableListOf<SeriesBanner>()
for (index in ids.indices) {
val banner = bannerRepository.findById(ids[index])
.orElseThrow { SodaException("배너를 찾을 수 없습니다: ${ids[index]}") }
if (!banner.isActive) throw SodaException("비활성화된 배너는 수정할 수 없습니다: ${ids[index]}")
.orElseThrow { SodaException(messageKey = "series.banner.error.not_found") }
if (!banner.isActive) throw SodaException(messageKey = "series.banner.error.inactive_cannot_update")
banner.sortOrder = index + 1
updated.add(bannerRepository.save(banner))
}

View File

@@ -22,7 +22,7 @@ class AudioContentThemeController(private val service: AudioContentThemeService)
fun getThemes(
@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.getThemes())
}
@@ -35,7 +35,7 @@ class AudioContentThemeController(private val service: AudioContentThemeService)
@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.getActiveThemeOfContent(
@@ -56,7 +56,7 @@ class AudioContentThemeController(private val service: AudioContentThemeService)
@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.getContentByTheme(

View File

@@ -119,7 +119,7 @@ class AudioContentThemeService(
limit: Long
): GetContentByThemeResponse {
val theme = queryRepository.findThemeByIdAndActive(themeId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
val totalCount = contentRepository.totalCountByTheme(
memberId = member.id!!,

View File

@@ -205,6 +205,83 @@ class SodaMessageSource {
)
)
private val orderMessages = mapOf(
"order.error.content_sold_out" to mapOf(
Lang.KO to "해당 콘텐츠가 매진되었습니다.",
Lang.EN to "This content is sold out.",
Lang.JA to "このコンテンツは売り切れです。"
),
"order.error.cannot_purchase_own_content" to mapOf(
Lang.KO to "자신이 올린 콘텐츠는 구매할 수 없습니다.",
Lang.EN to "You cannot purchase your own content.",
Lang.JA to "自分が投稿したコンテンツは購入できません。"
),
"order.error.already_purchased" to mapOf(
Lang.KO to "이미 구매한 콘텐츠 입니다.",
Lang.EN to "This content has already been purchased.",
Lang.JA to "すでに購入したコンテンツです。"
),
"order.error.rental_only" to mapOf(
Lang.KO to "대여만 가능한 콘텐츠 입니다.",
Lang.EN to "This content is available for rental only.",
Lang.JA to "このコンテンツはレンタルのみ可能です。"
),
"order.error.keep_only_update_required" to mapOf(
Lang.KO to "소장만 가능한 콘텐츠 입니다.\n앱 업데이트 후 구매해 주세요.",
Lang.EN to "This content is available for purchase only.\nPlease update the app before purchasing.",
Lang.JA to "このコンテンツは購入のみ可能です。\nアプリを更新してから購入してください。"
)
)
private val playlistMessages = mapOf(
"playlist.error.max_content_limit" to mapOf(
Lang.KO to "플레이 리스트에는 최대 30개의 콘텐츠를 저장할 수 있습니다.",
Lang.EN to "A playlist can contain up to 30 contents.",
Lang.JA to "プレイリストには最大30件のコンテンツを保存できます。"
),
"playlist.error.max_playlist_limit" to mapOf(
Lang.KO to "플레이 리스트는 최대 10개까지 생성할 수 있습니다.",
Lang.EN to "You can create up to 10 playlists.",
Lang.JA to "プレイリストは最大10個まで作成できます。"
),
"playlist.error.content_required" to mapOf(
Lang.KO to "콘텐츠를 1개 이상 추가하세요",
Lang.EN to "Please add at least one content.",
Lang.JA to "コンテンツを1つ以上追加してください。"
),
"playlist.error.not_purchased_content" to mapOf(
Lang.KO to "대여/소장하지 않은 콘텐츠는 재생목록에 추가할 수 없습니다.",
Lang.EN to "Content you haven't rented or purchased cannot be added to the playlist.",
Lang.JA to "レンタルまたは購入していないコンテンツは再生リストに追加できません。"
)
)
private val seriesMessages = mapOf(
"series.error.invalid_series_retry" to mapOf(
Lang.KO to "잘못된 시리즈 입니다.\n다시 시도해 주세요",
Lang.EN to "Invalid series.\nPlease try again.",
Lang.JA to "不正なシリーズです。\nもう一度お試しください。"
)
)
private val seriesBannerMessages = mapOf(
"series.banner.error.not_found" to mapOf(
Lang.KO to "배너를 찾을 수 없습니다.",
Lang.EN to "Banner not found.",
Lang.JA to "バナーが見つかりません。"
),
"series.banner.error.inactive_cannot_update" to mapOf(
Lang.KO to "비활성화된 배너는 수정할 수 없습니다.",
Lang.EN to "Inactive banners cannot be updated.",
Lang.JA to "無効化されたバナーは修正できません。"
),
"series.banner.error.series_not_found" to mapOf(
Lang.KO to "시리즈를 찾을 수 없습니다.",
Lang.EN to "Series not found.",
Lang.JA to "シリーズが見つかりません。"
)
)
private val categoryMessages = mapOf(
"category.error.invalid_access" to mapOf(
Lang.KO to "잘못된 접근입니다.",
@@ -2129,6 +2206,10 @@ class SodaMessageSource {
contentRankingMessages,
contentCommentMessages,
contentDonationMessages,
orderMessages,
playlistMessages,
seriesMessages,
seriesBannerMessages,
categoryMessages,
alarmMessages,
auditionMessages,