translation of all project, and new translation mechanism

This commit is contained in:
2026-09-03 09:18:48 +03:00
parent 00e7c957e4
commit 01af372ef2
32 changed files with 2813 additions and 537 deletions
+32 -32
View File
@@ -1,4 +1,5 @@
#include "versioninstaller.h"
#include "localization.h"
#include "gamelauncher.h"
#include "versionmanifestservice.h"
@@ -178,11 +179,11 @@ void VersionInstaller::install(const QString &gameDir, const QString &versionId)
return;
if (!isSafeRelativeSegment(id)) {
emit failed(id, tr("Недопустимое имя версии «%1»").arg(id));
emit failed(id, Loc::text("version.error.invalidName").arg(id));
return;
}
if (m_canceling) {
emit failed(id, tr("Идёт отмена предыдущей загрузки — попробуйте ещё раз"));
emit failed(id, Loc::text("version.error.cancelPending"));
return;
}
if (m_versionId == id || isQueued(id))
@@ -225,7 +226,7 @@ void VersionInstaller::startNext()
m_running = true;
if (!wasRunning)
emit started(m_versionId);
setStage(tr("Получение списка версий"));
setStage(Loc::text("version.progress.fetchingList"));
emit progressChanged();
resolveManifestEntry();
@@ -244,7 +245,7 @@ void VersionInstaller::resolveManifestEntry()
adoptVersionJson(local.readAll(), /*alreadySaved=*/true);
return;
}
failInstall(warning.isEmpty() ? tr("Список версий Mojang недоступен") : warning);
failInstall(warning.isEmpty() ? Loc::text("version.error.listUnavailable") : warning);
return;
}
@@ -264,15 +265,14 @@ void VersionInstaller::resolveManifestEntry()
// Иначе это вручную вписанный id сборки — сообщение не должно
// выглядеть как сетевая ошибка.
failInstall(tr("Версия «%1» отсутствует в манифесте Mojang — "
"установить её автоматически нельзя")
failInstall(Loc::text("version.error.notInManifest")
.arg(id));
});
}
void VersionInstaller::fetchVersionJson(const QUrl &url, const QString &expectedSha1)
{
setStage(tr("Описание версии"));
setStage(Loc::text("version.progress.description"));
m_currentLabel = m_versionId + QStringLiteral(".json");
QNetworkRequest request{url};
@@ -288,7 +288,7 @@ void VersionInstaller::fetchVersionJson(const QUrl &url, const QString &expected
return;
if (reply->error() != QNetworkReply::NoError) {
failInstall(tr("Не удалось получить описание версии: %1").arg(reply->errorString()));
failInstall(Loc::text("version.error.descriptionFetchFailed").arg(reply->errorString()));
return;
}
@@ -297,7 +297,7 @@ void VersionInstaller::fetchVersionJson(const QUrl &url, const QString &expected
const QString actual = QString::fromLatin1(
QCryptographicHash::hash(payload, QCryptographicHash::Sha1).toHex());
if (actual.compare(expectedSha1, Qt::CaseInsensitive) != 0) {
failInstall(tr("Контрольная сумма описания версии «%1» не совпала").arg(id));
failInstall(Loc::text("version.error.descriptionChecksum").arg(id));
return;
}
}
@@ -314,14 +314,14 @@ void VersionInstaller::adoptVersionJson(const QByteArray &payload, bool alreadyS
m_versionJson = QJsonDocument::fromJson(payload).object();
if (m_versionJson.isEmpty()) {
failInstall(tr("Описание версии «%1» не удалось разобрать").arg(id));
failInstall(Loc::text("version.error.descriptionUnparsable").arg(id));
return;
}
if (!alreadySaved) {
const QString directory = m_gameDir + QStringLiteral("/versions/") + id;
if (!QDir().mkpath(directory)) {
failInstall(tr("Не удалось создать %1").arg(QDir::toNativeSeparators(directory)));
failInstall(Loc::text("common.error.createFailed").arg(QDir::toNativeSeparators(directory)));
return;
}
@@ -329,7 +329,7 @@ void VersionInstaller::adoptVersionJson(const QByteArray &payload, bool alreadyS
if (!file.open(QIODevice::WriteOnly) || file.write(payload) != payload.size()
|| !file.commit()) {
file.cancelWriting();
failInstall(tr("Не удалось сохранить описание версии «%1»").arg(id));
failInstall(Loc::text("common.error.versionSaveFailed").arg(id));
return;
}
}
@@ -340,7 +340,7 @@ void VersionInstaller::adoptVersionJson(const QByteArray &payload, bool alreadyS
if (!parentId.isEmpty() && isSafeRelativeSegment(parentId)
&& !QFile::exists(versionJsonPath(m_gameDir, parentId))) {
if (m_inheritDepth >= kMaxInheritDepth) {
failInstall(tr("Слишком длинная цепочка inheritsFrom у версии «%1»").arg(id));
failInstall(Loc::text("common.error.inheritsChainTooLong").arg(id));
return;
}
++m_inheritDepth;
@@ -361,7 +361,7 @@ void VersionInstaller::enqueue(DownloadTask task)
{
if (task.url.scheme() != QLatin1String("https")) {
// Подменённое описание версии не должно уводить загрузку на чужой адрес.
m_warnings << tr("Пропущен %1: недопустимая ссылка").arg(task.label);
m_warnings << Loc::text("version.warning.skippedBadUrl").arg(task.label);
return;
}
m_bytesTotal += task.size;
@@ -370,14 +370,14 @@ void VersionInstaller::enqueue(DownloadTask task)
bool VersionInstaller::prepareTasks()
{
setStage(tr("Подготовка"));
setStage(Loc::text("version.progress.preparing"));
QString error;
// features пустые намеренно: Mojang отбирает библиотеки только по ОС,
// а feature-правила живут в arguments, которые установщику не нужны.
m_version = VersionLoader::load(m_gameDir, m_versionId, {}, &error);
if (!m_version.isValid()) {
failInstall(error.isEmpty() ? tr("Не удалось прочитать версию «%1»").arg(m_versionId)
failInstall(error.isEmpty() ? Loc::text("common.error.versionReadFailed").arg(m_versionId)
: error);
return false;
}
@@ -401,7 +401,7 @@ bool VersionInstaller::prepareTasks()
// описания загрузки, разбирать json заново не нужно.
for (const MinecraftLibrary &library : std::as_const(m_version.libraries)) {
if (library.url.isEmpty() || !isSafeRelativePath(library.path)) {
m_warnings << tr("Нет ссылки на библиотеку %1").arg(library.name);
m_warnings << Loc::text("version.error.libraryNoUrl").arg(library.name);
continue;
}
DownloadTask task;
@@ -423,7 +423,7 @@ bool VersionInstaller::prepareTasks()
+ QStringLiteral(".json");
task.sha1 = assetIndex.value(QStringLiteral("sha1")).toString();
task.size = assetIndex.value(QStringLiteral("size")).toInteger();
task.label = tr("индекс ресурсов");
task.label = Loc::text("version.progress.assetIndex");
enqueue(std::move(task));
// totalSize — суммарный вес всех ресурсов, известный ещё до скачивания
@@ -450,7 +450,7 @@ bool VersionInstaller::prepareTasks()
enqueue(std::move(task));
}
setStage(tr("Файлы игры"));
setStage(Loc::text("version.progress.gameFiles"));
return true;
}
@@ -507,7 +507,7 @@ void VersionInstaller::startTask(DownloadTask task)
// Временный файл QSaveFile ложится рядом с целевым, поэтому папка нужна
// даже до открытия.
if (!QDir().mkpath(info.absolutePath())) {
failInstall(tr("Не удалось создать %1").arg(QDir::toNativeSeparators(info.absolutePath())));
failInstall(Loc::text("common.error.createFailed").arg(QDir::toNativeSeparators(info.absolutePath())));
return;
}
@@ -515,7 +515,7 @@ void VersionInstaller::startTask(DownloadTask task)
context->task = task;
context->file = std::make_unique<QSaveFile>(task.path);
if (!context->file->open(QIODevice::WriteOnly)) {
failInstall(tr("Не удалось открыть на запись %1").arg(QDir::toNativeSeparators(task.path)));
failInstall(Loc::text("common.error.openForWriteFailed").arg(QDir::toNativeSeparators(task.path)));
return;
}
@@ -572,7 +572,7 @@ void VersionInstaller::handleReplyFinished(const std::shared_ptr<ActiveDownload>
if (!retryOrFail(context->task, reason))
return;
} else {
failInstall(tr("Не удалось скачать %1: %2").arg(context->task.label, reason));
failInstall(Loc::text("common.error.downloadFailed").arg(context->task.label, reason));
return;
}
pump();
@@ -583,7 +583,7 @@ void VersionInstaller::handleReplyFinished(const std::shared_ptr<ActiveDownload>
const QString actual = QString::fromLatin1(context->hash.result().toHex());
if (actual.compare(context->task.sha1, Qt::CaseInsensitive) != 0) {
context->file->cancelWriting();
if (!retryOrFail(context->task, tr("контрольная сумма не совпала")))
if (!retryOrFail(context->task, Loc::text("common.error.checksumMismatch")))
return;
pump();
return;
@@ -591,7 +591,7 @@ void VersionInstaller::handleReplyFinished(const std::shared_ptr<ActiveDownload>
}
if (!context->file->commit()) {
failInstall(tr("Не удалось сохранить %1").arg(QDir::toNativeSeparators(context->task.path)));
failInstall(Loc::text("common.error.saveFailed").arg(QDir::toNativeSeparators(context->task.path)));
return;
}
@@ -608,7 +608,7 @@ void VersionInstaller::handleReplyFinished(const std::shared_ptr<ActiveDownload>
bool VersionInstaller::retryOrFail(DownloadTask task, const QString &reason)
{
if (task.attempts + 1 >= kMaxAttempts) {
failInstall(tr("Не удалось скачать %1: %2").arg(task.label, reason));
failInstall(Loc::text("common.error.downloadFailed").arg(task.label, reason));
return false;
}
@@ -628,7 +628,7 @@ void VersionInstaller::expandAssetIndex(const QString &indexPath)
{
QFile file(indexPath);
if (!file.open(QIODevice::ReadOnly)) {
failInstall(tr("Не удалось прочитать индекс ресурсов %1")
failInstall(Loc::text("version.error.assetIndexReadFailed")
.arg(QDir::toNativeSeparators(indexPath)));
return;
}
@@ -638,14 +638,14 @@ void VersionInstaller::expandAssetIndex(const QString &indexPath)
m_assetsVirtual = root.value(QStringLiteral("virtual")).toBool();
m_assetsMapToResources = root.value(QStringLiteral("map_to_resources")).toBool();
setStage(tr("Ресурсы"));
setStage(Loc::text("version.progress.assets"));
qint64 actualTotal = 0;
for (auto it = m_assetObjects.constBegin(); it != m_assetObjects.constEnd(); ++it) {
const QJsonObject object = it.value().toObject();
const QString hash = object.value(QStringLiteral("hash")).toString();
if (!isSha1Hex(hash)) {
m_warnings << tr("Пропущен ресурс %1: некорректный хэш").arg(it.key());
m_warnings << Loc::text("version.warning.skippedBadHash").arg(it.key());
continue;
}
@@ -684,7 +684,7 @@ void VersionInstaller::afterDownloads()
void VersionInstaller::startLegacyCopy()
{
setStage(tr("Подготовка ресурсов"));
setStage(Loc::text("version.progress.preparingAssets"));
const QString targetRoot = m_assetsMapToResources
? m_gameDir + QStringLiteral("/resources/")
@@ -728,10 +728,10 @@ void VersionInstaller::legacyTick()
QDir().mkpath(target.absolutePath());
QFile::remove(pair.second);
if (!QFile::copy(pair.first, pair.second))
m_warnings << tr("Не удалось подготовить ресурс %1").arg(target.fileName());
m_warnings << Loc::text("version.error.assetPrepareFailed").arg(target.fileName());
}
m_currentLabel = tr("%1 из %2").arg(m_legacyIndex).arg(m_legacyCopies.size());
m_currentLabel = Loc::text("version.progress.countOf").arg(m_legacyIndex).arg(m_legacyCopies.size());
scheduleProgress();
if (m_legacyIndex >= m_legacyCopies.size()) {
@@ -749,7 +749,7 @@ void VersionInstaller::complete()
probe.versionId = m_versionId;
const QStringList missing = GameLauncher::missingFiles(probe, m_version);
if (!missing.isEmpty()) {
failInstall(tr("После загрузки не хватает файлов (%1):\n%2")
failInstall(Loc::text("version.error.filesMissingAfterDownload")
.arg(missing.size())
.arg(missing.join(u'\n')));
return;