From 4fe5cd156ebc72be8122426a193f8aebde409e98 Mon Sep 17 00:00:00 2001 From: Andrew H Date: Thu, 1 Aug 2024 18:50:03 -0400 Subject: [PATCH 1/3] feat: retrieve a list of supported languages --- README.md | 38 +++++++++- composer.json | 1 + src/Exceptions/LanguagesRequestException.php | 10 +++ src/GoogleTranslate.php | 79 +++++++++++++++++++- tests/ExceptionTest.php | 8 ++ tests/SupportedLanguagesTest.php | 72 ++++++++++++++++++ 6 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 src/Exceptions/LanguagesRequestException.php create mode 100644 tests/SupportedLanguagesTest.php diff --git a/README.md b/README.md index cfc26c4..1c3e44a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Free Google Translate API PHP Package. Translates totally free of charge. - **[Basic Usage](#basic-usage)** - [Advanced Usage](#advanced-usage) - [Language Detection](#language-detection) + - [Supported Languages](#supported-languages) - [Preserving Parameters](#preserving-parameters) - [Using Raw Response](#using-raw-response) - [Custom URL](#custom-url) @@ -92,7 +93,41 @@ echo $tr->getLastDetectedSource(); // Output: en Return value will be `null` if the language couldn't be detected. -Supported languages are listed in [Google API docs](https://cloud.google.com/translate/docs/languages). +### Supported Languages + +You can get a list of all the supported languages using the `languages` method. + +```php +$tr = new GoogleTranslate(); + +$languages = $tr->languages(); // Get supported languages in iso-639 format + +// Output: [ 'ab', 'ace', 'ach', 'aa', 'af', 'sq', 'alz', ... ] +``` + +Optionally, pass a target language code to retrieve supported languages with names displayed in that language. + +```php +$tr = new GoogleTranslate(); + +$languages = $tr->languages('en'); // Get supported languages, display name in english +// Output: [ 'en' => English', 'es' => 'Spanish', 'it' => 'Italian', ... ] + +echo $languages['en']; // Output: 'English' +echo $languages['ka']; // Output: 'Georgian' +``` + +Same as with the `translate`/`trans` methods, you can also use a static `langs` method: + +```php +GoogleTranslate::langs(); +// Output: [ 'ab', 'ace', 'ach', 'aa', 'af', 'sq', 'alz', ... ] + +GoogleTranslate::langs('en'); +// Output: [ 'en' => English', 'es' => 'Spanish', 'it' => 'Italian', ... ] +``` + +Supported languages are also listed in [Google API docs](https://cloud.google.com/translate/docs/languages). ### Preserving Parameters @@ -241,4 +276,3 @@ If this package helped you reduce your time to develop something, or it solved a - [Patreon](https://www.patreon.com/stichoza) - [PayPal](https://paypal.me/stichoza) - diff --git a/composer.json b/composer.json index 123aea2..eddad93 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "require": { "php": "^8.0", "guzzlehttp/guzzle": "^7.0", + "ext-dom": "*", "ext-json": "*", "ext-mbstring": "*" }, diff --git a/src/Exceptions/LanguagesRequestException.php b/src/Exceptions/LanguagesRequestException.php new file mode 100644 index 0000000..fb5e8af --- /dev/null +++ b/src/Exceptions/LanguagesRequestException.php @@ -0,0 +1,10 @@ + $replacements[$matches[1]], @@ -456,4 +457,80 @@ protected function isValidLocale(string $lang): bool { return (bool) preg_match('/^([a-z]{2,3})(-[A-Za-z]{2,4})?$/', $lang); } + + /** + * Fetch the list of supported languages from Google Translate + * + * @param string|null $target iso code of display language, when null returns only iso codes + * @return string[]|array + * @throws RateLimitException + * @throws LanguagesRequestException + */ + public static function langs(?string $target = null): array + { + return (new self)->languages($target); + } + + /** + * Fetch the list of supported languages from Google Translate + * + * @param string|null $target iso code of display language, when null returns only iso codes + * @return string[]|array + * @throws RateLimitException + * @throws LanguagesRequestException + */ + public function languages(?string $target = null): array + { + $languages = $this->localizedLanguages($target ?? $this->target ?? $this->source ?? ''); + + if ($target === null) { + return array_keys($languages); + } + + return $languages; + } + + /** + * Fetch the list of supported languages from Google Translate + * + * @param string $target iso code of localized display language + * @return array + * @throws RateLimitException + * @throws LanguagesRequestException + */ + public function localizedLanguages(string $target): array + { + $menu = 'sl'; // 'tl'; + $url = "https://translate.google.com/m?mui=$menu&hl=$target"; + + try { + $response = $this->client->get($url, $this->options); + } catch (GuzzleException $e) { + match ($e->getCode()) { + 429, 503 => throw new RateLimitException($e->getMessage(), $e->getCode()), + default => throw new LanguagesRequestException($e->getMessage(), $e->getCode()), + }; + } catch (Throwable $e) { + throw new LanguagesRequestException($e->getMessage(), $e->getCode()); + } + + // add a meta tag to ensure the HTML content is treated as UTF-8, fixes xpath node values + $html = preg_replace('//i', '', $response->getBody()->getContents()); + + // Prepare to crawl DOM + $dom = new \DOMDocument(); + @$dom->loadHTML($html); + $xpath = new \DOMXPath($dom); + + $nodes = $xpath->query('//div[@class="language-item"]/a'); + + $languages = []; + foreach ($nodes as $node) { + $href = $node->getAttribute('href'); + $code = strtok(substr($href, strpos($href, "$menu=") + strlen("$menu=")), '&'); + $languages[$code] = $node->nodeValue; + } + + return $languages; + } } diff --git a/tests/ExceptionTest.php b/tests/ExceptionTest.php index 5fb72ef..b50063f 100644 --- a/tests/ExceptionTest.php +++ b/tests/ExceptionTest.php @@ -4,6 +4,7 @@ use ErrorException; use PHPUnit\Framework\TestCase; +use Stichoza\GoogleTranslate\Exceptions\LanguagesRequestException; use Stichoza\GoogleTranslate\Exceptions\LargeTextException; use Stichoza\GoogleTranslate\Exceptions\RateLimitException; use Stichoza\GoogleTranslate\Exceptions\TranslationDecodingException; @@ -68,4 +69,11 @@ public function testInheritanceForErrorException(): void $this->tr->setUrl('https://httpstat.us/413')->translate('Test'); } + + public function testLanguagesRequestException(): void + { + $this->expectException(LanguagesRequestException::class); + + $this->tr->setUrl('https://httpstat.us/418')->languages(); + } } diff --git a/tests/SupportedLanguagesTest.php b/tests/SupportedLanguagesTest.php new file mode 100644 index 0000000..ba9ec50 --- /dev/null +++ b/tests/SupportedLanguagesTest.php @@ -0,0 +1,72 @@ +tr = new GoogleTranslate(); + } + + public function testLanguageCodesRequest(): void + { + $result = $this->tr->languages(); + $this->assertContains('en', $result); + $this->assertContains('fr', $result); + $this->assertContains('ka', $result); + $this->assertContains('it', $result); + $this->assertContains('pt', $result); + $this->assertContains('pt-PT', $result); + $this->assertContains('pl', $result); + $this->assertContains('vi', $result); + $this->assertContains('ja', $result); + $this->assertContains('et', $result); + $this->assertContains('hr', $result); + $this->assertContains('es', $result); + $this->assertContains('zh-CN', $result); + $this->assertContains('zh-TW', $result); + } + + public function testLocalizedLanguages(): void + { + $result = $this->tr->languages('en'); + $this->assertEquals('English', $result['en']); + $this->assertEquals('French', $result['fr']); + $this->assertEquals('Georgian', $result['ka']); + $this->assertEquals('Italian', $result['it']); + $this->assertEquals('Portuguese (Brazil)', $result['pt']); + + $result = $this->tr->languages('ka'); + $this->assertEquals('ინგლისური', $result['en']); + $this->assertEquals('ფრანგული', $result['fr']); + $this->assertEquals('ქართული', $result['ka']); + $this->assertEquals('იტალიური', $result['it']); + $this->assertEquals('პორტუგალიური (ბრაზილია)', $result['pt']); + + $result = $this->tr->languages('pt'); + $this->assertEquals('Inglês', $result['en']); + $this->assertEquals('Francês', $result['fr']); + $this->assertEquals('Georgiano', $result['ka']); + $this->assertEquals('Italiano', $result['it']); + $this->assertEquals('Português (Brasil)', $result['pt']); + } + + public function testLanguagesEquality(): void + { + $resultOne = GoogleTranslate::langs(); + $resultTwo = $this->tr->languages(); + + $this->assertEqualsIgnoringCase($resultOne, $resultTwo, 'Static and instance methods should return same result.'); + + $resultOne = GoogleTranslate::langs('pt'); + $resultTwo = $this->tr->languages('pt'); + + $this->assertEqualsIgnoringCase($resultOne, $resultTwo, 'Static and instance methods should return same result.'); + } +} From a0f4a83bc39aa6a32a11210a013f125c9d8e6591 Mon Sep 17 00:00:00 2001 From: Andrew H Date: Sat, 3 Aug 2024 12:47:35 -0400 Subject: [PATCH 2/3] fix: reuse the base url to get languages + also fixes failing exceptions test --- src/GoogleTranslate.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/GoogleTranslate.php b/src/GoogleTranslate.php index dc1769d..0967a2b 100644 --- a/src/GoogleTranslate.php +++ b/src/GoogleTranslate.php @@ -501,7 +501,8 @@ public function languages(?string $target = null): array public function localizedLanguages(string $target): array { $menu = 'sl'; // 'tl'; - $url = "https://translate.google.com/m?mui=$menu&hl=$target"; + $url = parse_url($this->url); + $url = $url['scheme'].'://'.$url['host']."/m?mui=$menu&hl=$target"; try { $response = $this->client->get($url, $this->options); From 9885bb5588e77c13f80cd801843fa59b3f8fed8e Mon Sep 17 00:00:00 2001 From: Andrew H Date: Sat, 3 Aug 2024 12:51:51 -0400 Subject: [PATCH 3/3] fix: drop @ error supression on DOM loadHTML --- src/GoogleTranslate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GoogleTranslate.php b/src/GoogleTranslate.php index 0967a2b..3dc1e92 100644 --- a/src/GoogleTranslate.php +++ b/src/GoogleTranslate.php @@ -520,7 +520,7 @@ public function localizedLanguages(string $target): array // Prepare to crawl DOM $dom = new \DOMDocument(); - @$dom->loadHTML($html); + $dom->loadHTML($html); $xpath = new \DOMXPath($dom); $nodes = $xpath->query('//div[@class="language-item"]/a');