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
96
104 source = property(getsource, lisa.LISAunit.setsource)
105 rich_source = property(base.TranslationUnit._get_rich_source, base.TranslationUnit._set_rich_source)
106
108
109
110
111
112 if self.gettarget() == text:
113 return
114 strings = []
115 if isinstance(text, multistring):
116 strings = text.strings
117 elif isinstance(text, list):
118 strings = text
119 else:
120 strings = [text]
121 targetnode = self._gettargetnode()
122 type = targetnode.get("type")
123 targetnode.clear()
124 if type:
125 targetnode.set("type", type)
126 if self.hasplural() or len(strings) > 1:
127 self.xmlelement.set("numerus", "yes")
128 for string in strings:
129 numerus = etree.SubElement(targetnode, self.namespaced("numerusform"))
130 numerus.text = data.forceunicode(string) or u""
131 else:
132 targetnode.text = data.forceunicode(text) or u""
133
135 targetnode = self._gettargetnode()
136 if targetnode is None:
137 etree.SubElement(self.xmlelement, self.namespaced("translation"))
138 return None
139 if self.hasplural():
140 numerus_nodes = targetnode.findall(self.namespaced("numerusform"))
141 return multistring([node.text or u"" for node in numerus_nodes])
142 else:
143 return data.forceunicode(targetnode.text) or u""
144 target = property(gettarget, settarget)
145 rich_target = property(base.TranslationUnit._get_rich_target, base.TranslationUnit._set_rich_target)
146
148 return self.xmlelement.get("numerus") == "yes"
149
150 - def addnote(self, text, origin=None, position="append"):
151 """Add a note specifically in a "comment" tag"""
152 if isinstance(text, str):
153 text = text.decode("utf-8")
154 current_notes = self.getnotes(origin)
155 self.removenotes(origin)
156 if origin in ["programmer", "developer", "source code"]:
157 note = etree.SubElement(self.xmlelement, self.namespaced("extracomment"))
158 else:
159 note = etree.SubElement(self.xmlelement, self.namespaced("translatorcomment"))
160 if position == "append":
161 note.text = "\n".join(filter(None, [current_notes, text.strip()]))
162 else:
163 note.text = text.strip()
164
166
167 comments = []
168 if origin in ["programmer", "developer", "source code", None]:
169 notenode = self.xmlelement.find(self.namespaced("comment"))
170 if notenode is not None:
171 comments.append(notenode.text)
172 notenode = self.xmlelement.find(self.namespaced("extracomment"))
173 if notenode is not None:
174 comments.append(notenode.text)
175 if origin in ["translator", None]:
176 notenode = self.xmlelement.find(self.namespaced("translatorcomment"))
177 if notenode is not None:
178 comments.append(notenode.text)
179 return '\n'.join(comments)
180
182 """Remove all the translator notes."""
183 if origin in ["programmer", "developer", "source code", None]:
184 note = self.xmlelement.find(self.namespaced("comment"))
185 if not note is None:
186 self.xmlelement.remove(note)
187 note = self.xmlelement.find(self.namespaced("extracomment"))
188 if not note is None:
189 self.xmlelement.remove(note)
190 if origin in ["translator", None]:
191 note = self.xmlelement.find(self.namespaced("translatorcomment"))
192 if not note is None:
193 self.xmlelement.remove(note)
194
195
197 """Returns the type of this translation."""
198 targetnode = self._gettargetnode()
199 if targetnode is not None:
200 return targetnode.get("type")
201 return None
202
211
213 """States whether this unit needs to be reviewed"""
214 return self._gettype() == "unfinished"
215
217 return self._gettype() == "unfinished"
218
224
226 context_name = self.getcontext()
227
228
229 if context_name is not None:
230 return context_name + self.source
231 else:
232 return self.source
233
235
236
237
238
239
240 return bool(self.getid()) and not self.isobsolete()
241
242 - def getcontext(self):
243 parent = self.xmlelement.getparent()
244 if parent is None:
245 return None
246 context = parent.find("name")
247 if context is None:
248 return None
249 return context.text
250
252 if isinstance(location, str):
253 location = location.decode("utf-8")
254 location = etree.SubElement(self.xmlelement, self.namespaced("location"))
255 filename, line = location.split(':', 1)
256 location.set("filename", filename)
257 location.set("line", line or "")
258
260 location = self.xmlelement.find(self.namespaced("location"))
261 if location is None:
262 return []
263 else:
264 return [':'.join([location.get("filename"), location.get("line")])]
265
266 - def merge(self, otherunit, overwrite=False, comments=True, authoritative=False):
271
273 return self._gettype() == "obsolete"
274
275
277 """Class representing a XLIFF file store."""
278 UnitClass = tsunit
279 Name = _("Qt Linguist Translation File")
280 Mimetypes = ["application/x-linguist"]
281 Extensions = ["ts"]
282 rootNode = "TS"
283
284 bodyNode = "context"
285 XMLskeleton = '''<!DOCTYPE TS>
286 <TS>
287 </TS>
288 '''
289 namespace = ''
290
294
295 - def initbody(self):
296 """Initialises self.body."""
297 self.namespace = self.document.getroot().nsmap.get(None, None)
298 if self._contextname:
299 self.body = self.getcontextnode(self._contextname)
300 else:
301 self.body = self.document.getroot()
302
304 """Get the target language for this .ts file.
305
306 @return: ISO code e.g. af, fr, pt_BR
307 @rtype: String
308 """
309 return self.body.get('language')
310
312 """Set the target language for this .ts file to L{targetlanguage}.
313
314 @param targetlanguage: ISO code e.g. af, fr, pt_BR
315 @type targetlanguage: String
316 """
317 if targetlanguage:
318 self.body.set('language', targetlanguage)
319
320 - def _createcontext(self, contextname, comment=None):
321 """Creates a context node with an optional comment"""
322 context = etree.SubElement(self.document.getroot(), self.namespaced(self.bodyNode))
323 name = etree.SubElement(context, self.namespaced("name"))
324 name.text = contextname
325 if comment:
326 comment_node = context.SubElement(context, "comment")
327 comment_node.text = comment
328 return context
329
330 - def _getcontextname(self, contextnode):
331 """Returns the name of the given context node."""
332 return contextnode.find(self.namespaced("name")).text
333
335 """Returns all contextnames in this TS file."""
336 contextnodes = self.document.findall(self.namespaced("context"))
337 contextnames = [self.getcontextname(contextnode) for contextnode in contextnodes]
338 return contextnames
339
340 - def _getcontextnode(self, contextname):
341 """Returns the context node with the given name."""
342 contextnodes = self.document.findall(self.namespaced("context"))
343 for contextnode in contextnodes:
344 if self._getcontextname(contextnode) == contextname:
345 return contextnode
346 return None
347
348 - def addunit(self, unit, new=True, contextname=None, createifmissing=True):
349 """Adds the given unit to the last used body node (current context).
350
351 If the contextname is specified, switch to that context (creating it
352 if allowed by createifmissing)."""
353 if contextname is None:
354 contextname = unit.getcontext()
355
356 if self._contextname != contextname:
357 if not self._switchcontext(contextname, createifmissing):
358 return None
359 super(tsfile, self).addunit(unit, new)
360
361 return unit
362
363 - def _switchcontext(self, contextname, createifmissing=False):
364 """Switch the current context to the one named contextname, optionally
365 creating it if it doesn't exist."""
366 self._contextname = contextname
367 contextnode = self._getcontextnode(contextname)
368 if contextnode is None:
369 if not createifmissing:
370 return False
371 contextnode = self._createcontext(contextname)
372
373 self.body = contextnode
374 if self.body is None:
375 return False
376 return True
377
384
386 """Converts to a string containing the file's XML.
387
388 We have to override this to ensure mimic the Qt convention:
389 - no XML decleration
390 - plain DOCTYPE that lxml seems to ignore
391 """
392
393
394
395
396 output = etree.tostring(self.document, pretty_print=True,
397 xml_declaration=False, encoding='utf-8')
398 if not "<!DOCTYPE TS>" in output[:30]:
399 output = "<!DOCTYPE TS>" + output
400 return output
401