blob: 8a37bdae1de3baa5281f03f3167f02687e39c0a8 [file] [log] [blame]
Chung-Sheng Wuc0e32c72021-03-30 17:01:28 +08001#!/usr/bin/env python
2# Copyright 2021 The Chromium OS 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"""Script for strings updating."""
6
7import argparse
8import glob
9import logging
10import os
11import re
12from xml.etree import ElementTree
13
14
15DEFAULT_SRC_STRING_PATH = ('blaze-genfiles/googleclient/chrome/'
16 'transconsole_resources/strings/cros')
17DEFAULT_DEST_STRINGS_PATH = os.path.join(
18 os.path.dirname(__file__), 'strings', 'locale')
19
20
21def get_locales_from_dir(src_dir):
22 """Gets a set of locales of xtb files in src_dir."""
23 locales = set()
24 for file in glob.glob(os.path.join(src_dir, 'firmware_strings_*.xtb')):
25 basename = os.path.basename(file)
26 m = re.match(r'^firmware_strings_([A-Za-z0-9-]+).xtb$', basename)
27 locales.add(m.group(1))
28 return locales
29
30
31def load_xtb_to_dict(xtb_dir, locale):
32 """Loads xtb file to dict.
33
34 Args:
35 xtb_dir: The directory of xtb files.
36 locale: The locale of the xtb file to be loaded.
37
38 Returns:
39 A dict of message_id => message.
40 """
41 xtb_file = os.path.join(xtb_dir, f'firmware_strings_{locale}.xtb')
42 xtb_root = ElementTree.parse(xtb_file).getroot()
43 res = {}
44 for item in xtb_root:
45 res[item.attrib['id']] = item.text
46 return res
47
48
49def save_dict_to_xtb(data, out_dir, locale):
50 """Saves the dict to xtb file.
51
52 Args:
53 data: The dict of message_id => message.
54 out_dir: The directory of xtb files.
55 locale: The locale of the xtb file to be saved.
56 """
57 out_xtb = ElementTree.Element('translationbundle', {'lang': locale})
58 out_xtb.text = '\n'
59 for message_id, text in sorted(data.items()):
60 e = ElementTree.SubElement(out_xtb, 'translation', {'id': message_id})
61 e.text = text
62 e.tail = '\n'
63 out_file = os.path.join(out_dir, f'firmware_strings_{locale}.xtb')
64 logging.info(f'Saving {out_file!r}')
65 with open(out_file, 'rb+') as f:
66 # From chromium/src/tools/grit/grit/xtb_reader.py.
67 # Skip the header and write the data of the <translationbundle> tag.
68 front_of_file = f.read(1024)
69 f.seek(front_of_file.find(b'<translationbundle'))
70 out = ElementTree.tostring(out_xtb, encoding='utf-8')
71 f.write(out)
72 f.truncate()
73
74
75def merge_xtb_data(locale, in_dir, out_dir, message_ids):
76 """Merges the xtb data.
77
78 Args:
79 locale: The locale of the xtb file to be merged.
80 in_dir: The source.
81 out_dir: The destination.
82 message_ids: List of the message ids. Only these ids will be modified.
83
84 Returns:
85 (new_ids, update_ids, del_ids): The set of the new/updated/removed message
86 ids.
87 """
88 logging.info(f'Merging {locale!r}...')
89
90 new_ids = set()
91 update_ids = set()
92 del_ids = set()
93
94 in_data = load_xtb_to_dict(in_dir, locale)
95 out_data = load_xtb_to_dict(out_dir, locale)
96 for message_id in message_ids:
97 if message_id in in_data and message_id not in out_data:
98 new_ids.add(message_id)
99 out_data[message_id] = in_data[message_id]
100 elif message_id in in_data and message_id in out_data:
101 if in_data[message_id] != out_data[message_id]:
102 update_ids.add(message_id)
103 out_data[message_id] = in_data[message_id]
104 else:
105 logging.warning(f"Locale {locale!r}: Id {message_id!r} didn't change.")
106 elif message_id not in in_data and message_id in out_data:
107 del_ids.add(message_id)
108 out_data.pop(message_id)
109 else:
110 logging.warning(
111 f'Locale {locale!r}: Id {message_id!r} not in input/output file.')
112 logging.info(f'New: {new_ids}')
113 logging.info(f'Updated: {update_ids}')
114 logging.info(f'Removed: {del_ids}')
115 save_dict_to_xtb(out_data, out_dir, locale)
116 return new_ids, update_ids, del_ids
117
118
119def get_arguments():
120 parser = argparse.ArgumentParser()
121 parser.add_argument('--verbosity', '-v', action='count', default=0)
122 parser.add_argument('--from', dest='in_dir', default=DEFAULT_SRC_STRING_PATH,
123 help='The source directory of the generated xtb files.')
124 parser.add_argument('--to', dest='out_dir', default=DEFAULT_DEST_STRINGS_PATH,
125 help='The destination directory of the xtb files.')
126 subparser = parser.add_subparsers(dest='cmd')
127
128 merge_parser = subparser.add_parser(
129 'merge', help='Merge the xtb files with specific message ids.')
130 merge_parser.add_argument(
131 'message_ids', metavar='ID', nargs='+',
132 help='The ids of the strings which should be updated.')
133
134 diff_parser = subparser.add_parser(
135 'diff', help='Show the different of message ids of a xtb file.')
136 diff_parser.add_argument('--id-only', action='store_true',
137 help="Don't show the message content.")
138 diff_parser.add_argument('locale', help='The locale file to diff.')
139
140 return parser.parse_args(), parser
141
142
143def print_diff_item(key, data, id_only):
144 if id_only:
145 print(key)
146 else:
147 print(f'{key!r}: {data}')
148
149
150def diff(args):
151 in_data = load_xtb_to_dict(args.in_dir, args.locale)
152 out_data = load_xtb_to_dict(args.out_dir, args.locale)
153 print('---------------------------------------------------------------------')
154 print('New:')
155 for key in in_data:
156 if key not in out_data:
157 print_diff_item(key, in_data[key], args.id_only)
158 print('---------------------------------------------------------------------')
159 print('Updated:')
160 for key in in_data:
161 if key in out_data and in_data[key] != out_data[key]:
162 print_diff_item(key, f'\n{out_data[key]!r}\n=>\n{in_data[key]!r}\n',
163 args.id_only)
164 print('---------------------------------------------------------------------')
165 print('Deleted:')
166 for key in out_data:
167 if key not in in_data:
168 print_diff_item(key, out_data[key], args.id_only)
169 print('---------------------------------------------------------------------')
170
171
172def merge(args):
173 in_locales = get_locales_from_dir(args.in_dir)
174 out_locales = get_locales_from_dir(args.out_dir)
175 if in_locales != out_locales:
176 raise RuntimeError(
177 "The input xtb files and the output xtb files don't match.")
178
179 prev_id_sets = None
180 for locale in sorted(in_locales):
181 id_sets = merge_xtb_data(locale, args.in_dir, args.out_dir,
182 args.message_ids)
183 if prev_id_sets and id_sets != prev_id_sets:
184 logging.warning(f'Locale {locale!r}: Updated ids are different with the'
185 f' previous locale:\n{prev_id_sets!r}\n=>\n{id_sets!r}')
186 prev_id_sets = id_sets
187
188
189def main():
190 args, parser = get_arguments()
191 logging.basicConfig(level=logging.WARNING - 10 * args.verbosity)
192 if args.cmd == 'diff':
193 diff(args)
194 elif args.cmd == 'merge':
195 merge(args)
196 else:
197 parser.print_help()
198
199
200if __name__ == '__main__':
201 main()