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{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 getcontext(self):
212 return self.xmlelement.getparent().find("name").text
213
214 - def addlocation(self, location):
215 if isinstance(location, str): 216 text = text.decode("utf-8") 217 location = etree.SubElement(self.xmlelement, self.namespaced("location")) 218 filename, line = location.split(':', 1) 219 location.set("filename", filename) 220 location.set("line", line or "")
221
222 - def getlocations(self):
223 location = self.xmlelement.find(self.namespaced("location")) 224 if location is None: 225 return [] 226 else: 227 return [':'.join([location.get("filename"), location.get("line")])]
228
229 - def merge(self, otherunit, overwrite=False, comments=True):
230 super(tsunit, self).merge(otherunit, overwrite, comments) 231 #TODO: check if this is necessary: 232 if otherunit.isfuzzy(): 233 self.markfuzzy()
234
235 - def isobsolete(self):
236 return self._gettype() == "obsolete"
237 238
239 -class tsfile(lisa.LISAfile):
240 """Class representing a XLIFF file store.""" 241 UnitClass = tsunit 242 Name = _("Qt Linguist Translation File") 243 Mimetypes = ["application/x-linguist"] 244 Extensions = ["ts"] 245 rootNode = "TS" 246 # We will switch out .body to fit with the context we are working on 247 bodyNode = "context" 248 XMLskeleton = '''<!DOCTYPE TS> 249 <TS> 250 </TS> 251 ''' 252 namespace = '' 253
254 - def __init__(self, *args, **kwargs):
255 self._contextname = None 256 lisa.LISAfile.__init__(self, *args, **kwargs)
257
258 - def initbody(self):
259 """Initialises self.body.""" 260 self.namespace = self.document.getroot().nsmap.get(None, None) 261 if self._contextname: 262 self.body = self.getcontextnode(self._contextname) 263 else: 264 self.body = self.document.getroot()
265
266 - def gettargetlanguage(self):
267 """Get the target language for this .ts file. 268 269 @return: ISO code e.g. af, fr, pt_BR 270 @rtype: String 271 """ 272 return self.body.get('language')
273
274 - def settargetlanguage(self, targetlanguage):
275 """Set the target language for this .ts file to L{targetlanguage}. 276 277 @param targetlanguage: ISO code e.g. af, fr, pt_BR 278 @type targetlanguage: String 279 """ 280 if targetlanguage: 281 self.body.set('language', targetlanguage)
282
283 - def _createcontext(self, contextname, comment=None):
284 """Creates a context node with an optional comment""" 285 context = etree.SubElement(self.document.getroot(), self.namespaced(self.bodyNode)) 286 name = etree.SubElement(context, self.namespaced("name")) 287 name.text = contextname 288 if comment: 289 comment_node = context.SubElement(context, "comment") 290 comment_node.text = comment 291 return context
292
293 - def _getcontextname(self, contextnode):
294 """Returns the name of the given context node.""" 295 return filenode.find(self.namespaced("name")).text
296
297 - def _getcontextnames(self):
298 """Returns all contextnames in this TS file.""" 299 contextnodes = self.document.findall(self.namespaced("context")) 300 contextnames = [self.getcontextname(contextnode) for contextnode in contextnodes] 301 return contextnames
302
303 - def _getcontextnode(self, contextname):
304 """Returns the context node with the given name.""" 305 contextnodes = self.document.findall(self.namespaced("context")) 306 for contextnode in contextnodes: 307 if self.getcontextname(contextnode) == contextname: 308 return contextnode 309 return None
310
311 - def addunit(self, unit, new=True, contextname=None, createifmissing=False):
312 """Adds the given unit to the last used body node (current context). 313 314 If the contextname is specified, switch to that context (creating it 315 if allowed by createifmissing).""" 316 if self._contextname != contextname: 317 if not self._switchcontext(contextname, createifmissing): 318 return None 319 super(tsfile, self).addunit(unit, new) 320 # lisa.setXMLspace(unit.xmlelement, "preserve") 321 return unit
322
323 - def _switchcontext(self, contextname, createifmissing=False):
324 """Switch the current context to the one named contextname, optionally 325 creating it if it doesn't exist.""" 326 self._contextname = contextname 327 contextnode = self._getcontextnode(contextname) 328 if contextnode is None: 329 if not createifmissing: 330 return False 331 contextnode = self._createcontext(contextname) 332 333 self.body = contextnode 334 if self.body is None: 335 return False 336 return True
337
338 - def nplural(self):
339 lang = self.body.get("language") 340 if NPLURALS.has_key(lang): 341 return NPLURALS[lang] 342 else: 343 return 1
344
345 - def __str__(self):
346 """Converts to a string containing the file's XML. 347 348 We have to override this to ensure mimic the Qt convention: 349 - no XML decleration 350 - plain DOCTYPE that lxml seems to ignore 351 """ 352 # A bug in lxml means we have to output the doctype ourselves. For 353 # more information, see: 354 # http://codespeak.net/pipermail/lxml-dev/2008-October/004112.html 355 # The problem was fixed in lxml 2.1.3 356 output = etree.tostring(self.document, pretty_print=True, 357 xml_declaration=False, encoding='utf-8') 358 if not "<!DOCTYPE TS>" in output[:30]: 359 output = "<!DOCTYPE TS>" + output 360 return output
361