관련지식
node.js, pinyin, heteronym, hanzi-to-pinyin, pinyin-bot-core, hanzi-tools

중국어 공부를 해보신 분들은 알겠지만 중국어에는 발음 표기를 위한 병음(pinyin)이라는 글자가 존재합니다. 모르는 한자는 읽을수조차 없으니 발음 표기법이 생겨난 것이죠. 일본어는 공부한적이 없어서 잘 모르지만 일본에서는 한자 발음 표기를 위해 가타카나 또는 히라가나를 사용할것 같네요.(세종대왕님 감사합니다.)

그런데 중국어의 또다른 특징중에 동철이음이의어라는 것이 있습니다. 같은 문자(동철)가 읽는 법이 여러개(이음) 있고 뜻이 다르다(이의)는 것입니다. 즉 하나의 한자를 여러가지로 읽을수 있다는것인데 어떻게 읽어야 하는지를 알려면 그 한자가 어떻게 사용되었는지를 알아야 합니다. 즉 의미/구문 분석을 해야 한다는 것이죠. 그렇기 때문에 중국어를 시스템적으로 병음으로 변환하려면 생각보다 까다로운 상황이 됩니다.

그래서 이번에는 npm으로 설치가능한 중국어->병음 변환 패키지중 가장 성능이 좋았던 몇가지를 비교 설명드리겠습니다. 만약 병음 변환 기능을 만들어야 한다면 최소 2개 이상의 패키지를 같이 사용하는 것을 추천합니다.

테스트할 문장은 ‘어느 자리라도 괜찮아’ 뜻을 가진 ‘没什么哪儿都可以’로 중국어 공부 몇달만 했어도 대부분 읽을수 있는 문장이지만, 동철이음이의어가 많이 섞여있습니다.

pinyin

먼저 pinyin 패키지 입니다.

https://www.npmjs.com/package/pinyin

  1. let chn_text = "没什么哪儿都可以"; //méishénme nǎ'er dōu kěyǐ
  2. const pinyin = require("pinyin");
  3. console.log(pinyin(chn_text));
  4. console.log(pinyin(chn_text, {heteronym: true }));
  1. [ [ 'méi' ],
  2. [ 'shí' ],
  3. [ 'mó' ],
  4. [ 'nǎ' ],
  5. [ 'ér' ],
  6. [ 'dū' ],
  7. [ 'kě' ],
  8. [ 'yǐ' ] ]
  9. [ [ 'méi', 'mò' ],
  10. [ 'shí', 'shén' ],
  11. [ 'mó', 'ma', 'me', 'yāo' ],
  12. [ 'nǎ', 'něi', 'na', 'né' ],
  13. [ 'ér' ],
  14. [ 'dū', 'dōu' ],
  15. [ 'kě', 'kè' ],
  16. [ 'yǐ' ] ]

pinyin 패키지는 기본옵션으로 실행했을때 굉장히 이상한 병음을 출력합니다. 어휘 분석을 안한것인지 세글자가 잘못 표기되었습니다. 하지만 heteronym 옵션을 설정하면 동철이음이의어에 대한 발음을 모두 알려주므로 굉장히 강력한 패키지가 됩니다. pinyin 패키지는 자동으로 병음변환하는데는 적합하지 않지만 수동으로 변환하는데는 굉장히 큰 도움을 주는 패키지입니다.

다른 패키지를 쓰게 되면 아웃풋이 문자열인 경우도 있는데 pinyin 패키지와 같이 이중 배열 구조가 화면 구성하기에 편리합니다. 때문에 다른 패키지에서 아웃풋 형태가 달라도 이와 같은 이중 배열 구조로 변환하겠습니다.

hanzi-to-pinyin

hanzi-to-pinyin 패키지는 제가 사용했던 버전에선 문자열과 배열이 합쳐진 형태로 아웃풋이 나왔기 때문에 문자열을 배열로 바꾸는 코드가 추가되어 있습니다. 원래 아웃풋을 보려면 textArr를 출력해보면 됩니다.

