Completed
Push — master ( 2ad0de...2fd600 )
by Levan
03:06
created

GoogleTranslate::setTarget()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Stichoza\GoogleTranslate;
4
5
use BadMethodCallException;
6
use ErrorException;
7
use GuzzleHttp\Client;
8
use GuzzleHttp\Exception\RequestException;
9
use Stichoza\GoogleTranslate\Tokens\GoogleTokenGenerator;
10
use Stichoza\GoogleTranslate\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'   => 't',
57
        'hl'       => 'en',
58
        'dt'       => 't',
59
        'sl'       => null, // Source language
60
        'tl'       => null, // Target language
61
        'q'        => null, // String to translate
62
        'ie'       => 'UTF-8', // Input encoding
63
        'oe'       => 'UTF-8', // Output encoding
64
        'multires' => 1,
65
        'otf'      => 0,
66
        'pc'       => 1,
67
        'trs'      => 1,
68
        'ssel'     => 0,
69
        'tsel'     => 0,
70
        'kc'       => 1,
71
        'tk'       => null,
72
    ];
73
74
    /**
75
     * @var array Regex key-value patterns to replace on response data
76
     */
77
    protected $resultRegexes = [
78
        '/,+/'  => ',',
79
        '/\[,/' => '[',
80
    ];
81
82
    /**
83
     * @var TokenProviderInterface Token provider
84
     */
85
    protected $tokenProvider;
86
87
    /**
88
     * Class constructor.
89
     *
90
     * For more information about HTTP client configuration options, see "Request Options" in
91
     * GuzzleHttp docs: http://docs.guzzlephp.org/en/stable/request-options.html
92
     *
93
     * @param string $target Target language
94
     * @param string|null $source Source language
95
     * @param array|null $options Associative array of http client configuration options
96
     * @param TokenProviderInterface|null $tokenProvider
97
     */
98
    public function __construct(string $target = 'en', string $source = null, array $options = null, TokenProviderInterface $tokenProvider = null)
99
    {
100
        $this->client = new Client();
101
        $this->setTokenProvider($tokenProvider ?? new GoogleTokenGenerator)
102
            ->setOptions($options) // Options are already set in client constructor tho.
103
            ->setSource($source)
104
            ->setTarget($target);
105
    }
106
107
    /**
108
     * Set target language for translation.
109
     *
110
     * @param string $target Language code
111
     * @return GoogleTranslate
112
     */
113
    public function setTarget(string $target) : self
114
    {
115
        $this->target = $target;
116
        return $this;
117
    }
118
119
    /**
120
     * Set source language for translation.
121
     *
122
     * @param string|null $source Language code
123
     * @return GoogleTranslate
124
     */
125
    public function setSource(string $source = null) : self
126
    {
127
        $this->source = $source ?? 'auto';
128
        return $this;
129
    }
130
131
    /**
132
     * Set Google Translate URL base
133
     *
134
     * @param string $url Google Translate URL base
135
     * @return GoogleTranslate
136
     */
137
    public function setUrl(string $url) : self
138
    {
139
        $this->url = $url;
140
        return $this;
141
    }
142
143
    /**
144
     * Set GuzzleHttp client options.
145
     *
146
     * @param array $options guzzleHttp client options.
147
     * @return GoogleTranslate
148
     */
149
    public function setOptions(array $options = null) : self
150
    {
151
        $this->options = $options ?? [];
152
        return $this;
153
    }
154
155
    /**
156
     * Set token provider.
157
     *
158
     * @param TokenProviderInterface $tokenProvider
159
     * @return GoogleTranslate
160
     */
161
    public function setTokenProvider(TokenProviderInterface $tokenProvider) : self
162
    {
163
        $this->tokenProvider = $tokenProvider;
164
        return $this;
165
    }
166
167
    /**
168
     * Get last detected source language
169
     *
170
     * @return string|null Last detected source language
171
     */
172
    public function getLastDetectedSource()
173
    {
174
        return $this->lastDetectedSource;
175
    }
176
177
    /**
178
     * Override translate method for static call.
179
     *
180
     * @param string $string
181
     * @param string $target
182
     * @param string|null $source
183
     * @param array $options
184
     * @param TokenProviderInterface|null $tokenProvider
185
     * @return null|string
186
     * @throws ErrorException If the HTTP request fails
187
     * @throws UnexpectedValueException If received data cannot be decoded
188
     */
189
    public static function trans(string $string, string $target = 'en', string $source = null, array $options = [], TokenProviderInterface $tokenProvider = null)
