Package translate :: Package convert :: Module pot2po

[hide private]
[frames] | no frames]

# Source Code for Module translate.convert.pot2po

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  #
      4  # Copyright 2004-2009 Zuza Software Foundation
      5  #
      6  # This file is part of translate.
      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  """Convert template files (like .pot or template .xlf files) translation files,
     22  preserving existing translations.
     23
     24  See: http://translate.sourceforge.net/wiki/toolkit/pot2po for examples and
     25  usage instructions.
     26  """
     27
     28  from translate.storage import factory
     29  from translate.search import match
     30  from translate.misc.multistring import multistring
     31  from translate.tools import pretranslate
     32  from translate.storage import poheader
     33  from translate.storage import catkeys
     34
     35
     36 -def convertpot(input_file, output_file, template_file, tm=None, min_similarity=75, fuzzymatching=True, classes=factory.classes, **kwargs):
     37      """Main conversion function"""
     38
     39      input_store = factory.getobject(input_file, classes=classes)
     40      template_store = None
     41      if template_file is not None:
     42          template_store = factory.getobject(template_file, classes=classes)
     43      output_store = convert_stores(input_store, template_store, tm, min_similarity, fuzzymatching, **kwargs)
     44      output_file.write(str(output_store))
     45      return 1
     46
     47 -def convert_stores(input_store, template_store, tm=None, min_similarity=75, fuzzymatching=True, **kwargs):
     48      """Actual conversion function, works on stores not files, returns
     49      a properly initialized pretranslated output store, with structure
     50      based on input_store, metadata based on template_store, migrates
     51      old translations from template_store and pretranslating from tm"""
     52
     53      #prepare for merging
     54      output_store = type(input_store)()
     55      #create fuzzy matchers to be used by pretranslate.pretranslate_unit
     56      matchers = []
     57      _prepare_merge(input_store, output_store, template_store)
     58      if fuzzymatching:
     59          if template_store:
     60              matcher = match.matcher(template_store, max_candidates=1, min_similarity=min_similarity, max_length=3000, usefuzzy=True)
     61              matcher.addpercentage = False
     62              matchers.append(matcher)
     63          if tm:
     64              matcher = pretranslate.memory(tm, max_candidates=1, min_similarity=min_similarity, max_length=1000)
     65              matcher.addpercentage = False
     66              matchers.append(matcher)
     67
     68      #initialize store
     69      _store_pre_merge(input_store, output_store, template_store)
     70
     71      # Do matching
     72      for input_unit in input_store.units:
     73          if input_unit.isheader():
     74              continue
     75          if input_unit.istranslatable():
     76              input_unit = pretranslate.pretranslate_unit(input_unit, template_store, matchers, mark_reused=True)
     77              _unit_post_merge(input_unit, input_store, output_store, template_store)
     78          output_store.addunit(input_unit)
     79
     80      #finalize store
     81      _store_post_merge(input_store, output_store, template_store)
     82
     83      return output_store
     84
     85
     86  ##dispatchers
     87 -def _prepare_merge(input_store, output_store, template_store, **kwargs):
     88      """Prepare stores & TM matchers before merging."""
     89      #dispatch to format specific functions
     90      prepare_merge_hook = "_prepare_merge_%s" % input_store.__class__.__name__
     91      if  globals().has_key(prepare_merge_hook):
     92          globals()[prepare_merge_hook](input_store, output_store, template_store, **kwargs)
     93
     94      #generate an index so we can search by source string and location later on
     95      input_store.makeindex()
     96      if template_store:
     97          template_store.makeindex()
     98
     99
    100 -def _store_pre_merge(input_store, output_store, template_store, **kwargs) :
    101      """Initialize the new file with things like headers and metadata."""
    102      #formats that implement poheader interface are a special case
    103      if isinstance(input_store, poheader.poheader):
    104          _do_poheaders(input_store, output_store, template_store)
    105      elif isinstance(input_store, catkeys.CatkeysFile):
    106          output_store.header = input_store.header
    107
    108      #dispatch to format specific functions
    109      store_pre_merge_hook = "_store_pre_merge_%s" % input_store.__class__.__name__
    110      if  globals().has_key(store_pre_merge_hook):
    111          globals()[store_pre_merge_hook](input_store, output_store, template_store, **kwargs)
    112
    113
    114 -def _store_post_merge(input_store, output_store, template_store, **kwargs) :
    115      """Close file after merging all translations, used for adding
    116      statistics, obsolete messages and similar wrapup tasks."""
    117      #dispatch to format specific functions
    118      store_post_merge_hook = "_store_post_merge_%s" % input_store.__class__.__name__
    119      if  globals().has_key(store_post_merge_hook):
    120          globals()[store_post_merge_hook](input_store, output_store, template_store, **kwargs)
    121
    122 -def _unit_post_merge(input_unit, input_store, output_store, template_store, **kwargs):
    123      """Handle any unit level cleanup and situations not handled by the merge()
    124      function."""
    125      #dispatch to format specific functions
    126      unit_post_merge_hook = "_unit_post_merge_%s" % input_unit.__class__.__name__
    127      if  globals().has_key(unit_post_merge_hook):
    128          globals()[unit_post_merge_hook](input_unit, input_store, output_store, template_store, **kwargs)
    129
    130
    131  ##format specific functions
    132 -def _prepare_merge_pofile(input_store, output_store, template_store):
    133      """PO format specific template preparation logic."""
    134      #we need to revive obsolete units to be able to consider
    135      #their translation when matching
    136      if template_store:
    137          for unit in template_store.units:
    138              if unit.isobsolete():
    139                  unit.resurrect()
    140
    141
    142 -def _unit_post_merge_pounit(input_unit, input_store, output_store, template_store):
    143      """PO format specific plural string initializtion logic."""
    144      #FIXME: do we want to do that for poxliff also?
    145      if input_unit.hasplural() and len(input_unit.target) == 0:
    146          # untranslated plural unit; Let's ensure that we have the correct number of plural forms:
    147          nplurals, plural = output_store.getheaderplural()
    148          if nplurals and nplurals.isdigit() and nplurals != '2':
    149              input_unit.target = multistring([""]*int(nplurals))
    150
    151
    152 -def _store_post_merge_pofile(input_store, output_store, template_store):
    153      """PO format specific: adds newly obsoleted messages to end of store."""
    154      #Let's take care of obsoleted messages
    155      if template_store:
    156          newlyobsoleted = []
    157          for unit in template_store.units:
    158              if not unit.istranslatable() or not unit.istranslated():
    159                  continue
    160              if unit.target and not (input_store.findid(unit.getid()) or hasattr(unit, "reused")):
    161                  #not in .pot, make it obsolete
    162                  unit.makeobsolete()
    163                  newlyobsoleted.append(unit)
    164              elif unit.isobsolete():
    165                  output_store.addunit(unit)
    166          for unit in newlyobsoleted:
    167              output_store.addunit(unit)
    168
    169
    170 -def _do_poheaders(input_store, output_store, template_store):
    171      """Adds initialized PO headers to output store."""
    172      # header values
    173      charset = "UTF-8"
    174      encoding = "8bit"
    175      project_id_version = None
    176      pot_creation_date = None
    177      po_revision_date = None
    178      last_translator = None
    179      language_team = None
    180      mime_version = None
    181      plural_forms = None
    182      kwargs = {}
    183
    184      if template_store is not None and isinstance(template_store, poheader.poheader):
    185          templateheadervalues = template_store.parseheader()
    186          for key, value in templateheadervalues.iteritems():
    187              if key == "Project-Id-Version":
    188                  project_id_version = value
    189              elif key == "Last-Translator":
    190                  last_translator = value
    191              elif key == "Language-Team":
    192                  language_team = value
    193              elif key == "PO-Revision-Date":
    194                  po_revision_date = value
    195              elif key in ("POT-Creation-Date", "MIME-Version"):
    196                  # don't know how to handle these keys, or ignoring them
    197                  pass
    198              elif key == "Content-Type":
    199                  kwargs[key] = value
    200              elif key == "Content-Transfer-Encoding":
    201                  encoding = value
    202              elif key == "Plural-Forms":
    203                  plural_forms = value
    204              else:
    205                  kwargs[key] = value
    206
    207      inputheadervalues = input_store.parseheader()
    208      for key, value in inputheadervalues.iteritems():
    209          if key in ("Project-Id-Version", "Last-Translator", "Language-Team", "PO-Revision-Date", "Content-Type", "Content-Transfer-Encoding", "Plural-Forms"):
    210              # want to carry these from the template so we ignore them
    211              pass
    212          elif key == "POT-Creation-Date":
    213              pot_creation_date = value
    214          elif key == "MIME-Version":
    215              mime_version = value
    216          else:
    217              kwargs[key] = value
    218
    219      output_header = output_store.init_headers(charset=charset, encoding=encoding, project_id_version=project_id_version,
    220          pot_creation_date=pot_creation_date, po_revision_date=po_revision_date, last_translator=last_translator,
    221          language_team=language_team, mime_version=mime_version, plural_forms=plural_forms, **kwargs)
    222
    223      # Get the header comments and fuzziness state
    224
    225      # initial values from pot file
    226      input_header = input_store.header()
    227      if input_header is not None:
    228          if input_header.getnotes("developer"):
    229              output_header.addnote(input_header.getnotes("developer"), origin="developer", position="replace")
    230          if input_header.getnotes("translator"):
    231              output_header.addnote(input_header.getnotes("translator"), origin="translator", position="replace")
    232          output_header.markfuzzy(input_header.isfuzzy())
    233
    234      # override some values from input file
    235      if template_store is not None:
    236          template_header = template_store.header()
    237          if template_header is not None:
    238              if template_header.getnotes("translator"):
    239                  output_header.addnote(template_header.getnotes("translator"), "translator", position="replace")
    240              output_header.markfuzzy(template_header.isfuzzy())
    241
    242
    243 -def main(argv=None):
    244      from translate.convert import convert
    245      formats = {"pot": ("po", convertpot), ("pot", "po"): ("po", convertpot),
    246                 "xlf": ("xlf", convertpot), ("xlf", "xlf"): ("xlf", convertpot),
    247                 "catkeys": ("catkeys", convertpot), ("catkeys", "catkeys"): ("catkeys", convertpot),
    248              }
    249      parser = convert.ConvertOptionParser(formats, usepots=True, usetemplates=True,
    250          allowmissingtemplate=True, description=__doc__)
    251      parser.add_option("", "--tm", dest="tm", default=None,
    252          help="The file to use as translation memory when fuzzy matching")
    253      parser.passthrough.append("tm")
    254      defaultsimilarity = 75
    255      parser.add_option("-s", "--similarity", dest="min_similarity", default=defaultsimilarity,
    256          type="float", help="The minimum similarity for inclusion (default: %d%%)" % defaultsimilarity)
    257      parser.passthrough.append("min_similarity")
    258      parser.add_option("--nofuzzymatching", dest="fuzzymatching", action="store_false",
    259          default=True, help="Disable fuzzy matching")
    260      parser.passthrough.append("fuzzymatching")
    261      parser.run(argv)
    262
    263
    264  if __name__ == '__main__':
    265      main()
    266