Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(478)

Side by Side Diff: chrome/installer/util/prebuild/create_string_rc.py

Issue 2791593002: Allow installer::GetLocalizedString to return mode-specific strings. (Closed)
Patch Set: manzagop review part the deux Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/installer/util/l10n_string_util_unittest.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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).
80 # l10n_util::GetStringUTF16 (used within the rest of Chrome) is unaffected, and
81 # will always return the requested string.
82 #
83 # This mapping provides brand- and mode-specific string ids for a given input id
84 # as described here:
85 # {
86 # resource_id_1: { # A resource ID for use with GetLocalizedString.
87 # brand_1: [ # 'google_chrome', for example.
88 # string_id_1, # Strings listed in order of the brand's modes, as
89 # string_id_2, # specified in install_static::InstallConstantIndex.
90 # ...
91 # string_id_N,
92 # ],
93 # brand_2: [ # 'chromium', for example.
94 # ...
95 # ],
96 # },
97 # resource_id_2: ...
98 # }
99 # 'resource_id_1' names an existing string ID. All calls to
100 # installer::GetLocalizedString with this string ID will map to the
101 # mode-specific string.
102 #
103 # Note: Update the test expectations in GetBaseMessageIdForMode.GoogleStringIds
104 # when adding to/modifying this structure.
105 MODE_SPECIFIC_STRINGS = {
106 'IDS_APP_SHORTCUTS_SUBDIR_NAME': {
107 'google_chrome': [
108 'IDS_APP_SHORTCUTS_SUBDIR_NAME',
109 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY',
110 ],
111 'chromium': [
112 'IDS_APP_SHORTCUTS_SUBDIR_NAME',
113 ],
114 },
115 'IDS_INBOUND_MDNS_RULE_DESCRIPTION': {
116 'google_chrome': [
117 'IDS_INBOUND_MDNS_RULE_DESCRIPTION',
118 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY',
119 ],
120 'chromium': [
121 'IDS_INBOUND_MDNS_RULE_DESCRIPTION',
122 ],
123 },
124 'IDS_INBOUND_MDNS_RULE_NAME': {
125 'google_chrome': [
126 'IDS_INBOUND_MDNS_RULE_NAME',
127 'IDS_INBOUND_MDNS_RULE_NAME_CANARY',
128 ],
129 'chromium': [
130 'IDS_INBOUND_MDNS_RULE_NAME',
131 ],
132 },
133 # In contrast to the strings above, this one (IDS_PRODUCT_NAME) is used
134 # throughout Chrome in mode-independent contexts. Within the installer (the
135 # place where this mapping matters), it is only used for mode-specific strings
136 # such as the name of Chrome's shortcut.
137 'IDS_PRODUCT_NAME': {
138 'google_chrome': [
139 'IDS_PRODUCT_NAME',
140 'IDS_SXS_SHORTCUT_NAME',
141 ],
142 'chromium': [
143 'IDS_PRODUCT_NAME',
144 ],
145 },
146 }
147 # Note: Update the test expectations in GetBaseMessageIdForMode.GoogleStringIds
148 # when adding to/modifying the above structure.
149
74 # The ID of the first resource string. 150 # The ID of the first resource string.
75 FIRST_RESOURCE_ID = 1600 151 FIRST_RESOURCE_ID = 1600
76 152
77 153
78 class GrdHandler(sax.handler.ContentHandler): 154 class GrdHandler(sax.handler.ContentHandler):
79 """Extracts selected strings from a .grd file. 155 """Extracts selected strings from a .grd file.
80 156
81 Attributes: 157 Attributes:
82 messages: A dict mapping string identifiers to their corresponding messages. 158 messages: A dict mapping string identifiers to their corresponding messages.
83 """ 159 """
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after
208 translated_string = ''.join(self.__text_scraps).strip() 284 translated_string = ''.join(self.__text_scraps).strip()
209 for string_id in self.__string_ids: 285 for string_id in self.__string_ids:
210 self.translations[string_id] = translated_string 286 self.translations[string_id] = translated_string
211 self.__string_ids = None 287 self.__string_ids = None
212 self.__text_scraps = [] 288 self.__text_scraps = []
213 self.__characters_callback = None 289 self.__characters_callback = None
214 290
215 291
216 class StringRcMaker(object): 292 class StringRcMaker(object):
217 """Makes .h and .rc files containing strings and translations.""" 293 """Makes .h and .rc files containing strings and translations."""
218 def __init__(self, name, inputs, outdir): 294 def __init__(self, name, inputs, outdir, brand):
219 """Constructs a maker. 295 """Constructs a maker.
220 296
221 Args: 297 Args:
222 name: The base name of the generated files (e.g., 298 name: The base name of the generated files (e.g.,
223 'installer_util_strings'). 299 'installer_util_strings').
224 inputs: A list of (grd_file, xtb_dir) pairs containing the source data. 300 inputs: A list of (grd_file, xtb_dir) pairs containing the source data.
225 outdir: The directory into which the files will be generated. 301 outdir: The directory into which the files will be generated.
226 """ 302 """
227 self.name = name 303 self.name = name
228 self.inputs = inputs 304 self.inputs = inputs
229 self.outdir = outdir 305 self.outdir = outdir
306 self.brand = brand
230 307
231 def MakeFiles(self): 308 def MakeFiles(self):
232 translated_strings = self.__ReadSourceAndTranslatedStrings() 309 translated_strings = self.__ReadSourceAndTranslatedStrings()
233 self.__WriteRCFile(translated_strings) 310 self.__WriteRCFile(translated_strings)
234 self.__WriteHeaderFile(translated_strings) 311 self.__WriteHeaderFile(translated_strings)
235 312
236 class __TranslationData(object): 313 class __TranslationData(object):
237 """A container of information about a single translation.""" 314 """A container of information about a single translation."""
238 def __init__(self, resource_id_str, language, translation): 315 def __init__(self, resource_id_str, language, translation):
239 self.resource_id_str = resource_id_str 316 self.resource_id_str = resource_id_str
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
340 escaped_text)) 417 escaped_text))
341 outfile.write(FOOTER_TEXT) 418 outfile.write(FOOTER_TEXT)
342 419
343 def __WriteHeaderFile(self, translated_strings): 420 def __WriteHeaderFile(self, translated_strings):
344 """Writes a .h file with resource ids.""" 421 """Writes a .h file with resource ids."""
345 # TODO(grt): Stream the lines to the file rather than building this giant 422 # TODO(grt): Stream the lines to the file rather than building this giant
346 # list of lines first. 423 # list of lines first.
347 lines = [] 424 lines = []
348 do_languages_lines = ['\n#define DO_LANGUAGES'] 425 do_languages_lines = ['\n#define DO_LANGUAGES']
349 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING'] 426 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING']
427 do_mode_strings_lines = ['\n#define DO_MODE_STRINGS']
350 428
351 # Write the values for how the languages ids are offset. 429 # Write the values for how the languages ids are offset.
352 seen_languages = set() 430 seen_languages = set()
353 offset_id = 0 431 offset_id = 0
354 for translation_data in translated_strings: 432 for translation_data in translated_strings:
355 lang = translation_data.language 433 lang = translation_data.language
356 if lang not in seen_languages: 434 if lang not in seen_languages:
357 seen_languages.add(lang) 435 seen_languages.add(lang)
358 lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id)) 436 lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id))
359 do_languages_lines.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)' 437 do_languages_lines.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)'
360 % (lang.replace('_', '-').lower(), lang)) 438 % (lang.replace('_', '-').lower(), lang))
361 offset_id += 1 439 offset_id += 1
362 else: 440 else:
363 break 441 break
364 442
365 # Write the resource ids themselves. 443 # Write the resource ids themselves.
366 resource_id = FIRST_RESOURCE_ID 444 resource_id = FIRST_RESOURCE_ID
367 for translation_data in translated_strings: 445 for translation_data in translated_strings:
368 lines.append('#define %s %s' % (translation_data.resource_id_str + '_' + 446 lines.append('#define %s %s' % (translation_data.resource_id_str + '_' +
369 translation_data.language, 447 translation_data.language,
370 resource_id)) 448 resource_id))
371 resource_id += 1 449 resource_id += 1
372 450
451 # Handle mode-specific strings.
452 for string_id, brands in MODE_SPECIFIC_STRINGS.iteritems():
453 # Populate the DO_MODE_STRINGS macro.
454 brand_strings = brands.get(self.brand)
455 if not brand_strings:
456 raise exceptions.RuntimeError(
457 'No strings declared for brand \'%s\' in MODE_SPECIFIC_STRINGS for '
458 'message %s' % (self.brand, string_id))
459 do_mode_strings_lines.append(
460 ' HANDLE_MODE_STRING(%s_BASE, %s)'
461 % (string_id, ', '.join([ ('%s_BASE' % s) for s in brand_strings])))
462
373 # Write out base ID values. 463 # Write out base ID values.
374 for string_id in STRING_IDS: 464 for string_id in STRING_IDS:
375 lines.append('#define %s_BASE %s_%s' % (string_id, 465 lines.append('#define %s_BASE %s_%s' % (string_id,
376 string_id, 466 string_id,
377 translated_strings[0].language)) 467 translated_strings[0].language))
378 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)' 468 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)'
379 % (string_id, string_id)) 469 % (string_id, string_id))
380 470
381 with open(os.path.join(self.outdir, self.name + '.h'), 'wb') as outfile: 471 with open(os.path.join(self.outdir, self.name + '.h'), 'wb') as outfile:
382 outfile.write('\n'.join(lines)) 472 outfile.write('\n'.join(lines))
383 outfile.write('\n#ifndef RC_INVOKED') 473 outfile.write('\n#ifndef RC_INVOKED')
384 outfile.write(' \\\n'.join(do_languages_lines)) 474 outfile.write(' \\\n'.join(do_languages_lines))
385 outfile.write(' \\\n'.join(installer_string_mapping_lines)) 475 outfile.write(' \\\n'.join(installer_string_mapping_lines))
476 outfile.write(' \\\n'.join(do_mode_strings_lines))
386 # .rc files must end in a new line 477 # .rc files must end in a new line
387 outfile.write('\n#endif // ndef RC_INVOKED\n') 478 outfile.write('\n#endif // ndef RC_INVOKED\n')
388 479
389 480
390 def ParseCommandLine(): 481 def ParseCommandLine():
391 def GrdPathAndXtbDirPair(string): 482 def GrdPathAndXtbDirPair(string):
392 """Returns (grd_path, xtb_dir) given a colon-separated string of the same. 483 """Returns (grd_path, xtb_dir) given a colon-separated string of the same.
393 """ 484 """
394 parts = string.split(':') 485 parts = string.split(':')
395 if len(parts) is not 2: 486 if len(parts) is not 2:
396 raise argparse.ArgumentTypeError('%r is not grd_path:xtb_dir') 487 raise argparse.ArgumentTypeError('%r is not grd_path:xtb_dir')
397 return (parts[0], parts[1]) 488 return (parts[0], parts[1])
398 489
399 parser = argparse.ArgumentParser( 490 parser = argparse.ArgumentParser(
400 description='Generate .h and .rc files for installer strings.') 491 description='Generate .h and .rc files for installer strings.')
492 brands = [b for b in MODE_SPECIFIC_STRINGS.itervalues().next().iterkeys()]
493 parser.add_argument('-b',
494 choices=brands,
495 required=True,
496 help='identifier of the browser brand (e.g., chromium).',
497 dest='brand')
401 parser.add_argument('-i', action='append', 498 parser.add_argument('-i', action='append',
402 type=GrdPathAndXtbDirPair, 499 type=GrdPathAndXtbDirPair,
403 required=True, 500 required=True,
404 help='path to .grd file:relative path to .xtb dir', 501 help='path to .grd file:relative path to .xtb dir',
405 metavar='GRDFILE:XTBDIR', 502 metavar='GRDFILE:XTBDIR',
406 dest='inputs') 503 dest='inputs')
407 parser.add_argument('-o', 504 parser.add_argument('-o',
408 required=True, 505 required=True,
409 help='output directory for generated .rc and .h files', 506 help='output directory for generated .rc and .h files',
410 dest='outdir') 507 dest='outdir')
411 parser.add_argument('-n', 508 parser.add_argument('-n',
412 required=True, 509 required=True,
413 help='base name of generated .rc and .h files', 510 help='base name of generated .rc and .h files',
414 dest='name') 511 dest='name')
415 return parser.parse_args() 512 return parser.parse_args()
416 513
417 514
418 def main(): 515 def main():
419 args = ParseCommandLine() 516 args = ParseCommandLine()
420 StringRcMaker(args.name, args.inputs, args.outdir).MakeFiles() 517 StringRcMaker(args.name, args.inputs, args.outdir, args.brand).MakeFiles()
421 return 0 518 return 0
422 519
423 520
424 if '__main__' == __name__: 521 if '__main__' == __name__:
425 sys.exit(main()) 522 sys.exit(main())
OLDNEW
« no previous file with comments | « chrome/installer/util/l10n_string_util_unittest.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698