• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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'))
39sys.path.append(os.path.abspath('../tools/grit'))
40from grit.extern import tclib
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_SYSTEM_LEVEL_EXISTS',
56  'IDS_INSTALL_FAILED',
57  'IDS_SAME_VERSION_REPAIR_FAILED',
58  'IDS_SETUP_PATCH_FAILED',
59  'IDS_INSTALL_OS_NOT_SUPPORTED',
60  'IDS_INSTALL_OS_ERROR',
61  'IDS_INSTALL_TEMP_DIR_FAILED',
62  'IDS_INSTALL_UNCOMPRESSION_FAILED',
63  'IDS_INSTALL_INVALID_ARCHIVE',
64  'IDS_INSTALL_INSUFFICIENT_RIGHTS',
65  'IDS_INSTALL_NO_PRODUCTS_TO_UPDATE',
66  'IDS_UNINSTALL_COMPLETE',
67  'IDS_INSTALL_DIR_IN_USE',
68  'IDS_INSTALL_NON_MULTI_INSTALLATION_EXISTS',
69  'IDS_INSTALL_MULTI_INSTALLATION_EXISTS',
70  'IDS_INSTALL_READY_MODE_REQUIRES_CHROME',
71  'IDS_INSTALL_INCONSISTENT_UPDATE_POLICY',
72  'IDS_OEM_MAIN_SHORTCUT_NAME',
73  'IDS_SHORTCUT_TOOLTIP',
74  'IDS_SHORTCUT_NEW_WINDOW',
75  'IDS_APP_LAUNCHER_PRODUCT_DESCRIPTION',
76  'IDS_APP_LAUNCHER_SHORTCUT_TOOLTIP',
77  'IDS_UNINSTALL_APP_LAUNCHER',
78  'IDS_APP_LIST_SHORTCUT_NAME',
79  'IDS_APP_LIST_SHORTCUT_NAME_CANARY',
80  'IDS_APP_SHORTCUTS_SUBDIR_NAME',
81  'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY',
82]
83
84# The ID of the first resource string.
85kFirstResourceID = 1600
86
87
88class TranslationStruct:
89  """A helper struct that holds information about a single translation."""
90  def __init__(self, resource_id_str, language, translation):
91    self.resource_id_str = resource_id_str
92    self.language = language
93    self.translation = translation
94
95  def __cmp__(self, other):
96    """Allow TranslationStructs to be sorted by id."""
97    id_result = cmp(self.resource_id_str, other.resource_id_str)
98    return cmp(self.language, other.language) if id_result == 0 else id_result
99
100
101def CollectTranslatedStrings(branding):
102  """Collects all the translations for all the strings specified by kStringIds.
103  Returns a list of tuples of (string_id, language, translated string). The
104  list is sorted by language codes."""
105  strings_file = 'app/chromium_strings.grd'
106  translation_files = 'chromium_strings*.xtb'
107  if branding == 'Chrome':
108    strings_file = 'app/google_chrome_strings.grd'
109    translation_files = 'google_chrome_strings*.xtb'
110  kGeneratedResourcesPath = os.path.join(path_utils.ScriptDir(), '..', '..',
111                                         '..', strings_file)
112  kTranslationDirectory = os.path.join(path_utils.ScriptDir(), '..', '..',
113                                       '..', 'app', 'resources')
114  kTranslationFiles = glob.glob(os.path.join(kTranslationDirectory,
115                                             translation_files))
116
117  # Get the strings out of generated_resources.grd.
118  dom = minidom.parse(kGeneratedResourcesPath)
119  # message_nodes is a list of message dom nodes corresponding to the string
120  # ids we care about.  We want to make sure that this list is in the same
121  # order as kStringIds so we can associate them together.
122  message_nodes = []
123  all_message_nodes = dom.getElementsByTagName('message')
124  for string_id in kStringIds:
125    message_nodes.append([x for x in all_message_nodes if
126                          x.getAttribute('name') == string_id][0])
127  message_texts = [node.firstChild.nodeValue.strip() for node in message_nodes]
128
129  # Generate the message ID of the string to correlate it with its translations
130  # in the xtb files.
131  translation_ids = [tclib.GenerateMessageId(text) for text in message_texts]
132
133  # Manually put _EN_US in the list of translated strings because it doesn't
134  # have a .xtb file.
135  translated_strings = []
136  for string_id, message_text in zip(kStringIds, message_texts):
137    translated_strings.append(TranslationStruct(string_id,
138                                                'EN_US',
139                                                message_text))
140
141  # Gather the translated strings from the .xtb files.  If an .xtb file doesn't
142  # have the string we want, use the en-US string.
143  for xtb_filename in kTranslationFiles:
144    dom = minidom.parse(xtb_filename)
145    language = dom.documentElement.getAttribute('lang')
146    language = language.replace('-', '_').upper()
147    translation_nodes = {}
148    for translation_node in dom.getElementsByTagName('translation'):
149      translation_id = translation_node.getAttribute('id')
150      if translation_id in translation_ids:
151        translation_nodes[translation_id] = (translation_node.firstChild
152                                                             .nodeValue
153                                                             .strip())
154    for i, string_id in enumerate(kStringIds):
155      translated_string = translation_nodes.get(translation_ids[i],
156                                                message_texts[i])
157      translated_strings.append(TranslationStruct(string_id,
158                                                  language,
159                                                  translated_string))
160
161  translated_strings.sort()
162  return translated_strings
163
164
165def WriteRCFile(translated_strings, out_filename):
166  """Writes a resource (rc) file with all the language strings provided in
167  |translated_strings|."""
168  kHeaderText = (
169    u'#include "%s.h"\n\n'
170    u'STRINGTABLE\n'
171    u'BEGIN\n'
172  ) % os.path.basename(out_filename)
173  kFooterText = (
174    u'END\n'
175  )
176  lines = [kHeaderText]
177  for translation_struct in translated_strings:
178    # Escape special characters for the rc file.
179    translation = (translation_struct.translation.replace('"', '""')
180                                                 .replace('\t', '\\t')
181                                                 .replace('\n', '\\n'))
182    lines.append(u'  %s "%s"\n' % (translation_struct.resource_id_str + '_'
183                                       + translation_struct.language,
184                                   translation))
185  lines.append(kFooterText)
186  outfile = open(out_filename + '.rc', 'wb')
187  outfile.write(''.join(lines).encode('utf-16'))
188  outfile.close()
189
190
191def WriteHeaderFile(translated_strings, out_filename):
192  """Writes a .h file with resource ids.  This file can be included by the
193  executable to refer to identifiers."""
194  lines = []
195  do_languages_lines = ['\n#define DO_LANGUAGES']
196  installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING']
197
198  # Write the values for how the languages ids are offset.
199  seen_languages = set()
200  offset_id = 0
201  for translation_struct in translated_strings:
202    lang = translation_struct.language
203    if lang not in seen_languages:
204      seen_languages.add(lang)
205      lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id))
206      do_languages_lines.append('  HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)'
207                                % (lang.replace('_', '-').lower(), lang))
208      offset_id += 1
209    else:
210      break
211
212  # Write the resource ids themselves.
213  resource_id = kFirstResourceID
214  for translation_struct in translated_strings:
215    lines.append('#define %s %s' % (translation_struct.resource_id_str + '_'
216                                        + translation_struct.language,
217                                    resource_id))
218    resource_id += 1
219
220  # Write out base ID values.
221  for string_id in kStringIds:
222    lines.append('#define %s_BASE %s_%s' % (string_id,
223                                            string_id,
224                                            translated_strings[0].language))
225    installer_string_mapping_lines.append('  HANDLE_STRING(%s_BASE, %s)'
226                                          % (string_id, string_id))
227
228  outfile = open(out_filename, 'wb')
229  outfile.write('\n'.join(lines))
230  outfile.write('\n#ifndef RC_INVOKED')
231  outfile.write(' \\\n'.join(do_languages_lines))
232  outfile.write(' \\\n'.join(installer_string_mapping_lines))
233  # .rc files must end in a new line
234  outfile.write('\n#endif  // ndef RC_INVOKED\n')
235  outfile.close()
236
237
238def main(argv):
239  # TODO: Use optparse to parse command line flags.
240  if len(argv) < 2:
241    print 'Usage:\n  %s <output_directory> [branding]' % argv[0]
242    return 1
243  branding = ''
244  if (len(sys.argv) > 2):
245    branding = argv[2]
246  translated_strings = CollectTranslatedStrings(branding)
247  kFilebase = os.path.join(argv[1], 'installer_util_strings')
248  WriteRCFile(translated_strings, kFilebase)
249  WriteHeaderFile(translated_strings, kFilebase + '.h')
250  return 0
251
252
253if '__main__' == __name__:
254  sys.exit(main(sys.argv))
255