diff --git a/Classes/Domain/Service/DeepLService.php b/Classes/Domain/Service/DeepLService.php index 3953817..1fddca9 100644 --- a/Classes/Domain/Service/DeepLService.php +++ b/Classes/Domain/Service/DeepLService.php @@ -3,9 +3,6 @@ namespace CodeQ\DeepLTranslationHelper\Domain\Service; use Neos\Flow\Annotations as Flow; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\ClientException; -use GuzzleHttp\Exception\GuzzleException; use Psr\Log\LoggerInterface; /** @@ -13,10 +10,6 @@ */ class DeepLService { - /** - * @var Client|null - */ - protected ?Client $deeplClient = null; /** * @var array @@ -30,80 +23,86 @@ class DeepLService */ protected $logger; - protected function initializeObject() - { - $this->deeplClient = new Client([ - 'base_uri' => $this->settings['baseUri'], - 'timeout' => 0, - 'headers' => [ - 'Authorization' => sprintf('DeepL-Auth-Key %s', $this->settings['apiAuthKey']) - ] - ]); - } - /** - * @param string $text - * @param string $targetLanguage - * + * @param string[] $texts + * @param string $targetLanguage * @param string|null $sourceLanguage - * - * @return string + * @return array */ - public function translate( - string $text, - string $targetLanguage, - string $sourceLanguage = null - ): string { - if ($sourceLanguage === $targetLanguage) { - return $text; + public function translate(array $texts, string $targetLanguage, ?string $sourceLanguage = null): array + { + $keys = array_keys($texts); + $values = array_values($texts); + + $baseUri = $this->settings['useFreeApi'] ? $this->settings['baseUriFree'] : $this->settings['baseUri']; + + $curlHandle = curl_init($baseUri . 'translate'); + curl_setopt($curlHandle, CURLOPT_TIMEOUT, 0); + curl_setopt($curlHandle, CURLOPT_HTTPHEADER, ['Expect:']); + curl_setopt($curlHandle, CURLOPT_POST, true); + curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_setopt($curlHandle, CURLOPT_HTTPHEADER, [ + 'Accept: */*', + 'Content-Type: application/x-www-form-urlencoded', + sprintf('Authorization: DeepL-Auth-Key %s', $this->settings['apiAuthKey']) + ]); + + // create request body ... neither psr nor guzzle can create the body format that + // is required here + $body = http_build_query($this->settings['defaultOptions']); + if ($sourceLanguage) { + $body .= '&source_lang=' . urlencode($sourceLanguage); } + $body .= '&target_lang=' . urlencode($targetLanguage); - try { - $response = $this->deeplClient->get('translate', [ - 'query' => [ - 'text' => $text, - 'source_lang' => $sourceLanguage, - 'target_lang' => $targetLanguage, - 'tag_handling' => 'xml', - 'split_sentences' => 'nonewlines' - ] - ]); - - $responseBody = json_decode($response->getBody()->getContents(), - true); - $translations = $responseBody['translations']; - $translatedText = $translations[0]['text']; - } catch (ClientException $e) { - if ($e->getResponse()->getStatusCode() === 403) { + foreach($values as $part) { + $body .= '&text=' . urlencode($part); + } + + curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $body); + + // return + $curlResult = curl_exec($curlHandle); + if ($curlResult === false) { + return $texts; + } + + $status = curl_getinfo($curlHandle, CURLINFO_RESPONSE_CODE); + + if ($status != 200) { + if ($status === 403) { $this->logger->critical('Your DeepL API credentials are either wrong, or you don\'t have access to the requested API.'); - } elseif ($e->getResponse()->getStatusCode() === 429) { + } elseif ($status === 429) { $this->logger->warning('You sent too many requests to the DeepL API, we\'ll retry to connect to the API on the next request'); - } elseif ($e->getResponse()->getStatusCode() === 456) { + } elseif ($status === 456) { $this->logger->warning('You reached your DeepL API character limit. Upgrade your plan or wait until your quota is filled up again.'); - } elseif ($e->getResponse()->getStatusCode() === 400) { + } elseif ($status === 400) { $this->logger->warning('Your DeepL API request was not well-formed. Please check the source and the target language in particular.', [ 'sourceLanguage' => $sourceLanguage, 'targetLanguage' => $targetLanguage ]); } else { - $this->logger->warning('The DeepL API request did not complete successfully, see status code and message below.', [ - 'statusCode' => $e->getResponse()->getStatusCode(), - 'message' => $e->getResponse()->getBody()->getContents() - ]); + $this->logger->warning('Unexpected status from Deepl API', ['status' => $status]); } + return $texts; + } - // If the call went wrong, return the original text - $translatedText = $text; - } catch (GuzzleException $e) { - $this->logger->warning('The DeepL API request did not complete successfully, see status code and message below.', [ - 'statusCode' => $e->getResponse()->getStatusCode(), - 'message' => $e->getResponse()->getBody()->getContents() - ]); + curl_close($curlHandle); - // If the call went wrong, return the original text - $translatedText = $text; + $returnedData = json_decode($curlResult, true); + + if (is_null($returnedData)) { + return $texts; } - return $translatedText; + $translations = array_map( + function($part) { + return $part['text']; + }, + $returnedData['translations'] + ); + + return array_combine($keys, $translations); } } diff --git a/Classes/Domain/Service/NodeTranslationService.php b/Classes/Domain/Service/NodeTranslationService.php index 5cb7bef..81a4984 100644 --- a/Classes/Domain/Service/NodeTranslationService.php +++ b/Classes/Domain/Service/NodeTranslationService.php @@ -5,6 +5,7 @@ use Neos\Flow\Annotations as Flow; use Neos\ContentRepository\Domain\Model\NodeInterface; use Neos\ContentRepository\Domain\Service\Context; +use Psr\Log\LoggerInterface; class NodeTranslationService { @@ -16,7 +17,13 @@ class NodeTranslationService protected $deeplService; /** - * @Flow\InjectConfiguration(path="translateRichtextProperties") + * @Flow\Inject + * @var LoggerInterface + */ + protected $logger; + + /** + * @Flow\InjectConfiguration(path="nodeTranslations.translateInlineEditables") * @var bool */ protected $translateRichtextProperties; @@ -35,6 +42,7 @@ public function afterAdoptNode(NodeInterface $node, Context $context, $recursive $sourceLanguage = explode('_', $node->getContext()->getTargetDimensions()['language'])[0]; $targetLanguage = explode('_', $context->getTargetDimensions()['language'])[0]; + $propertiesToTranslate = []; foreach ($adoptedNode->getProperties() as $propertyName => $propertyValue) { if (empty($propertyValue)) { @@ -46,10 +54,13 @@ public function afterAdoptNode(NodeInterface $node, Context $context, $recursive if ($propertyDefinitions[$propertyName]['type'] != 'string' || !is_string($propertyValue)) { continue; } + if ((trim(strip_tags($propertyValue))) == "") { + continue; + } $translateProperty = false; $isInlineEditable = $propertyDefinitions[$propertyName]['ui']['inlineEditable'] ?? false; - $isTranslateEnabled = $propertyDefinitions[$propertyName]['options']['autotranslate'] ?? false; + $isTranslateEnabled = $propertyDefinitions[$propertyName]['options']['deeplTranslate'] ?? false; if ($this->translateRichtextProperties && $isInlineEditable == true) { $translateProperty = true; } @@ -58,7 +69,13 @@ public function afterAdoptNode(NodeInterface $node, Context $context, $recursive } if ($translateProperty) { - $translatedValue = $this->deeplService->translate($propertyValue, $targetLanguage, $sourceLanguage); + $propertiesToTranslate[$propertyName] = $propertyValue; + } + } + + if (count($propertiesToTranslate) > 0) { + $translatedProperties = $this->deeplService->translate($propertiesToTranslate, $targetLanguage, $sourceLanguage); + foreach($translatedProperties as $propertyName => $translatedValue) { $adoptedNode->setProperty($propertyName, $translatedValue); } } diff --git a/Classes/EelHelper/TranslationHelper.php b/Classes/EelHelper/TranslationHelper.php index 078dbc9..ce97852 100644 --- a/Classes/EelHelper/TranslationHelper.php +++ b/Classes/EelHelper/TranslationHelper.php @@ -35,8 +35,8 @@ public function translate(string $text, string $targetLanguage, string $sourceLa if ($translatedText = $this->translationCache->get($cacheIdentifier)) { return $translatedText; } - $translatedText = $this->deepLService->translate($text, $targetLanguage, $sourceLanguage); - $this->translationCache->set($cacheIdentifier, $translatedText); + $translatedTexts = $this->deepLService->translate(['text' => $text], $targetLanguage, $sourceLanguage); + $this->translationCache->set($cacheIdentifier, $translatedTexts['text']); return $translatedText; } diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml new file mode 100644 index 0000000..6d3079c --- /dev/null +++ b/Configuration/NodeTypes.yaml @@ -0,0 +1,11 @@ +'Neos.Neos:Document': + properties: + title: + options: + deeplTranslate: true + titleOverride: + options: + deeplTranslate: true + metaDescription: + options: + deeplTranslate: true diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 3f1ffea..c2e2e34 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -1,9 +1,20 @@ CodeQ: DeepLTranslationHelper: DeepLService: - baseUri: 'https://api.deepl.com/v2/' + useFreeApi: false apiAuthKey: '' - translateRichtextProperties: true + + baseUri: 'https://api.deepl.com/v2/' + baseUriFree: 'https://api-free.deepl.com/v2/' + + defaultOptions: + tag_handling: 'xml' + split_sentences: 'nonewlines' + preserve_formatting: 1 + formality: "default" + + nodeTranslations: + translateInlineEditables: true Neos: Fusion: diff --git a/README.md b/README.md index 9850f97..85962aa 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ If you are using the free API, you need to change the baseUri: CodeQ: DeepLTranslationHelper: DeepLService: - baseUri: 'https://api-free.deepl.com/v2/' + useFreeApi: true apiAuthKey: 'myapikey' ``` @@ -50,3 +50,27 @@ CodeQ_DeepLTranslationHelper_Translation: backendOptions: defaultLifetime: 2592000 ``` + + +## Node Translations + +When nodes are copied (adopted) into another languge the fields can be translated automatically. + +The following setting enables the translation of all inlineEditable properties. + +```yaml +CodeQ: + DeepLTranslationHelper: + nodeTranslations: + translateInlineEditables: true +``` + +Other properties of type string can be translated aswell with the following configuration. + + ```yaml +Neos.Neos:Document: + properties: + title: + options: + deeplTranslate: true +``` diff --git a/composer.json b/composer.json index cdeb45d..250f9cf 100644 --- a/composer.json +++ b/composer.json @@ -3,8 +3,7 @@ "type": "neos-package", "name": "codeq/deepltranslationhelper", "require": { - "neos/flow": "*", - "guzzlehttp/guzzle": "^7.0" + "neos/flow": "*" }, "autoload": { "psr-4": {