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 BASEDIR
= os
.path
.dirname(os
.path
.abspath(__file__
))
35 sys
.path
.append(os
.path
.join(BASEDIR
, '../../../../tools/grit'))
36 sys
.path
.append(os
.path
.join(BASEDIR
, '../../../../tools/python'))
38 from grit
.extern
import tclib
39 from google
import path_utils
41 # The IDs of strings we want to import from generated_resources.grd and include
42 # in setup.exe's resources.
45 'IDS_SXS_SHORTCUT_NAME',
46 'IDS_PRODUCT_APP_LAUNCHER_NAME',
47 'IDS_PRODUCT_BINARIES_NAME',
48 'IDS_PRODUCT_DESCRIPTION',
49 'IDS_UNINSTALL_CHROME',
50 'IDS_ABOUT_VERSION_COMPANY_NAME',
51 'IDS_INSTALL_HIGHER_VERSION',
52 'IDS_INSTALL_HIGHER_VERSION_APP_LAUNCHER',
54 'IDS_SAME_VERSION_REPAIR_FAILED',
55 'IDS_SETUP_PATCH_FAILED',
56 'IDS_INSTALL_OS_NOT_SUPPORTED',
57 'IDS_INSTALL_OS_ERROR',
58 'IDS_INSTALL_TEMP_DIR_FAILED',
59 'IDS_INSTALL_UNCOMPRESSION_FAILED',
60 'IDS_INSTALL_INVALID_ARCHIVE',
61 'IDS_INSTALL_INSUFFICIENT_RIGHTS',
62 'IDS_INSTALL_NO_PRODUCTS_TO_UPDATE',
63 'IDS_UNINSTALL_COMPLETE',
64 'IDS_INSTALL_DIR_IN_USE',
65 'IDS_INSTALL_MULTI_INSTALLATION_EXISTS',
66 'IDS_INSTALL_INCONSISTENT_UPDATE_POLICY',
67 'IDS_OEM_MAIN_SHORTCUT_NAME',
68 'IDS_SHORTCUT_TOOLTIP',
69 'IDS_SHORTCUT_NEW_WINDOW',
70 'IDS_APP_LAUNCHER_PRODUCT_DESCRIPTION',
71 'IDS_APP_LAUNCHER_SHORTCUT_TOOLTIP',
72 'IDS_UNINSTALL_APP_LAUNCHER',
73 'IDS_APP_LIST_SHORTCUT_NAME',
74 'IDS_APP_LIST_SHORTCUT_NAME_CANARY',
75 'IDS_APP_SHORTCUTS_SUBDIR_NAME',
76 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY',
79 # The ID of the first resource string.
80 kFirstResourceID
= 1600
83 class TranslationStruct
:
84 """A helper struct that holds information about a single translation."""
85 def __init__(self
, resource_id_str
, language
, translation
):
86 self
.resource_id_str
= resource_id_str
87 self
.language
= language
88 self
.translation
= translation
90 def __cmp__(self
, other
):
91 """Allow TranslationStructs to be sorted by id."""
92 id_result
= cmp(self
.resource_id_str
, other
.resource_id_str
)
93 return cmp(self
.language
, other
.language
) if id_result
== 0 else id_result
96 def CollectTranslatedStrings(branding
):
97 """Collects all the translations for all the strings specified by kStringIds.
98 Returns a list of tuples of (string_id, language, translated string). The
99 list is sorted by language codes."""
100 strings_file
= 'app/chromium_strings.grd'
101 translation_files
= 'chromium_strings*.xtb'
102 if branding
== 'Chrome':
103 strings_file
= 'app/google_chrome_strings.grd'
104 translation_files
= 'google_chrome_strings*.xtb'
105 kGeneratedResourcesPath
= os
.path
.join(path_utils
.ScriptDir(), '..', '..',
107 kTranslationDirectory
= os
.path
.join(path_utils
.ScriptDir(), '..', '..',
108 '..', 'app', 'resources')
109 kTranslationFiles
= glob
.glob(os
.path
.join(kTranslationDirectory
,
112 # Get the strings out of generated_resources.grd.
113 dom
= minidom
.parse(kGeneratedResourcesPath
)
114 # message_nodes is a list of message dom nodes corresponding to the string
115 # ids we care about. We want to make sure that this list is in the same
116 # order as kStringIds so we can associate them together.
118 all_message_nodes
= dom
.getElementsByTagName('message')
119 for string_id
in kStringIds
:
120 message_nodes
.append([x
for x
in all_message_nodes
if
121 x
.getAttribute('name') == string_id
][0])
122 message_texts
= [node
.firstChild
.nodeValue
.strip() for node
in message_nodes
]
124 # Generate the message ID of the string to correlate it with its translations
126 translation_ids
= [tclib
.GenerateMessageId(text
) for text
in message_texts
]
128 # Manually put _EN_US in the list of translated strings because it doesn't
130 translated_strings
= []
131 for string_id
, message_text
in zip(kStringIds
, message_texts
):
132 translated_strings
.append(TranslationStruct(string_id
,
136 # Gather the translated strings from the .xtb files. If an .xtb file doesn't
137 # have the string we want, use the en-US string.
138 for xtb_filename
in kTranslationFiles
:
139 dom
= minidom
.parse(xtb_filename
)
140 language
= dom
.documentElement
.getAttribute('lang')
141 language
= language
.replace('-', '_').upper()
142 translation_nodes
= {}
143 for translation_node
in dom
.getElementsByTagName('translation'):
144 translation_id
= translation_node
.getAttribute('id')
145 if translation_id
in translation_ids
:
146 translation_nodes
[translation_id
] = (translation_node
.firstChild
149 for i
, string_id
in enumerate(kStringIds
):
150 translated_string
= translation_nodes
.get(translation_ids
[i
],
152 translated_strings
.append(TranslationStruct(string_id
,
156 translated_strings
.sort()
157 return translated_strings
160 def WriteRCFile(translated_strings
, out_filename
):
161 """Writes a resource (rc) file with all the language strings provided in
162 |translated_strings|."""
164 u
'#include "%s.h"\n\n'
167 ) % os
.path
.basename(out_filename
)
171 lines
= [kHeaderText
]
172 for translation_struct
in translated_strings
:
173 # Escape special characters for the rc file.
174 translation
= (translation_struct
.translation
.replace('"', '""')
175 .replace('\t', '\\t')
176 .replace('\n', '\\n'))
177 lines
.append(u
' %s "%s"\n' % (translation_struct
.resource_id_str
+ '_'
178 + translation_struct
.language
,
180 lines
.append(kFooterText
)
181 outfile
= open(out_filename
+ '.rc', 'wb')
182 outfile
.write(''.join(lines
).encode('utf-16'))
186 def WriteHeaderFile(translated_strings
, out_filename
):
187 """Writes a .h file with resource ids. This file can be included by the
188 executable to refer to identifiers."""
190 do_languages_lines
= ['\n#define DO_LANGUAGES']
191 installer_string_mapping_lines
= ['\n#define DO_INSTALLER_STRING_MAPPING']
193 # Write the values for how the languages ids are offset.
194 seen_languages
= set()
196 for translation_struct
in translated_strings
:
197 lang
= translation_struct
.language
198 if lang
not in seen_languages
:
199 seen_languages
.add(lang
)
200 lines
.append('#define IDS_L10N_OFFSET_%s %s' % (lang
, offset_id
))
201 do_languages_lines
.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)'
202 % (lang
.replace('_', '-').lower(), lang
))
207 # Write the resource ids themselves.
208 resource_id
= kFirstResourceID
209 for translation_struct
in translated_strings
:
210 lines
.append('#define %s %s' % (translation_struct
.resource_id_str
+ '_'
211 + translation_struct
.language
,
215 # Write out base ID values.
216 for string_id
in kStringIds
:
217 lines
.append('#define %s_BASE %s_%s' % (string_id
,
219 translated_strings
[0].language
))
220 installer_string_mapping_lines
.append(' HANDLE_STRING(%s_BASE, %s)'
221 % (string_id
, string_id
))
223 outfile
= open(out_filename
, 'wb')
224 outfile
.write('\n'.join(lines
))
225 outfile
.write('\n#ifndef RC_INVOKED')
226 outfile
.write(' \\\n'.join(do_languages_lines
))
227 outfile
.write(' \\\n'.join(installer_string_mapping_lines
))
228 # .rc files must end in a new line
229 outfile
.write('\n#endif // ndef RC_INVOKED\n')
234 # TODO: Use optparse to parse command line flags.
236 print 'Usage:\n %s <output_directory> [branding]' % argv
[0]
239 if (len(sys
.argv
) > 2):
241 translated_strings
= CollectTranslatedStrings(branding
)
242 kFilebase
= os
.path
.join(argv
[1], 'installer_util_strings')
243 WriteRCFile(translated_strings
, kFilebase
)
244 WriteHeaderFile(translated_strings
, kFilebase
+ '.h')
248 if '__main__' == __name__
:
249 sys
.exit(main(sys
.argv
))