Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1427317
TranslationWebService.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
TranslationWebService.php
View Options
<?php
declare
(
strict_types
=
1
);
namespace
MediaWiki\Extension\Translate\WebService
;
use
Exception
;
use
MediaWiki\Logger\LoggerFactory
;
use
MediaWiki\MediaWikiServices
;
use
Psr\Log\LoggerAwareInterface
;
use
Psr\Log\LoggerInterface
;
use
Wikimedia\ObjectCache\BagOStuff
;
/**
* Multipurpose class:
* - 1) Interface for web services.
* - 2) Source text picking logic.
* - 3) Factory class.
* - 4) Service failure tracking and suspending.
* @author Niklas Laxström
* @license GPL-2.0-or-later
* @since 2013-01-01
* @defgroup TranslationWebService Translation Web Services
*/
abstract
class
TranslationWebService
implements
LoggerAwareInterface
{
private
?
BagOStuff
$cache
;
/* Public api */
/**
* Get a webservice handler.
* @see $wgTranslateTranslationServices
*/
public
static
function
factory
(
string
$serviceName
,
array
$config
):
?
TranslationWebService
{
$handlers
=
[
'microsoft'
=>
[
'class'
=>
MicrosoftWebService
::
class
,
'deps'
=>
[
'HttpRequestFactory'
]
],
'apertium'
=>
[
'class'
=>
ApertiumWebService
::
class
,
'deps'
=>
[
'HttpRequestFactory'
]
],
'yandex'
=>
[
'class'
=>
YandexWebService
::
class
,
'deps'
=>
[
'HttpRequestFactory'
]
],
'google'
=>
[
'class'
=>
GoogleTranslateWebService
::
class
,
'deps'
=>
[
'HttpRequestFactory'
]
],
'remote-ttmserver'
=>
[
'class'
=>
RemoteTTMServerWebService
::
class
],
'cxserver'
=>
[
'class'
=>
ApertiumCxserverWebService
::
class
,
'deps'
=>
[
'HttpRequestFactory'
]
],
'caighdean'
=>
[
'class'
=>
CaighdeanWebService
::
class
],
'mint'
=>
[
'class'
=>
MintCxserverWebService
::
class
,
'deps'
=>
[
'HttpRequestFactory'
]
]
];
if
(
!
isset
(
$config
[
'timeout'
]
)
)
{
$config
[
'timeout'
]
=
3
;
}
$serviceDetails
=
$handlers
[
$config
[
'type'
]]
??
null
;
if
(
$serviceDetails
)
{
$objectFactory
=
MediaWikiServices
::
getInstance
()->
getObjectFactory
();
$spec
=
[
'class'
=>
$serviceDetails
[
'class'
],
'args'
=>
[
$serviceName
,
$config
],
'services'
=>
$serviceDetails
[
'deps'
]
??
[],
];
// @phan-suppress-next-line PhanTypeInvalidCallableArraySize due to annotations on createObject?
$serviceObject
=
$objectFactory
->
createObject
(
$spec
);
if
(
$serviceObject
instanceof
LoggerAwareInterface
)
{
$serviceObject
->
setLogger
(
LoggerFactory
::
getInstance
(
'translationservices'
)
);
}
return
$serviceObject
;
}
return
null
;
}
/**
* Gets the name of this service, for example to display it for the user.
* @since 2014.02
*/
public
function
getName
():
string
{
return
$this
->
service
;
}
/**
* Get queries for this service. Queries from multiple services can be
* collected and run asynchronously with QueryAggregator.
* @return TranslationQuery[]
* @since 2015.12
* @throws TranslationWebServiceConfigurationException
*/
public
function
getQueries
(
string
$text
,
string
$sourceLanguage
,
string
$targetLanguage
):
array
{
$from
=
$this
->
mapCode
(
$sourceLanguage
);
$to
=
$this
->
mapCode
(
$targetLanguage
);
try
{
return
[
$this
->
getQuery
(
$text
,
$from
,
$to
)
];
}
catch
(
TranslationWebServiceException
$e
)
{
$this
->
reportTranslationServiceFailure
(
$e
->
getMessage
()
);
return
[];
}
catch
(
TranslationWebServiceInvalidInputException
$e
)
{
// Not much we can do about this, just ignore.
return
[];
}
}
/**
* Get the web service specific response returned by QueryAggregator.
* @return mixed|null Returns null on error.
* @since 2015.12
*/
public
function
getResultData
(
TranslationQueryResponse
$response
)
{
if
(
$response
->
getStatusCode
()
!==
200
)
{
$this
->
reportTranslationServiceFailure
(
'STATUS: '
.
$response
->
getStatusMessage
()
.
"
\n
"
.
'BODY: '
.
$response
->
getBody
()
);
return
null
;
}
try
{
return
$this
->
parseResponse
(
$response
);
}
catch
(
TranslationWebServiceException
$e
)
{
$this
->
reportTranslationServiceFailure
(
$e
->
getMessage
()
);
return
null
;
}
catch
(
TranslationWebServiceInvalidInputException
$e
)
{
// Not much we can do about this, just ignore.
return
null
;
}
}
/**
* Returns the type of this web service.
* @see \MediaWiki\Extension\Translate\TranslatorInterface\Aid\TranslationAid::getTypes
*/
abstract
public
function
getType
():
string
;
/* Service api */
/**
* Map a MediaWiki (almost standard) language code to the code used by the
* translation service.
*/
abstract
protected
function
mapCode
(
string
$code
):
string
;
/**
* Get the list of supported language pairs for the web service. The codes
* should be the ones used by the service. Caching is handled by the public
* getSupportedLanguagePairs.
* @return array $list[source language][target language] = true
* @throws TranslationWebServiceException
* @throws TranslationWebServiceConfigurationException
*/
abstract
protected
function
doPairs
():
array
;
/**
* Get the query. See getQueries for the public method.
* @param string $text Text to translate.
* @param string $sourceLanguage Language code of the text, as used by the service.
* @param string $targetLanguage Language code of the translation, as used by the service.
* @since 2015.02
* @throws TranslationWebServiceException
* @throws TranslationWebServiceConfigurationException
* @throws TranslationWebServiceInvalidInputException
*/
abstract
protected
function
getQuery
(
string
$text
,
string
$sourceLanguage
,
string
$targetLanguage
):
TranslationQuery
;
/**
* Get the response. See getResultData for the public method.
* @since 2015.02
* @throws TranslationWebServiceException
*/
abstract
protected
function
parseResponse
(
TranslationQueryResponse
$response
);
/* Default implementation */
/** @var string Name of this webservice. */
protected
$service
;
/** @var array */
protected
$config
;
/** @var LoggerInterface */
protected
$logger
;
public
function
__construct
(
string
$service
,
array
$config
)
{
$this
->
service
=
$service
;
$this
->
config
=
$config
;
}
/**
* Test whether given language pair is supported by the service.
* @since 2015.12
* @throws TranslationWebServiceConfigurationException
*/
public
function
isSupportedLanguagePair
(
string
$sourceLanguage
,
string
$targetLanguage
):
bool
{
$pairs
=
$this
->
getSupportedLanguagePairs
();
$from
=
$this
->
mapCode
(
$sourceLanguage
);
$to
=
$this
->
mapCode
(
$targetLanguage
);
return
isset
(
$pairs
[
$from
][
$to
]
);
}
/**
* @see self::doPairs
* @throws TranslationWebServiceConfigurationException
*/
protected
function
getSupportedLanguagePairs
():
array
{
$cache
=
$this
->
getObjectCache
();
return
$cache
->
getWithSetCallback
(
$cache
->
makeKey
(
'translate-tmsug-pairs-'
.
$this
->
service
),
$cache
::
TTL_DAY
,
function
(
&
$ttl
)
use
(
$cache
)
{
try
{
$pairs
=
$this
->
doPairs
();
}
catch
(
Exception
$e
)
{
$pairs
=
[];
$this
->
reportTranslationServiceFailure
(
$e
->
getMessage
()
);
$ttl
=
$cache
::
TTL_UNCACHEABLE
;
}
return
$pairs
;
}
);
}
/**
* Some mangling that tries to keep some parts of the message unmangled
* by the translation service. Most of them support either class=notranslate
* or translate=no.
*/
protected
function
wrapUntranslatable
(
string
$text
):
string
{
$text
=
str_replace
(
"
\n
"
,
'!N!'
,
$text
);
$pattern
=
'~%[^% ]+%|
\$\d
|{VAR:[^}]+}|{?{(PLURAL|GRAMMAR|GENDER):[^|]+
\|
|%(
\d\$
)?[sd]~'
;
$wrap
=
'<span class="notranslate" translate="no">
\0
</span>'
;
return
preg_replace
(
$pattern
,
$wrap
,
$text
);
}
/** Undo the hopyfully untouched mangling done by wrapUntranslatable. */
protected
function
unwrapUntranslatable
(
string
$text
):
string
{
$text
=
str_replace
(
'!N!'
,
"
\n
"
,
$text
);
$pattern
=
'~<span class="notranslate" translate="no">(.*?)</span>~'
;
return
preg_replace
(
$pattern
,
'
\1
'
,
$text
);
}
/* Failure handling and suspending */
public
function
setLogger
(
LoggerInterface
$logger
):
void
{
$this
->
logger
=
$logger
;
}
/**
* @var int How many failures during failure period need to happen to
* consider the service being temporarily off-line.
*/
protected
$serviceFailureCount
=
5
;
/**
* @var int How long after the last detected failure we clear the status and
* try again.
*/
protected
$serviceFailurePeriod
=
900
;
/** Checks whether the service has exceeded failure count */
public
function
checkTranslationServiceFailure
():
bool
{
$service
=
$this
->
service
;
$cache
=
$this
->
getObjectCache
();
$key
=
$cache
->
makeKey
(
"translate-service-$service"
);
$value
=
$cache
->
get
(
$key
);
if
(
!
is_string
(
$value
)
)
{
return
false
;
}
[
$count
,
$failed
]
=
explode
(
'|'
,
$value
,
2
);
$count
=
(
int
)
$count
;
$failed
=
(
int
)
$failed
;
$now
=
(
int
)
wfTimestamp
();
if
(
$failed
+
(
2
*
$this
->
serviceFailurePeriod
)
<
$now
)
{
if
(
$count
>=
$this
->
serviceFailureCount
)
{
$this
->
logger
->
warning
(
"Translation service $service (was) restored"
);
}
$cache
->
delete
(
$key
);
return
false
;
}
elseif
(
$failed
+
$this
->
serviceFailurePeriod
<
$now
)
{
/* We are in suspicious mode and one failure is enough to update
* failed timestamp. If the service works however, let's use it.
* Previous failures are forgotten after another failure period
* has passed */
return
false
;
}
// Check the failure count against the limit
return
$count
>=
$this
->
serviceFailureCount
;
}
/** Increases the failure count for this service */
protected
function
reportTranslationServiceFailure
(
string
$msg
):
void
{
$service
=
$this
->
service
;
$this
->
logger
->
warning
(
"Translation service $service problem: $msg"
);
$cache
=
$this
->
getObjectCache
();
$key
=
$cache
->
makeKey
(
"translate-service-$service"
);
$value
=
$cache
->
get
(
$key
);
if
(
!
is_string
(
$value
)
)
{
$count
=
0
;
}
else
{
[
$count
,
]
=
explode
(
'|'
,
$value
,
2
);
}
$count
++;
$failed
=
wfTimestamp
();
$cache
->
set
(
$key
,
"$count|$failed"
,
$this
->
serviceFailurePeriod
*
5
);
if
(
$count
===
$this
->
serviceFailureCount
)
{
$this
->
logger
->
error
(
"Translation service $service suspended"
);
}
elseif
(
$count
>
$this
->
serviceFailureCount
)
{
$this
->
logger
->
warning
(
"Translation service $service still suspended"
);
}
}
private
function
getObjectCache
():
BagOStuff
{
$this
->
cache
??=
MediaWikiServices
::
getInstance
()
->
getObjectCacheFactory
()
->
getInstance
(
CACHE_ANYTHING
);
return
$this
->
cache
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 14:28 (1 d, 6 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
cf/35/6f7aadee540b0b8b30168ac77aae
Default Alt Text
TranslationWebService.php (10 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment