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>