docs(content-i18n): 콘텐츠 목록 번역 요구사항과 검증을 기록한다
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# v2 콘텐츠 목록 요청 언어별 번역 PRD
|
||||
|
||||
## 문서 정보
|
||||
|
||||
- 작성일: 2026-09-09
|
||||
- 상태: Phase 1·2 구현 및 자동 검증 완료, test 서버 HTTP Gate 대기
|
||||
- 결정권자: 사용자
|
||||
- 관련 계획: [plan-task.md](plan-task.md)
|
||||
- 공개 API 경로·요청·응답 스키마는 유지하며 별도 API 계약 문서는 만들지 않는다.
|
||||
- 사용자의 2026-09-09 구현 지시 이후 Phase 1·2 코드·테스트 구현을 진행했다. 운영 데이터 변경과 번역 실행은 수행하지 않는다.
|
||||
|
||||
## 1. 문제와 목표
|
||||
|
||||
동일한 콘텐츠라도 v2 조회 위치에 따라 번역 제목과 원문이 혼재한다.
|
||||
요청의 Accept-Language로 결정된 언어에 맞춰 이미 저장된 번역을 표시하고,
|
||||
번역이 없거나 공백이면 해당 필드의 기존 원문을 표시한다.
|
||||
|
||||
서버의 언어 결정은 기존 LangInterceptor → 요청 범위 LangContext를 재사용한다.
|
||||
앱의 설정 언어가 실제 헤더로 전송되는지는 이 서버 작업에서 보장하지 않는다.
|
||||
|
||||
## 2. 대상 API와 필드
|
||||
|
||||
아래 경로는 모두 GET이다. 기존 응답에 존재하는 필드만 변경하며 설명·태그 같은 새 필드를 추가하지 않는다.
|
||||
|
||||
| ID | API | 대상 |
|
||||
|---|---|---|
|
||||
| LIST-01 | /api/v2/creator-channels/{creatorId}/audio | 오디오 title, 연결된 seriesName, 테마명 |
|
||||
| LIST-02 | /api/v2/creator-channels/{creatorId}/series | 시리즈 title. 기존 연재 요일 언어 처리 유지 |
|
||||
| LIST-03 | /api/v2/audio/contents | 오디오 title, 시리즈 title. 기존 type·정렬·요일 필터 유지 |
|
||||
| LIST-04 | /api/v2/creator-channels/{creatorId}/home | 오디오 카드 title·seriesName, 시리즈 title, 일정 중 AUDIO 유형의 title |
|
||||
| LIST-05 | /api/v2/audio/recommendations | latestAudios, newAndHotAudios, freeAudios, pointAudios, mostCommentedAudios, recommendedAudios의 title |
|
||||
| LIST-06 | /api/v2/audio/rankings | 각 랭킹 항목 title |
|
||||
| LIST-07 | /api/v2/contents | NEW_AND_HOT_AUDIO, FIRST_AUDIO_CONTENT의 title |
|
||||
|
||||
- LIST-05의 originalSeries는 현재 seriesId·coverImageUrl만 반환한다. 번역용 title 필드를 추가하지 않는다.
|
||||
- LIST-04의 라이브 제목, 후원·팬톡 문구 등 오디오·시리즈·테마 외 필드는 대상이 아니다.
|
||||
- /api/v2/home/recommendations 등 다른 경로는 신규 기능 범위가 아니다. 공유 조회 코드 변경 시 기존 동작 회귀를 검증한다.
|
||||
- 관리자 목록, 상세·검색 API, 음성 파일 번역, 설명·태그 응답 확장은 제외한다.
|
||||
|
||||
## 3. 기능 요구사항과 수용 기준
|
||||
|
||||
| ID | 상태/근거 | 요구사항 | 수용 기준 | 연결 |
|
||||
|---|---|---|---|---|
|
||||
| LANG-01 | 사용자 확정 | 대상 API 전체에서 Accept-Language 기준 번역 선택 | 동일 데이터에 ko/en/ja 요청 시 해당 locale 번역 표시 | 모든 Task |
|
||||
| LANG-02 | 기존 동작 유지 | Lang.fromAcceptLanguage 파싱 재사용 | 헤더 누락·빈 값·미지원 값은 ko, en-US는 en, ja-JP는 ja. q 가중치 파싱 확장 없음 | P1-GATE |
|
||||
| TEXT-01 | 사용자 확정 | 번역 없음·빈 문자열·공백 문자열은 원문 표시 | null/빈 문자열/공백별 원문 일치, 다른 언어 번역으로 대체하지 않음 | 모든 Task |
|
||||
| TEXT-02 | 사용자 확정 | 오디오 제목·시리즈 제목/시리즈명·테마명만 적용 | 댓글·닉네임·배너는 기존 값·선택 정책 유지 | 모든 Task |
|
||||
| COMPAT-01 | 기존 기능 유지 | 언어는 표시 문자열에만 영향 | ID·개수·순서·hasNext·권한·성인/차단/구매 필터·가격·이미지 불변 | 각 Gate |
|
||||
| RANK-01 | 구현 제약 | 랭킹 집계와 번역 표시를 분리 | 같은 스냅샷으로 언어만 바꿔 제목 변경, 순위·점수·rankChange 불변 | P2-T2 |
|
||||
| READ-01 | 범위 제약 | 저장된 번역만 조회 | GET에서 Papago 호출·번역 작업 생성·DB 쓰기 없음 | 각 Gate |
|
||||
| ISOLATE-01 | 기존 요청 범위 유지 | 언어가 다른 요청끼리 값 공유 금지 | en→ja→en 요청에서도 언어 혼입 없음 | 각 Gate |
|
||||
|
||||
원문이란 번역 적용 전 해당 경로에서 반환하던 문자열이다.
|
||||
랭킹은 기존 스냅샷 title을 fallback으로 유지하며 최신 콘텐츠 제목으로 갱신하는 별도 기능을 추가하지 않는다.
|
||||
번역 완료 전 원문이 보이는 것은 정상이다. 원문과 요청 언어가 같아도 별도 추정 없이 같은 locale 번역 조회/fallback 규칙을 적용한다.
|
||||
|
||||
## 4. 기술 근거와 최소 변경 방향
|
||||
|
||||
모든 경로는 src/main/kotlin/kr/co/vividnext/sodalive/ 아래를 기준으로 한다.
|
||||
|
||||
| 근거 파일 | 현재 확인한 동작 |
|
||||
|---|---|
|
||||
| i18n/LangInterceptor.kt, i18n/Lang.kt, configs/WebConfig.kt | 전체 경로에서 헤더를 읽고 ko/en/ja로 결정 |
|
||||
| content/translation/ContentTranslationRepository.kt | findByContentIdInAndLocale 일괄 조회가 이미 존재 |
|
||||
| i18n/translation/TranslationReadModelMaterializer.kt | content·series 번역을 renderedPayload에 저장 |
|
||||
| v2/creator/channel/audio/adapter/out/persistence/DefaultCreatorChannelAudioQueryRepository.kt | 오디오 title·seriesName·테마는 요청 locale 번역 우선 |
|
||||
| v2/creator/channel/series/adapter/out/persistence/DefaultCreatorChannelSeriesQueryRepository.kt | 시리즈 번역 우선과 원문 fallback 구현됨 |
|
||||
| v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt | 오디오·시리즈 제목 및 AUDIO 일정 제목은 요청 locale 번역 우선 |
|
||||
| v2/content/all/adapter/out/persistence/DefaultMainContentAllQueryRepository.kt | 오디오·시리즈 제목은 요청 locale 번역 우선 |
|
||||
| v2/content/recommendation/adapter/out/persistence/DefaultAudioRecommendationQueryRepository.kt | 일반 카드 및 mostCommentedAudios 제목은 요청 locale 번역 우선 |
|
||||
| v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt | FIRST_AUDIO_CONTENT 제목은 요청 locale 번역 우선 |
|
||||
| v2/content/ranking/application/AudioRankingQueryService.kt | 요청 locale을 조회 port에 전달하고 snapshot 표시 제목만 번역 |
|
||||
|
||||
기존 QueryService → Port → Persistence 경계를 유지하며 필요한 locale을 전달한다.
|
||||
기존 번역 조인 또는 ID 일괄 조회를 재사용한다. 항목별 단건 조회로 N+1을 만들지 않는다.
|
||||
페이지·정렬·필터 결과를 유지한 채 제목을 매핑한다. 별도 범용 번역 프레임워크·캐시·의존성·DDL은 추가하지 않는다.
|
||||
랭킹/추천 스냅샷 생성 작업에 요청 범위 LangContext를 주입하지 않는다.
|
||||
|
||||
## 5. 검증 시나리오
|
||||
|
||||
1. 같은 원문에 ko/en/ja별 서로 다른 번역을 준비하고 7개 API의 대상 필드가 헤더에 맞는지 확인한다.
|
||||
2. 번역 행 없음, 빈 제목, 공백 제목, 다른 locale 번역만 존재하는 경우 필드별 원문 fallback을 확인한다.
|
||||
3. 오디오 번역만 있고 시리즈 번역이 없는 혼합 사례에서 각 필드가 독립적으로 선택되는지 확인한다.
|
||||
4. 언어를 바꿔도 콘텐츠 ID·순서·페이지·필터·랭킹과 제외 필드가 동일한지 확인한다.
|
||||
5. 실제 HTTP 요청과 DB 조회 테스트로 연결을 검증한다. Mock 응답의 문자열 확인만으로 완료 처리하지 않는다.
|
||||
|
||||
## 6. 결정 기록
|
||||
|
||||
| 날짜 | ID | 구분 | 내용 |
|
||||
|---|---|---|---|
|
||||
| 2026-09-09 | DEC-LIST-01 | 사용자 확정 | 앞서 조사한 조회 위치 모두 Accept-Language에 따른 번역 표시 |
|
||||
| 2026-09-09 | DEC-LIST-02 | 사용자 확정 | 번역 없음·공백이면 원문 표시 |
|
||||
| 2026-09-09 | DEC-LIST-03 | 사용자 확정 | 오디오 제목·시리즈 제목/시리즈명·테마명 대상, 댓글·닉네임·배너 유지 |
|
||||
| 2026-09-09 | DEC-LIST-04 | 기존 동작 유지 | 언어 파싱·기본 언어·인증·조회 조건·응답 스키마 유지 |
|
||||
| 2026-09-09 | DEC-LIST-05 | 사용자 제한 | 문서만 작성, 구현하지 않음 |
|
||||
| 2026-09-09 | DEC-LIST-06 | 사용자 지시 | 현재 브랜치에서 Phase 1 구현 진행. PRD에도 구현 상태를 최종 갱신 |
|
||||
| 2026-09-09 | DEC-LIST-07 | 사용자 지시 | 로컬 수동 테스트가 불가능하므로 Phase 2까지 자동 검증을 완료하고 test 서버에서 HTTP 검증 |
|
||||
|
||||
제품 결정이 필요한 미결 항목은 없다. test 서버 배포 후 대상 7개 API의 수동 HTTP 검증이 남아 있다.
|
||||
Reference in New Issue
Block a user