diff options
Diffstat (limited to 'api/logic')
-rw-r--r-- | api/logic/CMakeLists.txt | 5 | ||||
-rw-r--r-- | api/logic/trans/TranslationDownloader.cpp | 51 | ||||
-rw-r--r-- | api/logic/trans/TranslationDownloader.h | 34 | ||||
-rw-r--r-- | api/logic/translations/TranslationsModel.cpp | 315 | ||||
-rw-r--r-- | api/logic/translations/TranslationsModel.h | 61 |
5 files changed, 378 insertions, 88 deletions
diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt index 02e9afbe..ffecb073 100644 --- a/api/logic/CMakeLists.txt +++ b/api/logic/CMakeLists.txt @@ -412,9 +412,8 @@ add_unit_test(JavaVersion ) set(TRANSLATIONS_SOURCES - # Translations - trans/TranslationDownloader.h - trans/TranslationDownloader.cpp + translations/TranslationsModel.h + translations/TranslationsModel.cpp ) set(TOOLS_SOURCES diff --git a/api/logic/trans/TranslationDownloader.cpp b/api/logic/trans/TranslationDownloader.cpp deleted file mode 100644 index 61e24c9a..00000000 --- a/api/logic/trans/TranslationDownloader.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "TranslationDownloader.h" -#include "net/NetJob.h" -#include "net/Download.h" -#include "net/URLConstants.h" -#include "Env.h" -#include <QDebug> - -TranslationDownloader::TranslationDownloader() -{ -} -void TranslationDownloader::downloadTranslations() -{ - qDebug() << "Downloading Translations Index..."; - m_index_job.reset(new NetJob("Translations Index")); - m_index_task = Net::Download::makeByteArray(QUrl("http://files.multimc.org/translations/index"), &m_data); - m_index_job->addNetAction(m_index_task); - connect(m_index_job.get(), &NetJob::failed, this, &TranslationDownloader::indexFailed); - connect(m_index_job.get(), &NetJob::succeeded, this, &TranslationDownloader::indexRecieved); - m_index_job->start(); -} -void TranslationDownloader::indexRecieved() -{ - qDebug() << "Got translations index!"; - m_dl_job.reset(new NetJob("Translations")); - QList<QByteArray> lines = m_data.split('\n'); - m_data.clear(); - for (const auto line : lines) - { - if (!line.isEmpty()) - { - MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "mmc_" + line); - entry->setStale(true); - m_dl_job->addNetAction(Net::Download::makeCached(QUrl(URLConstants::TRANSLATIONS_BASE_URL + line), entry)); - } - } - connect(m_dl_job.get(), &NetJob::succeeded, this, &TranslationDownloader::dlGood); - connect(m_dl_job.get(), &NetJob::failed, this, &TranslationDownloader::dlFailed); - m_dl_job->start(); -} -void TranslationDownloader::dlFailed(QString reason) -{ - qCritical() << "Translations Download Failed:" << reason; -} -void TranslationDownloader::dlGood() -{ - qDebug() << "Got translations!"; -} -void TranslationDownloader::indexFailed(QString reason) -{ - qCritical() << "Translations Index Download Failed:" << reason; -} diff --git a/api/logic/trans/TranslationDownloader.h b/api/logic/trans/TranslationDownloader.h deleted file mode 100644 index ad3a648d..00000000 --- a/api/logic/trans/TranslationDownloader.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include <QList> -#include <QUrl> -#include <memory> -#include <QObject> -#include <net/NetJob.h> -#include "multimc_logic_export.h" -namespace Net{ -class Download; -} -class NetJob; - -class MULTIMC_LOGIC_EXPORT TranslationDownloader : public QObject -{ - Q_OBJECT - -public: - TranslationDownloader(); - - void downloadTranslations(); - -private slots: - void indexRecieved(); - void indexFailed(QString reason); - void dlFailed(QString reason); - void dlGood(); - -private: - std::shared_ptr<Net::Download> m_index_task; - NetJobPtr m_dl_job; - NetJobPtr m_index_job; - QByteArray m_data; -}; diff --git a/api/logic/translations/TranslationsModel.cpp b/api/logic/translations/TranslationsModel.cpp new file mode 100644 index 00000000..1d44484a --- /dev/null +++ b/api/logic/translations/TranslationsModel.cpp @@ -0,0 +1,315 @@ +#include "TranslationsModel.h" + +#include <QCoreApplication> +#include <QTranslator> +#include <QLocale> +#include <QDir> +#include <QLibraryInfo> +#include <QDebug> +#include <FileSystem.h> +#include <net/NetJob.h> +#include <Env.h> +#include <net/URLConstants.h> + +const static QLatin1Literal defaultLangCode("en"); + +struct Language +{ + QString key; + QLocale locale; + bool updated; +}; + +struct TranslationsModel::Private +{ + QDir m_dir; + + // initial state is just english + QVector<Language> m_languages = {{defaultLangCode, QLocale(defaultLangCode), false}}; + QString m_selectedLanguage = defaultLangCode; + std::unique_ptr<QTranslator> m_qt_translator; + std::unique_ptr<QTranslator> m_app_translator; + + std::shared_ptr<Net::Download> m_index_task; + QString m_downloadingTranslation; + NetJobPtr m_dl_job; + NetJobPtr m_index_job; + QString m_nextDownload; +}; + +TranslationsModel::TranslationsModel(QString path, QObject* parent): QAbstractListModel(parent) +{ + d.reset(new Private); + d->m_dir.setPath(path); + loadLocalIndex(); +} + +TranslationsModel::~TranslationsModel() +{ +} + +QVariant TranslationsModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + + if (row < 0 || row >= d->m_languages.size()) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + return d->m_languages[row].locale.nativeLanguageName(); + case Qt::UserRole: + return d->m_languages[row].key; + default: + return QVariant(); + } +} + +int TranslationsModel::rowCount(const QModelIndex& parent) const +{ + return d->m_languages.size(); +} + +Language * TranslationsModel::findLanguage(const QString& key) +{ + auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language & lang) + { + return lang.key == key; + }); + if(found == d->m_languages.end()) + { + return nullptr; + } + else + { + return found; + } +} + +bool TranslationsModel::selectLanguage(QString key) +{ + QString &langCode = key; + auto langPtr = findLanguage(key); + if(!langPtr) + { + qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; + langCode = defaultLangCode; + } + else + { + langCode = langPtr->key; + } + + // uninstall existing translators if there are any + if (d->m_app_translator) + { + QCoreApplication::removeTranslator(d->m_app_translator.get()); + d->m_app_translator.reset(); + } + if (d->m_qt_translator) + { + QCoreApplication::removeTranslator(d->m_qt_translator.get()); + d->m_qt_translator.reset(); + } + + /* + * FIXME: potential source of crashes: + * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. + * This function is not reentrant. + */ + QLocale locale(langCode); + QLocale::setDefault(locale); + + // if it's the default UI language, finish + if(langCode == defaultLangCode) + { + d->m_selectedLanguage = langCode; + return true; + } + + // otherwise install new translations + bool successful = false; + // FIXME: this is likely never present. FIX IT. + d->m_qt_translator.reset(new QTranslator()); + if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + { + qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; + if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) + { + qCritical() << "Loading Qt Language File failed."; + d->m_qt_translator.reset(); + } + else + { + successful = true; + } + } + else + { + d->m_qt_translator.reset(); + } + + d->m_app_translator.reset(new QTranslator()); + if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path())) + { + qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; + if (!QCoreApplication::installTranslator(d->m_app_translator.get())) + { + qCritical() << "Loading Application Language File failed."; + d->m_app_translator.reset(); + } + else + { + successful = true; + } + } + else + { + d->m_app_translator.reset(); + } + d->m_selectedLanguage = langCode; + return successful; +} + +QModelIndex TranslationsModel::selectedIndex() +{ + auto found = findLanguage(d->m_selectedLanguage); + if(found) + { + // QVector iterator freely converts to pointer to contained type + return index(found - d->m_languages.begin(), 0, QModelIndex()); + } + return QModelIndex(); +} + +QString TranslationsModel::selectedLanguage() +{ + return d->m_selectedLanguage; +} + +void TranslationsModel::downloadIndex() +{ + qDebug() << "Downloading Translations Index..."; + d->m_index_job.reset(new NetJob("Translations Index")); + MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "index"); + d->m_index_task = Net::Download::makeCached(QUrl("http://files.multimc.org/translations/index"), entry); + d->m_index_job->addNetAction(d->m_index_task); + connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); + connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexRecieved); + d->m_index_job->start(); +} + +void TranslationsModel::indexRecieved() +{ + qDebug() << "Got translations index!"; + d->m_index_job.reset(); + loadLocalIndex(); + if(d->m_selectedLanguage != defaultLangCode) + { + downloadTranslation(d->m_selectedLanguage); + } +} + +void TranslationsModel::loadLocalIndex() +{ + QByteArray data; + try + { + data = FS::read(d->m_dir.absoluteFilePath("index")); + } + catch (Exception &e) + { + qCritical() << "Translations Download Failed: index file not readable"; + return; + } + QVector<Language> languages; + QList<QByteArray> lines = data.split('\n'); + // add the default english. + languages.append({defaultLangCode, QLocale(defaultLangCode), true}); + for (const auto line : lines) + { + if(!line.isEmpty()) + { + auto str = QString::fromLatin1(line); + str.remove(".qm"); + languages.append({str, QLocale(str), false}); + } + } + beginResetModel(); + d->m_languages.swap(languages); + endResetModel(); +} + +void TranslationsModel::updateLanguage(QString key) +{ + if(key == defaultLangCode) + { + qWarning() << "Cannot update builtin language" << key; + return; + } + auto found = findLanguage(key); + if(!found) + { + qWarning() << "Cannot update invalid language" << key; + return; + } + if(!found->updated) + { + downloadTranslation(key); + } +} + +void TranslationsModel::downloadTranslation(QString key) +{ + if(d->m_dl_job) + { + d->m_nextDownload = key; + return; + } + d->m_downloadingTranslation = key; + MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); + entry->setStale(true); + d->m_dl_job.reset(new NetJob("Translation for " + key)); + d->m_dl_job->addNetAction(Net::Download::makeCached(QUrl(URLConstants::TRANSLATIONS_BASE_URL + key + ".qm"), entry)); + connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); + connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); + d->m_dl_job->start(); +} + +void TranslationsModel::downloadNext() +{ + if(!d->m_nextDownload.isEmpty()) + { + downloadTranslation(d->m_nextDownload); + d->m_nextDownload.clear(); + } +} + +void TranslationsModel::dlFailed(QString reason) +{ + qCritical() << "Translations Download Failed:" << reason; + d->m_dl_job.reset(); + downloadNext(); +} + +void TranslationsModel::dlGood() +{ + qDebug() << "Got translation:" << d->m_downloadingTranslation; + + if(d->m_downloadingTranslation == d->m_selectedLanguage) + { + selectLanguage(d->m_selectedLanguage); + } + d->m_dl_job.reset(); + downloadNext(); +} + +void TranslationsModel::indexFailed(QString reason) +{ + qCritical() << "Translations Index Download Failed:" << reason; + d->m_index_job.reset(); +} diff --git a/api/logic/translations/TranslationsModel.h b/api/logic/translations/TranslationsModel.h new file mode 100644 index 00000000..90184b79 --- /dev/null +++ b/api/logic/translations/TranslationsModel.h @@ -0,0 +1,61 @@ +/* Copyright 2013-2016 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QAbstractListModel> +#include <memory> +#include "multimc_logic_export.h" + +struct Language; + +class MULTIMC_LOGIC_EXPORT TranslationsModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit TranslationsModel(QString path, QObject *parent = 0); + virtual ~TranslationsModel(); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool selectLanguage(QString key); + void updateLanguage(QString key); + QModelIndex selectedIndex(); + QString selectedLanguage(); + + void downloadIndex(); + +private: + Language *findLanguage(const QString & key); + void loadLocalIndex(); + void downloadTranslation(QString key); + void downloadNext(); + + // hide copy constructor + TranslationsModel(const TranslationsModel &) = delete; + // hide assign op + TranslationsModel &operator=(const TranslationsModel &) = delete; + +private slots: + void indexRecieved(); + void indexFailed(QString reason); + void dlFailed(QString reason); + void dlGood(); + +private: /* data */ + struct Private; + std::unique_ptr<Private> d; +}; |