File indexing completed on 2025-04-27 03:58:38
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. 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 DOnlineTranslator::DOnlineTranslator(QObject* const parent) 0022 : QObject(parent), 0023 d (new Private(this)) 0024 { 0025 connect(d->stateMachine, &QStateMachine::finished, 0026 this, &DOnlineTranslator::signalFinished); 0027 0028 connect(d->stateMachine, &QStateMachine::stopped, 0029 this, &DOnlineTranslator::signalFinished); 0030 } 0031 0032 DOnlineTranslator::~DOnlineTranslator() 0033 { 0034 delete d; 0035 } 0036 0037 void DOnlineTranslator::translate(const QString& text, 0038 Engine engine, 0039 Language translationLang, 0040 Language sourceLang, 0041 Language uiLang) 0042 { 0043 abort(); 0044 resetData(); 0045 0046 d->onlyDetectLanguage = false; 0047 d->source = text; 0048 d->sourceLang = sourceLang; 0049 d->translationLang = (translationLang == Auto) ? language(QLocale()) : translationLang; 0050 d->uiLang = (uiLang == Auto) ? language(QLocale()) : uiLang; 0051 0052 // Check if the selected languages are supported by the engine 0053 0054 if (!isSupportTranslation(engine, d->sourceLang)) 0055 { 0056 resetData(ParametersError, 0057 i18n("Selected source language %1 is not supported for %2", 0058 languageName(d->sourceLang), 0059 QString::fromUtf8(QMetaEnum::fromType<Engine>().valueToKey(engine)))); 0060 0061 Q_EMIT signalFinished(); 0062 0063 return; 0064 } 0065 0066 if (!isSupportTranslation(engine, d->translationLang)) 0067 { 0068 resetData(ParametersError, 0069 i18n("Selected translation language %1 is not supported for %2", 0070 languageName(d->translationLang), 0071 QString::fromUtf8(QMetaEnum::fromType<Engine>().valueToKey(engine)))); 0072 0073 Q_EMIT signalFinished(); 0074 0075 return; 0076 } 0077 0078 if (!isSupportTranslation(engine, d->uiLang)) 0079 { 0080 resetData(ParametersError, 0081 i18n("Selected ui language %1 is not supported for %2", 0082 languageName(d->uiLang), 0083 QString::fromUtf8(QMetaEnum::fromType<Engine>().valueToKey(engine)))); 0084 0085 Q_EMIT signalFinished(); 0086 0087 return; 0088 } 0089 0090 switch (engine) 0091 { 0092 case Google: 0093 { 0094 buildGoogleStateMachine(); 0095 break; 0096 } 0097 0098 case Yandex: 0099 { 0100 buildYandexStateMachine(); 0101 break; 0102 } 0103 0104 case Bing: 0105 { 0106 buildBingStateMachine(); 0107 break; 0108 } 0109 0110 case LibreTranslate: 0111 { 0112 if (d->libreUrl.isEmpty()) 0113 { 0114 resetData(ParametersError, 0115 i18n("%1 URL can't be empty.", 0116 QString::fromUtf8(QMetaEnum::fromType<Engine>().valueToKey(engine)))); 0117 0118 Q_EMIT signalFinished(); 0119 0120 return; 0121 } 0122 0123 buildLibreStateMachine(); 0124 break; 0125 } 0126 0127 case Lingva: 0128 { 0129 if (d->lingvaUrl.isEmpty()) 0130 { 0131 resetData(ParametersError, 0132 i18n("%1 URL can't be empty.", 0133 QString::fromUtf8(QMetaEnum::fromType<Engine>().valueToKey(engine)))); 0134 0135 Q_EMIT signalFinished(); 0136 0137 return; 0138 } 0139 0140 buildLingvaStateMachine(); 0141 break; 0142 } 0143 } 0144 0145 d->stateMachine->start(); 0146 } 0147 0148 QString DOnlineTranslator::engineName(Engine engine) 0149 { 0150 switch (engine) 0151 { 0152 case Yandex: 0153 { 0154 return QLatin1String("Yandex"); 0155 } 0156 0157 case Bing: 0158 { 0159 return QLatin1String("Bing"); 0160 } 0161 0162 case LibreTranslate: 0163 { 0164 return QLatin1String("Libre Translate"); 0165 } 0166 0167 case Lingva: 0168 { 0169 return QLatin1String("Lingva"); 0170 } 0171 0172 default: 0173 { 0174 return QLatin1String("Google"); 0175 } 0176 } 0177 } 0178 0179 void DOnlineTranslator::detectLanguage(const QString& text, Engine engine) 0180 { 0181 abort(); 0182 resetData(); 0183 0184 d->onlyDetectLanguage = true; 0185 d->source = text; 0186 d->sourceLang = Auto; 0187 d->translationLang = English; 0188 d->uiLang = language(QLocale()); 0189 0190 switch (engine) 0191 { 0192 case Google: 0193 { 0194 buildGoogleDetectStateMachine(); 0195 break; 0196 } 0197 0198 case Yandex: 0199 { 0200 buildYandexDetectStateMachine(); 0201 break; 0202 } 0203 0204 case Bing: 0205 { 0206 buildBingDetectStateMachine(); 0207 break; 0208 } 0209 0210 case LibreTranslate: 0211 { 0212 if (d->libreUrl.isEmpty()) 0213 { 0214 resetData(ParametersError, 0215 i18n("%1 URL can't be empty.", 0216 QString::fromUtf8(QMetaEnum::fromType<Engine>().valueToKey(engine)))); 0217 0218 Q_EMIT signalFinished(); 0219 0220 return; 0221 } 0222 0223 buildLibreDetectStateMachine(); 0224 break; 0225 } 0226 0227 case Lingva: 0228 { 0229 if (d->lingvaUrl.isEmpty()) 0230 { 0231 resetData(ParametersError, 0232 i18n("%1 URL can't be empty.", 0233 QString::fromUtf8(QMetaEnum::fromType<Engine>().valueToKey(engine)))); 0234 0235 Q_EMIT signalFinished(); 0236 0237 return; 0238 } 0239 0240 buildLingvaDetectStateMachine(); 0241 break; 0242 } 0243 } 0244 0245 d->stateMachine->start(); 0246 } 0247 0248 void DOnlineTranslator::abort() 0249 { 0250 if (d->currentReply != nullptr) 0251 { 0252 d->currentReply->abort(); 0253 } 0254 } 0255 0256 bool DOnlineTranslator::isRunning() const 0257 { 0258 return d->stateMachine->isRunning(); 0259 } 0260 0261 void DOnlineTranslator::slotSkipGarbageText() 0262 { 0263 d->translation.append(sender()->property(Private::s_textProperty).toString()); 0264 } 0265 0266 void DOnlineTranslator::buildSplitNetworkRequest(QState* const parent, 0267 void (DOnlineTranslator::*requestMethod)(), 0268 void (DOnlineTranslator::*parseMethod)(), 0269 const QString& text, 0270 int textLimit) 0271 { 0272 QString unsendedText = text; 0273 auto* nextTranslationState = new QState(parent); 0274 parent->setInitialState(nextTranslationState); 0275 0276 while (!unsendedText.isEmpty()) 0277 { 0278 auto* currentTranslationState = nextTranslationState; 0279 nextTranslationState = new QState(parent); 0280 0281 // Do not translate the part if it looks like garbage 0282 0283 const int splitIndex = getSplitIndex(unsendedText, textLimit); 0284 0285 if (splitIndex == -1) 0286 { 0287 currentTranslationState->setProperty(Private::s_textProperty, unsendedText.left(textLimit)); 0288 currentTranslationState->addTransition(nextTranslationState); 0289 0290 connect(currentTranslationState, &QState::entered, 0291 this, &DOnlineTranslator::slotSkipGarbageText); 0292 0293 // Remove the parsed part from the next parsing 0294 0295 unsendedText = unsendedText.mid(textLimit); 0296 } 0297 else 0298 { 0299 buildNetworkRequestState(currentTranslationState, requestMethod, parseMethod, unsendedText.left(splitIndex)); 0300 currentTranslationState->addTransition(currentTranslationState, &QState::finished, nextTranslationState); 0301 0302 // Remove the parsed part from the next parsing 0303 0304 unsendedText = unsendedText.mid(splitIndex); 0305 } 0306 } 0307 0308 nextTranslationState->addTransition(new QFinalState(parent)); 0309 } 0310 0311 void DOnlineTranslator::buildNetworkRequestState(QState* const parent, 0312 void (DOnlineTranslator::*requestMethod)(), 0313 void (DOnlineTranslator::*parseMethod)(), 0314 const QString& text) 0315 { 0316 // Network substates 0317 0318 auto* requestingState = new QState(parent); 0319 auto* parsingState = new QState(parent); 0320 0321 parent->setInitialState(requestingState); 0322 0323 connect(d->networkManager, &QNetworkAccessManager::finished, 0324 parsingState, [parsingState](QNetworkReply* reply) 0325 { 0326 parsingState->setProperty("QNetworkReply", (quintptr)reply); 0327 } 0328 ); 0329 0330 // Substates transitions 0331 0332 requestingState->addTransition(d->networkManager, &QNetworkAccessManager::finished, parsingState); 0333 parsingState->addTransition(new QFinalState(parent)); 0334 0335 // Setup requesting state 0336 0337 requestingState->setProperty(Private::s_textProperty, text); 0338 0339 connect(requestingState, &QState::entered, 0340 this, requestMethod); 0341 0342 // Setup parsing state 0343 0344 connect(parsingState, &QState::entered, 0345 this, parseMethod); 0346 } 0347 0348 void DOnlineTranslator::resetData(TranslationError error, const QString& errorString) 0349 { 0350 d->error = error; 0351 d->errorString = errorString; 0352 d->translation.clear(); 0353 d->translationTranslit.clear(); 0354 d->sourceTranslit.clear(); 0355 d->sourceTranscription.clear(); 0356 d->translationOptions.clear(); 0357 0358 d->stateMachine->stop(); 0359 0360 for (QAbstractState* state : d->stateMachine->findChildren<QAbstractState*>()) 0361 { 0362 if (!d->stateMachine->configuration().contains(state)) 0363 { 0364 state->deleteLater(); 0365 } 0366 } 0367 } 0368 0369 QString DOnlineTranslator::languageApiCode(Engine engine, Language lang) 0370 { 0371 if (!isSupportTranslation(engine, lang)) 0372 { 0373 return QString(); 0374 } 0375 0376 switch (engine) 0377 { 0378 case Google: 0379 { 0380 return DOnlineTranslator::Private::s_googleLanguageCodes.value(lang, DOnlineTranslator::Private::s_genericLanguageCodes.value(lang)); 0381 } 0382 0383 case Yandex: 0384 { 0385 return DOnlineTranslator::Private::s_yandexLanguageCodes.value(lang, DOnlineTranslator::Private::s_genericLanguageCodes.value(lang)); 0386 } 0387 0388 case Bing: 0389 { 0390 return DOnlineTranslator::Private::s_bingLanguageCodes.value(lang, DOnlineTranslator::Private::s_genericLanguageCodes.value(lang)); 0391 } 0392 0393 case LibreTranslate: 0394 { 0395 return DOnlineTranslator::Private::s_genericLanguageCodes.value(lang); 0396 } 0397 0398 case Lingva: 0399 { 0400 return DOnlineTranslator::Private::s_lingvaLanguageCodes.value(lang, DOnlineTranslator::Private::s_genericLanguageCodes.value(lang)); 0401 } 0402 } 0403 0404 Q_UNREACHABLE(); 0405 } 0406 0407 DOnlineTranslator::Language DOnlineTranslator::language(Engine engine, const QString& langCode) 0408 { 0409 // Engine exceptions 0410 0411 switch (engine) 0412 { 0413 case Google: 0414 { 0415 return DOnlineTranslator::Private::s_googleLanguageCodes.key(langCode, DOnlineTranslator::Private::s_genericLanguageCodes.key(langCode, NoLanguage)); 0416 } 0417 0418 case Yandex: 0419 { 0420 return DOnlineTranslator::Private::s_yandexLanguageCodes.key(langCode, DOnlineTranslator::Private::s_genericLanguageCodes.key(langCode, NoLanguage)); 0421 } 0422 0423 case Bing: 0424 { 0425 return DOnlineTranslator::Private::s_bingLanguageCodes.key(langCode, DOnlineTranslator::Private::s_genericLanguageCodes.key(langCode, NoLanguage)); 0426 } 0427 0428 case LibreTranslate: 0429 { 0430 return DOnlineTranslator::Private::s_genericLanguageCodes.key(langCode, NoLanguage); 0431 } 0432 0433 case Lingva: 0434 { 0435 return DOnlineTranslator::Private::s_lingvaLanguageCodes.key(langCode, DOnlineTranslator::Private::s_genericLanguageCodes.key(langCode, NoLanguage)); 0436 } 0437 } 0438 0439 Q_UNREACHABLE(); 0440 } 0441 0442 int DOnlineTranslator::getSplitIndex(const QString& untranslatedText, int limit) 0443 { 0444 if (untranslatedText.size() < limit) 0445 { 0446 return limit; 0447 } 0448 0449 int splitIndex = untranslatedText.lastIndexOf(QLatin1String(". "), limit - 1); 0450 0451 if (splitIndex != -1) 0452 { 0453 return splitIndex + 1; 0454 } 0455 0456 splitIndex = untranslatedText.lastIndexOf(QLatin1Char(' '), limit - 1); 0457 0458 if (splitIndex != -1) 0459 { 0460 return splitIndex + 1; 0461 } 0462 0463 splitIndex = untranslatedText.lastIndexOf(QLatin1Char('\n'), limit - 1); 0464 0465 if (splitIndex != -1) 0466 { 0467 return splitIndex + 1; 0468 } 0469 0470 // Non-breaking space 0471 0472 splitIndex = untranslatedText.lastIndexOf(QChar(0x00a0), limit - 1); 0473 0474 if (splitIndex != -1) 0475 { 0476 return splitIndex + 1; 0477 } 0478 0479 // If the text has not passed any check and is most likely garbage 0480 0481 return limit; 0482 } 0483 0484 bool DOnlineTranslator::isContainsSpace(const QString& text) 0485 { 0486 return std::any_of(text.cbegin(), text.cend(), [](QChar symbol) 0487 { 0488 return symbol.isSpace(); 0489 } 0490 ); 0491 } 0492 0493 void DOnlineTranslator::addSpaceBetweenParts(QString& text) 0494 { 0495 if (text.isEmpty()) 0496 { 0497 return; 0498 } 0499 0500 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) 0501 0502 if (!text.back().isSpace()) 0503 { 0504 0505 #else 0506 0507 if (!text.at(text.size() - 1).isSpace()) 0508 { 0509 0510 #endif 0511 0512 text.append(QLatin1Char(' ')); 0513 } 0514 } 0515 0516 } // namespace Digikam 0517 0518 #include "moc_donlinetranslator.cpp"