오디오 콘텐츠 테마 번역을 적용한다
오디오 콘텐츠 목록 응답에서 테마 문자열에 번역을 적용한다. 번역 데이터가 없을 때는 기존 원문을 유지한다.
This commit is contained in:
37
AGENTS.md
Normal file
37
AGENTS.md
Normal file
@@ -0,0 +1,37 @@
|
||||
> 이 문서는 본 저장소에서 **AI Coding Agent가 반드시 따라야 할 개발 헌법(운영 규칙)**이다.
|
||||
> 모든 신규 코드는 본 문서를 최우선 기준으로 작성한다.
|
||||
|
||||
---
|
||||
|
||||
## 0. 전제
|
||||
질문에 대한 답변과 설명은 한국어로 한다.
|
||||
|
||||
---
|
||||
|
||||
## 15. Commit Standards
|
||||
|
||||
1. 커밋 메시지는 **반드시 한국어로 작성한다.**
|
||||
2. 제목(subject)은 **현재형**으로 작성한다. (예: “기능 추가”, “기능 추가함” 금지)
|
||||
3. 제목은 **50자 이내**로 작성한다.
|
||||
4. 제목과 본문 사이에는 **반드시 한 줄 공백**을 둔다.
|
||||
5. 본문은 **한 줄당 72자 이내**로 작성한다.
|
||||
6. 하나의 문단에서는 72자를 초과할 때만 줄바꿈한다.
|
||||
7. **공개 API 변경 사항만 설명**하며, 패키지 프라이빗 구현 상세는 포함하지 않는다.
|
||||
8. **테스트 코드에 대한 언급은 커밋 메시지에 포함하지 않는다.**
|
||||
9. 제목에 `fix:`, `feat:`, `docs:` 등의 **접두어를 사용하지 않는다.**
|
||||
10. 제목은 **첫 글자를 대문자로 시작**한다. 단, 함수명 등 소문자가 합당한 경우만 예외를 허용한다.
|
||||
11. 도구 광고, 브랜딩, 홍보성 문구를 **절대 포함하지 않는다.**
|
||||
12. 커밋 전에는 **반드시 파일을 개별 stage 한다.**
|
||||
13. 커밋 전 **`work/scripts/check-commit-message-rules.sh` 검증을 통과해야 한다.**
|
||||
|
||||
---
|
||||
|
||||
## 16. AI 사용 규칙 (AI Interaction Rules)
|
||||
|
||||
- 매우 작은 단위의 변경만 수행한다.
|
||||
- 대규모 리팩터링은 반드시 사전 승인을 요청한다.
|
||||
|
||||
---
|
||||
|
||||
✅ 본 문서는 **AI와 사람이 동일한 개발 규칙을 공유하기 위한 최상위 기준 문서**이며,
|
||||
✅ 모든 신규 코드는 본 문서를 기준으로 검토된다.
|
||||
@@ -920,12 +920,34 @@ class AudioContentService(
|
||||
contentId = audioContent.id!!
|
||||
)
|
||||
|
||||
/**
|
||||
* themeStr 번역 처리
|
||||
*/
|
||||
val themeStrTranslated = run {
|
||||
val theme = audioContent.theme
|
||||
if (theme?.id != null) {
|
||||
val locale = langContext.lang.code
|
||||
val translated = contentThemeTranslationRepository
|
||||
.findByContentThemeIdAndLocale(theme.id!!, locale)
|
||||
val text = translated?.theme
|
||||
if (!text.isNullOrBlank()) text else theme.theme
|
||||
} else {
|
||||
audioContent.theme!!.theme
|
||||
}
|
||||
}
|
||||
|
||||
return GetAudioContentListItem(
|
||||
contentId = audioContent.id!!,
|
||||
coverImageUrl = "$coverImageHost/${audioContent.coverImage}",
|
||||
title = audioContent.title,
|
||||
title = run {
|
||||
val translatedTitle = contentTranslationRepository
|
||||
.findByContentIdAndLocale(audioContent.id!!, langContext.lang.code)
|
||||
?.renderedPayload
|
||||
?.title
|
||||
if (translatedTitle.isNullOrBlank()) audioContent.title else translatedTitle
|
||||
},
|
||||
price = audioContent.price,
|
||||
themeStr = audioContent.theme!!.theme,
|
||||
themeStr = themeStrTranslated,
|
||||
duration = audioContent.duration,
|
||||
likeCount = likeCount,
|
||||
commentCount = commentCount,
|
||||
@@ -1017,9 +1039,42 @@ class AudioContentService(
|
||||
items
|
||||
}
|
||||
|
||||
// theme 번역 적용: 번역 데이터가 있으면 번역, 없으면 원문 유지
|
||||
val themeTranslatedList = run {
|
||||
if (translatedContentList.isEmpty()) {
|
||||
translatedContentList
|
||||
} else {
|
||||
val locale = langContext.lang.code
|
||||
|
||||
// 활성 테마 목록에서 한글 원문 -> ID 매핑 구성
|
||||
val themesWithIds = themeQueryRepository.getActiveThemeWithIdsOfContent(
|
||||
isAdult = isAdult,
|
||||
isFree = false,
|
||||
isPointAvailableOnly = false,
|
||||
contentType = contentType
|
||||
)
|
||||
val idByKorean = themesWithIds.associate { it.theme to it.id }
|
||||
|
||||
val themeIds = idByKorean.values.distinct()
|
||||
val translatedById = if (themeIds.isNotEmpty()) {
|
||||
contentThemeTranslationRepository
|
||||
.findByContentThemeIdInAndLocale(themeIds, locale)
|
||||
.associate { it.contentThemeId to it.theme }
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
|
||||
translatedContentList.map { item ->
|
||||
val themeId = idByKorean[item.themeStr]
|
||||
val translated = if (themeId != null) translatedById[themeId] else null
|
||||
if (!translated.isNullOrBlank()) item.copy(themeStr = translated) else item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GetAudioContentListResponse(
|
||||
totalCount = totalCount,
|
||||
items = translatedContentList
|
||||
items = themeTranslatedList
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user