OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python2.4 |
| 2 # Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 '''The 'grit transl2tc' tool. |
| 7 ''' |
| 8 |
| 9 |
| 10 import getopt |
| 11 |
| 12 from grit.tool import interface |
| 13 from grit.tool import rc2grd |
| 14 from grit import grd_reader |
| 15 from grit import util |
| 16 |
| 17 from grit.extern import tclib |
| 18 |
| 19 |
| 20 class TranslationToTc(interface.Tool): |
| 21 '''A tool for importing existing translations in RC format into the |
| 22 Translation Console. |
| 23 |
| 24 Usage: |
| 25 |
| 26 grit -i GRD transl2tc [-l LIMITS] [RCOPTS] SOURCE_RC TRANSLATED_RC OUT_FILE |
| 27 |
| 28 The tool needs a "source" RC file, i.e. in English, and an RC file that is a |
| 29 translation of precisely the source RC file (not of an older or newer version). |
| 30 |
| 31 The tool also requires you to provide a .grd file (input file) e.g. using the |
| 32 -i global option or the GRIT_INPUT environment variable. The tool uses |
| 33 information from your .grd file to correct placeholder names in the |
| 34 translations and ensure that only translatable items and translations still |
| 35 being used are output. |
| 36 |
| 37 This tool will accept all the same RCOPTS as the 'grit rc2grd' tool. To get |
| 38 a list of these options, run 'grit help rc2grd'. |
| 39 |
| 40 Additionally, you can use the -l option (which must be the first option to the |
| 41 tool) to specify a file containing a list of message IDs to which output should |
| 42 be limited. This is only useful if you are limiting the output to your XMB |
| 43 files using the 'grit xmb' tool's -l option. See 'grit help xmb' for how to |
| 44 generate a file containing a list of the message IDs in an XMB file. |
| 45 |
| 46 The tool will scan through both of the RC files as well as any HTML files they |
| 47 refer to, and match together the source messages and translated messages. It |
| 48 will output a file (OUTPUT_FILE) you can import directly into the TC using the |
| 49 Bulk Translation Upload tool. |
| 50 ''' |
| 51 |
| 52 def ShortDescription(self): |
| 53 return 'Import existing translations in RC format into the TC' |
| 54 |
| 55 def Setup(self, globopt, args): |
| 56 '''Sets the instance up for use. |
| 57 ''' |
| 58 self.SetOptions(globopt) |
| 59 self.rc2grd = rc2grd.Rc2Grd() |
| 60 self.rc2grd.SetOptions(globopt) |
| 61 self.limits = None |
| 62 if len(args) and args[0] == '-l': |
| 63 limit_file = file(args[1]) |
| 64 self.limits = limit_file.read().split('\n') |
| 65 limit_file.close() |
| 66 args = args[2:] |
| 67 return self.rc2grd.ParseOptions(args) |
| 68 |
| 69 def Run(self, globopt, args): |
| 70 args = self.Setup(globopt, args) |
| 71 |
| 72 if len(args) != 3: |
| 73 self.Out('This tool takes exactly three arguments:\n' |
| 74 ' 1. The path to the original RC file\n' |
| 75 ' 2. The path to the translated RC file\n' |
| 76 ' 3. The output file path.\n') |
| 77 return 2 |
| 78 |
| 79 grd = grd_reader.Parse(self.o.input, debug=self.o.extra_verbose) |
| 80 grd.RunGatherers(recursive = True) |
| 81 |
| 82 source_rc = util.WrapInputStream(file(args[0], 'r'), self.rc2grd.input_encod
ing) |
| 83 transl_rc = util.WrapInputStream(file(args[1], 'r'), self.rc2grd.input_encod
ing) |
| 84 translations = self.ExtractTranslations(grd, |
| 85 source_rc.read(), args[0], |
| 86 transl_rc.read(), args[1]) |
| 87 transl_rc.close() |
| 88 source_rc.close() |
| 89 |
| 90 output_file = util.WrapOutputStream(file(args[2], 'w')) |
| 91 self.WriteTranslations(output_file, translations.items()) |
| 92 output_file.close() |
| 93 |
| 94 self.Out('Wrote output file %s' % args[2]) |
| 95 |
| 96 def ExtractTranslations(self, current_grd, source_rc, source_path, transl_rc,
transl_path): |
| 97 '''Extracts translations from the translated RC file, matching them with |
| 98 translations in the source RC file to calculate their ID, and correcting |
| 99 placeholders, limiting output to translateables, etc. using the supplied |
| 100 .grd file which is the current .grd file for your project. |
| 101 |
| 102 If this object's 'limits' attribute is not None but a list, the output of |
| 103 this function will be further limited to include only messages that have |
| 104 message IDs in the 'limits' list. |
| 105 |
| 106 Args: |
| 107 current_grd: grit.node.base.Node child, that has had RunGatherers(True) ru
n on it |
| 108 source_rc: Complete text of source RC file |
| 109 source_path: Path to the source RC file |
| 110 transl_rc: Complete text of translated RC file |
| 111 transl_path: Path to the translated RC file |
| 112 |
| 113 Return: |
| 114 { id1 : text1, '12345678' : 'Hello USERNAME, howzit?' } |
| 115 ''' |
| 116 source_grd = self.rc2grd.Process(source_rc, source_path) |
| 117 self.VerboseOut('Read %s into GRIT format, running gatherers.\n' % source_pa
th) |
| 118 source_grd.RunGatherers(recursive=True, debug=self.o.extra_verbose) |
| 119 transl_grd = self.rc2grd.Process(transl_rc, transl_path) |
| 120 self.VerboseOut('Read %s into GRIT format, running gatherers.\n' % transl_pa
th) |
| 121 transl_grd.RunGatherers(recursive=True, debug=self.o.extra_verbose) |
| 122 self.VerboseOut('Done running gatherers for %s.\n' % transl_path) |
| 123 |
| 124 # Proceed to create a map from ID to translation, getting the ID from the |
| 125 # source GRD and the translation from the translated GRD. |
| 126 id2transl = {} |
| 127 for source_node in source_grd: |
| 128 source_cliques = source_node.GetCliques() |
| 129 if not len(source_cliques): |
| 130 continue |
| 131 |
| 132 assert 'name' in source_node.attrs, 'All nodes with cliques should have an
ID' |
| 133 node_id = source_node.attrs['name'] |
| 134 self.ExtraVerboseOut('Processing node %s\n' % node_id) |
| 135 transl_node = transl_grd.GetNodeById(node_id) |
| 136 |
| 137 if transl_node: |
| 138 transl_cliques = transl_node.GetCliques() |
| 139 if not len(transl_cliques) == len(source_cliques): |
| 140 self.Out( |
| 141 'Warning: Translation for %s has wrong # of cliques, skipping.\n' % |
| 142 node_id) |
| 143 continue |
| 144 else: |
| 145 self.Out('Warning: No translation for %s, skipping.\n' % node_id) |
| 146 continue |
| 147 |
| 148 if source_node.name == 'message': |
| 149 # Fixup placeholders as well as possible based on information from |
| 150 # the current .grd file if they are 'TODO_XXXX' placeholders. We need |
| 151 # to fixup placeholders in the translated message so that it looks right |
| 152 # and we also need to fixup placeholders in the source message so that |
| 153 # its calculated ID will match the current message. |
| 154 current_node = current_grd.GetNodeById(node_id) |
| 155 if current_node: |
| 156 assert len(source_cliques) == 1 and len(current_node.GetCliques()) ==
1 |
| 157 |
| 158 source_msg = source_cliques[0].GetMessage() |
| 159 current_msg = current_node.GetCliques()[0].GetMessage() |
| 160 |
| 161 # Only do this for messages whose source version has not changed. |
| 162 if (source_msg.GetRealContent() != current_msg.GetRealContent()): |
| 163 self.VerboseOut('Info: Message %s has changed; skipping\n' % node_id
) |
| 164 else: |
| 165 transl_msg = transl_cliques[0].GetMessage() |
| 166 transl_content = transl_msg.GetContent() |
| 167 current_content = current_msg.GetContent() |
| 168 source_content = source_msg.GetContent() |
| 169 |
| 170 ok_to_fixup = True |
| 171 if (len(transl_content) != len(current_content)): |
| 172 # message structure of translation is different, don't try fixup |
| 173 ok_to_fixup = False |
| 174 if ok_to_fixup: |
| 175 for ix in range(len(transl_content)): |
| 176 if isinstance(transl_content[ix], tclib.Placeholder): |
| 177 if not isinstance(current_content[ix], tclib.Placeholder): |
| 178 ok_to_fixup = False # structure has changed |
| 179 break |
| 180 if (transl_content[ix].GetOriginal() != |
| 181 current_content[ix].GetOriginal()): |
| 182 ok_to_fixup = False # placeholders have likely been reorder
ed |
| 183 break |
| 184 else: # translated part is not a placeholder but a string |
| 185 if isinstance(current_content[ix], tclib.Placeholder): |
| 186 ok_to_fixup = False # placeholders have likely been reorder
ed |
| 187 break |
| 188 |
| 189 if not ok_to_fixup: |
| 190 self.VerboseOut( |
| 191 'Info: Structure of message %s has changed; skipping.\n' % node_
id) |
| 192 else: |
| 193 def Fixup(content, ix): |
| 194 if (isinstance(content[ix], tclib.Placeholder) and |
| 195 content[ix].GetPresentation().startswith('TODO_')): |
| 196 assert isinstance(current_content[ix], tclib.Placeholder) |
| 197 # Get the placeholder ID and example from the current message |
| 198 content[ix] = current_content[ix] |
| 199 for ix in range(len(transl_content)): |
| 200 Fixup(transl_content, ix) |
| 201 Fixup(source_content, ix) |
| 202 |
| 203 # Only put each translation once into the map. Warn if translations |
| 204 # for the same message are different. |
| 205 for ix in range(len(transl_cliques)): |
| 206 source_msg = source_cliques[ix].GetMessage() |
| 207 source_msg.GenerateId() # needed to refresh ID based on new placeholder
s |
| 208 message_id = source_msg.GetId() |
| 209 translated_content = transl_cliques[ix].GetMessage().GetPresentableConte
nt() |
| 210 |
| 211 if message_id in id2transl: |
| 212 existing_translation = id2transl[message_id] |
| 213 if existing_translation != translated_content: |
| 214 original_text = source_cliques[ix].GetMessage().GetPresentableConten
t() |
| 215 self.Out('Warning: Two different translations for "%s":\n' |
| 216 ' Translation 1: "%s"\n' |
| 217 ' Translation 2: "%s"\n' % |
| 218 (original_text, existing_translation, translated_content)) |
| 219 else: |
| 220 id2transl[message_id] = translated_content |
| 221 |
| 222 # Remove translations for messages that do not occur in the current .grd |
| 223 # or have been marked as not translateable, or do not occur in the 'limits' |
| 224 # list (if it has been set). |
| 225 current_message_ids = current_grd.UberClique().AllMessageIds() |
| 226 for message_id in id2transl.keys(): |
| 227 if (message_id not in current_message_ids or |
| 228 not current_grd.UberClique().BestClique(message_id).IsTranslateable()
or |
| 229 (self.limits and message_id not in self.limits)): |
| 230 del id2transl[message_id] |
| 231 |
| 232 return id2transl |
| 233 |
| 234 # static method |
| 235 def WriteTranslations(output_file, translations): |
| 236 '''Writes the provided list of translations to the provided output file |
| 237 in the format used by the TC's Bulk Translation Upload tool. The file |
| 238 must be UTF-8 encoded. |
| 239 |
| 240 Args: |
| 241 output_file: util.WrapOutputStream(file('bingo.out', 'w')) |
| 242 translations: [ [id1, text1], ['12345678', 'Hello USERNAME, howzit?'] ] |
| 243 |
| 244 Return: |
| 245 None |
| 246 ''' |
| 247 for id, text in translations: |
| 248 text = text.replace('<', '<').replace('>', '>') |
| 249 output_file.write(id) |
| 250 output_file.write(' ') |
| 251 output_file.write(text) |
| 252 output_file.write('\n') |
| 253 WriteTranslations = staticmethod(WriteTranslations) |
| 254 |
OLD | NEW |