Skip to content

Commit

Permalink
feat: translation with deepl
Browse files Browse the repository at this point in the history
  • Loading branch information
sitepark-veltrup committed Jan 9, 2025
1 parent e720b6b commit 9dd4360
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 1 deletion.
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
],
"require": {
"php": ">=8.1 <8.5.0",
"deeplcom/deepl-php": "^1.10",
"nyholm/psr7": "^1.8",
"symfony/cache": "^7.2",
"symfony/config": "^6.3 || ^7.0",
"symfony/dependency-injection": "^6.3 || ^7.0",
"symfony/http-client": "^7.2",
"symfony/http-kernel": "^6.3 || ^7.0",
"symfony/yaml": "^6.3 || ^7.0"
},
Expand Down Expand Up @@ -48,8 +52,9 @@
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"infection/extension-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true
"php-http/discovery": true
},
"optimize-autoloader": true,
"preferred-install": {
Expand Down
16 changes: 16 additions & 0 deletions src/Adapter/AbstractAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Atoolo\Translator\Adapter;

use Atoolo\Translator\Dto\TranslationParameter;

abstract class AbstractAdapter
{
/**
* @param array<string> $text
* @return array<string>
*/
abstract public function translate(array $text, TranslationParameter $parameter): array;
}
36 changes: 36 additions & 0 deletions src/Adapter/DeeplAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Atoolo\Translator\Adapter;

use Atoolo\Translator\Dto\Format;
use Atoolo\Translator\Dto\TranslationParameter;
use DeepL\DeepLException;

class DeeplAdapter extends AbstractAdapter
{
private \DeepL\Translator $translator;

/**
* @throws DeepLException
*/
public function __construct(string $authKey)
{
$this->translator = new \DeepL\Translator($authKey);
}

/**
* @param array<string> $text
* @return array<string>
* @throws DeepLException
*/
public function translate(array $text, TranslationParameter $parameter): array
{
$options = [];
if ($parameter->format === Format::HTML) {
$options['tagHandling'] = 'html';
}
return $this->translator->translateText($text, $parameter->sourceLang, $parameter->targetLang, $options);

Check failure on line 34 in src/Adapter/DeeplAdapter.php

View workflow job for this annotation

GitHub Actions / verify / Composer Verify (PHP 8.2)

Method Atoolo\Translator\Adapter\DeeplAdapter::translate() should return array<string> but returns array<DeepL\TextResult>.

Check failure on line 34 in src/Adapter/DeeplAdapter.php

View workflow job for this annotation

GitHub Actions / verify / Composer Verify (PHP 8.4)

Method Atoolo\Translator\Adapter\DeeplAdapter::translate() should return array<string> but returns array<DeepL\TextResult>.
}
}
11 changes: 11 additions & 0 deletions src/Dto/Format.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Atoolo\Translator\Dto;

enum Format
{
case TEXT;
case HTML;
}
16 changes: 16 additions & 0 deletions src/Dto/TranslationParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Atoolo\Translator\Dto;

class TranslationParameter
{
public function __construct(
public string $sourceLang,
public string $targetLang,
public Format $format,
)
{
}
}
15 changes: 15 additions & 0 deletions src/Service/TextHasher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Atoolo\Translator\Service;

use Atoolo\Translator\Dto\TranslationParameter;

class TextHasher
{
public function hash(string $text, TranslationParameter $parameter): string
{
return $parameter->sourceLang . '->' . $parameter->targetLang . ':' . hash('sha256', $text);
}
}
61 changes: 61 additions & 0 deletions src/Service/Translator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Atoolo\Translator\Service;

use Atoolo\Translator\Adapter\AbstractAdapter;
use Atoolo\Translator\Dto\TranslationParameter;
use Psr\Cache\InvalidArgumentException;
use Symfony\Contracts\Cache\CacheInterface;

class Translator
{
public function __construct(
private readonly AbstractAdapter $adapter,
private readonly CacheInterface $translationCache,
private readonly TextHasher $textHasher,
)
{
}

/**
* @param array<string> $text
* @return array<string>
* @throws InvalidArgumentException
*/
public function translate(array $text, TranslationParameter $parameter): array
{
$hashMapping = [];
foreach ($text as $value) {
$hash = $this->textHasher->hash($value, $parameter);
$hashMapping[$hash] = $this->translationCache->get($hash, function () {
return null;
});
}

$toTranslate = [];
foreach ($hashMapping as $hash => $translation) {
if ($translation === null) {
$toTranslate[$hash] = $text[$hash];
}
}

if (empty($toTranslate)) {
return array_values($hashMapping);

Check failure on line 45 in src/Service/Translator.php

View workflow job for this annotation

GitHub Actions / verify / Composer Verify (PHP 8.2)

Method Atoolo\Translator\Service\Translator::translate() should return array<string> but returns array<int, null>.

Check failure on line 45 in src/Service/Translator.php

View workflow job for this annotation

GitHub Actions / verify / Composer Verify (PHP 8.4)

Method Atoolo\Translator\Service\Translator::translate() should return array<string> but returns array<int, null>.
}

$adapterTranslated = $this->adapter->translate($toTranslate, $parameter);

$position = 0;
foreach ($hashMapping as $hash => $translation) {
if ($translation === null) {
$hashMapping[$hash] = $adapterTranslated[$position];
$this->translationCache->get($hash, fn() => $hashMapping[$hash]);
$position++;
}
}

return array_values($hashMapping);

Check failure on line 59 in src/Service/Translator.php

View workflow job for this annotation

GitHub Actions / verify / Composer Verify (PHP 8.2)

Method Atoolo\Translator\Service\Translator::translate() should return array<string> but returns array<int, string|null>.

Check failure on line 59 in src/Service/Translator.php

View workflow job for this annotation

GitHub Actions / verify / Composer Verify (PHP 8.4)

Method Atoolo\Translator\Service\Translator::translate() should return array<string> but returns array<int, string|null>.
}
}
Empty file added test/.gitkeep
Empty file.
69 changes: 69 additions & 0 deletions test/Service/TranslatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Atoolo\Translator\Test\Service;

use Atoolo\Translator\Adapter\AbstractAdapter;
use Atoolo\Translator\Dto\Format;
use Atoolo\Translator\Dto\TranslationParameter;
use Atoolo\Translator\Service\TextHasher;
use Atoolo\Translator\Service\Translator;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\TestCase;
use Psr\Cache\InvalidArgumentException;
use Symfony\Contracts\Cache\CacheInterface;

#[CoversClass(Translator::class)]
class TranslatorTest extends TestCase
{

/**
* @throws Exception
* @throws InvalidArgumentException
*/
public function testTranslate(): void
{
$adapter = $this->createMock(AbstractAdapter::class);
$cache = $this->createMock(CacheInterface::class);
$textHasher = $this->createMock(TextHasher::class);
$textHasher->method('hash')->willReturnCallback(fn (string $text, TranslationParameter $parameter) => $text);

$parameter = new TranslationParameter('en', 'de', Format::TEXT);

$cacheValues = [
$textHasher->hash('computer', $parameter) => 'Computer',
$textHasher->hash('water', $parameter) => 'Wasser',
$textHasher->hash('book', $parameter) => 'Buch',
];

$cache->method('get')->willReturnCallback(function (string $key) use ($cacheValues) {
return $cacheValues[$key];
});

$adapter->method('translate')->willReturn([
'Apfel',
'Fahrrad',
'Himmel',
'Telefon',
]);

$translator = new Translator($adapter, $cache, $textHasher);


$translation = $translator->translate([
'apple',
'computer',
'bicycle',
'sky',
'water',
'book',
'telephone',
], $parameter);

$expected = ['Apfel', 'Computer', 'Fahrrad', 'Himmel', 'Wasser', 'Buch', 'Telefon'];

$this->assertEquals($expected, $translation, 'Unexpected translation');
}
}

0 comments on commit 9dd4360

Please sign in to comment.