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
136 numerus.tail = u"\n "
137 else:
138 targetnode.text = data.forceunicode(text) or u""
139 targetnode.tail = u"\n "
140
142 targetnode = self._gettargetnode()
143 if targetnode is None:
144 etree.SubElement(self.xmlelement, self.namespaced("translation"))
145 return None
146 if self.hasplural():
147 numerus_nodes = targetnode.findall(self.namespaced("numerusform"))
148 return multistring([node.text or u"" for node in numerus_nodes])
149 else:
150 return data.forceunicode(targetnode.text) or u""
151 target = property(gettarget, settarget)
152 rich_target = property(base.TranslationUnit._get_rich_target, base.TranslationUnit._set_rich_target)
153
155 return self.xmlelement.get("numerus") == "yes"
156
157 - def addnote(self, text, origin=None, position="append"):
158 """Add a note specifically in a "comment" tag"""
159 if isinstance(text, str):
160 text = text.decode("utf-8")
161 current_notes = self.getnotes(origin)
162 self.removenotes(origin)
163 if origin in ["programmer", "developer", "source code"]:
164 note = etree.SubElement(self.xmlelement, self.namespaced("extracomment"))
165 else:
166 note = etree.SubElement(self.xmlelement, self.namespaced("translatorcomment"))
167 if position == "append":
168 note.text = "\n".join(filter(None, [current_notes, text.strip()]))
169 else:
170 note.text = text.strip()
171
173
174 comments = []
175 if origin in ["programmer", "developer", "source code", None]:
176 notenode = self.xmlelement.find(self.namespaced("comment"))
177 if notenode is not None and notenode.text is not None:
178 comments.append(notenode.text)
179 notenode = self.xmlelement.find(self.namespaced("extracomment"))
180 if notenode is not None and notenode.text is not None:
181 comments.append(notenode.text)
182 if origin in ["translator", None]:
183 notenode = self.xmlelement.find(self.namespaced("translatorcomment"))
184 if notenode is not None and notenode.text is not None:
185 comments.append(notenode.text)
186 return '\n'.join(comments)
187
189 """Remove all the translator notes."""
190 if origin in ["programmer", "developer", "source code", None]:
191 note = self.xmlelement.find(self.namespaced("comment"))
192 if not note is None:
193 self.xmlelement.remove(note)
194 note = self.xmlelement.find(self.namespaced("extracomment"))
195 if not note is None:
196 self.xmlelement.remove(note)
197 if origin in ["translator", None]:
198 note = self.xmlelement.find(self.namespaced("translatorcomment"))
199 if not note is None:
200 self.xmlelement.remove(note)
201
203 """Returns the type of this translation."""
204 targetnode = self._gettargetnode()
205 if targetnode is not None:
206 return targetnode.get("type")
207 return None
208
217
219 """States whether this unit needs to be reviewed"""
220 return self._gettype() == "unfinished"
221
223 return self._gettype() == "unfinished"
224
230
232 context_name = self.getcontext()
233
234
235 if context_name is not None:
236 return context_name + self.source
237 else:
238 return self.source
239
241
242
243
244
245
246 return bool(self.getid()) and not self.isobsolete()
247
248 - def getcontext(self):
249 parent = self.xmlelement.getparent()
250 if parent is None:
251 return None
252 context = parent.find("name")
253 if context is None:
254 return None
255 return context.text
256
258 if isinstance(location, str):
259 location = location.decode("utf-8")
260 newlocation = etree.SubElement(self.xmlelement, self.namespaced("location"))
261 try:
262 filename, line = location.split(':', 1)
263 except ValueError:
264 filename = location
265 line = None
266 newlocation.set("filename", filename)
267 if line is not None:
268 newlocation.set("line", line)
269
271 location_tags = self.xmlelement.iterfind(self.namespaced("location"))
272 locations = []
273 for location_tag in location_tags:
274 location = location_tag.get("filename")
275 line = location_tag.get("line")
276 if line:
277 if location:
278 location += ':' + line
279 else:
280 location = line
281 locations.append(location)
282 return locations
283
284 - def merge(self, otherunit, overwrite=False, comments=True, authoritative=False):
289
291 return self._gettype() == "obsolete"
292
293
295 """Class representing a XLIFF file store."""
296 UnitClass = tsunit
297 Name = _("Qt Linguist Translation File")
298 Mimetypes = ["application/x-linguist"]
299 Extensions = ["ts"]
300 rootNode = "TS"
301
302 bodyNode = "context"
303 XMLskeleton = '''<!DOCTYPE TS>
304 <TS>
305 </TS>
306 '''
307 namespace = ''
308
312
313 - def initbody(self):
314 """Initialises self.body."""
315 self.namespace = self.document.getroot().nsmap.get(None, None)
316 self.header = self.document.getroot()
317 if self._contextname:
318 self.body = self.getcontextnode(self._contextname)
319 else:
320 self.body = self.document.getroot()
321
323 """Get the source language for this .ts file.
324
325 The 'sourcelanguage' attribute was only added to the TS format in
326 Qt v4.5. We return 'en' if there is no sourcelanguage set.
327
328 We don't implement setsourcelanguage as users really shouldn't be
329 altering the source language in .ts files, it should be set correctly
330 by the extraction tools.
331
332 @return: ISO code e.g. af, fr, pt_BR
333 @rtype: String
334 """
335 lang = data.normalize_code(self.header.get('sourcelanguage', "en"))
336 if lang == 'en-us':
337 return 'en'
338 return lang
339
341 """Get the target language for this .ts file.
342
343 @return: ISO code e.g. af, fr, pt_BR
344 @rtype: String
345 """
346 return data.normalize_code(self.header.get('language'))
347
349 """Set the target language for this .ts file to L{targetlanguage}.
350
351 @param targetlanguage: ISO code e.g. af, fr, pt_BR
352 @type targetlanguage: String
353 """
354 if targetlanguage:
355 self.header.set('language', targetlanguage)
356
357 - def _createcontext(self, contextname, comment=None):
358 """Creates a context node with an optional comment"""
359 context = etree.SubElement(self.document.getroot(), self.namespaced(self.bodyNode))
360 name = etree.SubElement(context, self.namespaced("name"))
361 name.text = contextname
362 if comment:
363 comment_node = context.SubElement(context, "comment")
364 comment_node.text = comment
365 return context
366
367 - def _getcontextname(self, contextnode):
368 """Returns the name of the given context node."""
369 return contextnode.find(self.namespaced("name")).text
370
372 """Returns all contextnames in this TS file."""
373 contextnodes = self.document.findall(self.namespaced("context"))
374 contextnames = [self.getcontextname(contextnode) for contextnode in contextnodes]
375 return contextnames
376
377 - def _getcontextnode(self, contextname):
378 """Returns the context node with the given name."""
379 contextnodes = self.document.findall(self.namespaced("context"))
380 for contextnode in contextnodes:
381 if self._getcontextname(contextnode) == contextname:
382 return contextnode
383 return None
384
385 - def addunit(self, unit, new=True, contextname=None, createifmissing=True):
386 """Adds the given unit to the last used body node (current context).
387
388 If the contextname is specified, switch to that context (creating it
389 if allowed by createifmissing)."""
390 if contextname is None:
391 contextname = unit.getcontext()
392
393 if self._contextname != contextname:
394 if not self._switchcontext(contextname, createifmissing):
395 return None
396 super(tsfile, self).addunit(unit, new)
397
398 return unit
399
400 - def _switchcontext(self, contextname, createifmissing=False):
401 """Switch the current context to the one named contextname, optionally
402 creating it if it doesn't exist."""
403 self._contextname = contextname
404 contextnode = self._getcontextnode(contextname)
405 if contextnode is None:
406 if not createifmissing:
407 return False
408 contextnode = self._createcontext(contextname)
409
410 self.body = contextnode
411 if self.body is None:
412 return False
413 return True
414
421
423 """Converts to a string containing the file's XML.
424
425 We have to override this to ensure mimic the Qt convention:
426 - no XML decleration
427 - plain DOCTYPE that lxml seems to ignore
428 """
429
430
431
432
433 output = etree.tostring(self.document, pretty_print=True,
434 xml_declaration=False, encoding='utf-8')
435 if not "<!DOCTYPE TS>" in output[:30]:
436 output = "<!DOCTYPE TS>" + output
437 return output
438