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.
6 """This script generates an rc file and header (setup_strings.{rc,h}) to be
7 included in setup.exe. The rc file includes translations for strings pulled
8 from generated_resource.grd and the localized .xtb files.
10 The header file includes IDs for each string, but also has values to allow
11 getting a string based on a language offset. For example, the header file
14 #define IDS_L10N_OFFSET_AR 0
15 #define IDS_L10N_OFFSET_BG 1
16 #define IDS_L10N_OFFSET_CA 2
18 #define IDS_L10N_OFFSET_ZH_TW 41
20 #define IDS_MY_STRING_AR 1600
21 #define IDS_MY_STRING_BG 1601
23 #define IDS_MY_STRING_BASE IDS_MY_STRING_AR
25 This allows us to lookup an an ID for a string by adding IDS_MY_STRING_BASE and
26 IDS_L10N_OFFSET_* for the language we are interested in.
32 from xml.dom import minidom
34 # We are expected to use ../../../../third_party/python_24/python.exe
35 from google import path_utils
37 # Quick hack to fix the path.
38 sys.path.append(os.path.abspath('../../tools/grit'))
39 sys.path.append(os.path.abspath('../tools/grit'))
40 from grit.extern import tclib
42 # The IDs of strings we want to import from generated_resources.grd and include
43 # in setup.exe's resources.
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',
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',
85 # The ID of the first resource string.
86 kFirstResourceID = 1600
89 class 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
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
102 def 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(), '..', '..',
113 kTranslationDirectory = os.path.join(path_utils.ScriptDir(), '..', '..',
114 '..', 'app', 'resources')
115 kTranslationFiles = glob.glob(os.path.join(kTranslationDirectory,
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.
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]
130 # Generate the message ID of the string to correlate it with its translations
132 translation_ids = [tclib.GenerateMessageId(text) for text in message_texts]
134 # Manually put _EN_US in the list of translated strings because it doesn't
136 translated_strings = []
137 for string_id, message_text in zip(kStringIds, message_texts):
138 translated_strings.append(TranslationStruct(string_id,
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
155 for i, string_id in enumerate(kStringIds):
156 translated_string = translation_nodes.get(translation_ids[i],
158 translated_strings.append(TranslationStruct(string_id,
162 translated_strings.sort()
163 return translated_strings
166 def WriteRCFile(translated_strings, out_filename):
167 """Writes a resource (rc) file with all the language strings provided in
168 |translated_strings|."""
170 u'#include "%s.h"\n\n'
173 ) % os.path.basename(out_filename)
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,
186 lines.append(kFooterText)
187 outfile = open(out_filename + '.rc', 'wb')
188 outfile.write(''.join(lines).encode('utf-16'))
192 def 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."""
196 do_languages_lines = ['\n#define DO_LANGUAGES']
197 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING']
199 # Write the values for how the languages ids are offset.
200 seen_languages = set()
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))
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,
221 # Write out base ID values.
222 for string_id in kStringIds:
223 lines.append('#define %s_BASE %s_%s' % (string_id,
225 translated_strings[0].language))
226 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)'
227 % (string_id, string_id))
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')
240 # TODO: Use optparse to parse command line flags.
242 print 'Usage:\n %s <output_directory> [branding]' % argv[0]
245 if (len(sys.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')
254 if '__main__' == __name__:
255 sys.exit(main(sys.argv))