feat(creator-channel): 홈 콘텐츠 제목 번역을 적용한다

This commit is contained in:
2026-09-09 23:17:18 +09:00
parent 950ff98e18
commit 12b9a912c0
2 changed files with 190 additions and 17 deletions
@@ -10,6 +10,8 @@ import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
import kr.co.vividnext.sodalive.content.order.OrderType
import kr.co.vividnext.sodalive.content.order.QOrder.order
import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent
import kr.co.vividnext.sodalive.content.series.translation.QSeriesTranslation
import kr.co.vividnext.sodalive.content.translation.QContentTranslation
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers
@@ -153,13 +155,15 @@ class DefaultCreatorChannelHomeQueryRepository(
creatorId: Long,
now: LocalDateTime,
canViewAdultContent: Boolean,
viewerId: Long?
viewerId: Long?,
locale: String
): CreatorChannelAudioContentRecord? {
val row = findAudioContentRows(creatorId, now, null, canViewAdultContent, 1).firstOrNull() ?: return null
val contentId = itAudioId(row)
return row.toAudioRecord(
firstContentId = firstAudioContentId(creatorId, now, canViewAdultContent),
seriesByContentId = audioSeriesByContentIds(listOf(contentId)),
titlesByContentId = audioTitlesByContentIds(listOf(contentId), locale),
seriesByContentId = audioSeriesByContentIds(listOf(contentId), locale),
orderStatesByContentId = orderStatesByContentIds(viewerId, listOf(contentId), now)
)
}
@@ -209,6 +213,7 @@ class DefaultCreatorChannelHomeQueryRepository(
viewerId: Long?,
isViewerCreator: Boolean,
effectiveViewerGender: Gender?,
locale: String,
limit: Int
): List<CreatorChannelScheduleRecord> {
val liveSchedules = queryFactory
@@ -235,18 +240,21 @@ class DefaultCreatorChannelHomeQueryRepository(
)
.fetch()
val contentTranslation = QContentTranslation("homeScheduleContentTranslation")
val audioSchedules = queryFactory
.select(
Projections.constructor(
CreatorChannelScheduleRecord::class.java,
audioContent.releaseDate,
audioContent.title,
Expressions.constant(CreatorActivityType.AUDIO),
audioContent.id,
audioContent.isAdult
)
audioContent.releaseDate,
audioContent.title,
contentTranslation.renderedPayload,
audioContent.id,
audioContent.isAdult
)
.from(audioContent)
.leftJoin(contentTranslation)
.on(
contentTranslation.contentId.eq(audioContent.id),
contentTranslation.locale.eq(locale)
)
.where(
audioContent.member.id.eq(creatorId),
audioContent.member.isActive.isTrue,
@@ -256,6 +264,17 @@ class DefaultCreatorChannelHomeQueryRepository(
adultAudioCondition(canViewAdultContent)
)
.fetch()
.map {
val originalTitle = it.get(audioContent.title)!!
val translatedTitle = it.get(contentTranslation.renderedPayload)?.title
CreatorChannelScheduleRecord(
scheduledAt = it.get(audioContent.releaseDate)!!,
title = translatedTitle.takeUnless(String?::isNullOrBlank) ?: originalTitle,
type = CreatorActivityType.AUDIO,
targetId = it.get(audioContent.id)!!,
isAdult = it.get(audioContent.isAdult)!!
)
}
return (liveSchedules + audioSchedules)
.sortedWith(compareBy<CreatorChannelScheduleRecord> { it.scheduledAt }.thenBy { it.type.sortOrder })
@@ -268,14 +287,16 @@ class DefaultCreatorChannelHomeQueryRepository(
latestAudioContentId: Long?,
canViewAdultContent: Boolean,
viewerId: Long?,
locale: String,
limit: Int
): List<CreatorChannelAudioContentRecord> {
val rows = findAudioContentRows(creatorId, now, latestAudioContentId, canViewAdultContent, limit, pinFirst = true)
val contentIds = rows.map { itAudioId(it) }
val firstContentId = firstAudioContentId(creatorId, now, canViewAdultContent)
val seriesByContentId = audioSeriesByContentIds(contentIds)
val titlesByContentId = audioTitlesByContentIds(contentIds, locale)
val seriesByContentId = audioSeriesByContentIds(contentIds, locale)
val orderStatesByContentId = orderStatesByContentIds(viewerId, contentIds, now)
return rows.map { it.toAudioRecord(firstContentId, seriesByContentId, orderStatesByContentId) }
return rows.map { it.toAudioRecord(firstContentId, titlesByContentId, seriesByContentId, orderStatesByContentId) }
}
override fun findSeries(
@@ -284,16 +305,24 @@ class DefaultCreatorChannelHomeQueryRepository(
now: LocalDateTime,
canViewAdultContent: Boolean,
contentType: ContentType,
locale: String,
limit: Int
): List<CreatorChannelSeriesRecord> {
val seriesTranslation = QSeriesTranslation("homeSeriesTranslation")
val seriesRows = queryFactory
.select(
series.id,
series.title,
seriesTranslation.renderedPayload,
series.coverImage,
series.isOriginal
)
.from(series)
.leftJoin(seriesTranslation)
.on(
seriesTranslation.seriesId.eq(series.id),
seriesTranslation.locale.eq(locale)
)
.where(
series.member.id.eq(creatorId),
series.member.isActive.isTrue,
@@ -317,7 +346,8 @@ class DefaultCreatorChannelHomeQueryRepository(
val seriesId = seriesRow.get(series.id)!!
CreatorChannelSeriesRecord(
seriesId = seriesId,
title = seriesRow.get(series.title)!!,
title = seriesRow.get(seriesTranslation.renderedPayload)?.title.takeUnless(String?::isNullOrBlank)
?: seriesRow.get(series.title)!!,
coverImagePath = seriesRow.get(series.coverImage),
numberOfContent = stats.contentCount,
isNew = seriesId in newSeriesIds,
@@ -493,6 +523,7 @@ class DefaultCreatorChannelHomeQueryRepository(
private fun com.querydsl.core.Tuple.toAudioRecord(
firstContentId: Long?,
titlesByContentId: Map<Long, String>,
seriesByContentId: Map<Long, AudioSeriesSummary>,
orderStatesByContentId: Map<Long, AudioOrderState>
): CreatorChannelAudioContentRecord {
@@ -501,7 +532,7 @@ class DefaultCreatorChannelHomeQueryRepository(
val orderState = orderStatesByContentId[audioContentId]
return CreatorChannelAudioContentRecord(
audioContentId = audioContentId,
title = get(audioContent.title)!!,
title = titlesByContentId[audioContentId] ?: get(audioContent.title)!!,
duration = get(audioContent.duration),
imagePath = get(audioContent.coverImage),
price = get(audioContent.price)!!,
@@ -515,6 +546,24 @@ class DefaultCreatorChannelHomeQueryRepository(
)
}
private fun audioTitlesByContentIds(contentIds: List<Long>, locale: String): Map<Long, String> {
if (contentIds.isEmpty()) return emptyMap()
val contentTranslation = QContentTranslation("homeAudioContentTranslation")
return queryFactory
.select(contentTranslation.contentId, contentTranslation.renderedPayload)
.from(contentTranslation)
.where(
contentTranslation.contentId.`in`(contentIds),
contentTranslation.locale.eq(locale)
)
.fetch()
.mapNotNull {
val title = it.get(contentTranslation.renderedPayload)?.title.takeUnless(String?::isNullOrBlank)
title?.let { translatedTitle -> it.get(contentTranslation.contentId)!! to translatedTitle }
}
.toMap()
}
private fun orderStatesByContentIds(
viewerId: Long?,
contentIds: List<Long>,
@@ -542,17 +591,25 @@ class DefaultCreatorChannelHomeQueryRepository(
}
}
private fun audioSeriesByContentIds(contentIds: List<Long>): Map<Long, AudioSeriesSummary> {
private fun audioSeriesByContentIds(contentIds: List<Long>, locale: String): Map<Long, AudioSeriesSummary> {
if (contentIds.isEmpty()) return emptyMap()
val seriesTranslation = QSeriesTranslation("homeAudioSeriesTranslation")
return queryFactory
.select(seriesContent.content.id, series.title, series.isOriginal)
.select(seriesContent.content.id, series.title, seriesTranslation.renderedPayload, series.isOriginal)
.from(seriesContent)
.innerJoin(seriesContent.series, series)
.leftJoin(seriesTranslation)
.on(
seriesTranslation.seriesId.eq(series.id),
seriesTranslation.locale.eq(locale)
)
.where(seriesContent.content.id.`in`(contentIds))
.fetch()
.associate {
val originalTitle = it.get(series.title)!!
val translatedTitle = it.get(seriesTranslation.renderedPayload)?.title
it.get(seriesContent.content.id)!! to AudioSeriesSummary(
title = it.get(series.title)!!,
title = translatedTitle.takeUnless(String?::isNullOrBlank) ?: originalTitle,
isOriginal = it.get(series.isOriginal)!!
)
}