OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Generates .h and .rc files for installer strings. Run "python | 6 """Generates .h and .rc files for installer strings. Run "python |
7 create_string_rc.py" for usage details. | 7 create_string_rc.py" for usage details. |
8 | 8 |
9 This script generates an rc file and header (NAME.{rc,h}) to be included in | 9 This script generates an rc file and header (NAME.{rc,h}) to be included in |
10 setup.exe. The rc file includes translations for strings pulled from the given | 10 setup.exe. The rc file includes translations for strings pulled from the given |
(...skipping 12 matching lines...) Expand all Loading... | |
23 #define IDS_MY_STRING_AR 1600 | 23 #define IDS_MY_STRING_AR 1600 |
24 #define IDS_MY_STRING_BG 1601 | 24 #define IDS_MY_STRING_BG 1601 |
25 ... | 25 ... |
26 #define IDS_MY_STRING_BASE IDS_MY_STRING_AR | 26 #define IDS_MY_STRING_BASE IDS_MY_STRING_AR |
27 | 27 |
28 This allows us to lookup an an ID for a string by adding IDS_MY_STRING_BASE and | 28 This allows us to lookup an an ID for a string by adding IDS_MY_STRING_BASE and |
29 IDS_L10N_OFFSET_* for the language we are interested in. | 29 IDS_L10N_OFFSET_* for the language we are interested in. |
30 """ | 30 """ |
31 | 31 |
32 import argparse | 32 import argparse |
33 import exceptions | |
33 import glob | 34 import glob |
34 import io | 35 import io |
35 import os | 36 import os |
36 import sys | 37 import sys |
37 from xml import sax | 38 from xml import sax |
38 | 39 |
39 BASEDIR = os.path.dirname(os.path.abspath(__file__)) | 40 BASEDIR = os.path.dirname(os.path.abspath(__file__)) |
40 sys.path.append(os.path.join(BASEDIR, '../../../../tools/grit')) | 41 sys.path.append(os.path.join(BASEDIR, '../../../../tools/grit')) |
41 sys.path.append(os.path.join(BASEDIR, '../../../../tools/python')) | 42 sys.path.append(os.path.join(BASEDIR, '../../../../tools/python')) |
42 | 43 |
43 from grit.extern import tclib | 44 from grit.extern import tclib |
44 | 45 |
45 # The IDs of strings we want to import from the .grd files and include in | 46 # The IDs of strings we want to import from the .grd files and include in |
46 # setup.exe's resources. | 47 # setup.exe's resources. |
47 STRING_IDS = [ | 48 STRING_IDS = [ |
48 'IDS_PRODUCT_NAME', | |
49 'IDS_SXS_SHORTCUT_NAME', | |
50 'IDS_PRODUCT_DESCRIPTION', | |
51 'IDS_ABOUT_VERSION_COMPANY_NAME', | 49 'IDS_ABOUT_VERSION_COMPANY_NAME', |
50 'IDS_APP_SHORTCUTS_SUBDIR_NAME', | |
51 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY', | |
52 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', | |
53 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY', | |
54 'IDS_INBOUND_MDNS_RULE_NAME', | |
55 'IDS_INBOUND_MDNS_RULE_NAME_CANARY', | |
56 'IDS_INSTALL_EXISTING_VERSION_LAUNCHED', | |
57 'IDS_INSTALL_FAILED', | |
52 'IDS_INSTALL_HIGHER_VERSION', | 58 'IDS_INSTALL_HIGHER_VERSION', |
53 'IDS_INSTALL_FAILED', | 59 'IDS_INSTALL_INSUFFICIENT_RIGHTS', |
54 'IDS_SAME_VERSION_REPAIR_FAILED', | 60 'IDS_INSTALL_INVALID_ARCHIVE', |
55 'IDS_SETUP_PATCH_FAILED', | 61 'IDS_INSTALL_OS_ERROR', |
56 'IDS_INSTALL_OS_NOT_SUPPORTED', | 62 'IDS_INSTALL_OS_NOT_SUPPORTED', |
57 'IDS_INSTALL_OS_ERROR', | |
58 'IDS_INSTALL_SINGLETON_ACQUISITION_FAILED', | 63 'IDS_INSTALL_SINGLETON_ACQUISITION_FAILED', |
59 'IDS_INSTALL_TEMP_DIR_FAILED', | 64 'IDS_INSTALL_TEMP_DIR_FAILED', |
60 'IDS_INSTALL_UNCOMPRESSION_FAILED', | 65 'IDS_INSTALL_UNCOMPRESSION_FAILED', |
61 'IDS_INSTALL_INVALID_ARCHIVE', | 66 'IDS_PRODUCT_DESCRIPTION', |
62 'IDS_INSTALL_INSUFFICIENT_RIGHTS', | 67 'IDS_PRODUCT_NAME', |
68 'IDS_SAME_VERSION_REPAIR_FAILED', | |
69 'IDS_SETUP_PATCH_FAILED', | |
70 'IDS_SHORTCUT_NEW_WINDOW', | |
63 'IDS_SHORTCUT_TOOLTIP', | 71 'IDS_SHORTCUT_TOOLTIP', |
64 'IDS_SHORTCUT_NEW_WINDOW', | 72 'IDS_SXS_SHORTCUT_NAME', |
65 'IDS_APP_SHORTCUTS_SUBDIR_NAME', | |
66 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY', | |
67 'IDS_INBOUND_MDNS_RULE_NAME', | |
68 'IDS_INBOUND_MDNS_RULE_NAME_CANARY', | |
69 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', | |
70 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY', | |
71 'IDS_INSTALL_EXISTING_VERSION_LAUNCHED', | |
72 ] | 73 ] |
73 | 74 |
75 # Certain strings are conditional on a brand's install mode (see | |
76 # chrome/install_static/install_modes.h for details). This allows | |
77 # installer::GetLocalizedString to return a resource specific to the current | |
78 # install mode at runtime (e.g., "Google Chrome SxS" as IDS_SHORTCUT_NAME for | |
79 # the localized shortcut name for Google Chrome's canary channel). This mapping | |
80 # provides brand- and mode-specific string ids for a given input id as described | |
81 # here: | |
82 # { | |
83 # resource_id_1: { # A resource ID for use with GetLocalizedString. | |
84 # brand: [ # 'google_chrome', for example. | |
85 # string_id_1, # Strings listed in order of the brand's modes, as | |
86 # string_id_2, # specified in install_static::InstallConstantIndex. | |
87 # ... | |
88 # string_id_N, | |
89 # ], | |
90 # }, | |
91 # resource_id_2: ... | |
92 # } | |
93 # 'resource_id_1' names an existing string ID. All calls to | |
94 # installer::GetLocalizedString with this string ID will map to the | |
95 # mode-specific string. | |
96 # | |
97 # Note: Update the test expectations in GetBaseMessageIdForMode.GoogleStringIds | |
98 # when adding to/modifying this structure. | |
99 MODE_SPECIFIC_STRINGS = { | |
100 'IDS_APP_SHORTCUTS_SUBDIR_NAME': { | |
101 'google_chrome': [ | |
102 'IDS_APP_SHORTCUTS_SUBDIR_NAME', | |
103 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY', | |
104 ], | |
105 'chromium': [ | |
106 'IDS_APP_SHORTCUTS_SUBDIR_NAME', | |
107 ], | |
108 }, | |
109 'IDS_INBOUND_MDNS_RULE_DESCRIPTION': { | |
110 'google_chrome': [ | |
111 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', | |
112 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY', | |
113 ], | |
114 'chromium': [ | |
115 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', | |
116 ], | |
117 }, | |
118 'IDS_INBOUND_MDNS_RULE_NAME': { | |
119 'google_chrome': [ | |
120 'IDS_INBOUND_MDNS_RULE_NAME', | |
121 'IDS_INBOUND_MDNS_RULE_NAME_CANARY', | |
122 ], | |
123 'chromium': [ | |
124 'IDS_INBOUND_MDNS_RULE_NAME', | |
125 ], | |
126 }, | |
127 'IDS_PRODUCT_NAME': { | |
manzagop (departed)
2017/04/03 13:06:33
May be a good idea to also call attention here to
grt (UTC plus 2)
2017/04/03 13:31:30
Done. I also added a bit more text to the comment
| |
128 'google_chrome': [ | |
129 'IDS_PRODUCT_NAME', | |
130 'IDS_SXS_SHORTCUT_NAME', | |
131 ], | |
132 'chromium': [ | |
133 'IDS_PRODUCT_NAME', | |
134 ], | |
135 }, | |
136 } | |
137 # Note: Update the test expectations in GetBaseMessageIdForMode.GoogleStringIds | |
138 # when adding to/modifying the above structure. | |
139 | |
74 # The ID of the first resource string. | 140 # The ID of the first resource string. |
75 FIRST_RESOURCE_ID = 1600 | 141 FIRST_RESOURCE_ID = 1600 |
76 | 142 |
77 | 143 |
78 class GrdHandler(sax.handler.ContentHandler): | 144 class GrdHandler(sax.handler.ContentHandler): |
79 """Extracts selected strings from a .grd file. | 145 """Extracts selected strings from a .grd file. |
80 | 146 |
81 Attributes: | 147 Attributes: |
82 messages: A dict mapping string identifiers to their corresponding messages. | 148 messages: A dict mapping string identifiers to their corresponding messages. |
83 """ | 149 """ |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
208 translated_string = ''.join(self.__text_scraps).strip() | 274 translated_string = ''.join(self.__text_scraps).strip() |
209 for string_id in self.__string_ids: | 275 for string_id in self.__string_ids: |
210 self.translations[string_id] = translated_string | 276 self.translations[string_id] = translated_string |
211 self.__string_ids = None | 277 self.__string_ids = None |
212 self.__text_scraps = [] | 278 self.__text_scraps = [] |
213 self.__characters_callback = None | 279 self.__characters_callback = None |
214 | 280 |
215 | 281 |
216 class StringRcMaker(object): | 282 class StringRcMaker(object): |
217 """Makes .h and .rc files containing strings and translations.""" | 283 """Makes .h and .rc files containing strings and translations.""" |
218 def __init__(self, name, inputs, outdir): | 284 def __init__(self, name, inputs, outdir, brand): |
219 """Constructs a maker. | 285 """Constructs a maker. |
220 | 286 |
221 Args: | 287 Args: |
222 name: The base name of the generated files (e.g., | 288 name: The base name of the generated files (e.g., |
223 'installer_util_strings'). | 289 'installer_util_strings'). |
224 inputs: A list of (grd_file, xtb_dir) pairs containing the source data. | 290 inputs: A list of (grd_file, xtb_dir) pairs containing the source data. |
225 outdir: The directory into which the files will be generated. | 291 outdir: The directory into which the files will be generated. |
226 """ | 292 """ |
227 self.name = name | 293 self.name = name |
228 self.inputs = inputs | 294 self.inputs = inputs |
229 self.outdir = outdir | 295 self.outdir = outdir |
296 self.brand = brand | |
230 | 297 |
231 def MakeFiles(self): | 298 def MakeFiles(self): |
232 translated_strings = self.__ReadSourceAndTranslatedStrings() | 299 translated_strings = self.__ReadSourceAndTranslatedStrings() |
233 self.__WriteRCFile(translated_strings) | 300 self.__WriteRCFile(translated_strings) |
234 self.__WriteHeaderFile(translated_strings) | 301 self.__WriteHeaderFile(translated_strings) |
235 | 302 |
236 class __TranslationData(object): | 303 class __TranslationData(object): |
237 """A container of information about a single translation.""" | 304 """A container of information about a single translation.""" |
238 def __init__(self, resource_id_str, language, translation): | 305 def __init__(self, resource_id_str, language, translation): |
239 self.resource_id_str = resource_id_str | 306 self.resource_id_str = resource_id_str |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
340 escaped_text)) | 407 escaped_text)) |
341 outfile.write(FOOTER_TEXT) | 408 outfile.write(FOOTER_TEXT) |
342 | 409 |
343 def __WriteHeaderFile(self, translated_strings): | 410 def __WriteHeaderFile(self, translated_strings): |
344 """Writes a .h file with resource ids.""" | 411 """Writes a .h file with resource ids.""" |
345 # TODO(grt): Stream the lines to the file rather than building this giant | 412 # TODO(grt): Stream the lines to the file rather than building this giant |
346 # list of lines first. | 413 # list of lines first. |
347 lines = [] | 414 lines = [] |
348 do_languages_lines = ['\n#define DO_LANGUAGES'] | 415 do_languages_lines = ['\n#define DO_LANGUAGES'] |
349 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING'] | 416 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING'] |
417 do_mode_strings_lines = ['\n#define DO_MODE_STRINGS'] | |
350 | 418 |
351 # Write the values for how the languages ids are offset. | 419 # Write the values for how the languages ids are offset. |
352 seen_languages = set() | 420 seen_languages = set() |
353 offset_id = 0 | 421 offset_id = 0 |
354 for translation_data in translated_strings: | 422 for translation_data in translated_strings: |
355 lang = translation_data.language | 423 lang = translation_data.language |
356 if lang not in seen_languages: | 424 if lang not in seen_languages: |
357 seen_languages.add(lang) | 425 seen_languages.add(lang) |
358 lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id)) | 426 lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id)) |
359 do_languages_lines.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)' | 427 do_languages_lines.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)' |
360 % (lang.replace('_', '-').lower(), lang)) | 428 % (lang.replace('_', '-').lower(), lang)) |
361 offset_id += 1 | 429 offset_id += 1 |
362 else: | 430 else: |
363 break | 431 break |
364 | 432 |
365 # Write the resource ids themselves. | 433 # Write the resource ids themselves. |
366 resource_id = FIRST_RESOURCE_ID | 434 resource_id = FIRST_RESOURCE_ID |
367 for translation_data in translated_strings: | 435 for translation_data in translated_strings: |
368 lines.append('#define %s %s' % (translation_data.resource_id_str + '_' + | 436 lines.append('#define %s %s' % (translation_data.resource_id_str + '_' + |
369 translation_data.language, | 437 translation_data.language, |
370 resource_id)) | 438 resource_id)) |
371 resource_id += 1 | 439 resource_id += 1 |
372 | 440 |
441 # Handle mode-specific strings. | |
442 for string_id, brands in MODE_SPECIFIC_STRINGS.iteritems(): | |
443 # Populate the DO_MODE_STRINGS macro. | |
444 brand_strings = brands.get(self.brand) | |
445 if not brand_strings: | |
446 raise exceptions.RuntimeError( | |
447 'No strings declared for brand \'%s\' in MODE_SPECIFIC_STRINGS for ' | |
448 'message %s' % (self.brand, string_id)) | |
449 do_mode_strings_lines.append( | |
450 ' HANDLE_MODE_STRING(%s_BASE, %s)' | |
451 % (string_id, ', '.join([ ('%s_BASE' % s) for s in brand_strings]))) | |
452 | |
373 # Write out base ID values. | 453 # Write out base ID values. |
374 for string_id in STRING_IDS: | 454 for string_id in STRING_IDS: |
375 lines.append('#define %s_BASE %s_%s' % (string_id, | 455 lines.append('#define %s_BASE %s_%s' % (string_id, |
376 string_id, | 456 string_id, |
377 translated_strings[0].language)) | 457 translated_strings[0].language)) |
378 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)' | 458 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)' |
379 % (string_id, string_id)) | 459 % (string_id, string_id)) |
380 | 460 |
381 with open(os.path.join(self.outdir, self.name + '.h'), 'wb') as outfile: | 461 with open(os.path.join(self.outdir, self.name + '.h'), 'wb') as outfile: |
382 outfile.write('\n'.join(lines)) | 462 outfile.write('\n'.join(lines)) |
383 outfile.write('\n#ifndef RC_INVOKED') | 463 outfile.write('\n#ifndef RC_INVOKED') |
384 outfile.write(' \\\n'.join(do_languages_lines)) | 464 outfile.write(' \\\n'.join(do_languages_lines)) |
385 outfile.write(' \\\n'.join(installer_string_mapping_lines)) | 465 outfile.write(' \\\n'.join(installer_string_mapping_lines)) |
466 outfile.write(' \\\n'.join(do_mode_strings_lines)) | |
386 # .rc files must end in a new line | 467 # .rc files must end in a new line |
387 outfile.write('\n#endif // ndef RC_INVOKED\n') | 468 outfile.write('\n#endif // ndef RC_INVOKED\n') |
388 | 469 |
389 | 470 |
390 def ParseCommandLine(): | 471 def ParseCommandLine(): |
391 def GrdPathAndXtbDirPair(string): | 472 def GrdPathAndXtbDirPair(string): |
392 """Returns (grd_path, xtb_dir) given a colon-separated string of the same. | 473 """Returns (grd_path, xtb_dir) given a colon-separated string of the same. |
393 """ | 474 """ |
394 parts = string.split(':') | 475 parts = string.split(':') |
395 if len(parts) is not 2: | 476 if len(parts) is not 2: |
396 raise argparse.ArgumentTypeError('%r is not grd_path:xtb_dir') | 477 raise argparse.ArgumentTypeError('%r is not grd_path:xtb_dir') |
397 return (parts[0], parts[1]) | 478 return (parts[0], parts[1]) |
398 | 479 |
399 parser = argparse.ArgumentParser( | 480 parser = argparse.ArgumentParser( |
400 description='Generate .h and .rc files for installer strings.') | 481 description='Generate .h and .rc files for installer strings.') |
482 brands = [b for b in MODE_SPECIFIC_STRINGS.itervalues().next().iterkeys()] | |
483 parser.add_argument('-b', | |
484 choices=brands, | |
485 required=True, | |
486 help='identifier of the browser brand (e.g., chromium).', | |
487 dest='brand') | |
401 parser.add_argument('-i', action='append', | 488 parser.add_argument('-i', action='append', |
402 type=GrdPathAndXtbDirPair, | 489 type=GrdPathAndXtbDirPair, |
403 required=True, | 490 required=True, |
404 help='path to .grd file:relative path to .xtb dir', | 491 help='path to .grd file:relative path to .xtb dir', |
405 metavar='GRDFILE:XTBDIR', | 492 metavar='GRDFILE:XTBDIR', |
406 dest='inputs') | 493 dest='inputs') |
407 parser.add_argument('-o', | 494 parser.add_argument('-o', |
408 required=True, | 495 required=True, |
409 help='output directory for generated .rc and .h files', | 496 help='output directory for generated .rc and .h files', |
410 dest='outdir') | 497 dest='outdir') |
411 parser.add_argument('-n', | 498 parser.add_argument('-n', |
412 required=True, | 499 required=True, |
413 help='base name of generated .rc and .h files', | 500 help='base name of generated .rc and .h files', |
414 dest='name') | 501 dest='name') |
415 return parser.parse_args() | 502 return parser.parse_args() |
416 | 503 |
417 | 504 |
418 def main(): | 505 def main(): |
419 args = ParseCommandLine() | 506 args = ParseCommandLine() |
420 StringRcMaker(args.name, args.inputs, args.outdir).MakeFiles() | 507 StringRcMaker(args.name, args.inputs, args.outdir, args.brand).MakeFiles() |
421 return 0 | 508 return 0 |
422 | 509 |
423 | 510 |
424 if '__main__' == __name__: | 511 if '__main__' == __name__: |
425 sys.exit(main()) | 512 sys.exit(main()) |
OLD | NEW |