Completed
Pull Request — master (#68)
by André
02:51
created

Translator::getResponse()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 43
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 43
rs 8.439
c 0
b 0
f 0
cc 6
eloc 24
nc 7
nop 1
1
<?php
2
3
namespace Stichoza\GoogleTranslate;
4
5
use ErrorException;
6
use Exception;
7
use GuzzleHttp\Client as GuzzleHttpClient;
8
use GuzzleHttp\Exception\RequestException as GuzzleRequestException;
9
use InvalidArgumentException;
10
use Stichoza\GoogleTranslate\Tokens\GoogleTokenGenerator;
11
use Stichoza\GoogleTranslate\Tokens\TokenProviderInterface;
12
use UnexpectedValueException;
13
14
/**
15
 * Free Google Translate API PHP Package.
16
 *
17
 * @author      Levan Velijanashvili <[email protected]>
18
 *
19
 * @link        http://stichoza.com/
20
 *
21
 * @license     MIT
22
 *
23
 */
24
class Translator
25
{
26
    /**
27
     * @var \GuzzleHttp\Client HTTP Client
28
     */
29
    protected $httpClient;
30
31
    /**
32
     * @var string Source language - from where the string should be translated
33
     */
34
    protected $sourceLanguage;
35
36
    /**
37
     * @var string Target language - to which language string should be translated
38
     */
39
    protected $targetLanguage;
40
41
    /**
42
     * @var string|bool Last detected source language
43
     */
44
    protected $lastDetectedSource = false;
45
46
    /**
47
     * @var string Google Translate URL base
48
     */
49
    private $urlBase = 'http://translate.google.com/translate_a/single';
50
51
    /**
52
     * @var array Dynamic guzzleHTTP client options
53
     */
54
    protected $httpOptions = [];
55
56
    /**
57
     * @var array URL Parameters
58
     */
59
    protected $urlParams = [
60
        'client'   => 't',
61
        'hl'       => 'en',
62
        'dt'       => 't',
63
        'sl'       => null, // Source language
64
        'tl'       => null, // Target language
65
        'q'        => null, // String to translate
66
        'ie'       => 'UTF-8', // Input encoding
67
        'oe'       => 'UTF-8', // Output encoding
68
        'multires' => 1,
69
        'otf'      => 0,
70
        'pc'       => 1,
71
        'trs'      => 1,
72
        'ssel'     => 0,
73
        'tsel'     => 0,
74
        'kc'       => 1,
75
        'tk'       => null,
76
    ];
77
78
    /**
79
     * @var array Regex key-value patterns to replace on response data
80
     */
81
    protected $resultRegexes = [
82
        '/,+/'  => ',',
83
        '/\[,/' => '[',
84
    ];
85
86
    /**
87
     * @var TokenProviderInterface
88
     */
89
    protected $tokenProvider;
90
91
    /**
92
     * Class constructor.
93
     *
94
     * For more information about HTTP client configuration options, visit
95
     * "Creating a client" section of GuzzleHttp docs.
96
     * 5.x - http://guzzle.readthedocs.org/en/5.3/clients.html#creating-a-client
97
     *
98
     * @param string $source  Source language (Optional)
99
     * @param string $target  Target language (Optional)
100
     * @param array  $options Associative array of http client configuration options (Optional)
101
     *
102
     * @throws Exception If token provider does not implement TokenProviderInterface
103
     */
104
    public function __construct($source = null, $target = 'en', $options = [], TokenProviderInterface $tokenProvider = null)
105
    {
106
        $this->httpClient = new GuzzleHttpClient($options); // Create HTTP client
107
        $this->setSource($source)->setTarget($target); // Set languages
108
109
        if (!$tokenProvider) {
110
            $tokenProvider = new GoogleTokenGenerator();
111
        }
112
113
        $this->tokenProvider = $tokenProvider;
114
    }
115
116
    /**
117
     * Set source language we are transleting from.
118
     *
119
     * @param string $source Language code
120
     *
121
     * @return Translator
122
     */
123
    public function setSource($source = null)
124
    {
125
        $this->sourceLanguage = is_null($source) ? 'auto' : $source;
126
127
        return $this;
128
    }
129
130
    /**
131
     * Set translation language we are translating to.
132
     *
133
     * @param string $target Language code
134
     *
135
     * @return Translator
136
     */
137
    public function setTarget($target)
138
    {
139
        $this->targetLanguage = $target;
140
141
        return $this;
142
    }
143
144
    /**
145
     * Set guzzleHttp client options.
146
     *
147
     * @param array $options guzzleHttp client options.
148
     *
149
     * @return Translator
150
     */
151
    public function setHttpOption(array $options)
152
    {
153
        $this->httpOptions = $options;
154
155
        return $this;
156
    }
157
158
    /**
159
     * Get response array.
160
     *
161
     * @param string|array $data String or array of strings to translate
162
     *
163
     * @throws InvalidArgumentException If the provided argument is not of type 'string'
164
     * @throws ErrorException           If the HTTP request fails
165
     * @throws UnexpectedValueException If received data cannot be decoded
166
     *
167
     * @return array Response
168
     */
169
    public function getResponse($data)
170
    {
171
        if (!is_string($data) && !is_array($data)) {
172
            throw new InvalidArgumentException('Invalid argument provided');
173
        }
174
175
        $tokenData = is_array($data) ? implode('', $data) : $data;
176
177
        $queryArray = array_merge($this->urlParams, [
178
            'sl' => $this->sourceLanguage,
179
            'tl' => $this->targetLanguage,
180
            'tk' => $this->tokenProvider->generateToken($this->sourceLanguage, $this->targetLanguage, $tokenData),
181
        ]);
182
183
        $queryUrl = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($queryArray));
184
185
        $queryBodyArray = [
186
            'q' => $data,
187
        ];
188
189
        $queryBodyEncoded = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($queryBodyArray));
190
191
        try {
192
            $response = $this->httpClient->post($this->urlBase, [
193
                    'query' => $queryUrl,
194
                    'body'  => $queryBodyEncoded,
195
                ] + $this->httpOptions);
196
        } catch (GuzzleRequestException $e) {
197
            throw new ErrorException($e->getMessage());
198
        }
199
200
        $body = $response->getBody()->getContents(); // Get response body
201
202
        // Modify body to avoid json errors
203
        $bodyJson = preg_replace(array_keys($this->resultRegexes), array_values($this->resultRegexes), $body);
204
205
        // Decode JSON data
206
        if (($bodyArray = json_decode($bodyJson, true)) === null) {
207
            throw new UnexpectedValueException('Data cannot be decoded or it\'s deeper than the recursion limit');
208
        }
209
210
        return $bodyArray;
211
    }
