195640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org#!/usr/bin/env python
295640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org# Copyright (c) 2012 The Chromium Authors. All rights reserved.
301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# Use of this source code is governed by a BSD-style license that can be
401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# found in the LICENSE file.
501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''The 'grit transl2tc' tool.
701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''
801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import grd_reader
1101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import util
12b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.orgfrom grit.tool import interface
13b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.orgfrom grit.tool import rc2grd
1401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit.extern import tclib
1601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass TranslationToTc(interface.Tool):
1901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''A tool for importing existing translations in RC format into the
2001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgTranslation Console.
2101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgUsage:
2301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orggrit -i GRD transl2tc [-l LIMITS] [RCOPTS] SOURCE_RC TRANSLATED_RC OUT_FILE
2501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgThe tool needs a "source" RC file, i.e. in English, and an RC file that is a
2701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgtranslation of precisely the source RC file (not of an older or newer version).
2801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgThe tool also requires you to provide a .grd file (input file) e.g. using the
3001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org-i global option or the GRIT_INPUT environment variable.  The tool uses
3101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orginformation from your .grd file to correct placeholder names in the
3201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgtranslations and ensure that only translatable items and translations still
3301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgbeing used are output.
3401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
3501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgThis tool will accept all the same RCOPTS as the 'grit rc2grd' tool.  To get
3601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orga list of these options, run 'grit help rc2grd'.
3701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
3801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgAdditionally, you can use the -l option (which must be the first option to the
3901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgtool) to specify a file containing a list of message IDs to which output should
4001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgbe limited.  This is only useful if you are limiting the output to your XMB
4101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfiles using the 'grit xmb' tool's -l option.  See 'grit help xmb' for how to
4201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orggenerate a file containing a list of the message IDs in an XMB file.
4301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
4401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgThe tool will scan through both of the RC files as well as any HTML files they
4501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgrefer to, and match together the source messages and translated messages.  It
4601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgwill output a file (OUTPUT_FILE) you can import directly into the TC using the
4701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgBulk Translation Upload tool.
4801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''
4901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
5001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def ShortDescription(self):
5101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return 'Import existing translations in RC format into the TC'
5201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
5301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def Setup(self, globopt, args):
5401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Sets the instance up for use.
5501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
5601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.SetOptions(globopt)
5701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.rc2grd = rc2grd.Rc2Grd()
5801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.rc2grd.SetOptions(globopt)
5901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.limits = None
6001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if len(args) and args[0] == '-l':
61b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org      self.limits = util.ReadFile(args[1], util.RAW_TEXT).split('\n')
6201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      args = args[2:]
6301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.rc2grd.ParseOptions(args)
6401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
6501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def Run(self, globopt, args):
6601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    args = self.Setup(globopt, args)
6701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
6801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if len(args) != 3:
6901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.Out('This tool takes exactly three arguments:\n'
7001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org             '  1. The path to the original RC file\n'
7101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org             '  2. The path to the translated RC file\n'
7201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org             '  3. The output file path.\n')
7301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      return 2
7401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
7501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    grd = grd_reader.Parse(self.o.input, debug=self.o.extra_verbose)
76ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    grd.RunGatherers()
7701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
78b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org    source_rc = util.ReadFile(args[0], self.rc2grd.input_encoding)
79b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org    transl_rc = util.ReadFile(args[1], self.rc2grd.input_encoding)
8001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    translations = self.ExtractTranslations(grd,
81b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org                                            source_rc, args[0],
82b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org                                            transl_rc, args[1])
8301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
84b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org    with util.WrapOutputStream(open(args[2], 'w')) as output_file:
85b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org      self.WriteTranslations(output_file, translations.items())
8601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
8701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.Out('Wrote output file %s' % args[2])
8801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
89ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  def ExtractTranslations(self, current_grd, source_rc, source_path,
90ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org                                             transl_rc, transl_path):
9101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Extracts translations from the translated RC file, matching them with
9201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    translations in the source RC file to calculate their ID, and correcting
9301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    placeholders, limiting output to translateables, etc. using the supplied
9401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    .grd file which is the current .grd file for your project.
9501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
9601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    If this object's 'limits' attribute is not None but a list, the output of
9701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    this function will be further limited to include only messages that have
9801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    message IDs in the 'limits' list.
9901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
10001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Args:
101ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      current_grd: grit.node.base.Node child, that has had RunGatherers() run
102ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org                   on it
10301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      source_rc: Complete text of source RC file
10401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      source_path: Path to the source RC file
10501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      transl_rc: Complete text of translated RC file
10601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      transl_path: Path to the translated RC file
10701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
10801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Return:
10901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      { id1 : text1, '12345678' : 'Hello USERNAME, howzit?' }
11001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
11101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    source_grd = self.rc2grd.Process(source_rc, source_path)
11201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.VerboseOut('Read %s into GRIT format, running gatherers.\n' % source_path)
113783385ad40d453977d8b9d537e204f851e21591abenrg@chromium.org    source_grd.SetOutputLanguage(current_grd.output_language)
114783385ad40d453977d8b9d537e204f851e21591abenrg@chromium.org    source_grd.SetDefines(current_grd.defines)
115ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    source_grd.RunGatherers(debug=self.o.extra_verbose)
11601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    transl_grd = self.rc2grd.Process(transl_rc, transl_path)
117783385ad40d453977d8b9d537e204f851e21591abenrg@chromium.org    transl_grd.SetOutputLanguage(current_grd.output_language)
118783385ad40d453977d8b9d537e204f851e21591abenrg@chromium.org    transl_grd.SetDefines(current_grd.defines)
11901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.VerboseOut('Read %s into GRIT format, running gatherers.\n' % transl_path)
120ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    transl_grd.RunGatherers(debug=self.o.extra_verbose)
12101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.VerboseOut('Done running gatherers for %s.\n' % transl_path)
12201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
12301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Proceed to create a map from ID to translation, getting the ID from the
12401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # source GRD and the translation from the translated GRD.
12501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    id2transl = {}
12601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for source_node in source_grd:
12701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      source_cliques = source_node.GetCliques()
12801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if not len(source_cliques):
12901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        continue
13001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
13101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      assert 'name' in source_node.attrs, 'All nodes with cliques should have an ID'
13201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      node_id = source_node.attrs['name']
13301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.ExtraVerboseOut('Processing node %s\n' % node_id)
13401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      transl_node = transl_grd.GetNodeById(node_id)
13501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
13601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if transl_node:
13701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        transl_cliques = transl_node.GetCliques()
13801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if not len(transl_cliques) == len(source_cliques):
13901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          self.Out(
14001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            'Warning: Translation for %s has wrong # of cliques, skipping.\n' %
14101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            node_id)
14201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          continue
14301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
14401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        self.Out('Warning: No translation for %s, skipping.\n' % node_id)
14501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        continue
14601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
14701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if source_node.name == 'message':
14801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # Fixup placeholders as well as possible based on information from
14901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # the current .grd file if they are 'TODO_XXXX' placeholders.  We need
15001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # to fixup placeholders in the translated message so that it looks right
15101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # and we also need to fixup placeholders in the source message so that
15201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # its calculated ID will match the current message.
15301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        current_node = current_grd.GetNodeById(node_id)
15401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if current_node:
155ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org          assert len(source_cliques) == len(current_node.GetCliques()) == 1
15601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
15701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          source_msg = source_cliques[0].GetMessage()
15801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          current_msg = current_node.GetCliques()[0].GetMessage()
15901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
16001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          # Only do this for messages whose source version has not changed.
16101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          if (source_msg.GetRealContent() != current_msg.GetRealContent()):
16201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            self.VerboseOut('Info: Message %s has changed; skipping\n' % node_id)
16301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          else:
16401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            transl_msg = transl_cliques[0].GetMessage()
16501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            transl_content = transl_msg.GetContent()
16601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            current_content = current_msg.GetContent()
16701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            source_content = source_msg.GetContent()
16801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
16901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            ok_to_fixup = True
17001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            if (len(transl_content) != len(current_content)):
17101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org              # message structure of translation is different, don't try fixup
17201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org              ok_to_fixup = False
17301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            if ok_to_fixup:
17401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org              for ix in range(len(transl_content)):
17501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                if isinstance(transl_content[ix], tclib.Placeholder):
17601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                  if not isinstance(current_content[ix], tclib.Placeholder):
17701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                    ok_to_fixup = False  # structure has changed
17801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                    break
17901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                  if (transl_content[ix].GetOriginal() !=
18001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                      current_content[ix].GetOriginal()):
18101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                    ok_to_fixup = False  # placeholders have likely been reordered
18201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                    break
18301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                else:  # translated part is not a placeholder but a string
18401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                  if isinstance(current_content[ix], tclib.Placeholder):
18501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                    ok_to_fixup = False  # placeholders have likely been reordered
18601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                    break
18701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
18801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            if not ok_to_fixup:
18901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org              self.VerboseOut(
19001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                'Info: Structure of message %s has changed; skipping.\n' % node_id)
19101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            else:
19201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org              def Fixup(content, ix):
19301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                if (isinstance(content[ix], tclib.Placeholder) and
19401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                    content[ix].GetPresentation().startswith('TODO_')):
19501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                  assert isinstance(current_content[ix], tclib.Placeholder)
19601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                  # Get the placeholder ID and example from the current message
19701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                  content[ix] = current_content[ix]
19801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org              for ix in range(len(transl_content)):
19901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                Fixup(transl_content, ix)
20001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                Fixup(source_content, ix)
20101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
20201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # Only put each translation once into the map.  Warn if translations
20301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # for the same message are different.
20401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      for ix in range(len(transl_cliques)):
20501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        source_msg = source_cliques[ix].GetMessage()
20601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        source_msg.GenerateId()  # needed to refresh ID based on new placeholders
20701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        message_id = source_msg.GetId()
20801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        translated_content = transl_cliques[ix].GetMessage().GetPresentableContent()
20901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
21001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if message_id in id2transl:
21101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          existing_translation = id2transl[message_id]
21201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          if existing_translation != translated_content:
21301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            original_text = source_cliques[ix].GetMessage().GetPresentableContent()
21401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            self.Out('Warning: Two different translations for "%s":\n'
21501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                   '  Translation 1: "%s"\n'
21601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                   '  Translation 2: "%s"\n' %
21701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                   (original_text, existing_translation, translated_content))
21801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        else:
21901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          id2transl[message_id] = translated_content
22001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
22101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Remove translations for messages that do not occur in the current .grd
22201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # or have been marked as not translateable, or do not occur in the 'limits'
22301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # list (if it has been set).
22401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    current_message_ids = current_grd.UberClique().AllMessageIds()
22501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for message_id in id2transl.keys():
22601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if (message_id not in current_message_ids or
22701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          not current_grd.UberClique().BestClique(message_id).IsTranslateable() or
22801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          (self.limits and message_id not in self.limits)):
22901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        del id2transl[message_id]
23001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
23101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return id2transl
23201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
233b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org  @staticmethod
23401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def WriteTranslations(output_file, translations):
23501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Writes the provided list of translations to the provided output file
23601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    in the format used by the TC's Bulk Translation Upload tool.  The file
23701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    must be UTF-8 encoded.
23801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
23901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Args:
240b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org      output_file: util.WrapOutputStream(open('bingo.out', 'w'))
24101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      translations: [ [id1, text1], ['12345678', 'Hello USERNAME, howzit?'] ]
24201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
24301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Return:
24401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      None
24501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
24601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for id, text in translations:
24701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      text = text.replace('<', '&lt;').replace('>', '&gt;')
24801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      output_file.write(id)
24901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      output_file.write(' ')
25001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      output_file.write(text)
25101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      output_file.write('\n')
25201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
253