Issues (238)

src/Services/GoogleTranslate.php (6 issues)

1
<?php
2
3
namespace Translation\Services;
4
5
use BadMethodCallException;
6
use ErrorException;
7
use GuzzleHttp\Client;
0 ignored issues
show
The type GuzzleHttp\Client was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use GuzzleHttp\Exception\RequestException;
0 ignored issues
show
The type GuzzleHttp\Exception\RequestException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Translation\Tokens\GoogleTokenGenerator;
10
use Translation\Tokens\TokenProviderInterface;
11
use UnexpectedValueException;
12
13
/**
14
 * Free Google Translate API PHP Package.
15
 *
16
 * @author  Levan Velijanashvili <[email protected]>
17
 * @link    http://stichoza.com/
18
 * @license MIT
19
 */
20
class GoogleTranslate
21
{
22
    /**
23
     * @var \GuzzleHttp\Client HTTP Client
24
     */
25
    protected $client;
26
27
    /**
28
     * @var string|null Source language - from where the string should be translated
29
     */
30
    protected $source;
31
32
    /**
33
     * @var string Target language - to which language string should be translated
34
     */
35
    protected $target;
36
37
    /**
38
     * @var string|null Last detected source language
39
     */
40
    protected $lastDetectedSource;
41
42
    /**
43
     * @var string Google Translate URL base
44
     */
45
    protected $url = 'https://translate.google.com/translate_a/single';
46
47
    /**
48
     * @var array Dynamic GuzzleHttp client options
49
     */
50
    protected $options = [];
51
52
    /**
53
     * @var array URL Parameters
54
     */
55
    protected $urlParams = [
56
        'client'   => 'webapp',
57
        'hl'       => 'en',
58
        'dt'       => [
59
            't',   // Translate
60
            'bd',  // Full translate with synonym ($bodyArray[1])
61
            'at',  // Other translate ($bodyArray[5] - in google translate page this shows when click on translated word)
62
            'ex',  // Example part ($bodyArray[13])
63
            'ld',  // I don't know ($bodyArray[8])
64
            'md',  // Definition part with example ($bodyArray[12])
65
            'qca', // I don't know ($bodyArray[8])
66
            'rw',  // Read also part ($bodyArray[14])
67
            'rm',  // I don't know
68
            'ss'   // Full synonym ($bodyArray[11])
69
        ],
70
        'sl'       => null, // Source language
71
        'tl'       => null, // Target language
72
        'q'        => null, // String to translate
73
        'ie'       => 'UTF-8', // Input encoding
74
        'oe'       => 'UTF-8', // Output encoding
75
        'multires' => 1,
76
        'otf'      => 0,
77
        'pc'       => 1,
78
        'trs'      => 1,
79
        'ssel'     => 0,
80
        'tsel'     => 0,
81
        'kc'       => 1,
82
        'tk'       => null,
83
    ];
84
85
    /**
86
     * @var array Regex key-value patterns to replace on response data
87
     */
88
    protected $resultRegexes = [
89
        '/,+/'  => ',',
90
        '/\[,/' => '[',
91
    ];
92
93
    /**
94
     * @var TokenProviderInterface Token provider
95
     */
96
    protected $tokenProvider;
97
98
    /**
99
     * Class constructor.
100
     *
101
     * For more information about HTTP client configuration options, see "Request Options" in
102
     * GuzzleHttp docs: http://docs.guzzlephp.org/en/stable/request-options.html
103
     *
104
     * @param string                      $target        Target language
105
     * @param string|null                 $source        Source language
106
     * @param array|null                  $options       Associative array of http client configuration options
107
     * @param TokenProviderInterface|null $tokenProvider
108
     */
109
    public function __construct(string $target = 'en', string $source = null, array $options = null, TokenProviderInterface $tokenProvider = null)
110
    {
111
        $this->client = new Client();
112
        $this->setTokenProvider($tokenProvider ?? new GoogleTokenGenerator)
113
            ->setOptions($options) // Options are already set in client constructor tho.
114
            ->setSource($source)
115
            ->setTarget($target);
116
    }
117
118
    /**
119
     * Set target language for translation.
120
     *
121
     * @param  string $target Language code
122
     * @return GoogleTranslate
123
     */
124
    public function setTarget(string $target) : self
125
    {
126
        $this->target = $target;
127
        return $this;
128
    }
129
130
    /**
131
     * Set source language for translation.
132
     *
133
     * @param  string|null $source Language code
134
     * @return GoogleTranslate
135
     */
136
    public function setSource(string $source = null) : self
137
    {
138
        $this->source = $source ?? 'auto';
139
        return $this;
140
    }
141
142
    /**
143
     * Set Google Translate URL base
144
     *
145
     * @param  string $url Google Translate URL base
146
     * @return GoogleTranslate
147
     */
148
    public function setUrl(string $url) : self
149
    {
150
        $this->url = $url;
151
        return $this;
152
    }
153
154
    /**
155
     * Set GuzzleHttp client options.
156
     *
157
     * @param  array $options guzzleHttp client options.
158
     * @return GoogleTranslate
159
     */
160
    public function setOptions(array $options = null) : self
161
    {
162
        $this->options = $options ?? [];
163
        return $this;
164
    }
165
166
    /**
167
     * Set token provider.
168
     *
169
     * @param  TokenProviderInterface $tokenProvider
170
     * @return GoogleTranslate
171
     */
172
    public function setTokenProvider(TokenProviderInterface $tokenProvider) : self
173
    {
174
        $this->tokenProvider = $tokenProvider;
175
        return $this;
176
    }
177
178
    /**
179
     * Get last detected source language
180
     *
181
     * @return string|null Last detected source language
182
     */
183
    public function getLastDetectedSource()
184
    {
185
        return $this->lastDetectedSource;
186
    }
187
188
    /**
189
     * Override translate method for static call.
190
     *
191
     * @param  string                      $string
192
     * @param  string                      $target
193
     * @param  string|null                 $source
194
     * @param  array                       $options
195
     * @param  TokenProviderInterface|null $tokenProvider
196
     * @return null|string
197
     * @throws ErrorException If the HTTP request fails
198
     * @throws UnexpectedValueException If received data cannot be decoded
199
     */
200
    public static function trans(string $string, string $target = 'en', string $source = null, array $options = [], TokenProviderInterface $tokenProvider = null)
201
    {
202
        return (new self)
203
            ->setTokenProvider($tokenProvider ?? new GoogleTokenGenerator)
204
            ->setOptions($options) // Options are already set in client constructor tho.
205
            ->setSource($source)
206
            ->setTarget($target)
207
            ->translate($string);
208
    }
209
210
    /**
211
     * Translate text.
212
     *
213
     * This can be called from instance method translate() using __call() magic method.
214
     * Use $instance->translate($string) instead.
215
     *
216
     * @param  string $string String to translate
217
     * @return string|null
218
     * @throws ErrorException           If the HTTP request fails
219
     * @throws UnexpectedValueException If received data cannot be decoded
220
     */
221
    public function translate(string $string) : string
222
    {
223
        $responseArray = $this->getResponse($string);
224
225
        /*
226
         * if response in text and the content has zero the empty returns true, lets check
227
         * if response is string and not empty and create array for further logic
228
         */
229
        if (is_string($responseArray) && $responseArray != '') {
0 ignored issues
show
The condition is_string($responseArray) is always false.
Loading history...
230
            $responseArray = [$responseArray];
231
        }
232
233
        // Check if translation exists
234
        if (!isset($responseArray[0]) || empty($responseArray[0])) {
235
            return null;
236
        }
237
238
        // Detect languages
239
        $detectedLanguages = [];
240
241
        // the response contains only single translation, don't create loop that will end with
242
        // invalid foreach and warning
243
        if (!is_string($responseArray)) {
0 ignored issues
show
The condition is_string($responseArray) is always false.
Loading history...
244
            foreach ($responseArray as $item) {
245
                if (is_string($item)) {
246
                    $detectedLanguages[] = $item;
247
                }
248
            }
249
        }
250
251
        // Another case of detected language
252
        if (isset($responseArray[count($responseArray) - 2][0][0])) {
253
            $detectedLanguages[] = $responseArray[count($responseArray) - 2][0][0];
254
        }
255
256
        // Set initial detected language to null
257
        $this->lastDetectedSource = null;
258
259
        // Iterate and set last detected language
260
        foreach ($detectedLanguages as $lang) {
261
            if ($this->isValidLocale($lang)) {
262
                $this->lastDetectedSource = $lang;
263
                break;
264
            }
265
        }
266
267
        // the response can be sometimes an translated string.
268
        if (is_string($responseArray)) {
0 ignored issues
show
The condition is_string($responseArray) is always false.
Loading history...
269
            return $responseArray;
270
        } else {
271
            if (is_array($responseArray[0])) {
272
                return (string) array_reduce(
273
                    $responseArray[0], function ($carry, $item) {
274
                        $carry .= $item[0];
275
                        return $carry;
276
                    }
277
                );
278
            } else {
279
                return (string) $responseArray[0];
280
            }
281
        }
282
    }
283
284
    /**
285
     * Get response array.
286
     *
287
     * @param  string $string String to translate
288
     * @throws ErrorException           If the HTTP request fails
289
     * @throws UnexpectedValueException If received data cannot be decoded
290
     * @return array|string Response
291
     */
292
    public function getResponse(string $string) : array
293
    {
294
        $queryArray = array_merge(
295
            $this->urlParams, [
296
            'sl'   => $this->source,
297
            'tl'   => $this->target,
298
            'tk'   => $this->tokenProvider->generateToken($this->source, $this->target, $string),
0 ignored issues
show
It seems like $this->source can also be of type null; however, parameter $source of Translation\Tokens\Token...erface::generateToken() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

298
            'tk'   => $this->tokenProvider->generateToken(/** @scrutinizer ignore-type */ $this->source, $this->target, $string),
Loading history...
299
            'q'    => $string
300
            ]
301
        );
302
303
        $queryUrl = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($queryArray));
304
305
        try {
306
            $response = $this->client->get(
307
                $this->url, [
308
                    'query' => $queryUrl,
309
                ] + $this->options
310
            );
311
        } catch (RequestException $e) {
312
            throw new ErrorException($e->getMessage());
313
        }
314
315
        $body = $response->getBody(); // Get response body
316
317
        // Modify body to avoid json errors
318
        $bodyJson = preg_replace(array_keys($this->resultRegexes), array_values($this->resultRegexes), $body);
319
320
        // Decode JSON data
321
        if (($bodyArray = json_decode($bodyJson, true)) === null) {
322
            throw new UnexpectedValueException('Data cannot be decoded or it is deeper than the recursion limit');
323
        }
324
325
        return $bodyArray;
326
    }
327
328
    /**
329
     * Check if given locale is valid.
330
     *
331
     * @param  string $lang Langauge code to verify
332
     * @return bool
333
     */
334
    protected function isValidLocale(string $lang) : bool
335
    {
336
        return (bool) preg_match('/^([a-z]{2})(-[A-Z]{2})?$/', $lang);
337
    }
338
}
339