Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions example/retrieve_usage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

require_once __DIR__ . '/../vendor/autoload.php';

use Scn\DeeplApiConnector\DeeplClientFactory;

$apiKey = 'your-api-key';

$usage = DeeplClientFactory::create($apiKey)
->getUsage();

var_dump($usage);
73 changes: 73 additions & 0 deletions example/translate_file.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

require_once __DIR__ . '/../vendor/autoload.php';

use Scn\DeeplApiConnector\DeeplClientFactory;
use Scn\DeeplApiConnector\Model\FileSubmissionInterface;
use Scn\DeeplApiConnector\Model\FileTranslationConfig;

$apiKey = 'your-api-key';

$deepl = DeeplClientFactory::create($apiKey);

$fileTranslationConfig = new FileTranslationConfig(
'Das ist ein Test',
'testfile.txt',
'EN',
'DE',
);
/**
* 1. We request a new translation with an FileTranslationConfig
*/
/** @var FileSubmissionInterface $fileSubmission */
$fileSubmission = $deepl->translateFile($fileTranslationConfig);

/**
* Result look like a FileSubmission instance
*
* class Scn\DeeplApiConnector\Model\FileSubmission#22 (2) {
* private string $documentId => "<DOCUMENT_ID>"
* private string $documentKey => "<DOCUMENT_KEY>"
* }
*/
var_dump($fileSubmission);

/**
* We can simulate this by using our own instance with valid values:
* $fileSubmission = (new FileSubmission())
* ->setDocumentId('<DOCUMENT_ID>')
* ->setDocumentKey('<DOCUMENT_KEY>');
*/


/** 2. We request in a queue logic the translation status with the Submission instance **/
sleep(15);
$translationStatus = $deepl->getFileTranslationStatus($fileSubmission);

/**
* Result look like a FileTranslationStatus instance
*
* if the 'status' property value is 'done' we can get the fileTranslation
*
* class Scn\DeeplApiConnector\Model\FileTranslationStatus#43 (4) {
* private string $documentId => "<DOCUMENT_ID>"
* private string $status => "translating"
* .....
* }
*/
var_dump($translationStatus);


/** 3. We request in a queue logic the translation status with the Submission instance **/
$response = $deepl->getFileTranslation($fileSubmission);

/**
* Result look like a FileTranslation instance
*
* class Scn\DeeplApiConnector\Model\FileTranslation#26 (1) {
* private string $content => "This is a test"
* }
*/
var_dump($response);
65 changes: 44 additions & 21 deletions src/DeeplClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,30 @@

