diff --git a/README.md b/README.md index a0807d5..9dae96c 100644 --- a/README.md +++ b/README.md @@ -56,18 +56,13 @@ git submodule update --init --recursive bash .claude/common/scripts/install.sh https://git.sginfra.net/sgp-web-d/gameservice-fe-agent.git ``` -> `install.sh`는 `.gitignore`에 `.claude/common/`를 자동으로 추가합니다. -> `.gitignore`가 없으면 새로 생성합니다. - 설치 후 프로젝트 구조는 다음과 같이 됩니다. ``` your-project/ ├── .claude/ │ ├── common/ ← submodule (gameservice-fe-agent) -│ ├── project/ ← 프로젝트 고유 지침 -│ └── skills/ ← 공통 skill 심볼릭 링크 -├── .gitignore ← .claude/common/ 자동 추가됨 +│ └── project/ ← 프로젝트 고유 지침 ├── CLAUDE.md ← 공통 + 프로젝트 지침을 @import └── ... ``` @@ -114,7 +109,6 @@ bash .claude/common/scripts/update.sh ```bash bash .claude/common/scripts/install.sh https://git.sginfra.net/sgp-web-d/gameservice-fe-agent.git # → .claude/common 에 submodule 설치 -# → .gitignore 에 .claude/common/ 추가 # → templates/project/*.md 를 .claude/project/ 로 복사 # → templates/CLAUDE.md.tpl 을 루트 CLAUDE.md 로 복사 ``` diff --git a/scripts/install.sh b/scripts/install.sh index ec17374..b37d66f 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -43,22 +43,7 @@ else echo "✅ submodule 추가 완료: $TARGET_PATH" fi -# 2) .gitignore 에 .claude/common 추가 -GITIGNORE=".gitignore" -IGNORE_ENTRY=".claude/common/" -if [[ -f "$GITIGNORE" ]]; then - if grep -qF "$IGNORE_ENTRY" "$GITIGNORE" || grep -qF ".claude/common" "$GITIGNORE"; then - echo "ℹ️ .gitignore 에 '$IGNORE_ENTRY' 가 이미 있습니다. 건너뜁니다." - else - printf "\n# gameservice-fe-agent submodule\n%s\n" "$IGNORE_ENTRY" >> "$GITIGNORE" - echo "✅ .gitignore 에 '$IGNORE_ENTRY' 를 추가했습니다." - fi -else - printf "# gameservice-fe-agent submodule\n%s\n" "$IGNORE_ENTRY" > "$GITIGNORE" - echo "✅ .gitignore 를 생성하고 '$IGNORE_ENTRY' 를 추가했습니다." -fi - -# 4) 프로젝트 지침 양식 복사 (templates/project/ → .claude/project/) +# 2) 프로젝트 지침 양식 복사 (templates/project/ → .claude/project/) mkdir -p "$PROJECT_PATH" TEMPLATE_DIR="$TARGET_PATH/templates/project" @@ -79,7 +64,7 @@ else echo "⚠️ $TEMPLATE_DIR 를 찾지 못했습니다. 공통 저장소의 templates 가 오래됐을 수 있습니다." fi -# 5) 루트 CLAUDE.md 템플릿 복사 +# 3) 루트 CLAUDE.md 템플릿 복사 if [[ ! -f "CLAUDE.md" ]]; then TPL_FILE="$TARGET_PATH/templates/CLAUDE.md.tpl" if [[ -f "$TPL_FILE" ]]; then @@ -112,7 +97,7 @@ else echo "" fi -# 6) 공통 skill 심볼릭 링크 (.claude/common/skills/* → .claude/skills/*) +# 4) 공통 skill 심볼릭 링크 (.claude/common/skills/* → .claude/skills/*) SKILLS_SRC="$TARGET_PATH/skills" SKILLS_DEST=".claude/skills" @@ -136,33 +121,6 @@ if [[ -d "$SKILLS_SRC" ]]; then done fi -# 7) pre-commit 훅: .claude/common 변경 커밋 방지 -HOOK_DIR="$(git rev-parse --git-dir)/hooks" -HOOK_FILE="$HOOK_DIR/pre-commit" -HOOK_MARKER="# gameservice-fe-agent: block .claude/common commit" - -mkdir -p "$HOOK_DIR" - -if [[ -f "$HOOK_FILE" ]] && grep -qF "$HOOK_MARKER" "$HOOK_FILE"; then - echo "ℹ️ pre-commit 훅에 .claude/common 보호 규칙이 이미 있습니다. 건너뜁니다." -else - # 훅이 없으면 새로 생성, 있으면 기존 훅 뒤에 추가 - if [[ ! -f "$HOOK_FILE" ]]; then - echo "#!/usr/bin/env bash" > "$HOOK_FILE" - fi - cat >> "$HOOK_FILE" <<'HOOK' - -# gameservice-fe-agent: block .claude/common commit -if git diff --cached --name-only | grep -q "^\.claude/common"; then - echo "❌ .claude/common (gameservice-fe-agent submodule) 변경은 커밋할 수 없습니다." - echo " 공통 지침 수정은 gameservice-fe-agent 저장소에서 PR을 통해 진행해 주세요." - exit 1 -fi -HOOK - chmod +x "$HOOK_FILE" - echo "✅ pre-commit 훅을 설치했습니다. (.claude/common 변경 커밋 방지)" -fi - echo "" echo "🎉 설치가 완료되었습니다." echo " - 공통 지침: $TARGET_PATH/CLAUDE.md" @@ -173,5 +131,5 @@ echo "" echo "다음 작업을 진행해 주세요:" echo " 1) $PROJECT_PATH/*.md 내용을 프로젝트에 맞게 채우기" echo " 2) 변경 사항을 커밋하기" -echo " git add .gitmodules .gitignore .claude CLAUDE.md" +echo " git add .gitmodules .claude CLAUDE.md" echo " git commit -m 'chore: add gameservice-fe-agent submodule'" diff --git a/skills/project-init/SKILL.md b/skills/project-init/SKILL.md new file mode 100644 index 0000000..9e7b32b --- /dev/null +++ b/skills/project-init/SKILL.md @@ -0,0 +1,165 @@ +--- +name: project-init +description: > + 현재 프로젝트를 분석하여 .claude/project/ 하위 문서(overview.md, conventions.md, architecture.md)를 + 실제 내용으로 채웁니다. 사용자가 "/init", "프로젝트 초기화", "프로젝트 문서 작성해줘" 등을 + 요청할 때 트리거됩니다. +--- + +# Project Init — 프로젝트 문서 자동 작성 + +이 skill 은 현재 Git 저장소를 탐색하여 `.claude/project/` 하위 세 파일을 +실제 프로젝트 정보로 채웁니다. + +## 언제 사용하는가 + +- `/init` 실행 시 +- `.claude/project/*.md` 가 템플릿 상태(placeholder 가 남아 있음)일 때 +- 새로운 프로젝트에 gameservice-fe-agent 를 처음 적용할 때 + +## 작업 순서 + +### 1. 환경 파악 (읽기 전용) + +아래 파일들을 순서대로 읽어 프로젝트 정보를 수집한다. +파일이 없으면 건너뛴다. + +| 파일 / 명령 | 수집 정보 | +|---|---| +| `package.json` | 프로젝트 이름, 의존성, 스크립트, 패키지 매니저 | +| `pnpm-lock.yaml` / `yarn.lock` / `package-lock.json` | 패키지 매니저 확정 | +| `nuxt.config.ts` / `next.config.*` / `vite.config.*` | 프레임워크, 모듈, 빌드 설정 | +| `tsconfig.json` | TypeScript strict 여부 | +| `tailwind.config.*` | CSS 프레임워크 | +| `.eslintrc.*` / `eslint.config.*` | 린터 설정 | +| `README.md` | 서비스 설명, 팀 정보 | +| `app/` / `src/` / `pages/` 디렉토리 구조 | 레이어, 주요 기능 | +| `server/api/` 또는 `src/api/` | 외부 의존성, API 패턴 | +| `stores/` / `composables/` | 상태 관리 패턴 | + +### 2. 기존 문서 상태 확인 + +`.claude/project/overview.md`, `conventions.md`, `architecture.md` 를 읽어 +이미 작성된 내용이 있으면 덮어쓰지 않고 **비어 있는 섹션만 채운다**. + +- placeholder(`<...>`) 가 남아 있는 줄 → 채움 대상 +- 실제 내용이 작성된 줄 → 유지 + +### 3. 사용자 확인 후 작성 + +탐색이 끝나면 아래 요약을 사용자에게 먼저 보여준다. + +``` +📋 감지된 프로젝트 정보 + 이름: + 프레임워크: + 패키지 매니저: + 언어: TypeScript (strict: ) + CSS: + 상태관리: + 테스트: + +✍️ 아래 파일을 업데이트하려고 합니다: + - CLAUDE.md (제목 · 주요 명령어 · 주의사항) + - .claude/project/overview.md + - .claude/project/conventions.md + - .claude/project/architecture.md + +진행할까요? (y/n) +``` + +사용자가 **y** 또는 별도 지시 없이 진행을 허락할 때만 파일을 수정한다. + +### 4. 각 파일 작성 규칙 + +#### CLAUDE.md + +루트 `CLAUDE.md` 는 Claude 가 대화 시작 시 **항상** 읽는 파일이므로, 매번 하위 파일까지 탐색하지 않아도 되는 핵심 정보만 간결하게 기재한다. + +- **제목** (`# <프로젝트 이름>`): `package.json` 의 `name` 필드로 교체 + 예) `# epic7-esports` → `# Epic7 Esports E7WC` + `name` 이 slug 형태면 사람이 읽기 좋은 표기로 변환한다. + +- **`## 주요 명령어`** 섹션 (필수 추가): + `package.json` 의 `scripts` 에서 아래 유형을 추출하여 그룹별로 정리한다. + 주석은 스크립트 이름만으로 의도를 알기 어려울 때만 달고, 뻔한 것은 생략한다. + + ```markdown + ## 주요 명령어 + + # 개발 서버 + + + # 빌드 + + + # 린트 / 포맷 + + ``` + + - `dev`, `start`, `local` 등 → **개발 서버** 그룹 + - `build`, `build:*` → **빌드** 그룹 + - `lint`, `lint:*`, `format`, `prettier` → **린트 / 포맷** 그룹 + - `test`, `test:*` → **테스트** 그룹 (존재할 때만) + - 나머지 잡다한 스크립트는 포함하지 않는다. + +- **`## 주의사항`** 섹션 (선택 추가): + 프로젝트 고유의 "함정"이 될 수 있는 항목을 2-4개 이내로 요약한다. + 소스는 `conventions.md` 의 **금지 사항** 섹션 + CSS/API 탐색 결과. + 없으면 이 섹션은 추가하지 않는다. + + ```markdown + ## 주의사항 + + - CSS: <탐색 결과에 따라 기재. 예: Tailwind 아님, SCSS 사용 — Tailwind 클래스 금지> + - API: + - 환경: <.env 파일 분기 규칙> + ``` + +- 이미 제목이 실제 이름으로 바뀌어 있거나 섹션이 존재하면 덮어쓰지 않고 유지한다. + +#### overview.md + +- **서비스 > 이름**: `package.json` 의 `name` 필드 사용 +- **서비스 > 설명**: `README.md` 첫 단락 또는 `package.json` 의 `description` +- **기술 스택**: 탐색에서 수집한 실제 값으로 채움. 모르면 `<확인 필요>` 유지 +- **주요 기능**: `pages/` 또는 `app/pages/` 디렉토리 목록을 기반으로 유추 +- **참고 문서**: `README.md` 에 링크가 있으면 옮기고, 없으면 `<확인 필요>` 유지 + +#### conventions.md + +- **디렉토리 규칙**: 실제 디렉토리 구조에서 확인된 폴더만 설명, 없는 항목은 삭제 +- **API 호출 창구**: `composables/` 또는 `api/` 디렉토리 패턴에서 유추 +- **테스트 러너 / 위치**: `package.json` devDependencies + `vitest.config.*` 참고 +- 공통 지침과 다른 점이 없다면 "공통 지침을 따른다" 로 간단히 기재 + +#### architecture.md + +- **레이어 구조**: 실제 디렉토리 목록으로 ASCII 다이어그램을 업데이트 +- **상태 관리 가이드**: 탐색에서 확인한 라이브러리로 표의 권장 위치를 채움 +- **외부 의존성**: `server/api/` 파일명 또는 `useFetch` 호출에서 유추. 불확실하면 `<확인 필요>` + +### 5. 완료 안내 + +``` +✅ 문서 업데이트 완료 + +변경된 파일: + - CLAUDE.md + - .claude/project/overview.md + - .claude/project/conventions.md + - .claude/project/architecture.md + +다음 단계: + 1) 각 파일에서 <확인 필요> 항목을 직접 채워주세요. + 2) 변경 사항을 커밋해 주세요: + git add CLAUDE.md .claude/project/ + git commit -m "docs: 프로젝트 초기 문서 작성" +``` + +## 주의사항 + +- **파일 삭제 금지**: 기존 `.claude/project/*.md` 는 내용을 수정할 뿐 절대 삭제하지 않는다. +- **추측 표시**: 확실하지 않은 정보는 `(추정)` 을 붙이고, 사용자가 쉽게 찾아 수정할 수 있게 한다. +- **최소 변경**: 이미 올바르게 작성된 섹션은 건드리지 않는다. +- **커밋 금지**: 파일 수정 후 git commit 은 사용자 명시 요청이 없으면 실행하지 않는다. diff --git a/skills/translation-keys/SKILL.md b/skills/translation-keys/SKILL.md new file mode 100644 index 0000000..28be566 --- /dev/null +++ b/skills/translation-keys/SKILL.md @@ -0,0 +1,115 @@ +--- +name: translation-keys +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. **엑셀 저장** + - 사용자 승인 후 `번역코드` 컬럼에 값 기입 및 저장 + - 저장 완료 메시지와 변경 내역 요약 출력 + +## 코드 작성 예시 (실제 케이스) + +| 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
ANIME EXPO!` | `HERO-miresi-title` | 히어로 배너, 캐릭터명 | +| `CHAOS ZERO NIGHTMARE ARRIVES AT
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 태그 무시**: `
`, `` 등 마크업 태그는 의미 분석에서 제외한다. +- **날짜/숫자 데이터**: 날짜 형식 셀은 `EVENT-date`, `LABEL-date` 등 의미 기반으로 처리한다. +- **약어 처리**: 3단어 초과 시 캐릭터명·고유명사를 약어(초성/이니셜)로 압축한다. + - 예) `CHAOS ZERO NIGHTMARE` → `czn` +- 사용자가 코드를 직접 수정 요청하면 수정 후 재저장한다. diff --git a/skills/translation-keys/scripts/generate_translation_keys.py b/skills/translation-keys/scripts/generate_translation_keys.py new file mode 100644 index 0000000..b7d9332 --- /dev/null +++ b/skills/translation-keys/scripts/generate_translation_keys.py @@ -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)