File indexing completed on 2024-04-21 04:57:56

0001 /* This file is part of the KDE Project
0002     Original code: plugin code, connecting to Babelfish and support for selected text
0003     SPDX-FileCopyrightText: 2001 Kurt Granroth <granroth@kde.org>
0004 
0005     Submenus, KConfig file and support for other translation engines
0006     SPDX-FileCopyrightText: 2003 Rand 2342 <rand2342@yahoo.com>
0007 
0008     Add webkitkde support
0009     SPDX-FileCopyrightText: 2008 Montel Laurent <montel@kde.org>
0010 
0011     Ported to KParts::TextExtension
0012     SPDX-FileCopyrightText: 2010 David Faure <faure@kde.org>
0013 
0014     SPDX-License-Identifier: LGPL-2.0-only
0015 */
0016 #include "plugin_babelfish.h"
0017 
0018 #include <kparts/part.h>
0019 #include "kf5compat.h" //For NavigationExtension
0020 
0021 #include <kwidgetsaddons_version.h>
0022 #include <kactioncollection.h>
0023 #include <QMenu>
0024 #include <kmessagebox.h>
0025 #include <KLocalizedString>
0026 #include <KLazyLocalizedString>
0027 #include <kconfig.h>
0028 #include <kpluginfactory.h>
0029 #include <kaboutdata.h>
0030 #include <KIO/Job>
0031 
0032 #include <KConfigGroup>
0033 
0034 #include <QDebug>
0035 
0036 #include "textextension.h"
0037 
0038 static const KAboutData aboutdata(QStringLiteral("babelfish"), i18n("Translate Web Page"), QStringLiteral("1.0"));
0039 K_PLUGIN_CLASS_WITH_JSON(PluginBabelFish, "plugin_translator.json")
0040 
0041 PluginBabelFish::PluginBabelFish(QObject *parent,
0042                                  const QVariantList &)
0043     : KonqParts::Plugin(parent),
0044       m_actionGroup(this)
0045 {
0046     m_menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("babelfish")), i18n("Translate Web &Page"),
0047                              actionCollection());
0048     actionCollection()->addAction(QStringLiteral("translatewebpage"), m_menu);
0049     m_menu->setPopupMode(QToolButton::InstantPopup);
0050     connect(m_menu->menu(), &QMenu::aboutToShow, this, &PluginBabelFish::slotAboutToShow);
0051 
0052     KParts::ReadOnlyPart *part = qobject_cast<KParts::ReadOnlyPart *>(parent);
0053     if (part) {
0054         connect(part, &KParts::ReadOnlyPart::started, this, &PluginBabelFish::slotEnableMenu);
0055 #if QT_VERSION_MAJOR < 6
0056         connect(part, QOverload<>::of(&KParts::ReadOnlyPart::completed), this, &PluginBabelFish::slotEnableMenu);
0057 #else
0058         connect(part, &KParts::ReadOnlyPart::completed, this, &PluginBabelFish::slotEnableMenu);
0059 #endif
0060         connect(part, &KParts::ReadOnlyPart::completedWithPendingAction, this, &PluginBabelFish::slotEnableMenu);
0061     }
0062 }
0063 
0064 void PluginBabelFish::addTopLevelAction(const QString &name, const QString &text)
0065 {
0066     QAction *a = actionCollection()->addAction(name);
0067     a->setText(text);
0068     m_menu->addAction(a);
0069     m_actionGroup.addAction(a);
0070 }
0071 
0072 void PluginBabelFish::slotAboutToShow()
0073 {
0074     if (!m_menu->menu()->actions().isEmpty()) { // already populated
0075         return;
0076     }
0077 
0078     connect(&m_actionGroup, SIGNAL(triggered(QAction*)), this, SLOT(translateURL(QAction*)));
0079 
0080     // Create a menu for each "source->dest" translation possibility where
0081     // there's more than one dest for a given source.
0082 
0083     KActionMenu *menu_en = new KActionMenu(QIcon::fromTheme(QStringLiteral("babelfish")), i18n("&English To"), actionCollection());
0084     actionCollection()->addAction(QStringLiteral("translatewebpage_en"), menu_en);
0085 
0086     KActionMenu *menu_fr = new KActionMenu(QIcon::fromTheme(QStringLiteral("babelfish")), i18n("&French To"), actionCollection());
0087     actionCollection()->addAction(QStringLiteral("translatewebpage_fr"), menu_fr);
0088 
0089     KActionMenu *menu_de = new KActionMenu(QIcon::fromTheme(QStringLiteral("babelfish")), i18n("&German To"), actionCollection());
0090     actionCollection()->addAction(QStringLiteral("translatewebpage_de"), menu_de);
0091 
0092     KActionMenu *menu_el = new KActionMenu(QIcon::fromTheme(QStringLiteral("babelfish")), i18n("&Greek To"), actionCollection());
0093     actionCollection()->addAction(QStringLiteral("translatewebpage_el"), menu_el);
0094 
0095     KActionMenu *menu_es = new KActionMenu(QIcon::fromTheme(QStringLiteral("babelfish")), i18n("&Spanish To"), actionCollection());
0096     actionCollection()->addAction(QStringLiteral("translatewebpage_es"), menu_es);
0097 
0098     KActionMenu *menu_pt = new KActionMenu(QIcon::fromTheme(QStringLiteral("babelfish")), i18n("&Portuguese To"), actionCollection());
0099     actionCollection()->addAction(QStringLiteral("translatewebpage_pt"), menu_pt);
0100 
0101     KActionMenu *menu_it = new KActionMenu(QIcon::fromTheme(QStringLiteral("babelfish")), i18n("&Italian To"), actionCollection());
0102     actionCollection()->addAction(QStringLiteral("translatewebpage_it"), menu_it);
0103 
0104     KActionMenu *menu_nl = new KActionMenu(QIcon::fromTheme(QStringLiteral("babelfish")), i18n("&Dutch To"), actionCollection());
0105     actionCollection()->addAction(QStringLiteral("translatewebpage_nl"), menu_nl);
0106 
0107     KActionMenu *menu_ru = new KActionMenu(QIcon::fromTheme(QStringLiteral("babelfish")), i18n("&Russian To"), actionCollection());
0108     actionCollection()->addAction(QStringLiteral("translatewebpage_ru"), menu_ru);
0109 
0110     // List of translation possibilities for filling the above menus
0111     // Mostly from babelfish.
0112     // TODO: add all translation paths from www.reverso.net
0113     // and all translation paths from www.freetranslation.com
0114     // and all those from translate.google.com!! Full matrix supported... we need a different system...
0115     // [possibly in a different list, so that we can remove it more easily if
0116     //  it goes offline?]
0117     // TODO: Maybe the entire list of possibilities could come from translaterc?
0118     // (It would need source, dest, engine, language-code-on-that-engine)
0119     // Here we would just have the i18n texts for each lang code,
0120     // and we would make up the menus on the fly.
0121 
0122     struct {
0123         KLazyLocalizedString name;
0124         const char *language;
0125     } static constexpr const translations[] = {
0126         { kli18n("&Chinese (Simplified)"), "en_zh" },
0127         { kli18n("Chinese (&Traditional)"), "en_zt" },
0128         { kli18n("&Dutch"), "en_nl" },
0129         { kli18n("&French"), "en_fr" },
0130         { kli18n("&German"), "en_de" },
0131         { kli18n("&Greek"), "en_el" },
0132         { kli18n("&Italian"), "en_it" },
0133         { kli18n("&Japanese"), "en_ja" },
0134         { kli18n("&Korean"), "en_ko" },
0135         { kli18n("&Norwegian"), "en_no" },
0136         { kli18n("&Portuguese"), "en_pt" },
0137         { kli18n("&Russian"), "en_ru" },
0138         { kli18n("&Spanish"), "en_es" },
0139         { kli18n("T&hai"), "en_th" }, // only on parsit
0140         { kli18n("&Arabic"), "fr_ar" }, // google
0141         { kli18n("&Dutch"), "fr_nl" },
0142         { kli18n("&English"), "fr_en" },
0143         { kli18n("&German"), "fr_de" },
0144         { kli18n("&Greek"), "fr_el" },
0145         { kli18n("&Italian"), "fr_it" },
0146         { kli18n("&Portuguese"), "fr_pt" },
0147         { kli18n("&Spanish"), "fr_es" },
0148         { kli18n("&Russian"), "fr_ru" }, // only on voila
0149         { kli18n("&English"), "de_en" },
0150         { kli18n("&French"), "de_fr" },
0151         { kli18n("&English"), "el_en" },
0152         { kli18n("&French"), "el_fr" },
0153         { kli18n("&English"), "es_en" },
0154         { kli18n("&French"), "es_fr" },
0155         { kli18n("&English"), "pt_en" },
0156         { kli18n("&French"), "pt_fr" },
0157         { kli18n("&English"), "it_en" },
0158         { kli18n("&French"), "it_fr" },
0159         { kli18n("&English"), "nl_en" },
0160         { kli18n("&French"), "nl_fr" },
0161         { kli18n("&English"), "ru_en" },
0162         { kli18n("&French"), "ru_fr" }, // only on voila
0163     };
0164 
0165     for (const auto &entry : translations) {
0166         const QString translation = QString::fromLatin1(entry.language);
0167         const int underScorePos = translation.indexOf(QLatin1Char('_'));
0168         const QString srcLang = translation.left(underScorePos);
0169         QAction *srcAction = actionCollection()->action(QLatin1String("translatewebpage_") + srcLang);
0170         if (KActionMenu *actionMenu = qobject_cast<KActionMenu *>(srcAction)) {
0171             QAction *a = actionCollection()->addAction(translation);
0172             m_actionGroup.addAction(a);
0173             a->setText(entry.name.toString());
0174             actionMenu->addAction(a);
0175         } else {
0176             qWarning() << "No menu found for" << srcLang;
0177         }
0178     }
0179 
0180     // Fill the toplevel menu, both with single source->dest possibilities
0181     // and with submenus.
0182 
0183     addTopLevelAction(QStringLiteral("zh_en"), i18n("&Chinese (Simplified) to English"));
0184     addTopLevelAction(QStringLiteral("zt_en"), i18n("Chinese (&Traditional) to English"));
0185 
0186     m_menu->addAction(menu_nl);
0187     m_menu->addAction(menu_en);
0188     m_menu->addAction(menu_fr);
0189     m_menu->addAction(menu_de);
0190     m_menu->addAction(menu_el);
0191     m_menu->addAction(menu_it);
0192 
0193     addTopLevelAction(QStringLiteral("ja_en"), i18n("&Japanese to English"));
0194     addTopLevelAction(QStringLiteral("ko_en"), i18n("&Korean to English"));
0195 
0196     m_menu->addAction(menu_pt);
0197     m_menu->addAction(menu_ru);
0198     m_menu->addAction(menu_es);
0199 
0200     // TODO we could sort the action texts alphabetically, so that they wouldn't
0201     // be sorted in English only...
0202 }
0203 
0204 PluginBabelFish::~PluginBabelFish()
0205 {
0206     delete m_menu;
0207 }
0208 
0209 // Decide whether or not to enable the menu
0210 void PluginBabelFish::slotEnableMenu()
0211 {
0212     KParts::ReadOnlyPart *part = qobject_cast<KParts::ReadOnlyPart *>(parent());
0213     TextExtension *textExt = TextExtension::childObject(parent());
0214 
0215     //if (part)
0216     //    kDebug() << part->url();
0217 
0218     if (part && textExt) {
0219         const QString scheme = part->url().scheme();    // always lower case
0220         if ((scheme == QLatin1String("http")) || (scheme == QLatin1String("https"))) {
0221             if (KParts::NavigationExtension::childObject(part)) {
0222                 m_menu->setEnabled(true);
0223                 return;
0224             }
0225         }
0226     }
0227 
0228     m_menu->setEnabled(false);
0229 }
0230 
0231 void PluginBabelFish::translateURL(QAction *action)
0232 {
0233     // KHTMLPart and KWebKitPart provide a TextExtension, at least.
0234     // So if we got a part without a TextExtension, give an error.
0235     TextExtension *textExt = TextExtension::childObject(parent());
0236     Q_ASSERT(textExt); // already checked in slotAboutToShow
0237 
0238     // Select engine
0239     const KConfig cfg(QStringLiteral("translaterc"));
0240     const KConfigGroup grp(&cfg, QString());
0241     const QString language = action->objectName();
0242     const QString engine = grp.readEntry(language, QStringLiteral("google"));
0243 
0244     KParts::NavigationExtension *ext = KParts::NavigationExtension::childObject(parent());
0245     if (!ext) {
0246         return;
0247     }
0248     KParts::ReadOnlyPart *part = qobject_cast<KParts::ReadOnlyPart *>(parent());
0249 
0250     // we check if we have text selected.  if so, we translate that. If
0251     // not, we translate the url
0252     QString textForQuery;
0253     QUrl url = part->url();
0254     const bool hasSelection = textExt->hasSelection();
0255 
0256     if (hasSelection) {
0257         QString selection = textExt->selectedText(TextExtension::PlainText);
0258         textForQuery = QString::fromLatin1(QUrl::toPercentEncoding(selection));
0259     } else {
0260         // Check syntax
0261         if (!url.isValid()) {
0262             QString title = i18nc("@title:window", "Malformed URL");
0263             QString text = i18n("The URL you entered is not valid, please "
0264                                 "correct it and try again.");
0265             KMessageBox::error(part->widget(), text, title);
0266             return;
0267         }
0268     }
0269     const QString urlForQuery = QLatin1String(QUrl::toPercentEncoding(url.url()));
0270 
0271     if (url.scheme() == QLatin1String("https")) {
0272         if (KMessageBox::warningContinueCancel(part->widget(),
0273                                                xi18nc("@info", "\
0274 You are viewing this page over a secure connection.<nl/><nl/>\
0275 The URL of the page will sent to the online translation service,<nl/>\
0276 which may fetch the insecure version of the page."),
0277                                                i18nc("@title:window", "Security Warning"),
0278                                                KStandardGuiItem::cont(),
0279                                                KStandardGuiItem::cancel(),
0280                                                QStringLiteral("insecureTranslate")) != KMessageBox::Continue) {
0281             return;
0282         }
0283     }
0284 
0285     // Create URL
0286     QUrl result;
0287     QString query;
0288 
0289     if (engine == QLatin1String("freetranslation")) {
0290         query = QStringLiteral("sequence=core&language=");
0291         if (language == QStringLiteral("en_es")) {
0292             query += QLatin1String("English/Spanish");
0293         } else if (language == QStringLiteral("en_de")) {
0294             query += QLatin1String("English/German");
0295         } else if (language == QStringLiteral("en_it")) {
0296             query += QLatin1String("English/Italian");
0297         } else if (language == QStringLiteral("en_nl")) {
0298             query += QLatin1String("English/Dutch");
0299         } else if (language == QStringLiteral("en_pt")) {
0300             query += QLatin1String("English/Portuguese");
0301         } else if (language == QStringLiteral("en_no")) {
0302             query += QLatin1String("English/Norwegian");
0303         } else if (language == QStringLiteral("en_zh")) {
0304             query += QLatin1String("English/SimplifiedChinese");
0305         } else if (language == QStringLiteral("en_zhTW")) {
0306             query += QLatin1String("English/TraditionalChinese");
0307         } else if (language == QStringLiteral("es_en")) {
0308             query += QLatin1String("Spanish/English");
0309         } else if (language == QStringLiteral("fr_en")) {
0310             query += QLatin1String("French/English");
0311         } else if (language == QStringLiteral("de_en")) {
0312             query += QLatin1String("German/English");
0313         } else if (language == QStringLiteral("it_en")) {
0314             query += QLatin1String("Italian/English");
0315         } else if (language == QStringLiteral("nl_en")) {
0316             query += QLatin1String("Dutch/English");
0317         } else if (language == QStringLiteral("pt_en")) {
0318             query += QLatin1String("Portuguese/English");
0319         } else { // Should be en_fr
0320             query += QLatin1String("English/French");
0321         }
0322         if (hasSelection) {
0323             // ## does not work
0324             result = QUrl(QStringLiteral("http://ets.freetranslation.com"));
0325             query += QLatin1String("&mode=html&template=results_en-us.htm&srctext=");
0326             query += textForQuery;
0327         } else {
0328             result = QUrl(QStringLiteral("http://www.freetranslation.com/web.asp"));
0329             query += QLatin1String("&url=");
0330             query += urlForQuery;
0331         }
0332     } else if (engine == QLatin1String("parsit")) {
0333         // Does only English -> Thai
0334         result = QUrl(QStringLiteral("http://c3po.links.nectec.or.th/cgi-bin/Parsitcgi.exe"));
0335         query = QStringLiteral("mode=test&inputtype=");
0336         if (hasSelection) {
0337             query += QLatin1String("text&TxtEng=");
0338             query += textForQuery;
0339         } else {
0340             query += QLatin1String("html&inputurl=");
0341             query += urlForQuery;
0342         }
0343     } else if (engine == QLatin1String("reverso")) {
0344         result = QUrl(QStringLiteral("http://www.reverso.net/url/frame.asp"));
0345         query = QStringLiteral("autotranslate=on&templates=0&x=0&y=0&directions=");
0346         if (language == QStringLiteral("de_fr")) {
0347             query += QLatin1String("524292");
0348         } else if (language == QStringLiteral("fr_en")) {
0349             query += QLatin1String("65544");
0350         } else if (language == QStringLiteral("fr_de")) {
0351             query += QLatin1String("262152");
0352         } else if (language == QStringLiteral("de_en")) {
0353             query += QLatin1String("65540");
0354         } else if (language == QStringLiteral("en_de")) {
0355             query += QLatin1String("262145");
0356         } else if (language == QStringLiteral("en_es")) {
0357             query += QLatin1String("2097153");
0358         } else if (language == QStringLiteral("es_en")) {
0359             query += QLatin1String("65568");
0360         } else if (language == QStringLiteral("fr_es")) {
0361             query += QLatin1String("2097160");
0362         } else { // "en_fr"
0363             query += QLatin1String("524289");
0364         }
0365         query += QLatin1String("&url=");
0366         query += urlForQuery;
0367     } else if (engine == QLatin1String("tsail")) {
0368         result = QUrl(QStringLiteral("http://www.t-mail.com/cgi-bin/tsail"));
0369         query = QStringLiteral("sail=full&lp=");
0370         if (language == QStringLiteral("zhTW_en")) {
0371             query += QLatin1String("tw-en");
0372         } else if (language == QStringLiteral("en_zhTW")) {
0373             query += QLatin1String("en-tw");
0374         } else {
0375             query += language;
0376             query[15] = '-';
0377         }
0378         query += urlForQuery;
0379     } else if (engine == QLatin1String("voila")) {
0380         result = QUrl(QStringLiteral("http://tr.voila.fr/traduire-une-page-web-frame.php"));
0381         const QStringList parts = language.split('_');
0382         if (parts.count() == 2) {
0383             // The translation direction is "first letter of source, first letter of dest"
0384             query = QStringLiteral("translationDirection=");
0385             query += parts[0][0];
0386             query += parts[1][0];
0387             if (false /*hasSelection*/) {   // TODO: needs POST
0388                 query += QLatin1String("&isText=1&stext=");
0389                 query += textForQuery;
0390             } else {
0391                 query += QLatin1String("&urlToTranslate=");
0392                 query += urlForQuery;
0393             }
0394         }
0395     } else {
0396 
0397         const QStringList parts = language.split('_');
0398 
0399         if (hasSelection) { //https://translate.google.com/#en|de|text_to_translate
0400             query = QStringLiteral("https://translate.google.com/#");
0401             if (parts.count() == 2) {
0402                 query += parts[0] + "|" + parts[1];
0403             }
0404             query += "|" + textForQuery;
0405             result = QUrl(query);
0406         } else { //https://translate.google.com/translate?hl=en&sl=en&tl=de&u=http%3A%2F%2Fkde.org%2F%2F
0407             result = QUrl(QStringLiteral("https://translate.google.com/translate"));
0408             query = QStringLiteral("ie=UTF-8");
0409             if (parts.count() == 2) {
0410                 query += "&sl=" + parts[0] + "&tl=" + parts[1];
0411             }
0412             query += "&u=" + urlForQuery;
0413             result.setQuery(query);
0414         }
0415     }
0416 
0417     // Connect to the fish
0418     emit ext->openUrlRequest(result);
0419 }
0420 
0421 #include <plugin_babelfish.moc>