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 lxml import etree
37
38 from translate.lang import data
39 from translate.misc.multistring import multistring
40 from translate.storage import base, lisa
41 from translate.storage.placeables import general
42
43
44
45 NPLURALS = {
46 'jp': 1,
47 'en': 2,
48 'fr': 2,
49 'lv': 3,
50 'ga': 3,
51 'cs': 3,
52 'sk': 3,
53 'mk': 3,
54 'lt': 3,
55 'ru': 3,
56 'pl': 3,
57 'ro': 3,
58 'sl': 4,
59 'mt': 4,
60 'cy': 5,
61 'ar': 6,
62 }
63
64
99
107 source = property(getsource, lisa.LISAunit.setsource)
108 rich_source = property(base.TranslationUnit._get_rich_source, base.TranslationUnit._set_rich_source)
109
111
112
113
114
115
116
117 self._rich_target = None
118 if self.gettarget() == text:
119 return
120 strings = []
121 if isinstance(text, multistring):
122 strings = text.strings
123 elif isinstance(text, list):
124 strings = text
125 else:
126 strings = [text]
127 targetnode = self._gettargetnode()
128 type = targetnode.get("type")
129 targetnode.clear()
130 if type:
131 targetnode.set("type", type)
132 if self.hasplural() or len(strings) > 1:
133 self.xmlelement.set("numerus", "yes")
134 for string in strings:
135 numerus = etree.SubElement(targetnode, self.namespaced("numerusform"))
136 numerus.text = data.forceunicode(string) or u""
137
138 numerus.tail = u"\n "
139 else:
140 targetnode.text = data.forceunicode(text) or u""
141 targetnode.tail = u"\n "
142
144 targetnode = self._gettargetnode()
145 if targetnode is None:
146 etree.SubElement(self.xmlelement, self.namespaced("translation"))
147 return None
148 if self.hasplural():
149 numerus_nodes = targetnode.findall(self.namespaced("numerusform"))
150 return multistring([node.text or u"" for node in numerus_nodes])
151 else:
152 return data.forceunicode(targetnode.text) or u""
153 target = property(gettarget, settarget)
154 rich_target = property(base.TranslationUnit._get_rich_target, base.TranslationUnit._set_rich_target)
155
157 return self.xmlelement.get("numerus") == "yes"
158
159 - def addnote(self, text, origin=None, position="append"):
160 """Add a note specifically in a "comment" tag"""
161 if isinstance(text, str):
162 text = text.decode("utf-8")
163 current_notes = self.getnotes(origin)
164 self.removenotes(origin)
165 if origin in ["programmer", "developer", "source code"]:
166 note = etree.SubElement(self.xmlelement, self.namespaced("extracomment"))
167 else:
168 note = etree.SubElement(self.xmlelement, self.namespaced("translatorcomment"))
169 if position == "append":
170 note.text = "\n".join(filter(None, [current_notes, text.strip()]))
171 else:
172 note.text = text.strip()
173
175
176 comments = []
177 if origin in ["programmer", "developer", "source code", None]:
178 notenode = self.xmlelement.find(self.namespaced("comment"))
179 if notenode is not None and notenode.text is not None:
180 comments.append(notenode.text)
181 notenode = self.xmlelement.find(self.namespaced("extracomment"))
182 if notenode is not None and notenode.text is not None:
183 comments.append(notenode.text)
184 if origin in ["translator", None]:
185 notenode = self.xmlelement.find(self.namespaced("translatorcomment"))
186 if notenode is not None and notenode.text is not None:
187 comments.append(notenode.text)
188 return '\n'.join(comments)
189
191 """Remove all the translator notes."""
192 if origin in ["programmer", "developer", "source code", None]:
193 note = self.xmlelement.find(self.namespaced("comment"))
194 if not note is None:
195 self.xmlelement.remove(note)
196 note = self.xmlelement.find(self.namespaced("extracomment"))
197 if not note is None:
198 self.xmlelement.remove(note)
199 if origin in ["translator", None]:
200 note = self.xmlelement.find(self.namespaced("translatorcomment"))
201 if not note is None:
202 self.xmlelement.remove(note)
203
205 """Returns the type of this translation."""
206 targetnode = self._gettargetnode()
207 if targetnode is not None:
208 return targetnode.get("type")
209 return None
210
219
221 """States whether this unit needs to be reviewed"""
222 return self._gettype() == "unfinished"
223
225 return self._gettype() == "unfinished"
226
232
234 if self.source is None:
235 return None
236 context_name = self.getcontext()
237
238
239 if context_name is not None:
240 return context_name + self.source
241 else:
242 return self.source
243
245
246
247
248
249
250 return bool(self.getid()) and not self.isobsolete()
251
252 - def getcontext(self):
253 parent = self.xmlelement.getparent()
254 if parent is None:
255 return None
256 context = parent.find("name")
257 if context is None:
258 return None
259 return context.text
260
262 if isinstance(location, str):
263 location = location.decode("utf-8")
264 newlocation = etree.SubElement(self.xmlelement, self.namespaced("location"))
265 try:
266 filename, line = location.split(':', 1)
267 except ValueError:
268 filename = location
269 line = None
270 newlocation.set("filename", filename)
271 if line is not None:
272 newlocation.set("line", line)
273
275 location_tags = self.xmlelement.iterfind(self.namespaced("location"))
276 locations = []
277 for location_tag in location_tags:
278 location = location_tag.get("filename")
279 line = location_tag.get("line")
280 if line:
281 if location:
282 location += ':' + line
283 else:
284 location = line
285 locations.append(location)
286 return locations
287
288 - def merge(self, otherunit, overwrite=False, comments=True, authoritative=False):
293
295 return self._gettype() == "obsolete"
296
297
299 """Class representing a XLIFF file store."""
300 UnitClass = tsunit
301 Name = _("Qt Linguist Translation File")
302 Mimetypes = ["application/x-linguist"]
303 Extensions = ["ts"]
304 rootNode = "TS"
305
306 bodyNode = "context"
307 XMLskeleton = '''<!DOCTYPE TS>
308 <TS>
309 </TS>
310 '''
311 namespace = ''
312
316
317 - def initbody(self):
318 """Initialises self.body."""
319 self.namespace = self.document.getroot().nsmap.get(None, None)
320 self.header = self.document.getroot()
321 if self._contextname:
322 self.body = self.getcontextnode(self._contextname)
323 else:
324 self.body = self.document.getroot()
325
327 """Get the source language for this .ts file.
328
329 The 'sourcelanguage' attribute was only added to the TS format in
330 Qt v4.5. We return 'en' if there is no sourcelanguage set.
331
332 We don't implement setsourcelanguage as users really shouldn't be
333 altering the source language in .ts files, it should be set correctly
334 by the extraction tools.
335
336 @return: ISO code e.g. af, fr, pt_BR
337 @rtype: String
338 """
339 lang = data.normalize_code(self.header.get('sourcelanguage', "en"))
340 if lang == 'en-us':
341 return 'en'
342 return lang
343
345 """Get the target language for this .ts file.
346
347 @return: ISO code e.g. af, fr, pt_BR
348 @rtype: String
349 """
350 return data.normalize_code(self.header.get('language'))
351
353 """Set the target language for this .ts file to L{targetlanguage}.
354
355 @param targetlanguage: ISO code e.g. af, fr, pt_BR
356 @type targetlanguage: String
357 """
358 if targetlanguage:
359 self.header.set('language', targetlanguage)
360
361 - def _createcontext(self, contextname, comment=None):
362 """Creates a context node with an optional comment"""
363 context = etree.SubElement(self.document.getroot(), self.namespaced(self.bodyNode))
364 name = etree.SubElement(context, self.namespaced("name"))
365 name.text = contextname
366 if comment:
367 comment_node = context.SubElement(context, "comment")
368 comment_node.text = comment
369 return context
370
371 - def _getcontextname(self, contextnode):
372 """Returns the name of the given context node."""
373 return contextnode.find(self.namespaced("name")).text
374
376 """Returns all contextnames in this TS file."""
377 contextnodes = self.document.findall(self.namespaced("context"))
378 contextnames = [self.getcontextname(contextnode) for contextnode in contextnodes]
379 return contextnames
380
381 - def _getcontextnode(self, contextname):
382 """Returns the context node with the given name."""
383 contextnodes = self.document.findall(self.namespaced("context"))
384 for contextnode in contextnodes:
385 if self._getcontextname(contextnode) == contextname:
386 return contextnode
387 return None
388
389 - def addunit(self, unit, new=True, contextname=None, createifmissing=True):
390 """Adds the given unit to the last used body node (current context).
391
392 If the contextname is specified, switch to that context (creating it
393 if allowed by createifmissing)."""
394 if contextname is None:
395 contextname = unit.getcontext()
396
397 if self._contextname != contextname:
398 if not self._switchcontext(contextname, createifmissing):
399 return None
400 super(tsfile, self).addunit(unit, new)
401
402 return unit
403
404 - def _switchcontext(self, contextname, createifmissing=False):
405 """Switch the current context to the one named contextname, optionally
406 creating it if it doesn't exist."""
407 self._contextname = contextname
408 contextnode = self._getcontextnode(contextname)
409 if contextnode is None:
410 if not createifmissing:
411 return False
412 contextnode = self._createcontext(contextname)
413
414 self.body = contextnode
415 if self.body is None:
416 return False
417 return True
418
425
427 """Converts to a string containing the file's XML.
428
429 We have to override this to ensure mimic the Qt convention:
430 - no XML decleration
431 - plain DOCTYPE that lxml seems to ignore
432 """
433
434
435
436
437 output = etree.tostring(self.document, pretty_print=True,
438 xml_declaration=False, encoding='utf-8')
439 if not "<!DOCTYPE TS>" in output[:30]:
440 output = "<!DOCTYPE TS>" + output
441 return output
442