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.
36 import xml
.etree
.ElementTree
as ET
38 accepted_exts
= [".js", ".html", ".css"]
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:
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
)))
61 tree
= ET
.ElementTree(file = filename
)
63 print('\tFailed to parse %s!' % (os
.path
.normpath(filename
)))
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
:
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
87 if no_obsolete
or (translation
.attrib
.get('type', '') == 'unfinished'):
88 context
.remove(message
)
90 translation
.attrib
['type'] = 'vanished'
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
115 for context
in root
.findall('./context'):
116 context
.text
= '\n' + indent
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
129 elem
.tail
= '\n' + (indent
* 2)
130 elems
[-1:][0].tail
= '\n' + indent
131 messages
[-1:][0].tail
= '\n'
134 with
open(filename
, mode
= 'wb') as file:
135 file.write(b
'<?xml version="1.0" encoding="utf-8"?>\n'
137 tree
.write(file, encoding
= 'utf-8')
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',
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...")
161 for root
, dirs
, files
in os
.walk(www_folder
):
163 if os
.path
.splitext(file)[-1] in accepted_exts
:
164 parseSource(os
.path
.join(root
, file), source_ts
)
168 print("No source files found!")
171 nstrings
= sum(len(sublist
) for sublist
in source_ts
)
172 print("Found %d strings within %d contexts." % (nstrings
, len(source_ts
)))
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
))