옵션명 번역 방법 토글 기능 추가 및 Papago 번역 로직 개선. 한글 텍스트 필터링 기능 추가로 이미지 번역 시 한글 존재 여부에 따라 원본 이미지 반환 처리. 상세페이지 입력 메서드에서 입력 필드 인자 추가로 유연성 향상.

This commit is contained in:
9700X_PC 2025-07-14 16:37:44 +09:00
parent ddc0d90dc9
commit fee50e4f13
7 changed files with 192 additions and 17 deletions

View File

@ -651,6 +651,30 @@ class MAIN_GUI(QMainWindow):
"on": [],
"off": []
},
"visible": { # ON/OFF별 visible 위젯 리스트
"on": ['optionTrnas_method_toggle', 'optionTrnas_method_label'],
"off": [],
}
},
"optionTrnas_method_toggle": {
"key": "option/option_trans_method_enabled", # 옵션 번역 사용 여부
"state_key": "optionTrnas_method",
"dependents": { # ON/OFF별 enable 위젯 리스트
"on": [],
"off": []
},
"visible": { # ON/OFF별 visible 위젯 리스트
"on": [],
"off": [],
}
},
"optionTrnas_method_label": {
"key": "option/option_trans_method_label", # 옵션 번역 사용 여부
"state_key": "",
"dependents": { # ON/OFF별 enable 위젯 리스트
"on": [],
"off": []
},
"visible": { # ON/OFF별 visible 위젯 리스트
"on": [],
"off": [],
@ -1035,6 +1059,7 @@ class MAIN_GUI(QMainWindow):
'memo_toggle_exposer': False,
'memo_toggle_order' : False,
'optionTrnas': False,
'optionTrnas_method': True,
'optionIMGTrans': False,
'optionIMGTrans_type': None,
'optionAutoSelect': False,
@ -3035,6 +3060,36 @@ class MAIN_GUI(QMainWindow):
self.option_toggle_layout.addWidget(self.optionTrans_widget)
# 옵션명 번역방법 토글
self.optionTrans_method_widget = QWidget()
self.optionTrans_method_toggle_layout = QHBoxLayout(self.optionTrans_method_widget)
self.optionTrnas_method_toggle_label = QLabel("옵션명 번역방법", self)
self.optionTrnas_method_toggle = ToggleSwitch(self)
self.optionTrnas_method_toggle.setOnText("GPT")
self.optionTrnas_method_toggle.setOffText("파파고")
self.optionTrnas_method_toggle.setObjectName("optionTrnas_method_toggle")
self.optionTrnas_method_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.optionTrnas_method_toggle, checked))
self.optionTrans_method_widget.enterEvent = lambda e: self.show_manual_html(
self.option_manual_group,
"🔤 옵션명 번역방법",
self.option_manual_label,
"""
<p>소싱된 원본옵션명을 한국어로 자동 번역합니다. 구글번역과 GPT를 사용합니다.</p>
<p>색상, 사이즈, 타입 등의 옵션명을 정확하게 번역하여 고객이 쉽게 이해할 있도록 합니다.</p>
<p>모델명, 스펙 고유명사는 최대한 보존됩니다.</p>
"""
)
self.optionTrans_method_widget.leaveEvent = lambda e: self.reset_manual(self.option_manual_group, self.option_manual_label)
self.optionTrans_method_toggle_layout.addWidget(self.optionTrnas_method_toggle_label)
self.optionTrans_method_toggle_layout.addWidget(self.optionTrnas_method_toggle)
self.option_toggle_layout.addWidget(self.optionTrans_method_widget)
# 옵션 이미지번역 토글
self.optionIMGTrans_widget = QWidget()
self.optionIMGTrans_toggle_layout = QHBoxLayout(self.optionIMGTrans_widget)
@ -3093,7 +3148,7 @@ class MAIN_GUI(QMainWindow):
# 옵션 Auto선택 토글
self.optionAutoSelect_widget = QWidget()
self.optionAutoSelect_toggle_layout = QHBoxLayout(self.optionAutoSelect_widget)
self.optionAutoSelect_toggle_label = QLabel("옵션 Auto선택", self)
self.optionAutoSelect_toggle_label = QLabel("옵션 자동선택", self)
self.optionAutoSelect_toggle = ToggleSwitch(self)
self.optionAutoSelect_toggle.setObjectName("optionAutoSelect_toggle")
self.optionAutoSelect_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.optionAutoSelect_toggle, checked))