class DeeplClient implements DeeplClientInterface
{
private const DEEPL_PAID_BASE_URI = 'https://api.deepl.com';
private const DEEPL_FREE_BASE_URI = 'https://api-free.deepl.com';

private string $apiKey;
private DeeplRequestFactoryInterface $deeplRequestFactory;

private ClientInterface $httpClient;

private RequestFactoryInterface $requestFactory;

public function __construct(
string $apiKey,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly nullable if you don't have one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly nullable if you don't have one?

Does it even make sense constructing the object without having an apikey? I don't think so...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly nullable if you don't have one?

Does it even make sense constructing the object without having an apikey? I don't think so...

One option for example is to throw an Exception('No API key given'), but the DeepL API cannot be used without apikey, whether you use the free or the Pro version makes no difference.

What solution is prefered?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly nullable if you don't have one?

Does it even make sense constructing the object without having an apikey? I don't think so...

One option for example is to throw an Exception('No API key given'), but the DeepL API cannot be used without apikey, whether you use the free or the Pro version makes no difference.

What solution is prefered?

The only reason to make the api-key nullable + default-null here (and as the last parameter in the list) is backward compatibility.
If the api-key is not nullable, then this change is not compatible and requires a new major version in case someone creates the client without using the factory.

However, since the code is no longer 100% backwards compatible even with an exception in the case of a missing api key, it will end up with a new major version anyway. And then we can do without it and not make the api key nullable.

Unless you find a way to implement the whole thing in a way that makes it backwards compatible. But since the effort is probably not worth it, I would personally be in favor of: non-nullable and a new major version.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless you find a way to implement the whole thing in a way that makes it backwards compatible. But since the effort is probably not worth it, I would personally be in favor of: non-nullable and a new major version.

I would agree with that "non-nullable and a new major version" to prevent surprises using this lib.

DeeplRequestFactoryInterface $deeplRequestFactory,
ClientInterface $httpClient,
RequestFactoryInterface $requestFactory
RequestFactoryInterface $requestFactory,
) {
$this->apiKey = $apiKey;
$this->deeplRequestFactory = $deeplRequestFactory;
$this->httpClient = $httpClient;
$this->requestFactory = $requestFactory;
}

/**
* Return Usage of API- Key
* Return Usage of API-Key
* Possible Return:.
*
* Usage
Expand All @@ -62,7 +68,7 @@ public function __construct(
public function getUsage(): ResponseModelInterface
{
return (new Usage())->hydrate(
$this->executeRequest($this->deeplRequestFactory->createDeeplUsageRequestHandler())
$this->executeRequest($this->deeplRequestFactory->createDeeplUsageRequestHandler()),
);
}

Expand All @@ -79,7 +85,7 @@ public function getUsage(): ResponseModelInterface
public function getTranslation(TranslationConfigInterface $translation): ResponseModelInterface
{
return (new Translation())->hydrate($this->executeRequest(
$this->deeplRequestFactory->createDeeplTranslationRequestHandler($translation)
$this->deeplRequestFactory->createDeeplTranslationRequestHandler($translation),
));
}

Expand All @@ -98,38 +104,38 @@ public function translate(string $text, string $target_language): ResponseModelI
public function translateFile(FileTranslationConfigInterface $fileTranslation): ResponseModelInterface
{
return (new FileSubmission())->hydrate($this->executeRequest(
$this->deeplRequestFactory->createDeeplFileSubmissionRequestHandler($fileTranslation)
$this->deeplRequestFactory->createDeeplFileSubmissionRequestHandler($fileTranslation),
));
}

public function translateBatch(array $text, string $targetLanguage): ResponseModelInterface
{
return (new BatchTranslation())->hydrate($this->executeRequest(
$this->deeplRequestFactory->createDeeplBatchTranslationRequestHandler(
new BatchTranslationConfig($text, $targetLanguage)
new BatchTranslationConfig($text, $targetLanguage),
)
));
}

public function getFileTranslationStatus(FileSubmissionInterface $fileSubmission): ResponseModelInterface
{
return (new FileTranslationStatus())->hydrate($this->executeRequest(
$this->deeplRequestFactory->createDeeplFileTranslationStatusRequestHandler($fileSubmission)
$this->deeplRequestFactory->createDeeplFileTranslationStatusRequestHandler($fileSubmission),
));
}

public function getFileTranslation(FileSubmissionInterface $fileSubmission): ResponseModelInterface
{
return (new FileTranslation())->hydrate($this->executeRequest(
$this->deeplRequestFactory->createDeeplFileTranslationRequestHandler($fileSubmission)
$this->deeplRequestFactory->createDeeplFileTranslationRequestHandler($fileSubmission),
));
}

public function getSupportedLanguages(): ResponseModelInterface
{
return (new SupportedLanguages())->hydrate(
$this->executeRequest(
$this->deeplRequestFactory->createDeeplSupportedLanguageRetrievalRequestHandler()
$this->deeplRequestFactory->createDeeplSupportedLanguageRetrievalRequestHandler(),
)
);
}
Expand All @@ -138,7 +144,7 @@ public function getGlossariesSupportedLanguagesPairs(): ResponseModelInterface
{
return (new GlossariesSupportedLanguagesPairs())->hydrate(
$this->executeRequest(
$this->deeplRequestFactory->createDeeplGlossariesSupportedLanguagesPairsRetrievalRequestHandler()
$this->deeplRequestFactory->createDeeplGlossariesSupportedLanguagesPairsRetrievalRequestHandler(),
)
);
}
Expand All @@ -147,7 +153,7 @@ public function getGlossariesList(): ResponseModelInterface
{
return (new Glossaries())->hydrate(
$this->executeRequest(
$this->deeplRequestFactory->createDeeplGlossariesListRetrievalRequestHandler()
$this->deeplRequestFactory->createDeeplGlossariesListRetrievalRequestHandler(),
)
);
}
Expand Down Expand Up @@ -194,20 +200,23 @@ private function executeRequest(DeeplRequestHandlerInterface $requestHandler): s
$request = $this->requestFactory
->createRequest(
$requestHandler->getMethod(),
sprintf('%s%s', $this->deeplRequestFactory->getDeeplBaseUri(), $requestHandler->getPath())
sprintf('%s%s', $this->getDeeplBaseUri(), $requestHandler->getPath()),
)
->withHeader(
'Authorization',
$this->getAuthHeader(),
)
->withHeader(
'Content-Type',
$requestHandler->getContentType()
$requestHandler->getContentType(),
)
->withBody($requestHandler->getBody());

if ($requestHandler->getAuthHeader() !== null) {
$request = $request->withHeader('Authorization', $requestHandler->getAuthHeader());
}
->withBody(
$requestHandler->getBody(),
);

if ($requestHandler->getAcceptHeader() !== null) {
$request = $request->withHeader('Accept', $requestHandler->getAcceptHeader());
$acceptHeader = $requestHandler->getAcceptHeader();
if ($acceptHeader !== null) {
$request = $request->withHeader('Accept', $acceptHeader);
}

try {
Expand All @@ -216,7 +225,7 @@ private function executeRequest(DeeplRequestHandlerInterface $requestHandler): s
throw new RequestException(
$exception->getMessage(),
$exception->getCode(),
$exception
$exception,
);
}

Expand Down Expand Up @@ -253,4 +262,18 @@ private function executeRequest(DeeplRequestHandlerInterface $requestHandler): s
/** @var stdClass $result */
return $result;
}

