translate.convert.pot2po

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-2010 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=None, classes_str=factory.classes_str, **kwargs):
     37      """Main conversion function"""
     38
     39      input_store = factory.getobject(input_file, classes=classes, classes_str=classes_str)
     40      try:
     41          temp_store = factory.getobject(input_file, classes_str=classes_str)
     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_str=classes_str)
     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 _unit_post_merge_pounit(input_unit, input_store, output_store, template_store):
    141      """PO format specific plural string initializtion logic."""
    142      #FIXME: do we want to do that for poxliff also?
    143      if input_unit.hasplural() and len(input_unit.target) == 0:
    144          # untranslated plural unit; Let's ensure that we have the correct number of plural forms:
    145          nplurals, plural = output_store.getheaderplural()
    146          if nplurals and nplurals.isdigit() and nplurals != '2':
    147              input_unit.target = multistring([""] * int(nplurals))
    148
    149
    150 -def _store_post_merge_pofile(input_store, output_store, template_store):
    151      """PO format specific: adds newly obsoleted messages to end of store."""
    152      #Let's take care of obsoleted messages
    153      if template_store:
    154          newlyobsoleted = []
    155          for unit in template_store.units:
    156              if unit.isheader() or unit.isblank():
    157                  continue
    158              if unit.target and not (input_store.findid(unit.getid()) or hasattr(unit, "reused")):
    159                  #not in .pot, make it obsolete
    160                  unit.makeobsolete()
    161                  newlyobsoleted.append(unit)
    162          for unit in newlyobsoleted:
    163              output_store.addunit(unit)
    164
    165
    166 -def _do_poheaders(input_store, output_store, template_store):
    167      """Adds initialized PO headers to output store."""
    168      # header values
    169      charset = "UTF-8"
    170      encoding = "8bit"
    171      project_id_version = None
    172      pot_creation_date = None
    173      po_revision_date = None
    174      last_translator = None
    175      language_team = None
    176      mime_version = None
    177      plural_forms = None
    178      kwargs = {}
    179
    180      if template_store is not None and isinstance(template_store, poheader.poheader):
    181          templateheadervalues = template_store.parseheader()
    182          for key, value in templateheadervalues.iteritems():
    183              if key == "Project-Id-Version":
    184                  project_id_version = value
    185              elif key == "Last-Translator":
    186                  last_translator = value
    187              elif key == "Language-Team":
    188                  language_team = value
    189              elif key == "PO-Revision-Date":
    190                  po_revision_date = value
    191              elif key in ("POT-Creation-Date", "MIME-Version"):
    192                  # don't know how to handle these keys, or ignoring them
    193                  pass
    194              elif key == "Content-Type":
    195                  kwargs[key] = value
    196              elif key == "Content-Transfer-Encoding":
    197                  encoding = value
    198              elif key == "Plural-Forms":
    199                  plural_forms = value
    200              else:
    201                  kwargs[key] = value
    202
    203      inputheadervalues = input_store.parseheader()
    204      for key, value in inputheadervalues.iteritems():
    205          if key in ("Project-Id-Version", "Last-Translator", "Language-Team", "PO-Revision-Date", "Content-Type", "Content-Transfer-Encoding", "Plural-Forms"):
    206              # want to carry these from the template so we ignore them
    207              pass
    208          elif key == "POT-Creation-Date":
    209              pot_creation_date = value
    210          elif key == "MIME-Version":
    211              mime_version = value
    212          else:
    213              kwargs[key] = value
    214
    215      output_header = output_store.init_headers(charset=charset, encoding=encoding, project_id_version=project_id_version,
    216          pot_creation_date=pot_creation_date, po_revision_date=po_revision_date, last_translator=last_translator,
    217          language_team=language_team, mime_version=mime_version, plural_forms=plural_forms, **kwargs)
    218
    219      # Get the header comments and fuzziness state
    220      # override some values from input file
    221      if template_store is not None:
    222          template_header = template_store.header()
    223          if template_header is not None:
    224              if template_header.getnotes("translator"):
    225                  output_header.addnote(template_header.getnotes("translator"), "translator", position="replace")
    226              output_header.markfuzzy(template_header.isfuzzy())
    227
    228
    229 -def main(argv=None):
    230      from translate.convert import convert
    231      formats = {"pot": ("po", convertpot), ("pot", "po"): ("po", convertpot),
    232                 "xlf": ("xlf", convertpot), ("xlf", "xlf"): ("xlf", convertpot),
    233                 "ts": ("ts", convertpot), ("ts", "ts"): ("ts", convertpot),
    234                 "catkeys": ("catkeys", convertpot), ("catkeys", "catkeys"): ("catkeys", convertpot),
    235              }
    236      parser = convert.ConvertOptionParser(formats, usepots=True, usetemplates=True,
    237          allowmissingtemplate=True, description=__doc__)
    238      parser.add_option("", "--tm", dest="tm", default=None,
    239          help="The file to use as translation memory when fuzzy matching")
    240      parser.passthrough.append("tm")
    241      defaultsimilarity = 75
    242      parser.add_option("-s", "--similarity", dest="min_similarity", default=defaultsimilarity,
    243          type="float", help="The minimum similarity for inclusion (default: %d%%)" % defaultsimilarity)
    244      parser.passthrough.append("min_similarity")
    245      parser.add_option("--nofuzzymatching", dest="fuzzymatching", action="store_false",
    246          default=True, help="Disable fuzzy matching")
    247      parser.passthrough.append("fuzzymatching")
    248      parser.run(argv)
    249
    250
    251  if __name__ == '__main__':
    252      main()
    253