Sync translations from Transifex and run lupdate
[qBittorrent.git] / src / webui / www / tstool.py
blob65f83876579255943c79ae0e4d03bc949649e09f
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
4 # TSTool - script for update qBittorrent WebUI translation files
5 # Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 # In addition, as a special exception, the copyright holders give permission to
22 # link this program with the OpenSSL project's "OpenSSL" library (or with
23 # modified versions of it that use the same license as the "OpenSSL" library),
24 # and distribute the linked executables. You must obey the GNU General Public
25 # License in all respects for all of the code used other than "OpenSSL". If you
26 # modify file(s), you may extend this exception to your version of the file(s),
27 # but you are not obligated to do so. If you do not wish to do so, delete this
28 # exception statement from your version.
30 import argparse
31 import copy
32 import os
33 import os.path
34 import re
35 import sys
36 import xml.etree.ElementTree as ET
38 accepted_exts = [".js", ".html", ".css"]
40 no_obsolete = False
41 www_folder = "."
42 ts_folder = os.path.join(www_folder, "translations")
44 def parseSource(filename, sources):
45 print("Parsing %s..." % (os.path.normpath(filename)))
46 with open(filename, encoding = 'utf-8', mode = 'r') as file:
47 regex = re.compile(
48 r"QBT_TR\((([^\)]|\)(?!QBT_TR))+)\)QBT_TR\[CONTEXT=([a-zA-Z_][a-zA-Z0-9_]*)\]")
49 for match in regex.finditer(file.read()):
50 string = match.group(1)
51 context = match.group(3)
53 if context not in sources:
54 sources[context] = set()
55 sources[context].add(string)
57 def processTranslation(filename, sources):
58 print('Processing %s...' % (os.path.normpath(filename)))
60 try:
61 tree = ET.ElementTree(file = filename)
62 except Exception:
63 print('\tFailed to parse %s!' % (os.path.normpath(filename)))
64 return
66 root = tree.getroot()
67 for context in root.findall('context'):
68 context_name = context.find('name').text
69 has_context = context_name in sources
70 if not has_context and no_obsolete:
71 root.remove(context)
72 continue
74 for message in context.findall('message'):
75 for location in message.findall('location'):
76 message.remove(location)
78 source = message.find('source').text
79 translation = message.find('translation')
80 if has_context and source in sources[context_name]:
81 sources[context_name].remove(source)
83 trtype = translation.attrib.get('type')
84 if (trtype == 'obsolete') or (trtype == 'vanished'):
85 del translation.attrib['type'] # i.e. finished
86 else:
87 if no_obsolete or (translation.attrib.get('type', '') == 'unfinished'):
88 context.remove(message)
89 else:
90 translation.attrib['type'] = 'vanished'
92 if not has_context:
93 continue
95 # add new messages for current context
96 for source in sources[context_name]:
97 message = ET.SubElement(context, 'message')
98 ET.SubElement(message, 'source').text = source
99 ET.SubElement(message, 'translation', {'type': 'unfinished'})
100 del sources[context_name]
102 # add messages for new contexts
103 for context_name in sources:
104 context = ET.SubElement(root, 'context')
105 ET.SubElement(context, 'name').text = context_name
107 for source in sources[context_name]:
108 message = ET.SubElement(context, 'message')
109 ET.SubElement(message, 'source').text = source
110 ET.SubElement(message, 'translation', {'type': 'unfinished'})
112 # prettify output xml
113 indent = ' ' * 4
114 root.text = '\n'
115 for context in root.findall('./context'):
116 context.text = '\n' + indent
117 context.tail = '\n'
118 context.find('./name').tail = '\n' + indent
119 messages = context.findall('./message')
120 if len(messages) == 0: continue
122 for message in messages:
123 message.text = '\n' + (indent * 2)
124 message.tail = '\n' + indent
125 elems = message.findall('./')
126 if len(elems) == 0: continue
128 for elem in elems:
129 elem.tail = '\n' + (indent * 2)
130 elems[-1:][0].tail = '\n' + indent
131 messages[-1:][0].tail = '\n'
133 try:
134 with open(filename, mode = 'wb') as file:
135 file.write(b'<?xml version="1.0" encoding="utf-8"?>\n'
136 b'<!DOCTYPE TS>\n')
137 tree.write(file, encoding = 'utf-8')
138 except Exception:
139 print('\tFailed to write %s!' % (os.path.normpath(filename)))
141 argp = argparse.ArgumentParser(
142 prog = 'tstool.py', description = 'Update qBittorrent WebUI translation files.')
143 argp.add_argument('--no-obsolete', dest = 'no_obsolete', action = 'store_true',
144 default = no_obsolete,
145 help = 'remove obsolete messages (default: mark them as obsolete)')
146 argp.add_argument('--www-folder', dest = 'www_folder', action = 'store',
147 default = www_folder,
148 help = 'folder with WebUI source files (default: "%s")' % (www_folder))
149 argp.add_argument('--ts-folder', dest = 'ts_folder', action = 'store',
150 default = ts_folder,
151 help = 'folder with WebUI translation files (default: "%s")' % (ts_folder))
153 args = argp.parse_args()
154 no_obsolete = args.no_obsolete
155 www_folder = args.www_folder
156 ts_folder = args.ts_folder
158 print("Processing source files...")
159 nfiles = 0
160 source_ts = {}
161 for root, dirs, files in os.walk(www_folder):
162 for file in files:
163 if os.path.splitext(file)[-1] in accepted_exts:
164 parseSource(os.path.join(root, file), source_ts)
165 nfiles += 1
167 if nfiles == 0:
168 print("No source files found!")
169 sys.exit()
171 nstrings = sum(len(sublist) for sublist in source_ts)
172 print("Found %d strings within %d contexts." % (nstrings, len(source_ts)))
173 print("")
175 print("Processing translation files...")
176 for entry in os.scandir(ts_folder):
177 if (entry.is_file() and entry.name.startswith('webui_')
178 and entry.name.endswith(".ts")):
179 processTranslation(entry.path, copy.deepcopy(source_ts))
181 print("Done!")