create_string_rc.py revision 3551c9c881056c480085172ff9840cab31610854
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"""This script generates an rc file and header (setup_strings.{rc,h}) to be 7included in setup.exe. The rc file includes translations for strings pulled 8from generated_resource.grd and the localized .xtb files. 9 10The header file includes IDs for each string, but also has values to allow 11getting a string based on a language offset. For example, the header file 12looks like this: 13 14#define IDS_L10N_OFFSET_AR 0 15#define IDS_L10N_OFFSET_BG 1 16#define IDS_L10N_OFFSET_CA 2 17... 18#define IDS_L10N_OFFSET_ZH_TW 41 19 20#define IDS_MY_STRING_AR 1600 21#define IDS_MY_STRING_BG 1601 22... 23#define IDS_MY_STRING_BASE IDS_MY_STRING_AR 24 25This allows us to lookup an an ID for a string by adding IDS_MY_STRING_BASE and 26IDS_L10N_OFFSET_* for the language we are interested in. 27""" 28 29import glob 30import os 31import sys 32from xml.dom import minidom 33 34# We are expected to use ../../../../third_party/python_24/python.exe 35from google import path_utils 36 37# Quick hack to fix the path. 38sys.path.append(os.path.abspath('../../tools/grit/grit/extern')) 39sys.path.append(os.path.abspath('../tools/grit/grit/extern')) 40import FP 41 42# The IDs of strings we want to import from generated_resources.grd and include 43# in setup.exe's resources. 44kStringIds = [ 45 'IDS_PRODUCT_NAME', 46 'IDS_SXS_SHORTCUT_NAME', 47 'IDS_PRODUCT_APP_LAUNCHER_NAME', 48 'IDS_PRODUCT_BINARIES_NAME', 49 'IDS_PRODUCT_DESCRIPTION', 50 'IDS_PRODUCT_FRAME_NAME', 51 'IDS_UNINSTALL_CHROME', 52 'IDS_ABOUT_VERSION_COMPANY_NAME', 53 'IDS_INSTALL_HIGHER_VERSION', 54 'IDS_INSTALL_HIGHER_VERSION_APP_LAUNCHER', 55 'IDS_INSTALL_HIGHER_VERSION_CF', 56 'IDS_INSTALL_HIGHER_VERSION_CB_CF', 57 'IDS_INSTALL_SYSTEM_LEVEL_EXISTS', 58 'IDS_INSTALL_FAILED', 59 'IDS_SAME_VERSION_REPAIR_FAILED', 60 'IDS_SAME_VERSION_REPAIR_FAILED_CF', 61 'IDS_SETUP_PATCH_FAILED', 62 'IDS_INSTALL_OS_NOT_SUPPORTED', 63 'IDS_INSTALL_OS_ERROR', 64 'IDS_INSTALL_TEMP_DIR_FAILED', 65 'IDS_INSTALL_UNCOMPRESSION_FAILED', 66 'IDS_INSTALL_INVALID_ARCHIVE', 67 'IDS_INSTALL_INSUFFICIENT_RIGHTS', 68 'IDS_INSTALL_NO_PRODUCTS_TO_UPDATE', 69 'IDS_UNINSTALL_COMPLETE', 70 'IDS_INSTALL_DIR_IN_USE', 71 'IDS_INSTALL_NON_MULTI_INSTALLATION_EXISTS', 72 'IDS_INSTALL_MULTI_INSTALLATION_EXISTS', 73 'IDS_INSTALL_READY_MODE_REQUIRES_CHROME', 74 'IDS_INSTALL_INCONSISTENT_UPDATE_POLICY', 75 'IDS_OEM_MAIN_SHORTCUT_NAME', 76 'IDS_SHORTCUT_TOOLTIP', 77 'IDS_SHORTCUT_NEW_WINDOW', 78 'IDS_APP_LAUNCHER_PRODUCT_DESCRIPTION', 79 'IDS_APP_LAUNCHER_SHORTCUT_TOOLTIP', 80 'IDS_UNINSTALL_APP_LAUNCHER', 81 'IDS_APP_LIST_SHORTCUT_NAME', 82 'IDS_APP_LIST_SHORTCUT_NAME_CANARY', 83] 84 85# The ID of the first resource string. 86kFirstResourceID = 1600 87 88 89class TranslationStruct: 90 """A helper struct that holds information about a single translation.""" 91 def __init__(self, resource_id_str, language, translation): 92 self.resource_id_str = resource_id_str 93 self.language = language 94 self.translation = translation 95 96 def __cmp__(self, other): 97 """Allow TranslationStructs to be sorted by id.""" 98 id_result = cmp(self.resource_id_str, other.resource_id_str) 99 return cmp(self.language, other.language) if id_result == 0 else id_result 100 101 102def CollectTranslatedStrings(branding): 103 """Collects all the translations for all the strings specified by kStringIds. 104 Returns a list of tuples of (string_id, language, translated string). The 105 list is sorted by language codes.""" 106 strings_file = 'app/chromium_strings.grd' 107 translation_files = 'chromium_strings*.xtb' 108 if branding == 'Chrome': 109 strings_file = 'app/google_chrome_strings.grd' 110 translation_files = 'google_chrome_strings*.xtb' 111 kGeneratedResourcesPath = os.path.join(path_utils.ScriptDir(), '..', '..', 112 '..', strings_file) 113 kTranslationDirectory = os.path.join(path_utils.ScriptDir(), '..', '..', 114 '..', 'app', 'resources') 115 kTranslationFiles = glob.glob(os.path.join(kTranslationDirectory, 116 translation_files)) 117 118 # Get the strings out of generated_resources.grd. 119 dom = minidom.parse(kGeneratedResourcesPath) 120 # message_nodes is a list of message dom nodes corresponding to the string 121 # ids we care about. We want to make sure that this list is in the same 122 # order as kStringIds so we can associate them together. 123 message_nodes = [] 124 all_message_nodes = dom.getElementsByTagName('message') 125 for string_id in kStringIds: 126 message_nodes.append([x for x in all_message_nodes if 127 x.getAttribute('name') == string_id][0]) 128 message_texts = [node.firstChild.nodeValue.strip() for node in message_nodes] 129 130 # The fingerprint of the string is the message ID in the translation files 131 # (xtb files). 132 translation_ids = [str(FP.FingerPrint(text)) for text in message_texts] 133 134 # Manually put _EN_US in the list of translated strings because it doesn't 135 # have a .xtb file. 136 translated_strings = [] 137 for string_id, message_text in zip(kStringIds, message_texts): 138 translated_strings.append(TranslationStruct(string_id, 139 'EN_US', 140 message_text)) 141 142 # Gather the translated strings from the .xtb files. If an .xtb file doesn't 143 # have the string we want, use the en-US string. 144 for xtb_filename in kTranslationFiles: 145 dom = minidom.parse(xtb_filename) 146 language = dom.documentElement.getAttribute('lang') 147 language = language.replace('-', '_').upper() 148 translation_nodes = {} 149 for translation_node in dom.getElementsByTagName('translation'): 150 translation_id = translation_node.getAttribute('id') 151 if translation_id in translation_ids: 152 translation_nodes[translation_id] = (translation_node.firstChild 153 .nodeValue 154 .strip()) 155 for i, string_id in enumerate(kStringIds): 156 translated_string = translation_nodes.get(translation_ids[i], 157 message_texts[i]) 158 translated_strings.append(TranslationStruct(string_id, 159 language, 160 translated_string)) 161 162 translated_strings.sort() 163 return translated_strings 164 165 166def WriteRCFile(translated_strings, out_filename): 167 """Writes a resource (rc) file with all the language strings provided in 168 |translated_strings|.""" 169 kHeaderText = ( 170 u'#include "%s.h"\n\n' 171 u'STRINGTABLE\n' 172 u'BEGIN\n' 173 ) % os.path.basename(out_filename) 174 kFooterText = ( 175 u'END\n' 176 ) 177 lines = [kHeaderText] 178 for translation_struct in translated_strings: 179 # Escape special characters for the rc file. 180 translation = (translation_struct.translation.replace('"', '""') 181 .replace('\t', '\\t') 182 .replace('\n', '\\n')) 183 lines.append(u' %s "%s"\n' % (translation_struct.resource_id_str + '_' 184 + translation_struct.language, 185 translation)) 186 lines.append(kFooterText) 187 outfile = open(out_filename + '.rc', 'wb') 188 outfile.write(''.join(lines).encode('utf-16')) 189 outfile.close() 190 191 192def WriteHeaderFile(translated_strings, out_filename): 193 """Writes a .h file with resource ids. This file can be included by the 194 executable to refer to identifiers.""" 195 lines = [] 196 do_languages_lines = ['\n#define DO_LANGUAGES'] 197 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING'] 198 199 # Write the values for how the languages ids are offset. 200 seen_languages = set() 201 offset_id = 0 202 for translation_struct in translated_strings: 203 lang = translation_struct.language 204 if lang not in seen_languages: 205 seen_languages.add(lang) 206 lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id)) 207 do_languages_lines.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)' 208 % (lang.replace('_', '-').lower(), lang)) 209 offset_id += 1 210 else: 211 break 212 213 # Write the resource ids themselves. 214 resource_id = kFirstResourceID 215 for translation_struct in translated_strings: 216 lines.append('#define %s %s' % (translation_struct.resource_id_str + '_' 217 + translation_struct.language, 218 resource_id)) 219 resource_id += 1 220 221 # Write out base ID values. 222 for string_id in kStringIds: 223 lines.append('#define %s_BASE %s_%s' % (string_id, 224 string_id, 225 translated_strings[0].language)) 226 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)' 227 % (string_id, string_id)) 228 229 outfile = open(out_filename, 'wb') 230 outfile.write('\n'.join(lines)) 231 outfile.write('\n#ifndef RC_INVOKED') 232 outfile.write(' \\\n'.join(do_languages_lines)) 233 outfile.write(' \\\n'.join(installer_string_mapping_lines)) 234 # .rc files must end in a new line 235 outfile.write('\n#endif // ndef RC_INVOKED\n') 236 outfile.close() 237 238 239def main(argv): 240 # TODO: Use optparse to parse command line flags. 241 if len(argv) < 2: 242 print 'Usage:\n %s <output_directory> [branding]' % argv[0] 243 return 1 244 branding = '' 245 if (len(sys.argv) > 2): 246 branding = argv[2] 247 translated_strings = CollectTranslatedStrings(branding) 248 kFilebase = os.path.join(argv[1], 'installer_util_strings') 249 WriteRCFile(translated_strings, kFilebase) 250 WriteHeaderFile(translated_strings, kFilebase + '.h') 251 return 0 252 253 254if '__main__' == __name__: 255 sys.exit(main(sys.argv)) 256