test_gettext.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. import os
  2. import base64
  3. import gettext
  4. import unittest
  5. from test import support
  6. from test.support import os_helper
  7. # TODO:
  8. # - Add new tests, for example for "dgettext"
  9. # - Remove dummy tests, for example testing for single and double quotes
  10. # has no sense, it would have if we were testing a parser (i.e. pygettext)
  11. # - Tests should have only one assert.
  12. GNU_MO_DATA = b'''\
  13. 3hIElQAAAAAJAAAAHAAAAGQAAAAAAAAArAAAAAAAAACsAAAAFQAAAK0AAAAjAAAAwwAAAKEAAADn
  14. AAAAMAAAAIkBAAAHAAAAugEAABYAAADCAQAAHAAAANkBAAALAAAA9gEAAEIBAAACAgAAFgAAAEUD
  15. AAAeAAAAXAMAAKEAAAB7AwAAMgAAAB0EAAAFAAAAUAQAABsAAABWBAAAIQAAAHIEAAAJAAAAlAQA
  16. AABSYXltb25kIEx1eHVyeSBZYWNoLXQAVGhlcmUgaXMgJXMgZmlsZQBUaGVyZSBhcmUgJXMgZmls
  17. ZXMAVGhpcyBtb2R1bGUgcHJvdmlkZXMgaW50ZXJuYXRpb25hbGl6YXRpb24gYW5kIGxvY2FsaXph
  18. dGlvbgpzdXBwb3J0IGZvciB5b3VyIFB5dGhvbiBwcm9ncmFtcyBieSBwcm92aWRpbmcgYW4gaW50
  19. ZXJmYWNlIHRvIHRoZSBHTlUKZ2V0dGV4dCBtZXNzYWdlIGNhdGFsb2cgbGlicmFyeS4AV2l0aCBj
  20. b250ZXh0BFRoZXJlIGlzICVzIGZpbGUAVGhlcmUgYXJlICVzIGZpbGVzAG11bGx1c2sAbXkgY29u
  21. dGV4dARudWRnZSBudWRnZQBteSBvdGhlciBjb250ZXh0BG51ZGdlIG51ZGdlAG51ZGdlIG51ZGdl
  22. AFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDMtMDQtMTEgMTQ6
  23. MzItMDQwMApMYXN0LVRyYW5zbGF0b3I6IEouIERhdmlkIEliYW5leiA8ai1kYXZpZEBub29zLmZy
  24. PgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpNSU1FLVZlcnNpb246
  25. IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4NTktMQpDb250ZW50
  26. LVRyYW5zZmVyLUVuY29kaW5nOiA4Yml0CkdlbmVyYXRlZC1CeTogcHlnZXR0ZXh0LnB5IDEuMQpQ
  27. bHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0d29iYmxlciBNYW5n
  28. cm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFoeXIgY2ViaXZxcmYg
  29. dmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVnIHNiZSBsYmhlIENs
  30. Z3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1ciBUQUgKdHJnZ3Jr
  31. ZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4ASGF5ICVzIGZpY2hlcm8gKGNvbnRleHQpAEhheSAl
  32. cyBmaWNoZXJvcyAoY29udGV4dCkAYmFjb24Ad2luayB3aW5rIChpbiAibXkgY29udGV4dCIpAHdp
  33. bmsgd2luayAoaW4gIm15IG90aGVyIGNvbnRleHQiKQB3aW5rIHdpbmsA
  34. '''
  35. # This data contains an invalid major version number (5)
  36. # An unexpected major version number should be treated as an error when
  37. # parsing a .mo file
  38. GNU_MO_DATA_BAD_MAJOR_VERSION = b'''\
  39. 3hIElQAABQAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
  40. AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
  41. AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
  42. eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
  43. aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
  44. CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
  45. Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
  46. ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
  47. MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
  48. YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
  49. SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
  50. NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
  51. ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
  52. d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
  53. eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
  54. IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
  55. ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
  56. '''
  57. # This data contains an invalid minor version number (7)
  58. # An unexpected minor version number only indicates that some of the file's
  59. # contents may not be able to be read. It does not indicate an error.
  60. GNU_MO_DATA_BAD_MINOR_VERSION = b'''\
  61. 3hIElQcAAAAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
  62. AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
  63. AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
  64. eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
  65. aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
  66. CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
  67. Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
  68. ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
  69. MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
  70. YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
  71. SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
  72. NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
  73. ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
  74. d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
  75. eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
  76. IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
  77. ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
  78. '''
  79. UMO_DATA = b'''\
  80. 3hIElQAAAAADAAAAHAAAADQAAAAAAAAAAAAAAAAAAABMAAAABAAAAE0AAAAQAAAAUgAAAA8BAABj
  81. AAAABAAAAHMBAAAWAAAAeAEAAABhYsOeAG15Y29udGV4dMOeBGFiw54AUHJvamVjdC1JZC1WZXJz
  82. aW9uOiAyLjAKUE8tUmV2aXNpb24tRGF0ZTogMjAwMy0wNC0xMSAxMjo0Mi0wNDAwCkxhc3QtVHJh
  83. bnNsYXRvcjogQmFycnkgQS4gV0Fyc2F3IDxiYXJyeUBweXRob24ub3JnPgpMYW5ndWFnZS1UZWFt
  84. OiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpNSU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5
  85. cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9dXRmLTgKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzog
  86. N2JpdApHZW5lcmF0ZWQtQnk6IG1hbnVhbGx5CgDCpHl6AMKkeXogKGNvbnRleHQgdmVyc2lvbikA
  87. '''
  88. MMO_DATA = b'''\
  89. 3hIElQAAAAABAAAAHAAAACQAAAADAAAALAAAAAAAAAA4AAAAeAEAADkAAAABAAAAAAAAAAAAAAAA
  90. UHJvamVjdC1JZC1WZXJzaW9uOiBObyBQcm9qZWN0IDAuMApQT1QtQ3JlYXRpb24tRGF0ZTogV2Vk
  91. IERlYyAxMSAwNzo0NDoxNSAyMDAyClBPLVJldmlzaW9uLURhdGU6IDIwMDItMDgtMTQgMDE6MTg6
  92. NTgrMDA6MDAKTGFzdC1UcmFuc2xhdG9yOiBKb2huIERvZSA8amRvZUBleGFtcGxlLmNvbT4KSmFu
  93. ZSBGb29iYXIgPGpmb29iYXJAZXhhbXBsZS5jb20+Ckxhbmd1YWdlLVRlYW06IHh4IDx4eEBleGFt
  94. cGxlLmNvbT4KTUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFy
  95. c2V0PWlzby04ODU5LTE1CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IHF1b3RlZC1wcmludGFi
  96. bGUKR2VuZXJhdGVkLUJ5OiBweWdldHRleHQucHkgMS4zCgA=
  97. '''
  98. LOCALEDIR = os.path.join('xx', 'LC_MESSAGES')
  99. MOFILE = os.path.join(LOCALEDIR, 'gettext.mo')
  100. MOFILE_BAD_MAJOR_VERSION = os.path.join(LOCALEDIR, 'gettext_bad_major_version.mo')
  101. MOFILE_BAD_MINOR_VERSION = os.path.join(LOCALEDIR, 'gettext_bad_minor_version.mo')
  102. UMOFILE = os.path.join(LOCALEDIR, 'ugettext.mo')
  103. MMOFILE = os.path.join(LOCALEDIR, 'metadata.mo')
  104. class GettextBaseTest(unittest.TestCase):
  105. def setUp(self):
  106. self.addCleanup(os_helper.rmtree, os.path.split(LOCALEDIR)[0])
  107. if not os.path.isdir(LOCALEDIR):
  108. os.makedirs(LOCALEDIR)
  109. with open(MOFILE, 'wb') as fp:
  110. fp.write(base64.decodebytes(GNU_MO_DATA))
  111. with open(MOFILE_BAD_MAJOR_VERSION, 'wb') as fp:
  112. fp.write(base64.decodebytes(GNU_MO_DATA_BAD_MAJOR_VERSION))
  113. with open(MOFILE_BAD_MINOR_VERSION, 'wb') as fp:
  114. fp.write(base64.decodebytes(GNU_MO_DATA_BAD_MINOR_VERSION))
  115. with open(UMOFILE, 'wb') as fp:
  116. fp.write(base64.decodebytes(UMO_DATA))
  117. with open(MMOFILE, 'wb') as fp:
  118. fp.write(base64.decodebytes(MMO_DATA))
  119. self.env = self.enterContext(os_helper.EnvironmentVarGuard())
  120. self.env['LANGUAGE'] = 'xx'
  121. gettext._translations.clear()
  122. GNU_MO_DATA_ISSUE_17898 = b'''\
  123. 3hIElQAAAAABAAAAHAAAACQAAAAAAAAAAAAAAAAAAAAsAAAAggAAAC0AAAAAUGx1cmFsLUZvcm1z
  124. OiBucGx1cmFscz0yOyBwbHVyYWw9KG4gIT0gMSk7CiMtIy0jLSMtIyAgbWVzc2FnZXMucG8gKEVk
  125. WCBTdHVkaW8pICAjLSMtIy0jLSMKQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFyc2V0PVVU
  126. Ri04CgA=
  127. '''
  128. class GettextTestCase1(GettextBaseTest):
  129. def setUp(self):
  130. GettextBaseTest.setUp(self)
  131. self.localedir = os.curdir
  132. self.mofile = MOFILE
  133. gettext.install('gettext', self.localedir, names=['pgettext'])
  134. def test_some_translations(self):
  135. eq = self.assertEqual
  136. # test some translations
  137. eq(_('albatross'), 'albatross')
  138. eq(_('mullusk'), 'bacon')
  139. eq(_(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
  140. eq(_(r'nudge nudge'), 'wink wink')
  141. def test_some_translations_with_context(self):
  142. eq = self.assertEqual
  143. eq(pgettext('my context', 'nudge nudge'),
  144. 'wink wink (in "my context")')
  145. eq(pgettext('my other context', 'nudge nudge'),
  146. 'wink wink (in "my other context")')
  147. def test_double_quotes(self):
  148. eq = self.assertEqual
  149. # double quotes
  150. eq(_("albatross"), 'albatross')
  151. eq(_("mullusk"), 'bacon')
  152. eq(_(r"Raymond Luxury Yach-t"), 'Throatwobbler Mangrove')
  153. eq(_(r"nudge nudge"), 'wink wink')
  154. def test_triple_single_quotes(self):
  155. eq = self.assertEqual
  156. # triple single quotes
  157. eq(_('''albatross'''), 'albatross')
  158. eq(_('''mullusk'''), 'bacon')
  159. eq(_(r'''Raymond Luxury Yach-t'''), 'Throatwobbler Mangrove')
  160. eq(_(r'''nudge nudge'''), 'wink wink')
  161. def test_triple_double_quotes(self):
  162. eq = self.assertEqual
  163. # triple double quotes
  164. eq(_("""albatross"""), 'albatross')
  165. eq(_("""mullusk"""), 'bacon')
  166. eq(_(r"""Raymond Luxury Yach-t"""), 'Throatwobbler Mangrove')
  167. eq(_(r"""nudge nudge"""), 'wink wink')
  168. def test_multiline_strings(self):
  169. eq = self.assertEqual
  170. # multiline strings
  171. eq(_('''This module provides internationalization and localization
  172. support for your Python programs by providing an interface to the GNU
  173. gettext message catalog library.'''),
  174. '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
  175. fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
  176. trggrkg zrffntr pngnybt yvoenel.''')
  177. def test_the_alternative_interface(self):
  178. eq = self.assertEqual
  179. neq = self.assertNotEqual
  180. # test the alternative interface
  181. with open(self.mofile, 'rb') as fp:
  182. t = gettext.GNUTranslations(fp)
  183. # Install the translation object
  184. t.install()
  185. eq(_('nudge nudge'), 'wink wink')
  186. # Try unicode return type
  187. t.install()
  188. eq(_('mullusk'), 'bacon')
  189. # Test installation of other methods
  190. import builtins
  191. t.install(names=["gettext", "ngettext"])
  192. eq(_, t.gettext)
  193. eq(builtins.gettext, t.gettext)
  194. eq(ngettext, t.ngettext)
  195. neq(pgettext, t.pgettext)
  196. del builtins.gettext
  197. del builtins.ngettext
  198. class GettextTestCase2(GettextBaseTest):
  199. def setUp(self):
  200. GettextBaseTest.setUp(self)
  201. self.localedir = os.curdir
  202. # Set up the bindings
  203. gettext.bindtextdomain('gettext', self.localedir)
  204. gettext.textdomain('gettext')
  205. # For convenience
  206. self._ = gettext.gettext
  207. def test_bindtextdomain(self):
  208. self.assertEqual(gettext.bindtextdomain('gettext'), self.localedir)
  209. def test_textdomain(self):
  210. self.assertEqual(gettext.textdomain(), 'gettext')
  211. def test_bad_major_version(self):
  212. with open(MOFILE_BAD_MAJOR_VERSION, 'rb') as fp:
  213. with self.assertRaises(OSError) as cm:
  214. gettext.GNUTranslations(fp)
  215. exception = cm.exception
  216. self.assertEqual(exception.errno, 0)
  217. self.assertEqual(exception.strerror, "Bad version number 5")
  218. self.assertEqual(exception.filename, MOFILE_BAD_MAJOR_VERSION)
  219. def test_bad_minor_version(self):
  220. with open(MOFILE_BAD_MINOR_VERSION, 'rb') as fp:
  221. # Check that no error is thrown with a bad minor version number
  222. gettext.GNUTranslations(fp)
  223. def test_some_translations(self):
  224. eq = self.assertEqual
  225. # test some translations
  226. eq(self._('albatross'), 'albatross')
  227. eq(self._('mullusk'), 'bacon')
  228. eq(self._(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
  229. eq(self._(r'nudge nudge'), 'wink wink')
  230. def test_some_translations_with_context(self):
  231. eq = self.assertEqual
  232. eq(gettext.pgettext('my context', 'nudge nudge'),
  233. 'wink wink (in "my context")')
  234. eq(gettext.pgettext('my other context', 'nudge nudge'),
  235. 'wink wink (in "my other context")')
  236. def test_some_translations_with_context_and_domain(self):
  237. eq = self.assertEqual
  238. eq(gettext.dpgettext('gettext', 'my context', 'nudge nudge'),
  239. 'wink wink (in "my context")')
  240. eq(gettext.dpgettext('gettext', 'my other context', 'nudge nudge'),
  241. 'wink wink (in "my other context")')
  242. def test_double_quotes(self):
  243. eq = self.assertEqual
  244. # double quotes
  245. eq(self._("albatross"), 'albatross')
  246. eq(self._("mullusk"), 'bacon')
  247. eq(self._(r"Raymond Luxury Yach-t"), 'Throatwobbler Mangrove')
  248. eq(self._(r"nudge nudge"), 'wink wink')
  249. def test_triple_single_quotes(self):
  250. eq = self.assertEqual
  251. # triple single quotes
  252. eq(self._('''albatross'''), 'albatross')
  253. eq(self._('''mullusk'''), 'bacon')
  254. eq(self._(r'''Raymond Luxury Yach-t'''), 'Throatwobbler Mangrove')
  255. eq(self._(r'''nudge nudge'''), 'wink wink')
  256. def test_triple_double_quotes(self):
  257. eq = self.assertEqual
  258. # triple double quotes
  259. eq(self._("""albatross"""), 'albatross')
  260. eq(self._("""mullusk"""), 'bacon')
  261. eq(self._(r"""Raymond Luxury Yach-t"""), 'Throatwobbler Mangrove')
  262. eq(self._(r"""nudge nudge"""), 'wink wink')
  263. def test_multiline_strings(self):
  264. eq = self.assertEqual
  265. # multiline strings
  266. eq(self._('''This module provides internationalization and localization
  267. support for your Python programs by providing an interface to the GNU
  268. gettext message catalog library.'''),
  269. '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
  270. fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
  271. trggrkg zrffntr pngnybt yvoenel.''')
  272. class PluralFormsTestCase(GettextBaseTest):
  273. def setUp(self):
  274. GettextBaseTest.setUp(self)
  275. self.mofile = MOFILE
  276. def test_plural_forms1(self):
  277. eq = self.assertEqual
  278. x = gettext.ngettext('There is %s file', 'There are %s files', 1)
  279. eq(x, 'Hay %s fichero')
  280. x = gettext.ngettext('There is %s file', 'There are %s files', 2)
  281. eq(x, 'Hay %s ficheros')
  282. def test_plural_context_forms1(self):
  283. eq = self.assertEqual
  284. x = gettext.npgettext('With context',
  285. 'There is %s file', 'There are %s files', 1)
  286. eq(x, 'Hay %s fichero (context)')
  287. x = gettext.npgettext('With context',
  288. 'There is %s file', 'There are %s files', 2)
  289. eq(x, 'Hay %s ficheros (context)')
  290. def test_plural_forms2(self):
  291. eq = self.assertEqual
  292. with open(self.mofile, 'rb') as fp:
  293. t = gettext.GNUTranslations(fp)
  294. x = t.ngettext('There is %s file', 'There are %s files', 1)
  295. eq(x, 'Hay %s fichero')
  296. x = t.ngettext('There is %s file', 'There are %s files', 2)
  297. eq(x, 'Hay %s ficheros')
  298. def test_plural_context_forms2(self):
  299. eq = self.assertEqual
  300. with open(self.mofile, 'rb') as fp:
  301. t = gettext.GNUTranslations(fp)
  302. x = t.npgettext('With context',
  303. 'There is %s file', 'There are %s files', 1)
  304. eq(x, 'Hay %s fichero (context)')
  305. x = t.npgettext('With context',
  306. 'There is %s file', 'There are %s files', 2)
  307. eq(x, 'Hay %s ficheros (context)')
  308. # Examples from http://www.gnu.org/software/gettext/manual/gettext.html
  309. def test_ja(self):
  310. eq = self.assertEqual
  311. f = gettext.c2py('0')
  312. s = ''.join([ str(f(x)) for x in range(200) ])
  313. eq(s, "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
  314. def test_de(self):
  315. eq = self.assertEqual
  316. f = gettext.c2py('n != 1')
  317. s = ''.join([ str(f(x)) for x in range(200) ])
  318. eq(s, "10111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
  319. def test_fr(self):
  320. eq = self.assertEqual
  321. f = gettext.c2py('n>1')
  322. s = ''.join([ str(f(x)) for x in range(200) ])
  323. eq(s, "00111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
  324. def test_lv(self):
  325. eq = self.assertEqual
  326. f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2')
  327. s = ''.join([ str(f(x)) for x in range(200) ])
  328. eq(s, "20111111111111111111101111111110111111111011111111101111111110111111111011111111101111111110111111111011111111111111111110111111111011111111101111111110111111111011111111101111111110111111111011111111")
  329. def test_gd(self):
  330. eq = self.assertEqual
  331. f = gettext.c2py('n==1 ? 0 : n==2 ? 1 : 2')
  332. s = ''.join([ str(f(x)) for x in range(200) ])
  333. eq(s, "20122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
  334. def test_gd2(self):
  335. eq = self.assertEqual
  336. # Tests the combination of parentheses and "?:"
  337. f = gettext.c2py('n==1 ? 0 : (n==2 ? 1 : 2)')
  338. s = ''.join([ str(f(x)) for x in range(200) ])
  339. eq(s, "20122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
  340. def test_ro(self):
  341. eq = self.assertEqual
  342. f = gettext.c2py('n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2')
  343. s = ''.join([ str(f(x)) for x in range(200) ])
  344. eq(s, "10111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222111111111111111111122222222222222222222222222222222222222222222222222222222222222222222222222222222")
  345. def test_lt(self):
  346. eq = self.assertEqual
  347. f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2')
  348. s = ''.join([ str(f(x)) for x in range(200) ])
  349. eq(s, "20111111112222222222201111111120111111112011111111201111111120111111112011111111201111111120111111112011111111222222222220111111112011111111201111111120111111112011111111201111111120111111112011111111")
  350. def test_ru(self):
  351. eq = self.assertEqual
  352. f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2')
  353. s = ''.join([ str(f(x)) for x in range(200) ])
  354. eq(s, "20111222222222222222201112222220111222222011122222201112222220111222222011122222201112222220111222222011122222222222222220111222222011122222201112222220111222222011122222201112222220111222222011122222")
  355. def test_cs(self):
  356. eq = self.assertEqual
  357. f = gettext.c2py('(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2')
  358. s = ''.join([ str(f(x)) for x in range(200) ])
  359. eq(s, "20111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
  360. def test_pl(self):
  361. eq = self.assertEqual
  362. f = gettext.c2py('n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2')
  363. s = ''.join([ str(f(x)) for x in range(200) ])
  364. eq(s, "20111222222222222222221112222222111222222211122222221112222222111222222211122222221112222222111222222211122222222222222222111222222211122222221112222222111222222211122222221112222222111222222211122222")
  365. def test_sl(self):
  366. eq = self.assertEqual
  367. f = gettext.c2py('n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3')
  368. s = ''.join([ str(f(x)) for x in range(200) ])
  369. eq(s, "30122333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333012233333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333")
  370. def test_ar(self):
  371. eq = self.assertEqual
  372. f = gettext.c2py('n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5')
  373. s = ''.join([ str(f(x)) for x in range(200) ])
  374. eq(s, "01233333333444444444444444444444444444444444444444444444444444444444444444444444444444444444444444445553333333344444444444444444444444444444444444444444444444444444444444444444444444444444444444444444")
  375. def test_security(self):
  376. raises = self.assertRaises
  377. # Test for a dangerous expression
  378. raises(ValueError, gettext.c2py, "os.chmod('/etc/passwd',0777)")
  379. # issue28563
  380. raises(ValueError, gettext.c2py, '"(eval(foo) && ""')
  381. raises(ValueError, gettext.c2py, 'f"{os.system(\'sh\')}"')
  382. # Maximum recursion depth exceeded during compilation
  383. raises(ValueError, gettext.c2py, 'n+'*10000 + 'n')
  384. self.assertEqual(gettext.c2py('n+'*100 + 'n')(1), 101)
  385. # MemoryError during compilation
  386. raises(ValueError, gettext.c2py, '('*100 + 'n' + ')'*100)
  387. # Maximum recursion depth exceeded in C to Python translator
  388. raises(ValueError, gettext.c2py, '('*10000 + 'n' + ')'*10000)
  389. self.assertEqual(gettext.c2py('('*20 + 'n' + ')'*20)(1), 1)
  390. def test_chained_comparison(self):
  391. # C doesn't chain comparison as Python so 2 == 2 == 2 gets different results
  392. f = gettext.c2py('n == n == n')
  393. self.assertEqual(''.join(str(f(x)) for x in range(3)), '010')
  394. f = gettext.c2py('1 < n == n')
  395. self.assertEqual(''.join(str(f(x)) for x in range(3)), '100')
  396. f = gettext.c2py('n == n < 2')
  397. self.assertEqual(''.join(str(f(x)) for x in range(3)), '010')
  398. f = gettext.c2py('0 < n < 2')
  399. self.assertEqual(''.join(str(f(x)) for x in range(3)), '111')
  400. def test_decimal_number(self):
  401. self.assertEqual(gettext.c2py('0123')(1), 123)
  402. def test_invalid_syntax(self):
  403. invalid_expressions = [
  404. 'x>1', '(n>1', 'n>1)', '42**42**42', '0xa', '1.0', '1e2',
  405. 'n>0x1', '+n', '-n', 'n()', 'n(1)', '1+', 'nn', 'n n',
  406. ]
  407. for expr in invalid_expressions:
  408. with self.assertRaises(ValueError):
  409. gettext.c2py(expr)
  410. def test_nested_condition_operator(self):
  411. self.assertEqual(gettext.c2py('n?1?2:3:4')(0), 4)
  412. self.assertEqual(gettext.c2py('n?1?2:3:4')(1), 2)
  413. self.assertEqual(gettext.c2py('n?1:3?4:5')(0), 4)
  414. self.assertEqual(gettext.c2py('n?1:3?4:5')(1), 1)
  415. def test_division(self):
  416. f = gettext.c2py('2/n*3')
  417. self.assertEqual(f(1), 6)
  418. self.assertEqual(f(2), 3)
  419. self.assertEqual(f(3), 0)
  420. self.assertEqual(f(-1), -6)
  421. self.assertRaises(ZeroDivisionError, f, 0)
  422. def test_plural_number(self):
  423. f = gettext.c2py('n != 1')
  424. self.assertEqual(f(1), 0)
  425. self.assertEqual(f(2), 1)
  426. with self.assertWarns(DeprecationWarning):
  427. self.assertEqual(f(1.0), 0)
  428. with self.assertWarns(DeprecationWarning):
  429. self.assertEqual(f(2.0), 1)
  430. with self.assertWarns(DeprecationWarning):
  431. self.assertEqual(f(1.1), 1)
  432. self.assertRaises(TypeError, f, '2')
  433. self.assertRaises(TypeError, f, b'2')
  434. self.assertRaises(TypeError, f, [])
  435. self.assertRaises(TypeError, f, object())
  436. class GNUTranslationParsingTest(GettextBaseTest):
  437. def test_plural_form_error_issue17898(self):
  438. with open(MOFILE, 'wb') as fp:
  439. fp.write(base64.decodebytes(GNU_MO_DATA_ISSUE_17898))
  440. with open(MOFILE, 'rb') as fp:
  441. # If this runs cleanly, the bug is fixed.
  442. t = gettext.GNUTranslations(fp)
  443. def test_ignore_comments_in_headers_issue36239(self):
  444. """Checks that comments like:
  445. #-#-#-#-# messages.po (EdX Studio) #-#-#-#-#
  446. are ignored.
  447. """
  448. with open(MOFILE, 'wb') as fp:
  449. fp.write(base64.decodebytes(GNU_MO_DATA_ISSUE_17898))
  450. with open(MOFILE, 'rb') as fp:
  451. t = gettext.GNUTranslations(fp)
  452. self.assertEqual(t.info()["plural-forms"], "nplurals=2; plural=(n != 1);")
  453. class UnicodeTranslationsTest(GettextBaseTest):
  454. def setUp(self):
  455. GettextBaseTest.setUp(self)
  456. with open(UMOFILE, 'rb') as fp:
  457. self.t = gettext.GNUTranslations(fp)
  458. self._ = self.t.gettext
  459. self.pgettext = self.t.pgettext
  460. def test_unicode_msgid(self):
  461. self.assertIsInstance(self._(''), str)
  462. def test_unicode_msgstr(self):
  463. self.assertEqual(self._('ab\xde'), '\xa4yz')
  464. def test_unicode_context_msgstr(self):
  465. t = self.pgettext('mycontext\xde', 'ab\xde')
  466. self.assertTrue(isinstance(t, str))
  467. self.assertEqual(t, '\xa4yz (context version)')
  468. class UnicodeTranslationsPluralTest(GettextBaseTest):
  469. def setUp(self):
  470. GettextBaseTest.setUp(self)
  471. with open(MOFILE, 'rb') as fp:
  472. self.t = gettext.GNUTranslations(fp)
  473. self.ngettext = self.t.ngettext
  474. self.npgettext = self.t.npgettext
  475. def test_unicode_msgid(self):
  476. unless = self.assertTrue
  477. unless(isinstance(self.ngettext('', '', 1), str))
  478. unless(isinstance(self.ngettext('', '', 2), str))
  479. def test_unicode_context_msgid(self):
  480. unless = self.assertTrue
  481. unless(isinstance(self.npgettext('', '', '', 1), str))
  482. unless(isinstance(self.npgettext('', '', '', 2), str))
  483. def test_unicode_msgstr(self):
  484. eq = self.assertEqual
  485. unless = self.assertTrue
  486. t = self.ngettext("There is %s file", "There are %s files", 1)
  487. unless(isinstance(t, str))
  488. eq(t, "Hay %s fichero")
  489. unless(isinstance(t, str))
  490. t = self.ngettext("There is %s file", "There are %s files", 5)
  491. unless(isinstance(t, str))
  492. eq(t, "Hay %s ficheros")
  493. def test_unicode_msgstr_with_context(self):
  494. eq = self.assertEqual
  495. unless = self.assertTrue
  496. t = self.npgettext("With context",
  497. "There is %s file", "There are %s files", 1)
  498. unless(isinstance(t, str))
  499. eq(t, "Hay %s fichero (context)")
  500. t = self.npgettext("With context",
  501. "There is %s file", "There are %s files", 5)
  502. unless(isinstance(t, str))
  503. eq(t, "Hay %s ficheros (context)")
  504. class WeirdMetadataTest(GettextBaseTest):
  505. def setUp(self):
  506. GettextBaseTest.setUp(self)
  507. with open(MMOFILE, 'rb') as fp:
  508. try:
  509. self.t = gettext.GNUTranslations(fp)
  510. except:
  511. self.tearDown()
  512. raise
  513. def test_weird_metadata(self):
  514. info = self.t.info()
  515. self.assertEqual(len(info), 9)
  516. self.assertEqual(info['last-translator'],
  517. 'John Doe <jdoe@example.com>\nJane Foobar <jfoobar@example.com>')
  518. class DummyGNUTranslations(gettext.GNUTranslations):
  519. def foo(self):
  520. return 'foo'
  521. class GettextCacheTestCase(GettextBaseTest):
  522. def test_cache(self):
  523. self.localedir = os.curdir
  524. self.mofile = MOFILE
  525. self.assertEqual(len(gettext._translations), 0)
  526. t = gettext.translation('gettext', self.localedir)
  527. self.assertEqual(len(gettext._translations), 1)
  528. t = gettext.translation('gettext', self.localedir,
  529. class_=DummyGNUTranslations)
  530. self.assertEqual(len(gettext._translations), 2)
  531. self.assertEqual(t.__class__, DummyGNUTranslations)
  532. # Calling it again doesn't add to the cache
  533. t = gettext.translation('gettext', self.localedir,
  534. class_=DummyGNUTranslations)
  535. self.assertEqual(len(gettext._translations), 2)
  536. self.assertEqual(t.__class__, DummyGNUTranslations)
  537. class MiscTestCase(unittest.TestCase):
  538. def test__all__(self):
  539. support.check__all__(self, gettext,
  540. not_exported={'c2py', 'ENOENT'})
  541. if __name__ == '__main__':
  542. unittest.main()
  543. # For reference, here's the .po file used to created the GNU_MO_DATA above.
  544. #
  545. # The original version was automatically generated from the sources with
  546. # pygettext. Later it was manually modified to add plural forms support.
  547. b'''
  548. # Dummy translation for the Python test_gettext.py module.
  549. # Copyright (C) 2001 Python Software Foundation
  550. # Barry Warsaw <barry@python.org>, 2000.
  551. #
  552. msgid ""
  553. msgstr ""
  554. "Project-Id-Version: 2.0\n"
  555. "PO-Revision-Date: 2003-04-11 14:32-0400\n"
  556. "Last-Translator: J. David Ibanez <j-david@noos.fr>\n"
  557. "Language-Team: XX <python-dev@python.org>\n"
  558. "MIME-Version: 1.0\n"
  559. "Content-Type: text/plain; charset=iso-8859-1\n"
  560. "Content-Transfer-Encoding: 8bit\n"
  561. "Generated-By: pygettext.py 1.1\n"
  562. "Plural-Forms: nplurals=2; plural=n!=1;\n"
  563. #: test_gettext.py:19 test_gettext.py:25 test_gettext.py:31 test_gettext.py:37
  564. #: test_gettext.py:51 test_gettext.py:80 test_gettext.py:86 test_gettext.py:92
  565. #: test_gettext.py:98
  566. msgid "nudge nudge"
  567. msgstr "wink wink"
  568. msgctxt "my context"
  569. msgid "nudge nudge"
  570. msgstr "wink wink (in \"my context\")"
  571. msgctxt "my other context"
  572. msgid "nudge nudge"
  573. msgstr "wink wink (in \"my other context\")"
  574. #: test_gettext.py:16 test_gettext.py:22 test_gettext.py:28 test_gettext.py:34
  575. #: test_gettext.py:77 test_gettext.py:83 test_gettext.py:89 test_gettext.py:95
  576. msgid "albatross"
  577. msgstr ""
  578. #: test_gettext.py:18 test_gettext.py:24 test_gettext.py:30 test_gettext.py:36
  579. #: test_gettext.py:79 test_gettext.py:85 test_gettext.py:91 test_gettext.py:97
  580. msgid "Raymond Luxury Yach-t"
  581. msgstr "Throatwobbler Mangrove"
  582. #: test_gettext.py:17 test_gettext.py:23 test_gettext.py:29 test_gettext.py:35
  583. #: test_gettext.py:56 test_gettext.py:78 test_gettext.py:84 test_gettext.py:90
  584. #: test_gettext.py:96
  585. msgid "mullusk"
  586. msgstr "bacon"
  587. #: test_gettext.py:40 test_gettext.py:101
  588. msgid ""
  589. "This module provides internationalization and localization\n"
  590. "support for your Python programs by providing an interface to the GNU\n"
  591. "gettext message catalog library."
  592. msgstr ""
  593. "Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba\n"
  594. "fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH\n"
  595. "trggrkg zrffntr pngnybt yvoenel."
  596. # Manually added, as neither pygettext nor xgettext support plural forms
  597. # in Python.
  598. msgid "There is %s file"
  599. msgid_plural "There are %s files"
  600. msgstr[0] "Hay %s fichero"
  601. msgstr[1] "Hay %s ficheros"
  602. # Manually added, as neither pygettext nor xgettext support plural forms
  603. # and context in Python.
  604. msgctxt "With context"
  605. msgid "There is %s file"
  606. msgid_plural "There are %s files"
  607. msgstr[0] "Hay %s fichero (context)"
  608. msgstr[1] "Hay %s ficheros (context)"
  609. '''
  610. # Here's the second example po file example, used to generate the UMO_DATA
  611. # containing utf-8 encoded Unicode strings
  612. b'''
  613. # Dummy translation for the Python test_gettext.py module.
  614. # Copyright (C) 2001 Python Software Foundation
  615. # Barry Warsaw <barry@python.org>, 2000.
  616. #
  617. msgid ""
  618. msgstr ""
  619. "Project-Id-Version: 2.0\n"
  620. "PO-Revision-Date: 2003-04-11 12:42-0400\n"
  621. "Last-Translator: Barry A. WArsaw <barry@python.org>\n"
  622. "Language-Team: XX <python-dev@python.org>\n"
  623. "MIME-Version: 1.0\n"
  624. "Content-Type: text/plain; charset=utf-8\n"
  625. "Content-Transfer-Encoding: 7bit\n"
  626. "Generated-By: manually\n"
  627. #: nofile:0
  628. msgid "ab\xc3\x9e"
  629. msgstr "\xc2\xa4yz"
  630. #: nofile:1
  631. msgctxt "mycontext\xc3\x9e"
  632. msgid "ab\xc3\x9e"
  633. msgstr "\xc2\xa4yz (context version)"
  634. '''
  635. # Here's the third example po file, used to generate MMO_DATA
  636. b'''
  637. msgid ""
  638. msgstr ""
  639. "Project-Id-Version: No Project 0.0\n"
  640. "POT-Creation-Date: Wed Dec 11 07:44:15 2002\n"
  641. "PO-Revision-Date: 2002-08-14 01:18:58+00:00\n"
  642. "Last-Translator: John Doe <jdoe@example.com>\n"
  643. "Jane Foobar <jfoobar@example.com>\n"
  644. "Language-Team: xx <xx@example.com>\n"
  645. "MIME-Version: 1.0\n"
  646. "Content-Type: text/plain; charset=iso-8859-15\n"
  647. "Content-Transfer-Encoding: quoted-printable\n"
  648. "Generated-By: pygettext.py 1.3\n"
  649. '''
  650. #
  651. # messages.po, used for bug 17898
  652. #
  653. b'''
  654. # test file for http://bugs.python.org/issue17898
  655. msgid ""
  656. msgstr ""
  657. "Plural-Forms: nplurals=2; plural=(n != 1);\n"
  658. "#-#-#-#-# messages.po (EdX Studio) #-#-#-#-#\n"
  659. "Content-Type: text/plain; charset=UTF-8\n"
  660. '''