diff options
author | dcaiafa <dcaiafa@chromium.org> | 2014-12-01 15:28:57 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-12-01 23:29:50 +0000 |
commit | 9e715f2331c7932ae0b44cc13dc3befc133016b5 (patch) | |
tree | 780bc8a1d25d5afdd86632cd51b77b69534d9731 /remoting/tools | |
parent | aab8bd999e5bf1d0a8c4116c73ecdbbf31212acb (diff) | |
download | chromium_src-9e715f2331c7932ae0b44cc13dc3befc133016b5.zip chromium_src-9e715f2331c7932ae0b44cc13dc3befc133016b5.tar.gz chromium_src-9e715f2331c7932ae0b44cc13dc3befc133016b5.tar.bz2 |
Chromoting iOS Client l10n support
- Add a script to generate iOS localization files from grit-generated
data-packs.
- Update remoting_strings.grd to make Android strings available to iOS
as well.
Review URL: https://codereview.chromium.org/746313002
Cr-Commit-Position: refs/heads/master@{#306290}
Diffstat (limited to 'remoting/tools')
-rwxr-xr-x | remoting/tools/build/remoting_ios_localize.py | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/remoting/tools/build/remoting_ios_localize.py b/remoting/tools/build/remoting_ios_localize.py new file mode 100755 index 0000000..04beb18 --- /dev/null +++ b/remoting/tools/build/remoting_ios_localize.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python +# Copyright 2014 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. + +"""Tool to produce localized strings for the remoting iOS client. + +This script uses a subset of grit-generated string data-packs to produce +localized string files appropriate for iOS. + +For each locale, it generates the following: + +<locale>.lproj/ + Localizable.strings + InfoPlist.strings + +The strings in Localizable.strings are specified in a file containing a list of +IDS. E.g.: + +Given: Localizable_ids.txt: +IDS_PRODUCT_NAME +IDS_SIGN_IN_BUTTON +IDS_CANCEL + +Produces: Localizable.strings: +"IDS_PRODUCT_NAME" = "Remote Desktop"; +"IDS_SIGN_IN_BUTTON" = "Sign In"; +"IDS_CANCEL" = "Cancel"; + +The InfoPlist.strings is formatted using a Jinja2 template where the "ids" +variable is a dictionary of id -> string. E.g.: + +Given: InfoPlist.strings.jinja2: +"CFBundleName" = "{{ ids.IDS_PRODUCT_NAME }}" +"CFCopyrightNotice" = "{{ ids.IDS_COPYRIGHT }}" + +Produces: InfoPlist.strings: +"CFBundleName" = "Remote Desktop"; +"CFCopyrightNotice" = "Copyright 2014 The Chromium Authors."; + +Parameters: + --print-inputs + Prints the expected input file list, then exit. This can be used in gyp + input rules. + + --print-outputs + Prints the expected output file list, then exit. This can be used in gyp + output rules. + + --from-dir FROM_DIR + Specify the directory containing the data pack files generated by grit. + Each data pack should be named <locale>.pak. + + --to-dir TO_DIR + Specify the directory to write the <locale>.lproj directories containing + the string files. + + --localizable-list LOCALIZABLE_ID_LIST + Specify the file containing the list of the IDs of the strings that each + Localizable.strings file should contain. + + --infoplist-template INFOPLIST_TEMPLATE + Specify the Jinja2 template to be used to create each InfoPlist.strings + file. + + --resources-header RESOURCES_HEADER + Specifies the grit-generated header file that maps ID names to ID values. + It's required to map the IDs in LOCALIZABLE_ID_LIST and INFOPLIST_TEMPLATE + to strings in the data packs. +""" + + +import codecs +import optparse +import os +import re +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', + 'tools', 'grit')) +from grit.format import data_pack + +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', + 'third_party')) +import jinja2 + + +LOCALIZABLE_STRINGS = 'Localizable.strings' +INFOPLIST_STRINGS = 'InfoPlist.strings' + + +class LocalizeException(Exception): + pass + + +class LocalizedStringJinja2Adapter: + """Class that maps ID names to localized strings in Jinja2.""" + def __init__(self, id_map, pack): + self.id_map = id_map + self.pack = pack + + def __getattr__(self, name): + id_value = self.id_map.get(name) + if not id_value: + raise LocalizeException('Could not find id %s in resource header' % name) + data = self.pack.resources.get(id_value) + if not data: + raise LocalizeException( + 'Could not find string with id %s (%d) in data pack' % + (name, id_value)) + return decode_and_escape(data) + + +def get_inputs(from_dir, locales): + """Returns the list of files that would be required to run the tool.""" + inputs = [] + for locale in locales: + inputs.append(os.path.join(from_dir, '%s.pak' % locale)) + return format_quoted_list(inputs) + + +def get_outputs(to_dir, locales): + """Returns the list of files that would be produced by the tool.""" + outputs = [] + for locale in locales: + lproj_dir = format_lproj_dir(to_dir, locale) + outputs.append(os.path.join(lproj_dir, LOCALIZABLE_STRINGS)) + outputs.append(os.path.join(lproj_dir, INFOPLIST_STRINGS)) + return format_quoted_list(outputs) + + +def format_quoted_list(items): + """Formats a list as a string, with items space-separated and quoted.""" + return " ".join(['"%s"' % x for x in items]) + + +def format_lproj_dir(to_dir, locale): + """Formats the name of the lproj directory for a given locale.""" + locale = locale.replace('-', '_') + return os.path.join(to_dir, '%s.lproj' % locale) + + +def read_resources_header(resources_header_path): + """Reads and parses a grit-generated resource header file. + + This function will parse lines like the following: + + #define IDS_PRODUCT_NAME 28531 + #define IDS_CANCEL 28542 + + And return a dictionary like the following: + + { 'IDS_PRODUCT_NAME': 28531, 'IDS_CANCEL': 28542 } + """ + regex = re.compile(r'^#define\s+(\w+)\s+(\d+)$') + id_map = {} + try: + with open(resources_header_path, 'r') as f: + for line in f: + match = regex.match(line) + if match: + id_str = match.group(1) + id_value = int(match.group(2)) + id_map[id_str] = id_value + except: + sys.stderr.write('Error while reading header file %s\n' + % resources_header_path) + raise + + return id_map + + +def read_id_list(id_list_path): + """Read a text file with ID names. + + Names are stripped of leading and trailing spaces. Empty lines are ignored. + """ + with open(id_list_path, 'r') as f: + stripped_lines = [x.strip() for x in f] + non_empty_lines = [x for x in stripped_lines if x] + return non_empty_lines + + +def read_jinja2_template(template_path): + """Reads a Jinja2 template.""" + (template_dir, template_name) = os.path.split(template_path) + env = jinja2.Environment(loader = jinja2.FileSystemLoader(template_dir)) + template = env.get_template(template_name) + return template + + +def decode_and_escape(data): + """Decodes utf-8 data, and escapes it appropriately to use in *.strings.""" + u_string = codecs.decode(data, 'utf-8') + u_string = u_string.replace('\\', '\\\\') + u_string = u_string.replace('"', '\\"') + return u_string + + +def generate(from_dir, to_dir, localizable_list_path, infoplist_template_path, + resources_header_path, locales): + """Generates the <locale>.lproj directories and files.""" + + id_map = read_resources_header(resources_header_path) + localizable_ids = read_id_list(localizable_list_path) + infoplist_template = read_jinja2_template(infoplist_template_path) + + # Generate string files for each locale + for locale in locales: + pack = data_pack.ReadDataPack( + os.path.join(os.path.join(from_dir, '%s.pak' % locale))) + + lproj_dir = format_lproj_dir(to_dir, locale) + if not os.path.exists(lproj_dir): + os.makedirs(lproj_dir) + + # Generate Localizable.strings + localizable_strings_path = os.path.join(lproj_dir, LOCALIZABLE_STRINGS) + try: + with codecs.open(localizable_strings_path, 'w', 'utf-16') as f: + for id_str in localizable_ids: + id_value = id_map.get(id_str) + if not id_value: + raise LocalizeException('Could not find "%s" in %s' % + (id_str, resources_header_path)) + + localized_data = pack.resources.get(id_value) + if not localized_data: + raise LocalizeException( + 'Could not find localized string in %s for %s (%d)' % + (localizable_strings_path, id_str, id_value)) + + f.write(u'"%s" = "%s";\n' % + (id_str, decode_and_escape(localized_data))) + except: + sys.stderr.write('Error while creating %s\n' % localizable_strings_path) + raise + + # Generate InfoPlist.strings + infoplist_strings_path = os.path.join(lproj_dir, INFOPLIST_STRINGS) + try: + with codecs.open(infoplist_strings_path, 'w', 'utf-16') as f: + infoplist = infoplist_template.render( + ids = LocalizedStringJinja2Adapter(id_map, pack)) + f.write(infoplist) + except: + sys.stderr.write('Error while creating %s\n' % infoplist_strings_path) + raise + + +def DoMain(args): + """Entrypoint used by gyp's pymod_do_main.""" + parser = optparse.OptionParser("usage: %prog [options] locales") + parser.add_option("--print-inputs", action="store_true", dest="print_input", + default=False, + help="Print the expected input file list, then exit.") + parser.add_option("--print-outputs", action="store_true", dest="print_output", + default=False, + help="Print the expected output file list, then exit.") + parser.add_option("--from-dir", action="store", dest="from_dir", + help="Source data pack directory.") + parser.add_option("--to-dir", action="store", dest="to_dir", + help="Destination data pack directory.") + parser.add_option("--localizable-list", action="store", + dest="localizable_list", + help="File with list of IDS to build Localizable.strings") + parser.add_option("--infoplist-template", action="store", + dest="infoplist_template", + help="File with list of IDS to build InfoPlist.strings") + parser.add_option("--resources-header", action="store", + dest="resources_header", + help="Auto-generated header with resource ids.") + options, locales = parser.parse_args(args) + + if not locales: + parser.error('At least one locale is required.') + + if options.print_input and options.print_output: + parser.error('Only one of --print-inputs or --print-outputs is allowed') + + if options.print_input: + if not options.from_dir: + parser.error('--from-dir is required.') + return get_inputs(options.from_dir, locales) + + if options.print_output: + if not options.to_dir: + parser.error('--to-dir is required.') + return get_outputs(options.to_dir, locales) + + if not (options.from_dir and options.to_dir and options.localizable_list and + options.infoplist_template and options.resources_header): + parser.error('--from-dir, --to-dir, --localizable-list, ' + + '--infoplist-template and --resources-header are required.') + + try: + generate(options.from_dir, options.to_dir, options.localizable_list, + options.infoplist_template, options.resources_header, locales) + except LocalizeException as e: + sys.stderr.write('Error: %s\n' % str(e)) + sys.exit(1) + + return "" + + +def main(args): + print DoMain(args[1:]) + + +if __name__ == '__main__': + main(sys.argv) |