143 lines
4.7 KiB
Python
143 lines
4.7 KiB
Python
"""
|
||
번역 코드 자동 생성 스크립트
|
||
사용법: 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)
|