View File

@ -273,11 +273,9 @@ class DetailHandler:
self.logger.log(f"이미지 업로드 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
return False
async def input_option_list(self, option_data):
async def input_option_list(self, option_data, input_field):
"""옵션 목록을 입력하는 메서드"""
try:
input_field = await self.page.wait_for_selector(self.locator_manager.get_locator('detail_page', 'detail_input_field'), timeout=10000)
# 옵션 목록 헤더 입력
await input_field.type("# 옵션 목록")
await input_field.press('Enter')
@ -305,7 +303,7 @@ class DetailHandler:
self.logger.log(f"옵션 목록 입력 중 오류 발생: {str(e)}", level=logging.ERROR, exc_info=True)
raise
async def input_detail_text(self, optionHandler):
async def input_detail_text(self, optionHandler, input_field):
"""
상세페이지에 소개글과 옵션 데이터를 입력하는 메서드
@ -317,7 +315,6 @@ class DetailHandler:
"""
try:
# 텍스트 입력 필드 선택
input_field = await self.page.wait_for_selector(self.cke_text_editing_area_locator, timeout=5000)
if not input_field:
self.logger.log("텍스트 입력 필드를 찾을 수 없습니다.", level=logging.ERROR)
return
@ -337,13 +334,13 @@ class DetailHandler:
# 옵션이 2개 이상이면 무조건 다중 옵션으로 처리
if len(option_data) > 1:
self.logger.log(f"다중 옵션 감지 ({len(option_data)}개), 옵션 목록 입력 시작", level=logging.DEBUG)
await self.input_option_list(option_data)
await self.input_option_list(option_data, input_field)
else:
# 옵션이 1개인 경우에만 is_single 값 확인
is_single = optionHandler.option_info.get('is_single_option', True)
if not is_single:
self.logger.log("단일 옵션이 아님, 옵션 목록 입력 시작", level=logging.DEBUG)
await self.input_option_list(option_data)
await self.input_option_list(option_data, input_field)
else:
self.logger.log("단일 옵션 상품으로 판단, 옵션 목록 입력 건너뜀", level=logging.DEBUG)
@ -395,14 +392,18 @@ class DetailHandler:
self.logger.log("변환할 이미지가 없습니다.", level=logging.INFO)
return False
# 텍스트 입력 필드 선택
input_field = self.page.locator(self.cke_text_editing_area_locator)
# 상세페이지 옵션에 따라 처리
self.logger.log(f"self.detail_Option : {self.detail_Option}", level=logging.DEBUG)
self.logger.log(f"self.detail_IMGTrans : {self.detail_IMGTrans}", level=logging.DEBUG)
if self.detail_Option or self.detail_IMGTrans:
if self.detail_Option:
self.logger.log("소개글 입력 시작...", level=logging.INFO)
await self.input_detail_text(optionHandler)
await self.input_detail_text(optionHandler, input_field)
if self.detail_IMGTrans:
self.logger.log("이미지 번역 시작...", level=logging.INFO)
@ -417,13 +418,12 @@ class DetailHandler:
await self.clear_ckeditor_data()
# 텍스트 입력 필드 선택
input_field = self.page.locator(self.cke_text_editing_area_locator)
await input_field.click()
self.logger.log("입력 필드 선택", level=logging.DEBUG)
# 소개글과 옵션데이터 입력
if self.detail_Option:
await self.input_detail_text(optionHandler)
await self.input_detail_text(optionHandler, input_field)
# 이미지 처리 통계
success_count = 0

View File

@ -299,9 +299,23 @@ class OptionHandler:
self.logger.log(f"옵션 AI번역 : {optionTrnas}", level=logging.DEBUG)
try:
# gpt를 통한 번역 시도
# 번역 방법 토글 확인
original_names = self._get_all_original_names()
translated_options = self.gpt_client.translate_options(original_names, product_name)
use_gpt_method = toggle_states.get('optionTrnas_method_toggle', False)
if use_gpt_method:
# 1) optionTrnas_method_toggle=TRUE → GPT 방식 사용
self.logger.log("GPT 번역 방식 사용", level=logging.DEBUG)
translated_options = self.gpt_client.translate_options(original_names, product_name)
else:
# 2) optionTrnas_method_toggle=FALSE → Papago 방식 사용 + GPT 폴백
self.logger.log("Papago 번역 방식 사용", level=logging.DEBUG)
translated_options = await self.translate_options_papago(original_names, product_name)
# Papago 번역이 실패(원본 그대로 반환)한 경우 GPT 모델로 폴백
if all(k == v for k, v in translated_options.items()):
self.logger.log("Papago 번역 실패 또는 미변경 GPT 모델로 폴백", level=logging.WARNING)
translated_options = self.gpt_client.translate_options(original_names, product_name)
self.logger.log(f"번역된 옵션 입력", level=logging.DEBUG)
await self.apply_translated_options(translated_options, forbidden_manager)
@ -324,7 +338,7 @@ class OptionHandler:
self.is_percenty_success = True
# 번역 성공 여부에 따른 로그
self.logger.log(f"[{'GPT_AI' if self.is_gpt_success else '퍼센티AI'}] 를 이용한 옵션번역 성공", level=logging.DEBUG)
self.logger.log(f"[{'GPT_AI or papago' if self.is_gpt_success else '퍼센티AI'}] 를 이용한 옵션번역 성공", level=logging.DEBUG)
# 5. 옵션 필터링 및 조정
@ -1873,3 +1887,84 @@ class OptionHandler:
# }
# '''
async def translate_options_papago(self, original_data: dict, product_name: str, headless: bool = True) -> dict:
"""
Papago 번역기를 활용하여 옵션명을 번역하고 공통어를 제거·간결화합니다.
Args:
original_data (dict): {원본옵션명: 원본옵션명} 형태의 사전.
product_name (str): 상품명 (인터페이스 유지용, 현재 로직에서는 미사용).
headless (bool): Playwright 브라우저 헤드리스 옵션.
Returns:
dict: {원본옵션명: 간결한 번역옵션명} 형태의 매핑.
"""
try:
# 런타임 import 로 무거운 의존성을 지연 로딩
from translator.papago_translator import PapagoTranslatorWithPlaywright
except Exception as e:
self.logger.log(f"PapagoTranslator import 실패: {e}", level=logging.ERROR)
# 실패 시 원본 그대로 반환하여 이후 퍼센티 AI 로직으로 넘어가도록 함
return {k: k for k in original_data.keys()}
original_names = list(original_data.keys())
joined_text = "\n".join(original_names)
# 1차: 한 번에 번역 (속도 우선)
try:
async with PapagoTranslatorWithPlaywright(headless=headless) as translator:
result_text = await translator.translate(joined_text, source_lang="zh", target_lang="ko")
except Exception as e:
self.logger.log(f"Papago 1회 번역 실패: {e}", level=logging.ERROR, exc_info=True)
result_text = None
# 번역 결과 줄 수 불일치 또는 실패 시 → 배치 번역으로 재시도
translated_lines: list[str] = []
if result_text:
translated_lines = [line.strip() for line in result_text.splitlines() if line.strip()]
if len(translated_lines) != len(original_names):
self.logger.log("Papago 줄 수 불일치 batch 번역으로 재시도", level=logging.WARNING)
try:
async with PapagoTranslatorWithPlaywright(headless=headless) as translator:
batch_results = await translator.translate_batch(original_names, source_lang="zh", target_lang="ko")
translated_lines = [batch_results.get(name, "") or name for name in original_names]
except Exception as e:
self.logger.log(f"Papago 배치 번역 실패: {e}", level=logging.ERROR, exc_info=True)
translated_lines = [name for name in original_names]
# 공통 토큰 추출 및 간결화
common_tokens = self._extract_common_tokens(translated_lines)
simplified_lines = [self._simplify_translation(t, common_tokens, max_length=15) for t in translated_lines]
translated_options = {original_names[i]: simplified_lines[i] for i in range(len(original_names))}
self.logger.log(f"Papago 번역 최종 결과: {translated_options}", level=logging.DEBUG)
return translated_options
def _extract_common_tokens(self, translations: list[str]) -> set:
"""여러 옵션명에서 공통으로 등장하는 토큰 집합을 추출"""
import re
if not translations:
return set()
token_sets = [set(re.split(r"\s+", txt)) for txt in translations if txt]
return set.intersection(*token_sets) if token_sets else set()
def _simplify_translation(self, text: str, common_tokens: set, max_length: int = 15) -> str:
"""공통어 제거, 중복 토큰 제거 후 지정 길이 내로 축약"""
import re
tokens = [tok for tok in re.split(r"\s+", text) if tok and tok not in common_tokens]
# 순서 보존하며 중복 제거
seen = set()
filtered_tokens = []
for tok in tokens:
if tok not in seen:
seen.add(tok)
filtered_tokens.append(tok)
simplified = " ".join(filtered_tokens)
# 예: 8대 → 8세대 치환
simplified = re.sub(r"(\d+)대", r"\1세대", simplified)
# 15자 제한
simplified = simplified[:max_length]
return simplified.strip()

View File

@ -177,11 +177,16 @@ class ImageProcessor3:
self.logger.log(f"filter_ocr_results: {filter_ocr_results}", level=logging.DEBUG)
ocr_count = len(filter_ocr_results)
# 3. 중국어 텍스트 없는 경우 원본 이미지 반환
# 3. 중국어 텍스트 없는 경우 원본 이미지 반환으로 번역 패스
if not self.ocr_module.filter_chinese_text(filter_ocr_results):
self.logger.log(f"이미지 {index+1} 중국어 텍스트 없음, 원본 이미지 반환", level=logging.DEBUG)
return {'status': 'original', 'path': local_image_path}
# 4. 한글 텍스트가 존재하는 경우 원본 이미지 반환으로 번역 패스
if self.ocr_module.filter_korean_text(filter_ocr_results):
self.logger.log(f"이미지 {index+1} 한글 텍스트 존재, 원본 이미지 반환", level=logging.DEBUG)
return {'status': 'original', 'path': local_image_path}
# 4. 텍스트 번역
translated_texts = self.batch_google_translate_texts(filter_ocr_results)
self.logger.log(f"translated_texts: {translated_texts}", level=logging.DEBUG)

View File

@ -148,7 +148,26 @@ class OCRModule:
self.logger.log(f"중국어 텍스트 {len(chinese_results)}개 필터링 완료", level=logging.INFO)
return chinese_results
def filter_korean_text(self, ocr_results: List[Dict]) -> List[Dict]:
"""
한글 텍스트만 필터링
Args:
ocr_results (List[Dict]): OCR 결과
Returns:
List[Dict]: 한글 텍스트만 포함된 결과
"""
korean_results = []
for result in ocr_results:
text = result['text']
# 한글 유니코드 범위: 가~힣
if any('\uac00' <= char <= '\ud7a3' for char in text):
korean_results.append(result)
self.logger.log(f"한글 텍스트 {len(korean_results)}개 필터링 완료", level=logging.INFO)
return korean_results
def _detect_with_polygon(self, image: np.ndarray, ocr_raw_results: List) -> List[Dict[str, Any]]:
"""폴리곤 방식으로 텍스트 영역 감지 (기본 방식)"""

View File

@ -13,7 +13,8 @@
- 한시적으로 7월간 프리미엄 이상 회원에게 자체번역제공(VIP기능)
- 상품명 셔플-상품명 ai생성 모두 OFF 기능 추가
- 중복상품 문제로 상품수정에 문제가 발생할 경우 해당상품 삭제 후 다음상품 진행
- OCR 설정에서 치환값에 "이미지삭제" 단어를 넣으면 해당 이미지를 삭제
- 이미지에 한국어가 감지된 경우 이미 번역된걸로 판단하여 번역하지 않는 기능 추가