콘텐츠 제목, 시리즈 장르 번역 반환 구현

This commit is contained in:
2025-12-16 04:09:25 +09:00
parent 4e4235369c
commit 82f53ed8ab
2 changed files with 109 additions and 7 deletions

View File

@@ -11,6 +11,7 @@ import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslation
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationPayload
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationRepository
import kr.co.vividnext.sodalive.content.series.translation.TranslatedSeries
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesSortType
@@ -41,6 +42,7 @@ class ContentSeriesService(
private val seriesTranslationRepository: SeriesTranslationRepository,
private val seriesGenreTranslationRepository: SeriesGenreTranslationRepository,
private val contentTranslationRepository: ContentTranslationRepository,
private val translationService: PapagoTranslationService,
@Value("\${cloud.aws.cloud-front.host}")
@@ -58,11 +60,77 @@ class ContentSeriesService(
limit: Long = 20
): List<GetSeriesListResponse.SeriesListItem> {
val originalAudioDramaList = repository.getOriginalAudioDramaList(isAdult, contentType, orderByRandom, offset, limit)
return seriesToSeriesListItem(originalAudioDramaList, isAdult, contentType)
return getTranslatedSeriesList(seriesToSeriesListItem(originalAudioDramaList, isAdult, contentType))
}
fun getGenreList(memberId: Long, isAdult: Boolean, contentType: ContentType): List<GetSeriesGenreListResponse> {
return repository.getGenreList(memberId = memberId, isAdult = isAdult, contentType = contentType)
/**
* langContext.lang == Lang.EN || Lang.JA 일 때 번역된 콘텐츠 테마 반환
* 번역이 없으면 번역 API 호출 후 저장하고 반환
*/
val genres = repository.getGenreList(memberId = memberId, isAdult = isAdult, contentType = contentType)
val currentLang = langContext.lang
if (currentLang == Lang.EN || currentLang == Lang.JA) {
val targetLocale = currentLang.code
val ids = genres.map { it.id }
// 기존 번역 일괄 조회
val existing = if (ids.isNotEmpty()) {
// 메서드가 없을 경우 개별 조회로 대체되지만, 성능을 위해 우선 시도
try {
seriesGenreTranslationRepository
.findBySeriesGenreIdInAndLocale(ids, targetLocale)
} catch (_: Exception) {
// Spring Data 메서드 미존재 시 안전하게 개별 조회로 폴백
ids.mapNotNull { id ->
seriesGenreTranslationRepository.findBySeriesGenreIdAndLocale(id, targetLocale)
}
}
} else {
emptyList()
}
val existingMap = existing.associateBy { it.seriesGenreId }.toMutableMap()
// 미번역 항목 수집
val untranslated = genres.filter { existingMap[it.id] == null }
if (untranslated.isNotEmpty()) {
val texts = untranslated.map { it.genre }
val response = translationService.translate(
request = TranslateRequest(
texts = texts,
sourceLanguage = "ko",
targetLanguage = targetLocale
)
)
val translatedTexts = response.translatedText
val toSave = mutableListOf<kr.co.vividnext.sodalive.content.series.translation.SeriesGenreTranslation>()
untranslated.forEachIndexed { index, item ->
val translated = translatedTexts.getOrNull(index) ?: item.genre
toSave.add(
kr.co.vividnext.sodalive.content.series.translation.SeriesGenreTranslation(
seriesGenreId = item.id,
locale = targetLocale,
genre = translated
)
)
}
if (toSave.isNotEmpty()) {
seriesGenreTranslationRepository.saveAll(toSave)
toSave.forEach { saved -> existingMap[saved.seriesGenreId] = saved }
}
}
// 원래 순서 보존하여 결과 조립
return genres.map { g ->
val translated = existingMap[g.id]?.genre ?: g.genre
GetSeriesGenreListResponse(id = g.id, genre = translated)
}
}
return genres
}
fun getSeriesList(
@@ -128,7 +196,7 @@ class ContentSeriesService(
).filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) }
val items = seriesToSeriesListItem(seriesList = rawItems, isAdult = isAuth, contentType = contentType)
return GetSeriesListResponse(totalCount, items)
return GetSeriesListResponse(totalCount, getTranslatedSeriesList(items))
}
@Transactional
@@ -356,7 +424,33 @@ class ContentSeriesService(
it
}
return GetSeriesContentListResponse(totalCount, contentList)
/**
* 콘텐츠 리스트의 제목을 현재 언어(locale)에 맞춰 일괄 번역한다.
*
* 처리 절차:
* - 입력된 콘텐츠들의 contentId 집합을 만들고, 요청 언어 코드(langContext.lang.code)로
* contentTranslationRepository에서 번역 데이터를 한 번에 조회한다.
* - 각 항목에 대해 번역된 제목이 존재하고 비어있지 않으면 title만 번역 값으로 교체한다.
* - 번역이 없거나 공백이면 원본 항목을 그대로 반환한다.
*
* 성능:
* - N건의 항목을 1회의 조회로 해결하기 위해 IN 쿼리를 사용한다.
*/
val contentIds = contentList.map { it.contentId }
val translatedItems = if (contentIds.isNotEmpty()) {
val translations = contentTranslationRepository
.findByContentIdInAndLocale(contentIds = contentIds, locale = langContext.lang.code)
.associateBy { it.contentId }
contentList.map { item ->
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
if (translatedTitle.isNullOrBlank()) item else item.copy(title = translatedTitle)
}
} else {
contentList
}
return GetSeriesContentListResponse(totalCount, translatedItems)
}
fun getRecommendSeriesList(
@@ -371,7 +465,13 @@ class ContentSeriesService(
limit = 20
).filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) }
return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAuth, contentType = contentType)
return getTranslatedSeriesList(
seriesToSeriesListItem(
seriesList = seriesList,
isAdult = isAuth,
contentType = contentType
)
)
}
fun fetchSeriesByCurationId(
@@ -386,7 +486,7 @@ class ContentSeriesService(
isAdult = isAdult,
contentType = contentType
)
return seriesToSeriesListItem(seriesList, isAdult, contentType)
return getTranslatedSeriesList(seriesToSeriesListItem(seriesList, isAdult, contentType))
}
fun getDayOfWeekSeriesList(
@@ -416,7 +516,7 @@ class ContentSeriesService(
seriesList
}
return seriesToSeriesListItem(seriesList, isAdult, contentType)
return getTranslatedSeriesList(seriesToSeriesListItem(seriesList, isAdult, contentType))
}
private fun seriesToSeriesListItem(

View File

@@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository
interface SeriesGenreTranslationRepository : JpaRepository<SeriesGenreTranslation, Long> {
fun findBySeriesGenreIdAndLocale(seriesGenreId: Long, locale: String): SeriesGenreTranslation?
fun findBySeriesGenreIdInAndLocale(seriesGenreIds: List<Long>, locale: String): List<SeriesGenreTranslation>
}