2019-06-29 16:29:33 +05:30
# include " qtcommon.h "
# include "extension.h"
2020-02-25 04:39:27 -07:00
# include "blockmarkup.h"
2019-06-13 04:01:29 -04:00
# include "network.h"
2020-02-27 04:42:29 -07:00
# include <map>
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 ;
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 ;
extern const char * RATE_LIMIT_TOKEN_COUNT ;
extern const char * RATE_LIMIT_TOKEN_RESTORE_DELAY ;
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 ;
2019-06-13 04:01:29 -04:00
extern QStringList languages ;
2020-03-27 04:07:05 -06:00
extern bool translateSelectedOnly , rateLimitAll , rateLimitSelected , useCache ;
2020-03-29 20:55:12 -06:00
extern int tokenCount , tokenRestoreDelay , maxSentenceSize ;
2020-04-25 20:39:12 -06:00
std : : pair < bool , std : : wstring > Translate ( const std : : wstring & text ) ;
2019-06-13 04:01:29 -04:00
2019-08-12 10:44:51 -04:00
const char * LANGUAGE = u8 " Language " ;
2020-12-14 06:26:01 -07:00
const std : : string TRANSLATION_CACHE_FILE = FormatString ( " %s Translation Cache.txt " , TRANSLATION_PROVIDER ) ;
2019-08-12 10:44:51 -04:00
2020-03-16 02:56:04 -06:00
QFormLayout * display ;
2020-11-02 06:27:21 -07:00
Settings settings ;
2021-01-18 18:58:21 -07:00
Synchronized < std : : wstring > translateTo = L " en " , authKey ;
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-01-15 06:07:23 -07:00
Synchronized < std : : map < std : : wstring , std : : wstring > > translationCache ;
int savedSize ;
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 " ) ;
std : : ofstream ( TRANSLATION_CACHE_FILE , std : : ios : : binary | std : : ios : : trunc ) . write ( ( const char * ) allTranslations . c_str ( ) , allTranslations . size ( ) * sizeof ( wchar_t ) ) ;
savedSize = translationCache - > size ( ) ;
}
2019-06-29 16:29:33 +05:30
}
2020-03-16 02:56:04 -06:00
class Window : public QDialog
2019-06-13 04:01:29 -04:00
{
2020-03-16 02:56:04 -06:00
public :
Window ( ) :
QDialog ( nullptr , Qt : : WindowMinMaxButtonsHint )
2019-06-13 04:01:29 -04:00
{
2021-01-15 06:07:23 -07:00
Localize ( ) ;
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-18 18:58:21 -07:00
auto languageCombo = new QComboBox ( this ) ;
languageCombo - > addItems ( languages ) ;
2020-03-17 22:01:16 -06:00
int language = - 1 ;
2021-01-18 18:58:21 -07:00
if ( settings . contains ( LANGUAGE ) ) language = languageCombo - > findText ( settings . value ( LANGUAGE ) . toString ( ) , Qt : : MatchEndsWith ) ;
if ( language < 0 ) language = languageCombo - > findText ( NATIVE_LANGUAGE , Qt : : MatchStartsWith ) ;
if ( language < 0 ) language = languageCombo - > findText ( " English " , Qt : : MatchStartsWith ) ;
languageCombo - > setCurrentIndex ( language ) ;
saveLanguage ( languageCombo - > currentText ( ) ) ;
display - > addRow ( TRANSLATE_TO , languageCombo ) ;
connect ( languageCombo , & QComboBox : : currentTextChanged , this , & Window : : saveLanguage ) ;
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 } ,
2020-03-26 05:41:21 -06:00
{ rateLimitAll , RATE_LIMIT_ALL_THREADS } ,
{ rateLimitSelected , RATE_LIMIT_SELECTED_THREAD } ,
{ useCache , USE_TRANS_CACHE } ,
} )
{
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 * > {
{ tokenCount , RATE_LIMIT_TOKEN_COUNT } ,
{ tokenRestoreDelay , RATE_LIMIT_TOKEN_RESTORE_DELAY } ,
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 ) ;
authKey - > assign ( S ( keyEdit - > text ( ) ) ) ;
QObject : : connect ( keyEdit , & QLineEdit : : textChanged , [ ] ( QString key ) { settings . setValue ( API_KEY , S ( authKey - > assign ( 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-16 19:43:59 -04:00
2020-02-25 04:39:27 -07:00
std : : ifstream stream ( TRANSLATION_CACHE_FILE , std : : ios : : binary ) ;
BlockMarkupIterator savedTranslations ( stream , Array < std : : wstring_view > { L " |SENTENCE| " , L " |TRANSLATION| " } ) ;
auto translationCache = : : translationCache . Acquire ( ) ;
while ( auto read = savedTranslations . Next ( ) )
{
2020-02-27 04:42:29 -07:00
auto & [ sentence , translation ] = read . value ( ) ;
translationCache - > try_emplace ( std : : move ( sentence ) , std : : move ( translation ) ) ;
2020-02-25 04:39:27 -07:00
}
2019-06-29 16:29:33 +05:30
savedSize = translationCache - > size ( ) ;
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 :
void saveLanguage ( QString language )
{
settings . setValue ( LANGUAGE , S ( translateTo - > assign ( S ( language . split ( " : " ) [ 1 ] ) ) ) ) ;
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 ( )
{
auto tokens = this - > tokens . Acquire ( ) ;
tokens - > push_back ( GetTickCount ( ) ) ;
if ( tokens - > size ( ) > tokenCount * 5 ) tokens - > erase ( tokens - > begin ( ) , tokens - > begin ( ) + tokenCount * 3 ) ;
2020-03-26 05:41:21 -06:00
tokens - > erase ( std : : remove_if ( tokens - > begin ( ) , tokens - > end ( ) , [ ] ( DWORD token ) { return GetTickCount ( ) - token > tokenRestoreDelay ; } ) , tokens - > end ( ) ) ;
2019-06-13 04:01:29 -04:00
return tokens - > size ( ) < tokenCount ;
}
private :
Synchronized < std : : vector < DWORD > > tokens ;
} rateLimiter ;
2021-01-15 06:07:23 -07:00
auto StripWhitespace = [ ] ( std : : wstring & text )
{
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-01-15 06:07:23 -07:00
StripWhitespace ( sentence ) ;
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 " ] ) )
2020-04-25 20:39:12 -06:00
if ( rateLimiter . Request ( ) | | ! rateLimitAll | | ( ! rateLimitSelected & & sentenceInfo [ " current select " ] ) ) std : : tie ( cache , translation ) = Translate ( sentence ) ;
2020-03-26 05:41:21 -06:00
else translation = TOO_MANY_TRANS_REQUESTS ;
2021-01-15 06:07:23 -07:00
StripWhitespace ( 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 ;
}
2020-12-14 06:26:01 -07:00
TEST ( assert ( Translate ( L " こんにちは " ) . second . find ( L " ello " ) ! = std : : string : : npos ) ) ;