190
    {
191
        return (new self)
192
            ->setTokenProvider($tokenProvider ?? new GoogleTokenGenerator)
193
            ->setOptions($options) // Options are already set in client constructor tho.
194
            ->setSource($source)
195
            ->setTarget($target)
196
            ->translate($string);
197
    }
198
199
    /**
200
     * Translate text.
201
     *
202
     * This can be called from instance method translate() using __call() magic method.
203
     * Use $instance->translate($string) instead.
204
     *
205
     * @param string $string String to translate
206
     * @return string|null
207
     * @throws ErrorException           If the HTTP request fails
208
     * @throws UnexpectedValueException If received data cannot be decoded
209
     */
210
    public function translate(string $string) : string
211
    {
212
        $responseArray = $this->getResponse($string);
213
214
        /*
215
         * if response in text and the content has zero the empty returns true, lets check
216
         * if response is string and not empty and create array for further logic
217
         */
218
        if (is_string($responseArray) && $responseArray != '') {
219
            $responseArray = [$responseArray];
220
        }
221
222
        // Check if translation exists
223
        if (!isset($responseArray[0]) || empty($responseArray[0])) {
224
            return null;
225
        }
226
227
        // Detect languages
228
        $detectedLanguages = [];
229
230
        // the response contains only single translation, don't create loop that will end with
231
        // invalid foreach and warning
232
        if (!is_string($responseArray)) {
233
            foreach ($responseArray as $item) {
234
                if (is_string($item)) {
235
                    $detectedLanguages[] = $item;
236
                }
237
            }
238
        }
239
240
        // Another case of detected language
241
        if (isset($responseArray[count($responseArray) - 2][0][0])) {
242
            $detectedLanguages[] = $responseArray[count($responseArray) - 2][0][0];
243
        }
244
245
        // Set initial detected language to null
246
        $this->lastDetectedSource = null;
247
248
        // Iterate and set last detected language
249
        foreach ($detectedLanguages as $lang) {
250
            if ($this->isValidLocale($lang)) {
251
                $this->lastDetectedSource = $lang;
252
                break;
253
            }
254
        }
255
256
        // the response can be sometimes an translated string.
257
        if (is_string($responseArray)) {
258
            return $responseArray;
259
        } else {
260
            if (is_array($responseArray[0])) {
261
                return (string) array_reduce($responseArray[0], function ($carry, $item) {
262
                    $carry .= $item[0];
263
                    return $carry;
264
                });
265
            } else {
266
                return (string) $responseArray[0];
267
            }
268
        }
269
    }
270
271
    /**
272
     * Get response array.
273
     *
274
     * @param string $string String to translate
275
     * @throws ErrorException           If the HTTP request fails
276
     * @throws UnexpectedValueException If received data cannot be decoded
277
     * @return array|string Response
278
     */
279
    public function getResponse(string $string) : array
280
    {
281
        $queryArray = array_merge($this->urlParams, [
282
            'sl'   => $this->source,
283
            'tl'   => $this->target,
284
            'tk'   => $this->tokenProvider->generateToken($this->source, $this->target, $string),
285
        ]);
286
287
        $queryUrl = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($queryArray));
288
289
        $queryBodyArray = [
290
            'q' => $string,
291
        ];
292
293
        $queryBodyEncoded = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($queryBodyArray));
294
295
        try {
296
            $response = $this->client->post($this->url, [
297
                    'query' => $queryUrl,
298
                    'body'  => $queryBodyEncoded,
299
                ] + $this->options);
300
        } catch (RequestException $e) {
301
            throw new ErrorException($e->getMessage());
302
        }
303
304
        $body = $response->getBody(); // Get response body
305
306
        // Modify body to avoid json errors
307
        $bodyJson = preg_replace(array_keys($this->resultRegexes), array_values($this->resultRegexes), $body);
308
309
        // Decode JSON data
310
        if (($bodyArray = json_decode($bodyJson, true)) === null) {
311
            throw new UnexpectedValueException('Data cannot be decoded or it is deeper than the recursion limit');
312
        }
313
314
        return $bodyArray;
315
    }
316
317
    /**
318
     * Check if given locale is valid.
319
     *
320
     * @param string $lang Langauge code to verify
321
     * @return bool
322
     */
323
    protected function isValidLocale(string $lang) : bool
324
    {
325
        return (bool) preg_match('/^([a-z]{2})(-[A-Z]{2})?$/', $lang);
326
    }
327
}
328