File indexing completed on 2025-04-27 03:58:39
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2018-02-22 0007 * Description : A text translator using web-services - Tnadex methods. 0008 * 0009 * SPDX-FileCopyrightText: 2018-2022 by Hennadii Chernyshchyk <genaloner at gmail dot com> 0010 * SPDX-FileCopyrightText: 2021-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "donlinetranslator_p.h" 0017 0018 namespace Digikam 0019 { 0020 0021 void DOnlineTranslator::slotRequestYandexKey() 0022 { 0023 const QUrl url(QStringLiteral("https://translate.yandex.com")); 0024 d->currentReply = d->networkManager->get(QNetworkRequest(url)); 0025 } 0026 0027 void DOnlineTranslator::slotParseYandexKey() 0028 { 0029 if ((quintptr)d->currentReply.data() != sender()->property("QNetworkReply").value<quintptr>()) 0030 { 0031 return; 0032 } 0033 0034 d->currentReply->deleteLater(); 0035 0036 if (d->currentReply->error() != QNetworkReply::NoError) 0037 { 0038 resetData(NetworkError, d->currentReply->errorString()); 0039 return; 0040 } 0041 0042 // Check availability of service 0043 0044 const QByteArray webSiteData = d->currentReply->readAll(); 0045 0046 if (webSiteData.isEmpty() || 0047 webSiteData.contains("<title>Oops!</title>") || 0048 webSiteData.contains("<title>302 Found</title>")) 0049 { 0050 resetData(ServiceError, i18n("Error: Engine systems have detected suspicious traffic " 0051 "from your computer network. Please try your request again later.")); 0052 return; 0053 } 0054 0055 const QByteArray sidBeginString = "SID: '"; 0056 const int sidBeginStringPos = webSiteData.indexOf(sidBeginString); 0057 0058 if (sidBeginStringPos == -1) 0059 { 0060 resetData(ParsingError, i18n("Error: Unable to find Yandex SID in web version.")); 0061 return; 0062 } 0063 0064 const int sidBeginPosition = sidBeginStringPos + sidBeginString.size(); 0065 const int sidEndPosition = webSiteData.indexOf('\'', sidBeginPosition); 0066 0067 if (sidEndPosition == -1) 0068 { 0069 resetData(ParsingError, i18n("Error: Unable to extract Yandex SID from web version.")); 0070 return; 0071 } 0072 0073 // Yandex show reversed parts of session ID, need to decode 0074 0075 const QString sid = QString::fromUtf8(webSiteData.mid(sidBeginPosition, 0076 sidEndPosition - sidBeginPosition)); 0077 0078 QStringList sidParts = sid.split(QLatin1Char('.')); 0079 0080 for (int i = 0 ; i < sidParts.size() ; ++i) 0081 { 0082 std::reverse(sidParts[i].begin(), sidParts[i].end()); 0083 } 0084 0085 Private::s_yandexKey = sidParts.join(QLatin1Char('.')); 0086 } 0087 0088 void DOnlineTranslator::slotRequestYandexTranslate() 0089 { 0090 const QString sourceText = sender()->property(Private::s_textProperty).toString(); 0091 0092 QString lang; 0093 0094 if (d->sourceLang == Auto) 0095 { 0096 lang = languageApiCode(Yandex, d->translationLang); 0097 } 0098 else 0099 { 0100 lang = languageApiCode(Yandex, d->sourceLang) + QLatin1Char('-') + languageApiCode(Yandex, d->translationLang); 0101 } 0102 0103 // Generate API url 0104 0105 QUrl url(QStringLiteral("https://translate.yandex.net/api/v1/tr.json/translate")); 0106 0107 url.setQuery(QStringLiteral("id=%1-2-0&srv=tr-text&text=%2&lang=%3") 0108 .arg(Private::s_yandexKey, 0109 QString::fromUtf8(QUrl::toPercentEncoding(sourceText)), 0110 lang)); 0111 0112 // Setup request 0113 0114 QNetworkRequest request; 0115 request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); 0116 request.setUrl(url); 0117 0118 // Make reply 0119 0120 d->currentReply = d->networkManager->post(request, QByteArray()); 0121 } 0122 0123 void DOnlineTranslator::slotParseYandexTranslate() 0124 { 0125 if ((quintptr)d->currentReply.data() != sender()->property("QNetworkReply").value<quintptr>()) 0126 { 0127 return; 0128 } 0129 0130 d->currentReply->deleteLater(); 0131 0132 // Check for errors 0133 0134 if (d->currentReply->error() != QNetworkReply::NoError) 0135 { 0136 // Network errors 0137 0138 if (d->currentReply->error() < QNetworkReply::ContentAccessDenied) 0139 { 0140 resetData(NetworkError, d->currentReply->errorString()); 0141 return; 0142 } 0143 0144 // Parse data to get request error type 0145 0146 Private::s_yandexKey.clear(); 0147 const QJsonDocument jsonResponse = QJsonDocument::fromJson(d->currentReply->readAll()); 0148 resetData(ServiceError, jsonResponse.object().value(QStringLiteral("message")).toString()); 0149 0150 return; 0151 } 0152 0153 // Read Json 0154 0155 const QJsonDocument jsonResponse = QJsonDocument::fromJson(d->currentReply->readAll()); 0156 const QJsonObject jsonData = jsonResponse.object(); 0157 0158 // Parse language 0159 0160 if (d->sourceLang == Auto) 0161 { 0162 QString sourceCode = jsonData.value(QStringLiteral("lang")).toString(); 0163 sourceCode = sourceCode.left(sourceCode.indexOf(QLatin1Char('-'))); 0164 d->sourceLang = language(Yandex, sourceCode); 0165 0166 if (d->sourceLang == NoLanguage) 0167 { 0168 resetData(ParsingError, i18n("Error: Unable to parse autodetected language")); 0169 return; 0170 } 0171 0172 if (d->onlyDetectLanguage) 0173 { 0174 return; 0175 } 0176 } 0177 0178 // Parse translation data 0179 0180 d->translation += jsonData.value(QStringLiteral("text")).toArray().at(0).toString(); 0181 } 0182 0183 void DOnlineTranslator::slotRequestYandexSourceTranslit() 0184 { 0185 requestYandexTranslit(d->sourceLang); 0186 } 0187 0188 void DOnlineTranslator::slotParseYandexSourceTranslit() 0189 { 0190 parseYandexTranslit(d->sourceTranslit); 0191 } 0192 0193 void DOnlineTranslator::slotRequestYandexTranslationTranslit() 0194 { 0195 requestYandexTranslit(d->translationLang); 0196 } 0197 0198 void DOnlineTranslator::slotParseYandexTranslationTranslit() 0199 { 0200 parseYandexTranslit(d->translationTranslit); 0201 } 0202 0203 void DOnlineTranslator::slotRequestYandexDictionary() 0204 { 0205 // Check if language is supported (need to check here because language may be autodetected) 0206 0207 if (!isSupportDictionary(Yandex, d->sourceLang, d->translationLang) && 0208 !d->source.contains(QLatin1Char(' '))) 0209 { 0210 auto* state = qobject_cast<QState*>(sender()); 0211 state->addTransition(new QFinalState(state->parentState())); 0212 return; 0213 } 0214 0215 // Generate API url 0216 0217 const QString text = sender()->property(Private::s_textProperty).toString(); 0218 QUrl url(QStringLiteral("https://dictionary.yandex.net/dicservice.json/lookupMultiple")); 0219 0220 url.setQuery(QStringLiteral("text=%1&ui=%2&dict=%3-%4") 0221 .arg(QString::fromUtf8(QUrl::toPercentEncoding(text)), 0222 languageApiCode(Yandex, d->uiLang), 0223 languageApiCode(Yandex, d->sourceLang), 0224 languageApiCode(Yandex, d->translationLang))); 0225 0226 d->currentReply = d->networkManager->get(QNetworkRequest(url)); 0227 } 0228 0229 void DOnlineTranslator::slotParseYandexDictionary() 0230 { 0231 if ((quintptr)d->currentReply.data() != sender()->property("QNetworkReply").value<quintptr>()) 0232 { 0233 return; 0234 } 0235 0236 d->currentReply->deleteLater(); 0237 0238 if (d->currentReply->error() != QNetworkReply::NoError) 0239 { 0240 resetData(NetworkError, d->currentReply->errorString()); 0241 return; 0242 } 0243 0244 // Parse reply 0245 0246 const QJsonDocument jsonResponse = QJsonDocument::fromJson(d->currentReply->readAll()); 0247 const QJsonValue jsonData = jsonResponse.object().value(languageApiCode(Yandex, d->sourceLang) + 0248 QLatin1Char('-') + 0249 languageApiCode(Yandex, d->translationLang)).toObject().value(QStringLiteral("regular")); 0250 0251 if (d->sourceTranscriptionEnabled) 0252 { 0253 d->sourceTranscription = jsonData.toArray().at(0).toObject().value(QStringLiteral("ts")).toString(); 0254 } 0255 0256 for (const QJsonValueRef typeOfSpeechData : jsonData.toArray()) 0257 { 0258 QJsonObject typeOfSpeechObject = typeOfSpeechData.toObject(); 0259 const QString typeOfSpeech = typeOfSpeechObject.value(QStringLiteral("pos")).toObject().value(QStringLiteral("text")).toString(); 0260 0261 for (const QJsonValueRef wordData : typeOfSpeechObject.value(QStringLiteral("tr")).toArray()) 0262 { 0263 // Parse translation options 0264 0265 const QJsonObject wordObject = wordData.toObject(); 0266 const QString word = wordObject.value(QStringLiteral("text")).toString(); 0267 const QString gender = wordObject.value(QStringLiteral("gen")).toObject().value(QStringLiteral("text")).toString(); 0268 const QJsonArray translationsArray = wordObject.value(QStringLiteral("mean")).toArray(); 0269 QStringList translations; 0270 translations.reserve(translationsArray.size()); 0271 0272 for (const QJsonValue &wordTranslation : translationsArray) 0273 { 0274 translations.append(wordTranslation.toObject().value(QStringLiteral("text")).toString()); 0275 } 0276 0277 d->translationOptions[typeOfSpeech].append({word, gender, translations}); 0278 } 0279 } 0280 } 0281 0282 void DOnlineTranslator::buildYandexStateMachine() 0283 { 0284 // States 0285 0286 auto* keyState = new QState(d->stateMachine); // Generate SID from web version first to access API 0287 auto* translationState = new QState(d->stateMachine); 0288 auto* sourceTranslitState = new QState(d->stateMachine); 0289 auto* translationTranslitState = new QState(d->stateMachine); 0290 auto* dictionaryState = new QState(d->stateMachine); 0291 auto* finalState = new QFinalState(d->stateMachine); 0292 d->stateMachine->setInitialState(keyState); 0293 0294 // Transitions 0295 0296 keyState->addTransition(keyState, &QState::finished, translationState); 0297 translationState->addTransition(translationState, &QState::finished, sourceTranslitState); 0298 sourceTranslitState->addTransition(sourceTranslitState, &QState::finished, translationTranslitState); 0299 translationTranslitState->addTransition(translationTranslitState, &QState::finished, dictionaryState); 0300 dictionaryState->addTransition(dictionaryState, &QState::finished, finalState); 0301 0302 // Setup key state 0303 0304 if (Private::s_yandexKey.isEmpty()) 0305 { 0306 buildNetworkRequestState(keyState, 0307 &DOnlineTranslator::slotRequestYandexKey, 0308 &DOnlineTranslator::slotParseYandexKey); 0309 } 0310 else 0311 { 0312 keyState->setInitialState(new QFinalState(keyState)); 0313 } 0314 0315 // Setup translation state 0316 0317 buildSplitNetworkRequest(translationState, 0318 &DOnlineTranslator::slotRequestYandexTranslate, 0319 &DOnlineTranslator::slotParseYandexTranslate, 0320 d->source, 0321 Private::s_yandexTranslateLimit); 0322 0323 // Setup source translit state 0324 0325 if (d->sourceTranslitEnabled) 0326 { 0327 buildSplitNetworkRequest(sourceTranslitState, 0328 &DOnlineTranslator::slotRequestYandexSourceTranslit, 0329 &DOnlineTranslator::slotParseYandexSourceTranslit, 0330 d->source, 0331 Private::s_yandexTranslitLimit); 0332 } 0333 else 0334 { 0335 sourceTranslitState->setInitialState(new QFinalState(sourceTranslitState)); 0336 } 0337 0338 // Setup translation translit state 0339 0340 if (d->translationTranslitEnabled) 0341 { 0342 buildSplitNetworkRequest(translationTranslitState, 0343 &DOnlineTranslator::slotRequestYandexTranslationTranslit, 0344 &DOnlineTranslator::slotParseYandexTranslationTranslit, 0345 d->translation, 0346 Private::s_yandexTranslitLimit); 0347 } 0348 else 0349 { 0350 translationTranslitState->setInitialState(new QFinalState(translationTranslitState)); 0351 } 0352 0353 // Setup dictionary state 0354 0355 if (d->translationOptionsEnabled && !isContainsSpace(d->source)) 0356 { 0357 buildNetworkRequestState(dictionaryState, 0358 &DOnlineTranslator::slotRequestYandexDictionary, 0359 &DOnlineTranslator::slotParseYandexDictionary, 0360 d->source); 0361 } 0362 else 0363 { 0364 dictionaryState->setInitialState(new QFinalState(dictionaryState)); 0365 } 0366 } 0367 0368 void DOnlineTranslator::buildYandexDetectStateMachine() 0369 { 0370 // States 0371 0372 auto* keyState = new QState(d->stateMachine); // Generate SID from web version first to access API 0373 auto* detectState = new QState(d->stateMachine); 0374 auto* finalState = new QFinalState(d->stateMachine); 0375 d->stateMachine->setInitialState(keyState); 0376 0377 // Transitions 0378 0379 keyState->addTransition(keyState, &QState::finished, detectState); 0380 detectState->addTransition(detectState, &QState::finished, finalState); 0381 0382 // Setup key state 0383 0384 if (Private::s_yandexKey.isEmpty()) 0385 { 0386 buildNetworkRequestState(keyState, 0387 &DOnlineTranslator::slotRequestYandexKey, 0388 &DOnlineTranslator::slotParseYandexKey); 0389 } 0390 else 0391 { 0392 keyState->setInitialState(new QFinalState(keyState)); 0393 } 0394 0395 // Setup detect state 0396 0397 const QString text = d->source.left(getSplitIndex(d->source, Private::s_yandexTranslateLimit)); 0398 0399 buildNetworkRequestState(detectState, 0400 &DOnlineTranslator::slotRequestYandexTranslate, 0401 &DOnlineTranslator::slotParseYandexTranslate, 0402 text); 0403 } 0404 0405 void DOnlineTranslator::requestYandexTranslit(Language language) 0406 { 0407 // Check if language is supported (need to check here because language may be autodetected) 0408 0409 if (!isSupportTranslit(Yandex, language)) 0410 { 0411 auto* state = qobject_cast<QState *>(sender()); 0412 state->addTransition(new QFinalState(state->parentState())); 0413 0414 return; 0415 } 0416 0417 const QString text = sender()->property(Private::s_textProperty).toString(); 0418 0419 // Generate API url 0420 0421 QUrl url(QStringLiteral("https://translate.yandex.net/translit/translit")); 0422 url.setQuery(QString::fromUtf8("text=%1&lang=%2") 0423 .arg(QString::fromUtf8(QUrl::toPercentEncoding(text))) 0424 .arg(languageApiCode(Yandex, language))); 0425 0426 d->currentReply = d->networkManager->get(QNetworkRequest(url)); 0427 } 0428 0429 void DOnlineTranslator::parseYandexTranslit(QString& text) 0430 { 0431 if ((quintptr)d->currentReply.data() != sender()->property("QNetworkReply").value<quintptr>()) 0432 { 0433 return; 0434 } 0435 0436 d->currentReply->deleteLater(); 0437 0438 if (d->currentReply->error() != QNetworkReply::NoError) 0439 { 0440 resetData(NetworkError, d->currentReply->errorString()); 0441 0442 return; 0443 } 0444 0445 const QByteArray reply = d->currentReply->readAll(); 0446 0447 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) 0448 0449 text += QString::fromUtf8(reply.mid(1).chopped(1)); 0450 0451 #else 0452 0453 text += QString::fromUtf8(reply.mid(1)); 0454 text.chop(1); 0455 0456 #endif 0457 0458 } 0459 0460 } // namespace Digikam