diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 61bc31f04..307b7997e 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -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(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(); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 390627ec0..086a6a479 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -40,6 +40,8 @@ class ApiWrap : private MTP::Sender { public: ApiWrap(); + void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0); + using RequestMessageDataCallback = base::lambda; void requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback); diff --git a/Telegram/SourceFiles/boxes/about_box.cpp b/Telegram/SourceFiles/boxes/about_box.cpp index d9908e645..a87edf141 100644 --- a/Telegram/SourceFiles/boxes/about_box.cpp +++ b/Telegram/SourceFiles/boxes/about_box.cpp @@ -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; diff --git a/Telegram/SourceFiles/boxes/language_box.cpp b/Telegram/SourceFiles/boxes/language_box.cpp index 9c76cb066..2d5ad1272 100644 --- a/Telegram/SourceFiles/boxes/language_box.cpp +++ b/Telegram/SourceFiles/boxes/language_box.cpp @@ -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(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(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(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" warnings :(\n\nWarnings: ") + warn)); - return; + request(MTPlangpack_GetLanguages()).done([this](const MTPVector &result) { + auto currentId = Lang::Current().id(); + auto currentFound = false; + std::vector languageIds = { qsl("en") }; + std::vector 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(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(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(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(text, save, cancel, base::lambda_guarded(this, [this, languageId] { + // cSetLang(languageId); + // Local::writeSettings(); + // App::restart(); + //}), base::lambda_guarded(this, [this] { + // _langGroup->setValue(cLang()); + //})), KeepOtherLayers); } diff --git a/Telegram/SourceFiles/boxes/language_box.h b/Telegram/SourceFiles/boxes/language_box.h index 261d12b65..9c1d4579a 100644 --- a/Telegram/SourceFiles/boxes/language_box.h +++ b/Telegram/SourceFiles/boxes/language_box.h @@ -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); diff --git a/Telegram/SourceFiles/codegen/emoji/generator.cpp b/Telegram/SourceFiles/codegen/emoji/generator.cpp index 2e9c1c90b..861f20f70 100644 --- a/Telegram/SourceFiles/codegen/emoji/generator.cpp +++ b/Telegram/SourceFiles/codegen/emoji/generator.cpp @@ -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 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) { diff --git a/Telegram/SourceFiles/codegen/lang/generator.cpp b/Telegram/SourceFiles/codegen/lang/generator.cpp index f34e9a56b..e0fa74a08 100644 --- a/Telegram/SourceFiles/codegen/lang/generator.cpp +++ b/Telegram/SourceFiles/codegen/lang/generator.cpp @@ -24,7 +24,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include #include #include -#include #include #include @@ -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> &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; } diff --git a/Telegram/SourceFiles/codegen/lang/generator.h b/Telegram/SourceFiles/codegen/lang/generator.h index 60496a9d3..549a464e0 100644 --- a/Telegram/SourceFiles/codegen/lang/generator.h +++ b/Telegram/SourceFiles/codegen/lang/generator.h @@ -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 void writeSetSearch(const std::set> &set, ComputeResult computeResult, const QString &invalidResult); - const Langpack &langpack_; + const LangPack &langpack_; QString basePath_, baseName_; const common::ProjectInfo &project_; std::unique_ptr source_, header_; diff --git a/Telegram/SourceFiles/codegen/lang/parsed_file.cpp b/Telegram/SourceFiles/codegen/lang/parsed_file.cpp index 12597f9c5..93b5ee243 100644 --- a/Telegram/SourceFiles/codegen/lang/parsed_file.cpp +++ b/Telegram/SourceFiles/codegen/lang/parsed_file.cpp @@ -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); diff --git a/Telegram/SourceFiles/codegen/lang/parsed_file.h b/Telegram/SourceFiles/codegen/lang/parsed_file.h index 7ee1c4298..ae5431f59 100644 --- a/Telegram/SourceFiles/codegen/lang/parsed_file.h +++ b/Telegram/SourceFiles/codegen/lang/parsed_file.h @@ -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_; }; diff --git a/Telegram/SourceFiles/codegen/lang/processor.cpp b/Telegram/SourceFiles/codegen/lang/processor.cpp index d24d06b31..87a87c18a 100644 --- a/Telegram/SourceFiles/codegen/lang/processor.cpp +++ b/Telegram/SourceFiles/codegen/lang/processor.cpp @@ -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(".")) { diff --git a/Telegram/SourceFiles/codegen/lang/processor.h b/Telegram/SourceFiles/codegen/lang/processor.h index 0e412a7a0..d885495d1 100644 --- a/Telegram/SourceFiles/codegen/lang/processor.h +++ b/Telegram/SourceFiles/codegen/lang/processor.h @@ -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 parser_; const Options &options_; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 5d84a5703..042979477 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -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 diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 63c545890..1e6cb4471 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -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(); - - 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); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 493d7efe4..05cbb97a6 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -218,8 +218,6 @@ void finish(); uint64 UserTag(); -DeclareReadOnlyVar(QString, LangSystemISO); -DeclareReadOnlyVar(int32, LangSystem); DeclareVar(QByteArray, LastCrashDump); DeclareVar(ProxyData, PreLaunchProxy); diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index 48639864a..7399de2da 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -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(this, st::introBackButton), st::introSlideDuration) , _settings(this, object_ptr(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(this, text), st::introCoverDuration); - _changeLanguage->entity()->setClickedCallback([this, systemLangId] { changeLanguage(systemLangId); }); - } - } - } else { - _changeLanguage.create(this, object_ptr(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(this, text), st::introCoverDuration); + // _changeLanguage->entity()->setClickedCallback([this, systemLangId] { changeLanguage(systemLangId); }); + // } + // } + //} else { + // _changeLanguage.create(this, object_ptr(this, Lang::GetOriginalValue(lng_switch_to_this)), st::introCoverDuration); + // _changeLanguage->entity()->setClickedCallback([this] { changeLanguage(languageDefault); }); + //} MTP::send(MTPhelp_GetNearestDc(), rpcDone(&Widget::gotNearestDC)); diff --git a/Telegram/SourceFiles/lang/lang_file_parser.cpp b/Telegram/SourceFiles/lang/lang_file_parser.cpp index ca8fde6ad..9739bf696 100644 --- a/Telegram/SourceFiles/lang/lang_file_parser.cpp +++ b/Telegram/SourceFiles/lang/lang_file_parser.cpp @@ -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 &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 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 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(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 diff --git a/Telegram/SourceFiles/lang/lang_file_parser.h b/Telegram/SourceFiles/lang/lang_file_parser.h index b7bc2b561..84ca01495 100644 --- a/Telegram/SourceFiles/lang/lang_file_parser.h +++ b/Telegram/SourceFiles/lang/lang_file_parser.h @@ -28,7 +28,10 @@ class FileParser { public: using Result = QMap; - FileParser(const QString &file, const std::set &request = std::set()); + FileParser(const QString &file, const std::set &request); + FileParser(const QByteArray &content, base::lambda 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 _found = { { false } }; - QString _filePath; - std::set _request; + const QByteArray _content; + const std::set _request; + const base::lambda _callback; - bool _readingAll = false; Result _result; }; diff --git a/Telegram/SourceFiles/lang/lang_instance.cpp b/Telegram/SourceFiles/lang/lang_instance.cpp new file mode 100644 index 000000000..b745d64e7 --- /dev/null +++ b/Telegram/SourceFiles/lang/lang_instance.cpp @@ -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 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 _plural; + OrderedSet _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 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(); + 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 diff --git a/Telegram/SourceFiles/lang/lang_instance.h b/Telegram/SourceFiles/lang/lang_instance.h new file mode 100644 index 000000000..dc6ab770d --- /dev/null +++ b/Telegram/SourceFiles/lang/lang_instance.h @@ -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 _values; + std::map _nonDefaultValues; + +}; + +Instance &Current(); + +} // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_keys.h b/Telegram/SourceFiles/lang/lang_keys.h index fafeae4e1..66afd2e2e 100644 --- a/Telegram/SourceFiles/lang/lang_keys.h +++ b/Telegram/SourceFiles/lang/lang_keys.h @@ -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 inline QString langDateMaybeWithYear(QDate date, WithYear withYear, WithoutYear withoutYear) { diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index b5c985ee2..81a52abaf 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -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; } diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index a83a026ac..4a4c352a4 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -67,6 +67,7 @@ struct Messenger::Private { Messenger::Messenger() : QObject() , _private(std::make_unique()) +, _langpack(std::make_unique()) , _audio(std::make_unique()) , _logo(Window::LoadLogo()) , _logoNoMargin(Window::LoadLogoNoMargin()) { @@ -96,7 +97,10 @@ Messenger::Messenger() : QObject() cSetConfigScale(dbisOne); cSetRealScale(dbisOne); } - loadLanguage(); + + _translator = std::make_unique(); + 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(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(); - QCoreApplication::instance()->installTranslator(_translator.get()); -} - void Messenger::startLocalStorage() { _dcOptions = std::make_unique(); _dcOptions->constructFromBuiltIn(); @@ -417,7 +382,7 @@ void Messenger::startLocalStorage() { }); subscribe(authSessionChanged(), [this] { if (_mtproto) { - _mtproto->configLoadRequest(); + _mtproto->requestConfig(); } }); } diff --git a/Telegram/SourceFiles/messenger.h b/Telegram/SourceFiles/messenger.h index bb2d3cdad..7b2aaa601 100644 --- a/Telegram/SourceFiles/messenger.h +++ b/Telegram/SourceFiles/messenger.h @@ -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 &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 _window; FileUploader *_uploader = nullptr; + std::unique_ptr _langpack; std::unique_ptr _translator; std::unique_ptr _dcOptions; std::unique_ptr _mtproto; diff --git a/Telegram/SourceFiles/mtproto/connection.cpp b/Telegram/SourceFiles/mtproto/connection.cpp index bb6bf48ed..b6be6f221 100644 --- a/Telegram/SourceFiles/mtproto/connection.cpp +++ b/Telegram/SourceFiles/mtproto/connection.cpp @@ -780,7 +780,7 @@ void ConnectionPrivate::tryToSend() { MTPInitConnection 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() { diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.cpp b/Telegram/SourceFiles/mtproto/mtp_instance.cpp index 82947e6b1..50ec31e64 100644 --- a/Telegram/SourceFiles/mtproto/mtp_instance.cpp +++ b/Telegram/SourceFiles/mtproto/mtp_instance.cpp @@ -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 _stateChangedHandler; base::lambda _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 ¤t = 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 ¤t = 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) { diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.h b/Telegram/SourceFiles/mtproto/mtp_instance.h index 4202a76a5..26ffeb899 100644 --- a/Telegram/SourceFiles/mtproto/mtp_instance.h +++ b/Telegram/SourceFiles/mtproto/mtp_instance.h @@ -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(); diff --git a/Telegram/SourceFiles/mtproto/session.cpp b/Telegram/SourceFiles/mtproto/session.cpp index 9ceb485e1..f15c042c9 100644 --- a/Telegram/SourceFiles/mtproto/session.cpp +++ b/Telegram/SourceFiles/mtproto/session.cpp @@ -76,6 +76,8 @@ Session::Session(gsl::not_null 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(); } diff --git a/Telegram/SourceFiles/mtproto/session.h b/Telegram/SourceFiles/mtproto/session.h index 205bf7ab0..e948c53ba 100644 --- a/Telegram/SourceFiles/mtproto/session.h +++ b/Telegram/SourceFiles/mtproto/session.h @@ -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 diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index edbcca4b7..20f20a569 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -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() { diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index 290aa814d..1796d3519 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -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[]); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.cpp b/Telegram/SourceFiles/platform/mac/specific_mac.cpp index bde81f6ed..e30b80b25 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.cpp +++ b/Telegram/SourceFiles/platform/mac/specific_mac.cpp @@ -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() { diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h index 7fc27c454..00410c2bb 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac.h @@ -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[]); diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h index afdac112f..359b0476a 100644 --- a/Telegram/SourceFiles/platform/platform_specific.h +++ b/Telegram/SourceFiles/platform/platform_specific.h @@ -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 diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index 40e55d76d..d5ebadd63 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -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 { diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h index 1d98dbff9..3c48db3f2 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.h +++ b/Telegram/SourceFiles/platform/win/specific_win.h @@ -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(); diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 8c5ad87d2..5d99db480 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -42,8 +42,6 @@ QString gWorkingDir, gExeDir, gExeName; QStringList gSendPaths; QString gStartUrl; -QString gLangErrors; - QString gDialogLastPath, gDialogHelperPath; // optimize QFileDialog bool gStartMinimized = false; diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index 232c70bf0..f2b2a1ab9 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -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); diff --git a/Telegram/SourceFiles/settings/settings_general_widget.cpp b/Telegram/SourceFiles/settings/settings_general_widget.cpp index b56a120d6..f364cde1e 100644 --- a/Telegram/SourceFiles/settings/settings_general_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_general_widget.cpp @@ -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(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(text, save, cancel, base::lambda_guarded(this, [this, filePath] { + Lang::Current() = Lang::Instance(filePath, Lang::Instance::CreateFromCustomFileTag()); + Local::writeLangPack(); onRestart(); }))); } else { diff --git a/Telegram/SourceFiles/settings/settings_general_widget.h b/Telegram/SourceFiles/settings/settings_general_widget.h index 176455bd9..1c1d0942c 100644 --- a/Telegram/SourceFiles/settings/settings_general_widget.h +++ b/Telegram/SourceFiles/settings/settings_general_widget.h @@ -117,8 +117,6 @@ private: object_ptr> _startMinimized = { nullptr }; object_ptr _addInSendTo = { nullptr }; - QString _testLanguage; - }; } // namespace Settings diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index dda780cea..3fc7884df 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -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 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; } diff --git a/Telegram/SourceFiles/storage/localstorage.h b/Telegram/SourceFiles/storage/localstorage.h index 9039e08a9..f99051132 100644 --- a/Telegram/SourceFiles/storage/localstorage.h +++ b/Telegram/SourceFiles/storage/localstorage.h @@ -163,6 +163,8 @@ bool hasTheme(); QString themePaletteAbsolutePath(); bool copyThemeColorsToPalette(const QString &file); +void writeLangPack(); + void writeRecentHashtagsAndBots(); void readRecentHashtagsAndBots(); diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 52dd111b6..dc574336a 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -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