1from io import BytesIO 2 3from translate.convert import prop2po, test_convert 4from translate.storage import po, properties 5 6 7class TestProp2PO: 8 def prop2po(self, propsource, proptemplate=None, personality="java"): 9 """helper that converts .properties source to po source without requiring files""" 10 inputfile = BytesIO(propsource.encode()) 11 inputprop = properties.propfile(inputfile, personality=personality) 12 convertor = prop2po.prop2po(personality=personality) 13 if proptemplate: 14 templatefile = BytesIO(proptemplate.encode()) 15 templateprop = properties.propfile(templatefile) 16 outputpo = convertor.mergestore(templateprop, inputprop) 17 else: 18 outputpo = convertor.convertstore(inputprop) 19 return outputpo 20 21 def convertprop(self, propsource): 22 """call the convertprop, return the outputfile""" 23 inputfile = BytesIO(propsource.encode()) 24 outputfile = BytesIO() 25 templatefile = None 26 assert prop2po.convertprop(inputfile, outputfile, templatefile) 27 return outputfile.getvalue() 28 29 def singleelement(self, pofile): 30 """checks that the pofile contains a single non-header element, and returns it""" 31 assert len(pofile.units) == 2 32 assert pofile.units[0].isheader() 33 print(pofile) 34 return pofile.units[1] 35 36 def countelements(self, pofile): 37 """counts the number of non-header entries""" 38 assert pofile.units[0].isheader() 39 print(pofile) 40 return len(pofile.units) - 1 41 42 def test_simpleentry(self): 43 """checks that a simple properties entry converts properly to a po entry""" 44 propsource = "SAVEENTRY=Save file\n" 45 pofile = self.prop2po(propsource) 46 pounit = self.singleelement(pofile) 47 assert pounit.source == "Save file" 48 assert pounit.target == "" 49 50 def test_convertprop(self): 51 """checks that the convertprop function is working""" 52 propsource = "SAVEENTRY=Save file\n" 53 posource = self.convertprop(propsource) 54 pofile = po.pofile(BytesIO(posource)) 55 pounit = self.singleelement(pofile) 56 assert pounit.source == "Save file" 57 assert pounit.target == "" 58 59 def test_no_value_entry(self): 60 """checks that a properties entry without value is converted""" 61 propsource = "KEY = \n" 62 pofile = self.prop2po(propsource) 63 pounit = self.singleelement(pofile) 64 assert pounit.getcontext() == "KEY" 65 assert pounit.source == "" 66 assert pounit.target == "" 67 68 def test_no_separator_entry(self): 69 """checks that a properties entry without separator is converted""" 70 propsource = "KEY\n" 71 pofile = self.prop2po(propsource) 72 pounit = self.singleelement(pofile) 73 assert pounit.getcontext() == "KEY" 74 assert pounit.source == "" 75 assert pounit.target == "" 76 77 def test_tab_at_end_of_string(self): 78 """check that we preserve tabs at the end of a string""" 79 propsource = r"TAB_AT_END=This setence has a tab at the end.\t" 80 pofile = self.prop2po(propsource) 81 pounit = self.singleelement(pofile) 82 assert pounit.source == "This setence has a tab at the end.\t" 83 84 propsource = ( 85 r"SPACE_THEN_TAB_AT_END=This setence has a space then tab at the end. \t" 86 ) 87 pofile = self.prop2po(propsource) 88 pounit = self.singleelement(pofile) 89 assert pounit.source == "This setence has a space then tab at the end. \t" 90 91 propsource = r"SPACE_AT_END=This setence will keep its 4 spaces at the end. " 92 pofile = self.prop2po(propsource) 93 pounit = self.singleelement(pofile) 94 assert pounit.source == "This setence will keep its 4 spaces at the end. " 95 96 propsource = ( 97 r"SPACE_AT_END_NO_TRIM=This setence will keep its 4 spaces at the end.\ " 98 ) 99 pofile = self.prop2po(propsource) 100 pounit = self.singleelement(pofile) 101 assert pounit.source == "This setence will keep its 4 spaces at the end. " 102 103 propsource = r"SPACE_AT_END_NO_TRIM2=This setence will keep its 4 spaces at the end.\\ " 104 pofile = self.prop2po(propsource) 105 pounit = self.singleelement(pofile) 106 assert pounit.source == "This setence will keep its 4 spaces at the end.\\ " 107 108 def test_tab_at_start_of_value(self): 109 """check that tabs in a property are ignored where appropriate""" 110 propsource = r"property = value" 111 pofile = self.prop2po(propsource) 112 pounit = self.singleelement(pofile) 113 assert pounit.getlocations()[0] == "property" 114 assert pounit.source == "value" 115 116 def test_unicode(self): 117 """checks that unicode entries convert properly""" 118 unistring = r"Norsk bokm\u00E5l" 119 propsource = "nb = %s\n" % unistring 120 pofile = self.prop2po(propsource) 121 pounit = self.singleelement(pofile) 122 print(repr(pofile.units[0].target)) 123 print(repr(pounit.source)) 124 assert pounit.source == "Norsk bokm\u00E5l" 125 126 def test_multiline_escaping(self): 127 """checks that multiline enties can be parsed""" 128 propsource = r"""5093=Unable to connect to your IMAP server. You may have exceeded the maximum number \ 129of connections to this server. If so, use the Advanced IMAP Server Settings dialog to \ 130reduce the number of cached connections.""" 131 pofile = self.prop2po(propsource) 132 print(repr(pofile.units[1].target)) 133 assert self.countelements(pofile) == 1 134 135 def test_comments(self): 136 """test to ensure that we take comments from .properties and place them in .po""" 137 propsource = """# Comment 138prefPanel-smime=Security""" 139 pofile = self.prop2po(propsource) 140 pounit = self.singleelement(pofile) 141 assert pounit.getnotes("developer") == "# Comment" 142 143 def test_multiline_comments(self): 144 """test to ensure that we handle multiline comments well""" 145 propsource = """# Comment 146# commenty 2 147 148## @name GENERIC_ERROR 149## @loc none 150prefPanel-smime= 151""" 152 pofile = self.prop2po(propsource) 153 print(bytes(pofile)) 154 # header comments: 155 assert b"#. # Comment\n#. # commenty 2" in bytes(pofile) 156 pounit = self.singleelement(pofile) 157 assert pounit.getnotes("developer") == "## @name GENERIC_ERROR\n## @loc none" 158 159 def test_folding_accesskeys(self): 160 """check that we can fold various accesskeys into their associated label (bug #115)""" 161 propsource = r"""cmd_addEngine.label = Add Engines... 162cmd_addEngine.accesskey = A""" 163 pofile = self.prop2po(propsource, personality="mozilla") 164 pounit = self.singleelement(pofile) 165 assert pounit.source == "&Add Engines..." 166 167 def test_dont_translate(self): 168 """check that we know how to ignore don't translate instructions in properties files (bug #116)""" 169 propsource = """# LOCALIZATION NOTE (dont): DONT_TRANSLATE. 170dont=don't translate me 171do=translate me 172""" 173 pofile = self.prop2po(propsource) 174 assert self.countelements(pofile) == 1 175 176 def test_emptyproperty(self): 177 """checks that empty property definitions survive into po file, bug 15""" 178 for delimiter in ["=", ""]: 179 propsource = "# comment\ncredit%s" % delimiter 180 pofile = self.prop2po(propsource) 181 pounit = self.singleelement(pofile) 182 assert pounit.getlocations() == ["credit"] 183 assert pounit.getcontext() == "credit" 184 assert 'msgctxt "credit"' in str(pounit) 185 assert b"#. # comment" in bytes(pofile) 186 assert pounit.source == "" 187 188 def test_emptyproperty_translated(self): 189 """checks that if we translate an empty property it makes it into the PO""" 190 for delimiter in ["=", ""]: 191 proptemplate = "credit%s" % delimiter 192 propsource = "credit=Translators Names" 193 pofile = self.prop2po(propsource, proptemplate) 194 pounit = self.singleelement(pofile) 195 assert pounit.getlocations() == ["credit"] 196 # FIXME we don't seem to get a _: comment but we should 197 # assert pounit.getcontext() == "credit" 198 assert pounit.source == "" 199 assert pounit.target == "Translators Names" 200 201 def test_newlines_in_value(self): 202 """check that we can carry newlines that appear in the property value into the PO""" 203 propsource = """prop=\\nvalue\\n\n""" 204 pofile = self.prop2po(propsource) 205 unit = self.singleelement(pofile) 206 assert unit.source == "\nvalue\n" 207 208 def test_header_comments(self): 209 """check that we can handle comments not directly associated with a property""" 210 propsource = """# Header comment\n\n# Comment\n\nprop=value\n""" 211 pofile = self.prop2po(propsource) 212 unit = self.singleelement(pofile) 213 assert unit.source == "value" 214 assert unit.getnotes("developer") == "# Comment" 215 216 def test_unassociated_comment_order(self): 217 """check that we can handle the order of unassociated comments""" 218 propsource = """# Header comment\n\n# 1st Unassociated comment\n\n# 2nd Connected comment\nprop=value\n""" 219 pofile = self.prop2po(propsource) 220 unit = self.singleelement(pofile) 221 assert unit.source == "value" 222 assert ( 223 unit.getnotes("developer") 224 == "# 1st Unassociated comment\n\n# 2nd Connected comment" 225 ) 226 227 def test_x_header(self): 228 """Test that we correctly create the custom header entries 229 (accelerators, merge criterion). 230 """ 231 propsource = """prop=value\n""" 232 233 outputpo = self.prop2po(propsource, personality="mozilla") 234 assert b"X-Accelerator-Marker" in bytes(outputpo) 235 assert b"X-Merge-On" in bytes(outputpo) 236 237 # Even though the gaia flavour inherrits from mozilla, it should not 238 # get the header 239 outputpo = self.prop2po(propsource, personality="gaia") 240 assert b"X-Accelerator-Marker" not in bytes(outputpo) 241 assert b"X-Merge-On" not in bytes(outputpo) 242 243 def test_gaia_plurals(self): 244 """Test conversion of gaia plural units.""" 245 propsource = """ 246message-multiedit-header={[ plural(n) ]} 247message-multiedit-header[zero]=Edit 248message-multiedit-header[one]={{ n }} selected 249message-multiedit-header[two]={{ n }} selected 250message-multiedit-header[few]={{ n }} selected 251message-multiedit-header[many]={{ n }} selected 252message-multiedit-header[other]={{ n }} selected 253""" 254 outputpo = self.prop2po(propsource, personality="gaia") 255 pounit = outputpo.units[-1] 256 assert pounit.hasplural() 257 assert pounit.getlocations() == ["message-multiedit-header"] 258 259 print(outputpo) 260 zero_unit = outputpo.units[-2] 261 assert not zero_unit.hasplural() 262 assert zero_unit.source == "Edit" 263 264 def test_successive_gaia_plurals(self): 265 """Test conversion of two successive gaia plural units.""" 266 propsource = """ 267message-multiedit-header={[ plural(n) ]} 268message-multiedit-header[zero]=Edit 269message-multiedit-header[one]={{ n }} selected 270message-multiedit-header[two]={{ n }} selected 271message-multiedit-header[few]={{ n }} selected 272message-multiedit-header[many]={{ n }} selected 273message-multiedit-header[other]={{ n }} selected 274 275message-multiedit-header2={[ plural(n) ]} 276message-multiedit-header2[zero]=Edit 2 277message-multiedit-header2[one]={{ n }} selected 2 278message-multiedit-header2[two]={{ n }} selected 2 279message-multiedit-header2[few]={{ n }} selected 2 280message-multiedit-header2[many]={{ n }} selected 2 281message-multiedit-header2[other]={{ n }} selected 2 282""" 283 outputpo = self.prop2po(propsource, personality="gaia") 284 pounit = outputpo.units[-1] 285 assert pounit.hasplural() 286 assert pounit.getlocations() == ["message-multiedit-header2"] 287 288 pounit = outputpo.units[-3] 289 assert pounit.hasplural() 290 assert pounit.getlocations() == ["message-multiedit-header"] 291 292 print(outputpo) 293 zero_unit = outputpo.units[-2] 294 assert not zero_unit.hasplural() 295 assert zero_unit.source == "Edit 2" 296 297 zero_unit = outputpo.units[-4] 298 assert not zero_unit.hasplural() 299 assert zero_unit.source == "Edit" 300 301 def test_duplicate_keys(self): 302 """Check that we correctly handle duplicate keys.""" 303 source = """ 304key=value 305key=value 306""" 307 po_file = self.prop2po(source) 308 assert self.countelements(po_file) == 1 309 po_unit = self.singleelement(po_file) 310 assert po_unit.source == "value" 311 312 source = """ 313key=value 314key=another value 315""" 316 po_file = self.prop2po(source) 317 assert self.countelements(po_file) == 2 318 po_unit = po_file.units[1] 319 assert po_unit.source == "value" 320 assert po_unit.getlocations() == ["key"] 321 po_unit = po_file.units[2] 322 assert po_unit.source == "another value" 323 assert po_unit.getlocations() == ["key"] 324 325 source = """ 326key1=value 327key2=value 328""" 329 po_file = self.prop2po(source) 330 assert self.countelements(po_file) == 2 331 po_unit = po_file.units[1] 332 assert po_unit.source == "value" 333 assert po_unit.getlocations() == ["key1"] 334 po_unit = po_file.units[2] 335 assert po_unit.source == "value" 336 assert po_unit.getlocations() == ["key2"] 337 338 def test_gwt_plurals(self): 339 """Test conversion of gwt plural units.""" 340 propsource = """ 341message-multiedit-header={0,number} selected 342message-multiedit-header[none]=Edit 343message-multiedit-header[one]={0,number} selected 344message-multiedit-header[two]={0,number} selected 345message-multiedit-header[few]={0,number} selected 346message-multiedit-header[many]={0,number} selected 347""" 348 outputpo = self.prop2po(propsource, personality="gwt") 349 pounit = outputpo.units[-1] 350 assert pounit.getlocations() == ["message-multiedit-header"] 351 352 353class TestProp2POCommand(test_convert.TestConvertCommand, TestProp2PO): 354 """Tests running actual prop2po commands on files""" 355 356 convertmodule = prop2po 357 defaultoptions = {"progress": "none"} 358 359 def test_help(self, capsys): 360 """tests getting help""" 361 options = super().test_help(capsys) 362 options = self.help_check(options, "-P, --pot") 363 options = self.help_check(options, "-t TEMPLATE, --template=TEMPLATE") 364 options = self.help_check(options, "--personality=TYPE") 365 options = self.help_check(options, "--encoding=ENCODING") 366 options = self.help_check(options, "--duplicates=DUPLICATESTYLE", last=True) 367