Home | History | Annotate | Download | only in prebuild
      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
      7 included in setup.exe. The rc file includes translations for strings pulled
      8 from generated_resource.grd and the localized .xtb files.
      9 
     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
     12 looks 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 
     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.
     27 """
     28 
     29 import glob
     30 import os
     31 import sys
     32 from xml.dom import minidom
     33 
     34 # We are expected to use ../../../../third_party/python_24/python.exe
     35 from google import path_utils
     36 
     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
     41 
     42 # The IDs of strings we want to import from generated_resources.grd and include
     43 # in setup.exe's resources.
     44 kStringIds = [
     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.
     85 kFirstResourceID = 1600
     86 
     87 
     88 class 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 
    101 def 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 
    165 def 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 
    191 def 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 
    238 def 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 
    253 if '__main__' == __name__:
    254   sys.exit(main(sys.argv))
    255