Completed
Pull Request — master (#68)
by André
03:44 queued 01:32
created

Translator   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 4
dl 0
loc 323
rs 9.6
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 2
A setSource() 0 6 2
A setTarget() 0 6 1
A setHttpOption() 0 6 1
B getResponse() 0 43 6
A translate() 0 14 3
C translateText() 0 68 15
A getLastDetectedSource() 0 4 1
A isValidLocale() 0 4 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
class Translator
24
{
25
    /**
26
     * @var \GuzzleHttp\Client HTTP Client
27
     */
28
    protected $httpClient;
29
30
    /**
31
     * @var string Source language - from where the string should be translated
32
     */
33
    protected $sourceLanguage;
34
35
    /**
36
     * @var string Target language - to which language string should be translated
37
     */
38
    protected $targetLanguage;
39
40
    /**
41
     * @var string|bool Last detected source language
42
     */
43
    protected $lastDetectedSource = false;
44
45
    /**
46
     * @var string Google Translate URL base
47
     */
48
    private $urlBase = 'http://translate.google.com/translate_a/single';
49
50
    /**
51
     * @var array Dynamic guzzleHTTP client options
52
     */
53
    protected $httpOptions = [];
54
55
    /**
56
     * @var array URL Parameters
57
     */
58
    protected $urlParams = [
59
        'client'   => 't',
60
        'hl'       => 'en',
61
        'dt'       => 't',
62
        'sl'       => null, // Source language
63
        'tl'       => null, // Target language
64
        'q'        => null, // String to translate
65
        'ie'       => 'UTF-8', // Input encoding
66
        'oe'       => 'UTF-8', // Output encoding
67
        'multires' => 1,
68
        'otf'      => 0,
69
        'pc'       => 1,
70
        'trs'      => 1,
71
        'ssel'     => 0,
72
        'tsel'     => 0,
73
        'kc'       => 1,
74
        'tk'       => null,
75
    ];
76
77
    /**
78
     * @var array Regex key-value patterns to replace on response data
79
     */
80
    protected $resultRegexes = [
81
        '/,+/'  => ',',
82
        '/\[,/' => '[',
83
    ];
84
85
    /**
86
     * @var TokenProviderInterface
87
     */
88
    protected $tokenProvider;
89
90
    /**
91
     * Class constructor.
92
     *
93
     * For more information about HTTP client configuration options, visit
94
     * "Creating a client" section of GuzzleHttp docs.
95
     * 5.x - http://guzzle.readthedocs.org/en/5.3/clients.html#creating-a-client
96
     *
97
     * @param string $source  Source language (Optional)
98
     * @param string $target  Target language (Optional)
99
     * @param array  $options Associative array of http client configuration options (Optional)
100
     *
101
     * @throws Exception If token provider does not implement TokenProviderInterface
102
     */
103
    public function __construct($source = null, $target = 'en', $options = [], TokenProviderInterface $tokenProvider = null)
104
    {
105
        $this->httpClient = new GuzzleHttpClient($options); // Create HTTP client
106
        $this->setSource($source)->setTarget($target); // Set languages
107
108
        if (!$tokenProvider) {
109
            $tokenProvider = new GoogleTokenGenerator();
110
        }
111
112
        $this->tokenProvider = $tokenProvider;
113
    }
114
115
    /**
116
     * Set source language we are transleting from.
117
     *
118
     * @param string $source Language code
119
     *
120
     * @return Translator
121
     */
122
    public function setSource($source = null)
123
    {
124
        $this->sourceLanguage = is_null($source) ? 'auto' : $source;
125
126
        return $this;
127
    }
128
129
    /**
130
     * Set translation language we are translating to.
131
     *
132
     * @param string $target Language code
133
     *
134
     * @return Translator
135
     */
136
    public function setTarget($target)
137
    {
138
        $this->targetLanguage = $target;
139
140
        return $this;
141
    }
142
143
    /**
144
     * Set guzzleHttp client options.
145
     *
146
     * @param array $options guzzleHttp client options.
147
     *
148
     * @return Translator
149
     */
150
    public function setHttpOption(array $options)
151
    {
152
        $this->httpOptions = $options;
153
154
        return $this;
155
    }
156
157
    /**
158
     * Get response array.
159
     *
160
     * @param string|array $data String or array of strings to translate
161
     *
162
     * @throws InvalidArgumentException If the provided argument is not of type 'string'
163
     * @throws ErrorException           If the HTTP request fails
164
     * @throws UnexpectedValueException If received data cannot be decoded
165
     *
166
     * @return array Response
167
     */
168
    public function getResponse($data)
169
    {
170
        if (!is_string($data) && !is_array($data)) {
171
            throw new InvalidArgumentException('Invalid argument provided');
172
        }
173
174
        $tokenData = is_array($data) ? implode('', $data) : $data;
175
176
        $queryArray = array_merge($this->urlParams, [
177
            'sl' => $this->sourceLanguage,
178
            'tl' => $this->targetLanguage,
179
            'tk' => $this->tokenProvider->generateToken($this->sourceLanguage, $this->targetLanguage, $tokenData),
180
        ]);
181
182
        $queryUrl = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($queryArray));
183
184
        $queryBodyArray = [
185
            'q' => $data,
186
        ];
187
188
        $queryBodyEncoded = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($queryBodyArray));
189
190
        try {
191
            $response = $this->httpClient->post($this->urlBase, [
192
                    'query' => $queryUrl,
193
                    'body'  => $queryBodyEncoded,
194
                ] + $this->httpOptions);
195
        } catch (GuzzleRequestException $e) {
196
            throw new ErrorException($e->getMessage());
197
        }
198
199
        $body = $response->getBody()->getContents(); // Get response body
200
201
        // Modify body to avoid json errors
202
        $bodyJson = preg_replace(array_keys($this->resultRegexes), array_values($this->resultRegexes), $body);
203
204
        // Decode JSON data
205
        if (($bodyArray = json_decode($bodyJson, true)) === null) {
206
            throw new UnexpectedValueException('Data cannot be decoded or it\'s deeper than the recursion limit');
207
        }
208
209
        return $bodyArray;
210
    }
