diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt index 68a292f..63ae72b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt @@ -23,6 +23,7 @@ import kr.co.vividnext.sodalive.chat.character.translate.TranslatedAiCharacterPe import kr.co.vividnext.sodalive.chat.room.service.ChatRoomService 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.translation.PapagoTranslationService import kr.co.vividnext.sodalive.i18n.translation.TranslateRequest import kr.co.vividnext.sodalive.member.Member @@ -34,7 +35,6 @@ import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController -import kotlin.collections.map @RestController @RequestMapping("/api/chat/character") @@ -48,12 +48,13 @@ class ChatCharacterController( private val translationService: PapagoTranslationService, private val aiCharacterTranslationRepository: AiCharacterTranslationRepository, + private val langContext: LangContext, + @Value("\${cloud.aws.cloud-front.host}") private val imageHost: String ) { @GetMapping("/main") fun getCharacterMain( - @RequestParam(required = false) languageCode: String? = null, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ): ApiResponse = run { // 배너 조회 (최대 10개) @@ -80,32 +81,19 @@ class ChatCharacterController( } } - /** - * 최근 대화한 캐릭터 이름 번역 데이터 조회 - * - * languageCode != null - * aiCharacterTranslationRepository 이용해 번역 콘텐츠를 조회한다. - characterId, locale - * - * 한 번에 조회하고 characterId 매핑하여 recentCharacters 캐릭터 이름을 번역 데이터로 변경한다 - */ - val translatedRecentCharacters = if (!languageCode.isNullOrBlank()) { - val characterIds = recentCharacters.map { it.characterId } + val characterIds = recentCharacters.map { it.characterId } + val translatedRecentCharacters = if (characterIds.isNotEmpty()) { + val translations = aiCharacterTranslationRepository + .findByCharacterIdInAndLocale(characterIds = characterIds, locale = langContext.lang.code) + .associateBy { it.characterId } - if (characterIds.isNotEmpty()) { - val translations = aiCharacterTranslationRepository - .findByCharacterIdInAndLocale(characterIds = characterIds, locale = languageCode) - .associateBy { it.characterId } - - recentCharacters.map { character -> - val translatedName = translations[character.characterId]?.renderedPayload?.name - if (translatedName.isNullOrBlank()) { - character - } else { - character.copy(name = translatedName) - } + recentCharacters.map { character -> + val translatedName = translations[character.characterId]?.renderedPayload?.name + if (translatedName.isNullOrBlank()) { + character + } else { + character.copy(name = translatedName) } - } else { - recentCharacters } } else { recentCharacters @@ -114,76 +102,12 @@ class ChatCharacterController( // 인기 캐릭터 조회 val popularCharacters = service.getPopularCharacters() - /** - * popularCharacters 캐릭터 이름과 description 번역 데이터 조회 - * - * languageCode != null - * aiCharacterTranslationRepository 이용해 번역 콘텐츠를 조회한다. - characterId, locale - * - * 한 번에 조회하고 characterId 매핑하여 popularCharacters의 캐릭터 이름과 description을 번역 데이터로 변경한다 - */ - val translatedPopularCharacters = if (!languageCode.isNullOrBlank()) { - val characterIds = popularCharacters.map { it.characterId } - - if (characterIds.isNotEmpty()) { - val translations = aiCharacterTranslationRepository - .findByCharacterIdInAndLocale(characterIds = characterIds, locale = languageCode) - .associateBy { it.characterId } - - popularCharacters.map { character -> - val translatedName = translations[character.characterId]?.renderedPayload?.name - val translatedDesc = translations[character.characterId]?.renderedPayload?.description - if (translatedName.isNullOrBlank() || translatedDesc.isNullOrBlank()) { - character - } else { - character.copy(name = translatedName, description = translatedDesc) - } - } - } else { - popularCharacters - } - } else { - popularCharacters - } - // 최근 등록된 캐릭터 리스트 조회 val newCharacters = service.getRecentCharactersPage( page = 0, size = 50 ).content - /** - * 최근 등록된 캐릭터 이름 번역 데이터 조회 - * - * languageCode != null - * aiCharacterTranslationRepository 이용해 번역 콘텐츠를 조회한다. - characterId, locale - * - * 한 번에 조회하고 characterId 매핑하여 newCharacters 캐릭터 이름과 description을 번역 데이터로 변경한다 - */ - val translatedNewCharacters = if (!languageCode.isNullOrBlank()) { - val characterIds = newCharacters.map { it.characterId } - - if (characterIds.isNotEmpty()) { - val translations = aiCharacterTranslationRepository - .findByCharacterIdInAndLocale(characterIds = characterIds, locale = languageCode) - .associateBy { it.characterId } - - newCharacters.map { character -> - val translatedName = translations[character.characterId]?.renderedPayload?.name - val translatedDesc = translations[character.characterId]?.renderedPayload?.description - if (translatedName.isNullOrBlank() || translatedDesc.isNullOrBlank()) { - character - } else { - character.copy(name = translatedName, description = translatedDesc) - } - } - } else { - newCharacters - } - } else { - newCharacters - } - // 추천 캐릭터 조회 // 최근 대화한 캐릭터를 제외한 랜덤 30개 조회 // Controller에서는 호출만 @@ -191,38 +115,6 @@ class ChatCharacterController( val excludeIds = recentCharacters.map { it.characterId } val recommendCharacters = service.getRecommendCharacters(excludeIds, 30) - /** - * 추천 캐릭터 이름 번역 데이터 조회 - * - * languageCode != null - * aiCharacterTranslationRepository 이용해 번역 콘텐츠를 조회한다. - characterId, locale - * - * 한 번에 조회하고 characterId 매핑하여 recommendCharacters 캐릭터 이름과 description을 번역 데이터로 변경한다 - */ - val translatedRecommendCharacters = if (!languageCode.isNullOrBlank()) { - val characterIds = recommendCharacters.map { it.characterId } - - if (characterIds.isNotEmpty()) { - val translations = aiCharacterTranslationRepository - .findByCharacterIdInAndLocale(characterIds = characterIds, locale = languageCode) - .associateBy { it.characterId } - - recommendCharacters.map { character -> - val translatedName = translations[character.characterId]?.renderedPayload?.name - val translatedDesc = translations[character.characterId]?.renderedPayload?.description - if (translatedName.isNullOrBlank() || translatedDesc.isNullOrBlank()) { - character - } else { - character.copy(name = translatedName, description = translatedDesc) - } - } - } else { - recommendCharacters - } - } else { - recommendCharacters - } - // 큐레이션 섹션 (활성화된 큐레이션 + 캐릭터) val curationSections = curationQueryService.getActiveCurationsWithCharacters() .map { agg -> @@ -246,9 +138,9 @@ class ChatCharacterController( CharacterMainResponse( banners = banners, recentCharacters = translatedRecentCharacters, - popularCharacters = translatedPopularCharacters, - newCharacters = translatedNewCharacters, - recommendCharacters = translatedRecommendCharacters, + popularCharacters = getTranslatedAiCharacterList(popularCharacters), + newCharacters = getTranslatedAiCharacterList(newCharacters), + recommendCharacters = getTranslatedAiCharacterList(recommendCharacters), curationSections = curationSections ) ) @@ -261,7 +153,6 @@ class ChatCharacterController( @GetMapping("/{characterId}") fun getCharacterDetail( @PathVariable characterId: Long, - @RequestParam(required = false, defaultValue = "ko") languageCode: String? = "ko", @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { if (member == null) throw SodaException("로그인 정보를 확인해주세요.") @@ -292,11 +183,9 @@ class ChatCharacterController( } var translated: TranslatedAiCharacterDetail? = null - if (!languageCode.isNullOrBlank() && languageCode != character.languageCode) { - val locale = languageCode.lowercase() - + if (langContext.lang.code != character.languageCode) { val existing = aiCharacterTranslationRepository - .findByCharacterIdAndLocale(character.id!!, locale) + .findByCharacterIdAndLocale(character.id!!, langContext.lang.code) if (existing != null) { val payload = existing.renderedPayload @@ -344,7 +233,7 @@ class ChatCharacterController( request = TranslateRequest( texts = texts, sourceLanguage = sourceLanguage, - targetLanguage = locale + targetLanguage = langContext.lang.code ) ) @@ -387,7 +276,7 @@ class ChatCharacterController( val entity = AiCharacterTranslation( characterId = character.id!!, - locale = locale, + locale = langContext.lang.code, renderedPayload = payload ) @@ -427,27 +316,22 @@ class ChatCharacterController( * * 한 번에 조회하고 characterId 매핑하여 others 캐릭터 이름과 tags 번역 데이터로 변경한다 */ - val translatedOthers = if (!languageCode.isNullOrBlank()) { - val characterIds = others.map { it.characterId } + val characterIds = others.map { it.characterId } + val translatedOthers = if (characterIds.isNotEmpty()) { + val translations = aiCharacterTranslationRepository + .findByCharacterIdInAndLocale(characterIds = characterIds, locale = langContext.lang.code) + .associateBy { it.characterId } - if (characterIds.isNotEmpty()) { - val translations = aiCharacterTranslationRepository - .findByCharacterIdInAndLocale(characterIds = characterIds, locale = languageCode) - .associateBy { it.characterId } + others.map { other -> + val payload = translations[other.characterId]?.renderedPayload + val translatedName = payload?.name + val translatedTags = payload?.tags - others.map { other -> - val payload = translations[other.characterId]?.renderedPayload - val translatedName = payload?.name - val translatedTags = payload?.tags - - if (translatedName.isNullOrBlank() || translatedTags.isNullOrBlank()) { - other - } else { - other.copy(name = translatedName, tags = translatedTags) - } + if (translatedName.isNullOrBlank() || translatedTags.isNullOrBlank()) { + other + } else { + other.copy(name = translatedName, tags = translatedTags) } - } else { - others } } else { others @@ -488,7 +372,6 @@ class ChatCharacterController( */ @GetMapping("/recent") fun getRecentCharacters( - @RequestParam(required = false) languageCode: String? = null, @RequestParam("page", required = false) page: Int? ): ApiResponse = run { val characterPage = service.getRecentCharactersPage( @@ -496,41 +379,9 @@ class ChatCharacterController( size = 20 ) - /** - * 최근 등록된 캐릭터 이름 번역 데이터 조회 - * - * languageCode != null - * aiCharacterTranslationRepository 이용해 번역 콘텐츠를 조회한다. - characterId, locale - * - * 한 번에 조회하고 characterId 매핑하여 characterList 캐릭터 이름과 description을 번역 데이터로 변경한다 - */ - val translatedContent = if (!languageCode.isNullOrBlank()) { - val characterIds = characterPage.content.map { it.characterId } - - if (characterIds.isNotEmpty()) { - val translations = aiCharacterTranslationRepository - .findByCharacterIdInAndLocale(characterIds = characterIds, locale = languageCode) - .associateBy { it.characterId } - - characterPage.content.map { character -> - val translatedName = translations[character.characterId]?.renderedPayload?.name - val translatedDesc = translations[character.characterId]?.renderedPayload?.description - if (translatedName.isNullOrBlank() || translatedDesc.isNullOrBlank()) { - character - } else { - character.copy(name = translatedName, description = translatedDesc) - } - } - } else { - characterPage.content - } - } else { - characterPage.content - } - val translatedCharacterPage = RecentCharactersResponse( totalCount = characterPage.totalCount, - content = translatedContent + content = getTranslatedAiCharacterList(characterPage.content) ) ApiResponse.ok(translatedCharacterPage) @@ -543,7 +394,6 @@ class ChatCharacterController( */ @GetMapping("/recommend") fun getRecommendCharacters( - @RequestParam(required = false) languageCode: String? = null, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? ) = run { val recent = if (member == null || member.auth == null) { @@ -553,40 +403,48 @@ class ChatCharacterController( .listMyChatRooms(member, 0, 50) // 최근 기록은 최대 50개까지만 제외 대상으로 고려 .map { it.characterId } } - val characterList = service.getRecommendCharacters(recent, 20) - /** - * 추천 캐릭터 이름 번역 데이터 조회 - * - * languageCode != null - * aiCharacterTranslationRepository 이용해 번역 콘텐츠를 조회한다. - characterId, locale - * - * 한 번에 조회하고 characterId 매핑하여 characterList 캐릭터 이름과 description을 번역 데이터로 변경한다 - */ - val translatedCharacterList = if (!languageCode.isNullOrBlank()) { - val characterIds = characterList.map { it.characterId } + ApiResponse.ok( + getTranslatedAiCharacterList( + service.getRecommendCharacters( + recent, + 20 + ) + ) + ) + } - if (characterIds.isNotEmpty()) { - val translations = aiCharacterTranslationRepository - .findByCharacterIdInAndLocale(characterIds = characterIds, locale = languageCode) - .associateBy { it.characterId } + /** + * AI 캐릭터 리스트의 이름/설명을 현재 언어(locale)에 맞춰 일괄 번역한다. + * + * 처리 절차: + * - characterId 목록을 추출하고, 요청 언어 코드로 aiCharacterTranslationRepository에서 + * 번역 데이터를 한 번에 조회한다. + * - 각 캐릭터에 대해 name과 description 모두 번역 값이 존재하고 비어있지 않을 때에만 + * 해당 필드를 교체한다. 둘 중 하나라도 없으면 원본 캐릭터를 그대로 유지한다. + * + * @param aiCharacterList 번역 대상 캐릭터 목록 + * @return 가능한 경우 name/description이 번역된 캐릭터 목록, 그 외는 원본 유지 + */ + private fun getTranslatedAiCharacterList(aiCharacterList: List): List { + val characterIds = aiCharacterList.map { it.characterId } - characterList.map { character -> - val translatedName = translations[character.characterId]?.renderedPayload?.name - val translatedDesc = translations[character.characterId]?.renderedPayload?.description - if (translatedName.isNullOrBlank() || translatedDesc.isNullOrBlank()) { - character - } else { - character.copy(name = translatedName, description = translatedDesc) - } + return if (characterIds.isNotEmpty()) { + val translations = aiCharacterTranslationRepository + .findByCharacterIdInAndLocale(characterIds = characterIds, locale = langContext.lang.code) + .associateBy { it.characterId } + + aiCharacterList.map { character -> + val translatedName = translations[character.characterId]?.renderedPayload?.name + val translatedDesc = translations[character.characterId]?.renderedPayload?.description + if (translatedName.isNullOrBlank() || translatedDesc.isNullOrBlank()) { + character + } else { + character.copy(name = translatedName, description = translatedDesc) } - } else { - characterList } } else { - characterList + aiCharacterList } - - ApiResponse.ok(translatedCharacterList) } }