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 @GetMapping
fun fetchData( fun fetchData(
@RequestParam timezone: String, @RequestParam timezone: String,
@RequestParam(required = false) languageCode: String? = null,
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null, @RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null, @RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
@@ -24,6 +25,7 @@ class HomeController(private val service: HomeService) {
ApiResponse.ok( ApiResponse.ok(
service.fetchData( service.fetchData(
timezone = timezone, timezone = timezone,
languageCode = languageCode,
isAdultContentVisible = isAdultContentVisible ?: true, isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL, contentType = contentType ?: ContentType.ALL,
member member
@@ -34,6 +36,7 @@ class HomeController(private val service: HomeService) {
@GetMapping("/latest-content") @GetMapping("/latest-content")
fun getLatestContentByTheme( fun getLatestContentByTheme(
@RequestParam("theme") theme: String, @RequestParam("theme") theme: String,
@RequestParam(required = false) languageCode: String? = null,
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null, @RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null, @RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
@@ -41,6 +44,7 @@ class HomeController(private val service: HomeService) {
ApiResponse.ok( ApiResponse.ok(
service.getLatestContentByTheme( service.getLatestContentByTheme(
theme = theme, theme = theme,
languageCode = languageCode,
isAdultContentVisible = isAdultContentVisible ?: true, isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL, contentType = contentType ?: ContentType.ALL,
member member
@@ -70,13 +74,15 @@ class HomeController(private val service: HomeService) {
fun getRecommendContents( fun getRecommendContents(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null, @RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null, @RequestParam("contentType", required = false) contentType: ContentType? = null,
@RequestParam(required = false) languageCode: String? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
ApiResponse.ok( ApiResponse.ok(
service.getRecommendContentList( service.getRecommendContentList(
isAdultContentVisible = isAdultContentVisible ?: true, isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL, 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.audition.AuditionService
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService 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.AudioContentMainItem
import kr.co.vividnext.sodalive.content.AudioContentService import kr.co.vividnext.sodalive.content.AudioContentService
import kr.co.vividnext.sodalive.content.ContentType 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.ContentSeriesService
import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeService 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.creator.admin.content.series.SeriesPublishedDaysOfWeek
import kr.co.vividnext.sodalive.event.GetEventResponse import kr.co.vividnext.sodalive.event.GetEventResponse
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
@@ -47,6 +49,9 @@ class HomeService(
private val rankingRepository: RankingRepository, private val rankingRepository: RankingRepository,
private val explorerQueryRepository: ExplorerQueryRepository, private val explorerQueryRepository: ExplorerQueryRepository,
private val contentTranslationRepository: ContentTranslationRepository,
private val aiCharacterTranslationRepository: AiCharacterTranslationRepository,
@Value("\${cloud.aws.cloud-front.host}") @Value("\${cloud.aws.cloud-front.host}")
private val imageHost: String private val imageHost: String
) { ) {
@@ -57,6 +62,7 @@ class HomeService(
fun fetchData( fun fetchData(
timezone: String, timezone: String,
languageCode: String?,
isAdultContentVisible: Boolean, isAdultContentVisible: Boolean,
contentType: ContentType, contentType: ContentType,
member: Member? 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( val eventBannerList = GetEventResponse(
totalCount = 0, totalCount = 0,
eventList = emptyList() eventList = emptyList()
@@ -140,6 +177,38 @@ class HomeService(
// 인기 캐릭터 조회 // 인기 캐릭터 조회
val popularCharacters = characterService.getPopularCharacters() 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 currentDateTime = LocalDateTime.now()
val startDate = currentDateTime val startDate = currentDateTime
.withHour(15) .withHour(15)
@@ -159,12 +228,81 @@ class HomeService(
sort = ContentRankingSortType.REVENUE 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( val recommendChannelList = recommendChannelService.getRecommendChannel(
memberId = memberId, memberId = memberId,
isAdult = isAdult, isAdult = isAdult,
contentType = contentType 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( val freeContentList = contentService.getLatestContentByTheme(
theme = contentThemeService.getActiveThemeOfContent( theme = contentThemeService.getActiveThemeOfContent(
isAdult = isAdult, 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 조건 적용) // 포인트 사용가능 콘텐츠 리스트 - 랜덤으로 가져오기 (DB에서 isPointAvailable 조건 적용)
val pointAvailableContentList = contentService.getLatestContentByTheme( val pointAvailableContentList = contentService.getLatestContentByTheme(
theme = emptyList(), 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( val curationList = curationService.getContentCurationList(
tabId = 3L, // 기존에 사용하던 단편 탭의 큐레이션을 사용 tabId = 3L, // 기존에 사용하던 단편 탭의 큐레이션을 사용
isAdult = isAdult, isAdult = isAdult,
@@ -210,21 +410,22 @@ class HomeService(
liveList = liveList, liveList = liveList,
creatorRanking = creatorRanking, creatorRanking = creatorRanking,
latestContentThemeList = latestContentThemeList, latestContentThemeList = latestContentThemeList,
latestContentList = latestContentList, latestContentList = translatedLatestContentList,
bannerList = bannerList, bannerList = bannerList,
eventBannerList = eventBannerList, eventBannerList = eventBannerList,
originalAudioDramaList = originalAudioDramaList, originalAudioDramaList = originalAudioDramaList,
auditionList = auditionList, auditionList = auditionList,
dayOfWeekSeriesList = dayOfWeekSeriesList, dayOfWeekSeriesList = dayOfWeekSeriesList,
popularCharacters = popularCharacters, popularCharacters = translatedPopularCharacters,
contentRanking = contentRanking, contentRanking = translatedContentRanking,
recommendChannelList = recommendChannelList, recommendChannelList = translatedRecommendChannelList,
freeContentList = freeContentList, freeContentList = translatedFreeContentList,
pointAvailableContentList = pointAvailableContentList, pointAvailableContentList = translatedPointAvailableContentList,
recommendContentList = getRecommendContentList( recommendContentList = getRecommendContentList(
isAdultContentVisible = isAdultContentVisible, isAdultContentVisible = isAdultContentVisible,
contentType = contentType, contentType = contentType,
member = member member = member,
languageCode = languageCode
), ),
curationList = curationList curationList = curationList
) )
@@ -232,6 +433,7 @@ class HomeService(
fun getLatestContentByTheme( fun getLatestContentByTheme(
theme: String, theme: String,
languageCode: String?,
isAdultContentVisible: Boolean, isAdultContentVisible: Boolean,
contentType: ContentType, contentType: ContentType,
member: Member? member: Member?
@@ -249,7 +451,7 @@ class HomeService(
listOf(theme) listOf(theme)
} }
return contentService.getLatestContentByTheme( val contentList = contentService.getLatestContentByTheme(
theme = themeList, theme = themeList,
contentType = contentType, contentType = contentType,
isFree = false, isFree = false,
@@ -261,6 +463,39 @@ class HomeService(
true 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( fun getDayOfWeekSeriesList(
@@ -336,7 +571,8 @@ class HomeService(
fun getRecommendContentList( fun getRecommendContentList(
isAdultContentVisible: Boolean, isAdultContentVisible: Boolean,
contentType: ContentType, contentType: ContentType,
member: Member? member: Member?,
languageCode: String? = null
): List<AudioContentMainItem> { ): List<AudioContentMainItem> {
val memberId = member?.id val memberId = member?.id
val isAdult = member?.auth != null && isAdultContentVisible 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> { interface AiCharacterTranslationRepository : JpaRepository<AiCharacterTranslation, Long> {
fun findByCharacterIdAndLocale(characterId: Long, locale: String): AiCharacterTranslation? 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> { interface ContentTranslationRepository : JpaRepository<ContentTranslation, Long> {
fun findByContentIdAndLocale(contentId: Long, locale: String): ContentTranslation? fun findByContentIdAndLocale(contentId: Long, locale: String): ContentTranslation?
fun findByContentIdInAndLocale(contentIds: List<Long>, locale: String): List<ContentTranslation>
} }