Add extract configuration and enhance untranslated extraction functionality

This commit is contained in:
Gnill82
2025-10-30 18:05:59 +09:00
parent 0482c8299b
commit df126a641f
6 changed files with 168 additions and 31 deletions

View File

@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Read(//d/Work/WorldStalker/**)",
"Bash(python ds_l10n.py:*)"
],
"deny": [],
"ask": []
}
}

View File

@ -143,6 +143,19 @@ logging:
# 진행률 표시
show_progress: true
# ============================================================================
# 미번역 추출 설정 (Extract)
# ============================================================================
extract:
# 모든 대상 언어 검사 (false면 en만 검사)
check_all_languages: false
# fuzzy 플래그 항목도 포함 (원본 변경으로 리뷰 필요한 항목)
include_fuzzy: true
# 언어별 개별 파일로 추출 (false면 통합 파일)
separate_files: true
# ============================================================================
# 워크플로우 설정 (Workflow)
# ============================================================================

View File

@ -32,6 +32,24 @@ def command_extract(args, config, logger):
"""미번역 항목 추출"""
logger.section('미번역 항목 추출', Icons.SEARCH)
# extract 설정
check_all_languages = config.get('extract.check_all_languages', False)
include_fuzzy = config.get('extract.include_fuzzy', False)
separate_files = config.get('extract.separate_files', True)
# CLI 옵션으로 설정 오버라이드
if hasattr(args, 'include_fuzzy') and args.include_fuzzy is not None:
include_fuzzy = args.include_fuzzy
if hasattr(args, 'all_languages') and args.all_languages:
check_all_languages = True
# 출력 디렉토리
output_dir = config.get_path('paths.output_dir')
output_dir.mkdir(parents=True, exist_ok=True)
timestamp = get_timestamp()
po_handler = POHandler(config.data, logger)
# GUI 모드
if args.gui:
logger.info('GUI 모드로 실행합니다...')
@ -53,35 +71,105 @@ def command_extract(args, config, logger):
po_path = Path(po_path)
else:
# CLI 모드: config에서 경로 가져오기
unreal_loc = config.get_path('paths.unreal_localization')
source_lang = config.get('languages.source', 'ko')
po_path = unreal_loc / source_lang / config.get('files.po_filename', 'LocalExport.po')
# 출력 파일 경로
pattern = config.get('files.untranslated_pattern', 'untranslated_{timestamp}.tsv')
output_filename = pattern.format(timestamp=timestamp)
output_path = output_dir / output_filename
if not po_path.exists():
logger.error(f'PO 파일을 찾을 수 없습니다: {po_path}')
# 추출 실행
count = po_handler.extract_untranslated(po_path, output_path, include_fuzzy)
if count > 0:
logger.success(f'\n✅ 미번역 항목 {count}건 추출 완료')
logger.info(f'📄 출력 파일: {output_path}')
else:
logger.success(f'\n✅ 모든 텍스트가 번역되어 있습니다!')
return True
# CLI 모드
unreal_loc = config.get_path('paths.unreal_localization')
if not unreal_loc.exists():
logger.error(f'언리얼 현지화 폴더를 찾을 수 없습니다: {unreal_loc}')
return False
logger.info(f'언리얼 현지화 폴더: {unreal_loc}')
logger.info(f'fuzzy 항목 포함: {"" if include_fuzzy else "아니오"}')
# 검사할 언어 결정
if check_all_languages:
# 모든 대상 언어 검사
target_langs = config.get('languages.targets', [])
if not target_langs:
logger.error('config.yaml에 대상 언어(languages.targets)가 정의되지 않았습니다.')
return False
# 출력 파일 경로
output_dir = config.get_path('paths.output_dir')
output_dir.mkdir(parents=True, exist_ok=True)
timestamp = get_timestamp()
pattern = config.get('files.untranslated_pattern', 'untranslated_{timestamp}.tsv')
output_filename = pattern.format(timestamp=timestamp)
output_path = output_dir / output_filename
# 추출 실행
po_handler = POHandler(config.data, logger)
count = po_handler.extract_untranslated(po_path, output_path)
if count > 0:
logger.success(f'\n✅ 미번역 항목 {count}건 추출 완료')
logger.info(f'📄 출력 파일: {output_path}')
logger.info(f'대상 언어: {", ".join(target_langs)}')
else:
logger.success(f'\n✅ 모든 텍스트가 번역되어 있습니다!')
logger.info('미번역 항목이 없습니다. 번역 작업이 완료된 상태입니다.')
# en만 검사 (기본 동작)
target_langs = ['en']
logger.info(f'검사 언어: en')
# 언어별로 미번역 추출
total_extracted = 0
extracted_files = []
for lang in target_langs:
po_path = unreal_loc / lang / config.get('files.po_filename', 'LocalExport.po')
if not po_path.exists():
logger.warning(f' ⚠️ {lang}: PO 파일을 찾을 수 없음 - {po_path}')
continue
# 출력 파일 경로
if check_all_languages and separate_files:
# 모든 언어 검사 시: 언어별 개별 파일
output_filename = f'untranslated_{lang}_{timestamp}.tsv'
else:
# en만 검사 시: 단일 파일 (언어 코드 생략)
pattern = config.get('files.untranslated_pattern', 'untranslated_{timestamp}.tsv')
output_filename = pattern.format(timestamp=timestamp)
output_path = output_dir / output_filename
# 모든 언어 검사 시에만 언어별로 구분하여 표시
if check_all_languages:
logger.separator()
logger.info(f'🔍 {lang} 처리 중...')
# 추출 실행
count = po_handler.extract_untranslated(po_path, output_path, include_fuzzy)
if count > 0:
total_extracted += count
extracted_files.append((lang, output_path, count))
if check_all_languages:
logger.success(f'{lang}: {count}건 추출')
else:
if check_all_languages:
logger.success(f'{lang}: 모든 번역 완료')
# 최종 결과
logger.separator()
if total_extracted > 0:
if check_all_languages:
# 모든 언어 검사: 언어별 상세 표시
logger.success(f'\n✅ 총 {total_extracted}건의 미번역 항목 추출 완료')
logger.info('\n📄 생성된 파일:')
for lang, file_path, count in extracted_files:
logger.info(f' - {file_path.name} ({lang}: {count}건)')
else:
# en만 검사: 간단한 메시지
logger.success(f'\n✅ 미번역 항목 {total_extracted}건 추출 완료')
logger.info(f'📄 출력 파일: {extracted_files[0][1]}')
else:
if check_all_languages:
logger.success(f'\n✅ 모든 언어의 번역이 완료되어 있습니다!')
else:
logger.success(f'\n✅ 모든 텍스트가 번역되어 있습니다!')
logger.info('미번역 항목이 없습니다.')
return True
@ -301,6 +389,10 @@ def main():
# extract 명령어
parser_extract = subparsers.add_parser('extract', help='미번역 항목 추출')
parser_extract.add_argument('--gui', action='store_true', help='GUI 모드로 실행')
parser_extract.add_argument('--include-fuzzy', action='store_true', dest='include_fuzzy',
help='fuzzy 플래그 항목 포함 (원본 변경으로 리뷰 필요한 항목)')
parser_extract.add_argument('--all-languages', action='store_true', dest='all_languages',
help='모든 대상 언어 검사 (기본: en만 검사)')
# validate 명령어
parser_validate = subparsers.add_parser('validate', help='번역 검증')

