blob: a21f7e4a7d678eba58ed69cdabafb198158a6e0e [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2012 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'))
sys.path.append(os.path.abspath('../tools/grit'))
from grit.extern import tclib
# 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_APP_LAUNCHER_NAME',
'IDS_PRODUCT_BINARIES_NAME',
'IDS_PRODUCT_DESCRIPTION',
'IDS_PRODUCT_FRAME_NAME',
'IDS_UNINSTALL_CHROME',
'IDS_ABOUT_VERSION_COMPANY_NAME',
'IDS_INSTALL_HIGHER_VERSION',
'IDS_INSTALL_HIGHER_VERSION_APP_LAUNCHER',
'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',
'IDS_SHORTCUT_NEW_WINDOW',
'IDS_APP_LAUNCHER_PRODUCT_DESCRIPTION',
'IDS_APP_LAUNCHER_SHORTCUT_TOOLTIP',
'IDS_UNINSTALL_APP_LAUNCHER',
'IDS_APP_LIST_SHORTCUT_NAME',
'IDS_APP_LIST_SHORTCUT_NAME_CANARY',
'IDS_APP_SHORTCUTS_SUBDIR_NAME',
'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY',
]
# 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]
# Generate the message ID of the string to correlate it with its translations
# in the xtb files.
translation_ids = [tclib.GenerateMessageId(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 = ['\n#define DO_LANGUAGES']
installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING']
# 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))
installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)'
% (string_id, string_id))
outfile = open(out_filename, 'wb')
outfile.write('\n'.join(lines))
outfile.write('\n#ifndef RC_INVOKED')
outfile.write(' \\\n'.join(do_languages_lines))
outfile.write(' \\\n'.join(installer_string_mapping_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 + '.h')
return 0
if '__main__' == __name__:
sys.exit(main(sys.argv))