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