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, StringElem
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):
158
160
161 notenode = self.xmlelement.find(self.namespaced("comment"))
162 comment = ''
163 if not notenode is None:
164 comment = notenode.text
165 return comment
166
168 """Remove all the translator notes."""
169 note = self.xmlelement.find(self.namespaced("comment"))
170 if not note is None:
171 self.xmlelement.remove(note)
172
174 """Returns the type of this translation."""
175 targetnode = self._gettargetnode()
176 if targetnode is not None:
177 return targetnode.get("type")
178 return None
179
188
190 """States whether this unit needs to be reviewed"""
191 return self._gettype() == "unfinished"
192
194 return self._gettype() == "unfinished"
195
201
203 context_name = self.getcontext()
204
205
206 if context_name is not None:
207 return context_name + self.source
208 else:
209 return self.source
210
212
213
214
215
216
217 return bool(self.getid()) and not self.isobsolete()
218
219 - def getcontext(self):
220 return self.xmlelement.getparent().find("name").text
221
223 if isinstance(location, str):
224 text = text.decode("utf-8")
225 location = etree.SubElement(self.xmlelement, self.namespaced("location"))
226 filename, line = location.split(':', 1)
227 location.set("filename", filename)
228 location.set("line", line or "")
229
231 location = self.xmlelement.find(self.namespaced("location"))
232 if location is None:
233 return []
234 else:
235 return [':'.join([location.get("filename"), location.get("line")])]
236
237 - def merge(self, otherunit, overwrite=False, comments=True):
242
244 return self._gettype() == "obsolete"
245
246
248 """Class representing a XLIFF file store."""
249 UnitClass = tsunit
250 Name = _("Qt Linguist Translation File")
251 Mimetypes = ["application/x-linguist"]
252 Extensions = ["ts"]
253 rootNode = "TS"
254
255 bodyNode = "context"
256 XMLskeleton = '''<!DOCTYPE TS>
257 <TS>
258 </TS>
259 '''
260 namespace = ''
261
265
266 - def initbody(self):
267 """Initialises self.body."""
268 self.namespace = self.document.getroot().nsmap.get(None, None)
269 if self._contextname:
270 self.body = self.getcontextnode(self._contextname)
271 else:
272 self.body = self.document.getroot()
273
275 """Get the target language for this .ts file.
276
277 @return: ISO code e.g. af, fr, pt_BR
278 @rtype: String
279 """
280 return self.body.get('language')
281
283 """Set the target language for this .ts file to L{targetlanguage}.
284
285 @param targetlanguage: ISO code e.g. af, fr, pt_BR
286 @type targetlanguage: String
287 """
288 if targetlanguage:
289 self.body.set('language', targetlanguage)
290
291 - def _createcontext(self, contextname, comment=None):
292 """Creates a context node with an optional comment"""
293 context = etree.SubElement(self.document.getroot(), self.namespaced(self.bodyNode))
294 name = etree.SubElement(context, self.namespaced("name"))
295 name.text = contextname
296 if comment:
297 comment_node = context.SubElement(context, "comment")
298 comment_node.text = comment
299 return context
300
301 - def _getcontextname(self, contextnode):
302 """Returns the name of the given context node."""
303 return filenode.find(self.namespaced("name")).text
304
306 """Returns all contextnames in this TS file."""
307 contextnodes = self.document.findall(self.namespaced("context"))
308 contextnames = [self.getcontextname(contextnode) for contextnode in contextnodes]
309 return contextnames
310
311 - def _getcontextnode(self, contextname):
312 """Returns the context node with the given name."""
313 contextnodes = self.document.findall(self.namespaced("context"))
314 for contextnode in contextnodes:
315 if self.getcontextname(contextnode) == contextname:
316 return contextnode
317 return None
318
319 - def addunit(self, unit, new=True, contextname=None, createifmissing=False):
320 """Adds the given unit to the last used body node (current context).
321
322 If the contextname is specified, switch to that context (creating it
323 if allowed by createifmissing)."""
324 if self._contextname != contextname:
325 if not self._switchcontext(contextname, createifmissing):
326 return None
327 super(tsfile, self).addunit(unit, new)
328
329 return unit
330
331 - def _switchcontext(self, contextname, createifmissing=False):
332 """Switch the current context to the one named contextname, optionally
333 creating it if it doesn't exist."""
334 self._contextname = contextname
335 contextnode = self._getcontextnode(contextname)
336 if contextnode is None:
337 if not createifmissing:
338 return False
339 contextnode = self._createcontext(contextname)
340
341 self.body = contextnode
342 if self.body is None:
343 return False
344 return True
345
352
354 """Converts to a string containing the file's XML.
355
356 We have to override this to ensure mimic the Qt convention:
357 - no XML decleration
358 - plain DOCTYPE that lxml seems to ignore
359 """
360
361
362
363
364 output = etree.tostring(self.document, pretty_print=True,
365 xml_declaration=False, encoding='utf-8')
366 if not "<!DOCTYPE TS>" in output[:30]:
367 output = "<!DOCTYPE TS>" + output
368 return output
369