Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
GoogleTranslateWebService
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 7
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 mapCode
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 isSupportedLanguagePair
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 doPairs
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
 getQuery
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 parseResponse
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\WebService;
5
6use FormatJson;
7use MediaWiki\Http\HttpRequestFactory;
8use Sanitizer;
9
10/**
11 * Implements support for Google Translate API
12 * @author Carsten Schmitz / LimeSurvey GmbH
13 * @license GPL-2.0-or-later
14 * @since 2020.05
15 * @ingroup TranslationWebService
16 * @see https://cloud.google.com/translate/docs/reference/rest
17 */
18class GoogleTranslateWebService extends TranslationWebService {
19    private const PUBLIC_API = 'https://translation.googleapis.com/language/translate/v2';
20    private HttpRequestFactory $httpRequestFactory;
21
22    public function __construct(
23        HttpRequestFactory $httpRequestFactory,
24        string $serviceName,
25        array $config
26    ) {
27        parent::__construct( $serviceName, $config );
28        $this->httpRequestFactory = $httpRequestFactory;
29    }
30
31    /** @inheritDoc */
32    public function getType(): string {
33        return 'mt';
34    }
35
36    /** @inheritDoc */
37    protected function mapCode( string $code ): string {
38        /** @phpcs-require-sorted-array */
39        $map = [
40            'be-tarask' => 'be',
41            'nb' => 'no',
42            'tw' => 'ak',
43            'zh-cn' => 'zh-CN',
44            'zh-hans' => 'zh-CN',
45            'zh-hant' => 'zh-TW',
46            'zh-tw' => 'zh-TW',
47        ];
48
49        return $map[$code] ?? $code;
50    }
51
52    /** @inheritDoc */
53    public function isSupportedLanguagePair( string $sourceLanguage, string $targetLanguage ): bool {
54        $pairs = $this->getSupportedLanguagePairs();
55        $from = $this->mapCode( $sourceLanguage );
56        $to = $this->mapCode( $targetLanguage );
57
58        // As long as the source & target language exist at Google it is fine
59        return isset( $pairs[$from] ) && isset( $pairs[$to] ) && $from !== $to;
60    }
61
62    /** @inheritDoc */
63    protected function doPairs(): array {
64        if ( !isset( $this->config['key'] ) ) {
65            throw new TranslationWebServiceConfigurationException( 'API key is not set' );
66        }
67
68        $api = $this->config['pairs'] ?? self::PUBLIC_API . '/languages';
69        $json = $this->httpRequestFactory->get(
70            wfAppendQuery( $api, [ 'key' => $this->config['key'], ] ),
71            [ 'timeout' => $this->config['timeout'] ],
72            __METHOD__
73        );
74        if ( $json === null ) {
75            throw new TranslationWebServiceException( 'Failure encountered when contacting remote server' );
76        }
77
78        $response = FormatJson::decode( $json );
79        if ( !is_object( $response ) ) {
80            throw new TranslationWebServiceException( 'Malformed reply from remote server: ' . $json );
81        }
82
83        $pairs = [];
84        foreach ( $response->data->languages as $language ) {
85            // Google can translate from any language to any language
86            $pairs[$language->language] = true;
87        }
88
89        return $pairs;
90    }
91
92    /** @inheritDoc */
93    protected function getQuery( string $text, string $sourceLanguage, string $targetLanguage ): TranslationQuery {
94        if ( !isset( $this->config['key'] ) ) {
95            throw new TranslationWebServiceConfigurationException( 'API key is not set' );
96        }
97        # https://cloud.google.com/translate/docs/reference/translate
98        if ( strlen( $text ) > 10000 ) {
99            // There is no limitation but we don't want the translation service to be abused, don't we?
100            throw new TranslationWebServiceInvalidInputException( 'Source text too long' );
101        }
102
103        $url = $this->config['url'] ?? self::PUBLIC_API;
104        $text = trim( $text );
105        $text = $this->wrapUntranslatable( $text );
106
107        return TranslationQuery::factory( $url )
108            ->timeout( intval( $this->config['timeout'] ?? 3 ) )
109            ->postWithData( wfArrayToCgi( [
110                'key' => $this->config['key'],
111                'q' => $text,
112                'target' => $targetLanguage,
113                'source' => $sourceLanguage,
114                'format' => 'html',
115            ] ) );
116    }
117
118    /** @inheritDoc */
119    protected function parseResponse( TranslationQueryResponse $response ): string {
120        $body = $response->getBody();
121        $responseBody = FormatJson::decode( $body );
122        if ( !is_object( $responseBody ) ) {
123            throw new TranslationWebServiceException( 'Invalid json: ' . serialize( $body ) );
124        }
125        $text = Sanitizer::decodeCharReferences( $responseBody->data->translations[0]->translatedText );
126        $text = $this->unwrapUntranslatable( $text );
127
128        return trim( $text );
129    }
130}