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