Package translate :: Package storage :: Module ts2
[hide private]
[frames] | no frames]

Source Code for Module translate.storage.ts2

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # Copyright 2008-2009 Zuza Software Foundation 
  5  # 
  6  # This file is part of the Translate Toolkit. 
  7  # 
  8  # This program is free software; you can redistribute it and/or modify 
  9  # it under the terms of the GNU General Public License as published by 
 10  # the Free Software Foundation; either version 2 of the License, or 
 11  # (at your option) any later version. 
 12  # 
 13  # This program is distributed in the hope that it will be useful, 
 14  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 16  # GNU General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with this program; if not, see <http://www.gnu.org/licenses/>. 
 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  # TODO: handle translation types 
 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 -class tsunit(lisa.LISAunit):
64 """A single term in the xliff file.""" 65 66 rootNode = "message" 67 languageNode = "source" 68 textNode = "" 69 namespace = '' 70 rich_parsers = general.parsers 71
72 - def createlanguageNode(self, lang, text, purpose):
73 """Returns an xml Element setup with given parameters.""" 74 75 assert purpose 76 if purpose == "target": 77 purpose = "translation" 78 langset = etree.Element(self.namespaced(purpose)) 79 #TODO: check language 80 # lisa.setXMLlang(langset, lang) 81 82 langset.text = text 83 return langset
84
85 - def _getsourcenode(self):
86 return self.xmlelement.find(self.namespaced(self.languageNode))
87
88 - def _gettargetnode(self):
89 return self.xmlelement.find(self.namespaced("translation"))
90
91 - def getlanguageNodes(self):
92 """We override this to get source and target nodes.""" 93 def not_none(node): 94 return not node is None
95 return filter(not_none, [self._getsourcenode(), self._gettargetnode()])
96
97 - def getsource(self):
98 # TODO: support <byte>. See bug 528. 99 sourcenode = self._getsourcenode() 100 if self.hasplural(): 101 return multistring([sourcenode.text]) 102 else: 103 return data.forceunicode(sourcenode.text)
104 source = property(getsource, lisa.LISAunit.setsource) 105 rich_source = property(base.TranslationUnit._get_rich_source, base.TranslationUnit._set_rich_source) 106
107 - def settarget(self, text):
108 # This is a fairly destructive implementation. Don't assume that this 109 # is necessarily correct in all regards, but it does deal with a lot of 110 # cases. It is hard to deal with plurals, since 111 #Firstly deal with reinitialising to None or setting to identical string 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
134 - def gettarget(self):
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
147 - def hasplural(self):
148 return self.xmlelement.get("numerus") == "yes"
149
150 - def addnote(self, text, origin=None):
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() 156 note = etree.SubElement(self.xmlelement, self.namespaced("comment")) 157 note.text = "\n".join(filter(None, [current_notes, text.strip()]))
158
159 - def getnotes(self, origin=None):
160 #TODO: consider only responding when origin has certain values 161 notenode = self.xmlelement.find(self.namespaced("comment")) 162 comment = '' 163 if not notenode is None: 164 comment = notenode.text 165 return comment
166
167 - def removenotes(self):
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
173 - def _gettype(self):
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
180 - def _settype(self, value=None):
181 """Set the type of this translation.""" 182 if value is None and self._gettype: 183 # lxml recommends against using .attrib, but there seems to be no 184 # other way 185 self._gettargetnode().attrib.pop("type") 186 else: 187 self._gettargetnode().set("type", value)
188
189 - def isreview(self):
190 """States whether this unit needs to be reviewed""" 191 return self._gettype() == "unfinished"
192
193 - def isfuzzy(self):
194 return self._gettype() == "unfinished"
195
196 - def markfuzzy(self, value=True):
197 if value: 198 self._settype("unfinished") 199 else: 200 self._settype(None)
201
202 - def getid(self):
203 context_name = self.getcontext() 204 #XXX: context_name is not supposed to be able to be None (the <name> 205 # tag is compulsary in the <context> tag) 206 if context_name is not None: 207 return context_name + self.source 208 else: 209 return self.source
210
211 - def istranslatable(self):
212 # Found a file in the wild with no context and an empty source. This 213 # served as a header, so let's classify this as not translatable. 214 # http://bibletime.svn.sourceforge.net/viewvc/bibletime/trunk/bibletime/i18n/messages/bibletime_ui.ts 215 # Furthermore, let's decide to handle obsolete units as untranslatable 216 # like we do with PO. 217 return bool(self.getid()) and not self.isobsolete()
218
219 - def getcontext(self):
220 return self.xmlelement.getparent().find("name").text
221
222 - def addlocation(self, location):
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
230 - def getlocations(self):
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):
238 super(tsunit, self).merge(otherunit, overwrite, comments) 239 #TODO: check if this is necessary: 240 if otherunit.isfuzzy(): 241 self.markfuzzy()
242
243 - def isobsolete(self):
244 return self._gettype() == "obsolete"
245 246
247 -class tsfile(lisa.LISAfile):
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 # We will switch out .body to fit with the context we are working on 255 bodyNode = "context" 256 XMLskeleton = '''<!DOCTYPE TS> 257 <TS> 258 </TS> 259 ''' 260 namespace = '' 261
262 - def __init__(self, *args, **kwargs):
263 self._contextname = None 264 lisa.LISAfile.__init__(self, *args, **kwargs)
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
274 - def gettargetlanguage(self):
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
282 - def settargetlanguage(self, targetlanguage):
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
305 - def _getcontextnames(self):
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 # lisa.setXMLspace(unit.xmlelement, "preserve") 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
346 - def nplural(self):
347 lang = self.body.get("language") 348 if NPLURALS.has_key(lang): 349 return NPLURALS[lang] 350 else: 351 return 1
352
353 - def __str__(self):
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 # A bug in lxml means we have to output the doctype ourselves. For 361 # more information, see: 362 # http://codespeak.net/pipermail/lxml-dev/2008-October/004112.html 363 # The problem was fixed in lxml 2.1.3 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