/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "lang/lang_cloud_manager.h" #include "lang/lang_instance.h" #include "mtproto/mtp_instance.h" #include "storage/localstorage.h" #include "core/application.h" #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, Fn apply); protected: void prepare() override; private: QString _name; int _percent = 0; bool _official = false; QString _editLink; Fn _apply; }; class NotReadyBox : public BoxContent { public: NotReadyBox( QWidget*, const MTPDlangPackLanguage &data); protected: void prepare() override; private: QString _name; QString _editLink; }; ConfirmSwitchBox::ConfirmSwitchBox( QWidget*, const MTPDlangPackLanguage &data, Fn apply) : _name(qs(data.vnative_name)) , _percent(data.vtranslated_count.v * 100 / data.vstrings_count.v) , _official(data.is_official()) , _editLink(qs(data.vtranslations_url)) , _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 = (_official ? lng_language_switch_about_official__generic : lng_language_switch_about_unofficial__generic)( lt_lang_name, name, lt_percent, percent, lt_link, link); auto content = Ui::CreateChild>( this, object_ptr( this, rpl::single(text), st::boxLabel), 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::boxWideWidth); content->heightValue( ) | rpl::start_with_next([=](int height) { setDimensions(st::boxWideWidth, height); }, lifetime()); } NotReadyBox::NotReadyBox( QWidget*, const MTPDlangPackLanguage &data) : _name(qs(data.vnative_name)) , _editLink(qs(data.vtranslations_url)) { } void NotReadyBox::prepare() { setTitle(langFactory(lng_language_not_ready_title)); auto link = TextWithEntities{ lang(lng_language_not_ready_link) }; link.entities.push_back(EntityInText( EntityInTextCustomUrl, 0, link.text.size(), QString("internal:go_to_translations"))); auto name = TextWithEntities{ _name }; const auto text = lng_language_not_ready_about__generic( lt_lang_name, name, lt_link, link); auto content = Ui::CreateChild>( this, object_ptr( this, rpl::single(text), st::boxLabel), QMargins{ st::boxPadding.left(), 0, st::boxPadding.right(), 0 }); content->entity()->setClickHandlerFilter([=](auto&&...) { UrlClickHandler::Open(_editLink); return false; }); addButton(langFactory(lng_box_ok), [=] { closeBox(); }); content->resizeToWidth(st::boxWidth); content->heightValue( ) | rpl::start_with_next([=](int height) { setDimensions(st::boxWidth, height); }, lifetime()); } } // namespace Language ParseLanguage(const MTPLangPackLanguage &data) { return data.match([](const MTPDlangPackLanguage &data) { return Language{ qs(data.vlang_code), qs(data.vplural_code), (data.has_base_lang_code() ? qs(data.vbase_lang_code) : QString()), qs(data.vname), qs(data.vnative_name) }; }); } CloudManager::CloudManager( Instance &langpack, not_null mtproto) : MTP::Sender() , _langpack(langpack) { } 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) { request(base::take(packRequestId(pack))).cancel(); if (_langpack.isCustom()) { return; } const auto version = _langpack.version(pack); const auto code = _langpack.cloudLangCode(pack); if (code.isEmpty()) { return; } if (version > 0) { packRequestId(pack) = request(MTPlangpack_GetDifference( MTP_string(CloudLangPackName()), MTP_string(code), MTP_int(version) )).done([=](const MTPLangPackDifference &result) { packRequestId(pack) = 0; applyLangPackDifference(result); }).fail([=](const RPCError &error) { packRequestId(pack) = 0; }).send(); } else { packRequestId(pack) = request(MTPlangpack_GetLangPack( MTP_string(CloudLangPackName()), MTP_string(code) )).done([=](const MTPLangPackDifference &result) { packRequestId(pack) = 0; applyLangPackDifference(result); }).fail([=](const RPCError &error) { packRequestId(pack) = 0; }).send(); } } void CloudManager::setSuggestedLanguage(const QString &langCode) { if (Lang::LanguageIdOrDefault(langCode) != Lang::DefaultLanguageId()) { _suggestedLanguage = langCode; } else { _suggestedLanguage = QString(); } if (!_languageWasSuggested) { _languageWasSuggested = true; _firstLanguageSuggestion.notify(); if (AuthSession::Exists() && _langpack.id().isEmpty() && !_suggestedLanguage.isEmpty()) { auto isLegacy = [](const QString &languageId) { for (auto &legacyString : kLegacyLanguages) { auto legacyId = str_const_toString(legacyString); if (ConvertLegacyLanguageId(legacyId) == languageId) { return true; } } return false; }; // The old available languages (de/it/nl/ko/es/pt_BR) won't be // suggested anyway, because everyone saw the suggestion in intro. if (!isLegacy(_suggestedLanguage)) { _offerSwitchToId = _suggestedLanguage; offerSwitchLangPack(); } } } } void CloudManager::setCurrentVersions(int version, int baseVersion) { const auto check = [&](Pack pack, int version) { if (version > _langpack.version(pack) && !packRequestId(pack)) { requestLangPackDifference(pack); } }; check(Pack::Current, version); check(Pack::Base, baseVersion); } void CloudManager::applyLangPackDifference( const MTPLangPackDifference &difference) { Expects(difference.type() == mtpc_langPackDifference); if (_langpack.isCustom()) { return; } 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) { restartAfterSwitch(); } } else { LOG(("Lang Warning: " "Ignoring update for '%1' because our language is '%2'" ).arg(langpackId ).arg(_langpack.id())); } } void CloudManager::requestLanguageList() { _languagesRequestId = request(MTPlangpack_GetLanguages( MTP_string(CloudLangPackName()) )).done([=](const MTPVector &result) { auto languages = Languages(); for (const auto &language : result.v) { languages.push_back(ParseLanguage(language)); } if (_languages != languages) { _languages = languages; _languagesChanged.notify(); } _languagesRequestId = 0; }).fail([=](const RPCError &error) { _languagesRequestId = 0; }).send(); } void CloudManager::offerSwitchLangPack() { Expects(!_offerSwitchToId.isEmpty()); Expects(_offerSwitchToId != DefaultLanguageId()); if (!showOfferSwitchBox()) { subscribe(languageListChanged(), [this] { showOfferSwitchBox(); }); requestLanguageList(); } } Language CloudManager::findOfferedLanguage() const { for (const auto &language : _languages) { if (language.id == _offerSwitchToId) { return language; } } return {}; } bool CloudManager::showOfferSwitchBox() { const auto language = findOfferedLanguage(); if (language.id.isEmpty()) { return false; } const auto confirm = [=] { Ui::hideLayer(); if (_offerSwitchToId.isEmpty()) { return; } performSwitchAndRestart(language); }; const auto cancel = [=] { Ui::hideLayer(); changeIdAndReInitConnection(DefaultLanguage()); Local::writeLangPack(); }; Ui::show( Box( "Do you want to switch your language to " + language.nativeName + "? You can always change your language in Settings.", "Change", lang(lng_cancel), confirm, cancel), LayerOption::KeepOther); return true; } 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(pack, data); Local::writeLangPack(); } else if (_restartAfterSwitch) { Local::writeLangPack(); } else { LOG(("Lang Info: Up to date.")); } } bool CloudManager::canApplyWithoutRestart(const QString &id) const { if (id == qstr("#TEST_X") || id == qstr("#TEST_0")) { return true; } // We don't support instant language switch if the auth session exists :( return !AuthSession::Exists(); } void CloudManager::resetToDefault() { performSwitch(DefaultLanguage()); } void CloudManager::switchToLanguage(const QString &id) { requestLanguageAndSwitch(id, false); } void CloudManager::switchWithWarning(const QString &id) { requestLanguageAndSwitch(id, true); } void CloudManager::requestLanguageAndSwitch( const QString &id, bool warning) { Expects(!id.isEmpty()); if (LanguageIdOrDefault(_langpack.id()) == id) { Ui::show(Box(lang(lng_language_already))); return; } else if (id == qstr("#custom")) { performSwitchToCustom(); return; } request(_switchingToLanguageRequest).cancel(); _switchingToLanguageRequest = request(MTPlangpack_GetLanguage( MTP_string(Lang::CloudLangPackName()), MTP_string(id) )).done([=](const MTPLangPackLanguage &result) { _switchingToLanguageRequest = 0; const auto language = Lang::ParseLanguage(result); const auto finalize = [=] { performSwitchAndRestart(language); }; if (!warning) { finalize(); return; } result.match([=](const MTPDlangPackLanguage &data) { if (data.vstrings_count.v > 0) { Ui::show(Box(data, finalize)); } else { Ui::show(Box(data)); } }); }).fail([=](const RPCError &error) { _switchingToLanguageRequest = 0; if (error.type() == "LANG_CODE_NOT_SUPPORTED") { Ui::show(Box(lang(lng_language_not_found))); } }).send(); } void CloudManager::switchToLanguage(const Language &data) { if (_langpack.id() == data.id && data.id != qstr("#custom")) { return; } request(_switchingToLanguageRequest).cancel(); if (data.id == qstr("#custom")) { performSwitchToCustom(); } else if (canApplyWithoutRestart(data.id)) { performSwitch(data); } 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(data.id), 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); return (it == values.cend()) ? GetOriginalValue(key) : it->second; }; const auto text = lang(lng_sure_save_language) + "\n\n" + getValue(lng_sure_save_language); Ui::show( Box( text, lang(lng_box_ok), lang(lng_cancel), [=] { performSwitchAndRestart(data); }), LayerOption::KeepOther); }).fail([=](const RPCError &error) { _switchingToLanguageRequest = 0; }).send(); } } void CloudManager::performSwitchToCustom() { auto filter = qsl("Language files (*.strings)"); auto title = qsl("Choose language .strings file"); FileDialog::GetOpenPath(Core::App().getFileDialogParent(), title, filter, [weak = base::make_weak(this)](const FileDialog::OpenResult &result) { if (!weak || result.paths.isEmpty()) { return; } auto filePath = result.paths.front(); Lang::FileParser loader(filePath, { lng_sure_save_language }); if (loader.errors().isEmpty()) { weak->request(weak->_switchingToLanguageRequest).cancel(); if (weak->canApplyWithoutRestart(qsl("#custom"))) { weak->_langpack.switchToCustomFile(filePath); } else { const auto values = loader.found(); const auto getValue = [&](LangKey key) { const auto it = values.find(key); return (it == values.cend()) ? GetOriginalValue(key) : it.value(); }; const auto text = lang(lng_sure_save_language) + "\n\n" + getValue(lng_sure_save_language); const auto change = [=] { weak->_langpack.switchToCustomFile(filePath); App::restart(); }; Ui::show( Box( text, lang(lng_box_ok), lang(lng_cancel), change), LayerOption::KeepOther); } } else { Ui::show( Box("Custom lang failed :(\n\nError: " + loader.errors()), LayerOption::KeepOther); } }); } void CloudManager::switchToTestLanguage() { const auto testLanguageId = (_langpack.id() == qstr("#TEST_X")) ? qsl("#TEST_0") : qsl("#TEST_X"); performSwitch({ testLanguageId }); } void CloudManager::performSwitch(const Language &data) { _restartAfterSwitch = false; switchLangPackId(data); requestLangPackDifference(Pack::Current); requestLangPackDifference(Pack::Base); } void CloudManager::performSwitchAndRestart(const Language &data) { Local::pushRecentLanguage(data); performSwitch(data); restartAfterSwitch(); } void CloudManager::restartAfterSwitch() { if (_langPackRequestId || _langPackBaseRequestId) { _restartAfterSwitch = true; } else { App::restart(); } } void CloudManager::switchLangPackId(const Language &data) { const auto currentId = _langpack.id(); const auto currentBaseId = _langpack.baseId(); const auto notChanged = (currentId == data.id && currentBaseId == data.baseId) || (currentId.isEmpty() && currentBaseId.isEmpty() && data.id == DefaultLanguageId()); if (!notChanged) { changeIdAndReInitConnection(data); } } void CloudManager::changeIdAndReInitConnection(const Language &data) { _langpack.switchToId(data); auto mtproto = requestMTP(); mtproto->reInitConnection(mtproto->mainDcId()); } CloudManager &CurrentCloudManager() { auto result = Core::App().langCloudManager(); Assert(result != nullptr); return *result; } } // namespace Lang