|
|
|
|
@@ -6,7 +6,9 @@ import kr.co.vividnext.sodalive.content.order.OrderRepository
|
|
|
|
|
import kr.co.vividnext.sodalive.content.order.OrderType
|
|
|
|
|
import kr.co.vividnext.sodalive.content.series.content.ContentSeriesContentRepository
|
|
|
|
|
import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentListResponse
|
|
|
|
|
import kr.co.vividnext.sodalive.content.series.translation.SeriesGenreTranslationRepository
|
|
|
|
|
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationRepository
|
|
|
|
|
import kr.co.vividnext.sodalive.content.series.translation.TranslatedSeries
|
|
|
|
|
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
|
|
|
|
|
@@ -14,10 +16,13 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
|
|
|
|
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
|
|
|
|
|
import kr.co.vividnext.sodalive.i18n.Lang
|
|
|
|
|
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
|
|
|
|
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
|
|
|
|
import org.springframework.beans.factory.annotation.Value
|
|
|
|
|
import org.springframework.stereotype.Service
|
|
|
|
|
import org.springframework.transaction.annotation.Transactional
|
|
|
|
|
import java.time.LocalDateTime
|
|
|
|
|
import java.time.ZoneId
|
|
|
|
|
import java.time.format.DateTimeFormatter
|
|
|
|
|
@@ -29,8 +34,12 @@ class ContentSeriesService(
|
|
|
|
|
private val blockMemberRepository: BlockMemberRepository,
|
|
|
|
|
private val explorerQueryRepository: ExplorerQueryRepository,
|
|
|
|
|
private val seriesContentRepository: ContentSeriesContentRepository,
|
|
|
|
|
|
|
|
|
|
private val langContext: LangContext,
|
|
|
|
|
|
|
|
|
|
private val seriesTranslationRepository: SeriesTranslationRepository,
|
|
|
|
|
private val seriesGenreTranslationRepository: SeriesGenreTranslationRepository,
|
|
|
|
|
private val translationService: PapagoTranslationService,
|
|
|
|
|
|
|
|
|
|
@Value("\${cloud.aws.cloud-front.host}")
|
|
|
|
|
private val coverImageHost: String
|
|
|
|
|
@@ -120,6 +129,7 @@ class ContentSeriesService(
|
|
|
|
|
return GetSeriesListResponse(totalCount, items)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
fun getSeriesDetail(
|
|
|
|
|
seriesId: Long,
|
|
|
|
|
isAdultContentVisible: Boolean,
|
|
|
|
|
@@ -161,7 +171,115 @@ class ContentSeriesService(
|
|
|
|
|
limit = 5
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* series.languageCode != null && series.languageCode != languageCode
|
|
|
|
|
*
|
|
|
|
|
* 번역 시리즈를 조회한다. - series, locale
|
|
|
|
|
* 번역 콘텐츠가 있으면
|
|
|
|
|
* TranslatedSeries로 가공한다
|
|
|
|
|
*
|
|
|
|
|
* 번역 콘텐츠가 없으면
|
|
|
|
|
* 파파고 API를 통해 번역한 후 저장한다.
|
|
|
|
|
*
|
|
|
|
|
* 번역 대상: title, introduction, keywordList
|
|
|
|
|
*
|
|
|
|
|
* 파파고로 번역한 데이터를 TranslatedSeries 가공한다
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd")
|
|
|
|
|
|
|
|
|
|
// 요청된 언어(locale)에 대한 시리즈 번역을 조회하거나, 없으면 동기 번역 후 저장한다.
|
|
|
|
|
var translated: TranslatedSeries? = null
|
|
|
|
|
run {
|
|
|
|
|
val locale = langContext.lang.code
|
|
|
|
|
val languageCode = series.languageCode
|
|
|
|
|
// 원본 언어가 존재하고, 요청 언어와 다를 때만 번역 처리
|
|
|
|
|
if (!languageCode.isNullOrBlank() && languageCode != locale) {
|
|
|
|
|
val existing = seriesTranslationRepository.findBySeriesIdAndLocale(seriesId = seriesId, locale = locale)
|
|
|
|
|
if (existing != null) {
|
|
|
|
|
val payload = existing.renderedPayload
|
|
|
|
|
val kws = payload.keywords.ifEmpty { keywordList }
|
|
|
|
|
translated = TranslatedSeries(
|
|
|
|
|
title = payload.title,
|
|
|
|
|
introduction = payload.introduction,
|
|
|
|
|
keywords = kws
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
val texts = mutableListOf<String>()
|
|
|
|
|
texts.add(series.title)
|
|
|
|
|
texts.add(series.introduction)
|
|
|
|
|
// 키워드는 개별 항목으로 번역 요청하여 N회 호출을 방지한다.
|
|
|
|
|
val keywordListForTranslate = keywordList
|
|
|
|
|
texts.addAll(keywordListForTranslate)
|
|
|
|
|
|
|
|
|
|
val response = translationService.translate(
|
|
|
|
|
request = TranslateRequest(
|
|
|
|
|
texts = texts,
|
|
|
|
|
sourceLanguage = languageCode,
|
|
|
|
|
targetLanguage = locale
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val translatedTexts = response.translatedText
|
|
|
|
|
if (translatedTexts.size == texts.size) {
|
|
|
|
|
var index = 0
|
|
|
|
|
val translatedTitle = translatedTexts[index++]
|
|
|
|
|
val translatedIntroduction = translatedTexts[index++]
|
|
|
|
|
val translatedKeywords = if (keywordListForTranslate.isNotEmpty()) {
|
|
|
|
|
translatedTexts.subList(index, translatedTexts.size)
|
|
|
|
|
} else {
|
|
|
|
|
// 번역할 키워드가 없으면 원본 키워드 반환 정책 적용
|
|
|
|
|
keywordList
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val payload = kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationPayload(
|
|
|
|
|
title = translatedTitle,
|
|
|
|
|
introduction = translatedIntroduction,
|
|
|
|
|
keywords = translatedKeywords
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
seriesTranslationRepository.save(
|
|
|
|
|
kr.co.vividnext.sodalive.content.series.translation.SeriesTranslation(
|
|
|
|
|
seriesId = seriesId,
|
|
|
|
|
locale = locale,
|
|
|
|
|
renderedPayload = payload
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val kws = translatedKeywords.ifEmpty { keywordList }
|
|
|
|
|
translated = TranslatedSeries(
|
|
|
|
|
title = translatedTitle,
|
|
|
|
|
introduction = translatedIntroduction,
|
|
|
|
|
keywords = kws
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 장르 번역 조회 (있으면 반환)
|
|
|
|
|
val translatedGenre: String? = run {
|
|
|
|
|
val genreId = series.genre?.id
|
|
|
|
|
if (genreId != null) {
|
|
|
|
|
val locale = langContext.lang.code
|
|
|
|
|
val found = seriesGenreTranslationRepository.findBySeriesGenreIdAndLocale(genreId, locale)
|
|
|
|
|
val text = found?.genre
|
|
|
|
|
if (!text.isNullOrBlank()) {
|
|
|
|
|
text
|
|
|
|
|
} else {
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// publishedDateUtc는 ISO8601(Z 포함)로 반환
|
|
|
|
|
val publishedDateUtc = series.createdAt!!
|
|
|
|
|
.atZone(ZoneId.of("UTC"))
|
|
|
|
|
.toInstant()
|
|
|
|
|
.toString()
|
|
|
|
|
|
|
|
|
|
return GetSeriesDetailResponse(
|
|
|
|
|
seriesId = seriesId,
|
|
|
|
|
title = series.title,
|
|
|
|
|
@@ -176,6 +294,7 @@ class ContentSeriesService(
|
|
|
|
|
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
|
|
|
|
|
.toLocalDateTime()
|
|
|
|
|
.format(dateTimeFormatter),
|
|
|
|
|
publishedDateUtc = publishedDateUtc,
|
|
|
|
|
creator = GetSeriesDetailResponse.GetSeriesDetailCreator(
|
|
|
|
|
creatorId = series.member!!.id!!,
|
|
|
|
|
nickname = series.member!!.nickname,
|
|
|
|
|
@@ -191,7 +310,9 @@ class ContentSeriesService(
|
|
|
|
|
keywordList = keywordList,
|
|
|
|
|
publishedDaysOfWeek = publishedDaysOfWeekText(series.publishedDaysOfWeek),
|
|
|
|
|
contentList = seriesContentList.items,
|
|
|
|
|
contentCount = seriesContentList.totalCount
|
|
|
|
|
contentCount = seriesContentList.totalCount,
|
|
|
|
|
translated = translated,
|
|
|
|
|
translatedGenre = translatedGenre
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|