2019-06-29 18:59:33 +08:00
# include " qtcommon.h "
# include "extension.h"
2020-02-25 19:39:27 +08:00
# include "blockmarkup.h"
2019-06-13 16:01:29 +08:00
# include "network.h"
2020-02-27 19:42:29 +08:00
# include <map>
2020-02-25 19:39:27 +08:00
# include <fstream>
2020-03-16 16:56:04 +08:00
# include <QComboBox>
2019-06-13 16:01:29 +08:00
2020-02-25 19:39:27 +08:00
extern const char * NATIVE_LANGUAGE ;
2020-03-16 16:56:04 +08:00
extern const char * TRANSLATE_TO ;
2020-03-27 18:07:05 +08:00
extern const char * TRANSLATE_SELECTED_THREAD_ONLY ;
2020-03-26 19:41:21 +08: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-30 10:55:12 +08:00
extern const char * MAX_SENTENCE_SIZE ;
extern const char * API_KEY ;
2019-06-13 16:01:29 +08:00
extern const wchar_t * TOO_MANY_TRANS_REQUESTS ;
extern const char * TRANSLATION_PROVIDER ;
2020-03-30 10:55:12 +08:00
extern const char * GET_API_KEY_FROM ;
2019-06-13 16:01:29 +08:00
extern QStringList languages ;
2020-03-27 18:07:05 +08:00
extern bool translateSelectedOnly , rateLimitAll , rateLimitSelected , useCache ;
2020-03-30 10:55:12 +08:00
extern int tokenCount , tokenRestoreDelay , maxSentenceSize ;
2020-04-26 10:39:12 +08:00
std : : pair < bool , std : : wstring > Translate ( const std : : wstring & text ) ;
2019-06-13 16:01:29 +08:00
2019-08-12 22:44:51 +08:00
const char * LANGUAGE = u8 " Language " ;
2020-03-16 16:56:04 +08:00
const std : : string TRANSLATION_CACHE_FILE = FormatString ( " %s Cache.txt " , TRANSLATION_PROVIDER ) ;
2019-08-12 22:44:51 +08:00
2020-03-16 16:56:04 +08:00
QFormLayout * display ;
2020-03-18 12:01:16 +08:00
QSettings settings = openSettings ( ) ;
2020-03-30 10:55:12 +08:00
Synchronized < std : : wstring > translateTo = L " en " , apiKey ;
2020-02-25 19:39:27 +08:00
2020-02-27 19:42:29 +08:00
Synchronized < std : : map < std : : wstring , std : : wstring > > translationCache ;
2019-06-29 18:59:33 +08:00
int savedSize ;
2019-06-17 07:43:59 +08:00
2019-06-29 18:59:33 +08:00
void SaveCache ( )
{
2020-02-25 19:39:27 +08:00
std : : wstring allTranslations ( L " \xfeff " ) ;
2020-02-27 19:42:29 +08:00
for ( const auto & [ sentence , translation ] : translationCache . Acquire ( ) . contents )
allTranslations . append ( L " |SENTENCE| " ) . append ( sentence ) . append ( L " |TRANSLATION| " ) . append ( translation ) . append ( L " |END| \r \n " ) ;
2020-02-25 19:39:27 +08:00
std : : ofstream ( TRANSLATION_CACHE_FILE , std : : ios : : binary | std : : ios : : trunc ) . write ( ( const char * ) allTranslations . c_str ( ) , allTranslations . size ( ) * sizeof ( wchar_t ) ) ;
2019-06-29 18:59:33 +08:00
savedSize = translationCache - > size ( ) ;
}
2020-03-16 16:56:04 +08:00
class Window : public QDialog
2019-06-13 16:01:29 +08:00
{
2020-03-16 16:56:04 +08:00
public :
Window ( ) :
QDialog ( nullptr , Qt : : WindowMinMaxButtonsHint )
2019-06-13 16:01:29 +08:00
{
2020-03-18 12:01:16 +08:00
display = new QFormLayout ( this ) ;
2020-03-16 16:56:04 +08:00
2019-08-12 22:44:51 +08:00
settings . beginGroup ( TRANSLATION_PROVIDER ) ;
2020-03-16 16:56:04 +08:00
2020-03-18 12:01:16 +08:00
auto languageBox = new QComboBox ( this ) ;
languageBox - > addItems ( languages ) ;
int language = - 1 ;
if ( settings . contains ( LANGUAGE ) ) language = languageBox - > findText ( settings . value ( LANGUAGE ) . toString ( ) , Qt : : MatchEndsWith ) ;
if ( language < 0 ) language = languageBox - > findText ( NATIVE_LANGUAGE , Qt : : MatchStartsWith ) ;
if ( language < 0 ) language = languageBox - > findText ( " English " , Qt : : MatchStartsWith ) ;
languageBox - > setCurrentIndex ( language ) ;
saveLanguage ( languageBox - > currentText ( ) ) ;
display - > addRow ( TRANSLATE_TO , languageBox ) ;
2020-03-26 19:41:21 +08:00
connect ( languageBox , & QComboBox : : currentTextChanged , this , & Window : : saveLanguage ) ;
for ( auto [ value , label ] : Array < bool & , const char * > {
2020-03-27 18:07:05 +08:00
{ translateSelectedOnly , TRANSLATE_SELECTED_THREAD_ONLY } ,
2020-03-26 19:41:21 +08: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-30 10:55:12 +08:00
{ maxSentenceSize , MAX_SENTENCE_SIZE } ,
2020-03-26 19:41:21 +08: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-30 10:55:12 +08:00
if ( GET_API_KEY_FROM )
{
auto keyInput = new QLineEdit ( settings . value ( API_KEY ) . toString ( ) ) ;
apiKey - > assign ( S ( keyInput - > text ( ) ) ) ;
QObject : : connect ( keyInput , & QLineEdit : : textChanged , [ ] ( QString key ) { settings . setValue ( API_KEY , S ( apiKey - > assign ( S ( key ) ) ) ) ; } ) ;
auto keyLabel = new QLabel ( QString ( " <a href= \" %1 \" >%2</a> " ) . arg ( GET_API_KEY_FROM , API_KEY ) , this ) ;
keyLabel - > setOpenExternalLinks ( true ) ;
display - > addRow ( keyLabel , keyInput ) ;
}
2020-03-16 16:56:04 +08:00
setWindowTitle ( TRANSLATION_PROVIDER ) ;
QMetaObject : : invokeMethod ( this , & QWidget : : show , Qt : : QueuedConnection ) ;
2019-06-17 07:43:59 +08:00
2020-02-25 19:39:27 +08: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 19:42:29 +08:00
auto & [ sentence , translation ] = read . value ( ) ;
translationCache - > try_emplace ( std : : move ( sentence ) , std : : move ( translation ) ) ;
2020-02-25 19:39:27 +08:00
}
2019-06-29 18:59:33 +08:00
savedSize = translationCache - > size ( ) ;
2019-06-13 16:01:29 +08:00
}
2020-03-16 16:56:04 +08:00
~ Window ( )
2019-06-13 16:01:29 +08:00
{
2019-06-29 18:59:33 +08:00
SaveCache ( ) ;
2019-06-13 16:01:29 +08:00
}
2020-03-16 16:56:04 +08:00
private :
void saveLanguage ( QString language )
{
settings . setValue ( LANGUAGE , S ( translateTo - > assign ( S ( language . split ( " : " ) [ 1 ] ) ) ) ) ;
2019-06-13 16:01:29 +08:00
}
2020-03-16 16:56:04 +08:00
} window ;
2019-06-13 16:01:29 +08:00
bool ProcessSentence ( std : : wstring & sentence , SentenceInfo sentenceInfo )
{
2020-03-30 10:55:12 +08:00
if ( sentenceInfo [ " text number " ] = = 0 | | sentence . size ( ) > maxSentenceSize ) return false ;
2019-06-13 16:01:29 +08: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 19:41:21 +08:00
tokens - > erase ( std : : remove_if ( tokens - > begin ( ) , tokens - > end ( ) , [ ] ( DWORD token ) { return GetTickCount ( ) - token > tokenRestoreDelay ; } ) , tokens - > end ( ) ) ;
2019-06-13 16:01:29 +08:00
return tokens - > size ( ) < tokenCount ;
}
private :
Synchronized < std : : vector < DWORD > > tokens ;
} rateLimiter ;
bool cache = false ;
std : : wstring translation ;
2020-03-26 19:41:21 +08:00
if ( useCache )
2019-06-29 18:59:33 +08:00
{
2020-02-25 19:39:27 +08:00
auto translationCache = : : translationCache . Acquire ( ) ;
2020-03-27 18:07:05 +08: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 18:59:33 +08:00
}
2020-03-27 18:07:05 +08:00
if ( translation . empty ( ) & & ( ! translateSelectedOnly | | sentenceInfo [ " current select " ] ) )
2020-04-26 10:39:12 +08:00
if ( rateLimiter . Request ( ) | | ! rateLimitAll | | ( ! rateLimitSelected & & sentenceInfo [ " current select " ] ) ) std : : tie ( cache , translation ) = Translate ( sentence ) ;
2020-03-26 19:41:21 +08:00
else translation = TOO_MANY_TRANS_REQUESTS ;
if ( cache ) translationCache - > try_emplace ( sentence , translation ) ;
2020-02-25 19:39:27 +08:00
if ( cache & & translationCache - > size ( ) > savedSize + 50 ) SaveCache ( ) ;
2019-06-13 16:01:29 +08:00
2020-03-30 10:55:12 +08:00
JSON : : Unescape ( translation ) ;
2019-06-13 16:01:29 +08:00
sentence + = L " \n " + translation ;
return true ;
}
2020-03-30 10:55:12 +08:00
TEST ( assert ( Translate ( L " こんにちは " ) . second . find ( L " ello " ) ! = std : : wstring : : npos ) ) ;