feat(creator-channel): 오디오 목록 제목 번역을 적용한다
This commit is contained in:
+23
-2
@@ -12,6 +12,7 @@ import kr.co.vividnext.sodalive.content.order.QOrder.order
|
|||||||
import kr.co.vividnext.sodalive.content.series.translation.QSeriesTranslation
|
import kr.co.vividnext.sodalive.content.series.translation.QSeriesTranslation
|
||||||
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
||||||
import kr.co.vividnext.sodalive.content.theme.translation.QContentThemeTranslation
|
import kr.co.vividnext.sodalive.content.theme.translation.QContentThemeTranslation
|
||||||
|
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.QSeries.series
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
@@ -173,10 +174,11 @@ class DefaultCreatorChannelAudioQueryRepository(
|
|||||||
val rows = findAudioContentRows(creatorId, viewerId, themeId, now, canViewAdultContent, sort, offset, limit)
|
val rows = findAudioContentRows(creatorId, viewerId, themeId, now, canViewAdultContent, sort, offset, limit)
|
||||||
val contentIds = rows.map { itAudioId(it) }
|
val contentIds = rows.map { itAudioId(it) }
|
||||||
val firstContentId = firstAudioContentId(creatorId, now, canViewAdultContent)
|
val firstContentId = firstAudioContentId(creatorId, now, canViewAdultContent)
|
||||||
|
val titlesByContentId = audioTitlesByContentIds(contentIds, locale)
|
||||||
val seriesByContentId = audioSeriesByContentIds(contentIds, locale)
|
val seriesByContentId = audioSeriesByContentIds(contentIds, locale)
|
||||||
val orderStatesByContentId = orderStatesByContentIds(viewerId, contentIds, now)
|
val orderStatesByContentId = orderStatesByContentIds(viewerId, contentIds, now)
|
||||||
|
|
||||||
return rows.map { it.toAudioRecord(firstContentId, seriesByContentId, orderStatesByContentId) }
|
return rows.map { it.toAudioRecord(firstContentId, titlesByContentId, seriesByContentId, orderStatesByContentId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findAudioContentRows(
|
private fun findAudioContentRows(
|
||||||
@@ -307,6 +309,7 @@ class DefaultCreatorChannelAudioQueryRepository(
|
|||||||
|
|
||||||
private fun Tuple.toAudioRecord(
|
private fun Tuple.toAudioRecord(
|
||||||
firstContentId: Long?,
|
firstContentId: Long?,
|
||||||
|
titlesByContentId: Map<Long, String>,
|
||||||
seriesByContentId: Map<Long, AudioSeriesSummary>,
|
seriesByContentId: Map<Long, AudioSeriesSummary>,
|
||||||
orderStatesByContentId: Map<Long, AudioOrderState>
|
orderStatesByContentId: Map<Long, AudioOrderState>
|
||||||
): CreatorChannelAudioContentRecord {
|
): CreatorChannelAudioContentRecord {
|
||||||
@@ -315,7 +318,7 @@ class DefaultCreatorChannelAudioQueryRepository(
|
|||||||
val orderState = orderStatesByContentId[audioContentId]
|
val orderState = orderStatesByContentId[audioContentId]
|
||||||
return CreatorChannelAudioContentRecord(
|
return CreatorChannelAudioContentRecord(
|
||||||
audioContentId = audioContentId,
|
audioContentId = audioContentId,
|
||||||
title = get(audioContent.title)!!,
|
title = titlesByContentId[audioContentId] ?: get(audioContent.title)!!,
|
||||||
duration = get(audioContent.duration),
|
duration = get(audioContent.duration),
|
||||||
imagePath = get(audioContent.coverImage),
|
imagePath = get(audioContent.coverImage),
|
||||||
price = get(audioContent.price)!!,
|
price = get(audioContent.price)!!,
|
||||||
@@ -329,6 +332,24 @@ class DefaultCreatorChannelAudioQueryRepository(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun audioTitlesByContentIds(contentIds: List<Long>, locale: String): Map<Long, String> {
|
||||||
|
if (contentIds.isEmpty()) return emptyMap()
|
||||||
|
val contentTranslation = QContentTranslation("audioContentTranslation")
|
||||||
|
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 firstAudioContentId(
|
private fun firstAudioContentId(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
|
|||||||
+64
@@ -10,6 +10,8 @@ import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslation
|
|||||||
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationPayload
|
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationPayload
|
||||||
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||||
import kr.co.vividnext.sodalive.content.theme.translation.ContentThemeTranslation
|
import kr.co.vividnext.sodalive.content.theme.translation.ContentThemeTranslation
|
||||||
|
import kr.co.vividnext.sodalive.content.translation.ContentTranslation
|
||||||
|
import kr.co.vividnext.sodalive.content.translation.ContentTranslationPayload
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -204,6 +206,51 @@ class DefaultCreatorChannelAudioQueryRepositoryTest @Autowired constructor(
|
|||||||
assertFalse(firstPage.any { it.audioContentId == oldSelected.id && it.isFirstContent })
|
assertFalse(firstPage.any { it.audioContentId == oldSelected.id && it.isFirstContent })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("목록 오디오 제목은 요청 locale 번역을 사용하고 공백 번역은 원문으로 fallback한다")
|
||||||
|
fun shouldFindAudioContentsWithTranslatedTitleFallback() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 19, 12, 0)
|
||||||
|
val viewer = saveMember("translated-title-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("translated-title-creator", MemberRole.CREATOR)
|
||||||
|
val theme = saveTheme("수면")
|
||||||
|
val fallback = saveAudioContent(creator, now.minusDays(2), false, theme)
|
||||||
|
val translated = saveAudioContent(creator, now.minusDays(1), false, theme)
|
||||||
|
saveContentTranslation(translated, "en", "Translated Audio")
|
||||||
|
saveContentTranslation(translated, "ja", "翻訳オーディオ")
|
||||||
|
saveContentTranslation(fallback, "en", " ")
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val enRecords = repository.findAudioContents(
|
||||||
|
creator.id!!,
|
||||||
|
viewer.id!!,
|
||||||
|
null,
|
||||||
|
now,
|
||||||
|
false,
|
||||||
|
ContentSort.LATEST,
|
||||||
|
"en",
|
||||||
|
offset = 0,
|
||||||
|
limit = 10
|
||||||
|
)
|
||||||
|
val jaRecords = repository.findAudioContents(
|
||||||
|
creator.id!!,
|
||||||
|
viewer.id!!,
|
||||||
|
null,
|
||||||
|
now,
|
||||||
|
false,
|
||||||
|
ContentSort.LATEST,
|
||||||
|
"ja",
|
||||||
|
offset = 0,
|
||||||
|
limit = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(enRecords.map { it.audioContentId }, jaRecords.map { it.audioContentId })
|
||||||
|
assertEquals(listOf(translated.id, fallback.id), enRecords.map { it.audioContentId })
|
||||||
|
assertEquals("Translated Audio", enRecords.first().title)
|
||||||
|
assertEquals("翻訳オーディオ", jaRecords.first().title)
|
||||||
|
assertEquals(fallback.title, enRecords.last().title)
|
||||||
|
assertEquals(fallback.title, jaRecords.last().title)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("목록은 가격순과 인기순 can 매출 정렬을 적용한다")
|
@DisplayName("목록은 가격순과 인기순 can 매출 정렬을 적용한다")
|
||||||
fun shouldSortAudioContentsByPriceAndPopularRevenue() {
|
fun shouldSortAudioContentsByPriceAndPopularRevenue() {
|
||||||
@@ -391,6 +438,23 @@ class DefaultCreatorChannelAudioQueryRepositoryTest @Autowired constructor(
|
|||||||
return translation
|
return translation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun saveContentTranslation(content: AudioContent, locale: String, title: String): ContentTranslation {
|
||||||
|
val translation = ContentTranslation(
|
||||||
|
contentId = content.id!!,
|
||||||
|
locale = locale,
|
||||||
|
renderedPayload = ContentTranslationPayload(title = title, detail = "", tags = "")
|
||||||
|
)
|
||||||
|
entityManager.persist(translation)
|
||||||
|
entityManager.flush()
|
||||||
|
val payload = "{\"title\":\"$title\",\"detail\":\"\",\"tags\":\"\"}"
|
||||||
|
entityManager.createNativeQuery(
|
||||||
|
"update content_translation set rendered_payload = '$payload' format json where id = :id"
|
||||||
|
)
|
||||||
|
.setParameter("id", translation.id)
|
||||||
|
.executeUpdate()
|
||||||
|
return translation
|
||||||
|
}
|
||||||
|
|
||||||
private fun saveOrder(
|
private fun saveOrder(
|
||||||
member: Member,
|
member: Member,
|
||||||
creator: Member,
|
creator: Member,
|
||||||
|
|||||||
Reference in New Issue
Block a user