2
0
mirror of https://github.com/kotatogram/kotatogram-desktop synced 2025-08-26 04:17:09 +00:00
2021-08-24 21:32:43 +03:00

295 lines
7.7 KiB
C++

/*
This file is part of Kotatogram Desktop,
the unofficial app based on Telegram Desktop.
For license and copyright information please follow this link:
https://github.com/kotatogram/kotatogram-desktop/blob/dev/LEGAL
*/
#include "kotato/kotato_lang.h"
#include "base/parse_helper.h"
#include "lang/lang_tag.h"
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QDir>
namespace Kotato {
namespace Lang {
namespace {
const auto kDefaultLanguage = qsl("en");
const std::vector<QString> kPostfixes = {
"#zero",
"#one",
"#two",
"#few",
"#many",
"#other"
};
QString BaseLangCode;
QString LangCode;
QMap<QString, QString> DefaultValues;
QMap<QString, QString> CurrentValues;
rpl::event_stream<> LangChanges;
QString LangDir() {
return cWorkingDir() + "tdata/ktg_lang/";
}
void ParseLanguageData(
const QString &langCode,
bool isDefault) {
const auto filename = isDefault
? qsl(":/ktg_lang/%1.json").arg(langCode)
: LangDir() + (qsl("%1.json").arg(langCode));
QFile file(filename);
if (!file.exists()) {
return;
}
if (!file.open(QIODevice::ReadOnly)) {
LOG(("Kotato::Lang Info: file %1 could not be read.").arg(filename));
return;
}
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
const auto document = QJsonDocument::fromJson(
base::parse::stripComments(file.readAll()),
&error);
file.close();
if (error.error != QJsonParseError::NoError) {
LOG(("Kotato::Lang Info: file %1 has failed to parse. Error: %2"
).arg(filename
).arg(error.errorString()));
return;
} else if (!document.isObject()) {
LOG(("Kotato::Lang Info: file %1 has failed to parse. Error: object expected"
).arg(filename));
return;
}
const auto applyValue = [&](const QString &name, const QString &translation) {
if (langCode == kDefaultLanguage) {
DefaultValues.insert(name, translation);
} else {
CurrentValues.insert(name, translation);
}
};
const auto langKeys = document.object();
const auto keyList = langKeys.keys();
for (auto i = keyList.constBegin(), e = keyList.constEnd(); i != e; ++i) {
const auto key = *i;
if (key.startsWith("dummy_")) {
continue;
}
const auto value = langKeys.constFind(key);
if ((*value).isString()) {
applyValue(key, (*value).toString());
} else if ((*value).isObject()) {
const auto keyPlurals = (*value).toObject();
const auto pluralList = keyPlurals.keys();
for (auto pli = pluralList.constBegin(), ple = pluralList.constEnd(); pli != ple; ++pli) {
const auto plural = *pli;
const auto pluralValue = keyPlurals.constFind(plural);
if (!(*pluralValue).isString()) {
LOG(("Kotato::Lang Info: wrong value for key %1 in %2 in file %3, string expected")
.arg(plural).arg(key).arg(filename));
continue;
}
const auto name = QString(key + "#" + plural);
const auto translation = (*pluralValue).toString();
applyValue(name, translation);
}
} else {
LOG(("Kotato::Lang Info: wrong value for key %1 in file %2, string or object expected")
.arg(key).arg(filename));
}
}
}
void UnpackDefault() {
const auto langDir = LangDir();
if (!QDir().exists(langDir)) QDir().mkpath(langDir);
const auto langs = QDir(":/ktg_lang").entryList(QStringList() << "*.json", QDir::Files);
auto neededLangs = QStringList() << kDefaultLanguage << LangCode << BaseLangCode;
neededLangs.removeDuplicates();
for (auto language : langs) {
language.chop(5);
if (!neededLangs.contains(language)) {
continue;
}
const auto path = langDir + language + ".default.json";
auto input = QFile(qsl(":/ktg_lang/%1.json").arg(language));
auto output = QFile(path);
if (input.open(QIODevice::ReadOnly)) {
auto inputData = input.readAll();
if (output.open(QIODevice::WriteOnly)) {
output.write(inputData);
output.close();
}
input.close();
}
}
}
} // namespace
void Load(const QString &baseLangCode, const QString &langCode) {
BaseLangCode = baseLangCode;
if (BaseLangCode.endsWith("-raw")) {
BaseLangCode.chop(4);
}
LangCode = langCode.isEmpty() ? baseLangCode : langCode;
if (LangCode.endsWith("-raw")) {
LangCode.chop(4);
}
DefaultValues.clear();
CurrentValues.clear();
if (BaseLangCode != kDefaultLanguage) {
ParseLanguageData(kDefaultLanguage, true);
ParseLanguageData(kDefaultLanguage, false);
}
ParseLanguageData(BaseLangCode, true);
ParseLanguageData(BaseLangCode, false);
if (LangCode != BaseLangCode) {
ParseLanguageData(LangCode, true);
ParseLanguageData(LangCode, false);
}
UnpackDefault();
LangChanges.fire({});
}
QString Translate(const QString &key, Var var1, Var var2, Var var3, Var var4) {
auto phrase = (CurrentValues.contains(key) && !CurrentValues.value(key).isEmpty())
? CurrentValues.value(key)
: DefaultValues.value(key);
for (const auto &v : { var1, var2, var3, var4 }) {
if (!v.key.isEmpty()) {
auto skipNext = false;
const auto key = qsl("{%1}").arg(v.key);
const auto neededLength = phrase.length() - key.length();
for (auto i = 0; i <= neededLength; i++) {
if (skipNext) {
skipNext = false;
continue;
}
if (phrase.at(i) == QChar('\\')) {
skipNext = true;
} else if (phrase.at(i) == QChar('{') && phrase.mid(i, key.length()) == key) {
phrase.replace(i, key.length(), v.value);
break;
}
}
}
}
return phrase;
}
QString Translate(const QString &key, float64 value, Var var1, Var var2, Var var3, Var var4) {
const auto shift = ::Lang::PluralShift(value);
return Translate(key + kPostfixes.at(shift), var1, var2, var3);
}
TextWithEntities TranslateWithEntities(const QString &key, EntVar var1, EntVar var2, EntVar var3, EntVar var4) {
TextWithEntities phrase = {
(CurrentValues.contains(key) && !CurrentValues.value(key).isEmpty())
? CurrentValues.value(key)
: DefaultValues.value(key)
};
for (const auto &v : { var1, var2, var3, var4 }) {
if (!v.key.isEmpty()) {
auto skipNext = false;
const auto key = qsl("{%1}").arg(v.key);
const auto neededLength = phrase.text.length() - key.length();
for (auto i = 0; i <= neededLength; i++) {
if (skipNext) {
skipNext = false;
continue;
}
if (phrase.text.at(i) == QChar('\\')) {
skipNext = true;
} else if (phrase.text.at(i) == QChar('{') && phrase.text.mid(i, key.length()) == key) {
phrase.text.replace(i, key.length(), v.value.text);
const auto endOld = i + key.length();
const auto endNew = i + v.value.text.length();
// Shift existing entities
if (endNew > endOld) {
const auto diff = endNew - endOld;
for (auto &entity : phrase.entities) {
if (entity.offset() > endOld) {
entity.shiftRight(diff);
} else if (entity.offset() <= i && entity.offset() + entity.length() >= endOld) {
entity.extendToRight(diff);
}
}
} else if (endNew < endOld) {
const auto diff = endOld - endNew;
for (auto &entity : phrase.entities) {
if (entity.offset() > endNew) {
entity.shiftLeft(diff);
} else if (entity.offset() <= i && entity.offset() + entity.length() >= endNew) {
entity.shrinkFromRight(diff);
}
}
}
// Add new entities
for (auto entity : v.value.entities) {
phrase.entities.append(EntityInText(
entity.type(),
entity.offset() + i,
entity.length(),
entity.data()));
}
break;
}
}
}
}
return phrase;
}
TextWithEntities TranslateWithEntities(const QString &key, float64 value, EntVar var1, EntVar var2, EntVar var3, EntVar var4) {
const auto shift = ::Lang::PluralShift(value);
return TranslateWithEntities(key + kPostfixes.at(shift), var1, var2, var3, var4);
}
rpl::producer<> Events() {
return LangChanges.events();
}
} // namespace Lang
} // namespace Kotato