├── .gitignore ├── .travis.yml ├── src ├── Tokens │ ├── TokenProviderInterface.php │ ├── SampleTokenGenerator.php │ └── GoogleTokenGenerator.php └── GoogleTranslate.php ├── phpunit.xml ├── composer.json ├── LICENSE ├── tests ├── TranslationTest.php ├── LanguageDetectionTest.php └── UtilityTest.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 7.1 4 | - 7.2 5 | - 7.3 6 | 7 | before_script: 8 | - composer self-update 9 | - composer install --prefer-source --no-interaction --dev 10 | -------------------------------------------------------------------------------- /src/Tokens/TokenProviderInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Tokens/SampleTokenGenerator.php: -------------------------------------------------------------------------------- 1 | tr = new GoogleTranslate(); 15 | } 16 | 17 | public function testTranslationEquality() 18 | { 19 | try { 20 | $resultOne = GoogleTranslate::trans('Hello', 'ka', 'en'); 21 | } catch (\ErrorException $e) { 22 | $resultOne = null; 23 | } 24 | $resultTwo = $this->tr->setSource('en')->setTarget('ka')->translate('Hello'); 25 | 26 | $this->assertEquals($resultOne, $resultTwo, 'გამარჯობა'); 27 | } 28 | 29 | public function testUTF16Translation() 30 | { 31 | try { 32 | $resultOne = GoogleTranslate::trans('yes 👍🏽', 'de', 'en'); 33 | } catch (\ErrorException $e) { 34 | $resultOne = null; 35 | } 36 | $resultTwo = $this->tr->setSource('en')->setTarget('de')->translate('yes 👍🏽'); 37 | 38 | $this->assertEquals($resultOne, $resultTwo, 'ja 👍🏽'); 39 | } 40 | 41 | public function testRawResponse() 42 | { 43 | $rawResult = $this->tr->getResponse('cat'); 44 | 45 | $this->assertTrue(is_array($rawResult), 'Method getResponse() should return an array.'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/LanguageDetectionTest.php: -------------------------------------------------------------------------------- 1 | tr = new GoogleTranslate(); 15 | } 16 | 17 | public function testSingleWord() 18 | { 19 | $this->tr->translate('გამარჯობა'); 20 | $this->assertEquals($this->tr->getLastDetectedSource(), 'ka'); 21 | 22 | $this->tr->translate('Cześć'); 23 | $this->assertEquals($this->tr->getLastDetectedSource(), 'pl'); 24 | } 25 | 26 | public function testSingleSentence() 27 | { 28 | $this->tr->translate('იყო არაბეთს როსტევან'); 29 | $this->assertEquals($this->tr->getLastDetectedSource(), 'ka'); 30 | 31 | $this->tr->translate('Путин хуйло'); 32 | $this->assertEquals($this->tr->getLastDetectedSource(), 'ru'); 33 | } 34 | 35 | public function testMultipleSentence() 36 | { 37 | $this->tr->translate('ჩემი ხატია სამშობლო. სახატე - მთელი ქვეყანა. განათებული მთა-ბარი.'); 38 | $this->assertEquals($this->tr->getLastDetectedSource(), 'ka'); 39 | 40 | $this->tr->translate('Ще не вмерла Україна, И слава, и воля! Ще намъ, браття-молодці, Усміхнеться доля!'); 41 | $this->assertEquals($this->tr->getLastDetectedSource(), 'uk'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/UtilityTest.php: -------------------------------------------------------------------------------- 1 | tr = new GoogleTranslate(); 18 | $reflection = new ReflectionClass(get_class($this->tr)); 19 | $this->method = $reflection->getMethod('isValidLocale'); 20 | $this->method->setAccessible(true); 21 | } 22 | 23 | public function testIsValidLocale() 24 | { 25 | $m = $this->method; 26 | $t = $this->tr; 27 | 28 | $booleanAssertions = [ 29 | 'ab' => true, 30 | 'ab-CD' => true, 31 | 'ab-CDE' => false, 32 | 'abc-DE' => false, 33 | 'abc-DEF' => false, 34 | 'abc' => false, 35 | 'ab-' => false, 36 | 'a' => false, 37 | ]; 38 | 39 | foreach ($booleanAssertions as $key => $value) { 40 | $this->assertEquals($m->invokeArgs($t, [$key]), $value); 41 | } 42 | } 43 | 44 | public function testSetOptions() 45 | { 46 | $res = fopen('php://memory', 'r+'); 47 | 48 | $this->tr->setOptions([ 49 | 'debug' => $res, 50 | 'headers' => [ 51 | 'User-Agent' => 'Foo', 52 | ], 53 | ])->translate('hello'); 54 | rewind($res); 55 | $output = str_replace("\r", '', stream_get_contents($res)); 56 | $this->assertContains('User-Agent: Foo', $output); 57 | 58 | GoogleTranslate::trans('world', 'en', null, [ 59 | 'debug' => $res, 60 | 'headers' => [ 61 | 'User-Agent' => 'Bar', 62 | ], 63 | ]); 64 | rewind($res); 65 | $output = str_replace("\r", '', stream_get_contents($res)); 66 | $this->assertContains('User-Agent: Bar', $output); 67 | fclose($res); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Tokens/GoogleTokenGenerator.php: -------------------------------------------------------------------------------- 1 | "0" ]; 22 | /** 23 | * Generate and return a token. 24 | * 25 | * @param string $source Source language 26 | * @param string $target Target language 27 | * @param string $text Text to translate 28 | * @return string Token 29 | */ 30 | public function generateToken(string $source, string $target, string $text) : string 31 | { 32 | return $this->TL($text); 33 | } 34 | 35 | /** 36 | * Generate a valid Google Translate request token. 37 | * 38 | * @param string $a text to translate 39 | * 40 | * @return string 41 | */ 42 | private function TL($a) 43 | { 44 | $tkk = $this->updateTTK(); 45 | $b = $tkk[0] ? $tkk[0] + 0 : 0; 46 | for ($d = [], $e = 0, $f = 0; $f < $this->JS_length($a); $f++) { 47 | $g = $this->JS_charCodeAt($a, $f); 48 | if (128 > $g) { 49 | $d[$e++] = $g; 50 | } else { 51 | if (2048 > $g) { 52 | $d[$e++] = $g >> 6 | 192; 53 | } else { 54 | if (55296 == ($g & 64512) && $f + 1 < $this->JS_length($a) && 56320 == ($this->JS_charCodeAt($a, $f + 1) & 64512)) { 55 | $g = 65536 + (($g & 1023) << 10) + ($this->JS_charCodeAt($a, ++$f) & 1023); 56 | $d[$e++] = $g >> 18 | 240; 57 | $d[$e++] = $g >> 12 & 63 | 128; 58 | } else { 59 | $d[$e++] = $g >> 12 | 224; 60 | } 61 | $d[$e++] = $g >> 6 & 63 | 128; 62 | } 63 | $d[$e++] = $g & 63 | 128; 64 | } 65 | } 66 | $a = $b; 67 | for ($e = 0; $e < count($d); $e++) { 68 | $a += $d[$e]; 69 | $a = $this->RL($a, '+-a^+6'); 70 | } 71 | $a = $this->RL($a, '+-3^+b+-f'); 72 | $a ^= $tkk[1] ? $tkk[1] + 0 : 0; 73 | if (0 > $a) { 74 | $a = ($a & 2147483647) + 2147483648; 75 | } 76 | $a = fmod($a, pow(10, 6)); 77 | 78 | return $a.'.'.($a ^ $b); 79 | } 80 | 81 | private function updateTTK($opts = ["tld" => 'cn']) { 82 | 83 | $now = (float)floor(now() / 3600000); 84 | $cnow = explode(".", $this->win["TKK"])[0]; 85 | 86 | if ((float)$cnow !== $now) { 87 | $url = 'https://translate.google.'. $opts["tld"]; 88 | 89 | $ch = curl_init(); 90 | // Set the url, number of POST vars, POST data 91 | curl_setopt($ch, CURLOPT_URL, $url); 92 | 93 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 94 | curl_setopt($ch, CURLOPT_ENCODING, 'UTF-8'); 95 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 96 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 97 | curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36"); 98 | // Execute post 99 | $result = curl_exec($ch); 100 | 101 | preg_match("/tkk:\s?'(.+?)'/i", $result, $matches); 102 | if ($matches) { 103 | $this->win["TKK"] = $matches[1]; 104 | } 105 | // Close connection 106 | curl_close($ch); 107 | return explode(".", $this->win["TKK"]); 108 | } else { 109 | return explode(".", $this->win["TKK"]); 110 | } 111 | 112 | } 113 | 114 | /** 115 | * @return array 116 | */ 117 | private function TKK() 118 | { 119 | return [now(), (561666268 + 1526272306)]; 120 | } 121 | 122 | /** 123 | * Process token data by applying multiple operations. 124 | * (Params are safe, no need for multibyte functions) 125 | * 126 | * @param int $a 127 | * @param string $b 128 | * 129 | * @return int 130 | */ 131 | private function RL($a, $b) 132 | { 133 | for ($c = 0; $c < strlen($b) - 2; $c += 3) { 134 | $d = $b[$c + 2]; 135 | $d = 'a' <= $d ? ord($d[0]) - 87 : intval($d); 136 | $d = '+' == $b[$c + 1] ? $this->unsignedRightShift($a, $d) : $a << $d; 137 | $a = '+' == $b[$c] ? ($a + $d & 4294967295) : $a ^ $d; 138 | } 139 | 140 | return $a; 141 | } 142 | 143 | /** 144 | * Unsigned right shift implementation 145 | * https://msdn.microsoft.com/en-us/library/342xfs5s(v=vs.94).aspx 146 | * http://stackoverflow.com/a/43359819/2953830 147 | * 148 | * @param $a 149 | * @param $b 150 | * 151 | * @return number 152 | */ 153 | private function unsignedRightShift($a, $b) 154 | { 155 | if ($b >= 32 || $b < -32) { 156 | $m = (int)($b / 32); 157 | $b = $b - ($m * 32); 158 | } 159 | 160 | if ($b < 0) { 161 | $b = 32 + $b; 162 | } 163 | 164 | if ($b == 0) { 165 | return (($a >> 1) & 0x7fffffff) * 2 + (($a >> $b) & 1); 166 | } 167 | 168 | if ($a < 0) { 169 | $a = ($a >> 1); 170 | $a &= 2147483647; 171 | $a |= 0x40000000; 172 | $a = ($a >> ($b - 1)); 173 | } else { 174 | $a = ($a >> $b); 175 | } 176 | 177 | return $a; 178 | } 179 | 180 | /** 181 | * Get JS charCodeAt equivalent result with UTF-16 encoding 182 | * 183 | * @param string $str 184 | * @param int $index 185 | * 186 | * @return number 187 | */ 188 | private function JS_charCodeAt($str, $index) { 189 | $utf16 = mb_convert_encoding($str, 'UTF-16LE', 'UTF-8'); 190 | return ord($utf16[$index*2]) + (ord($utf16[$index*2+1]) << 8); 191 | } 192 | 193 | /** 194 | * Get JS equivalent string length with UTF-16 encoding 195 | * 196 | * @param string $str 197 | * 198 | * @return number 199 | */ 200 | private function JS_length($str) { 201 | $utf16 = mb_convert_encoding($str, 'UTF-16LE', 'UTF-8'); 202 | return strlen($utf16)/2; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Google Translate PHP 2 | ==================== 3 | 4 | [![Build Status](https://travis-ci.org/icai/google-translate-php-one.svg?branch=master)](https://travis-ci.org/icai/google-translate-php-one) [![Latest Stable Version](https://img.shields.io/packagist/v/icai/google-translate-php-one.svg)](https://packagist.org/packages/icai/google-translate-php-one) [![Total Downloads](https://img.shields.io/packagist/dt/icai/google-translate-php-one.svg)](https://packagist.org/packages/icai/google-translate-php-one) [![Downloads Month](https://img.shields.io/packagist/dm/icai/google-translate-php-one.svg)](https://packagist.org/packages/icai/google-translate-php-one) [![Petreon donation](https://img.shields.io/badge/patreon-donate-orange.svg)](https://www.patreon.com/stichoza) [![PayPal donation](https://img.shields.io/badge/paypal-donate-blue.svg)](https://paypal.me/stichoza) 5 | 6 | Free Google Translate API PHP Package. Translates totally free of charge. 7 | 8 | --- 9 | 10 | - **[Installation](#installation)** 11 | - **[Basic Usage](#basic-usage)** 12 | - [Advanced Usage](#advanced-usage) 13 | - [Language Detection](#language-detection) 14 | - [Using Raw Response](#using-raw-response) 15 | - [Custom URL](#custom-url) 16 | - [HTTP Client Configuration](#http-client-configuration) 17 | - [Custom Token Generator](#custom-token-generator) 18 | - [Errors and Exception Handling](#errors-and-exception-handling) 19 | - [Known Limitations](#known-limitations) 20 | - [Disclaimer](#disclaimer) 21 | - [Donation](#donation) 22 | 23 | ## Installation 24 | 25 | Install this package via [Composer](https://getcomposer.org/). 26 | 27 | ``` 28 | composer require icai/google-translate-php-one 29 | ``` 30 | 31 | > Note: **PHP 7.1 or later** is required. For older versoins, use `^3.2` version of this package (see [old docs](https://github.com/icai/google-translate-php-one/tree/3.2#google-translate-php)). 32 | 33 | ## Basic Usage 34 | 35 | Create GoogleTranslate object 36 | 37 | ```php 38 | use Stichoza\GoogleTranslate\GoogleTranslate; 39 | 40 | $tr = new GoogleTranslate('en'); // Translates into English 41 | ``` 42 | Or you can change languages later 43 | ```php 44 | $tr = new GoogleTranslate(); // Translates to 'en' from auto-detected language by default 45 | $tr->setSource('en'); // Translate from English 46 | $tr->setSource(); // Detect language automatically 47 | $tr->setTarget('ka'); // Translate to Georgian 48 | ``` 49 | Translate sentences 50 | ```php 51 | echo $tr->translate('Hello World!'); 52 | ``` 53 | Also, you can also use method chaining 54 | ```php 55 | echo $tr->setSource('en')->setTarget('ka')->translate('Goodbye'); 56 | ``` 57 | Or call a shorthand static method `trans` 58 | ```php 59 | echo GoogleTranslate::trans('Hello again', 'ka', 'en'); 60 | ``` 61 | 62 | ## Advanced Usage 63 | 64 | ### Language Detection 65 | 66 | To detect language automatically, just set the source language to `null`: 67 | 68 | ```php 69 | $tr = new GoogleTranslate('es', null); // Or simply do not pass the second parameter 70 | ``` 71 | 72 | ```php 73 | $tr->setSource(); // Another way 74 | ``` 75 | 76 | Use `getLastDetectedSource()` to get detected language: 77 | 78 | ```php 79 | $tr = new GoogleTranslate('fr'); 80 | 81 | $text = $tr->translate('Hello World!'); 82 | 83 | echo $tr->getLastDetectedSource(); // Output: en 84 | ``` 85 | 86 | Return value will be `null` if the language couldn't be detected. 87 | 88 | Supported languages are listed in [Google API docs](https://cloud.google.com/translate/docs/languages). 89 | 90 | ### Using Raw Response 91 | 92 | For advanced usage, you might need the raw results that Google Translate provides. you can use `getResponse` method for that. 93 | 94 | ```php 95 | $responseArray = $tr->getResponse('Hello world!'); 96 | ``` 97 | 98 | ### Custom URL 99 | 100 | You can override the default Google Translate url by `setUrl` method. Useful for some countries 101 | 102 | ```php 103 | $tr->setUrl('http://translate.google.cn/translate_a/single'); 104 | ``` 105 | 106 | ### HTTP Client Configuration 107 | 108 | This package uses [Guzzle](https://github.com/guzzle/guzzle) for HTTP requests. You can pass an array of [guzzle client configuration options](http://docs.guzzlephp.org/en/latest/request-options.html) as a third parameter to `GoogleTranslate` constructor, or just use `setOptions` method. 109 | 110 | You can configure proxy, user-agent, default headers, connection timeout and so on using this options. 111 | 112 | ```php 113 | $tr = new GoogleTranslate('en', 'ka', [ 114 | 'timeout' => 10, 115 | 'proxy' => [ 116 | 'http' => 'tcp://localhost:8125', 117 | 'https' => 'tcp://localhost:9124' 118 | ], 119 | 'headers' => [ 120 | 'User-Agent' => 'Foo/5.0 Lorem Ipsum Browser' 121 | ] 122 | ]); 123 | ``` 124 | 125 | ```php 126 | // Set proxy to tcp://localhost:8090 127 | $tr->setOptions(['proxy' => 'tcp://localhost:8090'])->translate('Hello'); 128 | 129 | // Set proxy to socks5://localhost:1080 130 | $tr->setOptions(['proxy' => 'socks5://localhost:1080'])->translate('World'); 131 | ``` 132 | 133 | For more information, see [Creating a Client](http://guzzle.readthedocs.org/en/latest/quickstart.html#creating-a-client) section in Guzzle docs (6.x version). 134 | 135 | ### Custom Token Generator 136 | 137 | You can override the token generator class by passing a generator object as a fourth parameter of constructor or just use `setTokenProvider` method. 138 | 139 | Generator must implement `Stichoza\GoogleTranslate\Tokens\TokenProviderInterface`. 140 | 141 | ```php 142 | use Stichoza\GoogleTranslate\Tokens\TokenProviderInterface; 143 | 144 | class MyTokenGenerator implements TokenProviderInterface 145 | { 146 | public function generateToken(string $source, string $target, string $text) : string 147 | { 148 | // Your code here 149 | } 150 | } 151 | ``` 152 | 153 | And use: 154 | 155 | ```php 156 | $tr->setTokenProvider(new MyTokenGenerator); 157 | ``` 158 | 159 | ### Errors and Exception Handling 160 | 161 | Static method `trans()` and non-static `translate()` and `getResponse()` will throw following Exceptions: 162 | 163 | - `ErrorException` If the HTTP request fails for some reason. 164 | - `UnexpectedValueException` If data received from Google cannot be decoded. 165 | 166 | In addition, `translate()` and `trans()` methods will return `null` if there is no translation available. 167 | 168 | ## Known Limitations 169 | 170 | - `503 Service Unavailable` response: 171 | If you are getting this error, it is most likely that Google has banned your external IP address and/or [requires you to solve a CAPTCHA](https://github.com/icai/google-translate-php-one/issues/18). This is not a bug in this package. Google has become stricter, and it seems like they keep lowering the number of allowed requests per IP per a certain amount of time. Try sending less requests to stay under the radar, or change your IP frequently ([for example using proxies](#http-client-configuration)). Please note that once an IP is banned, even if it's only temporary, the ban can last from a few minutes to more than 12-24 hours, as each case is different. 172 | - `429 Too Many Requests` response: 173 | This error is basically the same as explained above. 174 | - `413 Request Entity Too Large` response: 175 | This error means that your input string is too long. Google only allows a maximum of 5000 characters to be translated at once. If you want to translate a longer text, you can split it to shorter parts, and translate them one-by-one. 176 | 177 | ## Disclaimer 178 | 179 | This package is developed for educational purposes only. Do not depend on this package as it may break anytime as it is based on crawling the Google Translate website. Consider buying [Official Google Translate API](https://cloud.google.com/translate/) for other types of usage. 180 | 181 | ## Donation 182 | 183 | If this package helped you reduce your time to develop something, or it solved any major problems you had, feel free give me a cup of coffee :) 184 | 185 | - [Patreon](https://www.patreon.com/stichoza) 186 | - [PayPal](https://paypal.me/stichoza) 187 | 188 | -------------------------------------------------------------------------------- /src/GoogleTranslate.php: -------------------------------------------------------------------------------- 1 | 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 | 46 | protected $url = 'https://translate.google.cn/translate_a/single'; 47 | 48 | /** 49 | * @var array Dynamic GuzzleHttp client options 50 | */ 51 | protected $options = []; 52 | 53 | /** 54 | * @var array URL Parameters 55 | */ 56 | protected $urlParams = [ 57 | 'client' => 'webapp', 58 | 'hl' => 'zh-CN', 59 | 'anno' => '3', 60 | 'v' => "1.0", 61 | 'key' => null, 62 | 'logld' => "vTE_20190506_00", 63 | 'dt' => [ 64 | 't', // Translate 65 | 'bd', // Full translate with synonym ($bodyArray[1]) 66 | 'at', // Other translate ($bodyArray[5] - in google translate page this shows when click on translated word) 67 | 'ex', // Example part ($bodyArray[13]) 68 | 'ld', // I don't know ($bodyArray[8]) 69 | 'md', // Definition part with example ($bodyArray[12]) 70 | 'qca', // I don't know ($bodyArray[8]) 71 | 'rw', // Read also part ($bodyArray[14]) 72 | 'rm', // I don't know 73 | 'ss' // Full synonym ($bodyArray[11]) 74 | ], 75 | 'sl' => null, // Source language 76 | 'tl' => null, // Target language 77 | 'q' => null, // String to translate 78 | 'ie' => 'UTF-8', // Input encoding 79 | 'oe' => 'UTF-8', // Output encoding 80 | 'multires' => 1, 81 | 'otf' => 0, 82 | 'pc' => 1, 83 | 'trs' => 1, 84 | 'ssel' => 0, 85 | 'tsel' => 0, 86 | 'kc' => 6, 87 | 'tk' => null, 88 | 'sp' => "nmt", 89 | 'tc' => 2, 90 | 'sr' => 1, 91 | 'mode' => 1 92 | ]; 93 | 94 | /** 95 | * @var array Regex key-value patterns to replace on response data 96 | */ 97 | protected $resultRegexes = [ 98 | '/,+/' => ',', 99 | '/\[,/' => '[', 100 | ]; 101 | 102 | /** 103 | * @var TokenProviderInterface Token provider 104 | */ 105 | protected $tokenProvider; 106 | 107 | /** 108 | * Class constructor. 109 | * 110 | * For more information about HTTP client configuration options, see "Request Options" in 111 | * GuzzleHttp docs: http://docs.guzzlephp.org/en/stable/request-options.html 112 | * 113 | * @param string $target Target language 114 | * @param string|null $source Source language 115 | * @param array|null $options Associative array of http client configuration options 116 | * @param TokenProviderInterface|null $tokenProvider 117 | */ 118 | public function __construct(string $target = 'en', string $source = null, array $options = null, TokenProviderInterface $tokenProvider = null) 119 | { 120 | $this->client = new Client(); 121 | $this->setTokenProvider($tokenProvider ?? new GoogleTokenGenerator) 122 | ->setOptions($options) // Options are already set in client constructor tho. 123 | ->setSource($source) 124 | ->setTarget($target); 125 | } 126 | 127 | /** 128 | * Set target language for translation. 129 | * 130 | * @param string $target Language code 131 | * @return GoogleTranslate 132 | */ 133 | public function setTarget(string $target) : self 134 | { 135 | $this->target = $target; 136 | return $this; 137 | } 138 | 139 | /** 140 | * Set source language for translation. 141 | * 142 | * @param string|null $source Language code 143 | * @return GoogleTranslate 144 | */ 145 | public function setSource(string $source = null) : self 146 | { 147 | $this->source = $source ?? 'auto'; 148 | return $this; 149 | } 150 | 151 | /** 152 | * Set Google Translate URL base 153 | * 154 | * @param string $url Google Translate URL base 155 | * @return GoogleTranslate 156 | */ 157 | public function setUrl(string $url) : self 158 | { 159 | $this->url = $url; 160 | return $this; 161 | } 162 | 163 | /** 164 | * Set GuzzleHttp client options. 165 | * 166 | * @param array $options guzzleHttp client options. 167 | * @return GoogleTranslate 168 | */ 169 | public function setOptions(array $options = null) : self 170 | { 171 | $this->options = $options ?? []; 172 | return $this; 173 | } 174 | 175 | /** 176 | * Set token provider. 177 | * 178 | * @param TokenProviderInterface $tokenProvider 179 | * @return GoogleTranslate 180 | */ 181 | public function setTokenProvider(TokenProviderInterface $tokenProvider) : self 182 | { 183 | $this->tokenProvider = $tokenProvider; 184 | return $this; 185 | } 186 | 187 | /** 188 | * Get last detected source language 189 | * 190 | * @return string|null Last detected source language 191 | */ 192 | public function getLastDetectedSource() 193 | { 194 | return $this->lastDetectedSource; 195 | } 196 | 197 | /** 198 | * Override translate method for static call. 199 | * 200 | * @param string $string 201 | * @param string $target 202 | * @param string|null $source 203 | * @param array $options 204 | * @param TokenProviderInterface|null $tokenProvider 205 | * @return null|string 206 | * @throws ErrorException If the HTTP request fails 207 | * @throws UnexpectedValueException If received data cannot be decoded 208 | */ 209 | public static function trans(string $string, string $target = 'en', string $source = null, array $options = [], TokenProviderInterface $tokenProvider = null) 210 | { 211 | return (new self) 212 | ->setTokenProvider($tokenProvider ?? new GoogleTokenGenerator) 213 | ->setOptions($options) // Options are already set in client constructor tho. 214 | ->setSource($source) 215 | ->setTarget($target) 216 | ->translate($string); 217 | } 218 | 219 | /** 220 | * Translate text. 221 | * 222 | * This can be called from instance method translate() using __call() magic method. 223 | * Use $instance->translate($string) instead. 224 | * 225 | * @param string $string String to translate 226 | * @return string|null 227 | * @throws ErrorException If the HTTP request fails 228 | * @throws UnexpectedValueException If received data cannot be decoded 229 | */ 230 | public function translate(string $string) : string 231 | { 232 | /* 233 | * if source lang and target lang are the same 234 | * just return the string without any request to google 235 | */ 236 | if ($this->source == $this->target) return $string; 237 | 238 | $responseArray = $this->getResponse($string); 239 | 240 | /* 241 | * if response in text and the content has zero the empty returns true, lets check 242 | * if response is string and not empty and create array for further logic 243 | */ 244 | if (is_string($responseArray) && $responseArray != '') { 245 | $responseArray = [$responseArray]; 246 | } 247 | 248 | // Check if translation exists 249 | if (!isset($responseArray[0]) || empty($responseArray[0])) { 250 | return null; 251 | } 252 | 253 | // Detect languages 254 | $detectedLanguages = []; 255 | 256 | // the response contains only single translation, don't create loop that will end with 257 | // invalid foreach and warning 258 | if (!is_string($responseArray)) { 259 | foreach ($responseArray as $item) { 260 | if (is_string($item)) { 261 | $detectedLanguages[] = $item; 262 | } 263 | } 264 | } 265 | 266 | // Another case of detected language 267 | if (isset($responseArray[count($responseArray) - 2][0][0])) { 268 | $detectedLanguages[] = $responseArray[count($responseArray) - 2][0][0]; 269 | } 270 | 271 | // Set initial detected language to null 272 | $this->lastDetectedSource = null; 273 | 274 | // Iterate and set last detected language 275 | foreach ($detectedLanguages as $lang) { 276 | if ($this->isValidLocale($lang)) { 277 | $this->lastDetectedSource = $lang; 278 | break; 279 | } 280 | } 281 | 282 | // the response can be sometimes an translated string. 283 | if (is_string($responseArray)) { 284 | return $responseArray; 285 | } else { 286 | if (is_array($responseArray[0])) { 287 | return (string) array_reduce($responseArray[0], function ($carry, $item) { 288 | $carry .= $item[0]; 289 | return $carry; 290 | }); 291 | } else { 292 | return (string) $responseArray[0]; 293 | } 294 | } 295 | } 296 | 297 | /** 298 | * Get response array. 299 | * 300 | * @param string $string String to translate 301 | * @throws ErrorException If the HTTP request fails 302 | * @throws UnexpectedValueException If received data cannot be decoded 303 | * @return array|string Response 304 | */ 305 | public function getResponse(string $string) : array 306 | { 307 | $queryArray = array_merge($this->urlParams, [ 308 | 'sl' => $this->source, 309 | 'tl' => $this->target, 310 | 'tk' => $this->tokenProvider->generateToken($this->source, $this->target, $string), 311 | 'q' => $string 312 | ]); 313 | 314 | $queryUrl = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($queryArray)); 315 | 316 | try { 317 | $response = $this->client->request('GET', $this->url, [ 318 | 'query' => $queryUrl, 319 | 'headers' => [ 320 | "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", 321 | "Accept" => "application/json, text/plain, */*", 322 | 'X-Requested-With'=> 'XMLHttpRequest' 323 | ] 324 | ] + $this->options); 325 | } catch (RequestException $e) { 326 | throw new ErrorException($e->getMessage()); 327 | } 328 | 329 | $body = $response->getBody(); // Get response body 330 | 331 | // Modify body to avoid json errors 332 | $bodyJson = preg_replace(array_keys($this->resultRegexes), array_values($this->resultRegexes), $body); 333 | 334 | // Decode JSON data 335 | if (($bodyArray = json_decode($bodyJson, true)) === null) { 336 | throw new UnexpectedValueException('Data cannot be decoded or it is deeper than the recursion limit'); 337 | } 338 | 339 | return $bodyArray; 340 | } 341 | 342 | /** 343 | * Check if given locale is valid. 344 | * 345 | * @param string $lang Langauge code to verify 346 | * @return bool 347 | */ 348 | protected function isValidLocale(string $lang) : bool 349 | { 350 | return (bool) preg_match('/^([a-z]{2})(-[A-Z]{2})?$/', $lang); 351 | } 352 | } 353 | --------------------------------------------------------------------------------