2 # -*- coding: utf-8 -*-
4 from translate
.convert
import dtd2po
5 from translate
.convert
import test_convert
6 from translate
.misc
import wStringIO
7 from translate
.storage
import po
8 from translate
.storage
import dtd
11 def dtd2po(self
, dtdsource
, dtdtemplate
=None):
12 """helper that converts dtd source to po source without requiring files"""
13 inputfile
= wStringIO
.StringIO(dtdsource
)
14 inputdtd
= dtd
.dtdfile(inputfile
)
15 convertor
= dtd2po
.dtd2po()
17 outputpo
= convertor
.convertstore(inputdtd
)
19 templatefile
= wStringIO
.StringIO(dtdtemplate
)
20 templatedtd
= dtd
.dtdfile(templatefile
)
21 outputpo
= convertor
.mergestore(templatedtd
, inputdtd
)
24 def convertdtd(self
, dtdsource
):
25 """call the convertdtd, return the outputfile"""
26 inputfile
= wStringIO
.StringIO(dtdsource
)
27 outputfile
= wStringIO
.StringIO()
29 assert dtd2po
.convertdtd(inputfile
, outputfile
, templatefile
)
30 return outputfile
.getvalue()
32 def singleelement(self
, pofile
):
33 """checks that the pofile contains a single non-header element, and returns it"""
34 assert len(pofile
.units
) == 2
35 assert pofile
.units
[0].isheader()
37 return pofile
.units
[1]
39 def countelements(self
, pofile
):
40 """returns the number of non-header items"""
41 if pofile
.units
[0].isheader():
42 return len(pofile
.units
) - 1
44 return len(pofile
.units
)
46 def test_simpleentity(self
):
47 """checks that a simple dtd entity definition converts properly to a po entry"""
48 dtdsource
= '<!ENTITY test.me "bananas for sale">\n'
49 pofile
= self
.dtd2po(dtdsource
)
50 pounit
= self
.singleelement(pofile
)
51 assert pounit
.source
== "bananas for sale"
52 assert pounit
.target
== ""
53 # Now with a template language
54 dtdtemplate
= '<!ENTITY test.me "bananas for sale">\n'
55 dtdtranslated
= '<!ENTITY test.me "piesangs te koop">\n'
56 pofile
= self
.dtd2po(dtdtranslated
, dtdtemplate
)
57 pounit
= self
.singleelement(pofile
)
58 assert pounit
.source
== "bananas for sale"
59 assert pounit
.target
== "piesangs te koop"
61 def test_convertdtd(self
):
62 """checks that the convertdtd function is working"""
63 dtdsource
= '<!ENTITY saveas.label "Save As...">\n'
64 posource
= self
.convertdtd(dtdsource
)
65 pofile
= po
.pofile(wStringIO
.StringIO(posource
))
66 unit
= self
.singleelement(pofile
)
67 assert unit
.source
== "Save As..."
68 assert unit
.target
== ""
71 """apostrophe should not break a single-quoted entity definition, bug 69"""
72 dtdsource
= "<!ENTITY test.me 'bananas ' for sale'>\n"
73 pofile
= self
.dtd2po(dtdsource
)
74 pounit
= self
.singleelement(pofile
)
75 assert pounit
.source
== "bananas ' for sale"
77 def test_quotes(self
):
78 """quotes should be handled in a single-quoted entity definition"""
79 dtdsource
= """<!ENTITY test.metoo '"Bananas" for sale'>\n"""
80 pofile
= self
.dtd2po(dtdsource
)
81 pounit
= self
.singleelement(pofile
)
83 assert pounit
.source
== '"Bananas" for sale'
85 def test_emptyentity(self
):
86 """checks that empty entity definitions survive into po file, bug 15"""
87 dtdsource
= '<!ENTITY credit.translation "">\n'
88 pofile
= self
.dtd2po(dtdsource
)
89 pounit
= self
.singleelement(pofile
)
90 assert "credit.translation" in str(pounit
)
92 def test_emptyentity_translated(self
):
93 """checks that if we translate an empty entity it makes it into the PO, bug 101"""
94 dtdtemplate
= '<!ENTITY credit.translation "">\n'
95 dtdsource
= '<!ENTITY credit.translation "Translators Names">\n'
96 pofile
= self
.dtd2po(dtdsource
, dtdtemplate
)
97 unit
= self
.singleelement(pofile
)
99 assert "credit.translation" in str(unit
)
100 # We don't want this to simply be seen as a header:
101 assert len(unit
.getid()) != 0
102 assert unit
.target
== "Translators Names"
104 def test_localisaton_note_simple(self
):
105 """test the simple localisation more becomes a #. comment"""
106 dtdsource
= '''<!-- LOCALIZATION NOTE (alwaysCheckDefault.height):
107 There's some sort of bug which makes wrapping checkboxes not properly reflow,
108 causing the bottom border of the groupbox to be cut off; set this
109 appropriately if your localization causes this checkbox to wrap.
111 <!ENTITY alwaysCheckDefault.height "3em">
113 pofile
= self
.dtd2po(dtdsource
)
114 posource
= str(pofile
)
116 assert posource
.count('#.') == 5 # 1 Header extracted from, 3 comment lines, 1 autoinserted comment
118 def test_localisation_note_merge(self
):
119 """test that LOCALIZATION NOTES are added properly as #. comments and disambiguated with msgctxt entries"""
120 dtdtemplate
= '<!--LOCALIZATION NOTE (%s): Some note -->\n' + \
121 '<!ENTITY %s "Source text">\n'
122 dtdsource
= dtdtemplate
% ("note1.label", "note1.label") + dtdtemplate
% ("note2.label", "note2.label")
123 pofile
= self
.dtd2po(dtdsource
)
124 posource
= str(pofile
.units
[1]) + str(pofile
.units
[2])
126 assert posource
.count('#.') == 2
127 assert posource
.count('msgctxt') == 2
129 def test_donttranslate_simple(self
):
130 """check that we handle DONT_TRANSLATE messages properly"""
131 dtdsource
= '''<!-- LOCALIZATION NOTE (region.Altitude): DONT_TRANSLATE -->
132 <!ENTITY region.Altitude "Very High">'''
133 pofile
= self
.dtd2po(dtdsource
)
134 assert self
.countelements(pofile
) == 0
135 dtdsource
= '''<!-- LOCALIZATION NOTE (exampleOpenTag.label): DONT_TRANSLATE: they are text for HTML tagnames: "<i>" and "</i>" -->
136 <!ENTITY exampleOpenTag.label "<i>">'''
137 pofile
= self
.dtd2po(dtdsource
)
138 assert self
.countelements(pofile
) == 0
139 dtdsource
= '''<!-- LOCALIZATION NOTE (imapAdvanced.label): Do not translate "IMAP" -->
140 <!ENTITY imapAdvanced.label "Advanced IMAP Server Settings">'''
141 pofile
= self
.dtd2po(dtdsource
)
142 assert self
.countelements(pofile
) == 1
144 def test_donttranslate_label(self
):
145 """test strangeness when label entity is marked DONT_TRANSLATE and accesskey is not, bug 30"""
146 dtdsource
= '<!--LOCALIZATION NOTE (editorCheck.label): DONT_TRANSLATE -->\n' + \
147 '<!ENTITY editorCheck.label "Composer">\n<!ENTITY editorCheck.accesskey "c">\n'
148 pofile
= self
.dtd2po(dtdsource
)
149 posource
= str(pofile
)
150 # we need to decided what we're going to do here - see the comments in bug 30
151 # this tests the current implementation which is that the DONT_TRANSLATE string is removed, but the other remains
152 assert 'editorCheck.label' not in posource
153 assert 'editorCheck.accesskey' in posource
155 def test_donttranslate_onlyentity(self
):
156 """if the entity is itself just another entity then it shouldn't appear in the output PO file"""
157 dtdsource
= '''<!-- LOCALIZATION NOTE (mainWindow.title): DONT_TRANSLATE -->
158 <!ENTITY mainWindow.title "&brandFullName;">'''
159 pofile
= self
.dtd2po(dtdsource
)
160 assert self
.countelements(pofile
) == 0
162 def test_donttranslate_commentedout(self
):
163 """check that we don't process messages in <!-- comments -->: bug 102"""
164 dtdsource
= '''<!-- commenting out until bug 38906 is fixed
165 <!ENTITY messagesHeader.label "Messages"> -->'''
166 pofile
= self
.dtd2po(dtdsource
)
167 assert self
.countelements(pofile
) == 0
169 def test_spaces_at_start_of_dtd_lines(self
):
170 """test that pretty print spaces at the start of subsequent DTD element lines are removed from the PO file, bug 79"""
171 # Space at the end of the line
172 dtdsource
= '<!ENTITY noupdatesfound.intro "First line then \n' + \
174 pofile
= self
.dtd2po(dtdsource
)
175 pounit
= self
.singleelement(pofile
)
176 # We still need to decide how we handle line line breaks in the DTD entities. It seems that we should actually
177 # drop the line break but this has not been implemented yet.
178 assert pounit
.source
== "First line then \nnext lines."
179 # No space at the end of the line
180 dtdsource
= '<!ENTITY noupdatesfound.intro "First line then\n' + \
182 pofile
= self
.dtd2po(dtdsource
)
183 pounit
= self
.singleelement(pofile
)
184 assert pounit
.source
== "First line then \nnext lines."
186 def test_accesskeys_folding(self
):
187 """test that we fold accesskeys into message strings"""
188 dtdsource_template
= '<!ENTITY fileSaveAs.%s "Save As...">\n<!ENTITY fileSaveAs.%s "S">\n'
189 lang_template
= '<!ENTITY fileSaveAs.%s "Gcina ka...">\n<!ENTITY fileSaveAs.%s "G">\n'
190 for label
in ("label", "title"):
191 for accesskey
in ("accesskey", "accessKey", "akey"):
192 pofile
= self
.dtd2po(dtdsource_template
% (label
, accesskey
))
193 pounit
= self
.singleelement(pofile
)
194 assert pounit
.source
== "&Save As..."
195 # Test with template (bug 155)
196 pofile
= self
.dtd2po(lang_template
% (label
, accesskey
), dtdsource_template
% (label
, accesskey
))
197 pounit
= self
.singleelement(pofile
)
198 assert pounit
.source
== "&Save As..."
199 assert pounit
.target
== "&Gcina ka..."
201 def test_accesskeys_mismatch(self
):
202 """check that we can handle accesskeys that don't match and thus can't be folded into the .label entry"""
203 dtdsource
= '<!ENTITY fileSave.label "Save">\n' + \
204 '<!ENTITY fileSave.accesskey "z">\n'
205 pofile
= self
.dtd2po(dtdsource
)
206 assert self
.countelements(pofile
) == 2
208 def test_carriage_return_in_multiline_dtd(self
):
209 """test that we create nice PO files when we find a \r\n in a multiline DTD element"""
210 dtdsource
= '<!ENTITY noupdatesfound.intro "First line then \r\n' + \
212 pofile
= self
.dtd2po(dtdsource
)
213 unit
= self
.singleelement(pofile
)
214 assert unit
.source
== "First line then \nnext lines."
216 def test_multiline_with_blankline(self
):
217 """test that we can process a multiline entity that has a blank line in it, bug 331"""
219 <!ENTITY multiline.text "
224 pofile
= self
.dtd2po(dtdsource
)
225 unit
= self
.singleelement(pofile
)
226 assert unit
.source
== "Some text \n \nSome other text"
228 def test_mulitline_closing_quotes(self
):
229 """test that we support various styles and spaces after closing quotes on multiline entities"""
231 <!ENTITY pref.plural '<span>opsies</span><span
232 class="noWin">preferences</span>' >
234 pofile
= self
.dtd2po(dtdsource
)
235 unit
= self
.singleelement(pofile
)
236 assert unit
.source
== '<span>opsies</span><span \nclass="noWin">preferences</span>'
238 def test_preserving_spaces(self
):
239 """test that we preserve space that appear at the start of the first line of a DTD entity"""
240 # Space before first character
241 dtdsource
= '<!ENTITY mainWindow.titlemodifiermenuseparator " - ">'
242 pofile
= self
.dtd2po(dtdsource
)
243 unit
= self
.singleelement(pofile
)
244 assert unit
.source
== " - "
245 # Double line and spaces
246 dtdsource
= '<!ENTITY mainWindow.titlemodifiermenuseparator " - with a newline\n and more text">'
247 pofile
= self
.dtd2po(dtdsource
)
248 unit
= self
.singleelement(pofile
)
249 print repr(unit
.source
)
250 assert unit
.source
== " - with a newline \nand more text"
252 def test_escaping_newline_tabs(self
):
253 """test that we handle all kinds of newline permutations"""
254 dtdsource
= '<!ENTITY noupdatesfound.intro "A hard coded newline.\\nAnd tab\\t and a \\r carriage return.">\n'
255 converter
= dtd2po
.dtd2po()
256 thedtd
= dtd
.dtdunit()
257 thedtd
.parse(dtdsource
)
259 converter
.convertstrings(thedtd
, thepo
)
262 # \n in a dtd should also appear as \n in the PO file
263 assert thepo
.source
== r
"A hard coded newline.\nAnd tab\t and a \r carriage return."
265 def test_abandoned_accelerator(self
):
266 """test that when a language DTD has an accelerator but the template DTD does not that we abandon the accelerator"""
267 dtdtemplate
= '<!ENTITY test.label "Test">\n'
268 dtdlanguage
= '<!ENTITY test.label "Toets">\n<!ENTITY test.accesskey "T">\n'
269 pofile
= self
.dtd2po(dtdlanguage
, dtdtemplate
)
270 unit
= self
.singleelement(pofile
)
271 assert unit
.source
== "Test"
272 assert unit
.target
== "Toets"
274 def test_unassociable_accelerator(self
):
275 """test to see that we can handle accelerator keys that cannot be associated correctly"""
276 dtdsource
= '<!ENTITY managecerts.button "Manage Certificates...">\n<!ENTITY managecerts.accesskey "M">'
277 pofile
= self
.dtd2po(dtdsource
)
278 assert pofile
.units
[1].source
== "Manage Certificates..."
279 assert pofile
.units
[2].source
== "M"
280 pofile
= self
.dtd2po(dtdsource
, dtdsource
)
281 assert pofile
.units
[1].target
== "Manage Certificates..."
282 assert pofile
.units
[2].target
== "M"
284 def test_changed_labels_and_accelerators(self
):
285 """test to ensure that when the template changes an entity name we can still manage the accelerators"""
286 dtdtemplate
= '''<!ENTITY managecerts.caption "Manage Certificates">
287 <!ENTITY managecerts.text "Use the Certificate Manager to manage your personal certificates, as well as those of other people and certificate authorities.">
288 <!ENTITY managecerts.button "Manage Certificates...">
289 <!ENTITY managecerts.accesskey "M">'''
290 dtdlanguage
= '''<!ENTITY managecerts.label "ﺇﺩﺍﺭﺓ ﺎﻠﺸﻫﺍﺩﺎﺗ">
291 <!ENTITY managecerts.text "ﺎﺴﺘﺧﺪﻣ ﻡﺪﻳﺭ ﺎﻠﺸﻫﺍﺩﺎﺗ ﻹﺩﺍﺭﺓ ﺶﻫﺍﺩﺎﺘﻛ ﺎﻠﺸﺨﺼﻳﺓ، ﺏﺍﻺﺿﺎﻓﺓ ﻞﺘﻠﻛ ﺎﻠﺧﺎﺻﺓ ﺏﺍﻶﺧﺮﻴﻧ ﻭ ﺲﻠﻃﺎﺗ ﺎﻠﺸﻫﺍﺩﺎﺗ.">
292 <!ENTITY managecerts.button "ﺇﺩﺍﺭﺓ ﺎﻠﺸﻫﺍﺩﺎﺗ...">
293 <!ENTITY managecerts.accesskey "ﺩ">'''
294 pofile
= self
.dtd2po(dtdlanguage
, dtdtemplate
)
296 assert pofile
.units
[3].source
== "Manage Certificates..."
297 assert pofile
.units
[3].target
== u
"ﺇﺩﺍﺭﺓ ﺎﻠﺸﻫﺍﺩﺎﺗ..."
298 assert pofile
.units
[4].source
== "M"
299 assert pofile
.units
[4].target
== u
"ﺩ"
301 def wtest_accelerator_keys_not_in_sentence(self
):
302 """tests to ensure that we can manage accelerator keys that are not part of the transated sentence eg in Chinese"""
303 dtdtemplate
= '''<!ENTITY useAutoScroll.label "Use autoscrolling">
304 <!ENTITY useAutoScroll.accesskey "a">'''
305 dtdlanguage
= '''<!ENTITY useAutoScroll.label "使用自動捲動(Autoscrolling)">
306 <!ENTITY useAutoScroll.accesskey "a">'''
307 pofile
= self
.dtd2po(dtdlanguage
, dtdtemplate
)
309 assert pofile
.units
[1].target
== "使用自動捲動(&Autoscrolling)"
310 # We assume that accesskeys with no associated key should be done as follows "XXXX (&A)"
311 # TODO - check that we can unfold this from PO -> DTD
312 dtdlanguage
= '''<!ENTITY useAutoScroll.label "使用自動捲動">
313 <!ENTITY useAutoScroll.accesskey "a">'''
314 pofile
= self
.dtd2po(dtdlanguage
, dtdtemplate
)
316 assert pofile
.units
[1].target
== "使用自動捲動 (&A)"
318 def test_exclude_entity_includes(self
):
319 """test that we don't turn an include into a translatable string"""
320 dtdsource
= '<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">'
321 pofile
= self
.dtd2po(dtdsource
)
322 assert self
.countelements(pofile
) == 0
324 def test_linewraps(self
):
325 """check that redundant line wraps are removed from the po file"""
326 dtdsource
= '''<!ENTITY generic.longDesc "
329 pofile
= self
.dtd2po(dtdsource
)
330 pounit
= self
.singleelement(pofile
)
331 assert pounit
.source
== "<p>Test me.</p>"
333 def test_merging_with_new_untranslated(self
):
334 """test that when we merge in new untranslated strings with existing translations we manage the encodings properly"""
335 # This should probably be in test_po.py but was easier to do here
336 dtdtemplate
= '''<!ENTITY unreadFolders.label "Unread">\n<!ENTITY viewPickerUnread.label "Unread">\n<!ENTITY unreadColumn.label "Unread">'''
337 dtdlanguage
= '''<!ENTITY viewPickerUnread.label "Непрочетени">\n<!ENTITY unreadFolders.label "Непрочетени">'''
338 pofile
= self
.dtd2po(dtdlanguage
, dtdtemplate
)
340 assert pofile
.units
[1].source
== "Unread"
342 def test_merge_without_template(self
):
343 """test that we we manage the case where we merge and their is no template file"""
344 # If we supply a template file we should fail if the template file does not exist or is blank. We should
345 # not put the translation in as the source.
346 # TODO: this test fails, since line 16 checks for "not dtdtemplate"
347 # instead of checking for "dtdtemplate is None". What is correct?
349 dtdsource
= '<!ENTITY no.template "Target">'
350 pofile
= self
.dtd2po(dtdsource
, dtdtemplate
)
352 assert self
.countelements(pofile
) == 0
354 class TestDTD2POCommand(test_convert
.TestConvertCommand
, TestDTD2PO
):
355 """Tests running actual dtd2po commands on files"""
356 convertmodule
= dtd2po
357 defaultoptions
= {"progress": "none"}
360 """tests getting help"""
361 options
= test_convert
.TestConvertCommand
.test_help(self
)
362 options
= self
.help_check(options
, "-P, --pot")
363 options
= self
.help_check(options
, "-t TEMPLATE, --template=TEMPLATE")
364 options
= self
.help_check(options
, "--duplicates=DUPLICATESTYLE", last
=True)