summaryrefslogtreecommitdiffstats
path: root/chrome/installer/util/prebuild/create_string_rc.py
blob: 9e4ddb78a84b8fbf2a6e6e92f26d8e98fc0c3f6a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!/usr/bin/env python
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""This script generates an rc file and header (setup_strings.{rc,h}) to be
included in setup.exe. The rc file includes translations for strings pulled
from generated_resource.grd and the localized .xtb files.

The header file includes IDs for each string, but also has values to allow
getting a string based on a language offset.  For example, the header file
looks like this:

#define IDS_L10N_OFFSET_AR 0
#define IDS_L10N_OFFSET_BG 1
#define IDS_L10N_OFFSET_CA 2
...
#define IDS_L10N_OFFSET_ZH_TW 41

#define IDS_MY_STRING_AR 1600
#define IDS_MY_STRING_BG 1601
...
#define IDS_MY_STRING_BASE IDS_MY_STRING_AR

This allows us to lookup an an ID for a string by adding IDS_MY_STRING_BASE and
IDS_L10N_OFFSET_* for the language we are interested in.
"""

import glob
import os
import sys
from xml.dom import minidom

# We are expected to use ../../../../third_party/python_24/python.exe
from google import path_utils

# Quick hack to fix the path.
sys.path.append(os.path.abspath('../../tools/grit/grit/extern'))
sys.path.append(os.path.abspath('../tools/grit/grit/extern'))
import FP

# The IDs of strings we want to import from generated_resources.grd and include
# in setup.exe's resources.
kStringIds = [
  'IDS_PRODUCT_NAME',
  'IDS_SXS_SHORTCUT_NAME',
  'IDS_PRODUCT_DESCRIPTION',
  'IDS_PRODUCT_FRAME_NAME',
  'IDS_UNINSTALL_CHROME',
  'IDS_ABOUT_VERSION_COMPANY_NAME',
  'IDS_INSTALL_HIGHER_VERSION',
  'IDS_INSTALL_HIGHER_VERSION_CF',
  'IDS_INSTALL_HIGHER_VERSION_CB_CF',
  'IDS_INSTALL_SYSTEM_LEVEL_EXISTS',
  'IDS_INSTALL_FAILED',
  'IDS_SAME_VERSION_REPAIR_FAILED',
  'IDS_SAME_VERSION_REPAIR_FAILED_CF',
  'IDS_SETUP_PATCH_FAILED',
  'IDS_INSTALL_OS_NOT_SUPPORTED',
  'IDS_INSTALL_OS_ERROR',
  'IDS_INSTALL_TEMP_DIR_FAILED',
  'IDS_INSTALL_UNCOMPRESSION_FAILED',
  'IDS_INSTALL_INVALID_ARCHIVE',
  'IDS_INSTALL_INSUFFICIENT_RIGHTS',
  'IDS_INSTALL_NO_PRODUCTS_TO_UPDATE',
  'IDS_UNINSTALL_COMPLETE',
  'IDS_INSTALL_DIR_IN_USE',
  'IDS_INSTALL_NON_MULTI_INSTALLATION_EXISTS',
  'IDS_INSTALL_MULTI_INSTALLATION_EXISTS',
  'IDS_INSTALL_READY_MODE_REQUIRES_CHROME',
  'IDS_INSTALL_INCONSISTENT_UPDATE_POLICY',
  'IDS_OEM_MAIN_SHORTCUT_NAME',
  'IDS_SHORTCUT_TOOLTIP',
]

# The ID of the first resource string.
kFirstResourceID = 1600


class TranslationStruct:
  """A helper struct that holds information about a single translation."""
  def __init__(self, resource_id_str, language, translation):
    self.resource_id_str = resource_id_str
    self.language = language
    self.translation = translation

  def __cmp__(self, other):
    """Allow TranslationStructs to be sorted by id."""
    id_result = cmp(self.resource_id_str, other.resource_id_str)
    return cmp(self.language, other.language) if id_result == 0 else id_result


def CollectTranslatedStrings(branding):
  """Collects all the translations for all the strings specified by kStringIds.
  Returns a list of tuples of (string_id, language, translated string). The
  list is sorted by language codes."""
  strings_file = 'app/chromium_strings.grd'
  translation_files = 'chromium_strings*.xtb'
  if branding == 'Chrome':
    strings_file = 'app/google_chrome_strings.grd'
    translation_files = 'google_chrome_strings*.xtb'
  kGeneratedResourcesPath = os.path.join(path_utils.ScriptDir(), '..', '..',
                                         '..', strings_file)
  kTranslationDirectory = os.path.join(path_utils.ScriptDir(), '..', '..',
                                       '..', 'app', 'resources')
  kTranslationFiles = glob.glob(os.path.join(kTranslationDirectory,
                                             translation_files))

  # Get the strings out of generated_resources.grd.
  dom = minidom.parse(kGeneratedResourcesPath)
  # message_nodes is a list of message dom nodes corresponding to the string
  # ids we care about.  We want to make sure that this list is in the same
  # order as kStringIds so we can associate them together.
  message_nodes = []
  all_message_nodes = dom.getElementsByTagName('message')
  for string_id in kStringIds:
    message_nodes.append([x for x in all_message_nodes if
                          x.getAttribute('name') == string_id][0])
  message_texts = [node.firstChild.nodeValue.strip() for node in message_nodes]

  # The fingerprint of the string is the message ID in the translation files
  # (xtb files).
  translation_ids = [str(FP.FingerPrint(text)) for text in message_texts]

  # Manually put _EN_US in the list of translated strings because it doesn't
  # have a .xtb file.
  translated_strings = []
  for string_id, message_text in zip(kStringIds, message_texts):
    translated_strings.append(TranslationStruct(string_id,
                                                'EN_US',
                                                message_text))

  # Gather the translated strings from the .xtb files.  If an .xtb file doesn't
  # have the string we want, use the en-US string.
  for xtb_filename in kTranslationFiles:
    dom = minidom.parse(xtb_filename)
    language = dom.documentElement.getAttribute('lang')
    language = language.replace('-', '_').upper()
    translation_nodes = {}
    for translation_node in dom.getElementsByTagName('translation'):
      translation_id = translation_node.getAttribute('id')
      if translation_id in translation_ids:
        translation_nodes[translation_id] = (translation_node.firstChild
                                                             .nodeValue
                                                             .strip())
    for i, string_id in enumerate(kStringIds):
      translated_string = translation_nodes.get(translation_ids[i],
                                                message_texts[i])
      translated_strings.append(TranslationStruct(string_id,
                                                  language,
                                                  translated_string))

  translated_strings.sort()
  return translated_strings


def WriteRCFile(translated_strings, out_filename):
  """Writes a resource (rc) file with all the language strings provided in
  |translated_strings|."""
  kHeaderText = (
    u'#include "%s.h"\n\n'
    u'STRINGTABLE\n'
    u'BEGIN\n'
  ) % os.path.basename(out_filename)
  kFooterText = (
    u'END\n'
  )
  lines = [kHeaderText]
  for translation_struct in translated_strings:
    # Escape special characters for the rc file.
    translation = (translation_struct.translation.replace('"', '""')
                                                 .replace('\t', '\\t')
                                                 .replace('\n', '\\n'))
    lines.append(u'  %s "%s"\n' % (translation_struct.resource_id_str + '_'
                                       + translation_struct.language,
                                   translation))
  lines.append(kFooterText)
  outfile = open(out_filename + '.rc', 'wb')
  outfile.write(''.join(lines).encode('utf-16'))
  outfile.close()


def WriteHeaderFile(translated_strings, out_filename):
  """Writes a .h file with resource ids.  This file can be included by the
  executable to refer to identifiers."""
  lines = []
  do_languages_lines = ['#define DO_LANGUAGES']

  # Write the values for how the languages ids are offset.
  seen_languages = set()
  offset_id = 0
  for translation_struct in translated_strings:
    lang = translation_struct.language
    if lang not in seen_languages:
      seen_languages.add(lang)
      lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id))
      do_languages_lines.append('  HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)'
                                % (lang.replace('_', '-').lower(), lang))
      offset_id += 1
    else:
      break

  # Write the resource ids themselves.
  resource_id = kFirstResourceID
  for translation_struct in translated_strings:
    lines.append('#define %s %s' % (translation_struct.resource_id_str + '_'
                                        + translation_struct.language,
                                    resource_id))
    resource_id += 1

  # Write out base ID values.
  for string_id in kStringIds:
    lines.append('#define %s_BASE %s_%s' % (string_id,
                                            string_id,
                                            translated_strings[0].language))

  outfile = open(out_filename + '.h', 'wb')
  outfile.write('\n'.join(lines))
  outfile.write('\n#ifndef RC_INVOKED\n')
  outfile.write(' \\\n'.join(do_languages_lines))
  # .rc files must end in a new line
  outfile.write('\n#endif  // ndef RC_INVOKED\n')
  outfile.close()


def main(argv):
  # TODO: Use optparse to parse command line flags.
  if len(argv) < 2:
    print 'Usage:\n  %s <output_directory> [branding]' % argv[0]
    return 1
  branding = ''
  if (len(sys.argv) > 2):
    branding = argv[2]
  translated_strings = CollectTranslatedStrings(branding)
  kFilebase = os.path.join(argv[1], 'installer_util_strings')
  WriteRCFile(translated_strings, kFilebase)
  WriteHeaderFile(translated_strings, kFilebase)
  return 0


if '__main__' == __name__:
  sys.exit(main(sys.argv))