HomeApi - languageCode에 따라 콘텐츠, 캐릭터의 번역 데이터를 제공하도록 수정

This commit is contained in:
2025-12-11 23:58:17 +09:00
parent 28fbdd7826
commit 143ba2fbb2
4 changed files with 288 additions and 11 deletions

View File

@@ -17,6 +17,7 @@ class HomeController(private val service: HomeService) {
@GetMapping
fun fetchData(
@RequestParam timezone: String,
@RequestParam(required = false) languageCode: String? = null,
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
@@ -24,6 +25,7 @@ class HomeController(private val service: HomeService) {
ApiResponse.ok(
service.fetchData(
timezone = timezone,
languageCode = languageCode,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
member
@@ -34,6 +36,7 @@ class HomeController(private val service: HomeService) {
@GetMapping("/latest-content")
fun getLatestContentByTheme(
@RequestParam("theme") theme: String,
@RequestParam(required = false) languageCode: String? = null,
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
@@ -41,6 +44,7 @@ class HomeController(private val service: HomeService) {
ApiResponse.ok(
service.getLatestContentByTheme(
theme = theme,
languageCode = languageCode,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
member
@@ -70,13 +74,15 @@ class HomeController(private val service: HomeService) {
fun getRecommendContents(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@RequestParam(required = false) languageCode: String? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
ApiResponse.ok(
service.getRecommendContentList(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
member = member
member = member,
languageCode = languageCode
)
)
}

View File

@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.api.home
import kr.co.vividnext.sodalive.audition.AuditionService
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
import kr.co.vividnext.sodalive.chat.character.translate.AiCharacterTranslationRepository
import kr.co.vividnext.sodalive.content.AudioContentMainItem
import kr.co.vividnext.sodalive.content.AudioContentService
import kr.co.vividnext.sodalive.content.ContentType
@@ -11,6 +12,7 @@ import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationServic
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeService
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
import kr.co.vividnext.sodalive.event.GetEventResponse
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
@@ -47,6 +49,9 @@ class HomeService(
private val rankingRepository: RankingRepository,
private val explorerQueryRepository: ExplorerQueryRepository,
private val contentTranslationRepository: ContentTranslationRepository,
private val aiCharacterTranslationRepository: AiCharacterTranslationRepository,
@Value("\${cloud.aws.cloud-front.host}")
private val imageHost: String
) {
@@ -57,6 +62,7 @@ class HomeService(
fun fetchData(
timezone: String,
languageCode: String?,
isAdultContentVisible: Boolean,
contentType: ContentType,
member: Member?
@@ -111,6 +117,37 @@ class HomeService(
}
}
/**
* latestContentList 번역 데이터 조회
*
* languageCode != null
* contentTranslationRepository를 이용해 번역 콘텐츠를 조회한다. - contentId, locale
*
* 한 번에 조회하고 contentId를 매핑하여 latestContentList의 title을 번역 데이터로 변경한다
*/
val translatedLatestContentList = if (!languageCode.isNullOrBlank()) {
val contentIds = latestContentList.map { it.contentId }
if (contentIds.isNotEmpty()) {
val translations = contentTranslationRepository
.findByContentIdInAndLocale(contentIds = contentIds, locale = languageCode)
.associateBy { it.contentId }
latestContentList.map { item ->
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
if (translatedTitle.isNullOrBlank()) {
item
} else {
item.copy(title = translatedTitle)
}
}
} else {
latestContentList
}
} else {
latestContentList
}
val eventBannerList = GetEventResponse(
totalCount = 0,
eventList = emptyList()
@@ -140,6 +177,38 @@ class HomeService(
// 인기 캐릭터 조회
val popularCharacters = characterService.getPopularCharacters()
/**
* popularCharacters 캐릭터 이름 번역 데이터 조회
*
* languageCode != null
* aiCharacterTranslationRepository 이용해 번역 콘텐츠를 조회한다. - characterId, locale
*
* 한 번에 조회하고 characterId 매핑하여 popularCharacters의 캐릭터 이름을 번역 데이터로 변경한다
*/
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 currentDateTime = LocalDateTime.now()
val startDate = currentDateTime
.withHour(15)
@@ -159,12 +228,81 @@ class HomeService(
sort = ContentRankingSortType.REVENUE
)
/**
* contentRanking 번역 데이터 조회
*
* languageCode != null
* contentTranslationRepository를 이용해 번역 콘텐츠를 조회한다. - contentId, locale
*
* 한 번에 조회하고 contentId를 매핑하여 contentRanking title을 번역 데이터로 변경한다
*/
val translatedContentRanking = if (!languageCode.isNullOrBlank()) {
val contentIds = contentRanking.map { it.contentId }
if (contentIds.isNotEmpty()) {
val translations = contentTranslationRepository
.findByContentIdInAndLocale(contentIds = contentIds, locale = languageCode)
.associateBy { it.contentId }
contentRanking.map { item ->
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
if (translatedTitle.isNullOrBlank()) {
item
} else {
item.copy(title = translatedTitle)
}
}
} else {
contentRanking
}
} else {
contentRanking
}
val recommendChannelList = recommendChannelService.getRecommendChannel(
memberId = memberId,
isAdult = isAdult,
contentType = contentType
)
/**
* recommendChannelList의 콘텐츠 번역 데이터 조회
*
* languageCode != null
* contentTranslationRepository를 이용해 번역 콘텐츠를 조회한다. - contentId, locale
*
* 한 번에 조회하고 contentId를 매핑하여 recommendChannelList의 콘텐츠 title을 번역 데이터로 변경한다
*/
val translatedRecommendChannelList = if (!languageCode.isNullOrBlank()) {
val contentIds = recommendChannelList
.flatMap { it.contentList }
.map { it.contentId }
.distinct()
if (contentIds.isNotEmpty()) {
val translations = contentTranslationRepository
.findByContentIdInAndLocale(contentIds = contentIds, locale = languageCode)
.associateBy { it.contentId }
recommendChannelList.map { channel ->
val translatedContentList = channel.contentList.map { item ->
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
if (translatedTitle.isNullOrBlank()) {
item
} else {
item.copy(title = translatedTitle)
}
}
channel.copy(contentList = translatedContentList)
}
} else {
recommendChannelList
}
} else {
recommendChannelList
}
val freeContentList = contentService.getLatestContentByTheme(
theme = contentThemeService.getActiveThemeOfContent(
isAdult = isAdult,
@@ -183,6 +321,37 @@ class HomeService(
}
}
/**
* freeContentList 번역 데이터 조회
*
* languageCode != null
* contentTranslationRepository를 이용해 번역 콘텐츠를 조회한다. - contentId, locale
*
* 한 번에 조회하고 contentId를 매핑하여 freeContentList title을 번역 데이터로 변경한다
*/
val translatedFreeContentList = if (!languageCode.isNullOrBlank()) {
val contentIds = freeContentList.map { it.contentId }
if (contentIds.isNotEmpty()) {
val translations = contentTranslationRepository
.findByContentIdInAndLocale(contentIds = contentIds, locale = languageCode)
.associateBy { it.contentId }
freeContentList.map { item ->
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
if (translatedTitle.isNullOrBlank()) {
item
} else {
item.copy(title = translatedTitle)
}
}
} else {
freeContentList
}
} else {
freeContentList
}
// 포인트 사용가능 콘텐츠 리스트 - 랜덤으로 가져오기 (DB에서 isPointAvailable 조건 적용)
val pointAvailableContentList = contentService.getLatestContentByTheme(
theme = emptyList(),
@@ -199,6 +368,37 @@ class HomeService(
}
}
/**
* pointAvailableContentList 번역 데이터 조회
*
* languageCode != null
* contentTranslationRepository를 이용해 번역 콘텐츠를 조회한다. - contentId, locale
*
* 한 번에 조회하고 contentId를 매핑하여 pointAvailableContentList title을 번역 데이터로 변경한다
*/
val translatedPointAvailableContentList = if (!languageCode.isNullOrBlank()) {
val contentIds = pointAvailableContentList.map { it.contentId }
if (contentIds.isNotEmpty()) {
val translations = contentTranslationRepository
.findByContentIdInAndLocale(contentIds = contentIds, locale = languageCode)
.associateBy { it.contentId }
pointAvailableContentList.map { item ->
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
if (translatedTitle.isNullOrBlank()) {
item
} else {
item.copy(title = translatedTitle)
}
}
} else {
pointAvailableContentList
}
} else {
pointAvailableContentList
}
val curationList = curationService.getContentCurationList(
tabId = 3L, // 기존에 사용하던 단편 탭의 큐레이션을 사용
isAdult = isAdult,
@@ -210,21 +410,22 @@ class HomeService(
liveList = liveList,
creatorRanking = creatorRanking,
latestContentThemeList = latestContentThemeList,
latestContentList = latestContentList,
latestContentList = translatedLatestContentList,
bannerList = bannerList,
eventBannerList = eventBannerList,
originalAudioDramaList = originalAudioDramaList,
auditionList = auditionList,
dayOfWeekSeriesList = dayOfWeekSeriesList,
popularCharacters = popularCharacters,
contentRanking = contentRanking,
recommendChannelList = recommendChannelList,
freeContentList = freeContentList,
pointAvailableContentList = pointAvailableContentList,
popularCharacters = translatedPopularCharacters,
contentRanking = translatedContentRanking,
recommendChannelList = translatedRecommendChannelList,
freeContentList = translatedFreeContentList,
pointAvailableContentList = translatedPointAvailableContentList,
recommendContentList = getRecommendContentList(
isAdultContentVisible = isAdultContentVisible,
contentType = contentType,
member = member
member = member,
languageCode = languageCode
),
curationList = curationList
)
@@ -232,6 +433,7 @@ class HomeService(
fun getLatestContentByTheme(
theme: String,
languageCode: String?,
isAdultContentVisible: Boolean,
contentType: ContentType,
member: Member?
@@ -249,7 +451,7 @@ class HomeService(
listOf(theme)
}
return contentService.getLatestContentByTheme(
val contentList = contentService.getLatestContentByTheme(
theme = themeList,
contentType = contentType,
isFree = false,
@@ -261,6 +463,39 @@ class HomeService(
true
}
}
/**
* contentList 번역 데이터 조회
*
* languageCode != null
* contentTranslationRepository를 이용해 번역 콘텐츠를 조회한다. - contentId, locale
*
* 한 번에 조회하고 contentId를 매핑하여 contentList title을 번역 데이터로 변경한다
*/
val translatedContentList = if (!languageCode.isNullOrBlank()) {
val contentIds = contentList.map { it.contentId }
if (contentIds.isNotEmpty()) {
val translations = contentTranslationRepository
.findByContentIdInAndLocale(contentIds = contentIds, locale = languageCode)
.associateBy { it.contentId }
contentList.map { item ->
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
if (translatedTitle.isNullOrBlank()) {
item
} else {
item.copy(title = translatedTitle)
}
}
} else {
contentList
}
} else {
contentList
}
return translatedContentList
}
fun getDayOfWeekSeriesList(
@@ -336,7 +571,8 @@ class HomeService(
fun getRecommendContentList(
isAdultContentVisible: Boolean,
contentType: ContentType,
member: Member?
member: Member?,
languageCode: String? = null
): List<AudioContentMainItem> {
val memberId = member?.id
val isAdult = member?.auth != null && isAdultContentVisible
@@ -371,6 +607,37 @@ class HomeService(
}
}
return result
/**
* 추천 콘텐츠 번역 데이터 조회
*
* languageCode != null
* contentTranslationRepository를 이용해 번역 콘텐츠를 조회한다. - contentId, locale
*
* 한 번에 조회하고 contentId를 매핑하여 result의 title을 번역 데이터로 변경한다
*/
val translatedResult = if (!languageCode.isNullOrBlank()) {
val contentIds = result.map { it.contentId }
if (contentIds.isNotEmpty()) {
val translations = contentTranslationRepository
.findByContentIdInAndLocale(contentIds = contentIds, locale = languageCode)
.associateBy { it.contentId }
result.map { item ->
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
if (translatedTitle.isNullOrBlank()) {
item
} else {
item.copy(title = translatedTitle)
}
}
} else {
result
}
} else {
result
}
return translatedResult
}
}

View File

@@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository
interface AiCharacterTranslationRepository : JpaRepository<AiCharacterTranslation, Long> {
fun findByCharacterIdAndLocale(characterId: Long, locale: String): AiCharacterTranslation?
fun findByCharacterIdInAndLocale(characterIds: List<Long>, locale: String): List<AiCharacterTranslation>
}

View File

@@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository
interface ContentTranslationRepository : JpaRepository<ContentTranslation, Long> {
fun findByContentIdAndLocale(contentId: Long, locale: String): ContentTranslation?
fun findByContentIdInAndLocale(contentIds: List<Long>, locale: String): List<ContentTranslation>
}