https://www.npmjs.com/package/hanzi-to-pinyin

  1. let chn_text = "没什么哪儿都可以"; //méishénme nǎ'er dōu kěyǐ
  2. const hanzi_to_pinyin = require('hanzi-to-pinyin');
  3. hanzi_to_pinyin(chn_text).then(function(textArr) {
  4. let pinyins1 = [];
  5. textArr.map(function(part) {
  6. if(Array.isArray(part)) {
  7. pinyins1.push(part);
  8. return;
  9. }
  10. pinyins1 = pinyins1.concat(part.trim().split(' ').map(function(pinyin) {
  11. return [pinyin];
  12. }));
  13. });
  14. console.log(pinyins1);
  15. });
  1. [ [ 'méi' ],
  2. [ 'shén' ],
  3. [ 'me' ],
  4. [ 'nǎ' ],
  5. [ 'r' ],
  6. [ 'dū', 'dōu' ],
  7. [ 'kě' ],
  8. [ 'yǐ' ] ]

단어장을 사용하고 있는것인지 什么의 경성 표기도 잘 되어있습니다. 하지만  문자에 대해선 구분이 어려웠는지 두가지 발음을 보여주고 있습니다. 는 구글에서도 처리하기 어려운것 같습니다. 구글번역기에  한글자만 입력하면 dōu로 표기하고 음성은  발음하니까요. 일단 pinyin 패키지보다 깔끔한 출력을 보여줬습니다.

pinyin-bot-core

역시 마찬가지로 일관된 아웃풋을 위해 추가코드를 넣었습니다.

https://www.npmjs.com/package/pinyin-bot-core

  1. let chn_text = "没什么哪儿都可以"; //méishénme nǎ'er dōu kěyǐ
  2. const botCore = require('pinyin-bot-core');
  3. botCore.processMessage(chn_text).then(function(text) {
  4. let pinyins2 = text.split(' ').map(function(pinyin) {
  5. return [pinyin];
  6. });
  7. console.log(pinyins2);
  8. });
  1. [ [ 'méishén' ], [ 'me' ], [ 'nǎr' ], [ 'dū' ], [ 'kěyǐ' ] ]

pinyin-bot-core 패키지도  발음에서 벗어나질 못했네요. 그외엔 띄어쓰기가 좀 거슬립니다. 앞서 본 hanzi-to-pinyin 과 결과물은 비슷하지만 발음 선택권이 없다는것, 띄어쓰기가 좀 이상하지만(méishén me) 좋기도 하다는것(kěyǐ). 장단점이 섞여있네요.

hanzi-tools

마지막으로 hanzi-tools 패키지입니다. 자동화된 병음 변환이 필요하다면 이 패키지르 가장 추천합니다. 역시 일관된 아웃풋 모양을 위해 추가 코드를 넣었습니다.

https://www.npmjs.com/package/hanzi-tools

  1. let chn_text = "没什么哪儿都可以"; //méishénme nǎ'er dōu kěyǐ
  2. var pinyinify = require("hanzi-tools").pinyinify;
  3. let pinyins3 = pinyinify(chn_text).split(' ').map(function(pinyin) {
  4. return [pinyin];
  5. });
  6. console.log(pinyins3);
  1. [ [ 'méi​shén​me' ], [ 'nǎ​r' ], [ 'dōu' ], [ 'kě​yǐ' ] ]

띄어쓰기와 병음표기 모든 부분에서 가장 완벽한 출력결과를 보여주었습니다. 다른 문장의 아웃풋도 비교해보겠습니다. pinyin은 테스트에서 생략하겠습니다.

출력 비교

你叫什么名字 의 출력)

  1. hanzi-to-pinyin
  2. [ [ 'nǐ' ], [ 'jiào' ], [ 'shén' ], [ 'me' ], [ 'míng' ], [ 'zi' ] ]
  3. pinyin-bot-core
  4. [ [ 'nǐ' ], [ 'jiào' ], [ 'shénme' ], [ 'míngzi' ] ]
  5. hanzi-tools
  6. [ [ 'nǐ' ], [ 'jiào' ], [ 'shén​me' ], [ 'míng​zi' ] ]

