NOISSUE Translations model and initial setup wizard work
This commit is contained in:
315
api/logic/translations/TranslationsModel.cpp
Normal file
315
api/logic/translations/TranslationsModel.cpp
Normal file
@ -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();
|
||||
}
|
61
api/logic/translations/TranslationsModel.h
Normal file
61
api/logic/translations/TranslationsModel.h
Normal file
@ -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;
|
||||
};
|
Reference in New Issue
Block a user