212
213
    /**
214
     * Translate text.
215
     *
216
     * This can be called from instance method translate() using __call() magic method.
217
     * Use $instance->translate($string) instead.
218
     *
219
     * @param string|array $data Text or array of texts to translate
220
     *
221
     * @throws InvalidArgumentException If the provided argument is not of type 'string'
222
     * @throws ErrorException           If the HTTP request fails
223
     * @throws UnexpectedValueException If received data cannot be decoded
224
     *
225
     * @return string|bool Translated text
226
     */
227
    public function translate($data)
228
    {
229
        if (is_string($data)) {
230
            return $this->translateText($data);
231
        }
232
233
        if (is_array($data)) {
234
            $translatedText = $this->translateText(implode(' || ', $data));
235
236
            return array_combine(array_keys($data), explode(' || ', $translatedText));
237
        }
238
239
        throw new InvalidArgumentException("Expecting string or array as an argument on translate method.");
240
    }
241
242
    /**
243
     * Translate text.
244
     *
245
     * This can be called from instance method translate() using __call() magic method.
246
     * Use $instance->translate($string) instead.
247
     *
248
     * @param string $data Text or array of texts to translate
0 ignored issues
show
Bug introduced by
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
249
     *
250
     * @throws InvalidArgumentException If the provided argument is not of type 'string'
251
     * @throws ErrorException           If the HTTP request fails
252
     * @throws UnexpectedValueException If received data cannot be decoded
253
     *
254
     * @return string|bool Translated text
255
     */
256
    protected function translateText($string)
257
    {
258
        // Rethrow exceptions
259
        try {
260
            $responseArray = $this->getResponse($string);
261
        } catch (Exception $e) {
262
            throw $e;
263
        }
264
265
        // if response in text and the content has zero the empty returns true, lets check
266
        // if response is string and not empty and create array for further logic
267
        if (is_string($responseArray) && $responseArray != '') {
268
            $responseArray = [$responseArray];
269
        }
270
271
        // Check if translation exists
272
        if (!isset($responseArray[0]) || empty($responseArray[0])) {
273
            return false;
274
        }
275
276
        // Detect languages
277
        $detectedLanguages = [];
278
279
        // the response contains only single translation, dont create loop that will end with
280
        // invalid foreach and warning
281
        if (!is_string($responseArray)) {
282
            $responseArrayForLanguages = [$responseArray];
283
284
            foreach ($responseArrayForLanguages as $itemArray) {
285
                foreach ($itemArray as $item) {
286
                    if (is_string($item)) {
287
                        $detectedLanguages[] = $item;
288
                    }
289
                }
290
            }
291
        }
292
293
        // Another case of detected language
294
        if (isset($responseArray[count($responseArray) - 2][0][0])) {
295
            $detectedLanguages[] = $responseArray[count($responseArray) - 2][0][0];
296
        }
297
298
        // Set initial detected language to null
299
        $this->lastDetectedSource = false;
300
301
        // Iterate and set last detected language
302
        foreach ($detectedLanguages as $lang) {
303
            if ($this->isValidLocale($lang)) {
304
                $this->lastDetectedSource = $lang;
305
                break;
306
            }
307
        }
308
309
        // the response can be sometimes an translated string.
310
        if (is_string($responseArray)) {
311
            return $responseArray;
312
        }
313
314
        if (is_array($responseArray[0])) {
315
            return array_reduce($responseArray[0], function ($carry, $item) {
316
                $carry .= $item[0];
317
318
                return $carry;
319
            });
320
        } else {
321
            return $responseArray[0];
322
        }
323
    }
324
325
    /**
326
     * Get last detected language.
327
     *
328
     * @return string|bool Last detected language or boolean FALSE
329
     */
330
    public function getLastDetectedSource()
331
    {
332
        return $this->lastDetectedSource;
333
    }
334
335
    /**
336
     * Check if given locale is valid.
337
     *
338
     * @param string $lang Language code to verify
339
     *
340
     * @return bool
341
     */
342
    public function isValidLocale($lang)
343
    {
344
        return (bool)preg_match('/^([a-z]{2})(-[A-Z]{2})?$/', $lang);
345
    }
346
}
347