2 # Copyright 2014 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.
6 """Tool to produce localized strings for the remoting iOS client.
8 This script uses a subset of grit-generated string data-packs to produce
9 localized string files appropriate for iOS.
11 For each locale, it generates the following:
17 The strings in Localizable.strings are specified in a file containing a list of
20 Given: Localizable_ids.txt:
25 Produces: Localizable.strings:
26 "IDS_PRODUCT_NAME" = "Remote Desktop";
27 "IDS_SIGN_IN_BUTTON" = "Sign In";
28 "IDS_CANCEL" = "Cancel";
30 The InfoPlist.strings is formatted using a Jinja2 template where the "ids"
31 variable is a dictionary of id -> string. E.g.:
33 Given: InfoPlist.strings.jinja2:
34 "CFBundleName" = "{{ ids.IDS_PRODUCT_NAME }}"
35 "CFCopyrightNotice" = "{{ ids.IDS_COPYRIGHT }}"
37 Produces: InfoPlist.strings:
38 "CFBundleName" = "Remote Desktop";
39 "CFCopyrightNotice" = "Copyright 2014 The Chromium Authors.";
43 Prints the expected input file list, then exit. This can be used in gyp
47 Prints the expected output file list, then exit. This can be used in gyp
51 Specify the directory containing the data pack files generated by grit.
52 Each data pack should be named <locale>.pak.
55 Specify the directory to write the <locale>.lproj directories containing
58 --localizable-list LOCALIZABLE_ID_LIST
59 Specify the file containing the list of the IDs of the strings that each
60 Localizable.strings file should contain.
62 --infoplist-template INFOPLIST_TEMPLATE
63 Specify the Jinja2 template to be used to create each InfoPlist.strings
66 --resources-header RESOURCES_HEADER
67 Specifies the grit-generated header file that maps ID names to ID values.
68 It's required to map the IDs in LOCALIZABLE_ID_LIST and INFOPLIST_TEMPLATE
69 to strings in the data packs.
79 sys
.path
.append(os
.path
.join(os
.path
.dirname(__file__
), '..', '..', '..',
81 from grit
.format
import data_pack
83 sys
.path
.append(os
.path
.join(os
.path
.dirname(__file__
), '..', '..', '..',
88 LOCALIZABLE_STRINGS
= 'Localizable.strings'
89 INFOPLIST_STRINGS
= 'InfoPlist.strings'
92 class LocalizeException(Exception):
96 class LocalizedStringJinja2Adapter
:
97 """Class that maps ID names to localized strings in Jinja2."""
98 def __init__(self
, id_map
, pack
):
102 def __getattr__(self
, name
):
103 id_value
= self
.id_map
.get(name
)
105 raise LocalizeException('Could not find id %s in resource header' % name
)
106 data
= self
.pack
.resources
.get(id_value
)
108 raise LocalizeException(
109 'Could not find string with id %s (%d) in data pack' %
111 return decode_and_escape(data
)
114 def get_inputs(from_dir
, locales
):
115 """Returns the list of files that would be required to run the tool."""
117 for locale
in locales
:
118 inputs
.append(os
.path
.join(from_dir
, '%s.pak' % locale
))
119 return format_quoted_list(inputs
)
122 def get_outputs(to_dir
, locales
):
123 """Returns the list of files that would be produced by the tool."""
125 for locale
in locales
:
126 lproj_dir
= format_lproj_dir(to_dir
, locale
)
127 outputs
.append(os
.path
.join(lproj_dir
, LOCALIZABLE_STRINGS
))
128 outputs
.append(os
.path
.join(lproj_dir
, INFOPLIST_STRINGS
))
129 return format_quoted_list(outputs
)
132 def format_quoted_list(items
):
133 """Formats a list as a string, with items space-separated and quoted."""
134 return " ".join(['"%s"' % x
for x
in items
])
137 def format_lproj_dir(to_dir
, locale
):
138 """Formats the name of the lproj directory for a given locale."""
139 locale
= locale
.replace('-', '_')
140 return os
.path
.join(to_dir
, '%s.lproj' % locale
)
143 def read_resources_header(resources_header_path
):
144 """Reads and parses a grit-generated resource header file.
146 This function will parse lines like the following:
148 #define IDS_PRODUCT_NAME 28531
149 #define IDS_CANCEL 28542
151 And return a dictionary like the following:
153 { 'IDS_PRODUCT_NAME': 28531, 'IDS_CANCEL': 28542 }
155 regex
= re
.compile(r
'^#define\s+(\w+)\s+(\d+)$')
158 with
open(resources_header_path
, 'r') as f
:
160 match
= regex
.match(line
)
162 id_str
= match
.group(1)
163 id_value
= int(match
.group(2))
164 id_map
[id_str
] = id_value
166 sys
.stderr
.write('Error while reading header file %s\n'
167 % resources_header_path
)
173 def read_id_list(id_list_path
):
174 """Read a text file with ID names.
176 Names are stripped of leading and trailing spaces. Empty lines are ignored.
178 with
open(id_list_path
, 'r') as f
:
179 stripped_lines
= [x
.strip() for x
in f
]
180 non_empty_lines
= [x
for x
in stripped_lines
if x
]
181 return non_empty_lines
184 def read_jinja2_template(template_path
):
185 """Reads a Jinja2 template."""
186 (template_dir
, template_name
) = os
.path
.split(template_path
)
187 env
= jinja2
.Environment(loader
= jinja2
.FileSystemLoader(template_dir
))
188 template
= env
.get_template(template_name
)
192 def decode_and_escape(data
):
193 """Decodes utf-8 data, and escapes it appropriately to use in *.strings."""
194 u_string
= codecs
.decode(data
, 'utf-8')
195 u_string
= u_string
.replace('\\', '\\\\')
196 u_string
= u_string
.replace('"', '\\"')
200 def generate(from_dir
, to_dir
, localizable_list_path
, infoplist_template_path
,
201 resources_header_path
, locales
):
202 """Generates the <locale>.lproj directories and files."""
204 id_map
= read_resources_header(resources_header_path
)
205 localizable_ids
= read_id_list(localizable_list_path
)
206 infoplist_template
= read_jinja2_template(infoplist_template_path
)
208 # Generate string files for each locale
209 for locale
in locales
:
210 pack
= data_pack
.ReadDataPack(
211 os
.path
.join(os
.path
.join(from_dir
, '%s.pak' % locale
)))
213 lproj_dir
= format_lproj_dir(to_dir
, locale
)
214 if not os
.path
.exists(lproj_dir
):
215 os
.makedirs(lproj_dir
)
217 # Generate Localizable.strings
218 localizable_strings_path
= os
.path
.join(lproj_dir
, LOCALIZABLE_STRINGS
)
220 with codecs
.open(localizable_strings_path
, 'w', 'utf-16') as f
:
221 for id_str
in localizable_ids
:
222 id_value
= id_map
.get(id_str
)
224 raise LocalizeException('Could not find "%s" in %s' %
225 (id_str
, resources_header_path
))
227 localized_data
= pack
.resources
.get(id_value
)
228 if not localized_data
:
229 raise LocalizeException(
230 'Could not find localized string in %s for %s (%d)' %
231 (localizable_strings_path
, id_str
, id_value
))
233 f
.write(u
'"%s" = "%s";\n' %
234 (id_str
, decode_and_escape(localized_data
)))
236 sys
.stderr
.write('Error while creating %s\n' % localizable_strings_path
)
239 # Generate InfoPlist.strings
240 infoplist_strings_path
= os
.path
.join(lproj_dir
, INFOPLIST_STRINGS
)
242 with codecs
.open(infoplist_strings_path
, 'w', 'utf-16') as f
:
243 infoplist
= infoplist_template
.render(
244 ids
= LocalizedStringJinja2Adapter(id_map
, pack
))
247 sys
.stderr
.write('Error while creating %s\n' % infoplist_strings_path
)
252 """Entrypoint used by gyp's pymod_do_main."""
253 parser
= optparse
.OptionParser("usage: %prog [options] locales")
254 parser
.add_option("--print-inputs", action
="store_true", dest
="print_input",
256 help="Print the expected input file list, then exit.")
257 parser
.add_option("--print-outputs", action
="store_true", dest
="print_output",
259 help="Print the expected output file list, then exit.")
260 parser
.add_option("--from-dir", action
="store", dest
="from_dir",
261 help="Source data pack directory.")
262 parser
.add_option("--to-dir", action
="store", dest
="to_dir",
263 help="Destination data pack directory.")
264 parser
.add_option("--localizable-list", action
="store",
265 dest
="localizable_list",
266 help="File with list of IDS to build Localizable.strings")
267 parser
.add_option("--infoplist-template", action
="store",
268 dest
="infoplist_template",
269 help="File with list of IDS to build InfoPlist.strings")
270 parser
.add_option("--resources-header", action
="store",
271 dest
="resources_header",
272 help="Auto-generated header with resource ids.")
273 options
, locales
= parser
.parse_args(args
)
276 parser
.error('At least one locale is required.')
278 if options
.print_input
and options
.print_output
:
279 parser
.error('Only one of --print-inputs or --print-outputs is allowed')
281 if options
.print_input
:
282 if not options
.from_dir
:
283 parser
.error('--from-dir is required.')
284 return get_inputs(options
.from_dir
, locales
)
286 if options
.print_output
:
287 if not options
.to_dir
:
288 parser
.error('--to-dir is required.')
289 return get_outputs(options
.to_dir
, locales
)
291 if not (options
.from_dir
and options
.to_dir
and options
.localizable_list
and
292 options
.infoplist_template
and options
.resources_header
):
293 parser
.error('--from-dir, --to-dir, --localizable-list, ' +
294 '--infoplist-template and --resources-header are required.')
297 generate(options
.from_dir
, options
.to_dir
, options
.localizable_list
,
298 options
.infoplist_template
, options
.resources_header
, locales
)
299 except LocalizeException
as e
:
300 sys
.stderr
.write('Error: %s\n' % str(e
))
307 print DoMain(args
[1:])
310 if __name__
== '__main__':