diff --git a/src/webui/www/tstool.py b/src/webui/www/tstool.py new file mode 100755 index 000000000..10cdd5741 --- /dev/null +++ b/src/webui/www/tstool.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# TSTool - script for update qBittorrent WebUI translation files +# Copyright (C) 2018 Vladimir Golovnev +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give permission to +# link this program with the OpenSSL project's "OpenSSL" library (or with +# modified versions of it that use the same license as the "OpenSSL" library), +# and distribute the linked executables. You must obey the GNU General Public +# License in all respects for all of the code used other than "OpenSSL". If you +# modify file(s), you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete this +# exception statement from your version. + +import argparse +import copy +import os +import os.path +import re +import sys +import xml.etree.ElementTree as ET + +accepted_exts = [".js", ".html", ".css"] + +no_obsolete = False +www_folder = "." +ts_folder = os.path.join(www_folder, "translations") + +def parseSource(filename, sources): + print("Parsing %s..." % (os.path.normpath(filename))) + with open(filename, encoding = 'utf-8', mode = 'r') as file: + regex = re.compile( + r"QBT_TR\((([^\)]|\)(?!QBT_TR))+)\)QBT_TR\[CONTEXT=([a-zA-Z_][a-zA-Z0-9_]*)\]") + for match in regex.finditer(file.read()): + string = match.group(1) + context = match.group(3) + + if context not in sources: + sources[context] = set() + sources[context].add(string) + +def processTranslation(filename, sources): + print('Processing %s...' % (os.path.normpath(filename))) + + try: + tree = ET.ElementTree(file = filename) + except Exception: + print('\tFailed to parse %s!' % (os.path.normpath(filename))) + return + + root = tree.getroot() + for context in root.findall('context'): + context_name = context.find('name').text + has_context = context_name in sources + if not has_context and no_obsolete: + root.remove(context) + continue + + for message in context.findall('message'): + for location in message.findall('location'): + message.remove(location) + + source = message.find('source').text + translation = message.find('translation') + if has_context and source in sources[context_name]: + sources[context_name].remove(source) + + trtype = translation.attrib.get('type') + if (trtype == 'obsolete') or (trtype == 'vanished'): + del translation.attrib['type'] # i.e. finished + else: + if no_obsolete or (translation.attrib.get('type', '') == 'unfinished'): + context.remove(message) + else: + translation.attrib['type'] = 'vanished' + + if not has_context: + continue + + # add new messages for current context + for source in sources[context_name]: + message = ET.SubElement(context, 'message') + ET.SubElement(message, 'source').text = source + ET.SubElement(message, 'translation', {'type': 'unfinished'}) + del sources[context_name] + + # add messages for new contexts + for context_name in sources: + context = ET.SubElement(root, 'context') + ET.SubElement(context, 'name').text = context_name + + for source in sources[context_name]: + message = ET.SubElement(context, 'message') + ET.SubElement(message, 'source').text = source + ET.SubElement(message, 'translation', {'type': 'unfinished'}) + + # prettify output xml + indent = ' ' * 4 + root.text = '\n' + for context in root.findall('./context'): + context.text = '\n' + indent + context.tail = '\n' + context.find('./name').tail = '\n' + indent + messages = context.findall('./message') + if len(messages) == 0: continue + + for message in messages: + message.text = '\n' + (indent * 2) + message.tail = '\n' + indent + elems = message.findall('./') + if len(elems) == 0: continue + + for elem in elems: + elem.tail = '\n' + (indent * 2) + elems[-1:][0].tail = '\n' + indent + messages[-1:][0].tail = '\n' + + try: + with open(filename, mode = 'wb') as file: + file.write(b'\n' + b'\n') + tree.write(file, encoding = 'utf-8') + except Exception: + print('\tFailed to write %s!' % (os.path.normpath(filename))) + +argp = argparse.ArgumentParser( + prog = 'tstool.py', description = 'Update qBittorrent WebUI translation files.') +argp.add_argument('--no-obsolete', dest = 'no_obsolete', action = 'store_true', + default = no_obsolete, + help = 'remove obsolete messages (default: mark them as obsolete)') +argp.add_argument('--www-folder', dest = 'www_folder', action = 'store', + default = www_folder, + help = 'folder with WebUI source files (default: "%s")' % (www_folder)) +argp.add_argument('--ts-folder', dest = 'ts_folder', action = 'store', + default = ts_folder, + help = 'folder with WebUI translation files (default: "%s")' % (ts_folder)) + +args = argp.parse_args() +no_obsolete = args.no_obsolete +www_folder = args.www_folder +ts_folder = args.ts_folder + +print("Processing source files...") +nfiles = 0 +source_ts = {} +for root, dirs, files in os.walk(www_folder): + for file in files: + if os.path.splitext(file)[-1] in accepted_exts: + parseSource(os.path.join(root, file), source_ts) + nfiles += 1 + +if nfiles == 0: + print("No source files found!") + sys.exit() + +nstrings = sum(len(sublist) for sublist in source_ts) +print("Found %d strings within %d contexts." % (nstrings, len(source_ts))) +print("") + +print("Processing translation files...") +for entry in os.scandir(ts_folder): + if (entry.is_file() and entry.name.startswith('webui_') + and entry.name.endswith(".ts")): + processTranslation(entry.path, copy.deepcopy(source_ts)) + +print("Done!")