From becb002d87d533a783a7b8518a5cf4974f953f58 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Fri, 1 Jun 2018 17:49:06 +0300 Subject: [PATCH 1/2] Create WebUI translation update tool --- src/webui/www/tstool.py | 181 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100755 src/webui/www/tstool.py 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!") From c1a4ef13776d5dbdaddff3938a68d64df24b121e Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Thu, 7 Jun 2018 20:07:28 +0300 Subject: [PATCH 2/2] Use independent translation for WebUI --- .gitignore | 1 + src/lang/lang.pri | 15 +- src/webui/CMakeLists.txt | 1 - src/webui/extra_translations.h | 97 - src/webui/webapplication.cpp | 88 +- src/webui/webapplication.h | 4 + src/webui/webui.pri | 23 +- src/webui/www/private/index.html | 44 +- .../www/private/preferences_content.html | 16 +- src/webui/www/translations/webui_ar.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_be.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_bg.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_ca.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_cs.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_da.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_de.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_el.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_en.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_en_AU.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_en_GB.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_eo.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_es.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_eu.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_fi.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_fr.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_gl.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_he.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_hi_IN.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_hr.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_hu.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_hy.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_id.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_is.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_it.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_ja.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_ka.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_ko.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_lt.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_lv_LV.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_ms_MY.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_nb.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_nl.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_oc.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_pl.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_pt_BR.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_pt_PT.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_ro.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_ru.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_sk.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_sl.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_sr.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_sv.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_tr.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_uk.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_uz@Latn.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_vi.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_zh.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_zh_HK.ts | 1846 +++++++++++++++++ src/webui/www/translations/webui_zh_TW.ts | 1846 +++++++++++++++++ src/webui/www/webui.qrc | 50 + 60 files changed, 92461 insertions(+), 178 deletions(-) delete mode 100644 src/webui/extra_translations.h create mode 100644 src/webui/www/translations/webui_ar.ts create mode 100644 src/webui/www/translations/webui_be.ts create mode 100644 src/webui/www/translations/webui_bg.ts create mode 100644 src/webui/www/translations/webui_ca.ts create mode 100644 src/webui/www/translations/webui_cs.ts create mode 100644 src/webui/www/translations/webui_da.ts create mode 100644 src/webui/www/translations/webui_de.ts create mode 100644 src/webui/www/translations/webui_el.ts create mode 100644 src/webui/www/translations/webui_en.ts create mode 100644 src/webui/www/translations/webui_en_AU.ts create mode 100644 src/webui/www/translations/webui_en_GB.ts create mode 100644 src/webui/www/translations/webui_eo.ts create mode 100644 src/webui/www/translations/webui_es.ts create mode 100644 src/webui/www/translations/webui_eu.ts create mode 100644 src/webui/www/translations/webui_fi.ts create mode 100644 src/webui/www/translations/webui_fr.ts create mode 100644 src/webui/www/translations/webui_gl.ts create mode 100644 src/webui/www/translations/webui_he.ts create mode 100644 src/webui/www/translations/webui_hi_IN.ts create mode 100644 src/webui/www/translations/webui_hr.ts create mode 100644 src/webui/www/translations/webui_hu.ts create mode 100644 src/webui/www/translations/webui_hy.ts create mode 100644 src/webui/www/translations/webui_id.ts create mode 100644 src/webui/www/translations/webui_is.ts create mode 100644 src/webui/www/translations/webui_it.ts create mode 100644 src/webui/www/translations/webui_ja.ts create mode 100644 src/webui/www/translations/webui_ka.ts create mode 100644 src/webui/www/translations/webui_ko.ts create mode 100644 src/webui/www/translations/webui_lt.ts create mode 100644 src/webui/www/translations/webui_lv_LV.ts create mode 100644 src/webui/www/translations/webui_ms_MY.ts create mode 100644 src/webui/www/translations/webui_nb.ts create mode 100644 src/webui/www/translations/webui_nl.ts create mode 100644 src/webui/www/translations/webui_oc.ts create mode 100644 src/webui/www/translations/webui_pl.ts create mode 100644 src/webui/www/translations/webui_pt_BR.ts create mode 100644 src/webui/www/translations/webui_pt_PT.ts create mode 100644 src/webui/www/translations/webui_ro.ts create mode 100644 src/webui/www/translations/webui_ru.ts create mode 100644 src/webui/www/translations/webui_sk.ts create mode 100644 src/webui/www/translations/webui_sl.ts create mode 100644 src/webui/www/translations/webui_sr.ts create mode 100644 src/webui/www/translations/webui_sv.ts create mode 100644 src/webui/www/translations/webui_tr.ts create mode 100644 src/webui/www/translations/webui_uk.ts create mode 100644 src/webui/www/translations/webui_uz@Latn.ts create mode 100644 src/webui/www/translations/webui_vi.ts create mode 100644 src/webui/www/translations/webui_zh.ts create mode 100644 src/webui/www/translations/webui_zh_HK.ts create mode 100644 src/webui/www/translations/webui_zh_TW.ts diff --git a/.gitignore b/.gitignore index 8ed956f27..20862ab58 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ qrc_*.cpp ui_*.h *.moc src/lang/qbittorrent_*.qm +src/webui/www/translations/webui_*.qm .DS_Store .qmake.stash src/qbittorrent.app diff --git a/src/lang/lang.pri b/src/lang/lang.pri index 30d5b7106..504deced2 100644 --- a/src/lang/lang.pri +++ b/src/lang/lang.pri @@ -7,14 +7,15 @@ for(file, TS_FILES) { } isEmpty(QMAKE_LRELEASE) { - win32:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]\\lrelease.exe - else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease + win32: QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease.exe + else: QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease unix { - equals(QT_MAJOR_VERSION, 5) { - !exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease-qt5 } - } - } else { - !exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease } + equals(QT_MAJOR_VERSION, 5) { + !exists($$QMAKE_LRELEASE): QMAKE_LRELEASE = lrelease-qt5 + } + } + else { + !exists($$QMAKE_LRELEASE): QMAKE_LRELEASE = lrelease } } diff --git a/src/webui/CMakeLists.txt b/src/webui/CMakeLists.txt index 0f3471168..5c4ae7f71 100644 --- a/src/webui/CMakeLists.txt +++ b/src/webui/CMakeLists.txt @@ -11,7 +11,6 @@ api/synccontroller.h api/torrentscontroller.h api/transfercontroller.h api/serialize/serialize_torrent.h -extra_translations.h webapplication.h webui.h diff --git a/src/webui/extra_translations.h b/src/webui/extra_translations.h deleted file mode 100644 index 2d8ce1123..000000000 --- a/src/webui/extra_translations.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2014 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. - */ - -#ifndef EXTRA_TRANSLATIONS_H -#define EXTRA_TRANSLATIONS_H - -#include - -// Additional translations for Web UI -const char *QBT_WEBUI_TRANSLATIONS[] = { - QT_TRANSLATE_NOOP("HttpServer", "Logout"), - QT_TRANSLATE_NOOP("HttpServer", "Exit qBittorrent"), - QT_TRANSLATE_NOOP("HttpServer", "Download Torrents from their URLs or Magnet links"), - QT_TRANSLATE_NOOP("HttpServer", "Only one link per line"), - QT_TRANSLATE_NOOP("HttpServer", "Upload local torrent"), - QT_TRANSLATE_NOOP("HttpServer", "Are you sure you want to delete the selected torrents from the transfer list?"), - QT_TRANSLATE_NOOP("HttpServer", "Global upload rate limit must be greater than 0 or disabled."), - QT_TRANSLATE_NOOP("HttpServer", "Global download rate limit must be greater than 0 or disabled."), - QT_TRANSLATE_NOOP("HttpServer", "Alternative upload rate limit must be greater than 0 or disabled."), - QT_TRANSLATE_NOOP("HttpServer", "Alternative download rate limit must be greater than 0 or disabled."), - QT_TRANSLATE_NOOP("HttpServer", "Maximum active downloads must be greater than -1."), - QT_TRANSLATE_NOOP("HttpServer", "Maximum active uploads must be greater than -1."), - QT_TRANSLATE_NOOP("HttpServer", "Maximum active torrents must be greater than -1."), - QT_TRANSLATE_NOOP("HttpServer", "Maximum number of connections limit must be greater than 0 or disabled."), - QT_TRANSLATE_NOOP("HttpServer", "Maximum number of connections per torrent limit must be greater than 0 or disabled."), - QT_TRANSLATE_NOOP("HttpServer", "Maximum number of upload slots per torrent limit must be greater than 0 or disabled."), - QT_TRANSLATE_NOOP("HttpServer", "Unable to save program preferences, qBittorrent is probably unreachable."), - QT_TRANSLATE_NOOP("HttpServer", "The port used for incoming connections must be between 1 and 65535."), - QT_TRANSLATE_NOOP("HttpServer", "The port used for the Web UI must be between 1 and 65535."), - QT_TRANSLATE_NOOP("HttpServer", "Save"), - QT_TRANSLATE_NOOP("HttpServer", "qBittorrent client is not reachable"), - QT_TRANSLATE_NOOP("HttpServer", "qBittorrent has been shutdown."), - QT_TRANSLATE_NOOP("HttpServer", "Unable to log in, qBittorrent is probably unreachable."), - QT_TRANSLATE_NOOP("HttpServer", "Invalid Username or Password."), - QT_TRANSLATE_NOOP("HttpServer", "Username"), - QT_TRANSLATE_NOOP("HttpServer", "Password"), - QT_TRANSLATE_NOOP("HttpServer", "Login"), - QT_TRANSLATE_NOOP("HttpServer", "Original authors"), - QT_TRANSLATE_NOOP("HttpServer", "Apply"), - QT_TRANSLATE_NOOP("HttpServer", "Add"), - QT_TRANSLATE_NOOP("HttpServer", "Save files to location:"), - QT_TRANSLATE_NOOP("HttpServer", "Cookie:"), - QT_TRANSLATE_NOOP("HttpServer", "Type folder here"), - QT_TRANSLATE_NOOP("HttpServer", "More information"), - QT_TRANSLATE_NOOP("HttpServer", "Information about certificates"), - QT_TRANSLATE_NOOP("HttpServer", "Save Files to"), - QT_TRANSLATE_NOOP("HttpServer", "IRC: #qbittorrent on Freenode"), - QT_TRANSLATE_NOOP("HttpServer", "Invalid category name:\nPlease do not use any special characters in the category name."), - QT_TRANSLATE_NOOP("HttpServer", "Unknown"), - QT_TRANSLATE_NOOP("HttpServer", "Hard Disk"), - QT_TRANSLATE_NOOP("HttpServer", "Share ratio limit must be between 0 and 9998."), - QT_TRANSLATE_NOOP("HttpServer", "Seeding time limit must be between 0 and 525600 minutes."), - QT_TRANSLATE_NOOP("HttpServer", "Set location"), - QT_TRANSLATE_NOOP("HttpServer", "Limit upload rate"), - QT_TRANSLATE_NOOP("HttpServer", "Limit download rate"), - QT_TRANSLATE_NOOP("HttpServer", "Rename torrent"), - QT_TRANSLATE_NOOP("HttpServer", "Unable to create category") -}; - -const struct { const char *source; const char *comment; } QBT_WEBUI_COMMENTED_TRANSLATIONS[] = { - QT_TRANSLATE_NOOP3("HttpServer", "Other...", "Save Files to: Watch Folder / Default Folder / Other..."), - QT_TRANSLATE_NOOP3("HttpServer", "Monday", "Schedule the use of alternative rate limits on ..."), - QT_TRANSLATE_NOOP3("HttpServer", "Tuesday", "Schedule the use of alternative rate limits on ..."), - QT_TRANSLATE_NOOP3("HttpServer", "Wednesday", "Schedule the use of alternative rate limits on ..."), - QT_TRANSLATE_NOOP3("HttpServer", "Thursday", "Schedule the use of alternative rate limits on ..."), - QT_TRANSLATE_NOOP3("HttpServer", "Friday", "Schedule the use of alternative rate limits on ..."), - QT_TRANSLATE_NOOP3("HttpServer", "Saturday", "Schedule the use of alternative rate limits on ..."), - QT_TRANSLATE_NOOP3("HttpServer", "Sunday", "Schedule the use of alternative rate limits on ..."), - QT_TRANSLATE_NOOP3("HttpServer", "Upload Torrents", "Upload torrent files to qBittorent using WebUI") -}; - -#endif // EXTRA_TRANSLATIONS_H diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 828be87e5..440d30b1d 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -34,7 +34,6 @@ #include #include -#include #include #include #include @@ -91,46 +90,6 @@ namespace return ret; } - void translateDocument(const QString &locale, QString &data) - { - const QRegularExpression regex("QBT_TR\\((([^\\)]|\\)(?!QBT_TR))+)\\)QBT_TR(\\[CONTEXT=([a-zA-Z_][a-zA-Z0-9_]*)\\])"); - const QRegularExpression mnemonic("\\(?&([a-zA-Z]?\\))?"); - - const bool isTranslationNeeded = !locale.startsWith("en") - || locale.startsWith("en_AU") || locale.startsWith("en_GB"); - - int i = 0; - bool found = true; - while (i < data.size() && found) { - QRegularExpressionMatch regexMatch; - i = data.indexOf(regex, i, ®exMatch); - if (i >= 0) { - const QString word = regexMatch.captured(1); - const QString context = regexMatch.captured(4); - - QString translation = isTranslationNeeded - ? qApp->translate(context.toUtf8().constData(), word.toUtf8().constData(), nullptr, 1) - : word; - - // Remove keyboard shortcuts - translation.remove(mnemonic); - - // Use HTML code for quotes to prevent issues with JS - translation.replace('\'', "'"); - translation.replace('\"', """); - - data.replace(i, regexMatch.capturedLength(), translation); - i += translation.length(); - } - else { - found = false; // no more translatable strings - } - - data.replace(QLatin1String("${LANG}"), locale.left(2)); - data.replace(QLatin1String("${VERSION}"), QBT_VERSION); - } - } - inline QUrl urlFromHostHeader(const QString &hostHeader) { if (!hostHeader.contains(QLatin1String("://"))) @@ -234,6 +193,43 @@ void WebApplication::sendWebUIFile() sendFile(localPath); } +void WebApplication::translateDocument(QString &data) +{ + const QRegularExpression regex("QBT_TR\\((([^\\)]|\\)(?!QBT_TR))+)\\)QBT_TR\\[CONTEXT=([a-zA-Z_][a-zA-Z0-9_]*)\\]"); + + const bool isTranslationNeeded = !m_currentLocale.startsWith("en") + || m_currentLocale.startsWith("en_AU") || m_currentLocale.startsWith("en_GB") + || !m_translator.isEmpty(); + + int i = 0; + bool found = true; + while (i < data.size() && found) { + QRegularExpressionMatch regexMatch; + i = data.indexOf(regex, i, ®exMatch); + if (i >= 0) { + const QString word = regexMatch.captured(1); + const QString context = regexMatch.captured(3); + + QString translation = isTranslationNeeded + ? m_translator.translate(context.toUtf8().constData(), word.toUtf8().constData(), nullptr, 1) + : word; + + // Use HTML code for quotes to prevent issues with JS + translation.replace('\'', "'"); + translation.replace('\"', """); + + data.replace(i, regexMatch.capturedLength(), translation); + i += translation.length(); + } + else { + found = false; // no more translatable strings + } + + data.replace(QLatin1String("${LANG}"), m_currentLocale.left(2)); + data.replace(QLatin1String("${VERSION}"), QBT_VERSION); + } +} + WebSession *WebApplication::session() { return m_currentSession; @@ -429,6 +425,14 @@ void WebApplication::configure() if (m_currentLocale != newLocale) { m_currentLocale = newLocale; m_translatedFiles.clear(); + if (m_translator.load(m_rootFolder + QLatin1String("/translations/webui_") + m_currentLocale)) { + LogMsg(tr("WebUI translation for selected locale (%1) is successfully loaded.") + .arg(m_currentLocale)); + } + else { + LogMsg(tr("Couldn't load WebUI translation for selected locale (%1). Falling back to default (en).") + .arg(m_currentLocale), Log::WARNING); + } } m_isLocalAuthEnabled = pref->isWebUiLocalAuthEnabled(); @@ -490,7 +494,7 @@ void WebApplication::sendFile(const QString &path) // Translate the file if (isTranslatable) { QString dataStr {data}; - translateDocument(m_currentLocale, dataStr); + translateDocument(dataStr); data = dataStr.toUtf8(); m_translatedFiles[path] = {data, lastModified}; // caching translated file diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 0de4ad9f3..ebcf21db1 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -34,6 +34,7 @@ #include #include #include +#include #include "api/isessionmanager.h" #include "base/http/irequesthandler.h" @@ -109,6 +110,8 @@ private: void sendFile(const QString &path); void sendWebUIFile(); + void translateDocument(QString &data); + // Session management QString generateSid() const; void sessionInitialize(); @@ -142,6 +145,7 @@ private: }; QMap m_translatedFiles; QString m_currentLocale; + QTranslator m_translator; bool m_isLocalAuthEnabled; bool m_isAuthSubnetWhitelistEnabled; diff --git a/src/webui/webui.pri b/src/webui/webui.pri index 2c960ca7c..bdf4aede3 100644 --- a/src/webui/webui.pri +++ b/src/webui/webui.pri @@ -10,7 +10,6 @@ HEADERS += \ $$PWD/api/torrentscontroller.h \ $$PWD/api/transfercontroller.h \ $$PWD/api/serialize/serialize_torrent.h \ - $$PWD/extra_translations.h \ $$PWD/webapplication.h \ $$PWD/webui.h @@ -29,3 +28,25 @@ SOURCES += \ $$PWD/webui.cpp RESOURCES += $$PWD/www/webui.qrc + +# WebUI Translation +isEmpty(QMAKE_LRELEASE) { + win32: QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease.exe + else: QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease + unix { + equals(QT_MAJOR_VERSION, 5) { + !exists($$QMAKE_LRELEASE): QMAKE_LRELEASE = lrelease-qt5 + } + } + else { + !exists($$QMAKE_LRELEASE): QMAKE_LRELEASE = lrelease + } +} +WEBUI_TRANSLATIONS = $$files(www/translations/webui_*.ts) +WEBUI_TRANSLATIONS_NOEXT = $$replace(WEBUI_TRANSLATIONS, ".ts", "") +message("Building WebUI translations...") +for(L, WEBUI_TRANSLATIONS_NOEXT) { + message("Processing $${L}") + system("$$QMAKE_LRELEASE -silent $${L}.ts -qm $${L}.qm") + !exists("$${L}.qm"): error("Building WebUI translations failed, cannot continue!") +} diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index 6b77ce9fe..f3f8148af 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -37,22 +37,22 @@
   - QBT_TR(Add Torrent &Link...)QBT_TR[CONTEXT=MainWindow] - QBT_TR(&Add Torrent File...)QBT_TR[CONTEXT=MainWindow] + QBT_TR(Add Torrent Link...)QBT_TR[CONTEXT=MainWindow] + QBT_TR(Add Torrent File...)QBT_TR[CONTEXT=MainWindow] QBT_TR(Delete)QBT_TR[CONTEXT=TransferListWidget] QBT_TR(Resume)QBT_TR[CONTEXT=TransferListWidget] QBT_TR(Pause)QBT_TR[CONTEXT=TransferListWidget] diff --git a/src/webui/www/private/preferences_content.html b/src/webui/www/private/preferences_content.html index 17c13b536..4ec2b46b9 100644 --- a/src/webui/www/private/preferences_content.html +++ b/src/webui/www/private/preferences_content.html @@ -51,7 +51,7 @@
- +
@@ -74,7 +74,7 @@
- +
QBT_TR(Supported parameters (case sensitive):)QBT_TR[CONTEXT=OptionsDialog]
    @@ -192,7 +192,7 @@
    - +
    @@ -248,7 +248,7 @@
    - + QBT_TR(From:)QBT_TR[CONTEXT=OptionsDialog] : QBT_TR(To:)QBT_TR[CONTEXT=OptionsDialog] : @@ -299,7 +299,7 @@
    - +
    @@ -354,7 +354,7 @@
    - +
    @@ -426,7 +426,7 @@
    - +
    @@ -471,7 +471,7 @@
    - +