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