211
212
    /**
213
     * Translate text.
214
     *
215
     * This can be called from instance method translate() using __call() magic method.
216
     * Use $instance->translate($string) instead.
217
     *
218
     * @param string|array $data Text or array of texts to translate
219
     *
220
     * @throws InvalidArgumentException If the provided argument is not of type 'string'
221
     * @throws ErrorException           If the HTTP request fails
222
     * @throws UnexpectedValueException If received data cannot be decoded
223
     *
224
     * @return string|bool Translated text
225
     */
226
    public function translate($data)
227
    {
228
        if (is_string($data)) {
229
            return $this->translateText($data);
230
        }
231
232
        if (is_array($data)) {
233
            $translatedText = $this->translateText(implode(' || ', $data));
234
235
            return array_combine(array_keys($data), explode(' || ', $translatedText));
236
        }
237
238
        throw new InvalidArgumentException('Expecting string or array as an argument on translate method.');
239
    }
240
241
    /**
242
     * Translate text.
243
     *
244
     * This can be called from instance method translate() using __call() magic method.
245
     * Use $instance->translate($string) instead.
246
     *
247
     * @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...
248
     *
249
     * @throws InvalidArgumentException If the provided argument is not of type 'string'
250
     * @throws ErrorException           If the HTTP request fails
251
     * @throws UnexpectedValueException If received data cannot be decoded
252
     *
253
     * @return string|bool Translated text
254
     */
255
    protected function translateText($string)
256
    {
257
        // Rethrow exceptions
258
        try {
259
            $responseArray = $this->getResponse($string);
260
        } catch (Exception $e) {
261
            throw $e;
262
        }
263
264
        // if response in text and the content has zero the empty returns true, lets check
265
        // if response is string and not empty and create array for further logic
266
        if (is_string($responseArray) && $responseArray != '') {
267
            $responseArray = [$responseArray];
268
        }
269
270
        // Check if translation exists
271
        if (!isset($responseArray[0]) || empty($responseArray[0])) {
272
            return false;
273
        }
274
275
        // Detect languages
276
        $detectedLanguages = [];
277
278
        // the response contains only single translation, dont create loop that will end with
279
        // invalid foreach and warning
280
        if (!is_string($responseArray)) {
281
            $responseArrayForLanguages = [$responseArray];
282
283
            foreach ($responseArrayForLanguages as $itemArray) {
284
                foreach ($itemArray as $item) {
285
                    if (is_string($item)) {
286
                        $detectedLanguages[] = $item;
287
                    }
288
                }
289
            }
290
        }
291
292
        // Another case of detected language
293
        if (isset($responseArray[count($responseArray) - 2][0][0])) {
294
            $detectedLanguages[] = $responseArray[count($responseArray) - 2][0][0];
295
        }
296
297
        // Set initial detected language to null
298
        $this->lastDetectedSource = false;
299
300
        // Iterate and set last detected language
301
        foreach ($detectedLanguages as $lang) {
302
            if ($this->isValidLocale($lang)) {
303
                $this->lastDetectedSource = $lang;
304
                break;
305
            }
306
        }
307
308
        // the response can be sometimes an translated string.
309
        if (is_string($responseArray)) {
310
            return $responseArray;
311
        }
312
313
        if (is_array($responseArray[0])) {
314
            return array_reduce($responseArray[0], function ($carry, $item) {
315
                $carry .= $item[0];
316
317
                return $carry;
318
            });
319
        } else {
320
            return $responseArray[0];
321
        }
322
    }
323
324
    /**
325
     * Get last detected language.
326
     *
327
     * @return string|bool Last detected language or boolean FALSE
328
     */
329
    public function getLastDetectedSource()
330
    {
331
        return $this->lastDetectedSource;
332
    }
333
334
    /**
335
     * Check if given locale is valid.
336
     *
337
     * @param string $lang Language code to verify
338
     *
339
     * @return bool
340
     */
341
    public function isValidLocale($lang)
342
    {
343
        return (bool) preg_match('/^([a-z]{2})(-[A-Z]{2})?$/', $lang);
344
    }
345
}
346