forked from Psiphon-Inc/psiphon-ios-vpn
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgenstrings.py
executable file
·204 lines (166 loc) · 6.64 KB
/
genstrings.py
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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# NOTE: The master copy of this file is in the psiphon-ios-client-common-library repo.
# Copyright (c) 2017, Psiphon Inc.
# All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This script extracts strings from source (`.m` and `.plist`) files to create `.strings` files.
"""
from __future__ import print_function
import json
import os
import shutil
import errno
import re
import subprocess
import shlex
import glob
import codecs
import plistlib
from collections import OrderedDict
def load_config():
"""
Load config from file.
"""
default_config_filename = 'i18n_conf.json'
with open(default_config_filename) as config_fp:
config = json.load(config_fp)
if not config:
raise Exception('Unable to load config contents from %s' %
default_config_filename)
return config
def process_objc(config):
"""
Generate strings file from Objective-C source files.
"""
# Set up the temp dir
temp_dir = config['enLprojDir'] + '.temp'
shutil.rmtree(temp_dir, ignore_errors=True)
_mkdir_p(temp_dir)
# Gather our source files
objc_files = [os.path.join(dirpath, f)
for dirpath, _, files in os.walk(config['objcRootDir'])
for f in files if re.match(r'.+\.(m|h|swift)$', f)]
# Exclude ignored dirs
if config['objcIgnoreDirs']:
for ignore in config['objcIgnoreDirs']:
ignore = os.path.join(config['objcRootDir'], ignore)
objc_files = [f for f in objc_files
if os.path.commonprefix([f, ignore]) != ignore]
# Create the UTF-16LE encoded .strings files with genstrings
subprocess.check_call(shlex.split(
'genstrings -o "%s" "%s"' % (temp_dir, '" "'.join(objc_files))))
# Convert to UTF-8
for strings_file in glob.glob(os.path.join(temp_dir, '*.strings')):
with codecs.open(strings_file, 'r', 'utf-16-le') as infile:
with codecs.open(os.path.join(config['enLprojDir'], os.path.basename(strings_file)),
'w', 'utf-8') as outfile:
outfile.write('/* THIS FILE IS GENERATED. DO NOT EDIT. */\n\n')
data = infile.read()
if data[0] == u'\uFEFF':
# Strip the BOM
data = data[1:]
outfile.write(data)
shutil.rmtree(temp_dir, ignore_errors=True)
# From https://stackoverflow.com/a/600612/729729
def _mkdir_p(path):
"""
Like `mkdir -p`
"""
try:
os.makedirs(path)
except OSError as exc: # Python >2.5
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
raise
PLIST_STRING_KEYS = ['Title', 'ShortTitle', 'FooterText', 'IASKSubtitle', 'IASKPlaceholder']
PLIST_MULTISTRING_KEYS = ['Titles', 'ShortTitles']
def process_plist(plist_fname, strings):
"""
Copy strings, keys, and comments from `plist_fname` into the
`strings` dict, which is `key => {'key':..., 'default':..., 'description':...}`
"""
plist = plistlib.readPlist(plist_fname)
for item in plist['PreferenceSpecifiers']:
_process_plist_dict(item, strings)
def _process_plist_dict(plist_dict, strings):
"""
Helper for process_plist to process a single dict in a plist, which might
contains strings.
"""
if not isinstance(plist_dict, dict):
return
for multistring_key in PLIST_MULTISTRING_KEYS:
if multistring_key not in plist_dict:
continue
for plist_subdict in plist_dict.get(multistring_key):
_process_plist_dict(plist_subdict, strings)
for string_key in PLIST_STRING_KEYS:
if string_key not in plist_dict:
continue
key = plist_dict.get(string_key)
default = plist_dict.get(string_key + 'Default')
description = plist_dict.get(string_key + 'Description')
if not key:
raise Exception('ERROR: Empty key found: %s' % plist_dict)
elif not default:
# Skipping. This is probably an item covered by common-lib.
print('SKIPPING: %s' % key.encode('utf-8'))
continue
elif not description:
raise Exception(
'ERROR: Missing string description (if this string belongs to common-lib, '
'exclude the default; otherwise do not be lazy and add a description): %s'
% plist_dict)
default = default.replace('"', '\\"').replace('\n', '\\n')
if key in strings:
# The key is already present in strings, so we'll combine the
# descriptions (if necessary).
if default != strings[key]['default']:
raise Exception(
'ERROR: key used multiple times with non-matching defaults '
'(same key must have same default): %s' % plist_dict)
elif description != strings[key]['description']:
strings[key]['description'] += '\n ' + description
else:
strings[key] = {
'key': key,
'default': default,
'description': description}
def process_all_plists(config):
"""
Extract strings from configured plist files into Root.strings.
"""
strings = OrderedDict()
for plist_fname in config['plistFiles']:
process_plist(plist_fname, strings)
with codecs.open(os.path.join(config['enLprojDir'], 'Root.strings'),
'w', 'utf-8') as strings_file:
strings_file.write('/* THIS FILE IS GENERATED. DO NOT EDIT. */\n\n')
for key in strings:
strings_file.write('/* %s */\n"%s" = "%s";\n\n' % (strings[key]['description'],
strings[key]['key'],
strings[key]['default']))
def main():
"""
Do all of the string extraction work.
"""
conf = load_config() # pylint: disable=invalid-name
process_objc(conf)
process_all_plists(conf)
if __name__ == '__main__':
main()