feat(i18n): 일본어 문자열을 최신화한다
This commit is contained in:
548
docs/20260811_일본어_string_resource_반영/plan-task.md
Normal file
548
docs/20260811_일본어_string_resource_반영/plan-task.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# 일본어 string_resource 반영 Goal 실행형 구현 계획
|
||||
|
||||
| 문서 항목 | 내용 |
|
||||
|---|---|
|
||||
| 상태 | 구현 완료 |
|
||||
| 작성일 | `2026-08-11` |
|
||||
| 요구사항 기준 | `docs/20260811_일본어_string_resource_반영/prd.md` |
|
||||
| API 기준 | 해당 없음 |
|
||||
| 현재 Phase | Phase 2 일본어 리소스 반영 완료 |
|
||||
| 현재 활성 Goal | 없음 |
|
||||
|
||||
## 목표
|
||||
|
||||
CSV의 KR/JP 문자열 리소스를 현재 iOS `I18n.swift` 구조에 맞춰 안전하게 반영하되, 기존 한국어 리소스가 없는 항목은 신규 리소스로 추가하지 않는다.
|
||||
|
||||
## 현재 상태
|
||||
|
||||
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|
||||
|---:|---|---:|---|---|
|
||||
| 1 | 완료 | `3/3` | `P1-GATE` 완료 | 없음 |
|
||||
| 2 | 완료 | `3/3` | `P2-GATE` 완료 | 없음 |
|
||||
|
||||
- 동시에 하나의 미완료 goal만 운용한다.
|
||||
- 완료된 Task와 검증 기록은 되돌리거나 삭제하지 않는다. 후속 수정은 회귀 수정 Task와 새 goal ID를 추가한다.
|
||||
- 사용자가 명시적으로 요청하지 않으면 goal에 token budget을 설정하지 않는다.
|
||||
|
||||
## 범위
|
||||
|
||||
### 포함
|
||||
|
||||
- `docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv`의 KR/JP 242개 데이터 행 분석.
|
||||
- `SodaLive/Sources/I18n/I18n.swift`의 기존 `pick(ko:en:ja:)` 항목 중 CSV KR과 exact 또는 placeholder 정규화 기준으로 매칭되는 항목의 일본어 값 갱신 계획.
|
||||
- CSV 셀 안의 실제 줄바꿈과 HTML br 표기를 Swift 문자열 줄바꿈 escape `\n`으로 변환하는 계획.
|
||||
- placeholder 후보와 오탈자 후보의 확정 결정 기록.
|
||||
- 구현 후 `SodaLive`, `SodaLive-dev` Debug 빌드와 일본어 대표 화면 수동 QA 계획.
|
||||
|
||||
### 제외
|
||||
|
||||
- 이 문서 작성 턴에서 코드 수정.
|
||||
- `String Catalog` 신규 도입 또는 `I18n.swift` 구조 개편.
|
||||
- CSV에 없는 영어 번역 작성.
|
||||
- 기존 한국어 리소스가 없는 CSV 항목의 신규 리소스 추가.
|
||||
- UI 레이아웃, 폰트, API, 결제 SDK 입력값 변경.
|
||||
|
||||
## 기술적 제약
|
||||
|
||||
- 기술 스택: Swift, SwiftUI, Xcode, CocoaPods, `SodaLive.xcworkspace`.
|
||||
- 아키텍처: 기존 `I18n.Module.Domain.Role` 네임스페이스와 `pick(ko:en:ja:)` 헬퍼를 유지한다.
|
||||
- 데이터·보안: 토큰, 인증 헤더, API path, SDK 고정값은 번역 대상으로 보지 않는다.
|
||||
- 호환성: 기존 iOS 앱 스킴 `SodaLive`, `SodaLive-dev`를 기준으로 검증한다.
|
||||
- 의존성: 신규 dependency를 추가하지 않는다.
|
||||
- 계약: 제공되지 않은 영어 문구, enum, 서버 상태, 오류 키를 추정하지 않는다. 기존 한국어 리소스가 exact 또는 placeholder 정규화 기준으로 매칭되지 않는 항목은 신규 키를 만들지 않는다.
|
||||
- 검증: focused 정적 점검에서 시작해 `xcodebuild` 빌드와 수동 QA로 확장한다.
|
||||
- 테스트 제약: 현재 가이드 기준 앱 타깃 중심 구성이고 테스트 번들 타깃이 확인되지 않으므로, 테스트 액션 미구성은 코드 실패로 판정하지 않는다.
|
||||
|
||||
## Task TDD 작성 규칙
|
||||
|
||||
문자열 리소스 반영은 대부분 정적 데이터 변경이며 현재 테스트 타깃이 확인되지 않는다. 따라서 구현 Task는 TDD 예외 사유와 대체 검증 방법을 명시한다. 자동 테스트가 추가 가능한 구조가 확인되면 가장 작은 실패 test를 먼저 작성하고 `RED → GREEN → REFACTOR` 순서를 따른다.
|
||||
|
||||
## Phase 1 매핑 확정
|
||||
|
||||
**Phase 결과:** CSV 242행의 처리 방식을 `기존 키 갱신`, `placeholder 정규화 후 기존 키 갱신`, `오탈자 보정 후 기존 키 갱신`, `기존 리소스 없음으로 제외` 중 하나로 분류한다.
|
||||
|
||||
**선행조건:** PRD의 Open Questions 사용자 결정이 반영되어 있다.
|
||||
|
||||
**Phase 완료 조건:** `P1-T1`~`P1-T3`과 `P1-GATE` 완료, Progress 기록 누적.
|
||||
|
||||
### 구현 항목
|
||||
|
||||
#### Task 1.1 CSV 파싱 기준 확정
|
||||
|
||||
**Goal 실행 `P1-T1`:** CSV 데이터 행 수, 누락, CSV 셀 안의 실제 줄바꿈, HTML br 표기, placeholder 후보를 재현 가능한 명령으로 확정한다.
|
||||
|
||||
- **시작 조건:** `kr_jp_string_resource.csv` 존재.
|
||||
- **완료 증거:** CSV 파싱 결과가 Progress에 기록되고 PRD의 수치와 일치한다.
|
||||
- **범위 밖:** 앱 코드 수정.
|
||||
- **TDD 예외 사유:** production 동작을 변경하지 않는 read-only 데이터 점검 Task다.
|
||||
- **대체 검증 방법:** Python `csv.reader`로 CSV를 파싱해 행 수와 후보 목록을 출력한다.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Read: `docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv`
|
||||
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: CSV `KR`, `JP` 컬럼.
|
||||
- Produces: Phase 1에서 사용할 행 수, CSV 실제 줄바꿈, HTML br 표기와 품질 후보 목록.
|
||||
|
||||
- [x] CSV를 정규 파서로 읽어 데이터 행 수를 확인한다.
|
||||
|
||||
```bash
|
||||
python3 - <<'PY'
|
||||
import csv
|
||||
from pathlib import Path
|
||||
p = Path('docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv')
|
||||
with p.open(newline='', encoding='utf-8') as f:
|
||||
rows = list(csv.reader(f))
|
||||
data = rows[1:]
|
||||
print(len(data))
|
||||
print(sum(1 for row in data if len(row) < 3 or not row[1].strip() or not row[2].strip()))
|
||||
PY
|
||||
```
|
||||
|
||||
**Expected:** 첫 줄 `242`, 둘째 줄 `0`.
|
||||
|
||||
- [x] CSV 셀 안의 실제 줄바꿈 포함 행과 placeholder 유사 행을 산출해 Progress에 기록한다.
|
||||
|
||||
```bash
|
||||
python3 - <<'PY'
|
||||
import csv
|
||||
from pathlib import Path
|
||||
p = Path('docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv')
|
||||
with p.open(newline='', encoding='utf-8') as f:
|
||||
reader = csv.reader(f)
|
||||
next(reader)
|
||||
rows = [(i, row[1], row[2]) for i, row in enumerate(reader, start=2)]
|
||||
print('multiline', sum('\n' in kr or '\n' in jp for _, kr, jp in rows))
|
||||
print('placeholder_like', sum(any(t in kr + jp for t in ['{{', '}}', '{', '}', 'n%', 'n위', '0:00']) for _, kr, jp in rows))
|
||||
PY
|
||||
```
|
||||
|
||||
**Expected:** `multiline 33`, `placeholder_like 12`.
|
||||
|
||||
`multiline` 대상은 Swift 코드 반영 시 CSV 셀 안의 실제 줄바꿈을 그대로 붙여넣지 않고 문자열 escape `\n`으로 작성한다.
|
||||
|
||||
- [x] HTML br 표기 수를 산출해 Progress에 기록한다.
|
||||
|
||||
```bash
|
||||
python3 - <<'PY'
|
||||
import csv
|
||||
from pathlib import Path
|
||||
p = Path('docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv')
|
||||
tag = chr(60) + 'br' + chr(62)
|
||||
with p.open(newline='', encoding='utf-8') as f:
|
||||
reader = csv.reader(f)
|
||||
next(reader)
|
||||
rows = [(i, row[1], row[2]) for i, row in enumerate(reader, start=2)]
|
||||
print('html_br_like', sum(tag.lower() in (kr + jp).lower() for _, kr, jp in rows))
|
||||
PY
|
||||
```
|
||||
|
||||
**Expected:** 현재 CSV 기준 `html_br_like 0`. 향후 값이 1 이상이면 해당 항목은 앱 문자열에 HTML br literal이 아니라 `\n`으로 반영한다.
|
||||
|
||||
#### Task 1.2 기존 `I18n.swift` 매칭 목록 확정
|
||||
|
||||
**Goal 실행 `P1-T2`:** CSV KR과 기존 `I18n.swift`의 `ko:` literal 및 placeholder 정규화 매칭 수를 확정한다.
|
||||
|
||||
- **시작 조건:** `P1-T1` 완료.
|
||||
- **완료 증거:** exact 매칭, placeholder 정규화 추가 매칭, 기존 리소스 없음 제외 대상과 중복 JP 상이 항목 수가 Progress에 기록된다.
|
||||
- **범위 밖:** `ja` 값 수정.
|
||||
- **TDD 예외 사유:** production 동작을 변경하지 않는 read-only 매핑 점검 Task다.
|
||||
- **대체 검증 방법:** CSV KR literal과 placeholder 정규화 문자열을 Swift `ko:` literal 및 interpolation 정규화 문자열과 대조한다.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Read: `docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv`
|
||||
- Read: `SodaLive/Sources/I18n/I18n.swift`
|
||||
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: CSV 행 목록, `I18n.swift` source.
|
||||
- Produces: exact 매칭 수, placeholder 정규화 추가 매칭 수, 기존 리소스 없음 제외 대상 수, 중복 JP 상이 항목 수.
|
||||
|
||||
- [x] 기존 `ko:` literal 매칭 수와 placeholder 정규화 매칭 수를 산출한다.
|
||||
|
||||
```bash
|
||||
python3 - <<'PY'
|
||||
import csv
|
||||
from pathlib import Path
|
||||
csv_path = Path('docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv')
|
||||
i18n = Path('SodaLive/Sources/I18n/I18n.swift').read_text(encoding='utf-8')
|
||||
matched = 0
|
||||
missing = 0
|
||||
with csv_path.open(newline='', encoding='utf-8') as f:
|
||||
reader = csv.reader(f)
|
||||
next(reader)
|
||||
for row in reader:
|
||||
kr = row[1]
|
||||
swift = kr.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')
|
||||
if f'ko: "{swift}"' in i18n:
|
||||
matched += 1
|
||||
else:
|
||||
missing += 1
|
||||
print('matched', matched)
|
||||
print('missing', missing)
|
||||
PY
|
||||
```
|
||||
|
||||
**Expected:** 현재 기준 `matched 103`, `missing 139`. placeholder 정규화 스캔은 `direct 103`, `normalized_only 0`, `duplicate_different_ja 12`를 함께 기록한다.
|
||||
|
||||
- [x] `missing` 항목은 신규 키로 만들지 않고 `기존 리소스 없음으로 제외` 처리한다.
|
||||
|
||||
#### Task 1.3 질문 결정 반영
|
||||
|
||||
**Goal 실행 `P1-T3`:** PRD Open Questions에 반영된 사용자 결정을 기준으로 구현 가능 범위를 확정한다.
|
||||
|
||||
- **시작 조건:** `P1-T2` 완료.
|
||||
- **완료 증거:** `OQ-001`~`OQ-005`가 확정으로 표시되고 구현 가능 범위가 정해진다.
|
||||
- **범위 밖:** 기존 한국어 리소스가 없는 항목의 신규 리소스 추가.
|
||||
- **TDD 예외 사유:** 제품·번역 의사결정 Task다.
|
||||
- **대체 검증 방법:** PRD Open Questions와 회신 내용을 대조하고 Decision Log를 갱신한다.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/20260811_일본어_string_resource_반영/prd.md`
|
||||
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: `OQ-001`~`OQ-005`.
|
||||
- Produces: Phase 2에서 구현 가능한 기존 키 갱신 범위와 제외 목록.
|
||||
|
||||
- [x] `OQ-001`: `しいシリーズ`는 `新しいシリーズ`로 보정 반영한다.
|
||||
- [x] `OQ-002`: placeholder는 기존 한국어 리소스가 있는 항목만 Swift 함수 인자로 변환한다.
|
||||
- [x] `OQ-006`: `{{...}}`, `{...}`, `n%`, `n위`, `0:00` placeholder는 Swift interpolation과 정규화 매칭한다.
|
||||
- [x] `OQ-007`: 중복 KR 또는 중복 정규화 KR의 기존 JP가 서로 다르면 CSV JP를 우선한다.
|
||||
- [x] `OQ-008`: HTML br 표기는 Swift 문자열 줄바꿈 `\n`으로 변환한다.
|
||||
- [x] `OQ-009`: CSV 셀 안의 실제 줄바꿈은 Swift 문자열 줄바꿈 escape `\n`으로 변환한다.
|
||||
- [x] `OQ-003`: KR 오탈자 후보는 보정하지 않고 JP만 반영한다.
|
||||
- [x] `OQ-004`: 기존 `I18n.swift`에 없는 139개 항목은 신규 키로 추가하지 않는다.
|
||||
- [x] `OQ-005`: 없는 리소스는 추가하지 않으므로 신규 `en`도 작성하지 않는다.
|
||||
|
||||
### 완료 조건
|
||||
|
||||
- [x] `P1-T1`, `P1-T2`, `P1-T3`의 체크박스와 완료 증거가 모두 충족됐다.
|
||||
- [x] Phase 2 구현 범위가 exact 또는 placeholder 정규화 기준으로 기존 한국어 리소스가 있는 항목으로만 구성됐다.
|
||||
- [x] CSV 실제 줄바꿈이 있는 항목은 Swift 문자열 `\n` 변환 대상으로 분류됐다.
|
||||
- [x] HTML br 표기가 있는 항목은 `\n` 변환 대상으로 분류됐다.
|
||||
- [x] 제외 항목은 추정 구현 없이 PRD Open Questions와 Decision Log에 남았다.
|
||||
|
||||
### 검증 방법
|
||||
|
||||
#### Phase 1 Gate
|
||||
|
||||
**Goal 실행 `P1-GATE`:** CSV 입력, 기존 키 매칭, 질문 결정 처리 상태를 최종 판정한다.
|
||||
|
||||
- **시작 조건:** Phase 1의 모든 활성 Task goal 완료.
|
||||
- **완료 증거:** 아래 명령 결과와 PRD Decision Log 갱신.
|
||||
- **범위 밖:** 앱 코드 수정.
|
||||
|
||||
```bash
|
||||
python3 - <<'PY'
|
||||
import csv
|
||||
from pathlib import Path
|
||||
p = Path('docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv')
|
||||
with p.open(newline='', encoding='utf-8') as f:
|
||||
reader = csv.reader(f)
|
||||
header = next(reader)
|
||||
rows = list(reader)
|
||||
assert header[1:3] == ['KR', 'JP']
|
||||
assert len(rows) == 242
|
||||
assert all(len(row) >= 3 and row[1].strip() and row[2].strip() for row in rows)
|
||||
print('CSV gate passed')
|
||||
PY
|
||||
```
|
||||
|
||||
**Expected:** `CSV gate passed`.
|
||||
|
||||
수동 검증:
|
||||
|
||||
- [x] PRD `OQ-001`~`OQ-009`의 상태가 모두 확정이고 Decision Log가 최신 회신을 반영한다.
|
||||
- [x] Phase 2 구현 대상에 기존 한국어 리소스가 없는 항목이 포함되지 않았다.
|
||||
|
||||
## Phase 2 일본어 리소스 반영
|
||||
|
||||
**Phase 결과:** exact 또는 placeholder 정규화 기준으로 기존 한국어 리소스가 있는 확정 CSV JP 문구가 `I18n.swift`에 반영되고, 일본어 환경 대표 화면에서 문구가 확인된다.
|
||||
|
||||
**선행조건:** `P1-GATE` 완료.
|
||||
|
||||
**Phase 완료 조건:** `P2-T1`~`P2-T3`과 `P2-GATE` 완료, Progress 기록 누적.
|
||||
|
||||
### 구현 항목
|
||||
|
||||
#### Task 2.1 기존 키 `ja` 값 갱신
|
||||
|
||||
**Goal 실행 `P2-T1`:** CSV KR과 기존 `I18n.swift` `ko:`가 exact 또는 placeholder 정규화 기준으로 일치하는 확정 항목의 `ja` 값을 CSV JP로 갱신한다.
|
||||
|
||||
- **시작 조건:** `P1-GATE` 완료, 기존 리소스 없음 제외 목록 확정.
|
||||
- **완료 증거:** 기존 `en` 값이 유지되고, 갱신 대상 `ja` 값이 CSV와 일치한다.
|
||||
- **범위 밖:** 신규 키 추가, 호출부 변경, 영어 문구 변경.
|
||||
- **TDD 예외 사유:** 기존 string map 값 갱신이며 테스트 타깃이 확인되지 않는다.
|
||||
- **대체 검증 방법:** 갱신 전후 `ko/en/ja` tuple을 스크립트로 대조하고 `xcodebuild`로 컴파일을 확인한다.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `SodaLive/Sources/I18n/I18n.swift`
|
||||
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: Phase 1의 기존 키 갱신 대상 목록.
|
||||
- Produces: CSV JP가 반영된 `I18n.swift`.
|
||||
|
||||
- [x] 갱신 대상만 수정하고 `en:` 값을 변경하지 않는다.
|
||||
- [x] `OQ-001`의 `しいシリーズ`는 `新しいシリーズ`로 보정한 JP를 사용한다. 기존 리소스 미매칭 항목이므로 신규 키는 추가하지 않았다.
|
||||
- [x] CSV 셀 안의 실제 줄바꿈은 Swift 문자열에 실제 개행으로 붙여넣지 않고 `\n` escape로 작성한다.
|
||||
- [x] HTML br 표기는 앱 문자열에 그대로 두지 않고 `\n`으로 변환한다.
|
||||
- [x] placeholder 정규화로 매칭된 항목은 Swift 함수 인자 순서를 유지하고 `ja` 문장 내 인자 위치만 CSV JP 기준으로 반영한다.
|
||||
- [x] 기존 한국어 리소스가 exact 또는 placeholder 정규화 기준으로 없는 항목은 수정 대상에서 제외한다.
|
||||
- [x] 동일 KR 또는 동일 정규화 KR이 여러 키에 존재하고 기존 JP가 서로 다르면 CSV JP를 우선 반영한다.
|
||||
|
||||
#### Task 2.2 기존 리소스 없음 항목 제외 처리
|
||||
|
||||
**Goal 실행 `P2-T2`:** 기존 `I18n.swift`에 exact 또는 placeholder 정규화 기준으로 없는 CSV 항목을 신규 키로 추가하지 않고 제외 목록으로 문서화한다.
|
||||
|
||||
- **시작 조건:** `P2-T1` 완료, `OQ-004`, `OQ-005` 확정.
|
||||
- **완료 증거:** 제외 대상 수와 제외 사유가 Progress에 기록되고, 신규 `I18n` 키가 추가되지 않는다.
|
||||
- **범위 밖:** 영어 문구 추정, 호출부 없는 키 추가, 공통 abstraction 확장.
|
||||
- **TDD 예외 사유:** 문서화와 범위 제외 Task다.
|
||||
- **대체 검증 방법:** `git diff`와 정적 검색으로 `I18n.swift` 신규 키 추가가 없고 기존 키 `ja` 값만 변경됐는지 확인한다.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Read: `SodaLive/Sources/I18n/I18n.swift`
|
||||
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: Phase 1 기존 리소스 없음 제외 목록.
|
||||
- Produces: 제외 대상과 제외 사유 기록.
|
||||
|
||||
- [x] direct exact 미매칭 139개 중 분할 리소스 매핑 2개를 제외한 137개 항목은 신규 키로 추가하지 않는다.
|
||||
- [x] CSV에만 있는 placeholder 문구도 정규화 후 기존 한국어 리소스가 없으면 제외한다.
|
||||
- [x] 제외 대상은 Progress에 `기존 한국어 리소스 없음` 사유로 기록한다.
|
||||
- [x] `SodaLive/Sources/**` 호출부를 신규 `I18n` 키로 바꾸지 않는다.
|
||||
|
||||
#### Task 2.3 정적 점검과 문서 동기화
|
||||
|
||||
**Goal 실행 `P2-T3`:** 반영 결과와 문서를 동기화하고 누락·오반영 후보를 제거한다.
|
||||
|
||||
- **시작 조건:** `P2-T1`, `P2-T2` 완료.
|
||||
- **완료 증거:** 정적 검색, 문서 Progress, Decision Log가 최신 상태다.
|
||||
- **범위 밖:** Gate 실패와 무관한 추가 번역.
|
||||
- **TDD 예외 사유:** 정적 검증과 문서화 Task다.
|
||||
- **대체 검증 방법:** `rg`와 Python 대조 스크립트로 CSV 반영 상태를 확인한다.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Read: `SodaLive/Sources/I18n/I18n.swift`
|
||||
- Modify: `docs/20260811_일본어_string_resource_반영/prd.md`
|
||||
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: Phase 2 변경 diff.
|
||||
- Produces: 최신 Progress, Decision Log, 발견된 문제 목록.
|
||||
|
||||
- [x] `rg -n 'pick\(ko:.*ja:' SodaLive/Sources/I18n/I18n.swift`와 diff로 변경 구간을 점검한다.
|
||||
- [x] CSV 확정 항목과 `I18n.swift` `ja` 값을 exact/placeholder 정규화 기준으로 대조한다.
|
||||
- [x] CSV 실제 줄바꿈 대상이 `I18n.swift`에서 Swift 문자열 `\n`으로 작성됐는지 대조한다.
|
||||
- [x] HTML br 표기가 `I18n.swift`에 literal로 남지 않았는지 대조한다.
|
||||
- [x] 중복 KR 또는 중복 정규화 KR의 `ja` 값이 CSV JP로 통일됐는지 대조한다.
|
||||
- [x] 제외된 항목은 `발견된 문제` 또는 PRD Open Questions에 연결한다.
|
||||
|
||||
### 완료 조건
|
||||
|
||||
- [x] `P2-T1`, `P2-T2`, `P2-T3`의 체크박스와 완료 증거가 모두 충족됐다.
|
||||
- [x] 확정 항목은 기존 키 갱신 또는 명시적 제외로 추적된다.
|
||||
- [x] 기존 영어 문구와 기능 동작이 바뀌지 않았다.
|
||||
|
||||
### 검증 방법
|
||||
|
||||
#### Phase 2 Gate
|
||||
|
||||
**Goal 실행 `P2-GATE`:** 일본어 문자열 반영, 빌드, 대표 화면 수동 QA를 최종 판정한다.
|
||||
|
||||
- **시작 조건:** Phase 2의 모든 활성 Task goal 완료.
|
||||
- **완료 증거:** 아래 명령과 수동 검증 결과를 Progress에 기록.
|
||||
- **범위 밖:** Gate 통과를 위한 test 삭제·완화, 관련 없는 리팩터링.
|
||||
|
||||
```bash
|
||||
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
|
||||
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
|
||||
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test
|
||||
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
|
||||
```
|
||||
|
||||
**Expected:** 두 Debug 빌드는 `** BUILD SUCCEEDED **`. 테스트 액션이 미구성되어 실패하면 `Scheme ... is not currently configured for the test action.`을 스킴 제약으로 기록한다.
|
||||
|
||||
수동 검증:
|
||||
|
||||
- [x] 앱 언어 `ja`에서 홈 탭 `ホーム`, `コンテンツ`, `トーク`, `マイページ` 표시를 확인한다.
|
||||
- [x] 변경된 개행 문구는 실제 `I18n.swift` 런타임 드라이버에서 CSV 기준 줄바꿈으로 확인한다.
|
||||
- [x] CSV 셀 안의 실제 줄바꿈이 런타임 문자열에서 줄바꿈으로 확인된다.
|
||||
- [x] HTML br 표기 원문이 소스와 런타임 문자열에 남지 않았다.
|
||||
- [x] 크리에이터명·랭킹·퍼센트 placeholder 문구가 실제 값과 함께 깨지지 않는다.
|
||||
- [x] 구매/대여 완료 문자열이 일본어 런타임 값으로 확인된다. 기존 리소스가 없는 실패 문구는 제외 정책을 따른다.
|
||||
- [x] 기존 리소스 없음으로 제외한 항목이 신규 키로 추가되지 않았다.
|
||||
|
||||
## 실행 순서와 의존성
|
||||
|
||||
| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 |
|
||||
|---:|---|---|---|---|
|
||||
| 1 | `P1-T1` | 없음 | 아니요 | CSV 형식 문제를 PRD에 기록 |
|
||||
| 2 | `P1-T2` | `P1-T1` | 아니요 | 매칭 방식 보정 후 재실행 |
|
||||
| 3 | `P1-T3` | `P1-T2` | 아니요 | 제외 항목을 Phase 2 범위에서 제거 |
|
||||
| 4 | `P1-GATE` | Phase 1 Task 전체 | 아니요 | 실패 소유 Task 재실행 |
|
||||
| 5 | `P2-T1` | `P1-GATE` | 아니요 | 기존 키 갱신 대상만 축소 |
|
||||
| 6 | `P2-T2` | `P2-T1` | 아니요 | 제외 목록을 보정하고 신규 키 추가 여부를 재검증 |
|
||||
| 7 | `P2-T3` | `P2-T1`, `P2-T2` | 아니요 | 누락 소유 Task로 되돌림 |
|
||||
| 8 | `P2-GATE` | Phase 2 Task 전체 | 아니요 | 실패 소유 Task의 회귀 수정 goal 생성 |
|
||||
|
||||
```text
|
||||
P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-T3 → P2-GATE
|
||||
```
|
||||
|
||||
## 변경 금지 항목
|
||||
|
||||
- CSV에 없는 영어 문구를 임의 작성하지 않는다.
|
||||
- 기존 한국어 리소스가 없는 CSV 항목을 신규 리소스로 추가하지 않는다.
|
||||
- `I18n.pick` 구조를 개편하지 않는다.
|
||||
- `Pods/**`, `generated/**`, `build/**`를 수정하지 않는다.
|
||||
- 요청 범위 밖 UI 레이아웃, 폰트, API, 결제 SDK 입력값을 변경하지 않는다.
|
||||
- test를 삭제·skip·완화하거나 타입 오류를 우회해 Gate를 통과시키지 않는다.
|
||||
- 토큰, 이메일 실제값, signed URL 같은 민감정보를 log·fixture·문서에 기록하지 않는다.
|
||||
|
||||
## 의사결정 및 중단 규칙
|
||||
|
||||
- PRD와 CSV가 충돌하면 PRD Decision Log에 근거를 남긴 뒤 계획을 갱신한다.
|
||||
- 기존 한국어 리소스가 없는 항목은 구현 대상에서 제외한다.
|
||||
- 구현 범위가 바뀌면 PRD를 먼저 보강하고 이 문서 체크리스트를 갱신한 뒤 코드를 수정한다.
|
||||
- 같은 차단 사유가 3회 연속 반복되고 독립 작업도 불가능할 때만 goal을 `blocked`로 갱신한다.
|
||||
- 완료 증거와 Progress 기록까지 충족한 뒤에만 goal을 `complete`로 갱신한다.
|
||||
|
||||
## Progress
|
||||
|
||||
기존 기록을 삭제하거나 덮어쓰지 않고 실제 실행 결과를 차수별로 누적한다.
|
||||
|
||||
### P1-T1~P1-GATE 실행 — 2026-08-11
|
||||
|
||||
- 상태: 완료
|
||||
- 무엇을: CSV 구조, 실제 줄바꿈, placeholder, exact/정규화 매핑, 중복 JP와 제외 대상을 확정했다.
|
||||
- 왜: literal 매칭만으로는 런타임 값과 분할 문자열을 놓칠 수 있어 구현 전에 확정 분류가 필요했다.
|
||||
- 어떻게:
|
||||
- CSV Gate — 데이터 242행, 누락 0건, 실제 줄바꿈 33건, placeholder 12건, CSV KR 중복 0건.
|
||||
- 매핑 스캔 — exact 103행, placeholder 정규화 추가 0행, 기존 리소스 없음 139행.
|
||||
- 분할 리소스 확인 — 랭킹 문구와 전체 오디오 소장률 문구를 기존 함수/접두·접미 리소스 후보로 추가 확인.
|
||||
- 중복 리소스 확인 — 같은 KR의 기존 JP 상이 항목 12건을 CSV JP 우선 대상으로 확정.
|
||||
- RED `mapping-validator` — 현재 JP 불일치 64건을 검출하고 exit code 1 확인.
|
||||
- 남은 항목: P2 일본어 JP 값 갱신과 GREEN 검증.
|
||||
- 다음 행동: `P2-T1`에서 기존 `en`과 호출부를 유지하고 `ja`만 CSV 우선값으로 갱신한다.
|
||||
|
||||
### P2-T1~P2-GATE 실행 — 2026-08-11
|
||||
|
||||
- 상태: 완료
|
||||
- 무엇을: 기존 `I18n.swift`의 `ja` literal 64건을 CSV JP 우선값으로 갱신하고 정적·런타임·빌드·시뮬레이터 검증을 완료했다.
|
||||
- 왜: exact 매핑, 중복 KR, 분할 placeholder 리소스에 CSV 승인 일본어를 일관되게 적용하기 위해서다.
|
||||
- 어떻게:
|
||||
- RED `mapping-validator` — 변경 전 JP 불일치 64건, exit code 1.
|
||||
- GREEN `mapping-validator` — 변경 후 불일치 0건, `ko`/`en` literal 동일, `pick` literal 수 `1343 → 1343`.
|
||||
- 변경 범위 — `I18n.swift` 64 insertions/64 deletions, 모두 기존 `ja` literal 교체. 신규 키·호출부 변경 0건.
|
||||
- 분류 — exact CSV 103행, 분할 리소스 2행, 기존 리소스 없음 제외 137행.
|
||||
- 줄바꿈 — 매핑된 multiline occurrence 4건 모두 Swift `\n` escape, HTML br literal 0건.
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` — 성공, `** BUILD SUCCEEDED **`.
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` — 성공, `** BUILD SUCCEEDED **`.
|
||||
- 두 스킴 `test` — `Scheme ... is not currently configured for the test action.` 확인. 테스트 액션 미구성 제약이며 빌드 실패가 아니다.
|
||||
- 실제 `I18n.swift` Swift 런타임 드라이버 — 일본어 탭, 랭킹 interpolation, 소장률 interpolation, CSV `\n`, 구매/대여 완료 문자열 PASS.
|
||||
- iPhone 15 Pro Simulator, 앱 설정 `ja` — 스플래시와 홈 화면 렌더링 성공. `ホーム`, `コンテンツ`, `トーク`, `マイページ`, `新着クリエイター` 표시 확인.
|
||||
- 시각 QA Oracle 2건 — 기능/디자인 PASS, CJK 정밀도 PASS, blocking 0건.
|
||||
- compiled app artifact — `SodaLive`, `SodaLive-dev` 바이너리에서 대표 일본어 문자열 포함 확인.
|
||||
- 증거:
|
||||
- `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive-ja-evidence/launch-ja.png`
|
||||
- `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive-ja-evidence/launch-ja-settled.png`
|
||||
- 남은 항목: 없음.
|
||||
- 다음 행동: 최종 reviewer gate와 문서 체크박스 정합성을 확인한다.
|
||||
|
||||
### Phase 2 최종 리뷰 — 2026-08-11
|
||||
|
||||
- 상태: 판정 완료
|
||||
- 무엇을: 구현 diff, CSV 매핑, PRD·계획 문서의 완료 근거를 독립 reviewer가 재검증했다.
|
||||
- 왜: Phase 2 완료 판정 전에 요구사항 누락과 문서·코드 불일치를 차단하기 위해서다.
|
||||
- 어떻게:
|
||||
- Oracle 최종 판정 — `APPROVE`, blocker 0건, confidence High.
|
||||
- 독립 확인 — `ja` literal 64건만 변경, exact 103행·분할 2행·제외 137행, 매핑 불일치 0건.
|
||||
- 리뷰 보고서 — `reviews/phase2-japanese-string-resource.md`.
|
||||
- 남은 항목: 없음.
|
||||
- 다음 행동: 최종 diff와 working tree 상태를 확인하고 종료한다.
|
||||
|
||||
### 문서 생성 — 2026-08-11
|
||||
|
||||
- 상태: 완료
|
||||
- 무엇을: CSV 기반 일본어 string_resource 반영 PRD와 Goal 실행형 계획 문서를 생성했다.
|
||||
- 왜: 사용자 요청이 구현 전 PRD와 계획문서 생성으로 한정되어 있으며, 모호한 항목을 구현 전에 질문/보류해야 하기 때문이다.
|
||||
- 어떻게:
|
||||
- `docs/sample/sample-prd.md` 확인 — PRD 구조 기준 확인.
|
||||
- `docs/sample/sample-plan-task.md` 확인 — Phase/Task/Goal/검증 기록 구조 확인.
|
||||
- `docs/agent-guides/documentation-policy.md` 확인 — PRD와 계획문서 위치·작성 규칙 확인.
|
||||
- `rg --files | rg 'I18n|String|strings|xcstrings|lproj|Localizable|resource'` — 문자열 리소스 관련 파일 위치 확인.
|
||||
- `codegraph_node(file="SodaLive/Sources/I18n/I18n.swift")` — 현재 `I18n.swift` 구조 확인.
|
||||
- `python3` CSV 파싱 — 데이터 242행, KR 누락 0건, JP 누락 0건, 개행 33행, placeholder 유사 12행 확인.
|
||||
- `python3` KR literal 매칭 — 기존 `I18n.swift` 매칭 103건, 미매칭 139건 확인.
|
||||
- 남은 항목: 사용자 결정 반영 전 기록이며, 최신 결정은 아래 `Open Questions 결정 반영` 기록을 따른다.
|
||||
- 다음 행동: Phase 1의 `P1-T1`부터 실행하되, 기존 한국어 리소스가 없는 항목은 구현 범위에서 제외한다.
|
||||
|
||||
### Open Questions 결정 반영 — 2026-08-11
|
||||
|
||||
- 상태: 완료
|
||||
- 무엇을: 사용자 회신에 따라 `OQ-001`~`OQ-005`를 모두 확정 상태로 갱신하고, 신규 리소스 추가 계획을 제외 처리로 바꿨다.
|
||||
- 왜: 기존 한국어 리소스가 없는 항목은 반영하지 않는다는 결정이 내려졌으므로 신규 키 후보 처리와 신규 `en` 작성 계획을 제거해야 하기 때문이다.
|
||||
- 어떻게:
|
||||
- `OQ-001` — `しいシリーズ`는 오타로 확정하고 `新しいシリーズ`로 보정 반영하도록 기록.
|
||||
- `OQ-002` — placeholder는 기존 한국어 리소스가 있는 항목만 Swift 함수 인자로 변환하도록 기록.
|
||||
- `OQ-003` — 한국어 리소스는 보정하지 않고 JP만 반영하도록 기록.
|
||||
- `OQ-004`, `OQ-005` — 기존 한국어 리소스가 없는 항목과 없는 리소스는 추가하지 않도록 기록.
|
||||
- 남은 항목: 코드 반영은 아직 수행하지 않음.
|
||||
- 다음 행동: 구현 요청이 들어오면 `P1-T1`부터 실행하고, `P2-T2`에서는 신규 키 추가가 없음을 검증한다.
|
||||
|
||||
## Decision Log
|
||||
|
||||
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
|
||||
|---|---|---|---|---|---|
|
||||
| `2026-08-11` | `DEC-001` | 확정 | 문서는 CSV가 있는 폴더에 `prd.md`, `plan-task.md`로 생성한다. | 사용자 직접 지시 | 전체 |
|
||||
| `2026-08-11` | `DEC-002` | 확정 | 이번 턴에서는 코드 구현을 하지 않는다. | 사용자 직접 지시 | 전체 |
|
||||
| `2026-08-11` | `DEC-003` | 폐기 | 기존 `I18n.swift` 매칭 항목은 `ja`만 갱신하고 신규 키는 호출부와 영어 기준이 있는 경우로 제한한다. | 사용자 결정으로 기존 한국어 리소스가 없는 항목은 추가하지 않기로 변경 | `P2-T1`, `P2-T2` |
|
||||
| `2026-08-11` | `DEC-004` | 확정 | `しいシリーズ`는 `新しいシリーズ`로 보정한다. | 사용자 확인 | `P2-T1` |
|
||||
| `2026-08-11` | `DEC-005` | 확정 | placeholder 문구는 기존 한국어 리소스가 있는 항목만 Swift 함수 인자로 변환한다. | 사용자 결정 | `P2-T1` |
|
||||
| `2026-08-11` | `DEC-006` | 확정 | 한국어 리소스 원문은 보정하지 않고 JP만 반영한다. | 사용자 결정 | `P2-T1` |
|
||||
| `2026-08-11` | `DEC-007` | 확정 | 기존 한국어 리소스가 없는 CSV 항목은 신규 리소스로 추가하지 않는다. | 사용자 결정 | `P2-T2` |
|
||||
| `2026-08-11` | `DEC-008` | 확정 | `{{...}}`, `{...}`, `n%`, `n위`, `0:00` placeholder는 Swift interpolation과 정규화해 매핑 후보를 판정한다. | 사용자 결정과 재스캔 결과 | `P1-T2`, `P2-T1` |
|
||||
| `2026-08-11` | `DEC-009` | 확정 | 같은 KR 또는 같은 정규화 KR의 기존 JP가 여러 값이면 CSV JP를 우선 반영한다. | 사용자 결정과 중복 JP 재스캔 결과 | `P1-T2`, `P2-T1` |
|
||||
| `2026-08-11` | `DEC-010` | 확정 | HTML br 표기는 앱 문자열에 그대로 반영하지 않고 줄바꿈 `\n`으로 변환한다. | 사용자 결정과 CSV 검색 결과 현재 0건 | `P1-T1`, `P2-T1` |
|
||||
| `2026-08-11` | `DEC-011` | 확정 | CSV 셀 안의 실제 줄바꿈은 Swift 문자열 줄바꿈 escape `\n`으로 변환한다. | 사용자 결정과 CSV 파싱 결과 개행 33행 | `P1-T1`, `P2-T1` |
|
||||
|
||||
## 발견된 문제
|
||||
|
||||
| ID | 심각도 | 상태 | 발견 내용 | 영향 Goal | 처리 계획 |
|
||||
|---|---|---|---|---|---|
|
||||
| `ISSUE-001` | Medium | 해결 | CSV 물리 line 281~283의 JP가 `しいシリーズ`로 시작해 `新しいシリーズ` 오탈자 가능성이 있다. | `P1-T3`, `P2-T1` | 사용자 확인으로 `新しいシリーズ` 보정 반영 확정 |
|
||||
| `ISSUE-002` | Medium | 해결 | placeholder 유사 문구 12건이 `{{...}}`, `{...}`, `n`, `0:00` 형식으로 섞여 있다. | `P1-T2`, `P2-T1` | Swift interpolation과 정규화 매칭하고, 현재 재스캔 기준 추가 매칭은 0건으로 기록 |
|
||||
| `ISSUE-003` | Medium | 해결 | CSV KR 242행 중 현재 `I18n.swift` `ko:` literal과 직접 매칭되지 않는 항목이 139건이다. | `P1-T2`, `P2-T2` | 기존 한국어 리소스가 없는 항목은 신규 키로 추가하지 않음 |
|
||||
| `ISSUE-004` | Medium | 해결 | CSV 매칭 KR 중 기존 `I18n.swift` JP가 중복 항목끼리 서로 다른 경우가 12건이다. | `P1-T2`, `P2-T1` | CSV JP 우선 반영 규칙 확정 |
|
||||
| `ISSUE-005` | Low | 해결 | CSV와 기존 문서에서 HTML br 표기 검색 결과 현재 0건이다. | `P1-T1`, `P2-T1` | 향후 발견 시 literal 대신 `\n` 변환 반영 규칙 확정 |
|
||||
| `ISSUE-006` | Medium | 해결 | CSV 셀 안의 실제 줄바꿈 포함 항목이 33건이다. | `P1-T1`, `P2-T1` | Swift 문자열에는 실제 개행 붙여넣기가 아니라 `\n` escape로 반영 |
|
||||
|
||||
## 최종 보고 형식
|
||||
|
||||
최종 보고에는 다음 항목을 실제 실행 결과로 채운다.
|
||||
|
||||
- 구현 결과: 완료한 Phase와 사용자가 확인할 수 있는 흐름.
|
||||
- 변경: 주요 파일과 동작.
|
||||
- 결정: 중요한 Decision Log ID와 내용.
|
||||
- 검증: 실행 명령별 성공/실패, exit code, 핵심 수치, 수동 검증 결과.
|
||||
- 남은 항목: 외부 의존, 후속 범위, 제외 항목 또는 후속 질문.
|
||||
- 문서: 갱신한 PRD, plan, review 경로.
|
||||
|
||||
최종 보고는 성공을 추정하지 않는다. 실제 실행한 최신 검증 결과와 완료되지 않은 범위를 함께 전달한다.
|
||||
Reference in New Issue
Block a user