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