From 67ae8b10d379b0b79a9a379ef3cdebf89e3fd5da Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Thu, 19 Jul 2018 21:16:15 +0200 Subject: [PATCH] Support mutiple strings in same request --- composer.json | 1 + phpunit.xml.dist | 1 + src/Service/BingTranslator.php | 107 +++++++++++++++++++ src/Service/GoogleTranslator.php | 23 +--- src/Service/HttpTranslator.php | 48 ++++++++- src/Service/YandexTranslator.php | 23 +--- src/Translator.php | 16 ++- src/TranslatorService.php | 11 ++ tests/Integration/AbstractTranslatorTest.php | 60 +++++++++++ tests/Integration/BingTranslatorTest.php | 29 +++++ tests/Integration/GoogleTranslatorTest.php | 13 +-- tests/Integration/YandexTranslatorTest.php | 23 ++-- 12 files changed, 291 insertions(+), 64 deletions(-) create mode 100644 src/Service/BingTranslator.php create mode 100644 tests/Integration/AbstractTranslatorTest.php create mode 100644 tests/Integration/BingTranslatorTest.php diff --git a/composer.json b/composer.json index 4b290a0..cc42811 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ ], "require": { "php": "^5.5 || ^7.0", + "ext-mbstring": "*", "php-http/httplug": "^1.0", "php-http/client-implementation": "^1.0", "php-http/discovery": "^1.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3c59ce2..540a286 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -24,6 +24,7 @@ + diff --git a/src/Service/BingTranslator.php b/src/Service/BingTranslator.php new file mode 100644 index 0000000..ff888de --- /dev/null +++ b/src/Service/BingTranslator.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Translation\Translator\Service; + +use Http\Client\HttpClient; +use Http\Message\RequestFactory; +use Psr\Http\Message\ResponseInterface; +use Translation\Translator\Exception\ResponseException; + +/** + * @author Baptiste Leduc + */ +class BingTranslator extends HttpTranslator +{ + /** + * @var string + */ + private $key; + + /** + * @param string $key Google API key + * @param HttpClient|null $httpClient + * @param RequestFactory|null $requestFactory + */ + public function __construct($key, HttpClient $httpClient = null, RequestFactory $requestFactory = null) + { + parent::__construct($httpClient, $requestFactory); + if (empty($key)) { + throw new \InvalidArgumentException('Bing "key" can not be empty'); + } + + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + public function translate($string, $from, $to) + { + $body = json_encode([['Text' => $string]]); + $url = $this->getUrl($from, $to); + $request = $this->getRequestFactory()->createRequest('POST', $url, [], $body); + + $request = $request + ->withHeader('Ocp-Apim-Subscription-Key', $this->key) + ->withHeader('Content-Type', 'application/json') + ->withHeader('X-ClientTraceId', $this->createGuid()) + ->withHeader('Content-length', strlen($body)); + + /** @var ResponseInterface $response */ + $response = $this->getHttpClient()->sendRequest($request); + + if (200 !== $response->getStatusCode()) { + throw ResponseException::createNonSuccessfulResponse($this->getUrl($string, $from, $to, '[key]')); + } + + $responseBody = $response->getBody()->__toString(); + $data = json_decode($responseBody, true); + + if (!is_array($data)) { + throw ResponseException::createUnexpectedResponse($url, $responseBody); + } + + foreach ($data as $details) { + return $this->format($string, $details['translations'][0]['text']); + } + } + + /** + * @param string $from + * @param string $to + * + * @return string + */ + private function getUrl($from, $to) + { + return sprintf( + 'https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=%s&from=%s&textType=html', + $to, + $from + ); + } + + /** + * @return string + */ + private function createGuid() + { + return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + mt_rand(0, 0xffff), + mt_rand(0, 0x0fff) | 0x4000, + mt_rand(0, 0x3fff) | 0x8000, + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } +} diff --git a/src/Service/GoogleTranslator.php b/src/Service/GoogleTranslator.php index 1067475..26c5710 100644 --- a/src/Service/GoogleTranslator.php +++ b/src/Service/GoogleTranslator.php @@ -16,12 +16,11 @@ use Http\Message\RequestFactory; use Psr\Http\Message\ResponseInterface; use Translation\Translator\Exception\ResponseException; -use Translation\Translator\TranslatorService; /** * @author Tobias Nyholm */ -class GoogleTranslator extends HttpTranslator implements TranslatorService +class GoogleTranslator extends HttpTranslator { /** * @var string @@ -88,24 +87,4 @@ private function getUrl($string, $from, $to, $key) urlencode($string) ); } - - /** - * @param string $original - * @param string $translationHtmlEncoded - * - * @return string - */ - private function format($original, $translationHtmlEncoded) - { - $translation = html_entity_decode($translationHtmlEncoded, ENT_QUOTES | ENT_HTML401, 'UTF-8'); - - // if capitalized, make sure we also capitalize. - $firstChar = mb_substr($original, 0, 1); - if (mb_strtoupper($firstChar) === $firstChar) { - $first = mb_strtoupper(mb_substr($translation, 0, 1)); - $translation = $first.mb_substr($translation, 1); - } - - return $translation; - } } diff --git a/src/Service/HttpTranslator.php b/src/Service/HttpTranslator.php index be1f0ea..cb317a1 100644 --- a/src/Service/HttpTranslator.php +++ b/src/Service/HttpTranslator.php @@ -16,11 +16,12 @@ use Http\Discovery\HttpClientDiscovery; use Http\Discovery\MessageFactoryDiscovery; use Http\Message\RequestFactory; +use Translation\Translator\TranslatorService; /** * @author Tobias Nyholm */ -abstract class HttpTranslator +abstract class HttpTranslator implements TranslatorService { /** * @var HttpClient @@ -57,4 +58,49 @@ protected function getRequestFactory() { return $this->requestFactory; } + + /** + * {@inheritdoc} + */ + public function translateArray($strings, $from, $to) + { + $array = []; + + foreach ($strings as $string) { + $array[] = $this->translate($string, $from, $to); + } + + return $array; + } + + /** + * @param string $original + * @param string $translationHtmlEncoded + * + * @return string + */ + protected function format($original, $translationHtmlEncoded) + { + $translation = htmlspecialchars_decode($translationHtmlEncoded); + + // if capitalized, make sure we also capitalize. + $firstChar = \mb_substr($original, 0, 1); + $originalIsUpper = \mb_strtoupper($firstChar) === $firstChar; + + if ($originalIsUpper) { + $first = \mb_strtoupper(\mb_substr($translation, 0, 1)); + $translation = $first.\mb_substr($translation, 1); + } + + // also check on translated if capitalize and original isn't + $transFirstChar = \mb_substr($translationHtmlEncoded, 0, 1); + $translationIsUpper = \mb_strtoupper($transFirstChar) === $transFirstChar; + + if (!$originalIsUpper && $translationIsUpper) { + $first = \mb_strtolower(\mb_substr($translation, 0, 1)); + $translation = $first.\mb_substr($translation, 1); + } + + return $translation; + } } diff --git a/src/Service/YandexTranslator.php b/src/Service/YandexTranslator.php index b5dae91..2a0c3e3 100644 --- a/src/Service/YandexTranslator.php +++ b/src/Service/YandexTranslator.php @@ -16,12 +16,11 @@ use Http\Message\RequestFactory; use Psr\Http\Message\ResponseInterface; use Translation\Translator\Exception\ResponseException; -use Translation\Translator\TranslatorService; /** * @author Tobias Nyholm */ -class YandexTranslator extends HttpTranslator implements TranslatorService +class YandexTranslator extends HttpTranslator { /** * @var string @@ -88,24 +87,4 @@ private function getUrl($string, $from, $to, $key) urlencode($string) ); } - - /** - * @param string $original - * @param string $translationHtmlEncoded - * - * @return string - */ - private function format($original, $translationHtmlEncoded) - { - $translation = htmlspecialchars_decode($translationHtmlEncoded); - - // if capitalized, make sure we also capitalize. - $firstChar = mb_substr($original, 0, 1); - if (mb_strtoupper($firstChar) === $firstChar) { - $first = mb_strtoupper(mb_substr($translation, 0, 1)); - $translation = $first.mb_substr($translation, 1); - } - - return $translation; - } } diff --git a/src/Translator.php b/src/Translator.php index 2b34c69..e5e3080 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -42,10 +42,24 @@ final class Translator implements LoggerAwareInterface, TranslatorService * @return null|string Null is return when all translators failed. */ public function translate($string, $from, $to) + { + list($result) = $this->translateArray([$string], $from, $to); + + return $result; + } + + /** + * @param array $strings + * @param string $from + * @param string $to + * + * @return null|array Null is return when all translators failed. + */ + public function translateArray($strings, $from, $to) { foreach ($this->translatorServices as $service) { try { - return $service->translate($string, $from, $to); + return $service->translateArray($strings, $from, $to); } catch (TranslatorException\NoTranslationFoundException $e) { // Do nothing, try again. } catch (TranslatorException $e) { diff --git a/src/TranslatorService.php b/src/TranslatorService.php index 5d59523..5918773 100644 --- a/src/TranslatorService.php +++ b/src/TranslatorService.php @@ -27,4 +27,15 @@ interface TranslatorService * @throws \Translation\Translator\Exception if we could not translate string */ public function translate($string, $from, $to); + + /** + * @param array $strings array of strings to translate + * @param string $from from what locale + * @param string $to to what locale + * + * @return array Return the translated strings + * + * @throws \Translation\Translator\Exception if we could not translate string + */ + public function translateArray($strings, $from, $to); } diff --git a/tests/Integration/AbstractTranslatorTest.php b/tests/Integration/AbstractTranslatorTest.php new file mode 100644 index 0000000..3124462 --- /dev/null +++ b/tests/Integration/AbstractTranslatorTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Translation\translator\tests\Integration; + +use PHPUnit\Framework\TestCase; +use Translation\Translator\TranslatorService; + +/** + * @author Baptiste Leduc + */ +abstract class AbstractTranslatorTest extends TestCase +{ + /** + * @var TranslatorService + */ + protected $translator = null; + + /** + * @var array + */ + protected $requested = ['apple', 'cherry']; + + /** + * @var array + */ + protected $expected = ['pomme', 'cerise']; + + /** + * @var string + */ + protected $from = 'en'; + + /** + * @var string + */ + protected $to = 'fr'; + + public function testTranslate() + { + if (null === $this->translator) { + $this->markTestSkipped('No translator set.'); + } + + $result = $this->translator->translate($this->requested[0], $this->from, $this->to); + $this->assertEquals($this->expected[0], $result); + + $results = $this->translator->translateArray($this->requested, $this->from, $this->to); + $this->assertEquals($this->expected, $results); + } +} diff --git a/tests/Integration/BingTranslatorTest.php b/tests/Integration/BingTranslatorTest.php new file mode 100644 index 0000000..92cd43b --- /dev/null +++ b/tests/Integration/BingTranslatorTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +namespace Translation\translator\tests\Integration; + +use Translation\Translator\Service\BingTranslator; + +/** + * @author Baptiste Leduc + */ +class BingTranslatorTest extends AbstractTranslatorTest +{ + public function setUp() + { + $key = getenv('BING_KEY'); + if (!empty($key)) { + $this->translator = new BingTranslator($key); + } + } +} diff --git a/tests/Integration/GoogleTranslatorTest.php b/tests/Integration/GoogleTranslatorTest.php index cf499be..d15c3b2 100644 --- a/tests/Integration/GoogleTranslatorTest.php +++ b/tests/Integration/GoogleTranslatorTest.php @@ -12,23 +12,18 @@ namespace Translation\translator\tests\Integration; -use PHPUnit\Framework\TestCase; use Translation\Translator\Service\GoogleTranslator; /** * @author Tobias Nyholm */ -class GoogleTranslatorTest extends TestCase +class GoogleTranslatorTest extends AbstractTranslatorTest { - public function testTranslate() + public function setUp() { $key = getenv('GOOGLE_KEY'); - if (empty($key)) { - $this->markTestSkipped('No Google key in environment'); + if (!empty($key)) { + $this->translator = new GoogleTranslator($key); } - - $translator = new GoogleTranslator($key); - $result = $translator->translate('Grattis, du är klar!', 'sv', 'en'); - $this->assertEquals('Congratulations, you\'re done!', $result); } } diff --git a/tests/Integration/YandexTranslatorTest.php b/tests/Integration/YandexTranslatorTest.php index 1fd6e64..9a414ae 100644 --- a/tests/Integration/YandexTranslatorTest.php +++ b/tests/Integration/YandexTranslatorTest.php @@ -12,23 +12,28 @@ namespace Translation\translator\tests\Integration; -use PHPUnit\Framework\TestCase; use Translation\Translator\Service\YandexTranslator; /** * @author Tobias Nyholm */ -class YandexTranslatorTest extends TestCase +class YandexTranslatorTest extends AbstractTranslatorTest { - public function testTranslate() + /** + * @var array + */ + protected $expected = ['яблоко', 'вишня']; + + /** + * @var string + */ + protected $to = 'ru'; + + public function setUp() { $key = getenv('YANDEX_KEY'); - if (empty($key)) { - $this->markTestSkipped('No Yandex key in environment'); + if (!empty($key)) { + $this->translator = new YandexTranslator($key); } - - $translator = new YandexTranslator($key); - $result = $translator->translate('apple', 'en', 'ru'); - $this->assertEquals('яблоко', $result); } }