我的猫喜欢吃苹果 의 출력)

  1. hanzi-to-pinyin
  2. [ [ 'wǒ' ],
  3. [ 'de', 'dī', 'dí', 'dì' ],
  4. [ 'māo' ],
  5. [ 'xǐ' ],
  6. [ 'huan' ],
  7. [ 'chī' ],
  8. [ 'píng' ],
  9. [ 'guǒ' ] ]
  10. pinyin-bot-core
  11. [ [ 'wǒ' ],
  12. [ 'de' ],
  13. [ 'māo' ],
  14. [ 'xǐhuan' ],
  15. [ 'chī' ],
  16. [ 'píngguǒ' ] ]
  17. hanzi-tools
  18. [ [ 'wǒ' ],
  19. [ 'de' ],
  20. [ 'māo' ],
  21. [ 'xǐ​huan' ],
  22. [ 'chī' ],
  23. [ 'píng​guǒ' ] ]

哪里哪里 의 출력)

  1. hanzi-to-pinyin
  2. [ [ 'nǎ' ], [ 'lǐ' ], [ 'nǎ' ], [ 'lǐ' ] ]
  3. pinyin-bot-core
  4. [ [ 'nǎlǐ' ], [ 'nǎ' ], [ 'lǐ' ] ]
  5. hanzi-tools
  6. [ [ 'nǎ​lǐ' ], [ 'nǎ​lǐ' ] ]

대부분 비슷한 성능을 보여주지만 hanzi-tools가 조금 더 좋은 출력을 보여줍니다. 조금더 긴문장인 ‘시간은 때로는 빨리가고, 때로는 천천히 지나간다’를 변환해보겠습니다.

时间有时候过得很快,有时候过得很慢 의 출력)

  1. hanzi-to-pinyin
  2. [ [ 'shí' ],
  3. [ 'jiān' ],
  4. [ 'yǒu' ],
  5. [ 'shí' ],
  6. [ 'hou' ],
  7. [ 'guò' ],
  8. [ 'dé' ],
  9. [ 'hěn' ],
  10. [ 'kuài' ],
  11. [ 'yǒu' ],
  12. [ 'shí' ],
  13. [ 'hou' ],
  14. [ 'guò' ],
  15. [ 'dé' ],
  16. [ 'hěn' ],
  17. [ 'màn' ] ]
  18. pinyin-bot-core
  19. [ [ 'shíjiān' ],
  20. [ 'yǒushí' ],
  21. [ 'hou' ],
  22. [ 'guòdé' ],
  23. [ 'hěn' ],
  24. [ 'kuài,yǒushí' ],
  25. [ 'hou' ],
  26. [ 'guòdé' ],
  27. [ 'hěn' ],
  28. [ 'màn' ] ]
  29. hanzi-tools
  30. [ [ 'shí​jiān' ],
  31. [ 'yǒu​shí​hou' ],
  32. [ 'guò' ],
  33. [ 'de' ],
  34. [ 'hěn' ],
  35. [ 'kuài,' ],
  36. [ 'yǒu​shí​hou' ],
  37. [ 'guò' ],
  38. [ 'de' ],
  39. [ 'hěn' ],
  40. [ 'màn' ] ]

제 경험상 병음 변환이 100% 잘되기는 어려운것 같습니다. 동철이음이의어가 어디선가 반드시 문제가 되기 때문입니다. 따라서 변환기능이 가장 좋은 hanzi-tools을 메인으로 쓰고, 변환이 어딘가 이상하면 pinyin을 이용해서 수동으로 교정하는게 가장 좋은것 같습니다. hanzi-to-pinyin와 pinyin-bot-core은 성능이 비슷하지만 조금씩 다른 결과를 줄때가 있으므로 3차, 4차 교정용으로 쓰는것을 추천드립니다.

추가 팁
hanzi-tools 패키지로 만들어진 병음에서 yǒu​shí​hou 같이 여러글자로 이루어진 한단어의 병음에는 글자 사이사이에 웹에서 인식가능한 ‘Zero width space &# 8203;’ 가 들어있습니다. 그렇기 때문에 이 값을 HTML까지 유지시킨다면 웹에서 더블클릭시 글자단위로 병음 선택이 가능합니다.