View File

@ -42,10 +42,15 @@ class POHandler:
self.logger.error(f'PO 파일 로드 실패: {po_path} - {e}')
return None
def extract_untranslated(self, po_path: Path, output_path: Path) -> int:
def extract_untranslated(self, po_path: Path, output_path: Path, include_fuzzy: bool = False) -> int:
"""
미번역 항목 추출
Args:
po_path: PO 파일 경로
output_path: 출력 TSV 파일 경로
include_fuzzy: fuzzy 플래그 항목 포함 여부 (원본 변경으로 리뷰 필요한 항목)
Returns:
추출된 항목 개수
"""
@ -57,20 +62,37 @@ class POHandler:
# 전체 항목 및 미번역 항목 필터링
total_entries = len([entry for entry in po if entry.msgid]) # msgid가 있는 항목만 카운트
# 미번역 항목: msgstr이 비어있는 항목
untranslated = [entry for entry in po if not entry.msgstr.strip()]
if not untranslated:
# fuzzy 항목: fuzzy 플래그가 있는 항목 (원본 변경으로 리뷰 필요)
fuzzy_entries = []
if include_fuzzy:
fuzzy_entries = [entry for entry in po if 'fuzzy' in entry.flags and entry.msgstr.strip()]
# 통합 리스트
all_entries = untranslated + fuzzy_entries
if not all_entries:
translated_count = total_entries
self.logger.info(f'전체 {total_entries}개 항목 중 {translated_count}개 번역 완료 (100%)')
return 0
self.logger.info(f'전체 {total_entries}개 항목 중 미번역 {len(untranslated)}건 발견')
# 통계 출력
if include_fuzzy and fuzzy_entries:
self.logger.info(f'전체 {total_entries}개 항목 중:')
self.logger.info(f' - 미번역 항목: {len(untranslated)}')
self.logger.info(f' - 리뷰 필요 (fuzzy): {len(fuzzy_entries)}')
self.logger.info(f' - 총 추출 항목: {len(all_entries)}')
else:
self.logger.info(f'전체 {total_entries}개 항목 중 미번역 {len(all_entries)}건 발견')
# TSV 파일로 저장
self._save_to_tsv(untranslated, output_path)
self._save_to_tsv(all_entries, output_path)
self.logger.success(f'미번역 항목 추출 완료: {output_path}')
return len(untranslated)
return len(all_entries)
def merge_to_csv(self, localization_root: Path, output_path: Path) -> int:
"""

View File

@ -1,2 +1,2 @@
msgctxt SourceLocation ko en ja zh-Hans zh-Hant es-ES es-419 fr-FR de-DE ru-RU pt-BR pt-PT it-IT pl-PL tr-TR uk-UA vi-VN th
,UITitleAdventurerGuild /Game/Blueprints/DataTable/DT_StringTable.DT_StringTable 모험가 길드 Advent. Guild 冒険家ギルド 冒险家公会 冒險家公會 Gremio Avent. Gremio Avent. Guilde Avent. Abenteurergilde Гильдия Авант. Guilda Avent. Guilda Avent. Gilda Avvent. Gildia Awant. Macera Loncası Гільдія Авант. Hội Thám Hiểm กิลด์ผู้ผจญภัย
,UIReputationPointExpect /Game/Blueprints/DataTable/DT_StringTable.DT_StringTable 이번 시즌 추가 예정 점수 Points scheduled for this season 今シーズン追加予定点数 本赛季计划新增点数 本賽季計畫新增點數 Puntos programados para esta temporada Puntos programados para esta temporada Points prévus pour cette saison Für diese Saison geplante Punkte Заплановані очки на цей сезон Pontos programados para esta temporada Pontos previstos para esta época Punti previsti per questa stagione Punkty zaplanowane na ten sezon Bu sezon için planlanan puanlar Заплановані очки цього сезону Điểm dự kiến cho mùa này คะแนนที่กำหนดสำหรับซีซันนี้
1 msgctxt SourceLocation ko en ja zh-Hans zh-Hant es-ES es-419 fr-FR de-DE ru-RU pt-BR pt-PT it-IT pl-PL tr-TR uk-UA vi-VN th
2 ,UITitleAdventurerGuild ,UIReputationPointExpect /Game/Blueprints/DataTable/DT_StringTable.DT_StringTable 모험가 길드 이번 시즌 추가 예정 점수 Advent. Guild Points scheduled for this season 冒険家ギルド 今シーズン追加予定点数 冒险家公会 本赛季计划新增点数 冒險家公會 本賽季計畫新增點數 Gremio Avent. Puntos programados para esta temporada Gremio Avent. Puntos programados para esta temporada Guilde Avent. Points prévus pour cette saison Abenteurergilde Für diese Saison geplante Punkte Гильдия Авант. Заплановані очки на цей сезон Guilda Avent. Pontos programados para esta temporada Guilda Avent. Pontos previstos para esta época Gilda Avvent. Punti previsti per questa stagione Gildia Awant. Punkty zaplanowane na ten sezon Macera Loncası Bu sezon için planlanan puanlar Гільдія Авант. Заплановані очки цього сезону Hội Thám Hiểm Điểm dự kiến cho mùa này กิลด์ผู้ผจญภัย คะแนนที่กำหนดสำหรับซีซันนี้