From 280b21c3cb687063ac26268d9660c0e5b6c0eca0 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 22 Dec 2025 22:30:05 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=8B=A4=EA=B5=AD?= =?UTF-8?q?=EC=96=B4=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/chat/AdminChatBannerController.kt | 14 +- .../calculate/AdminChatCalculateService.kt | 6 +- .../character/AdminChatCharacterController.kt | 28 +-- .../CharacterCurationAdminController.kt | 2 +- .../curation/CharacterCurationAdminService.kt | 24 +-- .../image/AdminCharacterImageController.kt | 20 ++- .../service/AdminChatCharacterService.kt | 2 +- .../original/AdminOriginalWorkController.kt | 4 +- .../service/AdminOriginalWorkService.kt | 20 +-- .../sodalive/i18n/SodaMessageSource.kt | 161 +++++++++++++++++- 10 files changed, 231 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/AdminChatBannerController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/AdminChatBannerController.kt index 968bc61e..086ab3ed 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/AdminChatBannerController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/AdminChatBannerController.kt @@ -13,6 +13,8 @@ import kr.co.vividnext.sodalive.aws.s3.S3Uploader import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterBannerService import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException +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.security.access.prepost.PreAuthorize @@ -35,6 +37,8 @@ class AdminChatBannerController( private val bannerService: ChatCharacterBannerService, private val adminCharacterService: AdminChatCharacterService, private val s3Uploader: S3Uploader, + private val langContext: LangContext, + private val messageSource: SodaMessageSource, @Value("\${cloud.aws.s3.bucket}") private val s3Bucket: String, @@ -158,8 +162,8 @@ class AdminChatBannerController( filePath = "characters/banners/$bannerId/$fileName", metadata = metadata ) - } catch (e: Exception) { - throw SodaException("이미지 저장에 실패했습니다: ${e.message}") + } catch (_: Exception) { + throw SodaException(messageKey = "admin.chat.banner.image_save_failed") } } @@ -208,7 +212,8 @@ class AdminChatBannerController( fun deleteBanner(@PathVariable bannerId: Long) = run { bannerService.deleteBanner(bannerId) - ApiResponse.ok("배너가 성공적으로 삭제되었습니다.") + val message = messageSource.getMessage("admin.chat.banner.delete_success", langContext.lang) + ApiResponse.ok(message) } /** @@ -224,6 +229,7 @@ class AdminChatBannerController( ) = run { bannerService.updateBannerOrders(request.ids) - ApiResponse.ok(null, "배너 정렬 순서가 성공적으로 변경되었습니다.") + val message = messageSource.getMessage("admin.chat.banner.reorder_success", langContext.lang) + ApiResponse.ok(null, message) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/calculate/AdminChatCalculateService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/calculate/AdminChatCalculateService.kt index 34593e84..d8af09ca 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/calculate/AdminChatCalculateService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/calculate/AdminChatCalculateService.kt @@ -29,13 +29,13 @@ class AdminChatCalculateService( val todayKst = LocalDate.now(kstZone) if (endDate.isAfter(todayKst)) { - throw SodaException("끝 날짜는 오늘 날짜까지만 입력 가능합니다.") + throw SodaException(messageKey = "admin.chat.calculate.end_date_max_today") } if (startDate.isAfter(endDate)) { - throw SodaException("시작 날짜는 끝 날짜보다 이후일 수 없습니다.") + throw SodaException(messageKey = "admin.chat.calculate.start_date_after_end") } if (endDate.isAfter(startDate.plusMonths(6))) { - throw SodaException("조회 가능 기간은 최대 6개월입니다.") + throw SodaException(messageKey = "admin.chat.calculate.max_period_6_months") } val startUtc = startDateStr.convertLocalDateTime() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt index 85c42988..6c3143a5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt @@ -124,7 +124,7 @@ class AdminChatCharacterController( // 외부 API 호출 전 DB에 동일한 이름이 있는지 조회 val existingCharacter = service.findByName(request.name) if (existingCharacter != null) { - throw SodaException("동일한 이름은 등록이 불가능합니다: ${request.name}") + throw SodaException(messageKey = "admin.chat.character.duplicate_name") } // 1. 외부 API 호출 @@ -233,14 +233,18 @@ class AdminChatCharacterController( // success가 false이면 throw if (!apiResponse.success) { - throw SodaException(apiResponse.message ?: "등록에 실패했습니다. 다시 시도해 주세요.") + val apiMessage = apiResponse.message + if (apiMessage.isNullOrBlank()) { + throw SodaException(messageKey = "admin.chat.character.register_failed_retry") + } + throw SodaException(apiMessage) } // success가 true이면 data.id 반환 - return apiResponse.data?.id ?: throw SodaException("등록에 실패했습니다. 응답에 ID가 없습니다.") + return apiResponse.data?.id ?: throw SodaException(messageKey = "admin.chat.character.register_failed_no_id") } catch (e: Exception) { e.printStackTrace() - throw SodaException("${e.message}, 등록에 실패했습니다. 다시 시도해 주세요.") + throw SodaException(messageKey = "admin.chat.character.register_failed_retry") } } @@ -257,7 +261,7 @@ class AdminChatCharacterController( metadata = metadata ) } catch (e: Exception) { - throw SodaException("이미지 저장에 실패했습니다: ${e.message}") + throw SodaException(messageKey = "admin.chat.character.image_save_failed") } } @@ -297,19 +301,19 @@ class AdminChatCharacterController( request.originalWorkId != null if (!hasChangedData && !hasImage && !hasDbOnlyChanges) { - throw SodaException("변경된 데이터가 없습니다.") + throw SodaException(messageKey = "admin.chat.character.no_changes") } // 외부 API로 전달할 변경이 있을 때만 외부 API 호출(3가지 필드만 변경된 경우는 호출하지 않음) if (hasChangedData) { val chatCharacter = service.findById(request.id) - ?: throw SodaException("해당 ID의 캐릭터를 찾을 수 없습니다: ${request.id}") + ?: throw SodaException(messageKey = "admin.chat.character.not_found") // 이름이 수정된 경우 DB에 동일한 이름이 있는지 확인 if (request.name != null && request.name != chatCharacter.name) { val existingCharacter = service.findByName(request.name) if (existingCharacter != null) { - throw SodaException("동일한 이름은 등록이 불가능합니다: ${request.name}") + throw SodaException(messageKey = "admin.chat.character.duplicate_name") } } @@ -438,11 +442,15 @@ class AdminChatCharacterController( // success가 false이면 throw if (!apiResponse.success) { - throw SodaException(apiResponse.message ?: "수정에 실패했습니다. 다시 시도해 주세요.") + val apiMessage = apiResponse.message + if (apiMessage.isNullOrBlank()) { + throw SodaException(messageKey = "admin.chat.character.update_failed_retry") + } + throw SodaException(apiMessage) } } catch (e: Exception) { e.printStackTrace() - throw SodaException("${e.message} 수정에 실패했습니다. 다시 시도해 주세요.") + throw SodaException(messageKey = "admin.chat.character.update_failed_retry") } } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/curation/CharacterCurationAdminController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/curation/CharacterCurationAdminController.kt index f67002e3..b4b0c55c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/curation/CharacterCurationAdminController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/curation/CharacterCurationAdminController.kt @@ -63,7 +63,7 @@ class CharacterCurationAdminController( @RequestBody request: CharacterCurationAddCharacterRequest ): ApiResponse { val ids = request.characterIds.filter { it > 0 }.distinct() - if (ids.isEmpty()) throw SodaException("등록할 캐릭터 ID 리스트가 비어있습니다") + if (ids.isEmpty()) throw SodaException(messageKey = "admin.chat.curation.character_ids_empty") service.addCharacters(curationId, ids) return ApiResponse.ok(true) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/curation/CharacterCurationAdminService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/curation/CharacterCurationAdminService.kt index 77da8f6d..d16f77b0 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/curation/CharacterCurationAdminService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/curation/CharacterCurationAdminService.kt @@ -32,7 +32,7 @@ class CharacterCurationAdminService( @Transactional fun update(request: CharacterCurationUpdateRequest): CharacterCuration { val curation = curationRepository.findById(request.id) - .orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: ${request.id}") } + .orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") } request.title?.let { curation.title = it } request.isAdult?.let { curation.isAdult = it } @@ -44,7 +44,7 @@ class CharacterCurationAdminService( @Transactional fun softDelete(curationId: Long) { val curation = curationRepository.findById(curationId) - .orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") } + .orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") } curation.isActive = false curationRepository.save(curation) } @@ -53,7 +53,7 @@ class CharacterCurationAdminService( fun reorder(ids: List) { ids.forEachIndexed { index, id -> val curation = curationRepository.findById(id) - .orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $id") } + .orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") } curation.sortOrder = index + 1 curationRepository.save(curation) } @@ -61,14 +61,14 @@ class CharacterCurationAdminService( @Transactional fun addCharacters(curationId: Long, characterIds: List) { - if (characterIds.isEmpty()) throw SodaException("등록할 캐릭터 ID 리스트가 비어있습니다") + if (characterIds.isEmpty()) throw SodaException(messageKey = "admin.chat.curation.character_ids_empty") val curation = curationRepository.findById(curationId) - .orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") } - if (!curation.isActive) throw SodaException("비활성화된 큐레이션입니다: $curationId") + .orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") } + if (!curation.isActive) throw SodaException(messageKey = "admin.chat.curation.inactive") val uniqueIds = characterIds.filter { it > 0 }.distinct() - if (uniqueIds.isEmpty()) throw SodaException("유효한 캐릭터 ID가 없습니다") + if (uniqueIds.isEmpty()) throw SodaException(messageKey = "admin.chat.curation.invalid_character_ids") // 활성 캐릭터만 조회 (조회 단계에서 검증 포함) val characters = characterRepository.findByIdInAndIsActiveTrue(uniqueIds) @@ -101,23 +101,23 @@ class CharacterCurationAdminService( @Transactional fun removeCharacter(curationId: Long, characterId: Long) { val curation = curationRepository.findById(curationId) - .orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") } + .orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") } val mappings = mappingRepository.findByCuration(curation) val target = mappings.firstOrNull { it.chatCharacter.id == characterId } - ?: throw SodaException("매핑을 찾을 수 없습니다: curation=$curationId, character=$characterId") + ?: throw SodaException(messageKey = "admin.chat.curation.mapping_not_found") mappingRepository.delete(target) } @Transactional fun reorderCharacters(curationId: Long, characterIds: List) { val curation = curationRepository.findById(curationId) - .orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") } + .orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") } val mappings = mappingRepository.findByCuration(curation) val mappingByCharacterId = mappings.associateBy { it.chatCharacter.id } characterIds.forEachIndexed { index, cid -> val mapping = mappingByCharacterId[cid] - ?: throw SodaException("큐레이션에 포함되지 않은 캐릭터입니다: $cid") + ?: throw SodaException(messageKey = "admin.chat.curation.character_not_in_curation") mapping.sortOrder = index + 1 mappingRepository.save(mapping) } @@ -146,7 +146,7 @@ class CharacterCurationAdminService( @Transactional(readOnly = true) fun listCharacters(curationId: Long): List { val curation = curationRepository.findById(curationId) - .orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") } + .orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") } val mappings = mappingRepository.findByCurationWithCharacterOrderBySortOrderAsc(curation) return mappings.map { it.chatCharacter } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/image/AdminCharacterImageController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/image/AdminCharacterImageController.kt index 7a8e1892..219f1da9 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/image/AdminCharacterImageController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/image/AdminCharacterImageController.kt @@ -11,6 +11,8 @@ import kr.co.vividnext.sodalive.aws.s3.S3Uploader import kr.co.vividnext.sodalive.chat.character.image.CharacterImageService import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.i18n.LangContext +import kr.co.vividnext.sodalive.i18n.SodaMessageSource import kr.co.vividnext.sodalive.utils.ImageBlurUtil import kr.co.vividnext.sodalive.utils.generateFileName import org.springframework.beans.factory.annotation.Value @@ -34,6 +36,8 @@ class AdminCharacterImageController( private val imageService: CharacterImageService, private val s3Uploader: S3Uploader, private val imageCloudFront: ImageContentCloudFront, + private val langContext: LangContext, + private val messageSource: SodaMessageSource, @Value("\${cloud.aws.s3.content-bucket}") private val s3Bucket: String, @@ -106,14 +110,18 @@ class AdminCharacterImageController( @DeleteMapping("/{imageId}") fun delete(@PathVariable imageId: Long) = run { imageService.deleteImage(imageId) - ApiResponse.ok(null, "이미지가 삭제되었습니다.") + val message = messageSource.getMessage("admin.chat.character.image_deleted", langContext.lang) + ApiResponse.ok(null, message) } @PutMapping("/orders") fun updateOrders(@RequestBody request: UpdateCharacterImageOrdersRequest) = run { - if (request.characterId == null) throw SodaException("characterId는 필수입니다") + if (request.characterId == null) { + throw SodaException(messageKey = "admin.chat.character.character_id_required") + } imageService.updateOrders(request.characterId, request.ids) - ApiResponse.ok(null, "정렬 순서가 변경되었습니다.") + val message = messageSource.getMessage("admin.chat.character.order_updated", langContext.lang) + ApiResponse.ok(null, message) } private fun buildS3Key(characterId: Long): String { @@ -132,7 +140,7 @@ class AdminCharacterImageController( metadata = metadata ) } catch (e: Exception) { - throw SodaException("이미지 저장에 실패했습니다: ${e.message}") + throw SodaException(messageKey = "admin.chat.character.image_save_failed") } } @@ -141,7 +149,7 @@ class AdminCharacterImageController( // 멀티파트를 BufferedImage로 읽기 val bytes = image.bytes val bimg = javax.imageio.ImageIO.read(java.io.ByteArrayInputStream(bytes)) - ?: throw SodaException("이미지 포맷을 인식할 수 없습니다.") + ?: throw SodaException(messageKey = "admin.chat.character.image_format_invalid") val blurred = ImageBlurUtil.blurFast(bimg) // PNG로 저장(알파 유지), JPEG 업로드가 필요하면 포맷 변경 가능 @@ -164,7 +172,7 @@ class AdminCharacterImageController( metadata = metadata ) } catch (e: Exception) { - throw SodaException("블러 이미지 저장에 실패했습니다: ${e.message}") + throw SodaException(messageKey = "admin.chat.character.blur_image_save_failed") } } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/service/AdminChatCharacterService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/service/AdminChatCharacterService.kt index 36633694..5fcdc353 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/service/AdminChatCharacterService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/service/AdminChatCharacterService.kt @@ -58,7 +58,7 @@ class AdminChatCharacterService( @Transactional(readOnly = true) fun getChatCharacterDetail(characterId: Long, imageHost: String = ""): ChatCharacterDetailResponse { val chatCharacter = chatCharacterRepository.findById(characterId) - .orElseThrow { SodaException("해당 ID의 캐릭터를 찾을 수 없습니다: $characterId") } + .orElseThrow { SodaException(messageKey = "admin.chat.character.not_found") } return ChatCharacterDetailResponse.from(chatCharacter, imageHost) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/AdminOriginalWorkController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/AdminOriginalWorkController.kt index 95365dd6..6b13b8c8 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/AdminOriginalWorkController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/AdminOriginalWorkController.kt @@ -192,8 +192,8 @@ class AdminOriginalWorkController( filePath = "originals/$originalWorkId/${generateFileName(prefix = "original")}", metadata = metadata ) - } catch (e: Exception) { - throw SodaException("이미지 저장에 실패했습니다: ${e.message}") + } catch (_: Exception) { + throw SodaException(messageKey = "admin.chat.original.image_save_failed") } } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/service/AdminOriginalWorkService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/service/AdminOriginalWorkService.kt index 22b67b6b..c181f33e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/service/AdminOriginalWorkService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/service/AdminOriginalWorkService.kt @@ -38,7 +38,7 @@ class AdminOriginalWorkService( @Transactional fun createOriginalWork(request: OriginalWorkRegisterRequest): OriginalWork { originalWorkRepository.findByTitleAndIsDeletedFalse(request.title)?.let { - throw SodaException("동일한 제목의 원작이 이미 존재합니다: ${request.title}") + throw SodaException(messageKey = "admin.chat.original.duplicate_title") } val entity = OriginalWork( title = request.title, @@ -107,7 +107,7 @@ class AdminOriginalWorkService( @Transactional fun updateOriginalWork(request: OriginalWorkUpdateRequest, imagePath: String? = null): OriginalWork { val ow = originalWorkRepository.findByIdAndIsDeletedFalse(request.id) - .orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") } + .orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") } request.title?.let { ow.title = it } request.contentType?.let { ow.contentType = it } @@ -177,7 +177,7 @@ class AdminOriginalWorkService( @Transactional fun updateOriginalWorkImage(originalWorkId: Long, imagePath: String): OriginalWork { val ow = originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId) - .orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") } + .orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") } ow.imagePath = imagePath return originalWorkRepository.save(ow) } @@ -186,7 +186,7 @@ class AdminOriginalWorkService( @Transactional fun deleteOriginalWork(id: Long) { val ow = originalWorkRepository.findByIdAndIsDeletedFalse(id) - .orElseThrow { SodaException("해당 원작을 찾을 수 없습니다: $id") } + .orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") } ow.isDeleted = true originalWorkRepository.save(ow) } @@ -195,7 +195,7 @@ class AdminOriginalWorkService( @Transactional(readOnly = true) fun getOriginalWork(id: Long): OriginalWork { return originalWorkRepository.findByIdAndIsDeletedFalse(id) - .orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") } + .orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") } } /** 원작 페이징 조회 */ @@ -216,7 +216,7 @@ class AdminOriginalWorkService( fun getCharactersOfOriginalWorkPage(originalWorkId: Long, page: Int, size: Int): Page { // 원작 존재 및 소프트 삭제 여부 확인 originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId) - .orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") } + .orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") } val safePage = if (page < 0) 0 else page val safeSize = when { @@ -238,7 +238,7 @@ class AdminOriginalWorkService( @Transactional fun assignCharacters(originalWorkId: Long, characterIds: List) { val ow = originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId) - .orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") } + .orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") } if (characterIds.isEmpty()) return val characters = chatCharacterRepository.findByIdInAndIsActiveTrue(characterIds) characters.forEach { it.originalWork = ow } @@ -250,7 +250,7 @@ class AdminOriginalWorkService( fun unassignCharacters(originalWorkId: Long, characterIds: List) { // 원작 존재 확인 (소프트 삭제 제외) originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId) - .orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") } + .orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") } if (characterIds.isEmpty()) return val characters = chatCharacterRepository.findByIdInAndIsActiveTrue(characterIds) characters.forEach { it.originalWork = null } @@ -261,13 +261,13 @@ class AdminOriginalWorkService( @Transactional fun assignOneCharacter(originalWorkId: Long, characterId: Long) { val character = chatCharacterRepository.findById(characterId) - .orElseThrow { SodaException("해당 캐릭터를 찾을 수 없습니다") } + .orElseThrow { SodaException(messageKey = "admin.chat.character.not_found") } if (originalWorkId == 0L) { character.originalWork = null } else { val ow = originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId) - .orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") } + .orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") } character.originalWork = ow } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt index 135fc869..8d2ad780 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt @@ -150,6 +150,159 @@ class SodaMessageSource { ) ) + private val adminChatBannerMessages = mapOf( + "admin.chat.banner.image_save_failed" to mapOf( + Lang.KO to "이미지 저장에 실패했습니다.", + Lang.EN to "Failed to save image.", + Lang.JA to "画像の保存に失敗しました。" + ), + "admin.chat.banner.delete_success" to mapOf( + Lang.KO to "배너가 성공적으로 삭제되었습니다.", + Lang.EN to "Banner deleted successfully.", + Lang.JA to "バナーが削除されました。" + ), + "admin.chat.banner.reorder_success" to mapOf( + Lang.KO to "배너 정렬 순서가 성공적으로 변경되었습니다.", + Lang.EN to "Banner order updated successfully.", + Lang.JA to "バナーの並び順が変更されました。" + ) + ) + + private val adminChatCalculateMessages = mapOf( + "admin.chat.calculate.end_date_max_today" to mapOf( + Lang.KO to "끝 날짜는 오늘 날짜까지만 입력 가능합니다.", + Lang.EN to "End date can be at most today.", + Lang.JA to "終了日は本日まで指定できます。" + ), + "admin.chat.calculate.start_date_after_end" to mapOf( + Lang.KO to "시작 날짜는 끝 날짜보다 이후일 수 없습니다.", + Lang.EN to "Start date cannot be after end date.", + Lang.JA to "開始日は終了日より後にできません。" + ), + "admin.chat.calculate.max_period_6_months" to mapOf( + Lang.KO to "조회 가능 기간은 최대 6개월입니다.", + Lang.EN to "Maximum query period is 6 months.", + Lang.JA to "照会期間は最大6ヶ月です。" + ) + ) + + private val adminChatCharacterMessages = mapOf( + "admin.chat.character.duplicate_name" to mapOf( + Lang.KO to "동일한 이름은 등록이 불가능합니다.", + Lang.EN to "A character with the same name already exists.", + Lang.JA to "同じ名前は登録できません。" + ), + "admin.chat.character.register_failed_retry" to mapOf( + Lang.KO to "등록에 실패했습니다. 다시 시도해 주세요.", + Lang.EN to "Registration failed. Please try again.", + Lang.JA to "登録に失敗しました。もう一度お試しください。" + ), + "admin.chat.character.register_failed_no_id" to mapOf( + Lang.KO to "등록에 실패했습니다. 응답에 ID가 없습니다.", + Lang.EN to "Registration failed. No ID in response.", + Lang.JA to "登録に失敗しました。応答にIDがありません。" + ), + "admin.chat.character.image_save_failed" to mapOf( + Lang.KO to "이미지 저장에 실패했습니다.", + Lang.EN to "Failed to save image.", + Lang.JA to "画像の保存に失敗しました。" + ), + "admin.chat.character.no_changes" to mapOf( + Lang.KO to "변경된 데이터가 없습니다.", + Lang.EN to "No changes detected.", + Lang.JA to "変更されたデータがありません。" + ), + "admin.chat.character.not_found" to mapOf( + Lang.KO to "해당 캐릭터를 찾을 수 없습니다.", + Lang.EN to "Character not found.", + Lang.JA to "該当キャラクターが見つかりません。" + ), + "admin.chat.character.update_failed_retry" to mapOf( + Lang.KO to "수정에 실패했습니다. 다시 시도해 주세요.", + Lang.EN to "Update failed. Please try again.", + Lang.JA to "更新に失敗しました。もう一度お試しください。" + ) + ) + + private val adminChatCurationMessages = mapOf( + "admin.chat.curation.not_found" to mapOf( + Lang.KO to "큐레이션을 찾을 수 없습니다.", + Lang.EN to "Curation not found.", + Lang.JA to "キュレーションが見つかりません。" + ), + "admin.chat.curation.character_ids_empty" to mapOf( + Lang.KO to "등록할 캐릭터 ID 리스트가 비어있습니다", + Lang.EN to "Character ID list to register is empty.", + Lang.JA to "登録するキャラクターIDリストが空です。" + ), + "admin.chat.curation.inactive" to mapOf( + Lang.KO to "비활성화된 큐레이션입니다.", + Lang.EN to "Curation is inactive.", + Lang.JA to "無効化されたキュレーションです。" + ), + "admin.chat.curation.invalid_character_ids" to mapOf( + Lang.KO to "유효한 캐릭터 ID가 없습니다", + Lang.EN to "No valid character IDs.", + Lang.JA to "有効なキャラクターIDがありません。" + ), + "admin.chat.curation.mapping_not_found" to mapOf( + Lang.KO to "매핑을 찾을 수 없습니다.", + Lang.EN to "Mapping not found.", + Lang.JA to "マッピングが見つかりません。" + ), + "admin.chat.curation.character_not_in_curation" to mapOf( + Lang.KO to "큐레이션에 포함되지 않은 캐릭터입니다.", + Lang.EN to "Character not included in this curation.", + Lang.JA to "このキュレーションに含まれていないキャラクターです。" + ) + ) + + private val adminChatCharacterImageMessages = mapOf( + "admin.chat.character.image_deleted" to mapOf( + Lang.KO to "이미지가 삭제되었습니다.", + Lang.EN to "Image deleted.", + Lang.JA to "画像が削除されました。" + ), + "admin.chat.character.character_id_required" to mapOf( + Lang.KO to "characterId는 필수입니다", + Lang.EN to "characterId is required.", + Lang.JA to "characterIdは必須です。" + ), + "admin.chat.character.order_updated" to mapOf( + Lang.KO to "정렬 순서가 변경되었습니다.", + Lang.EN to "Order updated.", + Lang.JA to "並び順が変更されました。" + ), + "admin.chat.character.image_format_invalid" to mapOf( + Lang.KO to "이미지 포맷을 인식할 수 없습니다.", + Lang.EN to "Unsupported image format.", + Lang.JA to "画像形式を認識できません。" + ), + "admin.chat.character.blur_image_save_failed" to mapOf( + Lang.KO to "블러 이미지 저장에 실패했습니다.", + Lang.EN to "Failed to save blurred image.", + Lang.JA to "ぼかし画像の保存に失敗しました。" + ) + ) + + private val adminChatOriginalWorkMessages = mapOf( + "admin.chat.original.image_save_failed" to mapOf( + Lang.KO to "이미지 저장에 실패했습니다.", + Lang.EN to "Failed to save image.", + Lang.JA to "画像の保存に失敗しました。" + ), + "admin.chat.original.duplicate_title" to mapOf( + Lang.KO to "동일한 제목의 원작이 이미 존재합니다.", + Lang.EN to "An original work with the same title already exists.", + Lang.JA to "同じタイトルの原作が既に存在します。" + ), + "admin.chat.original.not_found" to mapOf( + Lang.KO to "해당 원작을 찾을 수 없습니다.", + Lang.EN to "Original work not found.", + Lang.JA to "該当の原作が見つかりません。" + ) + ) + fun getMessage(key: String, lang: Lang): String? { val messageGroups = listOf( commonMessages, @@ -158,7 +311,13 @@ class SodaMessageSource { auditionNotificationMessages, auditionRoleMessages, settlementRatioMessages, - adminCanMessages + adminCanMessages, + adminChatBannerMessages, + adminChatCalculateMessages, + adminChatCharacterMessages, + adminChatCurationMessages, + adminChatCharacterImageMessages, + adminChatOriginalWorkMessages ) for (messages in messageGroups) { val translations = messages[key] ?: continue