Skip to content
This repository has been archived by the owner on Jan 10, 2023. It is now read-only.

Commit

Permalink
TASK: Adjust translation api to support the translation of multiple f…
Browse files Browse the repository at this point in the history
…ragments with a single request.
  • Loading branch information
mficzel committed Sep 29, 2021
1 parent fe0a17b commit eac360d
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 74 deletions.
127 changes: 63 additions & 64 deletions Classes/Domain/Service/DeepLService.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,13 @@
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;

/**
* @Flow\Scope("singleton")
*/
class DeepLService
{
/**
* @var Client|null
*/
protected ?Client $deeplClient = null;

/**
* @var array
Expand All @@ -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);
}
}
23 changes: 20 additions & 3 deletions Classes/Domain/Service/NodeTranslationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
Expand All @@ -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)) {
Expand All @@ -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;
}
Expand All @@ -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);
}
}
Expand Down
4 changes: 2 additions & 2 deletions Classes/EelHelper/TranslationHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
11 changes: 11 additions & 0 deletions Configuration/NodeTypes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'Neos.Neos:Document':
properties:
title:
options:
deeplTranslate: true
titleOverride:
options:
deeplTranslate: true
metaDescription:
options:
deeplTranslate: true
15 changes: 13 additions & 2 deletions Configuration/Settings.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
```
Expand Down Expand Up @@ -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
```
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"type": "neos-package",
"name": "codeq/deepltranslationhelper",
"require": {
"neos/flow": "*",
"guzzlehttp/guzzle": "^7.0"
"neos/flow": "*"
},
"autoload": {
"psr-4": {
Expand Down

0 comments on commit eac360d

Please sign in to comment.