LanguageTranslation.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. #! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # COPYRIGHT: Openmoko Inc. 2010
  4. # LICENSE: GPL Version 3 or later
  5. # DESCRIPTION: Converting Asian language titles to phonetic representation
  6. # AUTHORS: Sean Moss-Pultz <sean@openmoko.com>
  7. # Christopher Hall <hsw@openmoko.com>
  8. import os
  9. import sys
  10. import string
  11. import unicodedata
  12. import PinyinTable
  13. try:
  14. import MeCab
  15. except:
  16. print 'error: Missing python module: python-mecab'
  17. print ' sudo apt-get install python-mecab mecab-ipadic-utf8'
  18. exit(1)
  19. class LanguageProcessor(object):
  20. EQUIVALENTS = {
  21. u'æ': u'ae',
  22. u'ƀ': u'b',
  23. u'ƃ': u'b',
  24. u'ɓ': u'b',
  25. u'ƈ': u'c',
  26. u'đ': u'd',
  27. u'ȡ': u'd',
  28. u'ð': u'eth',
  29. u'ħ': u'h',
  30. u'ij': u'ij',
  31. u'ŀ': u'l',
  32. u'ł': u'l',
  33. u'ʼn': u'n',
  34. u'ƞ': u'n',
  35. u'ŋ': u'ng',
  36. u'ɔ': u'o',
  37. u'ø': u'o',
  38. u'œ': u'oe',
  39. u'ȣ': u'ou',
  40. u'ß': u's',
  41. u'ſ': u's',
  42. u'ŧ': u't',
  43. u'þ': u'th',
  44. u'ȝ': u'y',
  45. u'ȥ': u'z',
  46. u'ƶ': u'z',
  47. u'×': u'*',
  48. u'÷': u'/',
  49. }
  50. def __init__(self, *args, **kw):
  51. """create new instance"""
  52. try:
  53. self.cjk_convert = kw['cjk_convert']
  54. except KeyError:
  55. self.cjk_convert = True
  56. for k, d in self.EQUIVALENTS.items():
  57. u = k.upper()
  58. if u not in self.EQUIVALENTS:
  59. self.EQUIVALENTS[u] = d.upper()
  60. def translate(self, text):
  61. """base translation using unicode tables"""
  62. if unicode != type(text):
  63. text = unicode(text, 'utf-8')
  64. text = text.strip()
  65. result = ''
  66. for c in text:
  67. try:
  68. n = unicodedata.name(c).split() + ['None', 'None', 'None', 'None']
  69. except ValueError:
  70. n = ('Nothing', 'None', 'None', 'None')
  71. character_class = n[0]
  72. is_letter = 'LETTER' == n[1] or 'LETTER' == n[2]
  73. is_small = 'SMALL' == n[1] or 'SMALL' == n[2]
  74. is_capital = 'CAPITAL' == n[1] or 'CAPITAL' == n[2]
  75. if 'HANGUL' == character_class:
  76. result += n[2]
  77. elif character_class in ['HIRAGANA', 'KATAKANA']:
  78. if self.cjk_convert and is_letter:
  79. # attempt to convert Japanese phonetic when doing Chinese->Pinyin
  80. if is_small:
  81. result += n[3]
  82. else:
  83. result += n[2]
  84. else:
  85. result += c
  86. elif self.cjk_convert and 'CJK' == character_class:
  87. # use only the first of the list of phonetics available
  88. try:
  89. p = PinyinTable.pinyin[c][0]
  90. except KeyError:
  91. p = c
  92. result += unicodedata.normalize('NFD', p)
  93. elif character_class in ['GREEK', 'COPTIC']:
  94. try:
  95. g = n[3][0]
  96. if is_small:
  97. g = g.lower()
  98. result += g
  99. except IndexError:
  100. result += c
  101. elif 'CYRILLIC' == character_class:
  102. try:
  103. if 'SHORT' == n[3]:
  104. g = n[4]
  105. else:
  106. g = n[3]
  107. if g in ['HARD', 'SOFT']:
  108. pass
  109. else:
  110. if len(g) >= 2:
  111. if 'E' == g[0]:
  112. g = g[1:]
  113. elif g[0] not in u'AEIOUY':
  114. g = g[:-1]
  115. if g[0] in u'G':
  116. g = g[:-1]
  117. if is_small:
  118. g = g.lower()
  119. result += g
  120. except IndexError:
  121. result += c
  122. else:
  123. for c in unicodedata.normalize('NFD', c):
  124. if c in self.EQUIVALENTS:
  125. result += self.EQUIVALENTS[c]
  126. else:
  127. result += c
  128. return result
  129. class LanguageNormal(LanguageProcessor):
  130. """no-op class"""
  131. def __init__(self, *args, **kw):
  132. """create new instance"""
  133. super(LanguageNormal, self).__init__(*args, **kw)
  134. def translate(self, text):
  135. """normal translation to alphabetic"""
  136. return super(LanguageNormal, self).translate(text)
  137. class LanguageJapanese(LanguageProcessor):
  138. """Convert Japanese to Romaji"""
  139. PHONETIC = {
  140. # Katakana
  141. u'ア': 'a', u'イ': 'i', u'ウ': 'u', u'エ': 'e', u'オ': 'o',
  142. u'カ': 'ka', u'キ': 'ki', u'ク': 'ku', u'ケ': 'ke', u'コ': 'ko',
  143. u'ガ': 'ga', u'ギ': 'gi', u'グ': 'gu', u'ゲ': 'ge', u'ゴ': 'go',
  144. u'サ': 'sa', u'シ': 'shi', u'ス': 'su', u'セ': 'se', u'ソ': 'so',
  145. u'ザ': 'za', u'ジ': 'ji', u'ズ': 'zu', u'ゼ': 'ze', u'ゾ': 'zo',
  146. u'タ': 'ta', u'チ': 'chi', u'ツ': 'tsu', u'テ': 'te', u'ト': 'to',
  147. u'ダ': 'da', u'ヂ': 'di', u'ヅ': 'du', u'デ': 'de', u'ド': 'do',
  148. u'ナ': 'na', u'ニ': 'ni', u'ヌ': 'nu', u'ネ': 'ne', u'ノ': 'no',
  149. u'ハ': 'ha', u'ヒ': 'hi', u'フ': 'fu', u'ヘ': 'he', u'ホ': 'ho',
  150. u'バ': 'ba', u'ビ': 'bi', u'ブ': 'bu', u'ベ': 'be', u'ボ': 'bo',
  151. u'パ': 'pa', u'ピ': 'pi', u'プ': 'pu', u'ペ': 'pe', u'ポ': 'po',
  152. u'マ': 'ma', u'ミ': 'mi', u'ム': 'mu', u'メ': 'me', u'モ': 'mo',
  153. u'ヤ': 'ya', u'ユ': 'yu', u'ヨ': 'yo',
  154. u'ラ': 'ra', u'リ': 'ri', u'ル': 'ru', u'レ': 're', u'ロ': 'ro',
  155. u'ワ': 'wa', u'ヱ': 'we', u'ヲ': 'wo',
  156. u'ン': 'nn',
  157. u'ー': '-',
  158. u'ウァ': 'wha', u'ウィ': 'whi', u'ウェ': 'whe', u'ウォ': 'who',
  159. u'ヴァ': 'va', u'ヴィ': 'vi', u'ヴ': 'vu', u'ヴェ': 've', u'ヴォ': 'vo',
  160. u'チャ': 'cya', u'チィ': 'cyi', u'チュ': 'cyu', u'チェ': 'cye', u'チョ': 'cyo',
  161. u'ニャ': 'nya', u'ニィ': 'nyi', u'ニュ': 'nyu', u'ニェ': 'nye', u'ニョ': 'nyo',
  162. u'シャ': 'sya', u'シィ': 'syi', u'シュ': 'syu', u'シェ': 'sye', u'ショ': 'syo',
  163. u'キァ': 'kya', u'キィ': 'kyi', u'キュ': 'kyu', u'キェ': 'kye', u'キョ': 'kyo',
  164. u'テャ': 'tha', u'ティ': 'thi', u'テュ': 'thu', u'テェ': 'the', u'テョ': 'tho',
  165. u'ヒャ': 'hya', u'ヒィ': 'hyi', u'ヒュ': 'hyu', u'ヒェ': 'hye', u'ヒョ': 'hyo',
  166. u'ミャ': 'mya', u'ミィ': 'myi', u'ミュ': 'myu', u'ミェ': 'mye', u'ミョ': 'myo',
  167. u'リャ': 'rya', u'リィ': 'ryi', u'リュ': 'ryu', u'リェ': 'rye', u'リョ': 'ryo',
  168. u'ジャ': 'ja', u'ジィ': 'jyi', u'ジュ': 'ju', u'ジェ': 'je' , u'ジョ': 'jo',
  169. u'ギャ': 'gya', u'ギィ': 'gyi', u'ギュ': 'gyu', u'ギェ': 'gye', u'ギョ': 'gyo',
  170. u'ビャ': 'bya', u'ビィ': 'byi', u'ビュ': 'byu', u'ビェ': 'bye', u'ビョ': 'byo',
  171. u'ピャ': 'pya', u'ピィ': 'pyi', u'ピュ': 'pyu', u'ピェ': 'pye', u'ピョ': 'pyo',
  172. u'クァ': 'kha', u'クィ': 'khi', u'クゥ': 'khu', u'クェ': 'khe', u'クォ': 'kho',
  173. u'グァ': 'gha', u'グィ': 'ghi', u'グゥ': 'ghu', u'グェ': 'ghe', u'グォ': 'gho',
  174. u'ファ': 'fa', u'フィ': 'fi', u'フェ': 'fe', u'フォ': 'fo',
  175. u'フャ': 'fya', u'フュ': 'fyu', u'フョ': 'fyo',
  176. u'デァ': 'dha', u'ディ': 'dhi', u'デュ': 'dhu', u'デェ': 'dhe', u'デョ': 'dho',
  177. u'ツァ': 'tsa', u'ツィ': 'tsi', u'ツェ': 'tse', u'ツォ': 'tso',
  178. # Hiragana
  179. u'あ': 'a', u'い': 'i', u'う': 'u', u'え': 'e', u'お': 'o',
  180. u'か': 'ka', u'き': 'ki', u'く': 'ku', u'け': 'ke', u'こ': 'ko',
  181. u'が': 'ga', u'ぎ': 'gi', u'ぐ': 'gu', u'げ': 'ge', u'ご': 'go',
  182. u'さ': 'sa', u'し': 'shi', u'す': 'su', u'せ': 'se', u'そ': 'so',
  183. u'ざ': 'za', u'じ': 'ji', u'ず': 'zu', u'ぜ': 'ze', u'ぞ': 'zo',
  184. u'た': 'ta', u'ち': 'chi', u'つ': 'tsu', u'て': 'te', u'と': 'to',
  185. u'だ': 'da', u'ぢ': 'di', u'づ': 'du', u'で': 'de', u'ど': 'do',
  186. u'な': 'na', u'に': 'ni', u'ぬ': 'nu', u'ね': 'ne', u'の': 'no',
  187. u'は': 'ha', u'ひ': 'hi', u'ふ': 'fu', u'へ': 'he', u'ほ': 'ho',
  188. u'ば': 'ba', u'び': 'bi', u'ぶ': 'bu', u'べ': 'be', u'ぼ': 'bo',
  189. u'ぱ': 'pa', u'ぴ': 'pi', u'ぷ': 'pu', u'ぺ': 'pe', u'ぽ': 'po',
  190. u'ま': 'ma', u'み': 'mi', u'む': 'mu', u'め': 'me', u'も': 'mo',
  191. u'や': 'ya', u'ゆ': 'yu', u'よ': 'yo',
  192. u'ら': 'ra', u'り': 'ri', u'る': 'ru', u'れ': 're', u'ろ': 'ro',
  193. u'わ': 'wa', u'ゐ': 'wi', u'ゑ': 'we', u'を': 'wo',
  194. u'ん': 'nn',
  195. u'ー': '-',
  196. u'うぁ': 'wha', u'うぃ': 'whi', u'うぇ': 'whe', u'うぉ': 'who',
  197. u'ゔぁ': 'va', u'ゔぃ': 'vi', u'ゔ': 'vu', u'ゔぇ': 've', u'ゔぉ': 'vo',
  198. u'チゃ': 'cya', u'チぃ': 'cyi', u'チゅ': 'cyu', u'チぇ': 'cye', u'チょ': 'cyo',
  199. u'にゃ': 'nya', u'にぃ': 'nyi', u'にゅ': 'nyu', u'にぇ': 'nye', u'にょ': 'nyo',
  200. u'しゃ': 'sya', u'しぃ': 'syi', u'しゅ': 'syu', u'しぇ': 'sye', u'しょ': 'syo',
  201. u'きぁ': 'kya', u'きぃ': 'kyi', u'きゅ': 'kyu', u'きぇ': 'kye', u'きょ': 'kyo',
  202. u'てゃ': 'tha', u'てぃ': 'thi', u'てゅ': 'thu', u'てぇ': 'the', u'てょ': 'tho',
  203. u'ひゃ': 'hya', u'ひぃ': 'hyi', u'ひゅ': 'hyu', u'ひぇ': 'hye', u'ひょ': 'hyo',
  204. u'みゃ': 'mya', u'みぃ': 'myi', u'みゅ': 'myu', u'みぇ': 'mye', u'みょ': 'myo',
  205. u'りゃ': 'rya', u'りぃ': 'ryi', u'りゅ': 'ryu', u'りぇ': 'rye', u'りょ': 'ryo',
  206. u'じゃ': 'ja', u'じぃ': 'jyi', u'じゅ': 'ju', u'じぇ': 'je' , u'じょ': 'jo',
  207. u'ぎゃ': 'gya', u'ぎぃ': 'gyi', u'ぎゅ': 'gyu', u'ぎぇ': 'gye', u'ぎょ': 'gyo',
  208. u'びゃ': 'bya', u'びぃ': 'byi', u'びゅ': 'byu', u'びぇ': 'bye', u'びょ': 'byo',
  209. u'ぴゃ': 'pya', u'ぴぃ': 'pyi', u'ぴゅ': 'pyu', u'ぴぇ': 'pye', u'ぴょ': 'pyo',
  210. u'くぁ': 'kha', u'くぃ': 'khi', u'くぅ': 'khu', u'くぇ': 'khe', u'くぉ': 'kho',
  211. u'ぐぁ': 'gha', u'ぐぃ': 'ghi', u'ぐぅ': 'ghu', u'ぐぇ': 'ghe', u'ぐぉ': 'gho',
  212. u'ふぁ': 'fa', u'ふぃ': 'fi', u'ふぇ': 'fe', u'ふぉ': 'fo',
  213. u'ふゃ': 'fya', u'ふゅ': 'fyu', u'ふょ': 'fyo',
  214. u'でぁ': 'dha', u'でぃ': 'dhi', u'でゅ': 'dhu', u'でぇ': 'dhe', u'でょ': 'dho',
  215. u'つぁ': 'tsa', u'つぃ': 'tsi', u'つぇ': 'tse', u'つぉ': 'tso',
  216. }
  217. def __init__(self, *args, **kw):
  218. """intitialise MeCab library"""
  219. super(LanguageJapanese, self).__init__(*args, cjk_convert=False, **kw)
  220. self.mecab = MeCab.Tagger('-O chasen')
  221. def romanise(self, text):
  222. """private method for converting Japanese phonetics to Romaji"""
  223. if type(text) != unicode:
  224. text = unicode(text, "utf-8")
  225. result = ''
  226. i = 0
  227. duplicate = False
  228. last = len(text) - 1
  229. while i <= last:
  230. key = text[i:i + 2] # extract a pair of phonetics
  231. if not (i < last and key in self.PHONETIC):
  232. key = text[i]
  233. if key in self.PHONETIC:
  234. s = self.PHONETIC[key]
  235. i += len(key) - 1
  236. if duplicate:
  237. s = s[0] + s
  238. duplicate = False
  239. result += s
  240. elif key in u'ッっ':
  241. duplicate = True
  242. else:
  243. result += key
  244. duplicate = False
  245. i += 1
  246. return result
  247. def translate(self, text):
  248. """take Japanese string and convert to Roman letters"""
  249. result = ''
  250. for text in super(LanguageJapanese, self).translate(text).split():
  251. if type(text) == unicode:
  252. text = text.encode('utf-8')
  253. n = self.mecab.parseToNode(text)
  254. while n:
  255. if n.surface == '':
  256. n = n.next
  257. continue
  258. feature = unicode(n.feature,'utf-8').split(',')
  259. if len(feature) < 8 or feature[7] == '*':
  260. r = self.romanise(n.surface)
  261. else:
  262. r = self.romanise(feature[7])
  263. result += r
  264. n = n.next
  265. result += ' '
  266. return result.strip()
  267. def test_items(strings, translate):
  268. for lang, text in strings:
  269. print(u'{lang:s} in: {src:s}\n{lang:s} out: {dst:s}\n'.format(lang=lang, src=text, dst=translate(text)).encode('utf-8'))
  270. def main():
  271. """perform tests"""
  272. texts = [
  273. ('da', u'farvandsovervågning, søredning, isbrydning, forureningsbekæmpelse på havet'),
  274. ('is', u'um rannsóknaraðferðir vísindamanna og ádeilu á Danmörku og var þá um tíma ÞÁAÐ'),
  275. ('de', u'Άρκτος, arktós, Arktus (‚[Großer] Bär‘, für '),
  276. ('cs', u'je rovnoběžka, která vede východo-západně ve směru zemské rotace [skrýt] Čtyři světové strany'),
  277. ('ko', u'질량이 태양과 비슷한 별들은'),
  278. ('ja', u'GFDLのみでライセンスされたコンテンツ(あらゆる文章、ファイルを含む)の受け入れが禁止となりました。'),
  279. ('ja2', u'2004年新潟県中越地震 孫正義 孫悟空 孫子 バラク・オバマ スタぴか'),
  280. ('qq', u'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģ'),
  281. ('q1', u'ĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈ'),
  282. ('q2', u'ƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀǁǂǃDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯ'),
  283. ('q3', u'ǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏ'),
  284. ('q4', u'ɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀʁʂʃʄʅʆʇʈʉʊʋʌʍʎʏʐʑʒʓʔʕʖʗʘʙʚʛʜʝʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯ'),
  285. ('el', u'Ά·ΈΉΊ΋Ό΍ΎΏΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡ΢ΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώϏϐϑϒϓϔϕϖϗϘϙϚϛϜϝϞϟϠϡ'),
  286. ('coptic', u'ϢϣϤϥϦϧϨϩϪϫϬϭϮϯϰϱϲϳϴϵ϶ϷϸϹϺϻϼϽϾϿ'),
  287. ('el1', u'ἀἁἂἃἄἅἆἇἈἉἊἋἌἍἎἏἐἑἒἓἔἕ἖἗ἘἙἚἛἜἝ἞἟ἠἡἢἣἤἥἦἧἨἩἪἫἬἭἮἯἰἱἲἳἴἵἶἷἸἹἺἻἼἽἾἿὀὁὂὃὄὅ὆὇ὈὉὊὋὌὍ὎὏ὐὑὒὓὔὕὖὗ὘Ὑ὚Ὓ὜Ὕ὞Ὗ'),
  288. ('el2', u'ὠὡὢὣὤὥὦὧὨὩὪὫὬὭὮὯὰάὲέὴήὶίὸόὺύὼώ὾὿ᾀᾁᾂᾃᾄᾅᾆᾇᾈᾉᾊᾋᾌᾍᾎᾏᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾞᾟᾠᾡᾢᾣᾤᾥᾦᾧᾨᾩᾪᾫᾬᾭᾮᾯᾰᾱᾲᾳᾴ᾵ᾶᾷᾸᾹᾺΆᾼ᾽ι᾿῀῁'),
  289. ('el3', u'ῂῃῄ῅ῆῇῈΈῊΉῌ῍῎῏ῐῑῒΐ῔῕ῖῗῘῙῚΊ῜῝῞῟ῠῡῢΰῤῥῦῧῨῩῪΎῬ῭΅`῰῱ῲῳῴ῵ῶῷῸΌῺΏῼ´῾'),
  290. ('zh', u'欧洲,软件+互联网[用统一码] 歐洲,軟體及網際網路[讓統一碼] ABC 西安 先'),
  291. ('ru', u'Является административным центром Лозовской городской совет, в который, кроме того, входят'),
  292. ('ru1', u'а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я'),
  293. ('ru2', u'А Б В Г Д Е Ё Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я'),
  294. ]
  295. print(u'Normal translation\n==================\n')
  296. test_items(texts, LanguageNormal().translate)
  297. print(u'Japnese translation\n====================\n')
  298. test_items(texts, LanguageJapanese().translate)
  299. # run the program
  300. if __name__ == "__main__":
  301. main()