diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt index 3a2cc9b..f6c8c26 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt @@ -6,11 +6,14 @@ 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.SeriesTranslationRepository 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 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.member.Member import kr.co.vividnext.sodalive.member.block.BlockMemberRepository import org.springframework.beans.factory.annotation.Value @@ -26,6 +29,8 @@ class ContentSeriesService( private val blockMemberRepository: BlockMemberRepository, private val explorerQueryRepository: ExplorerQueryRepository, private val seriesContentRepository: ContentSeriesContentRepository, + private val langContext: LangContext, + private val seriesTranslationRepository: SeriesTranslationRepository, @Value("\${cloud.aws.cloud-front.host}") private val coverImageHost: String @@ -83,7 +88,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)) } fun getSeriesListByGenre( @@ -338,27 +343,105 @@ class ContentSeriesService( } private fun publishedDaysOfWeekText(publishedDaysOfWeek: Set): String { + /** + * i18n을 적용하여 언어별로 요일 표시를 변경한다. + */ + val lang = langContext.lang + + val labelRandom = when (lang) { + Lang.EN -> "Random" + Lang.JA -> "ランダム" + else -> "랜덤" + } + val labels = when (lang) { + Lang.EN -> mapOf( + SeriesPublishedDaysOfWeek.SUN to "Sun", + SeriesPublishedDaysOfWeek.MON to "Mon", + SeriesPublishedDaysOfWeek.TUE to "Tue", + SeriesPublishedDaysOfWeek.WED to "Wed", + SeriesPublishedDaysOfWeek.THU to "Thu", + SeriesPublishedDaysOfWeek.FRI to "Fri", + SeriesPublishedDaysOfWeek.SAT to "Sat", + SeriesPublishedDaysOfWeek.RANDOM to labelRandom + ) + + Lang.JA -> mapOf( + SeriesPublishedDaysOfWeek.SUN to "日", + SeriesPublishedDaysOfWeek.MON to "月", + SeriesPublishedDaysOfWeek.TUE to "火", + SeriesPublishedDaysOfWeek.WED to "水", + SeriesPublishedDaysOfWeek.THU to "木", + SeriesPublishedDaysOfWeek.FRI to "金", + SeriesPublishedDaysOfWeek.SAT to "土", + SeriesPublishedDaysOfWeek.RANDOM to labelRandom + ) + + else -> mapOf( + SeriesPublishedDaysOfWeek.SUN to "일", + SeriesPublishedDaysOfWeek.MON to "월", + SeriesPublishedDaysOfWeek.TUE to "화", + SeriesPublishedDaysOfWeek.WED to "수", + SeriesPublishedDaysOfWeek.THU to "목", + SeriesPublishedDaysOfWeek.FRI to "금", + SeriesPublishedDaysOfWeek.SAT to "토", + SeriesPublishedDaysOfWeek.RANDOM to labelRandom + ) + } + val dayOfWeekText = publishedDaysOfWeek.toList().sortedBy { it.ordinal } - .map { - when (it) { - SeriesPublishedDaysOfWeek.SUN -> "일" - SeriesPublishedDaysOfWeek.MON -> "월" - SeriesPublishedDaysOfWeek.TUE -> "화" - SeriesPublishedDaysOfWeek.WED -> "수" - SeriesPublishedDaysOfWeek.THU -> "목" - SeriesPublishedDaysOfWeek.FRI -> "금" - SeriesPublishedDaysOfWeek.SAT -> "토" - SeriesPublishedDaysOfWeek.RANDOM -> "랜덤" - } - } + .map { labels[it] ?: it.name } .joinToString(", ") { it } - return if (publishedDaysOfWeek.contains(SeriesPublishedDaysOfWeek.RANDOM)) { + val containsRandom = publishedDaysOfWeek.contains(SeriesPublishedDaysOfWeek.RANDOM) + return if (containsRandom) { dayOfWeekText } else if (publishedDaysOfWeek.size < 7) { - "매주 $dayOfWeekText" + when (lang) { + Lang.EN -> "Every $dayOfWeekText" + Lang.JA -> "毎週 $dayOfWeekText" + else -> "매주 $dayOfWeekText" + } } else { - "매일" + when (lang) { + Lang.EN -> "Daily" + Lang.JA -> "毎日" + else -> "매일" + } + } + } + + /** + * 시리즈 리스트의 제목을 현재 언어(locale)에 맞춰 일괄 번역한다. + * + * 처리 절차: + * - 입력된 시리즈들의 seriesId 집합을 만들고, 요청 언어 코드(langContext.lang.code)로 + * seriesTranslationRepository에서 번역 데이터를 한 번에 조회한다. + * - 각 항목에 대해 번역된 제목이 존재하고 비어있지 않으면 title만 번역 값으로 교체한다. + * - 번역이 없거나 공백이면 원본 항목을 그대로 반환한다. + * + * 성능: + * - N건의 항목을 1회의 조회로 해결하기 위해 IN 쿼리를 사용한다. + * + * @param seriesList 번역 대상 SeriesListItem 목록 + * @return 제목이 가능한 항목은 번역된 목록(불변 사본), 그 외는 원본 항목 유지 + */ + private fun getTranslatedSeriesList( + seriesList: List + ): List { + val seriesIds = seriesList.map { it.seriesId } + if (seriesIds.isEmpty()) return seriesList + + val translations = seriesTranslationRepository + .findBySeriesIdInAndLocale(seriesIds = seriesIds, locale = langContext.lang.code) + .associateBy { it.seriesId } + + return seriesList.map { item -> + val translatedTitle = translations[item.seriesId]?.renderedPayload?.title + if (translatedTitle.isNullOrBlank()) { + item + } else { + item.copy(title = translatedTitle) + } } } }