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 ;
2021-01-21 22:07:09 +08:00
extern const char * TRANSLATE_FROM ;
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 ;
2021-03-10 12:32:56 +08:00
extern const char * FILTER_GARBAGE ;
2020-03-26 19:41:21 +08:00
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 ;
2021-01-21 22:07:09 +08:00
extern std : : wstring autoDetectLanguage ;
2021-03-10 12:32:56 +08:00
extern bool translateSelectedOnly , rateLimitAll , rateLimitSelected , useCache , useFilter ;
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
2021-01-21 22:07:09 +08:00
// backwards compatibility
2019-08-12 22:44:51 +08:00
const char * LANGUAGE = u8 " Language " ;
2020-12-14 21:26:01 +08:00
const std : : string TRANSLATION_CACHE_FILE = FormatString ( " %s Translation Cache.txt " , TRANSLATION_PROVIDER ) ;
2019-08-12 22:44:51 +08:00
2020-03-16 16:56:04 +08:00
QFormLayout * display ;
2020-11-02 21:27:21 +08:00
Settings settings ;
2021-01-21 22:07:09 +08:00
Synchronized < std : : wstring > translateTo = L " en " , translateFrom = L " auto " , authKey ;
2020-02-25 19:39:27 +08:00
2021-01-15 21:07:23 +08:00
namespace
2019-06-29 18:59:33 +08:00
{
2021-01-15 21:07:23 +08: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 18:59:33 +08:00
}
2021-01-21 22:07:09 +08:00
class Window : public QDialog , Localizer
2019-06-13 16:01:29 +08:00
{
2020-03-16 16:56:04 +08:00
public :
2021-01-31 03:07:37 +08:00
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
2021-01-21 22:07:09 +08:00
auto translateToCombo = new QComboBox ( this ) ;
translateToCombo - > addItems ( languages ) ;
2020-03-18 12:01:16 +08:00
int language = - 1 ;
2021-01-21 22:07:09 +08:00
if ( settings . contains ( LANGUAGE ) ) language = translateToCombo - > findText ( settings . value ( LANGUAGE ) . toString ( ) , Qt : : MatchEndsWith ) ;
if ( settings . contains ( TRANSLATE_TO ) ) language = translateToCombo - > findText ( settings . value ( TRANSLATE_TO ) . toString ( ) , Qt : : MatchEndsWith ) ;
if ( language < 0 ) language = translateToCombo - > findText ( NATIVE_LANGUAGE , Qt : : MatchStartsWith ) ;
if ( language < 0 ) language = translateToCombo - > findText ( " English " , Qt : : MatchStartsWith ) ;
translateToCombo - > setCurrentIndex ( language ) ;
SaveTranslateTo ( translateToCombo - > currentText ( ) ) ;
display - > addRow ( TRANSLATE_TO , translateToCombo ) ;
connect ( translateToCombo , & QComboBox : : currentTextChanged , this , & Window : : SaveTranslateTo ) ;
languages . push_front ( " ?: " + S ( autoDetectLanguage ) ) ;
auto translateFromCombo = new QComboBox ( this ) ;
translateFromCombo - > addItems ( languages ) ;
language = - 1 ;
if ( settings . contains ( TRANSLATE_FROM ) ) language = translateFromCombo - > findText ( settings . value ( TRANSLATE_FROM ) . toString ( ) , Qt : : MatchEndsWith ) ;
if ( language < 0 ) language = translateFromCombo - > findText ( " ? " , Qt : : MatchStartsWith ) ;
translateFromCombo - > setCurrentIndex ( language ) ;
SaveTranslateFrom ( translateFromCombo - > currentText ( ) ) ;
display - > addRow ( TRANSLATE_FROM , translateFromCombo ) ;
connect ( translateFromCombo , & QComboBox : : currentTextChanged , this , & Window : : SaveTranslateFrom ) ;
2020-03-26 19:41:21 +08:00
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 } ,
2021-03-10 12:32:56 +08:00
{ useFilter , FILTER_GARBAGE }
2020-03-26 19:41:21 +08: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 * > {
{ 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 )
{
2021-01-19 09:58:21 +08: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-30 10:55:12 +08:00
auto keyLabel = new QLabel ( QString ( " <a href= \" %1 \" >%2</a> " ) . arg ( GET_API_KEY_FROM , API_KEY ) , this ) ;
keyLabel - > setOpenExternalLinks ( true ) ;
2021-01-19 09:58:21 +08:00
display - > addRow ( keyLabel , keyEdit ) ;
2020-03-30 10:55:12 +08:00
}
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 :
2021-01-21 22:07:09 +08:00
void SaveTranslateTo ( QString language )
2020-03-16 16:56:04 +08:00
{
2021-01-21 22:07:09 +08:00
settings . setValue ( TRANSLATE_TO , S ( translateTo - > assign ( S ( language . split ( " : " ) [ 1 ] ) ) ) ) ;
}
void SaveTranslateFrom ( QString language )
{
settings . setValue ( TRANSLATE_FROM , S ( translateFrom - > 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 ;
2021-03-10 12:32:56 +08:00
auto Trim = [ ] ( std : : wstring & text )
2021-01-15 21:07:23 +08: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 16:01:29 +08:00
bool cache = false ;
std : : wstring translation ;
2021-03-10 12:32:56 +08:00
if ( useFilter )
{
Trim ( sentence ) ;
sentence . erase ( std : : find_if ( sentence . begin ( ) , sentence . end ( ) , [ ] ( wchar_t ch ) { return ch < ' ' & & ch ! = ' \n ' ; } ) , sentence . end ( ) ) ;
}
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 ;
2021-03-10 12:32:56 +08:00
if ( useFilter ) Trim ( translation ) ;
2020-03-26 19:41:21 +08:00
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-12-14 21:26:01 +08: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-15 10:10:47 +08:00
if ( ! translation . empty ( ) ) ( sentence + = L " \x200b \n " ) + = translation ;
2019-06-13 16:01:29 +08:00
return true ;
}
2020-12-14 21:26:01 +08:00
TEST ( assert ( Translate ( L " こんにちは " ) . second . find ( L " ello " ) ! = std : : string : : npos ) ) ;