2019-06-29 16:29:33 +05:30
# include " qtcommon.h "
# include "extension.h"
2021-06-28 22:24:59 -06:00
# include "translatewrapper.h"
2020-02-25 04:39:27 -07:00
# include "blockmarkup.h"
2021-06-30 17:52:52 -06:00
# include <concurrent_priority_queue.h>
2020-02-25 04:39:27 -07:00
# include <fstream>
2020-03-16 02:56:04 -06:00
# include <QComboBox>
2019-06-13 04:01:29 -04:00
2020-02-25 04:39:27 -07:00
extern const char * NATIVE_LANGUAGE ;
2020-03-16 02:56:04 -06:00
extern const char * TRANSLATE_TO ;
2021-01-21 07:07:09 -07:00
extern const char * TRANSLATE_FROM ;
2020-03-27 04:07:05 -06:00
extern const char * TRANSLATE_SELECTED_THREAD_ONLY ;
2020-03-26 05:41:21 -06:00
extern const char * RATE_LIMIT_ALL_THREADS ;
extern const char * RATE_LIMIT_SELECTED_THREAD ;
extern const char * USE_TRANS_CACHE ;
2021-03-09 21:32:56 -07:00
extern const char * FILTER_GARBAGE ;
2021-06-30 17:52:52 -06:00
extern const char * MAX_TRANSLATIONS_IN_TIMESPAN ;
extern const char * TIMESPAN ;
2020-03-29 20:55:12 -06:00
extern const char * MAX_SENTENCE_SIZE ;
extern const char * API_KEY ;
2019-06-13 04:01:29 -04:00
extern const wchar_t * TOO_MANY_TRANS_REQUESTS ;
extern const char * TRANSLATION_PROVIDER ;
2020-03-29 20:55:12 -06:00
extern const char * GET_API_KEY_FROM ;
2021-06-28 22:24:59 -06:00
extern const QStringList languagesTo , languagesFrom ;
2021-08-17 22:48:16 -06:00
extern bool translateSelectedOnly , useRateLimiter , rateLimitSelected , useCache , useFilter ;
2021-06-30 17:52:52 -06:00
extern int tokenCount , rateLimitTimespan , maxSentenceSize ;
2021-06-28 22:24:59 -06:00
std : : pair < bool , std : : wstring > Translate ( const std : : wstring & text , TranslationParam tlp ) ;
2019-06-13 04:01:29 -04:00
2020-03-16 02:56:04 -06:00
QFormLayout * display ;
2020-11-02 06:27:21 -07:00
Settings settings ;
2020-02-25 04:39:27 -07:00
2021-01-15 06:07:23 -07:00
namespace
2019-06-29 16:29:33 +05:30
{
2021-06-28 22:24:59 -06:00
Synchronized < TranslationParam > tlp ;
2021-07-01 23:31:36 -06:00
Synchronized < std : : unordered_map < std : : wstring , std : : wstring > > translationCache ;
int savedSize = 0 ;
std : : string CacheFile ( )
{
return FormatString ( " %s Cache (%S) . txt " , TRANSLATION_PROVIDER, tlp->translateTo) ;
}
2021-01-15 06:07:23 -07:00
void SaveCache ( )
{
std : : wstring allTranslations ( L " \xfeff " ) ;
for ( const auto & [ sentence , translation ] : translationCache . Acquire ( ) . contents )
allTranslations . append ( L " |SENTENCE| " ) . append ( sentence ) . append ( L " |TRANSLATION| " ) . append ( translation ) . append ( L " |END| \r \n " ) ;
2021-07-01 23:31:36 -06:00
std : : ofstream ( CacheFile ( ) , std : : ios : : binary | std : : ios : : trunc ) . write ( ( const char * ) allTranslations . c_str ( ) , allTranslations . size ( ) * sizeof ( wchar_t ) ) ;
2021-01-15 06:07:23 -07:00
savedSize = translationCache - > size ( ) ;
}
2021-07-01 19:37:00 +02:00
void LoadCache ( )
{
translationCache - > clear ( ) ;
2021-07-01 23:31:36 -06:00
std : : ifstream stream ( CacheFile ( ) , std : : ios : : binary ) ;
2021-07-01 19:37:00 +02:00
BlockMarkupIterator savedTranslations ( stream , Array < std : : wstring_view > { L " |SENTENCE| " , L " |TRANSLATION| " } ) ;
auto translationCache = : : translationCache . Acquire ( ) ;
while ( auto read = savedTranslations . Next ( ) )
{
auto & [ sentence , translation ] = read . value ( ) ;
translationCache - > try_emplace ( std : : move ( sentence ) , std : : move ( translation ) ) ;
}
savedSize = translationCache - > size ( ) ;
}
2019-06-29 16:29:33 +05:30
}
2021-01-21 07:07:09 -07:00
class Window : public QDialog , Localizer
2019-06-13 04:01:29 -04:00
{
2020-03-16 02:56:04 -06:00
public :
2021-01-30 12:07:37 -07:00
Window ( ) : QDialog ( nullptr , Qt : : WindowMinMaxButtonsHint )
2019-06-13 04:01:29 -04:00
{
2020-03-17 22:01:16 -06:00
display = new QFormLayout ( this ) ;
2020-03-16 02:56:04 -06:00
2019-08-12 10:44:51 -04:00
settings . beginGroup ( TRANSLATION_PROVIDER ) ;
2020-03-16 02:56:04 -06:00
2021-01-21 07:07:09 -07:00
auto translateToCombo = new QComboBox ( this ) ;
2021-06-28 22:24:59 -06:00
translateToCombo - > addItems ( languagesTo ) ;
int i = - 1 ;
if ( settings . contains ( TRANSLATE_TO ) ) i = translateToCombo - > findText ( settings . value ( TRANSLATE_TO ) . toString ( ) ) ;
if ( i < 0 ) i = translateToCombo - > findText ( NATIVE_LANGUAGE , Qt : : MatchStartsWith ) ;
if ( i < 0 ) i = translateToCombo - > findText ( " English " , Qt : : MatchStartsWith ) ;
translateToCombo - > setCurrentIndex ( i ) ;
2021-01-21 07:07:09 -07:00
SaveTranslateTo ( translateToCombo - > currentText ( ) ) ;
display - > addRow ( TRANSLATE_TO , translateToCombo ) ;
connect ( translateToCombo , & QComboBox : : currentTextChanged , this , & Window : : SaveTranslateTo ) ;
auto translateFromCombo = new QComboBox ( this ) ;
2021-06-28 22:24:59 -06:00
translateFromCombo - > addItem ( " ? " ) ;
translateFromCombo - > addItems ( languagesFrom ) ;
i = - 1 ;
if ( settings . contains ( TRANSLATE_FROM ) ) i = translateFromCombo - > findText ( settings . value ( TRANSLATE_FROM ) . toString ( ) ) ;
if ( i < 0 ) i = 0 ;
translateFromCombo - > setCurrentIndex ( i ) ;
2021-01-21 07:07:09 -07:00
SaveTranslateFrom ( translateFromCombo - > currentText ( ) ) ;
display - > addRow ( TRANSLATE_FROM , translateFromCombo ) ;
connect ( translateFromCombo , & QComboBox : : currentTextChanged , this , & Window : : SaveTranslateFrom ) ;
2020-03-26 05:41:21 -06:00
for ( auto [ value , label ] : Array < bool & , const char * > {
2020-03-27 04:07:05 -06:00
{ translateSelectedOnly , TRANSLATE_SELECTED_THREAD_ONLY } ,
2021-08-17 22:48:16 -06:00
{ useRateLimiter , RATE_LIMIT_ALL_THREADS } ,
2020-03-26 05:41:21 -06:00
{ rateLimitSelected , RATE_LIMIT_SELECTED_THREAD } ,
{ useCache , USE_TRANS_CACHE } ,
2021-03-09 21:32:56 -07:00
{ useFilter , FILTER_GARBAGE }
2020-03-26 05:41:21 -06:00
} )
{
value = settings . value ( label , value ) . toBool ( ) ;
auto checkBox = new QCheckBox ( this ) ;
checkBox - > setChecked ( value ) ;
display - > addRow ( label , checkBox ) ;
connect ( checkBox , & QCheckBox : : clicked , [ label , & value ] ( bool checked ) { settings . setValue ( label , value = checked ) ; } ) ;
}
for ( auto [ value , label ] : Array < int & , const char * > {
2021-06-30 17:52:52 -06:00
{ tokenCount , MAX_TRANSLATIONS_IN_TIMESPAN } ,
{ rateLimitTimespan , TIMESPAN } ,
2020-03-29 20:55:12 -06:00
{ maxSentenceSize , MAX_SENTENCE_SIZE } ,
2020-03-26 05:41:21 -06:00
} )
{
value = settings . value ( label , value ) . toInt ( ) ;
auto spinBox = new QSpinBox ( this ) ;
spinBox - > setRange ( 0 , INT_MAX ) ;
spinBox - > setValue ( value ) ;
display - > addRow ( label , spinBox ) ;
connect ( spinBox , qOverload < int > ( & QSpinBox : : valueChanged ) , [ label , & value ] ( int newValue ) { settings . setValue ( label , value = newValue ) ; } ) ;
}
2020-03-29 20:55:12 -06:00
if ( GET_API_KEY_FROM )
{
2021-01-18 18:58:21 -07:00
auto keyEdit = new QLineEdit ( settings . value ( API_KEY ) . toString ( ) , this ) ;
2021-06-28 22:24:59 -06:00
tlp - > authKey = S ( keyEdit - > text ( ) ) ;
QObject : : connect ( keyEdit , & QLineEdit : : textChanged , [ ] ( QString key ) { settings . setValue ( API_KEY , S ( tlp - > authKey = S ( key ) ) ) ; } ) ;
2020-03-29 20:55:12 -06:00
auto keyLabel = new QLabel ( QString ( " <a href= \" %1 \" >%2</a> " ) . arg ( GET_API_KEY_FROM , API_KEY ) , this ) ;
keyLabel - > setOpenExternalLinks ( true ) ;
2021-01-18 18:58:21 -07:00
display - > addRow ( keyLabel , keyEdit ) ;
2020-03-29 20:55:12 -06:00
}
2020-03-16 02:56:04 -06:00
setWindowTitle ( TRANSLATION_PROVIDER ) ;
QMetaObject : : invokeMethod ( this , & QWidget : : show , Qt : : QueuedConnection ) ;
2019-06-13 04:01:29 -04:00
}
2020-03-16 02:56:04 -06:00
~ Window ( )
2019-06-13 04:01:29 -04:00
{
2019-06-29 16:29:33 +05:30
SaveCache ( ) ;
2019-06-13 04:01:29 -04:00
}
2020-03-16 02:56:04 -06:00
private :
2021-01-21 07:07:09 -07:00
void SaveTranslateTo ( QString language )
2020-03-16 02:56:04 -06:00
{
2021-07-01 19:37:00 +02:00
if ( translationCache - > size ( ) > savedSize ) SaveCache ( ) ;
2021-07-01 23:31:36 -06:00
settings . setValue ( TRANSLATE_TO , S ( tlp - > translateTo = S ( language ) ) ) ;
2021-07-01 19:37:00 +02:00
LoadCache ( ) ;
2021-01-21 07:07:09 -07:00
}
void SaveTranslateFrom ( QString language )
{
2021-06-28 22:24:59 -06:00
settings . setValue ( TRANSLATE_FROM , S ( tlp - > translateFrom = S ( language ) ) ) ;
2019-06-13 04:01:29 -04:00
}
2020-03-16 02:56:04 -06:00
} window ;
2019-06-13 04:01:29 -04:00
bool ProcessSentence ( std : : wstring & sentence , SentenceInfo sentenceInfo )
{
2020-03-29 20:55:12 -06:00
if ( sentenceInfo [ " text number " ] = = 0 | | sentence . size ( ) > maxSentenceSize ) return false ;
2019-06-13 04:01:29 -04:00
static class
{
public :
bool Request ( )
{
2021-07-01 13:35:33 -06:00
DWORD64 current = GetTickCount64 ( ) , token ;
2021-06-30 17:52:52 -06:00
while ( tokens . try_pop ( token ) ) if ( token > current - rateLimitTimespan )
{
tokens . push ( token ) ; // popped one too many
break ;
}
2021-11-06 06:09:07 -06:00
bool available = tokens . size ( ) < tokenCount ;
if ( available ) tokens . push ( current ) ;
return available ;
2019-06-13 04:01:29 -04:00
}
private :
2021-07-01 13:35:33 -06:00
concurrency : : concurrent_priority_queue < DWORD64 , std : : greater < DWORD64 > > tokens ;
2019-06-13 04:01:29 -04:00
} rateLimiter ;
2021-03-09 21:32:56 -07:00
auto Trim = [ ] ( std : : wstring & text )
2021-01-15 06:07:23 -07:00
{
text . erase ( text . begin ( ) , std : : find_if_not ( text . begin ( ) , text . end ( ) , iswspace ) ) ;
text . erase ( std : : find_if_not ( text . rbegin ( ) , text . rend ( ) , iswspace ) . base ( ) , text . end ( ) ) ;
} ;
2019-06-13 04:01:29 -04:00
bool cache = false ;
std : : wstring translation ;
2021-03-09 21:32:56 -07:00
if ( useFilter )
{
Trim ( sentence ) ;
2021-08-18 00:41:34 -06:00
sentence . erase ( std : : remove_if ( sentence . begin ( ) , sentence . end ( ) , [ ] ( wchar_t ch ) { return ch < ' ' & & ch ! = ' \n ' ; } ) , sentence . end ( ) ) ;
2021-03-09 21:32:56 -07:00
}
2020-03-26 05:41:21 -06:00
if ( useCache )
2019-06-29 16:29:33 +05:30
{
2020-02-25 04:39:27 -07:00
auto translationCache = : : translationCache . Acquire ( ) ;
2020-03-27 04:07:05 -06:00
if ( auto it = translationCache - > find ( sentence ) ; it ! = translationCache - > end ( ) ) translation = it - > second + L " \x200b " ; // dumb hack to not try to translate if stored empty translation
2019-06-29 16:29:33 +05:30
}
2020-03-27 04:07:05 -06:00
if ( translation . empty ( ) & & ( ! translateSelectedOnly | | sentenceInfo [ " current select " ] ) )
2021-08-17 22:48:16 -06:00
if ( rateLimiter . Request ( ) | | ! useRateLimiter | | ( ! rateLimitSelected & & sentenceInfo [ " current select " ] ) ) std : : tie ( cache , translation ) = Translate ( sentence , tlp . Copy ( ) ) ;
2020-03-26 05:41:21 -06:00
else translation = TOO_MANY_TRANS_REQUESTS ;
2021-03-09 21:32:56 -07:00
if ( useFilter ) Trim ( translation ) ;
2020-03-26 05:41:21 -06:00
if ( cache ) translationCache - > try_emplace ( sentence , translation ) ;
2020-02-25 04:39:27 -07:00
if ( cache & & translationCache - > size ( ) > savedSize + 50 ) SaveCache ( ) ;
2019-06-13 04:01:29 -04:00
2020-12-14 06:26:01 -07:00
for ( int i = 0 ; i < translation . size ( ) ; + + i ) if ( translation [ i ] = = ' \r ' & & translation [ i + 1 ] = = ' \n ' ) translation [ i ] = 0x200b ; // for some reason \r appears as newline - no need to double
2020-12-14 19:10:47 -07:00
if ( ! translation . empty ( ) ) ( sentence + = L " \x200b \n " ) + = translation ;
2019-06-13 04:01:29 -04:00
return true ;
}
2021-06-28 22:24:59 -06:00
extern const std : : unordered_map < std : : wstring , std : : wstring > codes ;
TEST (
{
assert ( Translate ( L " こんにちは " , { L " English " , L " ? " , L " " } ) . second . find ( L " ello " ) = = 1 | | strstr ( TRANSLATION_PROVIDER , " DevTools " ) ) ;
for ( auto languages : { languagesFrom , languagesTo } ) for ( auto language : languages )
assert ( codes . count ( S ( language ) ) ) ;
assert ( codes . count ( L " ? " ) ) ;
}
) ;