private function getAuthHeader(): string
{
return sprintf('DeepL-Auth-Key %s', $this->apiKey);
}

private function getDeeplBaseUri(): string
{
if (str_contains($this->apiKey, ':fx')) {
return self::DEEPL_FREE_BASE_URI;
}

return self::DEEPL_PAID_BASE_URI;
}
}
2 changes: 1 addition & 1 deletion src/DeeplClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ public static function create(
StreamFactoryInterface $streamFactory = null
): DeeplClientInterface {
return new DeeplClient(
$authKey,
new DeeplRequestFactory(
$authKey,
$streamFactory ?? Psr17FactoryDiscovery::findStreamFactory()
),
$httpClient ?? Psr18ClientDiscovery::find(),
Expand Down
6 changes: 3 additions & 3 deletions src/Handler/AbstractDeeplHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

abstract class AbstractDeeplHandler implements DeeplRequestHandlerInterface
{
public function getAuthHeader(): ?string
public function getAcceptHeader(): ?string
{
return null;
}

public function getAcceptHeader(): ?string
public function getContentType(): string
{
return null;
return 'application/json';
}
}
51 changes: 19 additions & 32 deletions src/Handler/DeeplBatchTranslationRequestHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,14 @@ final class DeeplBatchTranslationRequestHandler extends AbstractDeeplHandler
private const SEPARATOR = ',';
public const API_ENDPOINT = '/v2/translate';

private string $authKey;

private StreamFactoryInterface $streamFactory;

private BatchTranslationConfigInterface $translation;

public function __construct(
string $authKey,
StreamFactoryInterface $streamFactory,
BatchTranslationConfigInterface $translation
BatchTranslationConfigInterface $translation,
) {
$this->authKey = $authKey;
$this->streamFactory = $streamFactory;
$this->translation = $translation;
}
Expand All @@ -44,37 +40,28 @@ public function getPath(): string

public function getBody(): StreamInterface
{
$query = http_build_query(
array_filter(
[
'target_lang' => $this->translation->getTargetLang(),
'tag_handling' => implode(
self::SEPARATOR,
$this->translation->getTagHandling()
),
'non_splitting_tags' => implode(
self::SEPARATOR,
$this->translation->getNonSplittingTags()
),
'ignore_tags' => implode(self::SEPARATOR, $this->translation->getIgnoreTags()),
'split_sentences' => $this->translation->getSplitSentences(),
'preserve_formatting' => $this->translation->getPreserveFormatting(),
'glossary_id' => $this->translation->getGlossaryId(),
'auth_key' => $this->authKey,
]
)
);
$body = array_filter([
'target_lang' => $this->translation->getTargetLang(),
'tag_handling' => implode(
self::SEPARATOR,
$this->translation->getTagHandling(),
),
'non_splitting_tags' => implode(
self::SEPARATOR,
$this->translation->getNonSplittingTags(),
),
'ignore_tags' => implode(self::SEPARATOR, $this->translation->getIgnoreTags()),
'split_sentences' => $this->translation->getSplitSentences(),
'preserve_formatting' => $this->translation->getPreserveFormatting(),
'glossary_id' => $this->translation->getGlossaryId(),
'text' => [],
]);

// add the text parameters separately as http_build_query would create `text[]` params
foreach ($this->translation->getText() as $text) {
$query .= '&text=' . $text;
$body['text'][] = $text;
}

return $this->streamFactory->createStream($query);
}

public function getContentType(): string
{
return 'application/x-www-form-urlencoded';
return $this->streamFactory->createStream(json_encode($body, JSON_THROW_ON_ERROR));
}
}
Loading