관리자 콘텐츠 메시지 다국어 처리

This commit is contained in:
2025-12-22 22:51:19 +09:00
parent 280b21c3cb
commit 93e0411337
10 changed files with 137 additions and 34 deletions

View File

@@ -51,7 +51,9 @@ class AdminContentService(
searchWord: String,
pageable: Pageable
): GetAdminContentListResponse {
if (searchWord.length < 2) throw SodaException("2글자 이상 입력하세요.")
if (searchWord.length < 2) {
throw SodaException(messageKey = "admin.content.search_word_min_length")
}
val totalCount = repository.getAudioContentTotalCount(searchWord, status = status)
val audioContentAndThemeList = repository.getAudioContentList(
status = status,
@@ -82,7 +84,7 @@ class AdminContentService(
@Transactional
fun updateAudioContent(request: UpdateAdminContentRequest) {
val audioContent = repository.findByIdOrNull(id = request.id)
?: throw SodaException("없는 콘텐츠 입니다.")
?: throw SodaException(messageKey = "admin.content.not_found")
if (request.isDefaultCoverImage) {
audioContent.coverImage = "`profile/default_profile.png`"

View File

@@ -33,19 +33,19 @@ class AdminContentBannerService(
fun createAudioContentMainBanner(image: MultipartFile, requestString: String) {
val request = objectMapper.readValue(requestString, CreateContentBannerRequest::class.java)
if (request.type == AudioContentBannerType.CREATOR && request.creatorId == null) {
throw SodaException("크리에이터를 선택하세요.")
throw SodaException(messageKey = "admin.content.banner.creator_required")
}
if (request.type == AudioContentBannerType.SERIES && request.seriesId == null) {
throw SodaException("시리즈를 선택하세요.")
throw SodaException(messageKey = "admin.content.banner.series_required")
}
if (request.type == AudioContentBannerType.LINK && request.link == null) {
throw SodaException("링크 url을 입력하세요.")
throw SodaException(messageKey = "admin.content.banner.link_required")
}
if (request.type == AudioContentBannerType.EVENT && request.eventId == null) {
throw SodaException("이벤트를 선택하세요.")
throw SodaException(messageKey = "admin.content.banner.event_required")
}
val event = if (request.eventId != null && request.eventId > 0) {
@@ -94,7 +94,7 @@ class AdminContentBannerService(
fun updateAudioContentMainBanner(image: MultipartFile?, requestString: String) {
val request = objectMapper.readValue(requestString, UpdateContentBannerRequest::class.java)
val audioContentBanner = repository.findByIdOrNull(request.id)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (image != null) {
val fileName = generateFileName()
@@ -124,22 +124,22 @@ class AdminContentBannerService(
AudioContentBannerType.EVENT -> {
if (request.eventId != null) {
val event = eventRepository.findByIdOrNull(request.eventId)
?: throw SodaException("이벤트를 선택하세요.")
?: throw SodaException(messageKey = "admin.content.banner.event_required")
audioContentBanner.event = event
} else {
throw SodaException("이벤트를 선택하세요.")
throw SodaException(messageKey = "admin.content.banner.event_required")
}
}
AudioContentBannerType.CREATOR -> {
if (request.creatorId != null) {
val creator = memberRepository.findByIdOrNull(request.creatorId)
?: throw SodaException("크리에이터를 선택하세요.")
?: throw SodaException(messageKey = "admin.content.banner.creator_required")
audioContentBanner.creator = creator
} else {
throw SodaException("크리에이터를 선택하세요.")
throw SodaException(messageKey = "admin.content.banner.creator_required")
}
}
@@ -147,18 +147,18 @@ class AdminContentBannerService(
if (request.link != null) {
audioContentBanner.link = request.link
} else {
throw SodaException("링크 url을 입력하세요.")
throw SodaException(messageKey = "admin.content.banner.link_required")
}
}
AudioContentBannerType.SERIES -> {
if (request.seriesId != null) {
val series = seriesRepository.findByIdOrNull(request.seriesId)
?: throw SodaException("시리즈를 선택하세요.")
?: throw SodaException(messageKey = "admin.content.banner.series_required")
audioContentBanner.series = series
} else {
throw SodaException("시리즈를 선택하세요.")
throw SodaException(messageKey = "admin.content.banner.series_required")
}
}
}

View File

@@ -21,7 +21,7 @@ class AdminContentCurationService(
@Transactional
fun createContentCuration(request: CreateContentCurationRequest) {
val tab = contentMainTabRepository.findByIdOrNull(request.tabId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
val curation = AudioContentCuration(
title = request.title,
@@ -37,7 +37,7 @@ class AdminContentCurationService(
@Transactional
fun updateContentCuration(request: UpdateContentCurationRequest) {
val audioContentCuration = repository.findByIdOrNull(id = request.id)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (request.title != null) {
audioContentCuration.title = request.title
@@ -85,7 +85,7 @@ class AdminContentCurationService(
fun getCurationItem(curationId: Long): List<GetCurationItemResponse> {
val curation = repository.findByIdOrNull(curationId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
return if (curation.isSeries) {
contentCurationItemRepository.getAudioContentCurationSeriesItemList(curationId)
@@ -106,7 +106,7 @@ class AdminContentCurationService(
fun addItemToCuration(request: AddItemToCurationRequest) {
// 큐레이션 조회
val audioContentCuration = repository.findByIdOrNull(id = request.curationId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (audioContentCuration.isSeries) {
request.itemIdList.forEach { seriesId ->

View File

@@ -28,7 +28,7 @@ class AdminHashTagCurationService(
val isExists = repository.isExistsTag(tag = tag)
if (isExists) {
throw SodaException("이미 등록된 태그 입니다.")
throw SodaException(messageKey = "admin.content.hash_tag.already_registered")
}
repository.save(
@@ -42,7 +42,7 @@ class AdminHashTagCurationService(
@Transactional
fun updateContentHashTagCuration(request: UpdateContentHashTagCurationRequest) {
val hashTagCuration = repository.findByIdOrNull(id = request.id)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (request.tag != null) {
var tag = request.tag.trim()
@@ -88,7 +88,7 @@ class AdminHashTagCurationService(
@Transactional
fun addItemToHashTagCuration(request: AddItemToCurationRequest) {
val curation = repository.findByIdOrNull(id = request.curationId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
request.itemIdList.forEach { contentId ->
val audioContent = audioContentRepository.findByIdAndActive(contentId)

View File

@@ -43,12 +43,12 @@ class AdminContentSeriesService(
@Transactional
fun modifySeries(request: AdminModifySeriesRequest) {
val series = repository.findByIdAndActiveTrue(request.seriesId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (request.publishedDaysOfWeek != null) {
val days = request.publishedDaysOfWeek
if (days.contains(SeriesPublishedDaysOfWeek.RANDOM) && days.size > 1) {
throw SodaException("랜덤과 연재요일 동시에 선택할 수 없습니다.")
throw SodaException(messageKey = "admin.content.series.random_days_conflict")
}
series.publishedDaysOfWeek.clear()
series.publishedDaysOfWeek.addAll(days)
@@ -56,7 +56,7 @@ class AdminContentSeriesService(
if (request.genreId != null) {
val genre = genreRepository.findActiveSeriesGenreById(request.genreId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
series.genre = genre
}

View File

@@ -11,6 +11,8 @@ import kr.co.vividnext.sodalive.aws.s3.S3Uploader
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.utils.generateFileName
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.PageRequest
@@ -33,6 +35,8 @@ import org.springframework.web.multipart.MultipartFile
class AdminContentSeriesBannerController(
private val bannerService: ContentSeriesBannerService,
private val s3Uploader: S3Uploader,
private val langContext: LangContext,
private val messageSource: SodaMessageSource,
@Value("\${cloud.aws.s3.bucket}")
private val s3Bucket: String,
@@ -113,7 +117,8 @@ class AdminContentSeriesBannerController(
@DeleteMapping("/{bannerId}")
fun deleteBanner(@PathVariable bannerId: Long) = run {
bannerService.deleteBanner(bannerId)
ApiResponse.ok("배너가 성공적으로 삭제되었습니다.")
val message = messageSource.getMessage("admin.content.series.banner.delete_success", langContext.lang)
ApiResponse.ok(message)
}
/**
@@ -124,7 +129,8 @@ class AdminContentSeriesBannerController(
@RequestBody request: UpdateBannerOrdersRequest
) = run {
bannerService.updateBannerOrders(request.ids)
ApiResponse.ok(null, "배너 정렬 순서가 성공적으로 변경되었습니다.")
val message = messageSource.getMessage("admin.content.series.banner.reorder_success", langContext.lang)
ApiResponse.ok(null, message)
}
private fun saveImage(bannerId: Long, image: MultipartFile): String {
@@ -139,7 +145,7 @@ class AdminContentSeriesBannerController(
metadata = metadata
)
} catch (e: Exception) {
throw SodaException("이미지 저장에 실패했습니다: ${e.message}")
throw SodaException(messageKey = "admin.content.series.banner.image_save_failed")
}
}
}

View File

@@ -17,11 +17,11 @@ class AdminContentSeriesGenreService(private val repository: AdminContentSeriesG
@Transactional
fun modifySeriesGenre(request: ModifySeriesGenreRequest) {
if (request.genre == null && request.isAdult == null && request.isActive == null) {
throw SodaException("변경사항이 없습니다.")
throw SodaException(messageKey = "admin.content.series.genre.no_changes")
}
val seriesGenre = repository.findByIdOrNull(id = request.id)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (request.genre != null) {
seriesGenre.genre = request.genre

View File

@@ -30,7 +30,7 @@ class AdminRecommendSeriesService(
fun createRecommendSeries(image: MultipartFile, requestString: String) {
val request = objectMapper.readValue(requestString, CreateRecommendSeriesRequest::class.java)
val series = seriesRepository.findByIdOrNull(request.seriesId)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
val recommendSeries = RecommendSeries(isFree = request.isFree)
recommendSeries.series = series
@@ -49,7 +49,7 @@ class AdminRecommendSeriesService(
fun updateRecommendSeries(image: MultipartFile?, requestString: String) {
val request = objectMapper.readValue(requestString, UpdateRecommendSeriesRequest::class.java)
val recommendSeries = repository.findByIdOrNull(request.id)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
if (image != null) {
val fileName = generateFileName()

View File

@@ -53,13 +53,15 @@ class AdminContentThemeService(
}
fun themeExistCheck(request: CreateContentThemeRequest) {
repository.findIdByTheme(request.theme)?.let { throw SodaException("이미 등록된 테마 입니다.") }
repository.findIdByTheme(request.theme)?.let {
throw SodaException(messageKey = "admin.content.theme.already_registered")
}
}
@Transactional
fun deleteTheme(id: Long) {
val theme = repository.findByIdOrNull(id)
?: throw SodaException("잘못된 요청입니다.")
?: throw SodaException(messageKey = "common.error.invalid_request")
theme.theme = "${theme.theme}_deleted"
theme.isActive = false

View File

@@ -303,6 +303,92 @@ class SodaMessageSource {
)
)
private val adminContentMessages = mapOf(
"admin.content.search_word_min_length" to mapOf(
Lang.KO to "2글자 이상 입력하세요.",
Lang.EN to "Please enter at least 2 characters.",
Lang.JA to "2文字以上入力してください。"
),
"admin.content.not_found" to mapOf(
Lang.KO to "없는 콘텐츠 입니다.",
Lang.EN to "Content not found.",
Lang.JA to "該当のコンテンツが見つかりません。"
)
)
private val adminContentBannerMessages = mapOf(
"admin.content.banner.creator_required" to mapOf(
Lang.KO to "크리에이터를 선택하세요.",
Lang.EN to "Please select a creator.",
Lang.JA to "クリエイターを選択してください。"
),
"admin.content.banner.series_required" to mapOf(
Lang.KO to "시리즈를 선택하세요.",
Lang.EN to "Please select a series.",
Lang.JA to "シリーズを選択してください。"
),
"admin.content.banner.link_required" to mapOf(
Lang.KO to "링크 url을 입력하세요.",
Lang.EN to "Please enter a link URL.",
Lang.JA to "リンクURLを入力してください。"
),
"admin.content.banner.event_required" to mapOf(
Lang.KO to "이벤트를 선택하세요.",
Lang.EN to "Please select an event.",
Lang.JA to "イベントを選択してください。"
)
)
private val adminHashTagCurationMessages = mapOf(
"admin.content.hash_tag.already_registered" to mapOf(
Lang.KO to "이미 등록된 태그 입니다.",
Lang.EN to "Tag already registered.",
Lang.JA to "既に登録されたタグです。"
)
)
private val adminContentSeriesMessages = mapOf(
"admin.content.series.random_days_conflict" to mapOf(
Lang.KO to "랜덤과 연재요일 동시에 선택할 수 없습니다.",
Lang.EN to "Random and published days cannot be selected together.",
Lang.JA to "ランダムと連載曜日を同時に選択できません。"
)
)
private val adminContentSeriesBannerMessages = mapOf(
"admin.content.series.banner.delete_success" to mapOf(
Lang.KO to "배너가 성공적으로 삭제되었습니다.",
Lang.EN to "Banner deleted successfully.",
Lang.JA to "バナーが削除されました。"
),
"admin.content.series.banner.reorder_success" to mapOf(
Lang.KO to "배너 정렬 순서가 성공적으로 변경되었습니다.",
Lang.EN to "Banner order updated successfully.",
Lang.JA to "バナーの並び順が変更されました。"
),
"admin.content.series.banner.image_save_failed" to mapOf(
Lang.KO to "이미지 저장에 실패했습니다.",
Lang.EN to "Failed to save image.",
Lang.JA to "画像の保存に失敗しました。"
)
)
private val adminContentSeriesGenreMessages = mapOf(
"admin.content.series.genre.no_changes" to mapOf(
Lang.KO to "변경사항이 없습니다.",
Lang.EN to "No changes to update.",
Lang.JA to "変更事項がありません。"
)
)
private val adminContentThemeMessages = mapOf(
"admin.content.theme.already_registered" to mapOf(
Lang.KO to "이미 등록된 테마 입니다.",
Lang.EN to "Theme already registered.",
Lang.JA to "既に登録されたテーマです。"
)
)
fun getMessage(key: String, lang: Lang): String? {
val messageGroups = listOf(
commonMessages,
@@ -317,7 +403,14 @@ class SodaMessageSource {
adminChatCharacterMessages,
adminChatCurationMessages,
adminChatCharacterImageMessages,
adminChatOriginalWorkMessages
adminChatOriginalWorkMessages,
adminContentMessages,
adminContentBannerMessages,
adminHashTagCurationMessages,
adminContentSeriesMessages,
adminContentSeriesBannerMessages,
adminContentSeriesGenreMessages,
adminContentThemeMessages
)
for (messages in messageGroups) {
val translations = messages[key] ?: continue