From 31242a1f7684e508a9726bbb545d005c06d295c2 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 19 Dec 2025 02:32:20 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=EC=97=90=20=EC=96=B8?= =?UTF-8?q?=EC=96=B4=EB=B3=84=20=EB=B2=88=EC=97=AD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LangContext에 따라 카테고리명을 번역해 반환한다. 번역본이 없으면 Papago API로 번역 후 CategoryTranslation에 저장하고 즉시 결과를 반환한다. 공개 API의 getCategoryList 응답이 요청 로케일을 반영한다. --- .../content/category/CategoryService.kt | 82 ++++++++++++++++++- .../category/CategoryTranslationRepository.kt | 2 + 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/category/CategoryService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/category/CategoryService.kt index 5c91b2a..1fc1586 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/category/CategoryService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/category/CategoryService.kt @@ -4,8 +4,11 @@ import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.content.AudioContentRepository import kr.co.vividnext.sodalive.content.LanguageDetectEvent import kr.co.vividnext.sodalive.content.LanguageDetectTargetType +import kr.co.vividnext.sodalive.i18n.LangContext import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationEvent import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationTargetType +import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService +import kr.co.vividnext.sodalive.i18n.translation.TranslateRequest import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.block.BlockMemberRepository import org.springframework.context.ApplicationEventPublisher @@ -18,8 +21,11 @@ class CategoryService( private val contentRepository: AudioContentRepository, private val blockMemberRepository: BlockMemberRepository, private val categoryContentRepository: CategoryContentRepository, + private val categoryTranslationRepository: CategoryTranslationRepository, - private val applicationEventPublisher: ApplicationEventPublisher + private val langContext: LangContext, + private val applicationEventPublisher: ApplicationEventPublisher, + private val translationService: PapagoTranslationService ) { @Transactional fun createCategory(request: CreateCategoryRequest, member: Member) { @@ -119,11 +125,83 @@ class CategoryService( } } + @Transactional fun getCategoryList(creatorId: Long, memberId: Long): List { val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = creatorId) if (isBlocked) throw SodaException("잘못된 접근입니다.") - return repository.findByCreatorId(creatorId = creatorId) + // 기본 카테고리 목록 조회 (원본 언어 기준) + val baseList = repository.findByCreatorId(creatorId = creatorId) + if (baseList.isEmpty()) return baseList + + val locale = langContext.lang.code + + // 원본 엔티티를 조회하여 languageCode 파악 + val categoryIds = baseList.map { it.categoryId } + val entities = repository.findAllById(categoryIds) + val entityMap = entities.associateBy { it.id!! } + + // 요청 로케일로 이미 저장된 번역 일괄 조회 + val translations = categoryTranslationRepository + .findByCategoryIdInAndLocale(categoryIds, locale) + .associateBy { it.categoryId } + + // 각 항목에 대해 번역 적용. 없으면 Papago로 번역 저장 후 적용 + val result = mutableListOf() + for (item in baseList) { + val entity = entityMap[item.categoryId] + if (entity == null) { + result.add(item) + continue + } + + val sourceLang = entity.languageCode + if (!sourceLang.isNullOrBlank() && sourceLang != locale) { + val existing = translations[item.categoryId] + if (existing != null && !existing.category.isNullOrBlank()) { + result.add(GetCategoryListResponse(categoryId = item.categoryId, category = existing.category)) + continue + } + + // 번역본이 없으면 Papago 번역 후 저장 + val texts = listOf(entity.title) + val response = translationService.translate( + request = TranslateRequest( + texts = texts, + sourceLanguage = sourceLang, + targetLanguage = locale + ) + ) + + val translatedTexts = response.translatedText + if (translatedTexts.size == texts.size) { + val translatedCategory = translatedTexts[0] + + val existingOne = categoryTranslationRepository + .findByCategoryIdAndLocale(entity.id!!, locale) + if (existingOne == null) { + categoryTranslationRepository.save( + CategoryTranslation( + categoryId = entity.id!!, + locale = locale, + category = translatedCategory + ) + ) + } else { + existingOne.category = translatedCategory + categoryTranslationRepository.save(existingOne) + } + + result.add(GetCategoryListResponse(categoryId = item.categoryId, category = translatedCategory)) + continue + } + } + + // 번역이 필요 없거나 실패한 경우 원본 사용 + result.add(item) + } + + return result } private fun validateTitle(title: String) { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/category/CategoryTranslationRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/category/CategoryTranslationRepository.kt index 92b8839..a99f61d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/category/CategoryTranslationRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/category/CategoryTranslationRepository.kt @@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository interface CategoryTranslationRepository : JpaRepository { fun findByCategoryIdAndLocale(categoryId: Long, locale: String): CategoryTranslation? + + fun findByCategoryIdInAndLocale(categoryIds: Collection, locale: String): List }