Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 79 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
MicrosoftWebService | |
0.00% |
0 / 79 |
|
0.00% |
0 / 8 |
306 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
mapCode | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
doPairs | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
42 | |||
getQuery | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
20 | |||
parseResponse | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
wrapUntranslatable | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
unwrapUntranslatable | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\WebService; |
5 | |
6 | use MediaWiki\Http\HttpRequestFactory; |
7 | |
8 | /** |
9 | * Implements support for Microsoft translation api v3. |
10 | * @author Niklas Laxström |
11 | * @author Ulrich Strauss |
12 | * @license GPL-2.0-or-later |
13 | * @since 2013-01-01 |
14 | * @see https://docs.microsoft.com/fi-fi/azure/cognitive-services/Translator/reference/v3-0-reference |
15 | * @ingroup TranslationWebService |
16 | */ |
17 | class MicrosoftWebService extends TranslationWebService { |
18 | private HttpRequestFactory $httpRequestFactory; |
19 | |
20 | public function __construct( |
21 | HttpRequestFactory $httpRequestFactory, |
22 | string $serviceName, |
23 | array $config |
24 | ) { |
25 | parent::__construct( $serviceName, $config ); |
26 | $this->httpRequestFactory = $httpRequestFactory; |
27 | } |
28 | |
29 | /** @inheritDoc */ |
30 | public function getType(): string { |
31 | return 'mt'; |
32 | } |
33 | |
34 | /** @inheritDoc */ |
35 | protected function mapCode( string $code ): string { |
36 | $map = [ |
37 | 'tl' => 'fil', |
38 | 'zh-hant' => 'zh-Hant', |
39 | 'zh-hans' => 'zh-Hans', |
40 | 'sr-ec' => 'sr-Cyrl', |
41 | 'sr-el' => 'sr-Latn', |
42 | 'pt-br' => 'pt', |
43 | ]; |
44 | |
45 | return $map[$code] ?? $code; |
46 | } |
47 | |
48 | /** @inheritDoc */ |
49 | protected function doPairs(): array { |
50 | if ( !isset( $this->config['key'] ) ) { |
51 | throw new TranslationWebServiceConfigurationException( 'key is not set' ); |
52 | } |
53 | |
54 | $key = $this->config['key']; |
55 | |
56 | $options = []; |
57 | $options['method'] = 'GET'; |
58 | $options['timeout'] = $this->config['timeout']; |
59 | |
60 | $url = $this->config['url'] . '/languages?api-version=3.0'; |
61 | |
62 | $req = $this->httpRequestFactory->create( $url, $options, __METHOD__ ); |
63 | $req->setHeader( 'Ocp-Apim-Subscription-Key', $key ); |
64 | |
65 | $status = $req->execute(); |
66 | if ( !$status->isOK() ) { |
67 | $error = $req->getContent(); |
68 | // Most likely a timeout or other general error |
69 | throw new TranslationWebServiceException( |
70 | 'HttpRequestFactory::get failed:' . serialize( $error ) . serialize( $status ) |
71 | ); |
72 | } |
73 | |
74 | $json = $req->getContent(); |
75 | $response = json_decode( $json, true ); |
76 | if ( !isset( $response[ 'translation' ] ) ) { |
77 | throw new TranslationWebServiceException( |
78 | 'Unable to fetch list of available languages: ' . $json |
79 | ); |
80 | } |
81 | |
82 | $languages = array_keys( $response[ 'translation' ] ); |
83 | |
84 | // Let's make a cartesian product, assuming we can translate from any language to any language |
85 | $pairs = []; |
86 | foreach ( $languages as $from ) { |
87 | foreach ( $languages as $to ) { |
88 | $pairs[$from][$to] = true; |
89 | } |
90 | } |
91 | |
92 | return $pairs; |
93 | } |
94 | |
95 | /** @inheritDoc */ |
96 | protected function getQuery( string $text, string $sourceLanguage, string $targetLanguage ): TranslationQuery { |
97 | if ( !isset( $this->config['key'] ) ) { |
98 | throw new TranslationWebServiceConfigurationException( 'key is not set' ); |
99 | } |
100 | |
101 | $key = $this->config['key']; |
102 | $text = trim( $text ); |
103 | $text = $this->wrapUntranslatable( $text ); |
104 | |
105 | $url = $this->config['url'] . '/translate'; |
106 | $params = [ |
107 | 'api-version' => '3.0', |
108 | 'from' => $sourceLanguage, |
109 | 'to' => $targetLanguage, |
110 | 'textType' => 'html', |
111 | ]; |
112 | $headers = [ |
113 | 'Ocp-Apim-Subscription-Key' => $key, |
114 | 'Content-Type' => 'application/json', |
115 | ]; |
116 | $body = json_encode( [ [ 'Text' => $text ] ] ); |
117 | |
118 | if ( $body === false ) { |
119 | throw new TranslationWebServiceInvalidInputException( 'Could not JSON encode source text' ); |
120 | } |
121 | |
122 | if ( strlen( $body ) > 5000 ) { |
123 | throw new TranslationWebServiceInvalidInputException( 'Source text too long' ); |
124 | } |
125 | |
126 | return TranslationQuery::factory( $url ) |
127 | ->timeout( intval( $this->config['timeout'] ) ) |
128 | ->queryParameters( $params ) |
129 | ->queryHeaders( $headers ) |
130 | ->postWithData( $body ); |
131 | } |
132 | |
133 | /** @inheritDoc */ |
134 | protected function parseResponse( TranslationQueryResponse $reply ): string { |
135 | $body = $reply->getBody(); |
136 | |
137 | $response = json_decode( $body, true ); |
138 | if ( !isset( $response[ 0 ][ 'translations' ][ 0 ][ 'text' ] ) ) { |
139 | throw new TranslationWebServiceException( |
140 | 'Unable to parse translation response: ' . $body |
141 | ); |
142 | } |
143 | |
144 | $text = $response[ 0 ][ 'translations' ][ 0 ][ 'text' ]; |
145 | $text = $this->unwrapUntranslatable( $text ); |
146 | |
147 | return $text; |
148 | } |
149 | |
150 | /** @inheritDoc */ |
151 | protected function wrapUntranslatable( string $text ): string { |
152 | $pattern = '~%[^% ]+%|\$\d|{VAR:[^}]+}|{?{(PLURAL|GRAMMAR|GENDER):[^|]+\||%(\d\$)?[sd]~'; |
153 | $wrap = '<span class="notranslate">\0</span>'; |
154 | return preg_replace( $pattern, $wrap, $text ); |
155 | } |
156 | |
157 | /** @inheritDoc */ |
158 | protected function unwrapUntranslatable( string $text ): string { |
159 | $pattern = '~<span class="notranslate">\s*(.*?)\s*</span>~'; |
160 | return preg_replace( $pattern, '\1', $text ); |
161 | } |
162 | } |