qBittorrent
tstool.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 # TSTool - script for update qBittorrent WebUI translation files
5 # Copyright (C) 2018 Vladimir Golovnev <[email protected]>
6 #
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.
11 #
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.
16 #
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.
20 #
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.
29 
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
37 
38 accepted_exts = [".js", ".html", ".css"]
39 
40 no_obsolete = False
41 www_folder = "."
42 ts_folder = os.path.join(www_folder, "translations")
43 
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)
52 
53  if context not in sources:
54  sources[context] = set()
55  sources[context].add(string)
56 
57 def processTranslation(filename, sources):
58  print('Processing %s...' % (os.path.normpath(filename)))
59 
60  try:
61  tree = ET.ElementTree(file = filename)
62  except Exception:
63  print('\tFailed to parse %s!' % (os.path.normpath(filename)))
64  return
65 
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
73 
74  for message in context.findall('message'):
75  for location in message.findall('location'):
76  message.remove(location)
77 
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)
82 
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'
91 
92  if not has_context:
93  continue
94 
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]
101 
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
106 
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'})
111 
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
121 
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
127 
128  for elem in elems:
129  elem.tail = '\n' + (indent * 2)
130  elems[-1:][0].tail = '\n' + indent
131  messages[-1:][0].tail = '\n'
132 
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)))
140 
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))
152 
153 args = argp.parse_args()
154 no_obsolete = args.no_obsolete
155 www_folder = args.www_folder
156 ts_folder = args.ts_folder
157 
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
166 
167 if nfiles == 0:
168  print("No source files found!")
169  sys.exit()
170 
171 nstrings = sum(len(sublist) for sublist in source_ts)
172 print("Found %d strings within %d contexts." % (nstrings, len(source_ts)))
173 print("")
174 
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))
180 
181 print("Done!")
def processTranslation(filename, sources)
Definition: tstool.py:57
def parseSource(filename, sources)
Definition: tstool.py:44