크리에이터 콘텐츠 카테고리 언어 감지 및 번역 기능 추가

This commit is contained in:
2025-12-19 01:08:56 +09:00
parent 67a8de9e7a
commit 68cfa201eb
6 changed files with 126 additions and 4 deletions

View File

@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.content
import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentRepository
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepository
import kr.co.vividnext.sodalive.chat.original.OriginalWorkRepository
import kr.co.vividnext.sodalive.content.category.CategoryRepository
import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository
import kr.co.vividnext.sodalive.content.series.ContentSeriesRepository
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheersRepository
@@ -34,7 +35,9 @@ enum class LanguageDetectTargetType {
CHARACTER_COMMENT,
CREATOR_CHEERS,
SERIES,
ORIGINAL_WORK
ORIGINAL_WORK,
CREATOR_CONTENT_CATEGORY
}
class LanguageDetectEvent(
@@ -56,6 +59,7 @@ class LanguageDetectListener(
private val creatorCheersRepository: CreatorCheersRepository,
private val seriesRepository: ContentSeriesRepository,
private val originalWorkRepository: OriginalWorkRepository,
private val categoryRepository: CategoryRepository,
private val applicationEventPublisher: ApplicationEventPublisher,
@@ -89,6 +93,7 @@ class LanguageDetectListener(
LanguageDetectTargetType.CREATOR_CHEERS -> handleCreatorCheersLanguageDetect(event)
LanguageDetectTargetType.SERIES -> handleSeriesLanguageDetect(event)
LanguageDetectTargetType.ORIGINAL_WORK -> handleOriginalWorkLanguageDetect(event)
LanguageDetectTargetType.CREATOR_CONTENT_CATEGORY -> handleCreatorContentCategoryLanguageDetect(event)
}
}
@@ -341,6 +346,25 @@ class LanguageDetectListener(
)
}
private fun handleCreatorContentCategoryLanguageDetect(event: LanguageDetectEvent) {
val categoryId = event.id
val category = categoryRepository.findByIdOrNull(categoryId) ?: return
if (!category.languageCode.isNullOrBlank()) return
val langCode = requestPapagoLanguageCode(event.query, categoryId) ?: return
category.languageCode = langCode
categoryRepository.save(category)
applicationEventPublisher.publishEvent(
LanguageTranslationEvent(
id = categoryId,
targetType = LanguageTranslationTargetType.CREATOR_CONTENT_CATEGORY
)
)
}
private fun requestPapagoLanguageCode(query: String, targetIdForLog: Long): String? {
return try {
val headers = HttpHeaders().apply {

View File

@@ -8,9 +8,10 @@ import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
@Entity
data class Category(
class Category(
var title: String,
var orders: Int = 1,
var languageCode: String? = null,
var isActive: Boolean = true
) : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY)

View File

@@ -2,8 +2,13 @@ package kr.co.vividnext.sodalive.content.category
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.translation.LanguageTranslationEvent
import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationTargetType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@@ -12,7 +17,9 @@ class CategoryService(
private val repository: CategoryRepository,
private val contentRepository: AudioContentRepository,
private val blockMemberRepository: BlockMemberRepository,
private val categoryContentRepository: CategoryContentRepository
private val categoryContentRepository: CategoryContentRepository,
private val applicationEventPublisher: ApplicationEventPublisher
) {
@Transactional
fun createCategory(request: CreateCategoryRequest, member: Member) {
@@ -40,6 +47,14 @@ class CategoryService(
)
categoryContent.isActive = true
}
applicationEventPublisher.publishEvent(
LanguageDetectEvent(
id = category.id!!,
query = request.title,
targetType = LanguageDetectTargetType.CREATOR_CONTENT_CATEGORY
)
)
}
@Transactional
@@ -50,6 +65,13 @@ class CategoryService(
if (!request.title.isNullOrBlank()) {
validateTitle(title = request.title)
category.title = request.title
applicationEventPublisher.publishEvent(
LanguageTranslationEvent(
id = request.categoryId,
targetType = LanguageTranslationTargetType.CREATOR_CONTENT_CATEGORY
)
)
}
for (contentId in request.addContentIdList) {

View File

@@ -0,0 +1,18 @@
package kr.co.vividnext.sodalive.content.category
import kr.co.vividnext.sodalive.common.BaseEntity
import javax.persistence.Entity
import javax.persistence.Table
import javax.persistence.UniqueConstraint
@Entity
@Table(
uniqueConstraints = [
UniqueConstraint(columnNames = ["categoryId", "locale"])
]
)
class CategoryTranslation(
val categoryId: Long,
val locale: String,
var category: String
) : BaseEntity()

View File

@@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.content.category
import org.springframework.data.jpa.repository.JpaRepository
interface CategoryTranslationRepository : JpaRepository<CategoryTranslation, Long> {
fun findByCategoryIdAndLocale(categoryId: Long, locale: String): CategoryTranslation?
}

View File

@@ -13,6 +13,9 @@ import kr.co.vividnext.sodalive.chat.original.translation.OriginalWorkTranslatio
import kr.co.vividnext.sodalive.chat.original.translation.OriginalWorkTranslationPayload
import kr.co.vividnext.sodalive.chat.original.translation.OriginalWorkTranslationRepository
import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.category.CategoryRepository
import kr.co.vividnext.sodalive.content.category.CategoryTranslation
import kr.co.vividnext.sodalive.content.category.CategoryTranslationRepository
import kr.co.vividnext.sodalive.content.series.translation.SeriesGenreTranslation
import kr.co.vividnext.sodalive.content.series.translation.SeriesGenreTranslationRepository
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslation
@@ -41,7 +44,9 @@ enum class LanguageTranslationTargetType {
SERIES,
SERIES_GENRE,
ORIGINAL_WORK
ORIGINAL_WORK,
CREATOR_CONTENT_CATEGORY
}
class LanguageTranslationEvent(
@@ -65,6 +70,9 @@ class LanguageTranslationListener(
private val seriesGenreTranslationRepository: SeriesGenreTranslationRepository,
private val originalWorkTranslationRepository: OriginalWorkTranslationRepository,
private val categoryRepository: CategoryRepository,
private val categoryTranslationRepository: CategoryTranslationRepository,
private val translationService: PapagoTranslationService
) {
@Async
@@ -78,6 +86,7 @@ class LanguageTranslationListener(
LanguageTranslationTargetType.SERIES -> handleSeriesLanguageTranslation(event)
LanguageTranslationTargetType.SERIES_GENRE -> handleSeriesGenreLanguageTranslation(event)
LanguageTranslationTargetType.ORIGINAL_WORK -> handleOriginalWorkLanguageTranslation(event)
LanguageTranslationTargetType.CREATOR_CONTENT_CATEGORY -> handleCreatorContentCategoryLanguageTranslation(event)
}
}
@@ -453,4 +462,45 @@ class LanguageTranslationListener(
}
}
}
private fun handleCreatorContentCategoryLanguageTranslation(event: LanguageTranslationEvent) {
val category = categoryRepository.findByIdOrNull(event.id)
if (category == null || !category.isActive || category.languageCode.isNullOrBlank()) return
val sourceLanguage = category.languageCode ?: "ko"
getTranslatableLanguageCodes(sourceLanguage).forEach { locale ->
val texts = mutableListOf<String>()
texts.add(category.title)
val response = translationService.translate(
request = TranslateRequest(
texts = texts,
sourceLanguage = sourceLanguage,
targetLanguage = locale
)
)
val translatedTexts = response.translatedText
if (translatedTexts.size == texts.size) {
val translatedCategory = translatedTexts[0]
val existing = categoryTranslationRepository
.findByCategoryIdAndLocale(category.id!!, locale)
if (existing == null) {
categoryTranslationRepository.save(
CategoryTranslation(
categoryId = category.id!!,
locale = locale,
category = translatedCategory
)
)
} else {
existing.category = translatedCategory
categoryTranslationRepository.save(existing)
}
}
}
}
}