1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """Module for handling Qt linguist (.ts) files.
22
23 This will eventually replace the older ts.py which only supports the older
24 format. While converters haven't been updated to use this module, we retain
25 both.
26
27 U{TS file format 4.3<http://doc.trolltech.com/4.3/linguist-ts-file-format.html>},
28 U{4.5<http://doc.trolltech.com/4.5/linguist-ts-file-format.html>},
29 U{Example<http://svn.ez.no/svn/ezcomponents/trunk/Translation/docs/linguist-format.txt>},
30 U{Plurals forms<http://www.koders.com/cpp/fidE7B7E83C54B9036EB7FA0F27BC56BCCFC4B9DF34.aspx#L200>}
31
32 U{Specification of the valid variable entries <http://doc.trolltech.com/4.3/qstring.html#arg>},
33 U{2 <http://doc.trolltech.com/4.3/qstring.html#arg-2>}
34 """
35
36 from translate.storage import base, lisa
37 from translate.storage.placeables import general
38 from translate.misc.multistring import multistring
39 from translate.lang import data
40 from lxml import etree
41
42
43
44 NPLURALS = {
45 'jp': 1,
46 'en': 2,
47 'fr': 2,
48 'lv': 3,
49 'ga': 3,
50 'cs': 3,
51 'sk': 3,
52 'mk': 3,
53 'lt': 3,
54 'ru': 3,
55 'pl': 3,
56 'ro': 3,
57 'sl': 4,
58 'mt': 4,
59 'cy': 5,
60 'ar': 6,
61 }
62
63
97
105 source = property(getsource, lisa.LISAunit.setsource)
106 rich_source = property(base.TranslationUnit._get_rich_source, base.TranslationUnit._set_rich_source)
107
109
110
111
112
113
114
115 self._rich_target = None
116 if self.gettarget() == text:
117 return
118 strings = []
119 if isinstance(text, multistring):
120 strings = text.strings
121 elif isinstance(text, list):
122 strings = text
123 else:
124 strings = [text]
125 targetnode = self._gettargetnode()
126 type = targetnode.get("type")
127 targetnode.clear()
128 if type:
129 targetnode.set("type", type)
130 if self.hasplural() or len(strings) > 1:
131 self.xmlelement.set("numerus", "yes")
132 for string in strings:
133 numerus = etree.SubElement(targetnode, self.namespaced("numerusform"))
134 numerus.text = data.forceunicode(string) or u""
135 else:
136 targetnode.text = data.forceunicode(text) or u""
137
139 targetnode = self._gettargetnode()
140 if targetnode is None:
141 etree.SubElement(self.xmlelement, self.namespaced("translation"))
142 return None
143 if self.hasplural():
144 numerus_nodes = targetnode.findall(self.namespaced("numerusform"))
145 return multistring([node.text or u"" for node in numerus_nodes])
146 else:
147 return data.forceunicode(targetnode.text) or u""
148 target = property(gettarget, settarget)
149 rich_target = property(base.TranslationUnit._get_rich_target, base.TranslationUnit._set_rich_target)
150
152 return self.xmlelement.get("numerus") == "yes"
153
154 - def addnote(self, text, origin=None, position="append"):
155 """Add a note specifically in a "comment" tag"""
156 if isinstance(text, str):
157 text = text.decode("utf-8")
158 current_notes = self.getnotes(origin)
159 self.removenotes(origin)
160 if origin in ["programmer", "developer", "source code"]:
161 note = etree.SubElement(self.xmlelement, self.namespaced("extracomment"))
162 else:
163 note = etree.SubElement(self.xmlelement, self.namespaced("translatorcomment"))
164 if position == "append":
165 note.text = "\n".join(filter(None, [current_notes, text.strip()]))
166 else:
167 note.text = text.strip()
168
170
171 comments = []
172 if origin in ["programmer", "developer", "source code", None]:
173 notenode = self.xmlelement.find(self.namespaced("comment"))
174 if notenode is not None:
175 comments.append(notenode.text)
176 notenode = self.xmlelement.find(self.namespaced("extracomment"))
177 if notenode is not None:
178 comments.append(notenode.text)
179 if origin in ["translator", None]:
180 notenode = self.xmlelement.find(self.namespaced("translatorcomment"))
181 if notenode is not None:
182 comments.append(notenode.text)
183 return '\n'.join(comments)
184
186 """Remove all the translator notes."""
187 if origin in ["programmer", "developer", "source code", None]:
188 note = self.xmlelement.find(self.namespaced("comment"))
189 if not note is None:
190 self.xmlelement.remove(note)
191 note = self.xmlelement.find(self.namespaced("extracomment"))
192 if not note is None:
193 self.xmlelement.remove(note)
194 if origin in ["translator", None]:
195 note = self.xmlelement.find(self.namespaced("translatorcomment"))
196 if not note is None:
197 self.xmlelement.remove(note)
198
200 """Returns the type of this translation."""
201 targetnode = self._gettargetnode()
202 if targetnode is not None:
203 return targetnode.get("type")
204 return None
205
214
216 """States whether this unit needs to be reviewed"""
217 return self._gettype() == "unfinished"
218
220 return self._gettype() == "unfinished"
221
227
229 context_name = self.getcontext()
230
231
232 if context_name is not None:
233 return context_name + self.source
234 else:
235 return self.source
236
238
239
240
241
242
243 return bool(self.getid()) and not self.isobsolete()
244
245 - def getcontext(self):
246 parent = self.xmlelement.getparent()
247 if parent is None:
248 return None
249 context = parent.find("name")
250 if context is None:
251 return None
252 return context.text
253
255 if isinstance(location, str):
256 location = location.decode("utf-8")
257 newlocation = etree.SubElement(self.xmlelement, self.namespaced("location"))
258 try:
259 filename, line = location.split(':', 1)
260 except ValueError:
261 filename = location
262 line = None
263 newlocation.set("filename", filename)
264 if line is not None:
265 newlocation.set("line", line)
266
268 location_tags = self.xmlelement.iterfind(self.namespaced("location"))
269 locations = []
270 for location_tag in location_tags:
271 location = location_tag.get("filename")
272 line = location_tag.get("line")
273 if line is not None:
274 location += ':' + line
275 locations.append(location)
276 return locations
277
278 - def merge(self, otherunit, overwrite=False, comments=True, authoritative=False):
283
285 return self._gettype() == "obsolete"
286
287
289 """Class representing a XLIFF file store."""
290 UnitClass = tsunit
291 Name = _("Qt Linguist Translation File")
292 Mimetypes = ["application/x-linguist"]
293 Extensions = ["ts"]
294 rootNode = "TS"
295
296 bodyNode = "context"
297 XMLskeleton = '''<!DOCTYPE TS>
298 <TS>
299 </TS>
300 '''
301 namespace = ''
302
306
307 - def initbody(self):
308 """Initialises self.body."""
309 self.namespace = self.document.getroot().nsmap.get(None, None)
310 self.header = self.document.getroot()
311 if self._contextname:
312 self.body = self.getcontextnode(self._contextname)
313 else:
314 self.body = self.document.getroot()
315
317 """Get the source language for this .ts file.
318
319 The 'sourcelanguage' attribute was only added to the TS format in
320 Qt v4.5. We return 'en' if there is no sourcelanguage set.
321
322 We don't implement setsourcelanguage as users really shouldn't be
323 altering the source language in .ts files, it should be set correctly
324 by the extraction tools.
325
326 @return: ISO code e.g. af, fr, pt_BR
327 @rtype: String
328 """
329 lang = data.normalize_code(self.header.get('sourcelanguage', "en"))
330 if lang == 'en-us':
331 return 'en'
332 return lang
333
335 """Get the target language for this .ts file.
336
337 @return: ISO code e.g. af, fr, pt_BR
338 @rtype: String
339 """
340 return data.normalize_code(self.header.get('language'))
341
343 """Set the target language for this .ts file to L{targetlanguage}.
344
345 @param targetlanguage: ISO code e.g. af, fr, pt_BR
346 @type targetlanguage: String
347 """
348 if targetlanguage:
349 self.header.set('language', targetlanguage)
350
351 - def _createcontext(self, contextname, comment=None):
352 """Creates a context node with an optional comment"""
353 context = etree.SubElement(self.document.getroot(), self.namespaced(self.bodyNode))
354 name = etree.SubElement(context, self.namespaced("name"))
355 name.text = contextname
356 if comment:
357 comment_node = context.SubElement(context, "comment")
358 comment_node.text = comment
359 return context
360
361 - def _getcontextname(self, contextnode):
362 """Returns the name of the given context node."""
363 return contextnode.find(self.namespaced("name")).text
364
366 """Returns all contextnames in this TS file."""
367 contextnodes = self.document.findall(self.namespaced("context"))
368 contextnames = [self.getcontextname(contextnode) for contextnode in contextnodes]
369 return contextnames
370
371 - def _getcontextnode(self, contextname):
372 """Returns the context node with the given name."""
373 contextnodes = self.document.findall(self.namespaced("context"))
374 for contextnode in contextnodes:
375 if self._getcontextname(contextnode) == contextname:
376 return contextnode
377 return None
378
379 - def addunit(self, unit, new=True, contextname=None, createifmissing=True):
380 """Adds the given unit to the last used body node (current context).
381
382 If the contextname is specified, switch to that context (creating it
383 if allowed by createifmissing)."""
384 if contextname is None:
385 contextname = unit.getcontext()
386
387 if self._contextname != contextname:
388 if not self._switchcontext(contextname, createifmissing):
389 return None
390 super(tsfile, self).addunit(unit, new)
391
392 return unit
393
394 - def _switchcontext(self, contextname, createifmissing=False):
395 """Switch the current context to the one named contextname, optionally
396 creating it if it doesn't exist."""
397 self._contextname = contextname
398 contextnode = self._getcontextnode(contextname)
399 if contextnode is None:
400 if not createifmissing:
401 return False
402 contextnode = self._createcontext(contextname)
403
404 self.body = contextnode
405 if self.body is None:
406 return False
407 return True
408
415
417 """Converts to a string containing the file's XML.
418
419 We have to override this to ensure mimic the Qt convention:
420 - no XML decleration
421 - plain DOCTYPE that lxml seems to ignore
422 """
423
424
425
426
427 output = etree.tostring(self.document, pretty_print=True,
428 xml_declaration=False, encoding='utf-8')
429 if not "<!DOCTYPE TS>" in output[:30]:
430 output = "<!DOCTYPE TS>" + output
431 return output
432