/
www
/
wwwroot
/
alo88.autos
/
wp-content
/
plugins
/
wp-content-crawler
/
app
/
Objects
/
Transformation
/
Translation
/
Clients
/
Upload File
HOME
<?php /** * Created by PhpStorm. * User: turgutsaricam * Date: 29/10/2019 * Time: 23:16 * * @since 1.9.0 */ namespace WPCCrawler\Objects\Transformation\Translation\Clients; use Exception; use GuzzleHttp\Client; use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Pool; use GuzzleHttp\Psr7\Request; use Psr\Http\Message\ResponseInterface; use WPCCrawler\Objects\Chunk\Enum\ChunkType; use WPCCrawler\Objects\Chunk\TransformationChunker; use WPCCrawler\Objects\Enums\InformationType; use WPCCrawler\Objects\Informing\Information; use WPCCrawler\Objects\Informing\Informer; use WPCCrawler\Objects\Settings\Enums\SettingKey; use WPCCrawler\Objects\Settings\SettingsImpl; use WPCCrawler\Objects\Transformation\Translation\Clients\AwsTranslate\Credentials\Credentials; use WPCCrawler\Objects\Transformation\Translation\Clients\AwsTranslate\Signature\SignatureV4; use WPCCrawler\Objects\Transformation\Translation\TextTranslator; use WPCCrawler\Utils; class AmazonTranslateAPIClient extends AbstractTranslateAPIClient { /* Notes: API Reference: https://docs.aws.amazon.com/translate/latest/dg/API_Reference.html Regions: https://docs.aws.amazon.com/general/latest/gr/rande.html#translate_region Limits: https://docs.aws.amazon.com/en_pv/translate/latest/dg/what-is-limits.html - Maximum text length per request is 5000 bytes. Not all ASCII characters are 1 byte. An ASCII character can be 4 bytes: https://stackoverflow.com/a/4850316/2883487 Hence, to be on the safe side, limiting the maximum character length to 5000/4 = 1250 is OK. Otherwise, the texts should be chunked by their byte size, not character count. - Character encoding should be UTF-8 - The requests sent to AWS must be signed. An example of how to create the signature: https://docs.aws.amazon.com/translate/latest/dg/examples-sigv4.html */ /** * @var string Format that can be used to create a base URL for the translate API. This has only 1 variable which * should be replaced by the code of a region that supports translate API. * E.g. sprintf($hostFormat, "us-west-2") */ private $hostFormat = 'https://translate.%s.amazonaws.com'; /** @var string */ private $accessKey; /** @var string */ private $secret; /** @var string */ private $region; /** @var null|Client */ private $client; /** @var string */ private $settingKeyFrom = SettingKey::WPCC_TRANSLATION_AMAZON_TRANSLATE_FROM; /** @var string */ private $settingKeyTo = SettingKey::WPCC_TRANSLATION_AMAZON_TRANSLATE_TO; /** @var string */ private $settingKeyAccessKeyId = SettingKey::WPCC_TRANSLATION_AMAZON_TRANSLATE_ACCESS_KEY; /** @var string */ private $settingKeySecretKey = SettingKey::WPCC_TRANSLATION_AMAZON_TRANSLATE_SECRET; /** @var string */ private $settingKeyRegion = SettingKey::WPCC_TRANSLATION_AMAZON_TRANSLATE_REGION; /** @var string */ private $keyAccessKey = 'access_key'; /** @var string */ private $keySecret = 'secret'; /** @var string */ private $keyRegion = 'region'; /** @var SignatureV4 */ private $signatureV4; /** @var Credentials */ private $credentials; /** * @param array $options An associative array that must have 'key' key. * 'access_key': The access key using which the requests will be authenticated. * 'secret': The secret using which the requests will be authenticated. * 'region': The AWS region to which the requests will be sent */ public function __construct($options = []) { parent::__construct($options); } /** * Initialize the API client using the already-set options (See {@link getOption()}) * * @return void * @since 1.9.0 */ public function init() { $this->accessKey = $this->getOption($this->keyAccessKey) ?? ''; $this->secret = $this->getOption($this->keySecret) ?? ''; $this->region = $this->getOption($this->keyRegion) ?? ''; $this->signatureV4 = new SignatureV4('translate', $this->region); $this->credentials = new Credentials($this->accessKey, $this->secret); } /** * Translate texts using the settings. This method might override the options given in the constructor. * * @param TextTranslator $textTranslator * @return array See {@link TextTranslator::translate()} * @throws Exception When the settings do not have the required options. * @since 1.9.0 * @uses TextTranslator::translate() */ public function translate(TextTranslator $textTranslator) { $from = $this->getFrom(); $to = $this->getTo(); if (!$from || !$to) { throw new Exception("From and to languages must be set."); } return $textTranslator->translate($this, [ // If the language should be detected, AWS wants the source language to be set to "auto" 'SourceLanguageCode' => $this->sanitizeFrom($from) ? $from : 'auto', 'TargetLanguageCode' => $to, ]); } /** * Set the options of this API client using given settings. * * @param SettingsImpl $settings * @return void * @throws Exception When the settings do not have the required options. * @since 1.9.0 */ public function setOptionsUsingSettings(SettingsImpl $settings) { $accessKey = $settings->getStringSetting($this->settingKeyAccessKeyId); $secret = $settings->getStringSetting($this->settingKeySecretKey); $region = $settings->getStringSetting($this->settingKeyRegion); if (!$accessKey || !$secret || !$region || !$this->isValidRegion($region)) { throw new Exception("You must provide a valid access key, a secret, and a region for Amazon Translate API to work properly."); } $this ->setFrom($settings->getStringSetting($this->settingKeyFrom)) ->setTo($settings->getStringSetting($this->settingKeyTo)); $this->setOptions([ $this->keyAccessKey => $accessKey, $this->keySecret => $secret, $this->keyRegion => $region, ]); } /** * Create the chunker that will be used to divide given texts into several parts to satisfy the requirements of the * API * * @param array $texts See {@link TransformationChunker::__construct()} * @return TransformationChunker * @throws Exception * @since 1.9.0 */ public function createChunker(array $texts): TransformationChunker { return new TransformationChunker(ChunkType::T_BYTES, $texts, 4800, 4800 * 100, 100); } /** * Translate an array of strings. * * @param array $texts A flat sequential array of texts * @param array $options Translation options * @return array Translated texts in the same order as $texts. If an error occurs, returns an empty array. * @since 1.8.0 */ public function translateBatch(array $texts, $options = []) { // Created by following the instructions in: // http://docs.guzzlephp.org/en/stable/quickstart.html#concurrent-requests $requests = function() use (&$texts, $options) { $defaultHeaders = [ 'Content-Type' => 'application/x-amz-json-1.1', 'X-Amz-Target' => 'AWSShineFrontendService_20170701.TranslateText', ]; // Create the endpoint URL using the selected region $uri = sprintf($this->hostFormat, $this->region); foreach($texts as $text) { // Add to-be-translated text to the options $options['Text'] = $text; // JSON-encode the options and add the encoded string as the request body $request = new Request('POST', $uri, $defaultHeaders, json_encode($options) ?: null); $signedRequest = $this->signatureV4->signRequest($request, $this->credentials); yield function() use (&$signedRequest, $options) { return $this->getClient()->sendAsync($signedRequest, $options); }; } }; $translations = []; $pool = new Pool($this->getClient(), $requests(), [ // Define how many requests can be sent in parallel 'concurrency' => 5, // This is delivered for each successful response 'fulfilled' => function ($response, $index) use (&$translations, &$texts) { /** @var mixed|ResponseInterface $response */ $translation = $this->onRequestFulfilled($response); $translations[$index] = $translation ?: $texts[$index]; }, // This is delivered for each failed request 'rejected' => function ($reason, $index) use (&$translations, &$texts) { /** @var mixed|ResponseInterface $reason */ $this->onRequestRejected($reason); $translations[$index] = $texts[$index]; }, ]); // Initiate the transfers and create a promise $promise = $pool->promise(); // Force the pool of requests to complete. $promise->wait(); ksort($translations); return $translations; } /** * Get languages * * @param array $options A key-value pair array that defines the options for to-be-retrieved languages * @return array An array structured as [ ["code" => "lang1code", "name" => "Lang 1 Name], ["code" => "lang2code", * "name" => "Lang 2 Name"], ... ] In case of error, returns an empty array. * @since 1.8.0 */ public function localizedLanguages($options = []) { // Amazon Translate does not have an endpoint to dynamically get the supported languages. Hence, we hard-code // them here, unfortunately. $langs = [ "Afrikaans" => "af", "Albanian" => "sq", "Amharic" => "am", "Arabic" => "ar", "Armenian" => "hy", "Azerbaijani" => "az", "Bengali" => "bn", "Bosnian" => "bs", "Bulgarian" => "bg", "Catalan" => "ca", "Chinese (Simplified)" => "zh", "Chinese (Traditional)" => "zh-TW", "Croatian" => "hr", "Czech" => "cs", "Danish" => "da", "Dari" => "fa-AF", "Dutch" => "nl", "English" => "en", "Estonian" => "et", "Farsi (Persian)" => "fa", "Filipino, Tagalog" => "tl", "Finnish" => "fi", "French" => "fr", "French (Canada)" => "fr-CA", "Georgian" => "ka", "German" => "de", "Greek" => "el", "Gujarati" => "gu", "Haitian Creole" => "ht", "Hausa" => "ha", "Hebrew" => "he", "Hindi" => "hi", "Hungarian" => "hu", "Icelandic" => "is", "Indonesian" => "id", "Irish" => "ga", "Italian" => "it", "Japanese" => "ja", "Kannada" => "kn", "Kazakh" => "kk", "Korean" => "ko", "Latvian" => "lv", "Lithuanian" => "lt", "Macedonian" => "mk", "Malay" => "ms", "Malayalam" => "ml", "Maltese" => "mt", "Marathi" => "mr", "Mongolian" => "mn", "Norwegian" => "no", "Pashto" => "ps", "Polish" => "pl", "Portuguese" => "pt", "Portuguese (Portugal)" => "pt-PT", "Punjabi" => "pa", "Romanian" => "ro", "Russian" => "ru", "Serbian" => "sr", "Sinhala" => "si", "Slovak" => "sk", "Slovenian" => "sl", "Somali" => "so", "Spanish" => "es", "Spanish (Mexico)" => "es-MX", "Swahili" => "sw", "Swedish" => "sv", "Tamil" => "ta", "Telugu" => "te", "Thai" => "th", "Turkish" => "tr", "Ukrainian" => "uk", "Urdu" => "ur", "Uzbek" => "uz", "Vietnamese" => "vi", "Welsh" => "cy", ]; $results = []; foreach($langs as $name => $code) { $results[] = [ "name" => $name, "code" => $code ]; } return $results; } public function getTestResultMessage(SettingsImpl $settings) { return sprintf( _wpcc('<b>From:</b> %1$s, <b>To:</b> %2$s, <b>Access Key:</b> %3$s, <b>Secret:</b> %4$s, <b>Region:</b> %5$s'), $settings->getSetting($this->settingKeyFrom), $settings->getSetting($this->settingKeyTo), $settings->getSetting($this->settingKeyAccessKeyId), $settings->getSetting($this->settingKeySecretKey), $settings->getSetting($this->settingKeyRegion) ); } /* * PRIVATE METHODS */ /** * @return Client * @since 1.9.0 */ private function getClient() { if (!$this->client) { $this->client = new Client(); } return $this->client; } /** * Handles a fulfilled request and returns the translation extracted from the response * * @param mixed|ResponseInterface $response * @return string|null If translation exists in the response, then the translation will be returned as a string. * Otherwise, null will be returned. * @since 1.9.0 */ private function onRequestFulfilled($response) { // Get the response text $responseText = $response->getBody()->getContents(); // Parse it to JSON $responseJson = json_decode($responseText, true); // Make sure the response is parsed to JSON correctly. if (!$responseJson) { $message = _wpcc('The response retrieved from Amazon Translate API could not be parsed to JSON. Message: %1$s'); $info = new Information($message, json_last_error_msg(), InformationType::ERROR); Informer::add($info->addAsLog()); return null; } $translation = Utils::array_get($responseJson, 'TranslatedText'); if (!$translation) { Informer::addError(_wpcc('Translation does not exist in the response retrieved from Amazon Translate API')) ->addAsLog(); return null; } return $translation; } /** * Handles a rejected request * * @param mixed|ResponseInterface $reason * @since 1.9.0 */ private function onRequestRejected($reason): void { // We add $reason as a detail message in case that the reason is an instance of an unknown class. For an unknown // class, the string representation of the instance will be used as a detail message. $info = new Information( _wpcc('Translation request was not successful'), (string) $reason, InformationType::ERROR ); // Handle the known classes if ($reason instanceof TransferException) { $info ->setException($reason) ->setDetails($reason->getMessage()); } Informer::add($info->addAsLog()); } /** * Check if a region is valid * * @param string $region Name of an AWS region available for Amazon Translate API * @return bool True if the region is valid. Otherwise, false. * @since 1.9.0 */ private function isValidRegion(string $region) { return array_key_exists($region, static::getRegions()); } /* * PUBLIC STATIC METHODS */ /** * Get AWS regions that can be used to access the translate API * * @return array Key-value pairs where keys are region codes and the values are names of the regions for humans * @link https://docs.aws.amazon.com/general/latest/gr/rande.html#translate_region * @since 1.9.0 */ public static function getRegions() { return [ "us-east-2" => "US East (Ohio)", "us-east-1" => "US East (N. Virginia)", "us-west-1" => "US West (N. California)", "us-west-2" => "US West (Oregon)", "af-south-1" => "Africa (Cape Town)", "ap-east-1" => "Asia Pacific (Hong Kong)", "ap-southeast-3" => "Asia Pacific (Jakarta)", "ap-south-1" => "Asia Pacific (Mumbai)", "ap-northeast-3" => "Asia Pacific (Osaka)", "ap-northeast-2" => "Asia Pacific (Seoul)", "ap-southeast-1" => "Asia Pacific (Singapore)", "ap-southeast-2" => "Asia Pacific (Sydney)", "ap-northeast-1" => "Asia Pacific (Tokyo)", "ca-central-1" => "Canada (Central)", "cn-north-1" => "China (Beijing)", "cn-northwest-1" => "China (Ningxia)", "eu-central-1" => "Europe (Frankfurt)", "eu-west-1" => "Europe (Ireland)", "eu-west-2" => "Europe (London)", "eu-south-1" => "Europe (Milan)", "eu-west-3" => "Europe (Paris)", "eu-north-1" => "Europe (Stockholm)", "me-south-1" => "Middle East (Bahrain)", "sa-east-1" => "South America (São Paulo)", "us-gov-west-1" => "AWS GovCloud (US-West)", ]; } }