refactor(i18n): 공급자 전환과 캐시 선택을 공통 처리로 연결한다
This commit is contained in:
@@ -10,15 +10,11 @@ import kr.co.vividnext.sodalive.explorer.profile.CreatorCheersRepository
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityRepository
|
||||
import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationEvent
|
||||
import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationTargetType
|
||||
import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService.Companion.getTranslatableLanguageCodes
|
||||
import kr.co.vividnext.sodalive.i18n.translation.TranslationLanguages.getTranslatableLanguageCodes
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityTranslationService
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.ApplicationEventPublisher
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.http.HttpEntity
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.scheduling.annotation.Async
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.transaction.annotation.Propagation
|
||||
@@ -27,13 +23,11 @@ import org.springframework.transaction.event.TransactionPhase
|
||||
import org.springframework.transaction.event.TransactionalEventListener
|
||||
import org.springframework.transaction.support.TransactionSynchronization
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||
import org.springframework.util.LinkedMultiValueMap
|
||||
import org.springframework.web.client.RestTemplate
|
||||
import javax.persistence.EntityManager
|
||||
import javax.persistence.LockModeType
|
||||
|
||||
/**
|
||||
* 텍스트 기반 데이터(콘텐츠, 댓글 등)에 대해 파파고 언어 감지를 요청하기 위한 이벤트.
|
||||
* 텍스트 기반 데이터(콘텐츠, 댓글 등)에 대해 언어 감지를 요청하기 위한 이벤트.
|
||||
*/
|
||||
enum class LanguageDetectTargetType {
|
||||
CONTENT,
|
||||
@@ -56,10 +50,6 @@ class LanguageDetectEvent(
|
||||
val targetLanguage: String? = null
|
||||
)
|
||||
|
||||
data class PapagoLanguageDetectResponse(
|
||||
val langCode: String?
|
||||
)
|
||||
|
||||
@Component
|
||||
class LanguageDetectListener(
|
||||
private val audioContentRepository: AudioContentRepository,
|
||||
@@ -75,27 +65,17 @@ class LanguageDetectListener(
|
||||
private val creatorCommunityTranslationService: CreatorCommunityTranslationService,
|
||||
private val entityManager: EntityManager,
|
||||
|
||||
private val applicationEventPublisher: ApplicationEventPublisher,
|
||||
|
||||
@Value("\${cloud.naver.papago-client-id}")
|
||||
private val papagoClientId: String,
|
||||
|
||||
@Value("\${cloud.naver.papago-client-secret}")
|
||||
private val papagoClientSecret: String
|
||||
private val applicationEventPublisher: ApplicationEventPublisher
|
||||
) {
|
||||
|
||||
private val log = LoggerFactory.getLogger(LanguageDetectListener::class.java)
|
||||
|
||||
private val restTemplate: RestTemplate = RestTemplate()
|
||||
|
||||
private val papagoDetectUrl = "https://papago.apigw.ntruss.com/langs/v1/dect"
|
||||
|
||||
@Async
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
fun detectLanguage(event: LanguageDetectEvent) {
|
||||
if (event.query.isBlank()) {
|
||||
log.debug("[PapagoLanguageDetect] query is blank. Skip language detection. event={}", event)
|
||||
log.debug("[LanguageDetect] query is blank. Skip language detection. targetId={}", event.id)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -117,21 +97,21 @@ class LanguageDetectListener(
|
||||
|
||||
val character = chatCharacterRepository.findById(characterId).orElse(null)
|
||||
if (character == null) {
|
||||
log.warn("[PapagoLanguageDetect] ChatCharacter not found. characterId={}", characterId)
|
||||
log.warn("[LanguageDetect] ChatCharacter not found. characterId={}", characterId)
|
||||
return
|
||||
}
|
||||
|
||||
// 이미 언어 코드가 설정된 경우 호출하지 않음
|
||||
if (!character.languageCode.isNullOrBlank()) {
|
||||
log.debug(
|
||||
"[PapagoLanguageDetect] languageCode already set. Skip language detection. characterId={}, languageCode={}",
|
||||
"[LanguageDetect] languageCode already set. Skip language detection. characterId={}, languageCode={}",
|
||||
characterId,
|
||||
character.languageCode
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val langCode = detectLanguageCode(event, characterId) ?: return
|
||||
val langCode = detectLanguageCode(event) ?: return
|
||||
|
||||
character.languageCode = langCode
|
||||
chatCharacterRepository.save(character)
|
||||
@@ -144,7 +124,7 @@ class LanguageDetectListener(
|
||||
)
|
||||
|
||||
log.info(
|
||||
"[PapagoLanguageDetect] languageCode updated from Papago. characterId={}, langCode={}",
|
||||
"[LanguageDetect] languageCode updated. characterId={}, langCode={}",
|
||||
characterId,
|
||||
langCode
|
||||
)
|
||||
@@ -155,21 +135,21 @@ class LanguageDetectListener(
|
||||
|
||||
val audioContent = audioContentRepository.findById(contentId).orElse(null)
|
||||
if (audioContent == null) {
|
||||
log.warn("[PapagoLanguageDetect] AudioContent not found. contentId={}", contentId)
|
||||
log.warn("[LanguageDetect] AudioContent not found. contentId={}", contentId)
|
||||
return
|
||||
}
|
||||
|
||||
// 이미 언어 코드가 설정된 경우 호출하지 않음
|
||||
if (!audioContent.languageCode.isNullOrBlank()) {
|
||||
log.debug(
|
||||
"[PapagoLanguageDetect] languageCode already set. Skip language detection. contentId={}, languageCode={}",
|
||||
"[LanguageDetect] languageCode already set. Skip language detection. contentId={}, languageCode={}",
|
||||
contentId,
|
||||
audioContent.languageCode
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val langCode = detectLanguageCode(event, contentId) ?: return
|
||||
val langCode = detectLanguageCode(event) ?: return
|
||||
|
||||
audioContent.languageCode = langCode
|
||||
|
||||
@@ -184,7 +164,7 @@ class LanguageDetectListener(
|
||||
)
|
||||
|
||||
log.info(
|
||||
"[PapagoLanguageDetect] languageCode updated from Papago. contentId={}, langCode={}",
|
||||
"[LanguageDetect] languageCode updated. contentId={}, langCode={}",
|
||||
contentId,
|
||||
langCode
|
||||
)
|
||||
@@ -195,27 +175,27 @@ class LanguageDetectListener(
|
||||
|
||||
val comment = audioContentCommentRepository.findById(commentId).orElse(null)
|
||||
if (comment == null) {
|
||||
log.warn("[PapagoLanguageDetect] AudioContentComment not found. commentId={}", commentId)
|
||||
log.warn("[LanguageDetect] AudioContentComment not found. commentId={}", commentId)
|
||||
return
|
||||
}
|
||||
|
||||
// 이미 언어 코드가 설정된 경우 호출하지 않음
|
||||
if (!comment.languageCode.isNullOrBlank()) {
|
||||
log.debug(
|
||||
"[PapagoLanguageDetect] languageCode already set. Skip language detection. commentId={}, languageCode={}",
|
||||
"[LanguageDetect] languageCode already set. Skip language detection. commentId={}, languageCode={}",
|
||||
commentId,
|
||||
comment.languageCode
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val langCode = detectLanguageCode(event, commentId) ?: return
|
||||
val langCode = detectLanguageCode(event) ?: return
|
||||
|
||||
comment.languageCode = langCode
|
||||
audioContentCommentRepository.save(comment)
|
||||
|
||||
log.info(
|
||||
"[PapagoLanguageDetect] languageCode updated from Papago. commentId={}, langCode={}",
|
||||
"[LanguageDetect] languageCode updated. commentId={}, langCode={}",
|
||||
commentId,
|
||||
langCode
|
||||
)
|
||||
@@ -226,14 +206,14 @@ class LanguageDetectListener(
|
||||
|
||||
val comment = characterCommentRepository.findById(commentId).orElse(null)
|
||||
if (comment == null) {
|
||||
log.warn("[PapagoLanguageDetect] CharacterComment not found. commentId={}", commentId)
|
||||
log.warn("[LanguageDetect] CharacterComment not found. commentId={}", commentId)
|
||||
return
|
||||
}
|
||||
|
||||
// 이미 언어 코드가 설정된 경우 호출하지 않음
|
||||
if (!comment.languageCode.isNullOrBlank()) {
|
||||
log.debug(
|
||||
"[PapagoLanguageDetect] languageCode already set. Skip language detection. " +
|
||||
"[LanguageDetect] languageCode already set. Skip language detection. " +
|
||||
"characterCommentId={}, languageCode={}",
|
||||
commentId,
|
||||
comment.languageCode
|
||||
@@ -241,13 +221,13 @@ class LanguageDetectListener(
|
||||
return
|
||||
}
|
||||
|
||||
val langCode = detectLanguageCode(event, commentId) ?: return
|
||||
val langCode = detectLanguageCode(event) ?: return
|
||||
|
||||
comment.languageCode = langCode
|
||||
characterCommentRepository.save(comment)
|
||||
|
||||
log.info(
|
||||
"[PapagoLanguageDetect] languageCode updated from Papago. characterCommentId={}, langCode={}",
|
||||
"[LanguageDetect] languageCode updated. characterCommentId={}, langCode={}",
|
||||
commentId,
|
||||
langCode
|
||||
)
|
||||
@@ -258,27 +238,27 @@ class LanguageDetectListener(
|
||||
|
||||
val cheers = creatorCheersRepository.findById(cheersId).orElse(null)
|
||||
if (cheers == null) {
|
||||
log.warn("[PapagoLanguageDetect] CreatorCheers not found. cheersId={}", cheersId)
|
||||
log.warn("[LanguageDetect] CreatorCheers not found. cheersId={}", cheersId)
|
||||
return
|
||||
}
|
||||
|
||||
// 이미 언어 코드가 설정된 경우 호출하지 않음
|
||||
if (!cheers.languageCode.isNullOrBlank()) {
|
||||
log.debug(
|
||||
"[PapagoLanguageDetect] languageCode already set. Skip language detection. cheersId={}, languageCode={}",
|
||||
"[LanguageDetect] languageCode already set. Skip language detection. cheersId={}, languageCode={}",
|
||||
cheersId,
|
||||
cheers.languageCode
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val langCode = detectLanguageCode(event, cheersId) ?: return
|
||||
val langCode = detectLanguageCode(event) ?: return
|
||||
|
||||
cheers.languageCode = langCode
|
||||
creatorCheersRepository.save(cheers)
|
||||
|
||||
log.info(
|
||||
"[PapagoLanguageDetect] languageCode updated from Papago. cheersId={}, langCode={}",
|
||||
"[LanguageDetect] languageCode updated. cheersId={}, langCode={}",
|
||||
cheersId,
|
||||
langCode
|
||||
)
|
||||
@@ -289,21 +269,21 @@ class LanguageDetectListener(
|
||||
|
||||
val series = seriesRepository.findByIdOrNull(seriesId)
|
||||
if (series == null) {
|
||||
log.warn("[PapagoLanguageDetect] Series not found. seriesId={}", seriesId)
|
||||
log.warn("[LanguageDetect] Series not found. seriesId={}", seriesId)
|
||||
return
|
||||
}
|
||||
|
||||
// 이미 언어 코드가 설정된 경우 호출하지 않음
|
||||
if (!series.languageCode.isNullOrBlank()) {
|
||||
log.debug(
|
||||
"[PapagoLanguageDetect] languageCode already set. Skip language detection. seriesId={}, languageCode={}",
|
||||
"[LanguageDetect] languageCode already set. Skip language detection. seriesId={}, languageCode={}",
|
||||
seriesId,
|
||||
series.languageCode
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val langCode = detectLanguageCode(event, seriesId) ?: return
|
||||
val langCode = detectLanguageCode(event) ?: return
|
||||
|
||||
series.languageCode = langCode
|
||||
seriesRepository.save(series)
|
||||
@@ -316,7 +296,7 @@ class LanguageDetectListener(
|
||||
)
|
||||
|
||||
log.info(
|
||||
"[PapagoLanguageDetect] languageCode updated from Papago. seriesId={}, langCode={}",
|
||||
"[LanguageDetect] languageCode updated. seriesId={}, langCode={}",
|
||||
seriesId,
|
||||
langCode
|
||||
)
|
||||
@@ -327,21 +307,21 @@ class LanguageDetectListener(
|
||||
|
||||
val originalWork = originalWorkRepository.findByIdOrNull(originalWorkId)
|
||||
if (originalWork == null) {
|
||||
log.warn("[PapagoLanguageDetect] OriginalWork not found. originalWorkId={}", originalWorkId)
|
||||
log.warn("[LanguageDetect] OriginalWork not found. originalWorkId={}", originalWorkId)
|
||||
return
|
||||
}
|
||||
|
||||
// 이미 언어 코드가 설정된 경우 호출하지 않음
|
||||
if (!originalWork.languageCode.isNullOrBlank()) {
|
||||
log.debug(
|
||||
"[PapagoLanguageDetect] languageCode already set. Skip language detection. originalWorkId={}, languageCode={}",
|
||||
"[LanguageDetect] languageCode already set. Skip language detection. originalWorkId={}, languageCode={}",
|
||||
originalWorkId,
|
||||
originalWork.languageCode
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val langCode = detectLanguageCode(event, originalWorkId) ?: return
|
||||
val langCode = detectLanguageCode(event) ?: return
|
||||
|
||||
originalWork.languageCode = langCode
|
||||
originalWorkRepository.save(originalWork)
|
||||
@@ -355,7 +335,7 @@ class LanguageDetectListener(
|
||||
)
|
||||
|
||||
log.info(
|
||||
"[PapagoLanguageDetect] languageCode updated from Papago. originalWorkId={}, langCode={}",
|
||||
"[LanguageDetect] languageCode updated. originalWorkId={}, langCode={}",
|
||||
originalWorkId,
|
||||
langCode
|
||||
)
|
||||
@@ -367,7 +347,7 @@ class LanguageDetectListener(
|
||||
val category = categoryRepository.findByIdOrNull(categoryId) ?: return
|
||||
if (!category.languageCode.isNullOrBlank()) return
|
||||
|
||||
val langCode = detectLanguageCode(event, categoryId) ?: return
|
||||
val langCode = detectLanguageCode(event) ?: return
|
||||
|
||||
category.languageCode = langCode
|
||||
categoryRepository.save(category)
|
||||
@@ -395,7 +375,7 @@ class LanguageDetectListener(
|
||||
return
|
||||
}
|
||||
|
||||
val languageCode = detectLanguageCode(event, event.id) ?: return
|
||||
val languageCode = detectLanguageCode(event) ?: return
|
||||
if (languageCode !in getTranslatableLanguageCodes(null)) return
|
||||
|
||||
val currentCreatorCommunity = creatorCommunityRepository.findByIdAndIsActiveTrueForUpdate(event.id) ?: return
|
||||
@@ -427,60 +407,7 @@ class LanguageDetectListener(
|
||||
)
|
||||
}
|
||||
|
||||
private fun detectLanguageCode(event: LanguageDetectEvent, targetIdForLog: Long): String? {
|
||||
return languageDetectionCacheService.detectWithCache(event.query) {
|
||||
requestPapagoLanguageCode(event.query, targetIdForLog)
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestPapagoLanguageCode(query: String, targetIdForLog: Long): String? {
|
||||
return try {
|
||||
val headers = HttpHeaders().apply {
|
||||
contentType = MediaType.APPLICATION_FORM_URLENCODED
|
||||
set("X-NCP-APIGW-API-KEY-ID", papagoClientId)
|
||||
set("X-NCP-APIGW-API-KEY", papagoClientSecret)
|
||||
}
|
||||
|
||||
val body = LinkedMultiValueMap<String, String>().apply {
|
||||
// 파파고 스펙에 따라 query 파라미터에 텍스트를 공백으로 구분하여 전달
|
||||
add("query", query)
|
||||
}
|
||||
|
||||
val requestEntity = HttpEntity(body, headers)
|
||||
|
||||
val response = restTemplate.postForEntity(
|
||||
papagoDetectUrl,
|
||||
requestEntity,
|
||||
PapagoLanguageDetectResponse::class.java
|
||||
)
|
||||
|
||||
if (!response.statusCode.is2xxSuccessful) {
|
||||
log.warn(
|
||||
"[PapagoLanguageDetect] Non-success status from Papago. status={}, targetId={}",
|
||||
response.statusCode,
|
||||
targetIdForLog
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
val langCode = response.body?.langCode?.takeIf { it.isNotBlank() }
|
||||
if (langCode == null) {
|
||||
log.warn(
|
||||
"[PapagoLanguageDetect] langCode is null or blank in Papago response. targetId={}",
|
||||
targetIdForLog
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
langCode
|
||||
} catch (ex: Exception) {
|
||||
// 언어 감지는 부가 기능이므로, 실패 시 예외를 전파하지 않고 로그만 남긴다.
|
||||
log.error(
|
||||
"[PapagoLanguageDetect] Failed to detect language via Papago. targetId={}",
|
||||
targetIdForLog,
|
||||
ex
|
||||
)
|
||||
null
|
||||
}
|
||||
private fun detectLanguageCode(event: LanguageDetectEvent): String? {
|
||||
return languageDetectionCacheService.detectWithCache(event.query)?.detectedLanguage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,64 @@
|
||||
package kr.co.vividnext.sodalive.content
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.translation.DetectedLanguage
|
||||
import kr.co.vividnext.sodalive.i18n.translation.LanguageDetectionProvider
|
||||
import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderProperties
|
||||
import kr.co.vividnext.sodalive.i18n.translation.SourceTextNormalizer
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
class LanguageDetectionCacheService(
|
||||
private val languageDetectionResultRepository: LanguageDetectionResultRepository
|
||||
private val languageDetectionResultRepository: LanguageDetectionResultRepository,
|
||||
providers: List<LanguageDetectionProvider>,
|
||||
properties: LanguageProviderProperties
|
||||
) {
|
||||
private val providers = properties.orderedProviders(properties.detectionOrder, providers) { it.providerName }
|
||||
|
||||
@Transactional
|
||||
fun detectWithCache(
|
||||
query: String,
|
||||
provider: String = DEFAULT_PROVIDER,
|
||||
detector: () -> String?
|
||||
): String? {
|
||||
fun detectWithCache(query: String): DetectedLanguage? {
|
||||
val normalizedQuery = SourceTextNormalizer.normalize(query)
|
||||
if (normalizedQuery.isBlank()) return null
|
||||
|
||||
val sourceHash = SourceTextNormalizer.hash(normalizedQuery)
|
||||
val cached = languageDetectionResultRepository.findBySourceHashAndProviderAndNormalizationVersion(
|
||||
sourceHash = sourceHash,
|
||||
provider = provider,
|
||||
normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION
|
||||
)
|
||||
if (cached != null) return cached.detectedLanguage
|
||||
|
||||
val detectedLanguage = detector()?.takeIf { it.isNotBlank() }?.lowercase() ?: return null
|
||||
languageDetectionResultRepository.insertIfAbsent(
|
||||
sourceHash = sourceHash,
|
||||
sourceTextSample = normalizedQuery.take(MAX_SAMPLE_LENGTH),
|
||||
detectedLanguage = detectedLanguage,
|
||||
provider = provider,
|
||||
confidence = null,
|
||||
normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION
|
||||
)
|
||||
return requireNotNull(
|
||||
languageDetectionResultRepository.findDetectedLanguageForUpdate(
|
||||
providers.forEach { provider ->
|
||||
val cached = languageDetectionResultRepository.findBySourceHashAndProviderAndNormalizationVersion(
|
||||
sourceHash = sourceHash,
|
||||
provider = provider,
|
||||
provider = provider.providerName,
|
||||
normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION
|
||||
)
|
||||
)
|
||||
if (cached != null && cached.detectedLanguage.isNotBlank()) {
|
||||
return DetectedLanguage(cached.detectedLanguage, provider.providerName)
|
||||
}
|
||||
}
|
||||
|
||||
providers.forEach { provider ->
|
||||
val detectedLanguage = try {
|
||||
provider.detect(query)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}?.takeIf { it.isNotBlank() }?.lowercase() ?: return@forEach
|
||||
languageDetectionResultRepository.insertIfAbsent(
|
||||
sourceHash = sourceHash,
|
||||
sourceTextSample = normalizedQuery.take(MAX_SAMPLE_LENGTH),
|
||||
detectedLanguage = detectedLanguage,
|
||||
provider = provider.providerName,
|
||||
confidence = null,
|
||||
normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION
|
||||
)
|
||||
val winner = requireNotNull(
|
||||
languageDetectionResultRepository.findDetectedLanguageForUpdate(
|
||||
sourceHash = sourceHash,
|
||||
provider = provider.providerName,
|
||||
normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION
|
||||
)
|
||||
)
|
||||
return DetectedLanguage(winner, provider.providerName)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_PROVIDER = "papago"
|
||||
private const val MAX_SAMPLE_LENGTH = 500
|
||||
}
|
||||
}
|
||||
|
||||
+1
-23
@@ -92,32 +92,10 @@ class PapagoTranslationService(
|
||||
}
|
||||
|
||||
private fun validateLanguages(sourceLanguage: String, targetLanguage: String): Boolean {
|
||||
return requireSupportedLanguage(sourceLanguage) && requireSupportedLanguage(targetLanguage)
|
||||
}
|
||||
|
||||
private fun requireSupportedLanguage(language: String): Boolean {
|
||||
val normalized = language.lowercase()
|
||||
return SUPPORTED_LANGUAGE_CODES.contains(normalized)
|
||||
return TranslationLanguages.isSupported(sourceLanguage) && TranslationLanguages.isSupported(targetLanguage)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_REQUEST_TEXT_LENGTH = 5000
|
||||
|
||||
private val SUPPORTED_LANGUAGE_CODES = setOf(
|
||||
"ko",
|
||||
"en",
|
||||
"ja"
|
||||
)
|
||||
|
||||
/**
|
||||
* 번역 대상 언어 코드 집합을 반환한다.
|
||||
*
|
||||
* @param excludedLanguageCode 번역 대상에서 제외할 언어 코드(대소문자 무시)
|
||||
* @return 지원되는 언어 코드 중 [excludedLanguageCode]를 제외한 집합
|
||||
*/
|
||||
fun getTranslatableLanguageCodes(excludedLanguageCode: String?): Set<String> {
|
||||
val normalized = excludedLanguageCode?.lowercase()
|
||||
return SUPPORTED_LANGUAGE_CODES.filterNot { it == normalized }.toSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package kr.co.vividnext.sodalive.i18n.translation
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService.Companion.getTranslatableLanguageCodes
|
||||
import kr.co.vividnext.sodalive.i18n.translation.TranslationLanguages.getTranslatableLanguageCodes
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
|
||||
+1
-5
@@ -21,7 +21,7 @@ class TranslationJobScheduler(
|
||||
if (normalizedText.isBlank()) return
|
||||
|
||||
val normalizedSourceLanguage = sourceLanguage.lowercase()
|
||||
if (!SUPPORTED_SOURCE_LANGUAGE_CODES.contains(normalizedSourceLanguage)) return
|
||||
if (!TranslationLanguages.isSupported(normalizedSourceLanguage)) return
|
||||
|
||||
val normalizedTargetLanguage = targetLanguage.lowercase()
|
||||
if (normalizedSourceLanguage == normalizedTargetLanguage) return
|
||||
@@ -49,8 +49,4 @@ class TranslationJobScheduler(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val SUPPORTED_SOURCE_LANGUAGE_CODES = setOf("ko", "en", "ja")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.time.LocalDateTime
|
||||
class TranslationJobWorker(
|
||||
private val translationJobRepository: TranslationJobRepository,
|
||||
private val translationMemoryRepository: TranslationMemoryRepository,
|
||||
private val translationProvider: TranslationProvider,
|
||||
private val translationCoordinator: TranslationCoordinator,
|
||||
private val materializer: TranslationReadModelMaterializer,
|
||||
transactionManager: PlatformTransactionManager
|
||||
) {
|
||||
@@ -69,25 +69,17 @@ class TranslationJobWorker(
|
||||
}
|
||||
|
||||
private fun ensureMemory(job: TranslationJob) {
|
||||
val existing = translationMemoryRepository
|
||||
.findBySourceHashAndSourceLanguageAndTargetLanguageAndProviderAndNormalizationVersion(
|
||||
sourceHash = job.sourceHash,
|
||||
sourceLanguage = job.sourceLanguage,
|
||||
targetLanguage = job.targetLanguage,
|
||||
provider = translationProvider.providerName,
|
||||
normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION
|
||||
)
|
||||
val existing = translationCoordinator.findCached(job.sourceHash, job.sourceLanguage, job.targetLanguage)
|
||||
if (existing != null) return
|
||||
|
||||
val response = translationProvider.translate(
|
||||
val response = translationCoordinator.translate(
|
||||
TranslateRequest(
|
||||
texts = listOf(job.sourceText),
|
||||
sourceLanguage = job.sourceLanguage,
|
||||
targetLanguage = job.targetLanguage
|
||||
)
|
||||
)
|
||||
val translated = response.translatedText.firstOrNull()?.takeIf { it.isNotBlank() }
|
||||
?: throw IllegalStateException("empty translation result")
|
||||
val translated = response.result.translatedText.single()
|
||||
|
||||
transactionTemplate.executeWithoutResult {
|
||||
val memory = translationMemoryRepository
|
||||
@@ -95,7 +87,7 @@ class TranslationJobWorker(
|
||||
sourceHash = job.sourceHash,
|
||||
sourceLanguage = job.sourceLanguage,
|
||||
targetLanguage = job.targetLanguage,
|
||||
provider = translationProvider.providerName,
|
||||
provider = response.providerName,
|
||||
normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION
|
||||
)
|
||||
if (memory == null) {
|
||||
@@ -106,8 +98,8 @@ class TranslationJobWorker(
|
||||
sourceLanguage = job.sourceLanguage,
|
||||
targetLanguage = job.targetLanguage,
|
||||
translatedText = translated,
|
||||
provider = translationProvider.providerName,
|
||||
providerVersion = translationProvider.providerVersion
|
||||
provider = response.providerName,
|
||||
providerVersion = response.providerVersion
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+6
-13
@@ -29,7 +29,7 @@ import javax.persistence.LockModeType
|
||||
@Service
|
||||
class TranslationReadModelMaterializer(
|
||||
private val sourceExtractor: TranslationSourceExtractor,
|
||||
private val translationMemoryRepository: TranslationMemoryRepository,
|
||||
private val translationCoordinator: TranslationCoordinator,
|
||||
private val contentTranslationRepository: ContentTranslationRepository,
|
||||
private val aiCharacterTranslationRepository: AiCharacterTranslationRepository,
|
||||
private val contentThemeTranslationRepository: ContentThemeTranslationRepository,
|
||||
@@ -71,14 +71,11 @@ class TranslationReadModelMaterializer(
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val memory = translationMemoryRepository
|
||||
.findBySourceHashAndSourceLanguageAndTargetLanguageAndProviderAndNormalizationVersion(
|
||||
sourceHash = SourceTextNormalizer.hash(normalizedText),
|
||||
sourceLanguage = source.sourceLanguage.lowercase(),
|
||||
targetLanguage = targetLanguage,
|
||||
provider = DEFAULT_PROVIDER,
|
||||
normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION
|
||||
) ?: return null
|
||||
val memory = translationCoordinator.findCached(
|
||||
sourceHash = SourceTextNormalizer.hash(normalizedText),
|
||||
sourceLanguage = source.sourceLanguage.lowercase(),
|
||||
targetLanguage = targetLanguage
|
||||
) ?: return null
|
||||
|
||||
result[field.fieldKey] = memory.translatedText
|
||||
}
|
||||
@@ -231,8 +228,4 @@ class TranslationReadModelMaterializer(
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_PROVIDER = "papago"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,9 +5,9 @@ import kr.co.vividnext.sodalive.content.LanguageDetectTargetType
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityRepository
|
||||
import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationTargetType
|
||||
import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService.Companion.getTranslatableLanguageCodes
|
||||
import kr.co.vividnext.sodalive.i18n.translation.ResourceTranslationJobScheduler
|
||||
import kr.co.vividnext.sodalive.i18n.translation.SourceTextNormalizer
|
||||
import kr.co.vividnext.sodalive.i18n.translation.TranslationLanguages.getTranslatableLanguageCodes
|
||||
import kr.co.vividnext.sodalive.i18n.translation.TranslationReadModelMaterializer
|
||||
import kr.co.vividnext.sodalive.i18n.translation.TranslationSourceExtractor
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.adapter.out.persistence.CreatorCommunityTranslation
|
||||
|
||||
Reference in New Issue
Block a user