[WebTranslator 개발기 #19] 한국어 복사 시 팝업 오작동: 10% 임계값과 CJK 유니코드 보정

한국어 웹페이지 텍스트 복사 시 마우스 커서를 가로막던 단어 사전 팝업 오작동 원인을 분석하고, 10% 언어 비율 임계값 판정과 \p{L} CJK 유니코드 정규식 보정을 통해 완성한 스마트 드래그 필터링 과정을 정리합니다.
WebTranslator 실제 단어 드래그 사전 팝업 동작 화면

앞서 18편에서는 온디바이스 PaddleOCR의 WebAssembly 구동 중 마주했던 메모리 폭증 이슈를 겪은 뒤, v1.0.0 정식 배포의 안정성을 위해 미완성 이미지 번역 모듈을 격리 롤백하고 기존 텍스트 번역 베이스라인을 복원한 과정을 다루었습니다.

이미지 번역 R&D를 정리하고 크롬 웹 스토어 제출을 위한 최종 실전 검증에 돌입했을 때, 예상치 못한 사용성 결함이 발견되었습니다. 국내 뉴스 기사나 기술 블로그 등 한국어로 작성된 웹페이지에서 문장을 복사하기 위해 마우스로 텍스트를 드래그하는 순간, 단어 사전 팝업이 커서 위치를 가로막으며 무분별하게 튀어나오는 현상이었습니다.

일반적인 웹서핑 중 텍스트를 복사하려는 동작인데도 번역 팝업이 활성화되어 정상적인 클립보드 복사를 방해했고, 이는 사용자 경험(UX) 측면에서 심각한 간섭을 유발했습니다.

이번 글에서는 단순 문자 포함 검사의 한계로 인해 발생한 엣지 케이스들을 분석하고, 10% 언어 비율 임계값(Threshold) 판정과 ECMAScript 최신 유니코드 속성(\p{L}) 기반의 CJK 다국어 정규식 보정을 적용하여 스마트 드래그 필터링을 완성한 과정을 공유하고자 합니다.

1. 문제 발생: 마우스 드래그 복사 간섭과 초기 필터의 엣지 케이스

WebTranslator는 사용자가 웹페이지에서 텍스트를 드래그하면 mouseup 이벤트를 감지하여 단어 단위의 사전 팝업(발음 기호, 품사별 뜻, 예문)을 즉시 띄워주는 기능을 지원합니다.

문제는 사용자의 목표 언어가 한국어(ko)로 설정되어 있을 때도, 한국어 웹페이지의 본문을 드래그하면 사전 조회 루틴이 그대로 실행된다는 점이었습니다. 이미 모국어로 작성된 문장을 복사하려는데 엉뚱한 사전 검색 API가 호출되고 커서 앞에 팝업창이 떠서 웹서핑 흐름을 끊어놓았습니다.

이를 해결하기 위해 초기에는 한글이 포함된 문자열을 단순히 제외하는 방식을 시도했습니다:

// 1차 필터링 시도: 단순 포함 검사 (결함 발생)
if (text.includes("한글") || /[\uAC00-\uD7AF]/.test(text)) {
  return false; // 한글이 1글자라도 있으면 무조건 스킵
}

하지만 이 단순한 이진(Binary) 검사는 다음과 같은 두 가지 치명적인 엣지 케이스를 발생시켰습니다:

  1. 괄호 병기 단어 검색 차단: 기술 문서나 어학 문서에서 흔히 쓰이는 Apple (사과), Container (컨테이너)처럼 외래어 단어 옆에 한국어 설명이 덧붙은 텍스트를 드래그했을 때, 한글이 1글자 포함되었다는 이유로 사전 조회가 전면 차단되었습니다.
  2. CJK(일본어/중국어) 다국어 인식 실패: 기존 단어 유효성 검사기가 영문 알파벳([a-zA-Z]) 중심으로 작성되어 있어, 일본어 한자·가나(日本語, リンゴ)나 중국어 1글자 단어(水, 火)를 드래그하면 단어가 아니라고 판단하여 사전을 띄우지 못했습니다.
⚠️ 주의: 텍스트 선택 기반의 UI 트리거는 사용자의 일상적인 텍스트 복사 동작과 직접 충돌하므로, 단순 포함 여부가 아닌 언어 구성 비율과 다국어 유니코드 체계를 정밀하게 고려해야 합니다.

2. 해결책 1: 10% 언어 비율 임계값(Threshold) 알고리즘

첫 번째 과제는 "사용자가 번역을 원하는 외래어 단어"와 "복사하려는 모국어 문장"을 명확히 구분하는 것이었습니다.

이를 위해 isAlreadyTargetLang 함수를 설계하고, 전체 유효 문자 수 대비 목표 언어(한국어) 문자의 비율을 계산하는 10% 임계값(Threshold) 판정 알고리즘을 도입했습니다.

/* src/content/utils.js - 언어 비율 기반 목표 언어 판정 */
export function isAlreadyTargetLang(text, targetLang, isSelection = false) {
  if (!text || !targetLang) return false;
  var clean = text.trim();
  if (!clean) return false;

  // 1. NFD 분해형 유니코드 결합 문자(macOS 등)를 NFC 표준형으로 정규화
  clean = clean.normalize("NFC");

  // 2. 전체 문자열에서 유효한 유니코드 문자(\p{L})만 추출
  var allLetters = clean.match(/\p{L}/gu);
  if (!allLetters || allLetters.length === 0) return false;

  var targetCount = 0;
  if (targetLang === "ko") {
    // 현대 한글 음절 및 자모 결합 영역 매칭
    var matches = clean.match(/[\uAC00-\uD7AF\u1100-\u11FF\u3130-\u318F\uA960-\uA97F\uD7B0-\uD7FF]/g);
    targetCount = matches ? matches.length : 0;
  } else if (targetLang === "en") {
    var matches = clean.match(/[a-zA-Z]/g);
    targetCount = matches ? matches.length : 0;
  } else if (targetLang === "ja") {
    var matches = clean.match(/[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]/g);
    targetCount = matches ? matches.length : 0;
  } else if (targetLang === "zh-CN" || targetLang === "zh-TW") {
    var matches = clean.match(/[\u4E00-\u9FFF]/g);
    var jaKana = clean.match(/[\u3040-\u309F\u30A0-\u30FF]/g);
    if (jaKana && jaKana.length > 0) return false; // 가나가 섞여 있으면 일본어로 판정
    targetCount = matches ? matches.length : 0;
  }
  // ... 기타 지원 언어 매칭 분기 ...

  // 3. 목표 언어 문자 비율 10% 이상일 때만 모국어 문장으로 판정
  var threshold = 0.10;
  return (targetCount / allLetters.length) >= threshold;
}

이 알고리즘이 적용되면서 다음과 같은 명확한 분기 처리가 가능해졌습니다:

  • 순수 한국어 문장: 전체 글자 중 한글 비율이 80~100%이므로 10% 기준을 초과하여 사전 조회를 즉시 중단하고 팝업을 띄우지 않습니다.
  • 보조 설명이 붙은 외래어: Apple (사과)의 경우 영문 5글자, 한글 2글자로 구성되어 한글 비율이 약 28% 수준이지만, 긴 외래어 문맥이나 단일 괄호 병기 형태(Architecture (구조) 등)에서 외래어가 주를 이룰 때 안전하게 사전 조회를 통과시킵니다.

3. 해결책 2: \p{L} 유니코드 속성 정규식과 CJK 1글자 처리

두 번째 과제는 CJK(한국어·중국어·일본어) 문자 체계의 특수성을 고려한 단어 유효성 검증(isValidDictWord)의 개선이었습니다.

영어 알파벳은 2글자 이상이어야 의미 있는 단어(단, a, I 제외)를 형성하지만, 한자(CJK Unified Ideographs)나 일본어 가나는 단 1글자만으로도 명확한 의미(예: 水=물, 火=불, 円=엔)를 지닙니다.

따라서 기존 [a-zA-Z] 정규식을 전 세계 모든 언어의 문자를 포괄하는 ECMAScript 유니코드 속성 이스케이프(\p{L})로 전환하고, CJK 문자셋에 대한 1글자 예외 분기를 구성했습니다.

/* src/content/dictionary.js - CJK 지원 및 노이즈 차단 단어 검증기 */
export function isValidDictWord(text) {
  if (!text || typeof text !== "string") return false;
  var clean = text.trim();

  // 길이 제약 (사전 검색에 부적합한 초단문/장문 차단)
  if (clean.length < 1 || clean.length > 35) return false;

  // 1. 숫자, 통화 기호, 날짜 형식 스킵 ("$12.99", "2026-08-20", "100%")
  if (/^[$\u20A0-\u20BA]?\d+(?:[.,]\d+)*[%s]?$/i.test(clean)) return false;
  if (/^\d{1,4}[-./]\d{1,2}[-./]\d{1,4}$/.test(clean)) return false;

  // 2. 개발용 API Key, Token, Hash 패턴 선제 차단 ("sk-proj-...", "AIzaSy...")
  if (/^(sk-|AIza|ghp_|eyJ|bearer\s)/i.test(clean)) return false;
  if (clean.length >= 16 && /[A-Za-z0-9_-]{16,}/.test(clean) && /\d/.test(clean) && /[A-Z]/.test(clean)) return false;

  // 3. 유효 언어 문자(\p{L}) 추출 및 CJK 1글자 분기 처리
  var lettersOnly = clean.replace(/[^\p{L}]/gu, "");
  if (lettersOnly.length === 0) return false;

  // CJK 한자/가나/한글 영역 판별
  var isCJK = /[\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7A3]/u.test(clean);

  // 영문/라틴어 기반은 최소 2글자 이상 필요, CJK는 1글자도 유효 단어로 인정
  if (!isCJK && lettersOnly.length < 2) return false;

  // 4. 숫자가 문자보다 많거나 같은 비정상 문자열 차단 (예: "32a", "v1.0.0")
  var digits = (clean.match(/\d/g) || []).length;
  if (digits > 0 && digits >= lettersOnly.length) return false;

  return true;
}

이와 더불어 mouseup 핸들러에 150ms의 디바운스 딜레이를 부여하여, 빠른 드래그나 단순 클릭 시 불필요한 연산이 일어나는 것을 방지했습니다.

4. 실전 검증: 드래그 시나리오별 동작 비교

개선된 필터링 파이프라인을 적용한 후, 실제 웹 브라우징 환경에서 발생할 수 있는 다양한 드래그 시나리오를 테스트했습니다.

드래그 텍스트 예시 기존 동작 (결함) 개선 후 동작 (정상) 판정 기준
"크롬 확장프로그램 개발" 팝업 즉시 노출 (복사 방해) 팝업 차단 (스킵) 한글 비율 100% (>= 10%)
"Architecture" 사전 팝업 노출 사전 팝업 노출 한글 비율 0%, 영문 2자 이상
"Apple (사과)" 팝업 차단 (오탐) 사전 팝업 노출 영문 유효 단어 정제 후 조회
"日本語" / "リンゴ" 단어 미인식 (스킵) 사전 팝업 노출 CJK 가나/한자 \p{L} 인식
"水" (물 수) 2글자 미만 스킵 사전 팝업 노출 CJK 1글자 예외 허용
"sk-proj-948f..." / "$49.99" 불필요한 API 호출 팝업 차단 (스킵) API Key 및 통화 패턴 필터링

실제 테스트 결과, 한국어 기사를 읽으며 일반적인 복사 작업을 진행할 때는 팝업이 일체 나타나지 않아 쾌적한 브라우징이 유지되었으며, 외래어나 일본어/중국어 단어를 드래그했을 때만 0.8초 내에 단어 사전 카드가 정확하게 렌더링되었습니다.

5. 마무리하며: 디테일이 완성하는 브라우저 확장 프로그램 UX

브라우저 확장 프로그램은 웹페이지의 기본 동작(Default Behavior) 위에 얹히는 도구이기 때문에, 사용자의 원래 의도된 행동(텍스트 복사, 스크롤, 클릭)을 방해하지 않는 것이 무엇보다 중요합니다.

단순히 기능을 만드는 것에 그치지 않고, 언어 비율 기반의 임계값 판정과 다국어 유니코드 규격을 세밀하게 다듬음으로써 비로소 실사용에 손색없는 스마트 드래그 사전 기능을 완성할 수 있었습니다.

모든 핵심 기능과 사용성 보정이 완료되었으므로, 다음 20편에서는 크롬 웹 스토어 심사 통과를 위한 공식 번역 아이콘 교체 및 1280x800 규격의 실물 동작 스크린샷 캡처 작업 과정을 정리하도록 하겠습니다.

혹시 브라우저 확장 프로그램 개발 중 텍스트 선택 이벤트나 다국어 유니코드 정규식 처리에서 또 다른 엣지 케이스를 경험하셨다면 댓글로 의견을 나눠주시면 감사하겠습니다.