Huny's Dev Blog
huny.dev

TTS 사전에서 같은 한글이 매칭되지 않을 때: Unicode NFC와 NFKC

같아 보이는 한글이 TTS 사전과 매칭되지 않는 원인을 NFC·NFKC 차이로 설명한다. 실행 가능한 Python 예제로 사전 조회와 호환 문자 키 충돌을 재현하고 안전한 정규화 적용 순서를 정리한다.

Hun Jang
Hun Jang Oct 27, 2026

화면에서는 같은 한글인데 TTS 사용자 사전이 적용되지 않는다면, 입력과 사전 키의 Unicode 정규화 형태부터 확인할 필요가 있다. 일반적인 텍스트 매칭에는 NFC를 일관되게 적용하는 것을 출발점으로 삼고, 전각 문자나 호환 기호까지 합칠 NFKC는 도메인 정책에 따라 선택하는 편이 좋다. 이 글에서는 Python으로 매칭 실패를 재현하고, 두 방식의 차이가 발음 사전 설계에 미치는 영향을 정리한다.

서로 다른 조각과 결합된 모양을 공통 형태로 맞추는 Unicode 정규화의 개념 일러스트. 정확한 문자 변환을 나타내는 도식은 아니다.

같은 '가'가 사전 키와 다를 수 있다

가는 U+AC00 한 글자로 표현할 수 있고, 초성 U+1100과 중성 U+1161의 연속으로도 표현할 수 있다. 두 문자열은 정준적으로 동등하지만, 정규화 전의 코드 포인트 배열은 다르다. 렌더링된 모양만 보고 문자열 키가 같다고 판단해서는 안 된다. Unicode 정규화 FAQ

사전에 U+AC00 형태만 저장하고 입력은 두 코드 포인트 형태로 들어오면 단순한 문자열 비교나 키 조회가 실패할 수 있다. UTF-8로 저장했다는 사실은 이 차이를 자동으로 없애주지 않는다.

NFC와 NFKC를 구분해서 선택한다

NFC는 정준 분해 후 가능한 문자를 다시 합성한다. NFKC는 호환 분해까지 수행한 뒤 합성하므로 더 많은 표기 구분을 없앤다. 네 가지 정규화 형태의 정의와 제약은 Unicode UAX #15에서 확인할 수 있다.

아래는 이 글의 실행 환경에서 직접 확인한 결과다.

입력
NFC
NFKC
U+1100 + U+1161
가
가
e + U+0301
é
é
AI
AI
AI
①
①
1
㎏
㎏
kg
Ⅳ
Ⅳ
IV

①과 1을 구분하는 사전이라면 NFKC를 거친 뒤 같은 키로 합쳐진다. ㎏도 kg로 바뀌지만, 이것만으로 발음이 '킬로그램'으로 결정되는 것은 아니다. Unicode 정규화와 숫자·단위·문맥에 맞는 발화 정규화는 별도의 처리다.

재현 코드: 입력과 사전 키에 같은 규칙 적용하기

다음 코드를 UTF-8로 저장한 normalize_demo.py에서 실행한다. 외부 패키지는 필요하지 않다. Python unicodedata 문서는 normalize의 지원 형태와 동작을 설명한다.

처음 두 검증은 원본 문자열이 다르다는 것과, NFC를 거친 입력으로 사전을 조회할 수 있다는 것을 확인한다. 마지막 검증은 NFKC로 ①과 1의 키 구분이 사라지는 상황을 확인한다. 정상 실행이면 변환 결과 다음에 lookup and compatibility-key collision: PASS가 출력된다.

TTS 파이프라인에 적용하는 순서

다음은 이 재현 결과를 바탕으로 권하는 설계다. 특정 TTS 엔진에 대한 성능 측정이나 음질 평가 결과는 아니다.

  1. 원문을 보존하고, 사전 조회용 텍스트를 별도로 만든다.
  1. 입력뿐 아니라 사용자·도메인 사전을 적재할 때도 같은 정규화 정책을 적용한다.
  1. 정규화 후 키가 같아지는 항목을 검사한다. 발음 값이 다르면 조용히 덮어쓰지 말고 충돌로 처리한다.
  1. 전각 문자 등도 통합해야 하는 영역에만 호환 정규화를 적용한다. 기호 의미를 먼저 분석해야 한다면 그 단계를 앞에 둔다.
  1. 숫자·단위 읽기와 언어·문맥 판단을 별도로 수행한다.

원문과 조회용 텍스트의 길이가 달라질 수 있으므로 자막 하이라이트나 오류 위치를 원문에 표시한다면 위치 대응도 고려해야 한다. 또한 정규화된 조각끼리 이어 붙인 결과가 항상 정규화 상태인 것은 아니다. 스트리밍 입력은 임의의 청크를 각각 처리하기보다 경계 상태를 유지하는 정규화 구현을 사용해야 한다. 문자열 연결 시의 주의점은 Unicode FAQ에서 설명한다.

적용 범위와 검증 환경

이 글의 재현 코드는 Python 3.12.14, Unicode 데이터베이스 15.0.0에서 실행했다. 한글·결합 악센트·전각 문자·호환 기호의 결과, NFC 사전 조회, NFKC 키 충돌을 확인했고, 별도로 샘플에 대한 NFC·NFKC 반복 적용 결과도 검사했다. 전체 Unicode 적합성 테스트나 실제 음성합성 엔진의 발음·음질 검증은 수행하지 않았다.

NFC가 모든 비슷한 글자를 같게 만드는 것은 아니며 NFKC도 일반적인 시각적 유사 문자 판별 기능은 아니다. 정규화는 바이트 인코딩 감지나 잘못 디코딩된 문자열의 복구 기능도 아니다. 인코딩과 문자 집합의 구분은 기존 글에서도 다뤘다.

한글 인코딩한글 인코딩

참고 문서

English version

Unicode NFC vs NFKC: Fixing TTS Dictionary MatchingUnicode NFC vs NFKC: Fixing TTS Dictionary Matching

추천 글