/* 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 "boxes/translate_box.h" #include "core/application.h" #include "core/core_settings.h" #include "core/ui_integration.h" #include "data/data_peer.h" #include "lang/lang_instance.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "mtproto/sender.h" #include "settings/settings_common.h" #ifndef TDESKTOP_DISABLE_SPELLCHECK #include "spellcheck/platform/platform_language.h" #endif #include "ui/effects/loading_element.h" #include "ui/layers/generic_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" #include "styles/style_layers.h" #include "styles/style_settings.h" // settingsSubsectionTitlePadding. #include namespace Ui { namespace { [[nodiscard]] std::vector Languages() { return std::vector{ QLocale::English, QLocale::Albanian, QLocale::Armenian, QLocale::Bulgarian, QLocale::Catalan, QLocale::Chinese, QLocale::Croatian, QLocale::Czech, QLocale::Danish, QLocale::Dutch, QLocale::Esperanto, QLocale::Estonian, QLocale::French, QLocale::German, QLocale::Greek, QLocale::Hebrew, QLocale::Hindi, QLocale::Hungarian, QLocale::Indonesian, QLocale::Italian, QLocale::Japanese, QLocale::Korean, QLocale::Latvian, QLocale::Lithuanian, QLocale::Persian, QLocale::Polish, QLocale::Portuguese, QLocale::Romanian, QLocale::Russian, QLocale::Serbian, QLocale::Slovak, QLocale::Slovenian, QLocale::Spanish, QLocale::Swedish, QLocale::Tajik, QLocale::Tamil, QLocale::Turkish, QLocale::Ukrainian, QLocale::Vietnamese, QLocale::Welsh, }; } class ShowButton : public RpWidget { public: ShowButton(not_null parent); [[nodiscard]] rpl::producer clicks() const; protected: void paintEvent(QPaintEvent *e) override; private: LinkButton _button; }; ShowButton::ShowButton(not_null parent) : RpWidget(parent) , _button(this, tr::lng_usernames_activate_confirm(tr::now)) { _button.sizeValue( ) | rpl::start_with_next([=](const QSize &s) { resize( s.width() + st::emojiSuggestionsFadeRight.width(), s.height()); _button.moveToRight(0, 0); }, lifetime()); _button.show(); } void ShowButton::paintEvent(QPaintEvent *e) { auto p = QPainter(this); const auto clip = e->rect(); const auto &icon = st::emojiSuggestionsFadeRight; const auto fade = QRect(0, 0, icon.width(), height()); if (fade.intersects(clip)) { icon.fill(p, fade); } const auto fill = clip.intersected( { icon.width(), 0, width() - icon.width(), height() }); if (!fill.isEmpty()) { p.fillRect(fill, st::boxBg); } } rpl::producer ShowButton::clicks() const { return _button.clicks(); } } // namespace QString LanguageName(const QLocale &locale) { if (locale.language() == QLocale::English && (locale.country() == QLocale::UnitedStates || locale.country() == QLocale::AnyCountry)) { return u"English"_q; } else { const auto name = locale.nativeLanguageName(); return name.left(1).toUpper() + name.mid(1); } } void TranslateBox( not_null box, not_null peer, MsgId msgId, TextWithEntities text, bool hasCopyRestriction) { box->setWidth(st::boxWideWidth); box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); const auto container = box->verticalLayout(); const auto settingsLang = Core::App().settings().skipTranslationForLanguage(); const auto defaultId = (settingsLang == QLocale::English) ? Lang::LanguageIdOrDefault(Lang::Id()) : (settingsLang == QLocale::C) ? u"en"_q : QLocale(settingsLang).name().mid(0, 2); const auto api = box->lifetime().make_state( &peer->session().mtp()); struct State { rpl::event_stream locale; }; const auto state = box->lifetime().make_state(); text.entities = ranges::views::all( text.entities ) | ranges::views::filter([](const EntityInText &e) { return e.type() != EntityType::Spoiler; }) | ranges::to(); if (!IsServerMsgId(msgId)) { msgId = 0; } using Flag = MTPmessages_translateText::Flag; const auto flags = msgId ? (Flag::f_peer | Flag::f_msg_id) : !text.text.isEmpty() ? Flag::f_text : Flag(0); const auto &stLabel = st::aboutLabel; const auto lineHeight = stLabel.style.lineHeight; Settings::AddSkip(container); // Settings::AddSubsectionTitle( // container, // tr::lng_translate_box_original()); const auto original = box->addRow(object_ptr>( box, object_ptr(box, stLabel))); { if (hasCopyRestriction) { original->entity()->setContextMenuHook([](auto&&) { }); } original->entity()->setMarkedText( text, Core::MarkedTextContext{ .session = &peer->session(), .customEmojiRepaint = [=] { original->entity()->update(); }, }); original->setMinimalHeight(lineHeight); original->hide(anim::type::instant); const auto show = Ui::CreateChild>( container.get(), object_ptr(container)); show->hide(anim::type::instant); rpl::combine( container->widthValue(), original->geometryValue() ) | rpl::start_with_next([=](int width, const QRect &rect) { show->moveToLeft( width - show->width() - st::boxRowPadding.right(), rect.y() + std::abs(lineHeight - show->height()) / 2); }, show->lifetime()); original->entity()->heightValue( ) | rpl::filter([](int height) { return height > 0; }) | rpl::take(1) | rpl::start_with_next([=](int height) { if (height > lineHeight) { show->show(anim::type::instant); } }, show->lifetime()); show->toggleOn(show->entity()->clicks() | rpl::map_to(false)); original->toggleOn(show->entity()->clicks() | rpl::map_to(true)); } Settings::AddSkip(container); Settings::AddSkip(container); Settings::AddDivider(container); Settings::AddSkip(container); { const auto padding = st::settingsSubsectionTitlePadding; const auto subtitle = Settings::AddSubsectionTitle( container, state->locale.events() | rpl::map(LanguageName)); // Workaround. state->locale.events( ) | rpl::start_with_next([=] { subtitle->resizeToWidth(container->width() - padding.left() - padding.right()); }, subtitle->lifetime()); } const auto translated = box->addRow(object_ptr>( box, object_ptr(box, stLabel))); translated->entity()->setSelectable(!hasCopyRestriction); translated->hide(anim::type::instant); constexpr auto kMaxLines = 3; container->resizeToWidth(box->width()); const auto loading = box->addRow(object_ptr>( box, CreateLoadingTextWidget( box, st::aboutLabel, std::min(original->entity()->height() / lineHeight, kMaxLines), state->locale.events() | rpl::map([=](const QLocale &locale) { return locale.textDirection() == Qt::RightToLeft; })))); loading->show(anim::type::instant); const auto showText = [=](const QString &text) { translated->entity()->setText(text); translated->show(anim::type::instant); loading->hide(anim::type::instant); }; const auto send = [=](const QString &toLang) { api->request(MTPmessages_TranslateText( MTP_flags(flags), msgId ? peer->input : MTP_inputPeerEmpty(), MTP_int(msgId), MTP_string(text.text), MTPstring(), MTP_string(toLang) )).done([=](const MTPmessages_TranslatedText &result) { const auto text = result.match([]( const MTPDmessages_translateNoResult &data) { return tr::lng_translate_box_error(tr::now); }, [](const MTPDmessages_translateResultText &data) { return qs(data.vtext()); }); showText(text); }).fail([=](const MTP::Error &error) { showText(tr::lng_translate_box_error(tr::now)); }).send(); }; send(defaultId); state->locale.fire(QLocale(defaultId)); box->addLeftButton(tr::lng_settings_language(), [=] { if (loading->toggled()) { return; } Ui::BoxShow(box).showBox(Box(ChooseLanguageBox, [=](QLocale locale) { state->locale.fire_copy(locale); loading->show(anim::type::instant); translated->hide(anim::type::instant); send(locale.name().mid(0, 2)); })); }); } void ChooseLanguageBox( not_null box, Fn callback) { box->setTitle(tr::lng_languages()); for (const auto &lang : Languages()) { const auto locale = QLocale(lang); const auto button = Settings::AddButton( box->verticalLayout(), rpl::single(LanguageName(locale)), st::defaultSettingsButton); button->setClickedCallback([=] { callback(locale); box->closeBox(); }); } box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } bool SkipTranslate(TextWithEntities textWithEntities) { const auto &text = textWithEntities.text; if (text.isEmpty()) { return true; } if (!Core::App().settings().translateButtonEnabled()) { return true; } constexpr auto kFirstChunk = size_t(100); auto hasLetters = (text.size() >= kFirstChunk); for (auto i = 0; i < kFirstChunk; i++) { if (i >= text.size()) { break; } if (text.at(i).isLetter()) { hasLetters = true; break; } } if (!hasLetters) { return true; } #ifndef TDESKTOP_DISABLE_SPELLCHECK const auto result = Platform::Language::Recognize(text); if (result.unknown) { return false; } const auto settingsLang = Core::App().settings().skipTranslationForLanguage(); const auto skip = (settingsLang == QLocale::English) ? QLocale(Lang::LanguageIdOrDefault(Lang::Id())).language() : (settingsLang == QLocale::C) ? QLocale::English : settingsLang; return (result.locale.language() == skip); #else return false; #endif } } // namespace Ui