diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 76fe0ac46..e587594d9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1788,6 +1788,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_export_about_chats" = "This page lists all chats from this export and where to look for their data."; "lng_export_about_left_chats" = "Below are the supergroups and channels from this export that you've left or where you were banned.\n\nNote that when you leave a channel or supergroup you've created, you have the option to either delete it, or simply leave (in case you want to rejoin later, or keep the community alive despite not being a member)."; +"lng_language_switch_title" = "Change language?"; +"lng_language_switch_about" = "You are about to apply a custom language pack {lang_name} that is {percent}% complete.\n\nThis will translate the entire interface. You can suggest corrections in the {link}.\n\nYou can change your language back at any time in Settings."; +"lng_language_switch_link" = "translation panel"; +"lng_language_switch_apply" = "Change"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/boxes/language_box.cpp b/Telegram/SourceFiles/boxes/language_box.cpp index 2ea33f775..710fe3efc 100644 --- a/Telegram/SourceFiles/boxes/language_box.cpp +++ b/Telegram/SourceFiles/boxes/language_box.cpp @@ -83,10 +83,10 @@ void LanguageBox::Inner::languageChanged(int languageIndex) { } void LanguageBox::Inner::activateCurrent() { - auto currentId = Lang::Current().id(); + const auto currentId = Lang::LanguageIdOrDefault(Lang::Current().id()); for (auto i = 0, count = _languages->size(); i != count; ++i) { - auto languageId = (*_languages)[i].id; - auto isCurrent = (languageId == currentId) || (languageId == Lang::DefaultLanguageId() && currentId.isEmpty()); + const auto languageId = (*_languages)[i].id; + const auto isCurrent = (languageId == currentId); if (isCurrent) { _group->setValue(i); return; @@ -128,11 +128,11 @@ void LanguageBox::refreshLanguages() { _languages = Languages(); auto list = Lang::CurrentCloudManager().languageList(); _languages.reserve(list.size() + 1); - auto currentId = Lang::Current().id(); + const auto currentId = Lang::LanguageIdOrDefault(Lang::Current().id()); auto currentIndex = -1; _languages.push_back({ qsl("en"), qsl("English"), qsl("English") }); for (auto &language : list) { - auto isCurrent = (language.id == currentId) || (language.id == Lang::DefaultLanguageId() && currentId.isEmpty()); + const auto isCurrent = (language.id == currentId); if (language.id != qstr("en")) { if (isCurrent) { currentIndex = _languages.size(); diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 9bdc6f47b..3fb82c161 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -36,6 +36,8 @@ QString tryConvertUrlToLocal(QString url) { return qsl("tg://join?invite=") + url_encode(joinChatMatch->captured(1)); } else if (auto stickerSetMatch = regex_match(qsl("^addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), query, matchOptions)) { return qsl("tg://addstickers?set=") + url_encode(stickerSetMatch->captured(1)); + } else if (auto languageMatch = regex_match(qsl("^setlanguage/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) { + return qsl("tg://setlanguage?lang=") + url_encode(languageMatch->captured(1)); } else if (auto shareUrlMatch = regex_match(qsl("^share/url/?\\?(.+)$"), query, matchOptions)) { return qsl("tg://msg_url?") + shareUrlMatch->captured(1); } else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)"), query, matchOptions)) { diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index 8dc101d97..e6a67b9e2 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -142,21 +142,21 @@ void Widget::createLanguageLink() { updateControlsGeometry(); }; - auto currentId = Lang::Current().id(); - auto defaultId = Lang::DefaultLanguageId(); - auto suggestedId = Lang::CurrentCloudManager().suggestedLanguage(); - if (!currentId.isEmpty() && currentId != defaultId) { + const auto currentId = Lang::LanguageIdOrDefault(Lang::Current().id()); + const auto defaultId = Lang::DefaultLanguageId(); + const auto suggested = Lang::CurrentCloudManager().suggestedLanguage(); + if (currentId != defaultId) { createLink(Lang::GetOriginalValue(lng_switch_to_this), defaultId); - } else if (!suggestedId.isEmpty() && suggestedId != currentId) { + } else if (!suggested.isEmpty() && suggested != currentId) { request(MTPlangpack_GetStrings( MTP_string(Lang::CloudLangPackName()), - MTP_string(suggestedId), + MTP_string(suggested), MTP_vector(1, MTP_string("lng_switch_to_this")) )).done([=](const MTPVector &result) { auto strings = Lang::Instance::ParseStrings(result); auto it = strings.find(lng_switch_to_this); if (it != strings.end()) { - createLink(it->second, suggestedId); + createLink(it->second, suggested); } }).send(); } @@ -619,10 +619,10 @@ void Widget::Step::finish(const MTPUser &user, QImage &&photo) { } // Save the default language if we've suggested some other and user ignored it. - auto currentId = Lang::Current().id(); - auto defaultId = Lang::DefaultLanguageId(); - auto suggestedId = Lang::CurrentCloudManager().suggestedLanguage(); - if (currentId.isEmpty() && !suggestedId.isEmpty() && suggestedId != defaultId) { + const auto currentId = Lang::Current().id(); + const auto defaultId = Lang::DefaultLanguageId(); + const auto suggested = Lang::CurrentCloudManager().suggestedLanguage(); + if (currentId.isEmpty() && !suggested.isEmpty() && suggested != defaultId) { Lang::Current().switchToId(defaultId); Local::writeLangPack(); } diff --git a/Telegram/SourceFiles/lang/lang_cloud_manager.cpp b/Telegram/SourceFiles/lang/lang_cloud_manager.cpp index 74505aa5c..34bd9d436 100644 --- a/Telegram/SourceFiles/lang/lang_cloud_manager.cpp +++ b/Telegram/SourceFiles/lang/lang_cloud_manager.cpp @@ -14,51 +14,179 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "auth_session.h" #include "boxes/confirm_box.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/widgets/labels.h" #include "lang/lang_file_parser.h" #include "core/file_utilities.h" +#include "core/click_handler_types.h" +#include "styles/style_boxes.h" namespace Lang { +namespace { + +class ConfirmSwitchBox : public BoxContent { +public: + ConfirmSwitchBox( + QWidget*, + const MTPDlangPackLanguage &data, + const QString &editLink, + Fn apply); + +protected: + void prepare() override; + +private: + QString _name; + int _percent = 0; + QString _editLink; + Fn _apply; + +}; + +ConfirmSwitchBox::ConfirmSwitchBox( + QWidget*, + const MTPDlangPackLanguage &data, + const QString &editLink, + Fn apply) +: _name(qs(data.vname)) +, _percent(data.vtranslated_count.v * 100 / data.vstrings_count.v) +, _editLink(editLink) +, _apply(std::move(apply)) { +} + +void ConfirmSwitchBox::prepare() { + setTitle(langFactory(lng_language_switch_title)); + + auto link = TextWithEntities{ lang(lng_language_switch_link) }; + link.entities.push_back(EntityInText( + EntityInTextCustomUrl, + 0, + link.text.size(), + QString("internal:go_to_translations"))); + auto name = TextWithEntities{ _name }; + name.entities.push_back(EntityInText( + EntityInTextBold, + 0, + name.text.size())); + auto percent = TextWithEntities{ QString::number(_percent) }; + percent.entities.push_back(EntityInText( + EntityInTextBold, + 0, + percent.text.size())); + const auto text = lng_language_switch_about__generic( + lt_lang_name, + name, + lt_percent, + percent, + lt_link, + link); + auto content = Ui::CreateChild>( + this, + object_ptr( + this, + rpl::single(text)), + QMargins{ st::boxPadding.left(), 0, st::boxPadding.right(), 0 }); + content->entity()->setClickHandlerFilter([=](auto&&...) { + UrlClickHandler::Open(_editLink); + return false; + }); + + addButton(langFactory(lng_language_switch_apply), [=] { + const auto apply = _apply; + closeBox(); + apply(); + }); + addButton(langFactory(lng_cancel), [=] { closeBox(); }); + + content->resizeToWidth(st::boxWidth); + content->heightValue( + ) | rpl::start_with_next([=](int height) { + setDimensions(st::boxWidth, height); + }, lifetime()); +} + +} // namespace CloudManager::CloudManager( Instance &langpack, not_null mtproto) : MTP::Sender() , _langpack(langpack) { - requestLangPackDifference(); + const auto current = LanguageIdOrDefault(_langpack.id()); + requestLangPackDifference(current); + if (const auto base = _langpack.baseId(); !base.isEmpty()) { + requestLangPackDifference(base); + } } -void CloudManager::requestLangPackDifference() { - request(_langPackRequestId).cancel(); +Pack CloudManager::packTypeFromId(const QString &id) const { + if (id == LanguageIdOrDefault(_langpack.id())) { + return Pack::Current; + } else if (id == _langpack.baseId()) { + return Pack::Base; + } + return Pack::None; +} + +void CloudManager::requestLangPackDifference(const QString &langId) { + Expects(!langId.isEmpty()); + + if (langId == LanguageIdOrDefault(_langpack.id())) { + requestLangPackDifference(Pack::Current); + } else { + requestLangPackDifference(Pack::Base); + } +} + +mtpRequestId &CloudManager::packRequestId(Pack pack) { + return (pack != Pack::Base) + ? _langPackRequestId + : _langPackBaseRequestId; +} + +mtpRequestId CloudManager::packRequestId(Pack pack) const { + return (pack != Pack::Base) + ? _langPackRequestId + : _langPackBaseRequestId; +} + +void CloudManager::requestLangPackDifference(Pack pack) { + const auto base = (pack == Pack::Base); + request(base::take(packRequestId(pack))).cancel(); if (_langpack.isCustom()) { return; } - auto version = _langpack.version(); + const auto version = _langpack.version(pack); + const auto code = _langpack.cloudLangCode(pack); + if (code.isEmpty()) { + return; + } if (version > 0) { - _langPackRequestId = request(MTPlangpack_GetDifference( + packRequestId(pack) = request(MTPlangpack_GetDifference( + MTP_string(code), MTP_int(version) )).done([=](const MTPLangPackDifference &result) { - _langPackRequestId = 0; + packRequestId(pack) = 0; applyLangPackDifference(result); }).fail([=](const RPCError &error) { - _langPackRequestId = 0; + packRequestId(pack) = 0; }).send(); } else { - _langPackRequestId = request(MTPlangpack_GetLangPack( + packRequestId(pack) = request(MTPlangpack_GetLangPack( MTP_string(CloudLangPackName()), - MTP_string(_langpack.cloudLangCode()) + MTP_string(code) )).done([=](const MTPLangPackDifference &result) { - _langPackRequestId = 0; + packRequestId(pack) = 0; applyLangPackDifference(result); }).fail([=](const RPCError &error) { - _langPackRequestId = 0; + packRequestId(pack) = 0; }).send(); } } void CloudManager::setSuggestedLanguage(const QString &langCode) { - if (!langCode.isEmpty() - && langCode != Lang::DefaultLanguageId()) { + if (Lang::LanguageIdOrDefault(langCode) != Lang::DefaultLanguageId()) { _suggestedLanguage = langCode; } else { _suggestedLanguage = QString(); @@ -89,21 +217,27 @@ void CloudManager::setSuggestedLanguage(const QString &langCode) { } } -void CloudManager::applyLangPackDifference(const MTPLangPackDifference &difference) { +void CloudManager::applyLangPackDifference( + const MTPLangPackDifference &difference) { Expects(difference.type() == mtpc_langPackDifference); + if (_langpack.isCustom()) { return; } - auto &langpack = difference.c_langPackDifference(); - auto langpackId = qs(langpack.vlang_code); - if (needToApplyLangPack(langpackId)) { - applyLangPackData(langpack); + const auto &langpack = difference.c_langPackDifference(); + const auto langpackId = qs(langpack.vlang_code); + const auto pack = packTypeFromId(langpackId); + if (pack != Pack::None) { + applyLangPackData(pack, langpack); if (_restartAfterSwitch) { - App::restart(); + restartAfterSwitch(); } } else { - LOG(("Lang Warning: Ignoring update for '%1' because our language is '%2'").arg(langpackId).arg(_langpack.id())); + LOG(("Lang Warning: " + "Ignoring update for '%1' because our language is '%2'" + ).arg(langpackId + ).arg(_langpack.id())); } } @@ -127,16 +261,6 @@ void CloudManager::requestLanguageList() { }).send(); } -bool CloudManager::needToApplyLangPack(const QString &id) { - auto currentId = _langpack.id(); - if (currentId == id) { - return true; - } else if (currentId.isEmpty() && id == DefaultLanguageId()) { - return true; - } - return false; -} - void CloudManager::offerSwitchLangPack() { Expects(!_offerSwitchToId.isEmpty()); Expects(_offerSwitchToId != DefaultLanguageId()); @@ -150,7 +274,7 @@ void CloudManager::offerSwitchLangPack() { } QString CloudManager::findOfferedLanguageName() { - for_const (auto &language, _languages) { + for (const auto &language : _languages) { if (language.id == _offerSwitchToId) { return language.name; } @@ -178,12 +302,13 @@ bool CloudManager::showOfferSwitchBox() { return true; } -void CloudManager::applyLangPackData(const MTPDlangPackDifference &data) { - switchLangPackId(qs(data.vlang_code)); - if (_langpack.version() < data.vfrom_version.v) { - requestLangPackDifference(); +void CloudManager::applyLangPackData( + Pack pack, + const MTPDlangPackDifference &data) { + if (_langpack.version(pack) < data.vfrom_version.v) { + requestLangPackDifference(pack); } else if (!data.vstrings.v.isEmpty()) { - _langpack.applyDifference(data); + _langpack.applyDifference(pack, data); Local::writeLangPack(); } else if (_restartAfterSwitch) { Local::writeLangPack(); @@ -205,28 +330,64 @@ void CloudManager::resetToDefault() { performSwitch(DefaultLanguageId()); } -void CloudManager::switchToLanguage(QString id) { - if (id.isEmpty()) { - id = DefaultLanguageId(); - } - if (_langpack.id() == id && id != qstr("#custom")) { +void CloudManager::switchWithWarning(const QString &id) { + Expects(!id.isEmpty()); + + if (LanguageIdOrDefault(_langpack.id()) == id) { return; } request(_switchingToLanguageRequest).cancel(); - if (id == qstr("#custom")) { + _switchingToLanguageRequest = request(MTPlangpack_GetLanguage( + MTP_string(Lang::CloudLangPackName()), + MTP_string(id) + )).done([=](const MTPLangPackLanguage &result) { + _switchingToLanguageRequest = 0; + result.match([=](const MTPDlangPackLanguage &data) { + if (data.vtranslated_count.v > 0) { + const auto pluralId = qs(data.vplural_code); + const auto baseId = qs(data.vbase_lang_code); + const auto perform = [=] { + performSwitchAndRestart(id, pluralId, baseId); + }; + Ui::show(Box( + data, + "https://translations.telegram.org/" + id + '/', + perform)); + } else { + Ui::show(Box( + "not translated :(")); + } + }); + }).fail([=](const RPCError &error) { + _switchingToLanguageRequest = 0; + }).send(); +} + +void CloudManager::switchToLanguage( + const QString &id, + const QString &pluralId, + const QString &baseId) { + const auto requested = LanguageIdOrDefault(id); + if (_langpack.id() == requested && requested != qstr("#custom")) { + return; + } + + request(_switchingToLanguageRequest).cancel(); + if (requested == qstr("#custom")) { performSwitchToCustom(); - } else if (canApplyWithoutRestart(id)) { - performSwitch(id); + } else if (canApplyWithoutRestart(requested)) { + performSwitch(requested, pluralId, baseId); } else { QVector keys; keys.reserve(3); keys.push_back(MTP_string("lng_sure_save_language")); _switchingToLanguageRequest = request(MTPlangpack_GetStrings( MTP_string(Lang::CloudLangPackName()), - MTP_string(id), + MTP_string(requested), MTP_vector(std::move(keys)) )).done([=](const MTPVector &result) { + _switchingToLanguageRequest = 0; const auto values = Instance::ParseStrings(result); const auto getValue = [&](LangKey key) { auto it = values.find(key); @@ -237,13 +398,18 @@ void CloudManager::switchToLanguage(QString id) { const auto text = lang(lng_sure_save_language) + "\n\n" + getValue(lng_sure_save_language); + const auto perform = [=] { + performSwitchAndRestart(requested, pluralId, baseId); + }; Ui::show( Box( text, lang(lng_box_ok), lang(lng_cancel), - [=] { performSwitchAndRestart(id); }), + perform), LayerOption::KeepOther); + }).fail([=](const RPCError &error) { + _switchingToLanguageRequest = 0; }).send(); } } @@ -300,31 +466,52 @@ void CloudManager::switchToTestLanguage() { performSwitch(testLanguageId); } -void CloudManager::performSwitch(const QString &id) { +void CloudManager::performSwitch( + const QString &id, + const QString &pluralId, + const QString &baseId) { _restartAfterSwitch = false; - switchLangPackId(id); - requestLangPackDifference(); + switchLangPackId(id, pluralId, baseId); + requestLangPackDifference(Pack::Current); + requestLangPackDifference(Pack::Base); } -void CloudManager::performSwitchAndRestart(const QString &id) { - performSwitch(id); - if (_langPackRequestId) { +void CloudManager::performSwitchAndRestart( + const QString &id, + const QString &pluralId, + const QString &baseId) { + performSwitch(id, pluralId, baseId); + restartAfterSwitch(); +} + +void CloudManager::restartAfterSwitch() { + if (_langPackRequestId || _langPackBaseRequestId) { _restartAfterSwitch = true; } else { App::restart(); } } -void CloudManager::switchLangPackId(const QString &id) { - auto currentId = _langpack.id(); - auto notChanged = (currentId == id) || (currentId.isEmpty() && id == DefaultLanguageId()); +void CloudManager::switchLangPackId( + const QString &id, + const QString &pluralId, + const QString &baseId) { + const auto currentId = _langpack.id(); + const auto currentBaseId = _langpack.baseId(); + const auto notChanged = (currentId == id && currentBaseId == baseId) + || (currentId.isEmpty() + && currentBaseId.isEmpty() + && id == DefaultLanguageId()); if (!notChanged) { - changeIdAndReInitConnection(id); + changeIdAndReInitConnection(id, pluralId, baseId); } } -void CloudManager::changeIdAndReInitConnection(const QString &id) { - _langpack.switchToId(id); +void CloudManager::changeIdAndReInitConnection( + const QString &id, + const QString &pluralId, + const QString &baseId) { + _langpack.switchToId(id, pluralId, baseId); auto mtproto = requestMTP(); mtproto->reInitConnection(mtproto->mainDcId()); diff --git a/Telegram/SourceFiles/lang/lang_cloud_manager.h b/Telegram/SourceFiles/lang/lang_cloud_manager.h index 4c0f48dd1..05aa74acb 100644 --- a/Telegram/SourceFiles/lang/lang_cloud_manager.h +++ b/Telegram/SourceFiles/lang/lang_cloud_manager.h @@ -17,6 +17,7 @@ class Instance; namespace Lang { class Instance; +enum class Pack; class CloudManager : public base::has_weak_ptr, private MTP::Sender, private base::Subscriber { public: @@ -36,11 +37,15 @@ public: base::Observable &languageListChanged() { return _languagesChanged; } - void requestLangPackDifference(); + void requestLangPackDifference(const QString &langId); void applyLangPackDifference(const MTPLangPackDifference &difference); void resetToDefault(); - void switchToLanguage(QString id); + void switchWithWarning(const QString &id); + void switchToLanguage( + const QString &id, + const QString &pluralId = QString(), + const QString &baseId = QString()); void switchToTestLanguage(); void setSuggestedLanguage(const QString &langCode); QString suggestedLanguage() const { @@ -51,23 +56,40 @@ public: } private: + mtpRequestId &packRequestId(Pack pack); + mtpRequestId packRequestId(Pack pack) const; + Pack packTypeFromId(const QString &id) const; + void requestLangPackDifference(Pack pack); bool canApplyWithoutRestart(const QString &id) const; void performSwitchToCustom(); - void performSwitch(const QString &id); - void performSwitchAndRestart(const QString &id); + void performSwitch( + const QString &id, + const QString &pluralId = QString(), + const QString &baseId = QString()); + void performSwitchAndRestart( + const QString &id, + const QString &pluralId = QString(), + const QString &baseId = QString()); + void restartAfterSwitch(); void offerSwitchLangPack(); bool showOfferSwitchBox(); QString findOfferedLanguageName(); - bool needToApplyLangPack(const QString &id); - void applyLangPackData(const MTPDlangPackDifference &data); - void switchLangPackId(const QString &id); - void changeIdAndReInitConnection(const QString &id); + void applyLangPackData(Pack pack, const MTPDlangPackDifference &data); + void switchLangPackId( + const QString &id, + const QString &pluralId, + const QString &baseId); + void changeIdAndReInitConnection( + const QString &id, + const QString &pluralId = QString(), + const QString &baseId = QString()); Instance &_langpack; Languages _languages; base::Observable _languagesChanged; mtpRequestId _langPackRequestId = 0; + mtpRequestId _langPackBaseRequestId = 0; mtpRequestId _languagesRequestId = 0; QString _offerSwitchToId; diff --git a/Telegram/SourceFiles/lang/lang_instance.cpp b/Telegram/SourceFiles/lang/lang_instance.cpp index 2cd85d1e9..af974108e 100644 --- a/Telegram/SourceFiles/lang/lang_instance.cpp +++ b/Telegram/SourceFiles/lang/lang_instance.cpp @@ -20,8 +20,18 @@ namespace { constexpr auto kDefaultLanguage = str_const("en"); constexpr auto kCloudLangPackName = str_const("tdesktop"); +constexpr auto kCustomLanguage = str_const("#custom"); constexpr auto kLangValuesLimit = 20000; +std::vector PrepareDefaultValues() { + auto result = std::vector(); + result.reserve(kLangKeysCount); + for (auto i = 0; i != kLangKeysCount; ++i) { + result.emplace_back(GetOriginalValue(LangKey(i))); + } + return result; +} + class ValueParser { public: ValueParser( @@ -163,55 +173,126 @@ QString PrepareTestValue(const QString ¤t, QChar filler) { return result; } +QString PluralCodeForCustom( + const QString &absolutePath, + const QString &relativePath) { + const auto path = !absolutePath.isEmpty() + ? absolutePath + : relativePath; + const auto name = QFileInfo(path).fileName(); + if (const auto match = qthelp::regex_match( + "_([a-z]{2,3}_[A-Z]{2,3}|\\-[a-z]{2,3})?\\.", + name)) { + return match->captured(1); + } + return DefaultLanguageId(); +} + +template +void ParseKeyValue( + const QByteArray &key, + const QByteArray &value, + Save &&save) { + const auto index = GetKeyIndex(QLatin1String(key)); + if (index != kLangKeysCount) { + ValueParser parser(key, index, value); + if (parser.parse()) { + save(index, parser.takeResult()); + } + } else if (!key.startsWith("cloud_")) { + DEBUG_LOG(("Lang Warning: Unknown key '%1'" + ).arg(QString::fromLatin1(key))); + } +} + } // namespace QString DefaultLanguageId() { return str_const_toString(kDefaultLanguage); } +QString LanguageIdOrDefault(const QString &id) { + return !id.isEmpty() ? id : DefaultLanguageId(); +} + QString CloudLangPackName() { return str_const_toString(kCloudLangPackName); } -void Instance::switchToId(const QString &id) { - reset(); - _id = id; +QString CustomLanguageId() { + return str_const_toString(kCustomLanguage); +} + +struct Instance::PrivateTag { +}; + +Instance::Instance() +: _values(PrepareDefaultValues()) +, _nonDefaultSet(kLangKeysCount, 0) { +} + +Instance::Instance(not_null derived, const PrivateTag &) +: _derived(derived) +, _nonDefaultSet(kLangKeysCount, 0) { +} + +void Instance::switchToId( + const QString &id, + const QString &pluralId, + const QString &baseId) { + reset(id, pluralId, baseId); if (_id == qstr("#TEST_X") || _id == qstr("#TEST_0")) { for (auto &value : _values) { value = PrepareTestValue(value, _id[5]); } - _updated.notify(); + if (!_derived) { + _updated.notify(); + } } updatePluralRules(); } -void Instance::switchToCustomFile(const QString &filePath) { - reset(); - fillFromCustomFile(filePath); - Local::writeLangPack(); - _updated.notify(); +void Instance::setBaseId(const QString &baseId, const QString &pluralId) { + if (baseId.isEmpty()) { + _base = nullptr; + } else { + if (!_base) { + _base = std::make_unique(this, PrivateTag{}); + } + _base->switchToId(baseId, _pluralId, QString()); + } } -void Instance::reset() { - _values.clear(); - _nonDefaultValues.clear(); - _nonDefaultSet.clear(); +void Instance::switchToCustomFile(const QString &filePath) { + if (loadFromCustomFile(filePath)) { + Local::writeLangPack(); + _updated.notify(); + } +} + +void Instance::reset( + const QString &id, + const QString &pluralId, + const QString &baseId) { + const auto computedPluralId = !pluralId.isEmpty() + ? pluralId + : !baseId.isEmpty() + ? baseId + : id; + setBaseId(baseId, computedPluralId); + _id = LanguageIdOrDefault(id); + _pluralId = computedPluralId; + _legacyId = kLegacyLanguageNone; _customFilePathAbsolute = QString(); _customFilePathRelative = QString(); _customFileContent = QByteArray(); _version = 0; - - fillDefaults(); -} - -void Instance::fillDefaults() { - Expects(_values.empty()); - _values.reserve(kLangKeysCount); - for (auto i = 0; i != kLangKeysCount; ++i) { - _values.emplace_back(GetOriginalValue(LangKey(i))); + _nonDefaultValues.clear(); + for (auto i = 0, count = int(_values.size()); i != count; ++i) { + _values[i] = GetOriginalValue(LangKey(i)); } - _nonDefaultSet = std::vector(kLangKeysCount, 0); + ranges::fill(_nonDefaultSet, 0); } QString Instance::systemLangCode() const { @@ -230,11 +311,40 @@ QString Instance::systemLangCode() const { return _systemLanguage; } -QString Instance::cloudLangCode() const { - if (isCustom() || id().isEmpty()) { - return DefaultLanguageId(); - } - return id(); +QString Instance::cloudLangCode(Pack pack) const { + return (isCustom() || id().isEmpty()) + ? DefaultLanguageId() + : id(pack); +} + +QString Instance::id() const { + return id(Pack::Current); +} + +QString Instance::baseId() const { + return id(Pack::Base); +} + +QString Instance::id(Pack pack) const { + return (pack != Pack::Base) + ? _id + : _base + ? _base->id(Pack::Current) + : QString(); +} + +bool Instance::isCustom() const { + return (_id == CustomLanguageId()) + || (_id == qstr("#TEST_X")) + || (_id == qstr("#TEST_0")); +} + +int Instance::version(Pack pack) const { + return (pack != Pack::Base) + ? _version + : _base + ? _base->version(Pack::Current) + : 0; } QString Instance::langPackName() const { @@ -250,6 +360,11 @@ QByteArray Instance::serialize() const { for (auto &nonDefault : _nonDefaultValues) { size += Serialize::bytearraySize(nonDefault.first) + Serialize::bytearraySize(nonDefault.second); } + size += Serialize::stringSize(_pluralId); + auto base = _base ? _base->serialize() : QByteArray(); + if (!base.isEmpty()) { + size += Serialize::bytearraySize(base); + } auto result = QByteArray(); result.reserve(size); @@ -262,37 +377,52 @@ QByteArray Instance::serialize() const { for (auto &nonDefault : _nonDefaultValues) { stream << nonDefault.first << nonDefault.second; } + stream << _pluralId; + if (!base.isEmpty()) { + stream << base; + } } return result; } -void Instance::fillFromSerialized(const QByteArray &data) { +void Instance::fillFromSerialized( + const QByteArray &data, + int dataAppVersion) { QDataStream stream(data); stream.setVersion(QDataStream::Qt_5_1); QString id; + QString pluralId; qint32 version = 0; QString customFilePathAbsolute, customFilePathRelative; QByteArray customFileContent; qint32 nonDefaultValuesCount = 0; stream >> id >> version; - stream >> customFilePathAbsolute >> customFilePathRelative >> customFileContent; + 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)); + LOG(("Lang Error: Values count limit exceeded: %1" + ).arg(nonDefaultValuesCount)); return; } if (!customFilePathAbsolute.isEmpty()) { - if (id == qstr("custom")) { - id = '#' + id; - } - auto currentCustomFileContent = Lang::FileParser::ReadFile(customFilePathAbsolute, customFilePathRelative); - if (!currentCustomFileContent.isEmpty() && currentCustomFileContent != customFileContent) { - loadFromCustomContent(customFilePathAbsolute, customFilePathRelative, currentCustomFileContent); + id = CustomLanguageId(); + auto currentCustomFileContent = Lang::FileParser::ReadFile( + customFilePathAbsolute, + customFilePathRelative); + if (!currentCustomFileContent.isEmpty() + && currentCustomFileContent != customFileContent) { + fillFromCustomContent( + customFilePathAbsolute, + customFilePathRelative, + currentCustomFileContent); Local::writeLangPack(); return; } @@ -304,7 +434,8 @@ void Instance::fillFromSerialized(const QByteArray &data) { QByteArray key, value; stream >> key >> value; if (stream.status() != QDataStream::Ok) { - LOG(("Lang Error: Could not read data from serialized langpack.")); + LOG(("Lang Error: " + "Could not read data from serialized langpack.")); return; } @@ -312,7 +443,35 @@ void Instance::fillFromSerialized(const QByteArray &data) { nonDefaultStrings.push_back(value); } + _base = nullptr; + if (!stream.atEnd()) { + stream >> pluralId; + if (!stream.atEnd()) { + QByteArray base; + stream >> base; + if (stream.status() != QDataStream::Ok || base.isEmpty()) { + LOG(("Lang Error: " + "Could not read data from serialized langpack.")); + return; + } + _base = std::make_unique(this, PrivateTag{}); + _base->fillFromSerialized(base, dataAppVersion); + } + if (stream.status() != QDataStream::Ok) { + LOG(("Lang Error: " + "Could not read data from serialized langpack.")); + return; + } + } else { + pluralId = id; + } + _id = id; + _pluralId = (id == CustomLanguageId()) + ? PluralCodeForCustom( + customFilePathAbsolute, + customFilePathRelative) + : pluralId; _version = version; _customFilePathAbsolute = customFilePathAbsolute; _customFilePathRelative = customFilePathRelative; @@ -335,8 +494,20 @@ void Instance::loadFromContent(const QByteArray &content) { } } -void Instance::loadFromCustomContent(const QString &absolutePath, const QString &relativePath, const QByteArray &content) { - _id = qsl("#custom"); +void Instance::fillFromCustomContent( + const QString &absolutePath, + const QString &relativePath, + const QByteArray &content) { + setBaseId(QString(), QString()); + _id = CustomLanguageId(); + _pluralId = PluralCodeForCustom(absolutePath, relativePath); + loadFromCustomContent(absolutePath, relativePath, content); +} + +void Instance::loadFromCustomContent( + const QString &absolutePath, + const QString &relativePath, + const QByteArray &content) { _version = 0; _customFilePathAbsolute = absolutePath; _customFilePathRelative = relativePath; @@ -344,14 +515,20 @@ void Instance::loadFromCustomContent(const QString &absolutePath, const QString loadFromContent(_customFileContent); } -void Instance::fillFromCustomFile(const QString &filePath) { +bool Instance::loadFromCustomFile(const QString &filePath) { auto absolutePath = QFileInfo(filePath).absoluteFilePath(); auto relativePath = QDir().relativeFilePath(filePath); auto content = Lang::FileParser::ReadFile(absolutePath, relativePath); if (!content.isEmpty()) { + reset( + CustomLanguageId(), + PluralCodeForCustom(absolutePath, relativePath), + QString()); loadFromCustomContent(absolutePath, relativePath, content); updatePluralRules(); + return true; } + return false; } void Instance::fillFromLegacy(int legacyId, const QString &legacyPath) { @@ -370,7 +547,7 @@ void Instance::fillFromLegacy(int legacyId, const QString &legacyPath) { auto content = Lang::FileParser::ReadFile(absolutePath, relativePath); if (!content.isEmpty()) { _legacyId = legacyId; - loadFromCustomContent(absolutePath, relativePath, content); + fillFromCustomContent(absolutePath, relativePath, content); } } else if (legacyId > kLegacyDefaultLanguage && legacyId < base::array_size(kLegacyLanguages)) { auto languageId = str_const_toString(kLegacyLanguages[legacyId]); @@ -383,66 +560,104 @@ void Instance::fillFromLegacy(int legacyId, const QString &legacyPath) { loadFromContent(content); } } - _id = ConvertLegacyLanguageId(_id); + _id = LanguageIdOrDefault(ConvertLegacyLanguageId(_id)); + if (!isCustom()) { + _pluralId = _id; + } + _base = nullptr; updatePluralRules(); } +// SetCallback takes two QByteArrays: key, value. +// It is called for all key-value pairs in string. +// ResetCallback takes one QByteArray: key. template -void Instance::HandleString(const MTPLangPackString &mtpString, SetCallback setCallback, ResetCallback resetCallback) { - switch (mtpString.type()) { - case mtpc_langPackString: { - auto &string = mtpString.c_langPackString(); - setCallback(qba(string.vkey), qba(string.vvalue)); - } break; - - case mtpc_langPackStringPluralized: { - auto &string = mtpString.c_langPackStringPluralized(); - auto key = qba(string.vkey); - setCallback(key + "#zero", string.has_zero_value() ? qba(string.vzero_value) : QByteArray()); - setCallback(key + "#one", string.has_one_value() ? qba(string.vone_value) : QByteArray()); - setCallback(key + "#two", string.has_two_value() ? qba(string.vtwo_value) : QByteArray()); - setCallback(key + "#few", string.has_few_value() ? qba(string.vfew_value) : QByteArray()); - setCallback(key + "#many", string.has_many_value() ? qba(string.vmany_value) : QByteArray()); - setCallback(key + "#other", qba(string.vother_value)); - } break; - - case mtpc_langPackStringDeleted: { - auto &string = mtpString.c_langPackStringDeleted(); - auto key = qba(string.vkey); +void HandleString( + const MTPLangPackString &string, + SetCallback setCallback, + ResetCallback resetCallback) { + string.match([&](const MTPDlangPackString &data) { + setCallback(qba(data.vkey), qba(data.vvalue)); + }, [&](const MTPDlangPackStringPluralized &data) { + const auto key = qba(data.vkey); + setCallback( + key + "#zero", + data.has_zero_value() ? qba(data.vzero_value) : QByteArray()); + setCallback( + key + "#one", + data.has_one_value() ? qba(data.vone_value) : QByteArray()); + setCallback( + key + "#two", + data.has_two_value() ? qba(data.vtwo_value) : QByteArray()); + setCallback( + key + "#few", + data.has_few_value() ? qba(data.vfew_value) : QByteArray()); + setCallback( + key + "#many", + data.has_many_value() ? qba(data.vmany_value) : QByteArray()); + setCallback(key + "#other", qba(data.vother_value)); + }, [&](const MTPDlangPackStringDeleted &data) { + auto key = qba(data.vkey); resetCallback(key); - for (auto plural : { "#zero", "#one", "#two", "#few", "#many", "#other" }) { + const auto postfixes = { + "#zero", + "#one", + "#two", + "#few", + "#many", + "#other" + }; + for (const auto plural : postfixes) { resetCallback(key + plural); } - } break; + }); +} - default: Unexpected("LangPack string type in applyUpdate()."); +void Instance::applyDifference( + Pack pack, + const MTPDlangPackDifference &difference) { + switch (pack) { + case Pack::Current: + applyDifferenceToMe(difference); + break; + case Pack::Base: + Assert(_base != nullptr); + _base->applyDifference(Pack::Current, difference); + break; + default: + Unexpected("Pack in Instance::applyDifference."); } } -void Instance::applyDifference(const MTPDlangPackDifference &difference) { - auto updateLanguageId = qs(difference.vlang_code); - auto isValidUpdate = (updateLanguageId == _id) || (_id.isEmpty() && updateLanguageId == DefaultLanguageId()); - Expects(isValidUpdate); +void Instance::applyDifferenceToMe( + const MTPDlangPackDifference &difference) { + Expects(LanguageIdOrDefault(_id) == qs(difference.vlang_code)); Expects(difference.vfrom_version.v <= _version); _version = difference.vversion.v; - for_const (auto &mtpString, difference.vstrings.v) { - HandleString(mtpString, [this](auto &&key, auto &&value) { + for (const auto &string : difference.vstrings.v) { + HandleString(string, [&](auto &&key, auto &&value) { applyValue(key, value); - }, [this](auto &&key) { + }, [&](auto &&key) { resetValue(key); }); } - _updated.notify(); + if (!_derived) { + _updated.notify(); + } else { + _derived->_updated.notify(); + } } std::map Instance::ParseStrings( const MTPVector &strings) { auto result = std::map(); - for (auto &mtpString : strings.v) { - HandleString(mtpString, [&result](auto &&key, auto &&value) { - ParseKeyValue(key, value, result); - }, [&result](auto &&key) { + for (const auto &string : strings.v) { + HandleString(string, [&](auto &&key, auto &&value) { + ParseKeyValue(key, value, [&](LangKey key, QString &&value) { + result[key] = std::move(value); + }); + }, [&](auto &&key) { auto keyIndex = GetKeyIndex(QLatin1String(key)); if (keyIndex != kLangKeysCount) { result.erase(keyIndex); @@ -452,65 +667,54 @@ std::map Instance::ParseStrings( return result; } -template -LangKey Instance::ParseKeyValue( - const QByteArray &key, - const QByteArray &value, - Result &result) { - auto keyIndex = GetKeyIndex(QLatin1String(key)); - if (keyIndex == kLangKeysCount) { - if (!key.startsWith("cloud_")) { - LOG(("Lang Warning: Unknown key '%1'" - ).arg(QString::fromLatin1(key))); - } - return kLangKeysCount; - } - - ValueParser parser(key, keyIndex, value); - if (parser.parse()) { - result[keyIndex] = parser.takeResult(); - return keyIndex; - } - return kLangKeysCount; -} - QString Instance::getNonDefaultValue(const QByteArray &key) const { const auto i = _nonDefaultValues.find(key); return (i != end(_nonDefaultValues)) ? QString::fromUtf8(i->second) + : _base + ? _base->getNonDefaultValue(key) : QString(); } void Instance::applyValue(const QByteArray &key, const QByteArray &value) { _nonDefaultValues[key] = value; - auto index = ParseKeyValue(key, value, _values); - if (index != kLangKeysCount) { - _nonDefaultSet[index] = 1; - } + ParseKeyValue(key, value, [&](LangKey key, QString &&value) { + _nonDefaultSet[key] = 1; + if (!_derived) { + _values[key] = std::move(value); + } else if (!_derived->_nonDefaultSet[key]) { + _derived->_values[key] = std::move(value); + } + }); } void Instance::updatePluralRules() { - auto id = _id; - if (isCustom()) { - auto path = _customFilePathAbsolute.isEmpty() - ? _customFilePathRelative - : _customFilePathAbsolute; - auto name = QFileInfo(path).fileName(); - if (auto match = qthelp::regex_match("_([a-z]{2,3})(_[A-Z]{2,3}|\\-[a-z]{2,3})?\\.", name)) { - id = match->captured(1); - } - } else if (auto match = qthelp::regex_match("^([a-z]{2,3})(_[A-Z]{2,3}|\\-[a-z]{2,3})$", id)) { - id = match->captured(1); + if (_pluralId.isEmpty()) { + _pluralId = isCustom() + ? PluralCodeForCustom( + _customFilePathAbsolute, + _customFilePathRelative) + : LanguageIdOrDefault(_id); } - UpdatePluralRules(id); + UpdatePluralRules(_pluralId); } void Instance::resetValue(const QByteArray &key) { _nonDefaultValues.erase(key); - auto keyIndex = GetKeyIndex(QLatin1String(key)); + const auto keyIndex = GetKeyIndex(QLatin1String(key)); if (keyIndex != kLangKeysCount) { - _values[keyIndex] = GetOriginalValue(keyIndex); + _nonDefaultSet[keyIndex] = 0; + if (!_derived) { + const auto base = _base + ? _base->getNonDefaultValue(key) + : QString(); + _values[keyIndex] = !base.isEmpty() + ? base + : GetOriginalValue(keyIndex); + } else if (!_derived->_nonDefaultSet[keyIndex]) { + _derived->_values[keyIndex] = GetOriginalValue(keyIndex); + } } } diff --git a/Telegram/SourceFiles/lang/lang_instance.h b/Telegram/SourceFiles/lang/lang_instance.h index 3518d94af..cb5eb6641 100644 --- a/Telegram/SourceFiles/lang/lang_instance.h +++ b/Telegram/SourceFiles/lang/lang_instance.h @@ -32,19 +32,32 @@ inline QString ConvertLegacyLanguageId(const QString &languageId) { } QString DefaultLanguageId(); +QString LanguageIdOrDefault(const QString &id); QString CloudLangPackName(); +QString CustomLanguageId(); class Instance; Instance &Current(); rpl::producer Viewer(LangKey key); +enum class Pack { + None, + Current, + Base, +}; + class Instance { + struct PrivateTag; + public: - Instance() { - fillDefaults(); - } - void switchToId(const QString &id); + Instance(); + Instance(not_null derived, const PrivateTag &); + + void switchToId( + const QString &id, + const QString &pluralId = QString(), + const QString &baseId = QString()); void switchToCustomFile(const QString &filePath); Instance(const Instance &other) = delete; @@ -53,26 +66,21 @@ public: Instance &operator=(Instance &&other) = default; QString systemLangCode() const; - QString cloudLangCode() const; QString langPackName() const; - - QString id() const { - return _id; - } - bool isCustom() const { - return (_id == qstr("#custom")) - || (_id == qstr("#TEST_X")) - || (_id == qstr("#TEST_0")); - } - int version() const { - return _version; - } + QString cloudLangCode(Pack pack) const; + QString id() const; + QString baseId() const; + QString id(Pack pack) const; + bool isCustom() const; + int version(Pack pack) const; QByteArray serialize() const; - void fillFromSerialized(const QByteArray &data); + void fillFromSerialized(const QByteArray &data, int dataAppVersion); void fillFromLegacy(int legacyId, const QString &legacyPath); - void applyDifference(const MTPDlangPackDifference &difference); + void applyDifference( + Pack pack, + const MTPDlangPackDifference &difference); static std::map ParseStrings( const MTPVector &strings); base::Observable &updated() { @@ -80,46 +88,38 @@ public: } QString getValue(LangKey key) const { - Expects(key >= 0 && key < kLangKeysCount); - Expects(_values.size() == kLangKeysCount); + Expects(key >= 0 && key < _values.size()); return _values[key]; } QString getNonDefaultValue(const QByteArray &key) const; bool isNonDefaultPlural(LangKey key) const { - Expects(key >= 0 && key < kLangKeysCount); - Expects(_nonDefaultSet.size() == kLangKeysCount); + Expects(key >= 0 && key + 5 < _nonDefaultSet.size()); return _nonDefaultSet[key] || _nonDefaultSet[key + 1] || _nonDefaultSet[key + 2] || _nonDefaultSet[key + 3] || _nonDefaultSet[key + 4] - || _nonDefaultSet[key + 5]; + || _nonDefaultSet[key + 5] + || (_base && _base->isNonDefaultPlural(key)); } private: - // SetCallback takes two QByteArrays: key, value. - // It is called for all key-value pairs in string. - // ResetCallback takes one QByteArray: key. - template - static void HandleString( - const MTPLangPackString &mtpString, - SetCallback setCallback, - ResetCallback resetCallback); - - // Writes each key-value pair in the result container. - template - static LangKey ParseKeyValue( - const QByteArray &key, - const QByteArray &value, - Result &result); + void setBaseId(const QString &baseId, const QString &pluralId); + void applyDifferenceToMe(const MTPDlangPackDifference &difference); void applyValue(const QByteArray &key, const QByteArray &value); void resetValue(const QByteArray &key); - void reset(); - void fillDefaults(); - void fillFromCustomFile(const QString &filePath); + void reset( + const QString &id, + const QString &pluralId, + const QString &baseId); + void fillFromCustomContent( + const QString &absolutePath, + const QString &relativePath, + const QByteArray &content); + bool loadFromCustomFile(const QString &filePath); void loadFromContent(const QByteArray &content); void loadFromCustomContent( const QString &absolutePath, @@ -127,7 +127,9 @@ private: const QByteArray &content); void updatePluralRules(); - QString _id; + Instance *_derived = nullptr; + + QString _id, _pluralId; int _legacyId = kLegacyLanguageNone; QString _customFilePathAbsolute; QString _customFilePathRelative; @@ -141,6 +143,8 @@ private: std::vector _nonDefaultSet; std::map _nonDefaultValues; + std::unique_ptr _base; + }; } // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_tag.cpp b/Telegram/SourceFiles/lang/lang_tag.cpp index 53160f4a5..46aba1323 100644 --- a/Telegram/SourceFiles/lang/lang_tag.cpp +++ b/Telegram/SourceFiles/lang/lang_tag.cpp @@ -499,12 +499,16 @@ struct PluralsKey { uint64 data = 0; }; +char ConvertKeyChar(char ch) { + return (ch == '_') ? '-' : QChar::toLower(ch); +} + PluralsKey::PluralsKey(uint64 key) : data(key) { } PluralsKey::PluralsKey(const char *value) { for (auto ch = *value; ch; ch = *++value) { - data = (data << 8) | uint64(ch); + data = (data << 8) | uint64(ConvertKeyChar(ch)); } } @@ -942,11 +946,21 @@ PluralResult Plural(ushort keyBase, float64 value) { void UpdatePluralRules(const QString &languageId) { static auto kMap = GeneratePluralRulesMap(); + auto parent = uint64(0); auto key = uint64(0); for (const auto ch : languageId) { - key = (key << 8) | ch.unicode(); + const auto converted = ConvertKeyChar(ch.unicode()); + if (converted == '-' && !parent) { + parent = key; + } + key = (key << 8) | uint64(converted); } - const auto i = kMap.find(key); + const auto i = [&] { + const auto result = kMap.find(key); + return (result != end(kMap) || !parent) + ? result + : kMap.find(parent); + }(); ChoosePlural = (i == end(kMap)) ? ChoosePlural1 : i->second; } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index bd082b287..9d96d44fd 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -4898,12 +4898,16 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { ////// Cloud langpacks case mtpc_updateLangPack: { - auto &langpack = update.c_updateLangPack(); - Lang::CurrentCloudManager().applyLangPackDifference(langpack.vdifference); + const auto &data = update.c_updateLangPack(); + Lang::CurrentCloudManager().applyLangPackDifference(data.vdifference); } break; case mtpc_updateLangPackTooLong: { - Lang::CurrentCloudManager().requestLangPackDifference(); + const auto &data = update.c_updateLangPackTooLong(); + const auto code = qs(data.vlang_code); + if (!code.isEmpty()) { + Lang::CurrentCloudManager().requestLangPackDifference(code); + } } break; } diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index a954e8f34..178b38dd9 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -842,6 +842,9 @@ bool Messenger::openLocalUrl(const QString &url, QVariant context) { main->stickersBox(MTP_inputStickerSetShortName(MTP_string(stickerSetMatch->captured(1)))); return true; } + } else if (auto languageMatch = regex_match(qsl("^setlanguage/?\\?lang=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), command, matchOptions)) { + const auto langId = languageMatch->captured(1); + Lang::CurrentCloudManager().switchWithWarning(langId); } else if (auto shareUrlMatch = regex_match(qsl("^msg_url/?\\?(.+)(#|$)"), command, matchOptions)) { if (auto main = App::main()) { auto params = url_parse_params(shareUrlMatch->captured(1), UrlParamNameTransform::ToLower); diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.cpp b/Telegram/SourceFiles/mtproto/mtp_instance.cpp index a900d37ca..4efc7f988 100644 --- a/Telegram/SourceFiles/mtproto/mtp_instance.cpp +++ b/Telegram/SourceFiles/mtproto/mtp_instance.cpp @@ -1537,7 +1537,7 @@ QString Instance::systemLangCode() const { } QString Instance::cloudLangCode() const { - return Lang::Current().cloudLangCode(); + return Lang::Current().cloudLangCode(Lang::Pack::Current); } QString Instance::langPackName() const { diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index e112f480a..4fe1bd8ef 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -4099,7 +4099,7 @@ void readLangPack() { auto data = QByteArray(); langpack.stream >> data; if (langpack.stream.status() == QDataStream::Ok) { - Lang::Current().fillFromSerialized(data); + Lang::Current().fillFromSerialized(data, langpack.version); } }