옵션명 번역 방법 토글 기능 추가 및 Papago 번역 로직 개선. 한글 텍스트 필터링 기능 추가로 이미지 번역 시 한글 존재 여부에 따라 원본 이미지 반환 처리. 상세페이지 입력 메서드에서 입력 필드 인자 추가로 유연성 향상.
This commit is contained in:
parent
ddc0d90dc9
commit
fee50e4f13
57
mainUI_SP.py
57
mainUI_SP.py
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]]:
|
||||
"""폴리곤 방식으로 텍스트 영역 감지 (기본 방식)"""
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -13,7 +13,8 @@
|
|||
- 한시적으로 7월간 프리미엄 이상 회원에게 자체번역제공(VIP기능)
|
||||
- 상품명 셔플-상품명 ai생성 모두 OFF 기능 추가
|
||||
- 중복상품 문제로 상품수정에 문제가 발생할 경우 해당상품 삭제 후 다음상품 진행
|
||||
|
||||
- OCR 설정에서 치환값에 "이미지삭제" 단어를 넣으면 해당 이미지를 삭제
|
||||
- 이미지에 한국어가 감지된 경우 이미 번역된걸로 판단하여 번역하지 않는 기능 추가
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue