✨ feat: 새로운 스킬 및 문서 추가 (EDM HTML, 요구사항 분석기, 번역 코드 생성기)
This commit is contained in:
138
skills/plan-translation-generator/SKILL.md
Normal file
138
skills/plan-translation-generator/SKILL.md
Normal file
@@ -0,0 +1,138 @@
|
||||
---
|
||||
name: plan-translation-generator
|
||||
description: 번역 요청 엑셀 파일의 EN 셀을 기반으로 '번역코드' 컬럼에 함축적인 코드를 자동 생성합니다. 사용자가 "번역코드 만들어줘", "translation key 생성", "번역 키 추출" 등을 요청하면 트리거됩니다.
|
||||
---
|
||||
|
||||
# 번역 코드 생성 (Translation Key Generator)
|
||||
|
||||
이 skill 은 번역 요청 엑셀 파일의 **EN 컬럼** 텍스트를 분석하여
|
||||
`번역코드` 컬럼에 함축적이고 일관된 코드를 자동으로 작성합니다.
|
||||
|
||||
## 코드 생성 규칙
|
||||
|
||||
| 규칙 | 설명 | 예시 |
|
||||
|------|------|------|
|
||||
| **단어 수** | 3단어 이하 | `NAV-main` ✅ / `NAV-main-section-title` ❌ |
|
||||
| **구분자** | 단어 사이 `-` (하이픈) | `EVENT-title` |
|
||||
| **첫 단어** | 카테고리를 나타내는 **대문자** | `HERO`, `NAV`, `TOAST` |
|
||||
| **함축성** | 텍스트의 핵심 의미를 최대한 압축 | `HERO-czn-title` (CHAOS ZERO NIGHTMARE → czn) |
|
||||
|
||||
## 카테고리 가이드
|
||||
|
||||
| 카테고리 | 사용 상황 | 예시 |
|
||||
|----------|----------|------|
|
||||
| `BRAND` | 브랜드명, 서비스 이름 | `BRAND-smilegate-ax` |
|
||||
| `NAV` | 내비게이션, 메뉴 항목 | `NAV-main`, `NAV-location` |
|
||||
| `EVENT` | 이벤트 정보 (제목/날짜/장소) | `EVENT-title`, `EVENT-date`, `EVENT-venue` |
|
||||
| `HERO` | 히어로 배너 텍스트 | `HERO-miresi-title` |
|
||||
| `SECTION` | 섹션 헤더/소제목 | `SECTION-intro` |
|
||||
| `BTN` | 버튼 레이블 | `BTN-register`, `BTN-more` |
|
||||
| `TOAST` | 토스트/알림 메시지 | `TOAST-link-copied`, `TOAST-paste-hint` |
|
||||
| `LABEL` | 폼 레이블, 태그 | `LABEL-date`, `LABEL-venue` |
|
||||
| `MSG` | 일반 안내 메시지 | `MSG-loading`, `MSG-empty` |
|
||||
| `MODAL` | 모달/팝업 내 텍스트 | `MODAL-confirm-title` |
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. **파일 확인**
|
||||
- 사용자가 엑셀 파일 경로를 제공했는지 확인
|
||||
- 미제공 시: 파일 경로를 먼저 요청
|
||||
|
||||
2. **데이터 파악**
|
||||
```python
|
||||
import openpyxl
|
||||
wb = openpyxl.load_workbook('파일경로.xlsx')
|
||||
ws = wb.active
|
||||
# 헤더 행에서 '번역코드', 'EN' 컬럼 인덱스 찾기
|
||||
```
|
||||
- 1행(헤더)에서 `번역코드` 컬럼과 `EN` 컬럼 위치를 동적으로 탐지
|
||||
- 데이터가 있는 행(EN 값이 None이 아닌 행)만 처리
|
||||
|
||||
3. **코드 생성**
|
||||
- EN 텍스트를 분석해 카테고리와 핵심 키워드 추출
|
||||
- 이미 `번역코드` 값이 있는 행은 **덮어쓰지 않음** (사용자 확인 후 진행)
|
||||
- 생성한 코드 목록을 사용자에게 미리 보여주고 승인 요청
|
||||
|
||||
4. **중복 코드 검사** ← 저장 전 반드시 수행
|
||||
- 기존에 이미 작성된 코드 + 이번에 새로 생성한 코드를 **전체 합산**하여 중복 여부 검사
|
||||
- 중복이 발견되면 **저장을 중단**하고 사용자에게 아래 형식으로 보고:
|
||||
```
|
||||
⚠️ 중복 코드 발견
|
||||
- HERO-title: 3행, 12행 (2건)
|
||||
- EVENT-date: 8행, 15행 (2건)
|
||||
```
|
||||
- 중복 해소 방법을 제안하고 사용자 승인 후 재생성
|
||||
- 중복이 없으면 "중복 없음 확인" 메시지 출력 후 저장 진행
|
||||
|
||||
5. **엑셀 저장**
|
||||
- 사용자 승인 후 `번역코드` 컬럼에 값 기입 및 저장
|
||||
- 저장 완료 메시지와 변경 내역 요약 출력
|
||||
|
||||
6. **참조 MD 파일 생성**
|
||||
- 엑셀 저장 완료 후 로컬 참조용 MD 파일을 생성 또는 갱신한다.
|
||||
- 파일 위치: `docs/translation/<엑셀파일명>.md` (확장자 `.xlsx` → `.md` 치환)
|
||||
- 예) `smilegate-ax-2026.xlsx` → `docs/translation/smilegate-ax-2026.md`
|
||||
- `docs/translation/` 디렉토리가 없으면 자동 생성한다.
|
||||
- 파일 내용: 엑셀의 **전체** 번역코드(기존 + 신규)를 아래 형식으로 작성
|
||||
- 헤더에서 `KO` 컬럼을 탐지하여 함께 기록한다 (개발 시 번역코드 매칭 참조용)
|
||||
```markdown
|
||||
# 번역 코드 참조 — <엑셀파일명>
|
||||
|
||||
> 생성일: YYYY-MM-DD
|
||||
> 원본 파일: <엑셀파일 상대경로>
|
||||
|
||||
| 번역코드 | KO | EN |
|
||||
|----------|----|----|
|
||||
| BRAND-smilegate-ax | 스마일게이트 x AX | Smilegate x AX |
|
||||
| NAV-main | 메인 | Main |
|
||||
| ... | ... | ... |
|
||||
```
|
||||
- 파일이 이미 존재하면 **덮어쓰지 않는다**. 새로 생성된 코드 행만 테이블 하단에 추가(append)한다.
|
||||
- 이미 파일에 존재하는 번역코드는 중복 추가하지 않는다.
|
||||
- 생성 완료 후 경로와 추가된 행 수를 사용자에게 출력한다.
|
||||
|
||||
## 코드 작성 예시 (실제 케이스)
|
||||
|
||||
| EN 텍스트 | 생성 코드 | 근거 |
|
||||
|-----------|----------|------|
|
||||
| `Smilegate x AX` | `BRAND-smilegate-ax` | 브랜드명 전체 |
|
||||
| `Main` | `NAV-main` | 내비 메인 항목 |
|
||||
| `Program & Events` | `NAV-program-events` | 내비 항목, 특수문자 제거 |
|
||||
| `Smilegate x Anime Expo` | `EVENT-title` | 이벤트 대표 제목 |
|
||||
| `07/02/2026 (THU)~ 07/05/2026 (SUN)` | `EVENT-date` | 날짜 데이터 |
|
||||
| `Los Angeles Convention Center` | `EVENT-venue` | 행사 장소 |
|
||||
| `The link has been copied.` | `TOAST-link-copied` | 토스트 알림, 링크 복사 완료 |
|
||||
| `You may now paste it elsewhere.` | `TOAST-paste-hint` | 토스트 알림, 붙여넣기 안내 |
|
||||
| `MIRESI ARRIVES AT <br> ANIME EXPO!` | `HERO-miresi-title` | 히어로 배너, 캐릭터명 |
|
||||
| `CHAOS ZERO NIGHTMARE ARRIVES AT <br> ANIME EXPO!` | `HERO-czn-title` | 히어로 배너, 약어 처리 |
|
||||
|
||||
## 처리 스크립트
|
||||
|
||||
스크립트 파일: `scripts/generate_translation_keys.py`
|
||||
|
||||
```bash
|
||||
python scripts/generate_translation_keys.py <엑셀파일경로>
|
||||
```
|
||||
|
||||
스크립트 주요 함수:
|
||||
|
||||
| 함수 | 역할 |
|
||||
|------|------|
|
||||
| `load_existing_codes(ws, code_col)` | 기존 번역 코드를 `{행번호: 코드}` 로 수집 |
|
||||
| `check_duplicates(existing, new)` | 기존+신규 코드 합산 중복 검사, 결과 반환 |
|
||||
| `run(filepath, new_codes)` | 중복 검사 → 저장 실행 (중복 시 중단) |
|
||||
|
||||
> `new_codes` 딕셔너리는 Claude AI가 EN 텍스트를 분석하여 직접 채웁니다.
|
||||
> 스크립트는 중복 검사와 저장만 담당합니다.
|
||||
|
||||
## 주의사항
|
||||
|
||||
- **행 순서 고정**: 엑셀의 기존 행 순서를 절대 변경하지 않는다. 코드는 원래 행 위치에 그대로 기입한다.
|
||||
- **이미지 셀 보존**: 셀에 이미지가 삽입된 경우 해당 셀 및 시트의 이미지를 덮어쓰거나 삭제하지 않는다. `openpyxl.load_workbook(path, keep_vba=True)` 옵션을 사용하고, 이미지 객체(`ws._images`)를 건드리지 않는다.
|
||||
- **기존 코드 보존**: 이미 `번역코드` 값이 있는 셀은 덮어쓰지 않는다.
|
||||
- **중복 코드 방지**: 동일한 코드가 두 행에 생기지 않도록 확인한다.
|
||||
- **HTML 태그 무시**: `<br>`, `<b>` 등 마크업 태그는 의미 분석에서 제외한다.
|
||||
- **날짜/숫자 데이터**: 날짜 형식 셀은 `EVENT-date`, `LABEL-date` 등 의미 기반으로 처리한다.
|
||||
- **약어 처리**: 3단어 초과 시 캐릭터명·고유명사를 약어(초성/이니셜)로 압축한다.
|
||||
- 예) `CHAOS ZERO NIGHTMARE` → `czn`
|
||||
- 사용자가 코드를 직접 수정 요청하면 수정 후 재저장한다.
|
||||
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
번역 코드 자동 생성 스크립트
|
||||
사용법: python generate_translation_keys.py <엑셀파일경로>
|
||||
|
||||
주의:
|
||||
- 행 순서를 변경하지 않는다 (원래 행 위치에만 값 기입)
|
||||
- 셀에 삽입된 이미지를 보존한다 (ws._images 미수정)
|
||||
"""
|
||||
import sys
|
||||
import openpyxl
|
||||
|
||||
|
||||
def generate_translation_key(en_text: str) -> str:
|
||||
"""
|
||||
EN 텍스트를 분석하여 번역 코드를 생성합니다.
|
||||
규칙:
|
||||
- 3단어 이하 (CATEGORY-word1-word2)
|
||||
- 단어 사이 '-' 구분
|
||||
- 첫 단어는 카테고리 대문자
|
||||
- 함축적인 의미로 생성
|
||||
|
||||
NOTE: 이 함수는 템플릿입니다.
|
||||
실제 코드 생성은 Claude AI가 EN 텍스트의 의미를 분석하여 수행합니다.
|
||||
"""
|
||||
raise NotImplementedError("AI가 EN 텍스트를 분석하여 코드를 직접 생성합니다.")
|
||||
|
||||
|
||||
def load_existing_codes(ws, code_col: int) -> dict[int, str]:
|
||||
"""기존에 입력된 번역 코드를 {행번호: 코드} 형태로 반환합니다."""
|
||||
return {
|
||||
row: ws.cell(row=row, column=code_col).value
|
||||
for row in range(2, ws.max_row + 1)
|
||||
if ws.cell(row=row, column=code_col).value
|
||||
}
|
||||
|
||||
|
||||
def check_duplicates(existing: dict[int, str], new: dict[int, str]) -> dict[str, list[int]]:
|
||||
"""
|
||||
기존 코드 + 신규 코드 전체를 합산하여 중복 검사합니다.
|
||||
반환: {중복코드: [행번호, ...]}
|
||||
"""
|
||||
seen: dict[str, int] = {}
|
||||
duplicates: dict[str, list[int]] = {}
|
||||
|
||||
for row_num, code in {**existing, **new}.items():
|
||||
if code in seen:
|
||||
duplicates.setdefault(code, [seen[code]]).append(row_num)
|
||||
else:
|
||||
seen[code] = row_num
|
||||
|
||||
return duplicates
|
||||
|
||||
|
||||
def run(filepath: str, new_codes: dict[int, str]) -> None:
|
||||
"""
|
||||
신규 코드를 중복 검사 후 엑셀에 저장합니다.
|
||||
|
||||
Args:
|
||||
filepath: 엑셀 파일 경로
|
||||
new_codes: {행번호: 생성된코드} 딕셔너리
|
||||
"""
|
||||
# keep_vba=True: 이미지·VBA 등 파일 내 임베딩 요소를 보존
|
||||
wb = openpyxl.load_workbook(filepath, keep_vba=True)
|
||||
ws = wb.active
|
||||
|
||||
# 이미지가 있는 셀 좌표를 미리 수집 (덮어쓰기 방지)
|
||||
image_cells: set[tuple[int, int]] = set()
|
||||
for img in getattr(ws, '_images', []):
|
||||
anchor = img.anchor
|
||||
if hasattr(anchor, '_from'):
|
||||
image_cells.add((anchor._from.row + 1, anchor._from.col + 1)) # 1-based
|
||||
|
||||
# 헤더에서 컬럼 인덱스 탐지
|
||||
headers = [cell.value for cell in ws[1]]
|
||||
if '번역코드' not in headers or 'EN' not in headers:
|
||||
print("❌ 헤더에 '번역코드' 또는 'EN' 컬럼이 없습니다.")
|
||||
sys.exit(1)
|
||||
|
||||
code_col = headers.index('번역코드') + 1 # 1-based
|
||||
en_col = headers.index('EN') + 1 # 1-based
|
||||
|
||||
# 기존 코드 수집
|
||||
existing_codes = load_existing_codes(ws, code_col)
|
||||
|
||||
# 빈 행 / 이미 코드 있는 행 필터링
|
||||
rows_to_fill: list[tuple[int, str]] = []
|
||||
for row in ws.iter_rows(min_row=2):
|
||||
row_num = row[0].row
|
||||
en_val = row[en_col - 1].value
|
||||
code_val = row[code_col - 1].value
|
||||
|
||||
if not en_val:
|
||||
continue # 빈 행 스킵
|
||||
if code_val:
|
||||
continue # 기존 코드 보존
|
||||
if (row_num, code_col) in image_cells:
|
||||
print(f" ⚠️ Row {row_num}: 이미지 셀 — 스킵 (보존)")
|
||||
continue # 이미지 있는 셀 보존
|
||||
|
||||
if row_num in new_codes:
|
||||
rows_to_fill.append((row_num, new_codes[row_num]))
|
||||
|
||||
if not rows_to_fill:
|
||||
print("ℹ️ 입력할 신규 코드가 없습니다.")
|
||||
return
|
||||
|
||||
# 중복 검사
|
||||
new_codes_filtered = dict(rows_to_fill)
|
||||
duplicates = check_duplicates(existing_codes, new_codes_filtered)
|
||||
|
||||
if duplicates:
|
||||
print("⚠️ 중복 코드 발견 — 저장 중단")
|
||||
for code, rows in duplicates.items():
|
||||
print(f" - {code}: {', '.join(map(str, rows))}행 ({len(rows)}건)")
|
||||
print("\n중복을 해소한 뒤 다시 실행해 주세요.")
|
||||
sys.exit(1)
|
||||
|
||||
# 중복 없음 → 저장
|
||||
print("✅ 중복 없음 확인")
|
||||
for row_num, code in rows_to_fill:
|
||||
ws.cell(row=row_num, column=code_col, value=code)
|
||||
en_text = ws.cell(row=row_num, column=en_col).value
|
||||
print(f" Row {row_num}: {code:<30} ← \"{en_text}\"")
|
||||
|
||||
wb.save(filepath)
|
||||
print(f"\n저장 완료: {filepath}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python generate_translation_keys.py <엑셀파일경로>")
|
||||
sys.exit(1)
|
||||
|
||||
# new_codes는 Claude AI가 EN 텍스트를 분석하여 채워줍니다.
|
||||
# 예시:
|
||||
# new_codes = {
|
||||
# 2: 'BRAND-smilegate-ax',
|
||||
# 3: 'NAV-main',
|
||||
# }
|
||||
new_codes: dict[int, str] = {}
|
||||
|
||||
run(sys.argv[1], new_codes)
|
||||
Reference in New Issue
Block a user