[개.고.생] 영어도 지원하는 한글 조사 처리

Park U-yeong·2021년 9월 4일
5
post-thumbnail

한글 조사

몇년 전에 담당했던 프로젝트 중에서 검색 결과를 표시하는 페이지를 만들어야 하는 상황이 있었다.
기획서에는 OOO(으)로 검색한 결과입니다. 형식으로 개발자를 배려해서 조사 처리 없이 메시지를 표시하게 되어 있었다.

그래도 단어에 맞게 조사를 적용하면 좋을 것 같아, 처리하는 방법과 라이브러리를 찾아본 후에 굳이 라이브러리를 쓸 필요까진 없다고 판단되어서 간단하게 직접 구현해보았다.

그런데 검색을 한글로 하면 문제가 없었지만 알파벳을 사용하는 경우는 별도의 처리가 없어서 조사가 맞지 않았다. 알파벳을 지원하는 라이브러리가 일부 있긴 했지만 막상 적용해보면 그 범위가 제한적이어서 역시나 서비스에 적용하기는 어려웠다.

영어 지원이 어려운 이유

한국어는 발음에 따라 사용되는 조사가 달라진다.

표음 문자인 한글은 한국어 발음을 표기하는 문자 조합이 규칙성을 가지기 때문에 아래 두 가지 조건을 파악하면 어떤 조사를 사용해야 되는지 쉽게 구분할 수 있다.

  • 종성의 유무
  • 조사가 로/으로일 경우 종성 은 없는 것으로 간주

그러나 영어를 표기하는 알파벳은 발음에 있어서 알파벳을 조합하는 규칙이 일정하지 않고, 조합된 글자도 상황에 따라 다르게 발음되는 경우가 있어서 글자만으로는 완벽하게 판단할 수가 없다.

  • private -> 프라이빗
  • motivate -> 모티베이트

알파벳 조합 패턴으로 종성 유무 파악

혹시나 영어를 한글로 표기하는 라이브러리가 있지 않을까 하고 찾아보았다. 당연히 없었다.
다만 서버를 거쳐서 변환해주는 영어-한글 표기 변환기 같은 서비스가 있긴 했다. 몇년 전에도 alpha였던거 같은데...
이런 서비스들은 보통 자체 db를 가지고 처리하는 방식이 있고, 웹에서 서비스하는 사전을 크롤링 해서 발음기호를 분석하는 방식 등이 있었다.

가벼운 마음으로 시작한 조사 처리가 점점 산으로...

조사 하나 붙이려고 서버를 왔다갔다 하는건 배보다 배꼽이 큰 격이라서, 다시 알파벳 조합을 체크하는 방법으로 돌아왔다.

하지만 단순히 단어의 마지막 알파벳을 체크하는 것은 안된다.
예를 들어 ~k를 체크한다면...

  • dunk -> 덩크
  • cook ->

~p를 체크한다면...

  • loop -> 루프
  • top ->

~ng는 괜찮겠지...

  • png -> 피엔지
  • ping ->

일단 예외 조건은 고려하지 않고 알파벳 자체 발음에 종성이 있는 것과, 종성이 있는 발음이 가능한 것을 모아봤다.

b, c, d, g, k, l, m, n, p, r, t

그리고 이 글자로 끝나는 단어에서 종성이 있는 발음을 조사했다.
당시에 주말 내내 사전 찾아가면서 했던거 같다...

예를 들면 b에 대한 조건을 처리한다면 아래와 같은 과정으로 패턴을 파악했다.

  • ~ab/~eb/~ib/~ob/~ub 처럼 모음+b 단어들을 조사
  • ~eab/~iob/~oub/~oob.. 형태의 모음+모음+b 단어들을 조사
  • hub/shrub/hubbub.. 같이 종성이 없는 케이스가 많은 모음+b 조합 조사
  • ~ub 처럼 종성이 없는 조건에서 다시 club/flub 같은 종성이 있는 예외 조사
  • 자음+b 조합에서 thumb/climb/bomb.. 처럼 예외가 있는 경우 조사

그래서 각 조건을 종합해 아래와 같은 정규식을 만들었다.

/\S(?:[aeiom]|lu)b$/i

그리고 글자별로 비슷한 과정으로 만든 정규식을 모아서 전체 정규식을 완성했다.

// >> 스크롤 있음
/(?:^[mn]'|\S[mn]e?|\S(?:[aeiom]|lu)b|(?:u|\S[aei]|[^o]o)p|(?:^i|[^auh]i|\Su|[^ei][ae]|[^oi]o)t|(?:\S[iou]|[^e][ae])c?k|\S[aeiou](?:c|ng))$/i

이렇게 작성한 정규식은 도저히 관리를 할 수 없을 것 같아서, 가독성 좋고 조건을 추가하기 쉽도록 아래와 같이 글자별로 분리해서 코드를 완성했다.

const REG_SPECIAL_CHAR = new RegExp(`(?:${[
    '[ㄱ-ㄷㅁ-ㅎ036]',
    '^[mn]',
    '\\S[mn]e?',
    '\\S(?:[aeiom]|lu)b',
    '(?:u|\\S[aei]|[^o]o)p',
    '(?:^i|[^auh]i|\\Su|[^ei][ae]|[^oi]o)t',
    '(?:\\S[iou]|[^e][ae])c?k',
    '\\S[aeiou](?:c|ng)',
    'foot|go+d|b[ai]g|private',
    '^(?:app|kor)',
].join('|')})$`, 'i');

그리고 종성이 이 되는 경우를 별도로 분리하여 사용할 조사가 로/으로인 경우는 위 정규식만 체크하고 이외의 경우는 위 조건과 아래 조건을 모두 체크하도록 처리하였다.

const REG_SPECIAL_RO = new RegExp(`(?:${[
    '[178ㄹ]',
    '^[lr]',
    '^\\Sr',
    '\\Sle?',
].join('|')})$`, 'i');

최종 완성한 라이브러리가 조사를 처리하는 과정은 아래와 같다.

  • 입력된 단어에서 마지막 글자 유니코드 체크
  • 유니코드가 한글 범위면 유니코드를 분석해 한글 조사 처리
  • 한글이 아니면 대상 문자열을 앞서 만든 정규식으로 체크하여 조사 처리

[전체 소스 코드 보기]

한계

마지막 글자조합이 동일해도 발음이 다른 경우가 있다.

  • food -> 푸드
  • good -> 굿

물론 이런 경우는 예외 단어들을 일일이 추가한다면 관리는 가능하다.
그러나 동일한 단어를 상황이나 사람에 따라 다르게 발음하는 경우는 방법이 없다.

  • it -> 아이티/잇
  • hook -> 후크/훅
  • talk -> 토크/톡

마치며

최근에 가볍게 글을 작성할 블로그가 필요해서 velog를 개설했다.
이왕 만든거 가끔 작성할 글 말고도 꾸준히 적을 만한 주제가 있을까 생각하다가 지금까지 개발하면서 고민했던 내용이나 문제를 해결 했던 경험을 개.고.생 시리즈로 한번 정리해보기로 했다.

시리즈 첫 글로 예전에 cox-postposition를 만들면서 고민했던 내용을 적어보았다.
구현한 내용은 데모 페이지에서 확인해볼 수 있다.

profile
What 12 9oing on?

0개의 댓글