Start cloud langpack support.

Change the way langpacks are stored.
Support custom langpacks in the new storage.
This commit is contained in:
John Preston 2017-04-13 20:59:05 +03:00
parent 2334ba1fe1
commit 139d4e72b5
43 changed files with 1264 additions and 741 deletions

View File

@ -51,6 +51,10 @@ ApiWrap::ApiWrap()
Window::Theme::Background()->start();
}
void ApiWrap::applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId) {
App::main()->feedUpdates(updates, sentMessageRandomId);
}
void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback) {
auto &req = (channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]);
if (callback) {
@ -114,10 +118,6 @@ void ApiWrap::resolveMessageDatas() {
}
}
void ApiWrap::updatesReceived(const MTPUpdates &updates) {
App::main()->sentUpdatesReceived(updates);
}
void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId requestId) {
switch (msgs.type()) {
case mtpc_messages_messages: {
@ -658,7 +658,7 @@ void ApiWrap::kickParticipant(PeerData *peer, UserData *user) {
if (auto channel = peer->asChannel()) {
auto requestId = request(MTPchannels_KickFromChannel(channel->inputChannel, user->inputUser, MTP_bool(true))).done([this, peer, user](const MTPUpdates &result) {
App::main()->sentUpdatesReceived(result);
applyUpdates(result);
_kickRequests.remove(KickRequest(peer, user));
if (auto channel = peer->asMegagroup()) {
@ -704,7 +704,7 @@ void ApiWrap::unblockParticipant(PeerData *peer, UserData *user) {
if (auto channel = peer->asChannel()) {
auto requestId = request(MTPchannels_KickFromChannel(channel->inputChannel, user->inputUser, MTP_bool(false))).done([this, peer, user](const MTPUpdates &result) {
App::main()->sentUpdatesReceived(result);
applyUpdates(result);
_kickRequests.remove(KickRequest(peer, user));
if (auto channel = peer->asMegagroup()) {
@ -877,7 +877,7 @@ void ApiWrap::joinChannel(ChannelData *channel) {
} else if (!_channelAmInRequests.contains(channel)) {
auto requestId = request(MTPchannels_JoinChannel(channel->inputChannel)).done([this, channel](const MTPUpdates &result) {
_channelAmInRequests.remove(channel);
updatesReceived(result);
applyUpdates(result);
}).fail([this, channel](const RPCError &error) {
if (error.type() == qstr("CHANNELS_TOO_MUCH")) {
Ui::show(Box<InformBox>(lang(lng_join_channel_error)));
@ -895,7 +895,7 @@ void ApiWrap::leaveChannel(ChannelData *channel) {
} else if (!_channelAmInRequests.contains(channel)) {
auto requestId = request(MTPchannels_LeaveChannel(channel->inputChannel)).done([this, channel](const MTPUpdates &result) {
_channelAmInRequests.remove(channel);
updatesReceived(result);
applyUpdates(result);
}).fail([this, channel](const RPCError &error) {
_channelAmInRequests.remove(channel);
}).send();

View File

@ -40,6 +40,8 @@ class ApiWrap : private MTP::Sender {
public:
ApiWrap();
void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0);
using RequestMessageDataCallback = base::lambda<void(ChannelData*, MsgId)>;
void requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback);

View File

@ -89,12 +89,10 @@ void AboutBox::keyPressEvent(QKeyEvent *e) {
QString telegramFaqLink() {
auto result = qsl("https://telegram.org/faq");
if (cLang() > languageDefault && cLang() < languageCount) {
const char *code = LanguageCodes[cLang()].c_str();
if (qstr("de") == code || qstr("es") == code || qstr("it") == code || qstr("ko") == code) {
result += '/' + code;
} else if (qstr("pt_BR") == code) {
result += qsl("/br");
auto language = Lang::Current().id();
for (auto faqLanguage : { "de", "es", "it", "ko", "br" }) {
if (language.startsWith(QLatin1String(faqLanguage))) {
result.append('/').append(faqLanguage);
}
}
return result;

View File

@ -27,7 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "boxes/confirm_box.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "lang/lang_file_parser.h"
#include "lang/lang_instance.h"
#include "styles/style_boxes.h"
void LanguageBox::prepare() {
@ -35,75 +35,74 @@ void LanguageBox::prepare() {
setTitle(lang(lng_languages));
auto haveTestLang = (cLang() == languageTest);
_langGroup = std::make_shared<Ui::RadiobuttonGroup>(cLang());
auto y = st::boxOptionListPadding.top();
_langs.reserve(languageCount + (haveTestLang ? 1 : 0));
if (haveTestLang) {
_langs.emplace_back(this, _langGroup, languageTest, qsl("Custom Lang"), st::langsButton);
_langs.back()->move(st::boxPadding.left() + st::boxOptionListPadding.left(), y);
y += _langs.back()->heightNoMargins() + st::boxOptionListSkip;
}
for (auto i = 0; i != languageCount; ++i) {
Lang::FileParser::Result result;
if (i) {
Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), { lng_language_name });
result = loader.found();
} else {
result.insert(lng_language_name, langOriginal(lng_language_name));
}
_langs.emplace_back(this, _langGroup, i, result.value(lng_language_name, LanguageCodes[i].c_str() + qsl(" language")), st::langsButton);
_langs.back()->move(st::boxPadding.left() + st::boxOptionListPadding.left(), y);
y += _langs.back()->heightNoMargins() + st::boxOptionListSkip;
}
_langGroup->setChangedCallback([this](int value) { languageChanged(value); });
auto optionsCount = languageCount + (haveTestLang ? 1 : 0);
setDimensions(st::langsWidth, st::boxOptionListPadding.top() + optionsCount * st::langsButton.height + (optionsCount - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom());
}
void LanguageBox::mousePressEvent(QMouseEvent *e) {
if ((e->modifiers() & Qt::CTRL) && (e->modifiers() & Qt::ALT) && (e->modifiers() & Qt::SHIFT)) {
for (int32 i = 1; i < languageCount; ++i) {
Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), { kLangKeysCount });
if (!loader.errors().isEmpty()) {
Ui::show(Box<InformBox>(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" error :(\n\nError: ") + loader.errors()));
return;
} else if (!loader.warnings().isEmpty()) {
auto warn = loader.warnings();
if (warn.size() > 256) warn = warn.mid(0, 253) + qsl("...");
Ui::show(Box<InformBox>(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" warnings :(\n\nWarnings: ") + warn));
return;
request(MTPlangpack_GetLanguages()).done([this](const MTPVector<MTPLangPackLanguage> &result) {
auto currentId = Lang::Current().id();
auto currentFound = false;
std::vector<QString> languageIds = { qsl("en") };
std::vector<QString> languageNames = { qsl("English") };
for (auto &language : result.v) {
t_assert(language.type() == mtpc_langPackLanguage);
auto &data = language.c_langPackLanguage();
auto languageId = qs(data.vlang_code);
auto languageName = qs(data.vname);
if (languageId != qstr("en")) {
languageIds.push_back(languageId);
languageNames.push_back(languageName);
}
}
Ui::show(Box<InformBox>(qsl("Everything seems great in all %1 languages!").arg(languageCount - 1)));
}
if (currentId == qstr("custom")) {
languageIds.insert(languageIds.begin(), currentId);
languageNames.insert(languageNames.begin(), qsl("Custom LangPack"));
currentFound = true;
}
auto languageCount = languageIds.size();
_langGroup = std::make_shared<Ui::RadiobuttonGroup>(cLang());
auto y = st::boxOptionListPadding.top();
_langs.reserve(languageCount);
for (auto i = 0; i != languageCount; ++i) {
if (!currentFound && languageIds[i] == currentId) {
currentFound = true;
}
_langs.emplace_back(this, _langGroup, i, languageNames[i], st::langsButton);
auto button = _langs.back().data();
button->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), y + st::langsButton.margin.top());
button->show();
y += button->heightNoMargins() + st::boxOptionListSkip;
}
_langGroup->setChangedCallback([this](int value) { languageChanged(value); });
setDimensions(st::langsWidth, st::boxOptionListPadding.top() + languageCount * st::langsButton.height + languageCount * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom());
}).fail([this](const RPCError &error) {
closeBox();
}).send();
setDimensions(st::langsWidth, st::langsWidth);
}
void LanguageBox::languageChanged(int languageId) {
Expects(languageId == languageTest || (languageId >= 0 && languageId < base::array_size(LanguageCodes)));
//Expects(languageId == languageTest || (languageId >= 0 && languageId < base::array_size(LanguageCodes)));
if (languageId == cLang()) {
return;
}
//if (languageId == cLang()) {
// return;
//}
Lang::FileParser::Result result;
if (languageId > 0) {
Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[languageId].c_str() + qsl(".strings"), { lng_sure_save_language, lng_cancel, lng_box_ok });
result = loader.found();
} else if (languageId == languageTest) {
Lang::FileParser loader(cLangFile(), { lng_sure_save_language, lng_cancel, lng_box_ok });
result = loader.found();
}
auto text = result.value(lng_sure_save_language, langOriginal(lng_sure_save_language)),
save = result.value(lng_box_ok, langOriginal(lng_box_ok)),
cancel = result.value(lng_cancel, langOriginal(lng_cancel));
Ui::show(Box<ConfirmBox>(text, save, cancel, base::lambda_guarded(this, [this, languageId] {
cSetLang(languageId);
Local::writeSettings();
App::restart();
}), base::lambda_guarded(this, [this] {
_langGroup->setValue(cLang());
})), KeepOtherLayers);
//Lang::FileParser::Result result;
//if (languageId > 0) {
// Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[languageId].c_str() + qsl(".strings"), { lng_sure_save_language, lng_cancel, lng_box_ok });
// result = loader.found();
//} else if (languageId == languageTest) {
// Lang::FileParser loader(cLangFile(), { lng_sure_save_language, lng_cancel, lng_box_ok });
// result = loader.found();
//}
//auto text = result.value(lng_sure_save_language, Lang::GetOriginalValue(lng_sure_save_language)),
// save = result.value(lng_box_ok, Lang::GetOriginalValue(lng_box_ok)),
// cancel = result.value(lng_cancel, Lang::GetOriginalValue(lng_cancel));
//Ui::show(Box<ConfirmBox>(text, save, cancel, base::lambda_guarded(this, [this, languageId] {
// cSetLang(languageId);
// Local::writeSettings();
// App::restart();
//}), base::lambda_guarded(this, [this] {
// _langGroup->setValue(cLang());
//})), KeepOtherLayers);
}

View File

@ -21,13 +21,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "boxes/abstract_box.h"
#include "mtproto/sender.h"
namespace Ui {
class RadiobuttonGroup;
class Radiobutton;
} // namespace Ui
class LanguageBox : public BoxContent {
class LanguageBox : public BoxContent, private MTP::Sender {
Q_OBJECT
public:
@ -37,8 +38,6 @@ public:
protected:
void prepare() override;
void mousePressEvent(QMouseEvent *e) override;
private:
void languageChanged(int languageId);

View File

@ -118,14 +118,6 @@ QRect computeSourceRect(const QImage &image) {
return result;
}
QString computeId(Id id) {
auto idAsParams = QStringList();
for (auto i = 0, size = id.size(); i != size; ++i) {
idAsParams.push_back("0x" + QString::number(id[i].unicode(), 16));
}
return "internal::ComputeId(" + idAsParams.join(", ") + ")";
}
} // namespace
Generator::Generator(const Options &options) : project_(Project)
@ -305,20 +297,11 @@ EmojiPtr Find(const QChar *start, const QChar *end, int *outLength) {\n\
return index ? &Items[index - 1] : nullptr;\n\
}\n\
\n\
inline QString ComputeId(gsl::span<ushort> utf16) {\n\
auto result = QString();\n\
result.reserve(utf16.size());\n\
for (auto ch : utf16) {\n\
result.append(QChar(ch));\n\
}\n\
return result;\n\
}\n\
\n\
void Init() {\n\
auto id = IdData;\n\
Items.reserve(base::array_size(Data));\n\
for (auto &data : Data) {\n\
Items.emplace_back(ComputeId(gsl::make_span(id, data.idSize)), data.column, data.row, data.postfixed, data.variated, data.original ? &Items[data.original - 1] : nullptr, One::CreationTag());\n\
Items.emplace_back(QString::fromRawData(id, data.idSize), data.column, data.row, data.postfixed, data.variated, data.original ? &Items[data.original - 1] : nullptr, One::CreationTag());\n\
id += data.idSize;\n\
}\n\
}\n\
@ -429,7 +412,7 @@ struct DataStruct {\n\
bool variated;\n\
};\n\
\n\
ushort IdData[] = {";
QChar IdData[] = {";
auto count = 0;
auto fulllength = 0;
if (!enumerateWholeList([this, &count, &fulllength](Id id, int column, int row, bool isPostfixed, bool isVariated, bool isColored, int original) {

View File

@ -24,7 +24,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include <functional>
#include <QtCore/QDir>
#include <QtCore/QSet>
#include <QtCore/QBuffer>
#include <QtGui/QImage>
#include <QtGui/QPainter>
@ -109,7 +108,7 @@ QString stringToBinaryArray(const std::string &str) {
} // namespace
Generator::Generator(const Langpack &langpack, const QString &destBasePath, const common::ProjectInfo &project)
Generator::Generator(const LangPack &langpack, const QString &destBasePath, const common::ProjectInfo &project)
: langpack_(langpack)
, basePath_(destBasePath)
, baseName_(QFileInfo(basePath_).baseName())
@ -141,8 +140,6 @@ enum LangKey {\n";
};\n\
\n\
QString lang(LangKey key);\n\
\n\
QString langOriginal(LangKey key);\n\
\n";
for (auto &entry : langpack_.entries) {
if (!entry.tags.empty()) {
@ -172,7 +169,7 @@ ushort GetTagIndex(QLatin1String tag);\n\
LangKey GetKeyIndex(QLatin1String key);\n\
LangKey GetSubkeyIndex(LangKey key, ushort tag, ushort index);\n\
bool IsTagReplaced(LangKey key, ushort tag);\n\
void FeedKeyValue(LangKey key, const QString &value);\n\
QString GetOriginalValue(LangKey key);\n\
\n";
return header_->finalize();
@ -191,26 +188,47 @@ const char *KeyNames[kLangKeysCount] = {\n\
\n\
};\n\
\n\
QString Values[kLangKeysCount], OriginalValues[kLangKeysCount];\n\
\n\
void set(LangKey key, const QString &val) {\n\
Values[key] = val;\n\
}\n\
\n\
class Initializer {\n\
public:\n\
Initializer() {\n";
QChar DefaultData[] = {";
auto count = 0;
auto fulllength = 0;
for (auto &entry : langpack_.entries) {
source_->stream() << "\t\tset(" << getFullKey(entry) << ", QString::fromUtf8(" << stringToEncodedString(entry.value) << "));\n";
for (auto ch : entry.value) {
if (fulllength > 0) source_->stream() << ",";
if (!count++) {
source_->stream() << "\n";
} else {
if (count == 12) {
count = 0;
}
source_->stream() << " ";
}
source_->stream() << "0x" << QString::number(ch.unicode(), 16);
++fulllength;
}
}
source_->stream() << "\
}\n\
source_->stream() << " };\n\
\n\
};\n\
\n\
Initializer Instance;\n\
\n";
int Offsets[] = {";
count = 0;
auto offset = 0;
auto writeOffset = [this, &count, &offset] {
if (offset > 0) source_->stream() << ",";
if (!count++) {
source_->stream() << "\n";
} else {
if (count == 12) {
count = 0;
}
source_->stream() << " ";
}
source_->stream() << offset;
};
for (auto &entry : langpack_.entries) {
writeOffset();
offset += entry.value.size();
}
writeOffset();
source_->stream() << " };\n";
source_->popNamespace().stream() << "\
\n\
const char *GetKeyName(LangKey key) {\n\
@ -314,25 +332,13 @@ bool IsTagReplaced(LangKey key, ushort tag) {\n\
return false;\n\
}\n\
\n\
void FeedKeyValue(LangKey key, const QString &value) {\n\
QString GetOriginalValue(LangKey key) {\n\
Expects(key >= 0 && key < kLangKeysCount);\n\
if (OriginalValues[key].isEmpty()) {\n\
OriginalValues[key] = Values[key].isEmpty() ? qsl(\"{}\") : Values[key];\n\
}\n\
Values[key] = value;\n\
auto offset = Offsets[key];\n\
return QString::fromRawData(DefaultData + offset, Offsets[key + 1] - offset);\n\
}\n\
\n";
source_->popNamespace().stream() << "\
\n\
QString lang(LangKey key) {\n\
return (key < 0 || key >= kLangKeysCount) ? QString() : Lang::Values[key];\n\
}\n\
\n\
QString langOriginal(LangKey key) {\n\
return (key < 0 || key >= kLangKeysCount || Lang::OriginalValues[key] == qsl(\"{}\")) ? QString() : (Lang::OriginalValues[key].isEmpty() ? Lang::Values[key] : Lang::OriginalValues[key]);\n\
}\n";
return source_->finalize();
}
@ -473,7 +479,7 @@ void Generator::writeSetSearch(const std::set<QString, std::greater<QString>> &s
return " << invalidResult << ";\n";
}
QString Generator::getFullKey(const Langpack::Entry &entry) {
QString Generator::getFullKey(const LangPack::Entry &entry) {
if (entry.tags.empty()) {
return entry.key;
}

View File

@ -34,7 +34,7 @@ namespace lang {
class Generator {
public:
Generator(const Langpack &langpack, const QString &destBasePath, const common::ProjectInfo &project);
Generator(const LangPack &langpack, const QString &destBasePath, const common::ProjectInfo &project);
Generator(const Generator &other) = delete;
Generator &operator=(const Generator &other) = delete;
@ -42,13 +42,13 @@ public:
bool writeSource();
private:
QString getFullKey(const Langpack::Entry &entry);
QString getFullKey(const LangPack::Entry &entry);
bool isTagPlural(const QString &key, const QString &tag) const;
template <typename ComputeResult>
void writeSetSearch(const std::set<QString, std::greater<QString>> &set, ComputeResult computeResult, const QString &invalidResult);
const Langpack &langpack_;
const LangPack &langpack_;
QString basePath_, baseName_;
const common::ProjectInfo &project_;
std::unique_ptr<common::CppFile> source_, header_;

View File

@ -128,7 +128,7 @@ common::LogStream ParsedFile::logErrorBadString() {
return logError(kErrorBadString);
}
QString ParsedFile::extractTagsData(const QString &value, Langpack *to) {
QString ParsedFile::extractTagsData(const QString &value, LangPack *to) {
auto tagStart = value.indexOf('{');
if (tagStart < 0) {
return value;
@ -157,7 +157,7 @@ QString ParsedFile::extractTagsData(const QString &value, Langpack *to) {
return finalValue;
}
QString ParsedFile::extractTagData(const QString &tagText, Langpack *to) {
QString ParsedFile::extractTagData(const QString &tagText, LangPack *to) {
auto numericPart = tagText.indexOf(':');
auto tag = (numericPart > 0) ? tagText.mid(0, numericPart) : tagText;
if (!ValidateTag(tag)) {
@ -190,7 +190,7 @@ QString ParsedFile::extractTagData(const QString &tagText, Langpack *to) {
}
auto index = 0;
for (auto &part : numericParts) {
auto numericPartEntry = Langpack::Entry();
auto numericPartEntry = LangPack::Entry();
numericPartEntry.key = tag + QString::number(index++);
if (part.indexOf('#') != part.lastIndexOf('#')) {
logErrorBadString() << "bad option for plural key part in tag: '" << tagText.toStdString() << "', too many '#'.";
@ -211,14 +211,14 @@ void ParsedFile::addEntity(const QString &key, const QString &value) {
return;
}
}
auto tagsData = Langpack();
auto entry = Langpack::Entry();
auto tagsData = LangPack();
auto entry = LangPack::Entry();
entry.key = key;
entry.value = extractTagsData(value, &tagsData);
entry.tags = tagsData.tags;
result_.entries.push_back(entry);
for (auto &pluralEntry : tagsData.entries) {
auto taggedEntry = Langpack::Entry();
auto taggedEntry = LangPack::Entry();
taggedEntry.key = key + "__" + pluralEntry.key;
taggedEntry.value = pluralEntry.value;
result_.entries.push_back(taggedEntry);

View File

@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace codegen {
namespace lang {
struct Langpack {
struct LangPack {
struct Tag {
QString tag;
};
@ -53,7 +53,7 @@ public:
bool read();
Langpack getResult() {
LangPack getResult() {
return result_;
}
@ -84,14 +84,14 @@ private:
BasicToken assertNextToken(BasicToken::Type type);
void addEntity(const QString &key, const QString &value);
QString extractTagsData(const QString &value, Langpack *to);
QString extractTagData(const QString &tag, Langpack *to);
QString extractTagsData(const QString &value, LangPack *to);
QString extractTagData(const QString &tag, LangPack *to);
QString filePath_;
common::BasicTokenizedFile file_;
Options options_;
bool failed_ = false;
Langpack result_;
LangPack result_;
};

View File

@ -51,7 +51,7 @@ int Processor::launch() {
return 0;
}
bool Processor::write(const Langpack &langpack) const {
bool Processor::write(const LangPack &langpack) const {
bool forceReGenerate = false;
QDir dir(options_.outputPath);
if (!dir.mkpath(".")) {

View File

@ -27,7 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace codegen {
namespace lang {
class ParsedFile;
struct Langpack;
struct LangPack;
// Walks through a file, parses it and generates the output.
class Processor {
@ -42,7 +42,7 @@ public:
~Processor();
private:
bool write(const Langpack &langpack) const;
bool write(const LangPack &langpack) const;
std::unique_ptr<ParsedFile> parser_;
const Options &options_;

View File

@ -305,9 +305,6 @@ inline const QString &cTempDir() {
return res;
}
static const char *DefaultCountry = "US";
static const char *DefaultLanguage = "en";
enum {
DialogsFirstLoad = 20, // first dialogs part size requested
DialogsPerPage = 500, // next dialogs part size

View File

@ -412,9 +412,6 @@ namespace Sandbox {
namespace internal {
struct Data {
QString LangSystemISO;
int32 LangSystem = languageDefault;
QByteArray LastCrashDump;
ProxyData PreLaunchProxy;
};
@ -523,16 +520,6 @@ void start() {
base::TaskQueue::ProcessMainTasks();
});
SandboxData = std::make_unique<internal::Data>();
SandboxData->LangSystemISO = psCurrentLanguage();
if (SandboxData->LangSystemISO.isEmpty()) SandboxData->LangSystemISO = qstr("en");
auto l = LangSystemISO().toLatin1();
for (auto i = 0; i < languageCount; ++i) {
if (l.at(0) == LanguageCodes[i][0] && l.at(1) == LanguageCodes[i][1]) {
SandboxData->LangSystem = i;
break;
}
}
}
bool started() {
@ -548,8 +535,6 @@ uint64 UserTag() {
return SandboxUserTag;
}
DefineReadOnlyVar(Sandbox, QString, LangSystemISO);
DefineReadOnlyVar(Sandbox, int32, LangSystem);
DefineVar(Sandbox, QByteArray, LastCrashDump);
DefineVar(Sandbox, ProxyData, PreLaunchProxy);

View File

@ -218,8 +218,6 @@ void finish();
uint64 UserTag();
DeclareReadOnlyVar(QString, LangSystemISO);
DeclareReadOnlyVar(int32, LangSystem);
DeclareVar(QByteArray, LastCrashDump);
DeclareVar(ProxyData, PreLaunchProxy);

View File

@ -47,12 +47,21 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "auth_session.h"
namespace Intro {
namespace {
constexpr str_const kDefaultCountry = "US";
} // namespace
Widget::Widget(QWidget *parent) : TWidget(parent)
, _back(this, object_ptr<Ui::IconButton>(this, st::introBackButton), st::introSlideDuration)
, _settings(this, object_ptr<Ui::RoundButton>(this, lang(lng_menu_settings), st::defaultBoxButton), st::introCoverDuration)
, _next(this, QString(), st::introNextButton) {
getData()->country = psCurrentCountry();
auto country = Platform::SystemCountry();
if (country.isEmpty()) {
country = str_const_toString(kDefaultCountry);
}
getData()->country = country;
_back->entity()->setClickedCallback([this] { historyMove(Direction::Back); });
_back->hideFast();
@ -61,20 +70,20 @@ Widget::Widget(QWidget *parent) : TWidget(parent)
_settings->entity()->setClickedCallback([] { App::wnd()->showSettings(); });
if (cLang() == languageDefault) {
auto systemLangId = Sandbox::LangSystem();
if (systemLangId != languageDefault) {
Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[systemLangId].c_str() + qsl(".strings"), { lng_switch_to_this });
auto text = loader.found().value(lng_switch_to_this);
if (!text.isEmpty()) {
_changeLanguage.create(this, object_ptr<Ui::LinkButton>(this, text), st::introCoverDuration);
_changeLanguage->entity()->setClickedCallback([this, systemLangId] { changeLanguage(systemLangId); });
}
}
} else {
_changeLanguage.create(this, object_ptr<Ui::LinkButton>(this, langOriginal(lng_switch_to_this)), st::introCoverDuration);
_changeLanguage->entity()->setClickedCallback([this] { changeLanguage(languageDefault); });
}
//if (cLang() == languageDefault) {
// auto systemLangId = Sandbox::LangSystem();
// if (systemLangId != languageDefault) {
// Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[systemLangId].c_str() + qsl(".strings"), { lng_switch_to_this });
// auto text = loader.found().value(lng_switch_to_this);
// if (!text.isEmpty()) {
// _changeLanguage.create(this, object_ptr<Ui::LinkButton>(this, text), st::introCoverDuration);
// _changeLanguage->entity()->setClickedCallback([this, systemLangId] { changeLanguage(systemLangId); });
// }
// }
//} else {
// _changeLanguage.create(this, object_ptr<Ui::LinkButton>(this, Lang::GetOriginalValue(lng_switch_to_this)), st::introCoverDuration);
// _changeLanguage->entity()->setClickedCallback([this] { changeLanguage(languageDefault); });
//}
MTP::send(MTPhelp_GetNearestDc(), rpcDone(&Widget::gotNearestDC));

View File

@ -23,73 +23,35 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "base/parse_helper.h"
namespace Lang {
namespace {
constexpr auto kLangFileLimit = 1024 * 1024;
} // namespace
FileParser::FileParser(const QString &file, const std::set<LangKey> &request)
: _filePath(file)
, _request(request)
, _readingAll(request.find(kLangKeysCount) != request.end()) {
QFile f(_filePath);
if (!f.open(QIODevice::ReadOnly)) {
error(qsl("Could not open input file!"));
: _content(base::parse::stripComments(ReadFile(file, file)))
, _request(request) {
parse();
}
FileParser::FileParser(const QByteArray &content, base::lambda<void(QLatin1String key, const QByteArray &value)> callback)
: _content(base::parse::stripComments(content))
, _callback(std::move(callback)) {
parse();
}
void FileParser::parse() {
if (_content.isEmpty()) {
error(qsl("Got empty lang file content"));
return;
}
if (f.size() > 1024 * 1024) {
error(qsl("Too big file: %1").arg(f.size()));
return;
}
QByteArray checkCodec = f.read(3);
if (checkCodec.size() < 3) {
error(qsl("Bad lang input file: %1").arg(file));
return;
}
f.seek(0);
QByteArray data;
int skip = 0;
if ((checkCodec.at(0) == '\xFF' && checkCodec.at(1) == '\xFE') || (checkCodec.at(0) == '\xFE' && checkCodec.at(1) == '\xFF') || (checkCodec.at(1) == 0)) {
QTextStream stream(&f);
stream.setCodec("UTF-16");
QString string = stream.readAll();
if (stream.status() != QTextStream::Ok) {
error(qsl("Could not read valid UTF-16 file: % 1").arg(file));
return;
auto text = _content.constData(), end = text + _content.size();
while (text != end) {
if (!readKeyValue(text, end)) {
break;
}
f.close();
data = string.toUtf8();
} else if (checkCodec.at(0) == 0) {
QByteArray tmp = "\xFE\xFF" + f.readAll(); // add fake UTF-16 BOM
f.close();
QTextStream stream(&tmp);
stream.setCodec("UTF-16");
QString string = stream.readAll();
if (stream.status() != QTextStream::Ok) {
error(qsl("Could not read valid UTF-16 file: % 1").arg(file));
return;
}
data = string.toUtf8();
} else {
data = f.readAll();
if (checkCodec.at(0) == '\xEF' && checkCodec.at(1) == '\xBB' && checkCodec.at(2) == '\xBF') {
skip = 3; // skip UTF-8 BOM
}
}
data = base::parse::stripComments(data);
auto text = data.constData() + skip, end = text + data.size() - skip;
try {
while (text != end) {
if (!readKeyValue(text, end)) {
break;
}
}
} catch (std::exception &e) {
error(QString::fromUtf8(e.what()));
return;
}
}
@ -101,217 +63,152 @@ const QString &FileParser::errors() const {
}
const QString &FileParser::warnings() const {
if (!_checked) {
for (auto i = 0; i < kLangKeysCount; ++i) {
if (!_found[i]) {
_warningsList.push_back(qsl("No value found for key '%1'").arg(GetKeyName(LangKey(i))));
}
}
_checked = true;
}
if (_warnings.isEmpty() && !_warningsList.isEmpty()) {
_warnings = _warningsList.join('\n');
}
return _warnings;
}
void FileParser::foundKeyValue(LangKey key) {
if (key < kLangKeysCount) {
_found[key] = true;
}
}
bool FileParser::readKeyValue(const char *&from, const char *end) {
using base::parse::skipWhitespaces;
if (!skipWhitespaces(from, end)) return false;
if (*from != '"') throw Exception(QString("Expected quote before key name!"));
if (*from != '"') {
return error("Expected quote before key name!");
}
++from;
const char *nameStart = from;
while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) {
++from;
}
auto varName = QLatin1String(nameStart, from - nameStart);
auto key = QLatin1String(nameStart, from - nameStart);
if (from == end || *from != '"') throw Exception(QString("Expected quote after key name '%1'!").arg(varName));
if (from == end || *from != '"') {
return error(qsl("Expected quote after key name '%1'!").arg(key));
}
++from;
if (!skipWhitespaces(from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName));
if (*from != '=') throw Exception(QString("'=' expected in key '%1'!").arg(varName));
if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName));
if (*from != '"') throw Exception(QString("Expected string after '=' in key '%1'!").arg(varName));
auto varKey = GetKeyIndex(varName);
bool feedingValue = _request.empty();
if (feedingValue) {
if (varKey == kLangKeysCount) {
warning(QString("Unknown key '%1'!").arg(varName));
}
} else if (!_readingAll && _request.find(varKey) == _request.end()) {
varKey = kLangKeysCount;
if (!skipWhitespaces(from, end)) {
return error(qsl("Unexpected end of file in key '%1'!").arg(key));
}
if (*from != '=') {
return error(qsl("'=' expected in key '%1'!").arg(key));
}
if (!skipWhitespaces(++from, end)) {
return error(qsl("Unexpected end of file in key '%1'!").arg(key));
}
if (*from != '"') {
return error(qsl("Expected string after '=' in key '%1'!").arg(key));
}
bool readingValue = (varKey != kLangKeysCount);
QByteArray varValue;
QMap<ushort, bool> tagsUsed;
auto skipping = false;
auto keyIndex = kLangKeysCount;
if (!_callback) {
keyIndex = GetKeyIndex(key);
skipping = (_request.find(keyIndex) == _request.end());
}
auto value = QByteArray();
auto appendValue = [&value, skipping](auto&&... args) {
if (!skipping) {
value.append(std::forward<decltype(args)>(args)...);
}
};
const char *start = ++from;
while (from < end && *from != '"') {
if (*from == '\n') {
throw Exception(QString("Unexpected end of string in key '%1'!").arg(varName));
return error(qsl("Unexpected end of string in key '%1'!").arg(key));
}
if (*from == '\\') {
if (from + 1 >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName));
if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{') {
if (readingValue && from > start) varValue.append(start, from - start);
if (from + 1 >= end) {
return error(qsl("Unexpected end of file in key '%1'!").arg(key));
}
if (*(from + 1) == '"' || *(from + 1) == '\\') {
if (from > start) appendValue(start, from - start);
start = ++from;
} else if (*(from + 1) == 'n') {
if (readingValue) {
if (from > start) varValue.append(start, int(from - start));
varValue.append('\n');
}
if (from > start) appendValue(start, from - start);
appendValue('\n');
start = (++from) + 1;
}
} else if (readingValue && *from == '{') {
if (from > start) varValue.append(start, int(from - start));
const char *tagStart = ++from;
while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) {
++from;
}
if (from == tagStart) {
readingValue = false;
warning(QString("Expected tag name in key '%1'!").arg(varName));
continue;
}
auto tagName = QLatin1String(tagStart, int(from - tagStart));
if (from == end || (*from != '}' && *from != ':')) throw Exception(QString("Expected '}' or ':' after tag name in key '%1'!").arg(varName));
auto index = GetTagIndex(tagName);
if (index == kTagsCount) {
readingValue = false;
warning(QString("Tag '%1' not found in key '%2', not using value.").arg(tagName).arg(varName));
continue;
}
if (!IsTagReplaced(varKey, index)) {
readingValue = false;
warning(QString("Unexpected tag '%1' in key '%2', not using value.").arg(tagName).arg(varName));
continue;
}
if (tagsUsed.contains(index)) throw Exception(QString("Tag '%1' double used in key '%2'!").arg(tagName).arg(varName));
tagsUsed.insert(index, true);
QString tagReplacer(4, TextCommand);
tagReplacer[1] = TextCommandLangTag;
tagReplacer[2] = QChar(0x0020 + index);
varValue.append(tagReplacer.toUtf8());
if (*from == ':') {
start = ++from;
QByteArray subvarValue;
bool foundtag = false;
int countedIndex = 0;
while (from < end && *from != '"' && *from != '}') {
if (*from == '|') {
if (from > start) subvarValue.append(start, int(from - start));
if (countedIndex >= kTagsPluralVariants) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
auto subkey = GetSubkeyIndex(varKey, index, countedIndex++);
if (subkey == kLangKeysCount) {
readingValue = false;
warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(tagName).arg(varName));
break;
} else {
if (feedingValue) {
if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(tagName).arg(varName));
} else {
foundKeyValue(subkey);
}
}
subvarValue = QByteArray();
foundtag = false;
start = from + 1;
}
if (*from == '\n') {
throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
}
if (*from == '\\') {
if (from + 1 >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{' || *(from + 1) == '#') {
if (from > start) subvarValue.append(start, int(from - start));
start = ++from;
} else if (*(from + 1) == 'n') {
if (from > start) subvarValue.append(start, int(from - start));
subvarValue.append('\n');
start = (++from) + 1;
}
} else if (*from == '{') {
throw Exception(QString("Unexpected tag inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
} else if (*from == '#') {
if (foundtag) throw Exception(QString("Replacement '#' double used inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
foundtag = true;
if (from > start) subvarValue.append(start, int(from - start));
subvarValue.append(tagReplacer.toUtf8());
start = from + 1;
}
++from;
}
if (!readingValue) continue;
if (from >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
if (*from == '"') throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
if (from > start) subvarValue.append(start, int(from - start));
if (countedIndex >= kTagsPluralVariants) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
auto subkey = GetSubkeyIndex(varKey, index, countedIndex++);
if (subkey == kLangKeysCount) {
readingValue = false;
warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(tagName).arg(varName));
break;
} else {
if (feedingValue) {
if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(tagName).arg(varName));
} else {
foundKeyValue(subkey);
}
}
}
start = from + 1;
}
++from;
}
if (from >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName));
if (readingValue && from > start) varValue.append(start, from - start);
if (from >= end) {
return error(qsl("Unexpected end of file in key '%1'!").arg(key));
}
if (from > start) {
appendValue(start, from - start);
}
if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName));
if (*from != ';') throw Exception(QString("';' expected after \"value\" in key '%1'!").arg(varName));
if (!skipWhitespaces(++from, end)) {
return error(qsl("Unexpected end of file in key '%1'!").arg(key));
}
if (*from != ';') {
return error(qsl("';' expected after \"value\" in key '%1'!").arg(key));
}
skipWhitespaces(++from, end);
if (readingValue) {
if (feedingValue) {
if (!feedKeyValue(varKey, QString::fromUtf8(varValue))) throw Exception(QString("Could not write value in key '%1'!").arg(varName));
} else {
foundKeyValue(varKey);
_result.insert(varKey, QString::fromUtf8(varValue));
}
if (_callback) {
_callback(key, value);
} else if (!skipping) {
_result.insert(keyIndex, QString::fromUtf8(value));
}
return true;
}
bool FileParser::feedKeyValue(LangKey key, const QString &value) {
if (key < kLangKeysCount) {
_found[key] = 1;
FeedKeyValue(key, value);
return true;
QByteArray FileParser::ReadFile(const QString &absolutePath, const QString &relativePath) {
QFile file(QFileInfo(relativePath).exists() ? relativePath : absolutePath);
if (!file.open(QIODevice::ReadOnly)) {
LOG(("Lang Error: Could not open file at '%1' ('%2')").arg(relativePath).arg(absolutePath));
return QByteArray();
}
return false;
if (file.size() > kLangFileLimit) {
LOG(("Lang Error: File is too big: %1").arg(file.size()));
return QByteArray();
}
constexpr auto kCodecMagicSize = 3;
auto codecMagic = file.read(kCodecMagicSize);
if (codecMagic.size() < kCodecMagicSize) {
LOG(("Lang Error: Found bad file at '%1' ('%2')").arg(relativePath).arg(absolutePath));
return QByteArray();
}
file.seek(0);
QByteArray data;
int skip = 0;
auto readUtf16Stream = [relativePath, absolutePath](auto &stream) {
stream.setCodec("UTF-16");
auto string = stream.readAll();
if (stream.status() != QTextStream::Ok) {
LOG(("Lang Error: Could not read UTF-16 data from '%1' ('%2')").arg(relativePath).arg(absolutePath));
return QByteArray();
}
if (string.isEmpty()) {
LOG(("Lang Error: Empty UTF-16 content in '%1' ('%2')").arg(relativePath).arg(absolutePath));
return QByteArray();
}
return string.toUtf8();
};
if ((codecMagic.at(0) == '\xFF' && codecMagic.at(1) == '\xFE') || (codecMagic.at(0) == '\xFE' && codecMagic.at(1) == '\xFF') || (codecMagic.at(1) == 0)) {
return readUtf16Stream(QTextStream(&file));
} else if (codecMagic.at(0) == 0) {
auto utf16WithBOM = "\xFE\xFF" + file.readAll();
return readUtf16Stream(QTextStream(utf16WithBOM));
}
data = file.readAll();
if (codecMagic.at(0) == '\xEF' && codecMagic.at(1) == '\xBB' && codecMagic.at(2) == '\xBF') {
data = data.mid(3); // skip UTF-8 BOM
}
if (data.isEmpty()) {
LOG(("Lang Error: Empty UTF-8 content in '%1' ('%2')").arg(relativePath).arg(absolutePath));
return QByteArray();
}
return data;
}
} // namespace Lang

View File

@ -28,7 +28,10 @@ class FileParser {
public:
using Result = QMap<LangKey, QString>;
FileParser(const QString &file, const std::set<LangKey> &request = std::set<LangKey>());
FileParser(const QString &file, const std::set<LangKey> &request);
FileParser(const QByteArray &content, base::lambda<void(QLatin1String key, const QByteArray &value)> callback);
static QByteArray ReadFile(const QString &absolutePath, const QString &relativePath);
const QString &errors() const;
const QString &warnings() const;
@ -38,11 +41,11 @@ public:
}
private:
bool feedKeyValue(LangKey key, const QString &value);
void foundKeyValue(LangKey key);
void parse();
void error(const QString &text) {
bool error(const QString &text) {
_errorsList.push_back(text);
return false;
}
void warning(const QString &text) {
_warningsList.push_back(text);
@ -51,13 +54,11 @@ private:
mutable QStringList _errorsList, _warningsList;
mutable QString _errors, _warnings;
mutable bool _checked = false;
std::array<bool, kLangKeysCount> _found = { { false } };
QString _filePath;
std::set<LangKey> _request;
const QByteArray _content;
const std::set<LangKey> _request;
const base::lambda<void(QLatin1String key, const QByteArray &value)> _callback;
bool _readingAll = false;
Result _result;
};

View File

@ -0,0 +1,449 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop 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 3 of the License, or
(at your option) any later version.
It 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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "lang/lang_instance.h"
#include "messenger.h"
#include "lang/lang_file_parser.h"
#include "platform/platform_specific.h"
#include "storage/serialize_common.h"
#include "storage/localstorage.h"
namespace Lang {
namespace {
constexpr auto kDefaultLanguage = str_const("en");
constexpr auto kLangValuesLimit = 20000;
class ValueParser {
public:
ValueParser(const QByteArray &key, LangKey keyIndex, const QByteArray &value);
QString takeResult() {
Expects(!_failed);
return std::move(_result);
}
std::map<LangKey, QString> takePluralValues() {
Expects(!_failed);
return std::move(_plural);
}
bool parse();
private:
void appendToResult(const char *nextBegin);
void appendToPlural(const char *nextBegin);
bool feedPluralValue();
bool logError(const QString &text);
bool readTag();
const QByteArray &_key;
LangKey _keyIndex = kLangKeysCount;
QLatin1String _currentTag;
ushort _currentTagIndex = 0;
QString _currentTagReplacer;
QString _pluralValue;
int _pluralIndex = 0;
bool _pluralNumericFound = false;
bool _failed = true;
const char *_begin, *_ch, *_end;
QString _result;
std::map<LangKey, QString> _plural;
OrderedSet<ushort> _tagsUsed;
};
ValueParser::ValueParser(const QByteArray &key, LangKey keyIndex, const QByteArray &value)
: _key(key)
, _keyIndex(keyIndex)
, _begin(value.constData())
, _ch(_begin)
, _end(_begin + value.size()) {
}
void ValueParser::appendToResult(const char *nextBegin) {
if (_ch > _begin) _result.append(QString::fromUtf8(_begin, _ch - _begin));
_begin = nextBegin;
}
void ValueParser::appendToPlural(const char *nextBegin) {
if (_ch > _begin) _pluralValue.append(QString::fromUtf8(_begin, _ch - _begin));
_begin = nextBegin;
}
bool ValueParser::feedPluralValue() {
appendToPlural(_ch + 1);
if (_pluralIndex >= kTagsPluralVariants) {
return logError("Too many values inside counted tag");
}
auto pluralKeyIndex = GetSubkeyIndex(_keyIndex, _currentTagIndex, _pluralIndex);
if (pluralKeyIndex == kLangKeysCount) {
return logError("Unexpected counted tag");
} else {
_plural.emplace(pluralKeyIndex, _pluralValue);
}
++_pluralIndex;
_pluralValue = QString();
_pluralNumericFound = false;
return true;
};
bool ValueParser::logError(const QString &text) {
_failed = true;
auto loggedKey = (_currentTag.size() > 0) ? (_key + QString(':') + _currentTag) : QString(_key);
LOG(("Lang Error: %1 (key '%2')").arg(text).arg(loggedKey));
return false;
}
bool ValueParser::readTag() {
auto tagStart = _ch;
auto isTagChar = [](QChar ch) {
if (ch >= 'a' && ch <= 'z') {
return true;
} else if (ch >= 'A' && ch <= 'z') {
return true;
} else if (ch >= '0' && ch <= '9') {
return true;
}
return (ch == '_');
};
while (_ch != _end && isTagChar(*_ch)) {
++_ch;
}
if (_ch == tagStart) {
return logError("Expected tag name");
}
_currentTag = QLatin1String(tagStart, _ch - tagStart);
if (_ch == _end || (*_ch != '}' && *_ch != ':')) {
return logError("Expected '}' or ':' after tag name");
}
_currentTagIndex = GetTagIndex(_currentTag);
if (_currentTagIndex == kTagsCount) {
return logError("Unknown tag");
}
if (!IsTagReplaced(_keyIndex, _currentTagIndex)) {
return logError("Unexpected tag");
}
if (_tagsUsed.contains(_currentTagIndex)) {
return logError("Repeated tag");
}
_tagsUsed.insert(_currentTagIndex);
if (_currentTagReplacer.isEmpty()) {
_currentTagReplacer = QString(4, TextCommand);
_currentTagReplacer[1] = TextCommandLangTag;
}
_currentTagReplacer[2] = QChar(0x0020 + _currentTagIndex);
return true;
}
bool ValueParser::parse() {
_failed = false;
_result.reserve(_end - _begin);
for (; _ch != _end; ++_ch) {
if (*_ch == '{') {
appendToResult(_ch);
++_ch;
if (!readTag()) {
return false;
}
_result.append(_currentTagReplacer);
_begin = _ch + 1;
if (*_ch == ':') {
_pluralIndex = 0;
while (_ch != _end && *_ch != '}') {
if (*_ch == '|') {
if (!feedPluralValue()) {
return false;
}
} else if (*_ch == '\\') {
if (_ch + 1 >= _end) {
return logError("Unexpected end of file inside counted tag");
}
if (*(_ch + 1) == '{' || *(_ch + 1) == '#' || *(_ch + 1) == '}') {
appendToPlural(_ch + 1);
}
} else if (*_ch == '{') {
return logError("Unexpected tag inside counted tag");
} else if (*_ch == '#') {
if (_pluralNumericFound) {
return logError("Replacement '#' double used inside counted tag");
}
_pluralNumericFound = true;
appendToPlural(_ch + 1);
_pluralValue.append(_currentTagReplacer);
}
++_ch;
}
if (_ch == _end) {
return logError("Unexpected end of value inside counted tag");
}
if (!feedPluralValue()) {
return false;
}
}
_currentTag = QLatin1String();
}
}
appendToResult(_end);
return true;
}
} // namespace
void Instance::fillDefaults() {
_values.clear();
_values.reserve(kLangKeysCount);
for (auto i = 0; i != kLangKeysCount; ++i) {
_values.emplace_back(GetOriginalValue(LangKey(i)));
}
_id = str_const_toString(kDefaultLanguage);
_legacyId = kLegacyDefaultLanguage;
}
QString Instance::cloudLangCode() const {
if (isCustom() || id().isEmpty()) {
if (_systemLanguage.isEmpty()) {
_systemLanguage = Platform::SystemLanguage();
if (_systemLanguage.isEmpty()) {
_systemLanguage = str_const_toString(kDefaultLanguage);
}
}
return _systemLanguage;
}
return id();
}
QByteArray Instance::serialize() const {
auto size = Serialize::stringSize(_id);
size += sizeof(qint32); // version
size += Serialize::stringSize(_customFilePathAbsolute) + Serialize::stringSize(_customFilePathRelative);
size += Serialize::bytearraySize(_customFileContent);
size += sizeof(qint32); // _nonDefaultValues.size()
for (auto &nonDefault : _nonDefaultValues) {
size += Serialize::bytearraySize(nonDefault.first) + Serialize::bytearraySize(nonDefault.second);
}
auto result = QByteArray();
result.reserve(size);
{
QDataStream stream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_1);
stream << _id << qint32(_version);
stream << _customFilePathAbsolute << _customFilePathRelative << _customFileContent;
stream << qint32(_nonDefaultValues.size());
for (auto &nonDefault : _nonDefaultValues) {
stream << nonDefault.first << nonDefault.second;
}
}
return result;
}
void Instance::fillFromSerialized(const QByteArray &data) {
QDataStream stream(data);
stream.setVersion(QDataStream::Qt_5_1);
QString id;
qint32 version = 0;
QString customFilePathAbsolute, customFilePathRelative;
QByteArray customFileContent;
qint32 nonDefaultValuesCount = 0;
stream >> id >> version;
stream >> customFilePathAbsolute >> customFilePathRelative >> customFileContent;
stream >> nonDefaultValuesCount;
if (stream.status() != QDataStream::Ok) {
LOG(("Lang Error: Could not read data from serialized langpack."));
return;
}
if (nonDefaultValuesCount > kLangValuesLimit) {
LOG(("Lang Error: Values count limit exceeded: %1").arg(nonDefaultValuesCount));
return;
}
if (!customFilePathAbsolute.isEmpty()) {
auto currentCustomFileContent = Lang::FileParser::ReadFile(customFilePathAbsolute, customFilePathRelative);
if (!currentCustomFileContent.isEmpty() && currentCustomFileContent != customFileContent) {
loadFromCustomContent(customFilePathAbsolute, customFilePathRelative, currentCustomFileContent);
Local::writeLangPack();
return;
}
}
std::vector<QByteArray> nonDefaultStrings;
nonDefaultStrings.reserve(2 * nonDefaultValuesCount);
for (auto i = 0; i != nonDefaultValuesCount; ++i) {
QByteArray key, value;
stream >> key >> value;
if (stream.status() != QDataStream::Ok) {
LOG(("Lang Error: Could not read data from serialized langpack."));
return;
}
nonDefaultStrings.push_back(key);
nonDefaultStrings.push_back(value);
}
_id = id;
_version = version;
_customFilePathAbsolute = customFilePathAbsolute;
_customFilePathRelative = customFilePathRelative;
_customFileContent = customFileContent;
LOG(("Lang Info: Loaded cached, keys: %1").arg(nonDefaultValuesCount));
for (auto i = 0, count = nonDefaultValuesCount * 2; i != count; i += 2) {
applyValue(nonDefaultStrings[i], nonDefaultStrings[i + 1]);
}
}
void Instance::loadFromContent(const QByteArray &content) {
Lang::FileParser loader(content, [this](QLatin1String key, const QByteArray &value) {
applyValue(QByteArray(key.data(), key.size()), value);
});
if (!loader.errors().isEmpty()) {
LOG(("Lang load errors: %1").arg(loader.errors()));
} else if (!loader.warnings().isEmpty()) {
LOG(("Lang load warnings: %1").arg(loader.warnings()));
}
}
void Instance::loadFromCustomContent(const QString &absolutePath, const QString &relativePath, const QByteArray &content) {
_id = qsl("custom");
_version = 0;
_customFilePathAbsolute = absolutePath;
_customFilePathRelative = relativePath;
_customFileContent = content;
loadFromContent(_customFileContent);
}
void Instance::fillFromCustomFile(const QString &filePath) {
auto absolutePath = QFileInfo(filePath).absoluteFilePath();
auto relativePath = QDir().relativeFilePath(filePath);
auto content = Lang::FileParser::ReadFile(absolutePath, relativePath);
if (!content.isEmpty()) {
loadFromCustomContent(absolutePath, relativePath, content);
}
}
void Instance::fillFromLegacy(int legacyId, const QString &legacyPath) {
if (legacyId == kLegacyDefaultLanguage) {
_legacyId = legacyId;
_id = str_const_toString(kLegacyLanguages[legacyId]);
} else if (legacyId == kLegacyCustomLanguage) {
auto absolutePath = QFileInfo(legacyPath).absoluteFilePath();
auto relativePath = QDir().relativeFilePath(absolutePath);
auto content = Lang::FileParser::ReadFile(absolutePath, relativePath);
if (!content.isEmpty()) {
loadFromCustomContent(absolutePath, relativePath, content);
}
} else if (legacyId > kLegacyDefaultLanguage && legacyId < base::array_size(kLegacyLanguages)) {
auto languageId = str_const_toString(kLegacyLanguages[_legacyId]);
auto resourcePath = qsl(":/langs/lang_") + languageId + qsl(".strings");
auto content = Lang::FileParser::ReadFile(resourcePath, resourcePath);
if (!content.isEmpty()) {
_legacyId = legacyId;
_id = languageId;
_version = 0;
loadFromContent(content);
}
}
}
void Instance::applyDifference(const MTPDlangPackDifference &difference) {
Expects(qs(difference.vlang_code) == _id);
Expects(difference.vfrom_version.v <= _version);
_version = difference.vversion.v;
for_const (auto &mtpString, difference.vstrings.v) {
switch (mtpString.type()) {
case mtpc_langPackString: {
auto &string = mtpString.c_langPackString();
applyValue(qba(string.vkey), qba(string.vvalue));
} break;
case mtpc_langPackStringPluralized: {
auto &string = mtpString.c_langPackStringPluralized();
auto key = qba(string.vkey);
applyValue(key + "#zero", string.has_zero_value() ? qba(string.vzero_value) : QByteArray());
applyValue(key + "#one", string.has_one_value() ? qba(string.vone_value) : QByteArray());
applyValue(key + "#two", string.has_two_value() ? qba(string.vtwo_value) : QByteArray());
applyValue(key + "#few", string.has_few_value() ? qba(string.vfew_value) : QByteArray());
applyValue(key + "#many", string.has_many_value() ? qba(string.vmany_value) : QByteArray());
applyValue(key + "#other", qba(string.vother_value));
} break;
case mtpc_langPackStringDeleted: {
auto &string = mtpString.c_langPackStringDeleted();
auto key = qba(string.vkey);
resetValue(key);
for (auto plural : { "#zero", "#one", "#two", "#few", "#many", "#other" }) {
resetValue(key + plural);
}
} break;
default: Unexpected("LangPack string type in applyUpdate().");
}
}
}
void Instance::resetValue(const QByteArray &key) {
_nonDefaultValues.erase(key);
auto keyIndex = GetKeyIndex(QLatin1String(key));
if (keyIndex != kLangKeysCount) {
_values[keyIndex] = GetOriginalValue(keyIndex);
}
}
void Instance::applyValue(const QByteArray &key, const QByteArray &value) {
_nonDefaultValues[key] = value;
auto pluralValues = std::map<ushort, QString>();
auto keyIndex = GetKeyIndex(QLatin1String(key));
if (keyIndex == kLangKeysCount) {
LOG(("Lang Error: Unknown key '%1'").arg(QString::fromLatin1(key)));
return;
}
ValueParser parser(key, keyIndex, value);
if (!parser.parse()) {
return;
}
_values[keyIndex] = parser.takeResult();
for (auto &plural : parser.takePluralValues()) {
_values[plural.first] = plural.second;
}
}
Instance &Current() {
return Messenger::Instance().langpack();
}
} // namespace Lang

View File

@ -0,0 +1,106 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop 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 3 of the License, or
(at your option) any later version.
It 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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "lang_auto.h"
namespace Lang {
constexpr auto kLegacyLanguageNone = -2;
constexpr auto kLegacyCustomLanguage = -1;
constexpr auto kLegacyDefaultLanguage = 0;
constexpr str_const kLegacyLanguages[] = {
"en",
"it",
"es",
"de",
"nl",
"pt_BR",
"ko",
};
class Instance {
public:
Instance() {
fillDefaults();
}
struct CreateFromIdTag {};
Instance(const QString &id, CreateFromIdTag) {
fillDefaults();
_id = id;
}
struct CreateFromCustomFileTag {};
Instance(const QString &filePath, CreateFromCustomFileTag) {
fillDefaults();
fillFromCustomFile(filePath);
}
Instance(const Instance &other) = delete;
Instance &operator=(const Instance &other) = delete;
Instance(Instance &&other) = default;
Instance &operator=(Instance &&other) = default;
QString id() const {
return _id;
}
bool isCustom() const {
return id() == qstr("custom");
}
int version() const {
return _version;
}
QString cloudLangCode() const;
QByteArray serialize() const;
void fillFromSerialized(const QByteArray &data);
void fillFromLegacy(int legacyId, const QString &legacyPath);
void applyDifference(const MTPDlangPackDifference &difference);
QString getValue(LangKey key) {
Expects(key >= 0 && key < kLangKeysCount);
Expects(_values.size() == kLangKeysCount);
return _values[key];
}
private:
void applyValue(const QByteArray &key, const QByteArray &value);
void resetValue(const QByteArray &key);
void fillDefaults();
void fillFromCustomFile(const QString &filePath);
void loadFromContent(const QByteArray &content);
void loadFromCustomContent(const QString &absolutePath, const QString &relativePath, const QByteArray &content);
QString _id;
int _legacyId = kLegacyLanguageNone;
QString _customFilePathAbsolute;
QString _customFilePathRelative;
QByteArray _customFileContent;
int _version = 0;
mutable QString _systemLanguage;
std::vector<QString> _values;
std::map<QByteArray, QByteArray> _nonDefaultValues;
};
Instance &Current();
} // namespace Lang

View File

@ -20,18 +20,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "lang_auto.h"
#include "lang/lang_instance.h"
constexpr const str_const LanguageCodes[] = {
"en",
"it",
"es",
"de",
"nl",
"pt_BR",
"ko",
};
constexpr const int languageTest = -1, languageDefault = 0, languageCount = base::array_size(LanguageCodes);
inline QString lang(LangKey key) {
return Lang::Current().getValue(key);
}
template <typename WithYear, typename WithoutYear>
inline QString langDateMaybeWithYear(QDate date, WithYear withYear, WithoutYear withoutYear) {

View File

@ -5264,7 +5264,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
} break;
case mtpc_updateConfig: {
Messenger::Instance().mtp()->configLoadRequest();
Messenger::Instance().mtp()->requestConfig();
} break;
case mtpc_updateUserPhone: {
@ -5689,9 +5689,11 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
////// Cloud langpacks
case mtpc_updateLangPack: {
auto &langpack = update.c_updateLangPack();
Messenger::Instance().mtp()->applyLangPackDifference(langpack.vdifference);
} break;
case mtpc_updateLangPackTooLong: {
Messenger::Instance().mtp()->requestLangPackDifference();
} break;
}

View File

@ -67,6 +67,7 @@ struct Messenger::Private {
Messenger::Messenger() : QObject()
, _private(std::make_unique<Private>())
, _langpack(std::make_unique<Lang::Instance>())
, _audio(std::make_unique<Media::Audio::Instance>())
, _logo(Window::LoadLogo())
, _logoNoMargin(Window::LoadLogoNoMargin()) {
@ -96,7 +97,10 @@ Messenger::Messenger() : QObject()
cSetConfigScale(dbisOne);
cSetRealScale(dbisOne);
}
loadLanguage();
_translator = std::make_unique<Lang::Translator>();
QCoreApplication::instance()->installTranslator(_translator.get());
style::startManager();
anim::startManager();
historyInit();
@ -202,12 +206,7 @@ QByteArray Messenger::serializeMtpAuthorization() const {
size += keysSize(keys) + keysSize(keysToDestroy);
result.reserve(size);
{
QBuffer buffer(&result);
if (!buffer.open(QIODevice::WriteOnly)) {
LOG(("MTP Error: could not open buffer to serialize mtp authorization."));
return result;
}
QDataStream stream(&buffer);
QDataStream stream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_1);
auto currentUserId = AuthSession::Exists() ? AuthSession::CurrentUserId() : 0;
@ -251,13 +250,7 @@ AuthSessionData *Messenger::getAuthSessionData() {
void Messenger::setMtpAuthorization(const QByteArray &serialized) {
Expects(!_mtproto);
auto readonly = serialized;
QBuffer buffer(&readonly);
if (!buffer.open(QIODevice::ReadOnly)) {
LOG(("MTP Error: could not open serialized mtp authorization for reading."));
return;
}
QDataStream stream(&buffer);
QDataStream stream(serialized);
stream.setVersion(QDataStream::Qt_5_1);
auto userId = Serialize::read<qint32>(stream);
@ -375,34 +368,6 @@ void Messenger::destroyStaleAuthorizationKeys() {
}
}
void Messenger::loadLanguage() {
if (cLang() < languageTest) {
cSetLang(Sandbox::LangSystem());
}
if (cLang() == languageTest) {
if (QFileInfo(cLangFile()).exists()) {
Lang::FileParser loader(cLangFile());
cSetLangErrors(loader.errors());
if (!cLangErrors().isEmpty()) {
LOG(("Lang load errors: %1").arg(cLangErrors()));
} else if (!loader.warnings().isEmpty()) {
LOG(("Lang load warnings: %1").arg(loader.warnings()));
}
} else {
cSetLang(languageDefault);
}
} else if (cLang() > languageDefault && cLang() < languageCount) {
Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[cLang()].c_str() + qsl(".strings"));
if (!loader.errors().isEmpty()) {
LOG(("Lang load errors: %1").arg(loader.errors()));
} else if (!loader.warnings().isEmpty()) {
LOG(("Lang load warnings: %1").arg(loader.warnings()));
}
}
_translator = std::make_unique<Lang::Translator>();
QCoreApplication::instance()->installTranslator(_translator.get());
}
void Messenger::startLocalStorage() {
_dcOptions = std::make_unique<MTP::DcOptions>();
_dcOptions->constructFromBuiltIn();
@ -417,7 +382,7 @@ void Messenger::startLocalStorage() {
});
subscribe(authSessionChanged(), [this] {
if (_mtproto) {
_mtproto->configLoadRequest();
_mtproto->requestConfig();
}
});
}

View File

@ -48,6 +48,7 @@ class Instance;
} // namespace Media
namespace Lang {
class Instance;
class Translator;
} // namespace Lang
@ -105,6 +106,9 @@ public:
AuthSession *authSession() {
return _authSession.get();
}
Lang::Instance &langpack() {
return *_langpack;
}
void authSessionCreate(UserId userId);
void authSessionDestroy();
base::Observable<void> &authSessionChanged() {
@ -175,7 +179,6 @@ public slots:
private:
void destroyMtpKeys(MTP::AuthKeysList &&keys);
void startLocalStorage();
void loadLanguage();
friend void App::quit();
static void QuitAttempt();
@ -193,6 +196,7 @@ private:
std::unique_ptr<MainWindow> _window;
FileUploader *_uploader = nullptr;
std::unique_ptr<Lang::Instance> _langpack;
std::unique_ptr<Lang::Translator> _translator;
std::unique_ptr<MTP::DcOptions> _dcOptions;
std::unique_ptr<MTP::Instance> _mtproto;

View File

@ -780,7 +780,7 @@ void ConnectionPrivate::tryToSend() {
MTPInitConnection<mtpRequest> initWrapper;
int32 initSize = 0, initSizeInInts = 0;
if (needsLayer) {
auto langCode = (cLang() == languageTest || cLang() == languageDefault) ? Sandbox::LangSystemISO() : str_const_toString(LanguageCodes[cLang()]);
auto langCode = sessionData->langCode();
auto langPack = "tdesktop";
auto deviceModel = (_dcType == DcType::Cdn) ? "n/a" : cApiDeviceModel();
auto systemVersion = (_dcType == DcType::Cdn) ? "n/a" : cApiSystemVersion();
@ -1031,7 +1031,7 @@ void ConnectionPrivate::connectToServer(bool afterConfig) {
DEBUG_LOG(("MTP Info: DC %1 options for IPv4 over HTTP not found, waiting for config").arg(_shiftedDcId));
if (Global::TryIPv6() && noIPv6) DEBUG_LOG(("MTP Info: DC %1 options for IPv6 over HTTP not found, waiting for config").arg(_shiftedDcId));
connect(_instance, SIGNAL(configLoaded()), this, SLOT(onConfigLoaded()), Qt::UniqueConnection);
InvokeQueued(_instance, [instance = _instance] { instance->configLoadRequest(); });
InvokeQueued(_instance, [instance = _instance] { instance->requestConfig(); });
return;
}
@ -1231,7 +1231,7 @@ void ConnectionPrivate::finishAndDestroy() {
void ConnectionPrivate::requestCDNConfig() {
connect(_instance, SIGNAL(cdnConfigLoaded()), this, SLOT(onCDNConfigLoaded()), Qt::UniqueConnection);
InvokeQueued(_instance, [instance = _instance] { instance->cdnConfigLoadRequest(); });
InvokeQueued(_instance, [instance = _instance] { instance->requestCDNConfig(); });
}
void ConnectionPrivate::handleReceived() {

View File

@ -23,10 +23,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mtproto/dc_options.h"
#include "storage/localstorage.h"
#include "auth_session.h"
#include "apiwrap.h"
#include "messenger.h"
#include "mtproto/connection.h"
#include "mtproto/sender.h"
#include "mtproto/rsa_public_key.h"
#include "lang/lang_instance.h"
#include "base/timer.h"
namespace MTP {
@ -46,8 +49,10 @@ public:
DcOptions *dcOptions();
void configLoadRequest();
void cdnConfigLoadRequest();
void requestConfig();
void requestCDNConfig();
void requestLangPackDifference();
void applyLangPackDifference(const MTPLangPackDifference &difference);
void restart();
void restart(ShiftedDcId shiftedDcId);
@ -124,6 +129,8 @@ private:
void checkDelayedRequests();
void switchLangPackId(const QString &id);
Instance *_instance = nullptr;
DcOptions *_dcOptions = nullptr;
Instance::Mode _mode = Instance::Mode::Normal;
@ -174,7 +181,9 @@ private:
base::lambda<void(ShiftedDcId shiftedDcId, int32 state)> _stateChangedHandler;
base::lambda<void(ShiftedDcId shiftedDcId)> _sessionResetHandler;
SingleTimer _checkDelayedTimer;
base::Timer _checkDelayedTimer;
mtpRequestId _langPackRequestId = 0;
// Debug flag to find out how we end up crashing.
bool MustNotCreateSessions = false;
@ -232,13 +241,12 @@ void Instance::Private::start(Config &&config) {
_mainSession->start();
}
_checkDelayedTimer.setTimeoutHandler([this] {
checkDelayedRequests();
});
_checkDelayedTimer.setCallback([this] { checkDelayedRequests(); });
t_assert((_mainDcId == Config::kNoneMainDc) == isKeysDestroyer());
if (!isKeysDestroyer()) {
configLoadRequest();
requestConfig();
requestLangPackDifference();
}
}
@ -263,11 +271,11 @@ void Instance::Private::setMainDcId(DcId mainDcId) {
}
DcId Instance::Private::mainDcId() const {
t_assert(_mainDcId != Config::kNoneMainDc);
Expects(_mainDcId != Config::kNoneMainDc);
return _mainDcId;
}
void Instance::Private::configLoadRequest() {
void Instance::Private::requestConfig() {
if (_configLoader) {
return;
}
@ -279,7 +287,7 @@ void Instance::Private::configLoadRequest() {
_configLoader->load();
}
void Instance::Private::cdnConfigLoadRequest() {
void Instance::Private::requestCDNConfig() {
if (_cdnConfigLoadRequestId || _mainDcId == Config::kNoneMainDc) {
return;
}
@ -295,6 +303,58 @@ void Instance::Private::cdnConfigLoadRequest() {
}).send();
}
void Instance::Private::requestLangPackDifference() {
auto &langpack = Lang::Current();
if (langpack.isCustom() || _langPackRequestId) {
return;
}
auto version = langpack.version();
if (version > 0) {
_langPackRequestId = request(MTPlangpack_GetDifference(MTP_int(version))).done([this](const MTPLangPackDifference &result) {
_langPackRequestId = 0;
applyLangPackDifference(result);
}).fail([this](const RPCError &error) {
_langPackRequestId = 0;
}).send();
} else {
_langPackRequestId = request(MTPlangpack_GetLangPack()).done([this](const MTPLangPackDifference &result) {
_langPackRequestId = 0;
applyLangPackDifference(result);
}).fail([this](const RPCError &error) {
_langPackRequestId = 0;
}).send();
}
}
void Instance::Private::applyLangPackDifference(const MTPLangPackDifference &difference) {
Expects(difference.type() == mtpc_langPackDifference);
auto &current = Lang::Current();
if (current.isCustom()) {
return;
}
auto &langpack = difference.c_langPackDifference();
switchLangPackId(qs(langpack.vlang_code));
if (current.version() < langpack.vfrom_version.v) {
requestLangPackDifference();
} else if (!langpack.vstrings.v.isEmpty()) {
current.applyDifference(langpack);
Local::writeLangPack();
} else {
LOG(("Lang Info: Up to date."));
}
}
void Instance::Private::switchLangPackId(const QString &id) {
auto &current = Lang::Current();
if (current.id() != id) {
current = Lang::Instance(id, Lang::Instance::CreateFromIdTag());
restart(maindc());
}
}
void Instance::Private::restart() {
for (auto &session : _sessions) {
session.second->restart();
@ -639,7 +699,7 @@ void Instance::Private::checkDelayedRequests() {
}
if (!_delayedRequests.empty()) {
_checkDelayedTimer.start(_delayedRequests.front().second - now);
_checkDelayedTimer.callOnce(_delayedRequests.front().second - now);
}
}
@ -1261,12 +1321,24 @@ DcId Instance::mainDcId() const {
return _private->mainDcId();
}
void Instance::configLoadRequest() {
_private->configLoadRequest();
QString Instance::cloudLangCode() const {
return Lang::Current().cloudLangCode();
}
void Instance::cdnConfigLoadRequest() {
_private->cdnConfigLoadRequest();
void Instance::requestConfig() {
_private->requestConfig();
}
void Instance::requestCDNConfig() {
_private->requestCDNConfig();
}
void Instance::requestLangPackDifference() {
_private->requestLangPackDifference();
}
void Instance::applyLangPackDifference(const MTPLangPackDifference &difference) {
_private->applyLangPackDifference(difference);
}
void Instance::connectionFinished(internal::Connection *connection) {

View File

@ -53,6 +53,7 @@ public:
void suggestMainDcId(DcId mainDcId);
void setMainDcId(DcId mainDcId);
DcId mainDcId() const;
QString cloudLangCode() const;
void setKeyForWrite(DcId dcId, const AuthKeyPtr &key);
AuthKeysList getKeysForWrite() const;
@ -119,9 +120,10 @@ public:
bool isKeysDestroyer() const;
void scheduleKeyDestroy(ShiftedDcId shiftedDcId);
void configLoadRequest();
void cdnConfigLoadRequest();
void requestConfig();
void requestCDNConfig();
void requestLangPackDifference();
void applyLangPackDifference(const MTPLangPackDifference &difference);
~Instance();

View File

@ -76,6 +76,8 @@ Session::Session(gsl::not_null<Instance*> instance, ShiftedDcId shiftedDcId) : Q
connect(&timeouter, SIGNAL(timeout()), this, SLOT(checkRequestsByTimer()));
timeouter.start(1000);
data.setLangCode(instance->cloudLangCode());
connect(&sender, SIGNAL(timeout()), this, SLOT(needToResumeAndSend()));
}
@ -124,6 +126,7 @@ void Session::restart() {
DEBUG_LOG(("Session Error: can't restart a killed session"));
return;
}
data.setLangCode(_instance->cloudLangCode());
emit needToRestart();
}

View File

@ -123,6 +123,15 @@ public:
_layerInited = was;
}
QString langCode() const {
QReadLocker locker(&_lock);
return _langCode;
}
void setLangCode(const QString &code) {
QWriteLocker locker(&_lock);
_langCode = code;
}
void setSalt(uint64 salt) {
QWriteLocker locker(&_lock);
_salt = salt;
@ -259,6 +268,7 @@ private:
AuthKeyPtr _authKey;
bool _keyChecked = false;
bool _layerInited = false;
QString _langCode;
mtpPreRequestMap _toSend; // map of request_id -> request, that is waiting to be sent
mtpRequestMap _haveSent; // map of msg_id -> request, that was sent, msDate = 0 for msgs_state_req (no resend / state req), msDate = 0, seqNo = 0 for containers

View File

@ -278,16 +278,6 @@ void psActivateProcess(uint64 pid) {
// objc_activateProgram();
}
QString psCurrentCountry() {
QString country;// = objc_currentCountry();
return country.isEmpty() ? QString::fromLatin1(DefaultCountry) : country;
}
QString psCurrentLanguage() {
QString lng;// = objc_currentLang();
return lng.isEmpty() ? QString::fromLatin1(DefaultLanguage) : lng;
}
namespace {
QString getHomeDir() {
@ -402,6 +392,14 @@ bool TranslucentWindowsSupported(QPoint globalPosition) {
return false;
}
QString SystemCountry() {
return QString();
}
QString SystemLanguage() {
return QString();
}
namespace ThirdParty {
void start() {

View File

@ -63,8 +63,6 @@ void psClearInitLogs();
void psActivateProcess(uint64 pid = 0);
QString psLocalServerPrefix();
QString psCurrentCountry();
QString psCurrentLanguage();
QString psAppDataPath();
QString psDownloadPath();
QString psCurrentExeDirectory(int argc, char *argv[]);

View File

@ -322,16 +322,6 @@ void psActivateProcess(uint64 pid) {
}
}
QString psCurrentCountry() {
QString country = objc_currentCountry();
return country.isEmpty() ? QString::fromLatin1(DefaultCountry) : country;
}
QString psCurrentLanguage() {
QString lng = objc_currentLang();
return lng.isEmpty() ? QString::fromLatin1(DefaultLanguage) : lng;
}
QString psAppDataPath() {
return objc_appDataPath();
}
@ -404,6 +394,15 @@ void StartTranslucentPaint(QPainter &p, QPaintEvent *e) {
#endif // OS_MAC_OLD
}
QString SystemCountry() {
QString country = objc_currentCountry();
return country.isEmpty() ? QString::fromLatin1(DefaultCountry) : country;
}
QString SystemLanguage() {
return objc_currentLang();
}
} // namespace Platform
void psNewVersion() {

View File

@ -64,8 +64,6 @@ void psClearInitLogs();
void psActivateProcess(uint64 pid = 0);
QString psLocalServerPrefix();
QString psCurrentCountry();
QString psCurrentLanguage();
QString psAppDataPath();
QString psDownloadPath();
QString psCurrentExeDirectory(int argc, char *argv[]);

View File

@ -20,14 +20,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#ifdef Q_OS_MAC
#include "platform/mac/specific_mac.h"
#elif defined Q_OS_LINUX // Q_OS_MAC
#include "platform/linux/specific_linux.h"
#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX
#include "platform/win/specific_win.h"
#endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WIN
namespace Platform {
void start();
@ -40,6 +32,9 @@ void InitOnTopPanel(QWidget *panel);
void DeInitOnTopPanel(QWidget *panel);
void ReInitOnTopPanel(QWidget *panel);
QString SystemLanguage();
QString SystemCountry();
namespace ThirdParty {
void start();
@ -47,3 +42,11 @@ void finish();
} // namespace ThirdParty
} // namespace Platform
#ifdef Q_OS_MAC
#include "platform/mac/specific_mac.h"
#elif defined Q_OS_LINUX // Q_OS_MAC
#include "platform/linux/specific_linux.h"
#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX
#include "platform/win/specific_win.h"
#endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WIN

View File

@ -183,171 +183,6 @@ void psActivateProcess(uint64 pid) {
}
}
QString psCurrentCountry() {
int chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrCountry[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, wstrCountry, chCount);
return len ? QString::fromStdWString(std::wstring(wstrCountry)) : QString::fromLatin1(DefaultCountry);
}
return QString::fromLatin1(DefaultCountry);
}
namespace {
QString langById(int lngId) {
int primary = lngId & 0xFF;
switch (primary) {
case 0x36: return qsl("af");
case 0x1C: return qsl("sq");
case 0x5E: return qsl("am");
case 0x01: return qsl("ar");
case 0x2B: return qsl("hy");
case 0x4D: return qsl("as");
case 0x2C: return qsl("az");
case 0x45: return qsl("bn");
case 0x6D: return qsl("ba");
case 0x2D: return qsl("eu");
case 0x23: return qsl("be");
case 0x1A:
if (lngId == LANG_CROATIAN) {
return qsl("hr");
} else if (lngId == LANG_BOSNIAN_NEUTRAL || lngId == LANG_BOSNIAN) {
return qsl("bs");
}
return qsl("sr");
break;
case 0x7E: return qsl("br");
case 0x02: return qsl("bg");
case 0x92: return qsl("ku");
case 0x03: return qsl("ca");
case 0x04: return qsl("zh");
case 0x83: return qsl("co");
case 0x05: return qsl("cs");
case 0x06: return qsl("da");
case 0x65: return qsl("dv");
case 0x13: return qsl("nl");
case 0x09: return qsl("en");
case 0x25: return qsl("et");
case 0x38: return qsl("fo");
case 0x0B: return qsl("fi");
case 0x0c: return qsl("fr");
case 0x62: return qsl("fy");
case 0x56: return qsl("gl");
case 0x37: return qsl("ka");
case 0x07: return qsl("de");
case 0x08: return qsl("el");
case 0x6F: return qsl("kl");
case 0x47: return qsl("gu");
case 0x68: return qsl("ha");
case 0x0D: return qsl("he");
case 0x39: return qsl("hi");
case 0x0E: return qsl("hu");
case 0x0F: return qsl("is");
case 0x70: return qsl("ig");
case 0x21: return qsl("id");
case 0x5D: return qsl("iu");
case 0x3C: return qsl("ga");
case 0x34: return qsl("xh");
case 0x35: return qsl("zu");
case 0x10: return qsl("it");
case 0x11: return qsl("ja");
case 0x4B: return qsl("kn");
case 0x3F: return qsl("kk");
case 0x53: return qsl("kh");
case 0x87: return qsl("rw");
case 0x12: return qsl("ko");
case 0x40: return qsl("ky");
case 0x54: return qsl("lo");
case 0x26: return qsl("lv");
case 0x27: return qsl("lt");
case 0x6E: return qsl("lb");
case 0x2F: return qsl("mk");
case 0x3E: return qsl("ms");
case 0x4C: return qsl("ml");
case 0x3A: return qsl("mt");
case 0x81: return qsl("mi");
case 0x4E: return qsl("mr");
case 0x50: return qsl("mn");
case 0x61: return qsl("ne");
case 0x14: return qsl("no");
case 0x82: return qsl("oc");
case 0x48: return qsl("or");
case 0x63: return qsl("ps");
case 0x29: return qsl("fa");
case 0x15: return qsl("pl");
case 0x16: return qsl("pt");
case 0x67: return qsl("ff");
case 0x46: return qsl("pa");
case 0x18: return qsl("ro");
case 0x17: return qsl("rm");
case 0x19: return qsl("ru");
case 0x3B: return qsl("se");
case 0x4F: return qsl("sa");
case 0x32: return qsl("tn");
case 0x59: return qsl("sd");
case 0x5B: return qsl("si");
case 0x1B: return qsl("sk");
case 0x24: return qsl("sl");
case 0x0A: return qsl("es");
case 0x41: return qsl("sw");
case 0x1D: return qsl("sv");
case 0x28: return qsl("tg");
case 0x49: return qsl("ta");
case 0x44: return qsl("tt");
case 0x4A: return qsl("te");
case 0x1E: return qsl("th");
case 0x51: return qsl("bo");
case 0x73: return qsl("ti");
case 0x1F: return qsl("tr");
case 0x42: return qsl("tk");
case 0x22: return qsl("uk");
case 0x20: return qsl("ur");
case 0x80: return qsl("ug");
case 0x43: return qsl("uz");
case 0x2A: return qsl("vi");
case 0x52: return qsl("cy");
case 0x88: return qsl("wo");
case 0x78: return qsl("ii");
case 0x6A: return qsl("yo");
}
return QString::fromLatin1(DefaultLanguage);
}
}
QString psCurrentLanguage() {
int chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNAME, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrLocale[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNAME, wstrLocale, chCount);
if (!len) return QString::fromLatin1(DefaultLanguage);
QString locale = QString::fromStdWString(std::wstring(wstrLocale));
QRegularExpressionMatch m = QRegularExpression("(^|[^a-z])([a-z]{2})-").match(locale);
if (m.hasMatch()) {
return m.captured(2);
}
}
chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrLocale[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, wstrLocale, chCount), lngId = 0;
if (len < 5) return QString::fromLatin1(DefaultLanguage);
for (int i = 0; i < 4; ++i) {
WCHAR ch = wstrLocale[i];
lngId *= 16;
if (ch >= WCHAR('0') && ch <= WCHAR('9')) {
lngId += (ch - WCHAR('0'));
} else if (ch >= WCHAR('A') && ch <= WCHAR('F')) {
lngId += (10 + ch - WCHAR('A'));
} else {
return QString::fromLatin1(DefaultLanguage);
}
}
return langById(lngId);
}
return QString::fromLatin1(DefaultLanguage);
}
QString psAppDataPath() {
static const int maxFileLen = MAX_PATH * 10;
WCHAR wstrPath[maxFileLen];
@ -527,15 +362,177 @@ void finish() {
EventFilter::destroy();
}
namespace ThirdParty {
void start() {
QString SystemCountry() {
int chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrCountry[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, wstrCountry, chCount);
if (len) {
return QString::fromStdWString(std::wstring(wstrCountry));
}
}
return QString();
}
void finish() {
namespace {
QString GetLangCodeById(int lngId) {
int primary = lngId & 0xFF;
switch (primary) {
case 0x36: return qsl("af");
case 0x1C: return qsl("sq");
case 0x5E: return qsl("am");
case 0x01: return qsl("ar");
case 0x2B: return qsl("hy");
case 0x4D: return qsl("as");
case 0x2C: return qsl("az");
case 0x45: return qsl("bn");
case 0x6D: return qsl("ba");
case 0x2D: return qsl("eu");
case 0x23: return qsl("be");
case 0x1A:
if (lngId == LANG_CROATIAN) {
return qsl("hr");
} else if (lngId == LANG_BOSNIAN_NEUTRAL || lngId == LANG_BOSNIAN) {
return qsl("bs");
}
return qsl("sr");
break;
case 0x7E: return qsl("br");
case 0x02: return qsl("bg");
case 0x92: return qsl("ku");
case 0x03: return qsl("ca");
case 0x04: return qsl("zh");
case 0x83: return qsl("co");
case 0x05: return qsl("cs");
case 0x06: return qsl("da");
case 0x65: return qsl("dv");
case 0x13: return qsl("nl");
case 0x09: return qsl("en");
case 0x25: return qsl("et");
case 0x38: return qsl("fo");
case 0x0B: return qsl("fi");
case 0x0c: return qsl("fr");
case 0x62: return qsl("fy");
case 0x56: return qsl("gl");
case 0x37: return qsl("ka");
case 0x07: return qsl("de");
case 0x08: return qsl("el");
case 0x6F: return qsl("kl");
case 0x47: return qsl("gu");
case 0x68: return qsl("ha");
case 0x0D: return qsl("he");
case 0x39: return qsl("hi");
case 0x0E: return qsl("hu");
case 0x0F: return qsl("is");
case 0x70: return qsl("ig");
case 0x21: return qsl("id");
case 0x5D: return qsl("iu");
case 0x3C: return qsl("ga");
case 0x34: return qsl("xh");
case 0x35: return qsl("zu");
case 0x10: return qsl("it");
case 0x11: return qsl("ja");
case 0x4B: return qsl("kn");
case 0x3F: return qsl("kk");
case 0x53: return qsl("kh");
case 0x87: return qsl("rw");
case 0x12: return qsl("ko");
case 0x40: return qsl("ky");
case 0x54: return qsl("lo");
case 0x26: return qsl("lv");
case 0x27: return qsl("lt");
case 0x6E: return qsl("lb");
case 0x2F: return qsl("mk");
case 0x3E: return qsl("ms");
case 0x4C: return qsl("ml");
case 0x3A: return qsl("mt");
case 0x81: return qsl("mi");
case 0x4E: return qsl("mr");
case 0x50: return qsl("mn");
case 0x61: return qsl("ne");
case 0x14: return qsl("no");
case 0x82: return qsl("oc");
case 0x48: return qsl("or");
case 0x63: return qsl("ps");
case 0x29: return qsl("fa");
case 0x15: return qsl("pl");
case 0x16: return qsl("pt");
case 0x67: return qsl("ff");
case 0x46: return qsl("pa");
case 0x18: return qsl("ro");
case 0x17: return qsl("rm");
case 0x19: return qsl("ru");
case 0x3B: return qsl("se");
case 0x4F: return qsl("sa");
case 0x32: return qsl("tn");
case 0x59: return qsl("sd");
case 0x5B: return qsl("si");
case 0x1B: return qsl("sk");
case 0x24: return qsl("sl");
case 0x0A: return qsl("es");
case 0x41: return qsl("sw");
case 0x1D: return qsl("sv");
case 0x28: return qsl("tg");
case 0x49: return qsl("ta");
case 0x44: return qsl("tt");
case 0x4A: return qsl("te");
case 0x1E: return qsl("th");
case 0x51: return qsl("bo");
case 0x73: return qsl("ti");
case 0x1F: return qsl("tr");
case 0x42: return qsl("tk");
case 0x22: return qsl("uk");
case 0x20: return qsl("ur");
case 0x80: return qsl("ug");
case 0x43: return qsl("uz");
case 0x2A: return qsl("vi");
case 0x52: return qsl("cy");
case 0x88: return qsl("wo");
case 0x78: return qsl("ii");
case 0x6A: return qsl("yo");
}
return QString();
}
} // namespace
QString SystemLanguage() {
int chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNAME, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrLocale[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNAME, wstrLocale, chCount);
if (!len) {
return QString();
}
QString locale = QString::fromStdWString(std::wstring(wstrLocale));
QRegularExpressionMatch m = QRegularExpression("(^|[^a-z])([a-z]{2})-").match(locale);
if (m.hasMatch()) {
return m.captured(2);
}
}
chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrLocale[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, wstrLocale, chCount), lngId = 0;
if (len < 5) return QString();
for (int i = 0; i < 4; ++i) {
WCHAR ch = wstrLocale[i];
lngId *= 16;
if (ch >= WCHAR('0') && ch <= WCHAR('9')) {
lngId += (ch - WCHAR('0'));
} else if (ch >= WCHAR('A') && ch <= WCHAR('F')) {
lngId += (10 + ch - WCHAR('A'));
} else {
return QString();
}
}
return GetLangCodeById(lngId);
}
return QString();
}
} // namespace ThirdParty
} // namespace Platform
namespace {

View File

@ -43,6 +43,15 @@ inline void DeInitOnTopPanel(QWidget *panel) {
inline void ReInitOnTopPanel(QWidget *panel) {
}
namespace ThirdParty {
inline void start() {
}
inline void finish() {
}
} // namespace ThirdParty
} // namespace Platform
inline QString psServerPrefix() {
@ -66,8 +75,6 @@ void psClearInitLogs();
void psActivateProcess(uint64 pid = 0);
QString psLocalServerPrefix();
QString psCurrentCountry();
QString psCurrentLanguage();
QString psAppDataPath();
QString psAppDataPathOld();
QString psDownloadPath();

View File

@ -42,8 +42,6 @@ QString gWorkingDir, gExeDir, gExeName;
QStringList gSendPaths;
QString gStartUrl;
QString gLangErrors;
QString gDialogLastPath, gDialogHelperPath; // optimize QFileDialog
bool gStartMinimized = false;

View File

@ -240,8 +240,6 @@ DeclareSetting(QString, LangFile);
DeclareSetting(QStringList, SendPaths);
DeclareSetting(QString, StartUrl);
DeclareSetting(QString, LangErrors);
DeclareSetting(bool, Retina);
DeclareSetting(float64, RetinaFactor);
DeclareSetting(int32, IntRetinaFactor);

View File

@ -212,17 +212,16 @@ void GeneralWidget::chooseCustomLang() {
return;
}
_testLanguage = QFileInfo(result.paths.front()).absoluteFilePath();
Lang::FileParser loader(_testLanguage, { lng_sure_save_language, lng_cancel, lng_box_ok });
auto filePath = result.paths.front();
Lang::FileParser loader(filePath, { lng_sure_save_language, lng_cancel, lng_box_ok });
if (loader.errors().isEmpty()) {
auto result = loader.found();
auto text = result.value(lng_sure_save_language, langOriginal(lng_sure_save_language)),
save = result.value(lng_box_ok, langOriginal(lng_box_ok)),
cancel = result.value(lng_cancel, langOriginal(lng_cancel));
Ui::show(Box<ConfirmBox>(text, save, cancel, base::lambda_guarded(this, [this] {
cSetLangFile(_testLanguage);
cSetLang(languageTest);
Local::writeSettings();
auto text = result.value(lng_sure_save_language, Lang::GetOriginalValue(lng_sure_save_language)),
save = result.value(lng_box_ok, Lang::GetOriginalValue(lng_box_ok)),
cancel = result.value(lng_cancel, Lang::GetOriginalValue(lng_cancel));
Ui::show(Box<ConfirmBox>(text, save, cancel, base::lambda_guarded(this, [this, filePath] {
Lang::Current() = Lang::Instance(filePath, Lang::Instance::CreateFromCustomFileTag());
Local::writeLangPack();
onRestart();
})));
} else {

View File

@ -117,8 +117,6 @@ private:
object_ptr<Ui::WidgetSlideWrap<Ui::Checkbox>> _startMinimized = { nullptr };
object_ptr<Ui::Checkbox> _addInSendTo = { nullptr };
QString _testLanguage;
};
} // namespace Settings

View File

@ -538,8 +538,8 @@ enum {
dbiNotifyView = 0x1c,
dbiSendToMenu = 0x1d,
dbiCompressPastedImage = 0x1e,
dbiLang = 0x1f,
dbiLangFile = 0x20,
dbiLangOld = 0x1f,
dbiLangFileOld = 0x20,
dbiTileBackground = 0x21,
dbiAutoLock = 0x22,
dbiDialogLastPath = 0x23,
@ -568,13 +568,14 @@ enum {
dbiNativeNotifications = 0x44,
dbiNotificationsCount = 0x45,
dbiNotificationsCorner = 0x46,
dbiTheme = 0x47,
dbiThemeKey = 0x47,
dbiDialogsWidthRatio = 0x48,
dbiUseExternalVideoPlayer = 0x49,
dbiDcOptions = 0x4a,
dbiMtpAuthorization = 0x4b,
dbiLastSeenWarningSeenOld = 0x4c,
dbiAuthSessionData = 0x4d,
dbiLangPackKey = 0x4e,
dbiEncryptedWithSalt = 333,
dbiEncrypted = 444,
@ -625,6 +626,7 @@ FileKey _recentHashtagsAndBotsKey = 0;
bool _recentHashtagsAndBotsWereRead = false;
FileKey _savedPeersKey = 0;
FileKey _langPackKey = 0;
typedef QMap<StorageKey, FileDesc> StorageMap;
StorageMap _imagesMap, _stickerImagesMap, _audiosMap;
@ -850,11 +852,17 @@ void _readReportSpamStatuses() {
}
struct ReadSettingsContext {
int legacyLanguageId = Lang::kLegacyLanguageNone;
QString legacyLanguageFile;
MTP::DcOptions dcOptions;
};
void applyReadContext(ReadSettingsContext &&context) {
Messenger::Instance().dcOptions()->addFromOther(std::move(context.dcOptions));
if (context.legacyLanguageId != Lang::kLegacyLanguageNone) {
Lang::Current().fillFromLegacy(context.legacyLanguageId, context.legacyLanguageFile);
writeLangPack();
}
}
bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSettingsContext &context) {
@ -1142,7 +1150,7 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
};
} break;
case dbiTheme: {
case dbiThemeKey: {
quint64 themeKey = 0;
stream >> themeKey;
if (!_checkStreamStatus(stream)) return false;
@ -1150,6 +1158,14 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
_themeKey = themeKey;
} break;
case dbiLangPackKey: {
quint64 langPackKey = 0;
stream >> langPackKey;
if (!_checkStreamStatus(stream)) return false;
_langPackKey = langPackKey;
} break;
case dbiTryIPv6: {
qint32 v;
stream >> v;
@ -1205,22 +1221,20 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
cSetRealScale(s);
} break;
case dbiLang: {
case dbiLangOld: {
qint32 v;
stream >> v;
if (!_checkStreamStatus(stream)) return false;
if (v == languageTest || (v >= 0 && v < languageCount)) {
cSetLang(v);
}
context.legacyLanguageId = v;
} break;
case dbiLangFile: {
case dbiLangFileOld: {
QString v;
stream >> v;
if (!_checkStreamStatus(stream)) return false;
cSetLangFile(v);
context.legacyLanguageFile = v;
} break;
case dbiWindowPosition: {
@ -2212,9 +2226,10 @@ void finish() {
}
void readTheme();
void readLangPack();
void start() {
t_assert(_manager == 0);
Expects(!_manager);
_manager = new internal::Manager();
_localLoader = new TaskQueue(0, FileLoaderQueueStopTimeout);
@ -2269,6 +2284,7 @@ void start() {
_settingsSalt = salt;
readTheme();
readLangPack();
applyReadContext(std::move(context));
}
@ -2301,7 +2317,10 @@ void writeSettings() {
size += Serialize::stringSize(proxy.host) + sizeof(qint32) + Serialize::stringSize(proxy.user) + Serialize::stringSize(proxy.password);
}
if (_themeKey) {
size += sizeof(quint32) + 2 * sizeof(quint64);
size += sizeof(quint32) + sizeof(quint64);
}
if (_langPackKey) {
size += sizeof(quint32) + sizeof(quint64);
}
size += sizeof(quint32) + sizeof(qint32) * 7;
@ -2318,9 +2337,7 @@ void writeSettings() {
data.stream << quint32(dbiAutoUpdate) << qint32(cAutoUpdate());
data.stream << quint32(dbiLastUpdateCheck) << qint32(cLastUpdateCheck());
data.stream << quint32(dbiScale) << qint32(cConfigScale());
data.stream << quint32(dbiLang) << qint32(cLang());
data.stream << quint32(dbiDcOptions) << dcOptionsSerialized;
data.stream << quint32(dbiLangFile) << cLangFile();
data.stream << quint32(dbiConnectionType) << qint32(Global::ConnectionType());
if (Global::ConnectionType() == dbictHttpProxy || Global::ConnectionType() == dbictTcpProxy) {
@ -2329,7 +2346,10 @@ void writeSettings() {
}
data.stream << quint32(dbiTryIPv6) << qint32(Global::TryIPv6());
if (_themeKey) {
data.stream << quint32(dbiTheme) << quint64(_themeKey);
data.stream << quint32(dbiThemeKey) << quint64(_themeKey);
}
if (_langPackKey) {
data.stream << quint32(dbiLangPackKey) << quint64(_langPackKey);
}
TWindowPos pos(cWindowPos());
@ -3808,7 +3828,7 @@ void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const
_themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString();
if (!_themeKey) {
_themeKey = genKey();
_themeKey = genKey(FileOption::Safe);
writeSettings();
}
@ -3839,6 +3859,32 @@ bool hasTheme() {
return (_themeKey != 0);
}
void readLangPack() {
FileReadDescriptor langpack;
if (!_langPackKey || !readEncryptedFile(langpack, _langPackKey, FileOption::Safe, SettingsKey)) {
return;
}
auto data = QByteArray();
langpack.stream >> data;
if (langpack.stream.status() == QDataStream::Ok) {
Lang::Current().fillFromSerialized(data);
}
}
void writeLangPack() {
auto langpack = Lang::Current().serialize();
if (!_langPackKey) {
_langPackKey = genKey(FileOption::Safe);
writeSettings();
}
EncryptedDescriptor data(Serialize::bytearraySize(langpack));
data.stream << langpack;
FileWriteDescriptor file(_langPackKey, FileOption::Safe);
file.writeEncrypted(data, SettingsKey);
}
QString themePaletteAbsolutePath() {
return _themePaletteAbsolutePath;
}

View File

@ -163,6 +163,8 @@ bool hasTheme();
QString themePaletteAbsolutePath();
bool copyThemeColorsToPalette(const QString &file);
void writeLangPack();
void writeRecentHashtagsAndBots();
void readRecentHashtagsAndBots();

View File

@ -179,6 +179,8 @@
<(src_loc)/intro/introstart.h
<(src_loc)/lang/lang_file_parser.cpp
<(src_loc)/lang/lang_file_parser.h
<(src_loc)/lang/lang_instance.cpp
<(src_loc)/lang/lang_instance.h
<(src_loc)/lang/lang_keys.cpp
<(src_loc)/lang/lang_keys.h
<(src_loc)/lang/lang_tag.cpp