From d6d05b183eee92fe714907cfbb959fe8a7a6150c Mon Sep 17 00:00:00 2001 From: Markus Hofmann Date: Sun, 21 Sep 2025 17:51:34 +0200 Subject: [PATCH 1/5] [TASK] Replace DeepL Translator with new DeepLClient In preparation of switching DeepL TYPO3 extensions to API v3 support, it is necessary replacing the old `Translator` to the new `DeepLClient`, which holds the new features introduced. --- Classes/AbstractClient.php | 14 +++++++------- Tests/Functional/AbstractDeepLTestCase.php | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Classes/AbstractClient.php b/Classes/AbstractClient.php index 366de9ef..1cda3a77 100644 --- a/Classes/AbstractClient.php +++ b/Classes/AbstractClient.php @@ -4,8 +4,8 @@ namespace WebVision\Deepltranslate\Core; -use DeepL\Translator; -use DeepL\TranslatorOptions; +use DeepL\DeepLClient; +use DeepL\DeepLClientOptions; use Psr\Log\LoggerInterface; use TYPO3\CMS\Core\Http\Client\GuzzleClientFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -18,7 +18,7 @@ abstract class AbstractClient implements ClientInterface { protected ConfigurationInterface $configuration; - protected ?Translator $translator = null; + protected ?DeepLClient $translator = null; protected LoggerInterface $logger; @@ -37,16 +37,16 @@ public function setLogger(LoggerInterface $logger): void * * @throws ApiKeyNotSetException */ - protected function getTranslator(): Translator + protected function getTranslator(): DeepLClient { - if ($this->translator instanceof Translator) { + if ($this->translator instanceof DeepLClient) { return $this->translator; } if ($this->configuration->getApiKey() === '') { throw new ApiKeyNotSetException('The api key ist not set', 1708081233823); } - $options[TranslatorOptions::HTTP_CLIENT] = GeneralUtility::makeInstance(GuzzleClientFactory::class)->getClient(); - $this->translator = new Translator($this->configuration->getApiKey(), $options); + $options[DeepLClientOptions::HTTP_CLIENT] = GeneralUtility::makeInstance(GuzzleClientFactory::class)->getClient(); + $this->translator = new DeepLClient($this->configuration->getApiKey(), $options); return $this->translator; } diff --git a/Tests/Functional/AbstractDeepLTestCase.php b/Tests/Functional/AbstractDeepLTestCase.php index 36f0538c..5ea572fe 100644 --- a/Tests/Functional/AbstractDeepLTestCase.php +++ b/Tests/Functional/AbstractDeepLTestCase.php @@ -5,7 +5,7 @@ namespace WebVision\Deepltranslate\Core\Tests\Functional; use Closure; -use DeepL\Translator; +use DeepL\DeepLClient; use DeepL\TranslatorOptions; use Exception; use phpmock\phpunit\PHPMock; @@ -222,9 +222,9 @@ protected function instantiateMockServerClient(array $options = []): void $client->setLogger(new NullLogger()); // use closure to set private option for translation - $translator = new Translator(self::getInstanceIdentifier(), $mergedOptions); + $translator = new DeepLClient(self::getInstanceIdentifier(), $mergedOptions); Closure::bind( - function (Translator $translator) { + function (DeepLClient $translator) { $this->translator = $translator; }, $client, From 1b21e9061827ad7e1e0a52bc5c3b0c1a9eb9179f Mon Sep 17 00:00:00 2001 From: Markus Hofmann Date: Sun, 21 Sep 2025 18:07:41 +0200 Subject: [PATCH 2/5] [TASK] Mark old glossary functions in client as deprecated The new API v3 changes the behaviour in glossary handling completely. However, the support for v2 is not dropped for the moment, but will be. As it is more simple to maintain only one functionality for glossaries, deprecate the old methods. --- Classes/ClientInterface.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Classes/ClientInterface.php b/Classes/ClientInterface.php index 0f4d56d2..10dffda3 100644 --- a/Classes/ClientInterface.php +++ b/Classes/ClientInterface.php @@ -53,11 +53,13 @@ public function getGlossaryLanguagePairs(): array; * @return GlossaryInfo[] * * @throws ApiKeyNotSetException + * @deprecated This function is deprecated in favour of multilingual glossaries and will be removed in future versions */ public function getAllGlossaries(): array; /** * @throws ApiKeyNotSetException + * @deprecated This function is deprecated in favour of multilingual glossaries and will be removed in future versions */ public function getGlossary(string $glossaryId): ?GlossaryInfo; @@ -65,6 +67,7 @@ public function getGlossary(string $glossaryId): ?GlossaryInfo; * @param array $entries * * @throws ApiKeyNotSetException + * @deprecated This function is deprecated in favour of multilingual glossaries and will be removed in future versions */ public function createGlossary( string $glossaryName, @@ -75,11 +78,13 @@ public function createGlossary( /** * @throws ApiKeyNotSetException + * @deprecated This function is deprecated in favour of multilingual glossaries and will be removed in future versions */ public function deleteGlossary(string $glossaryId): void; /** * @throws ApiKeyNotSetException + * @deprecated This function is deprecated in favour of multilingual glossaries and will be removed in future versions */ public function getGlossaryEntries(string $glossaryId): ?GlossaryEntries; From 9d2de999c82d43f0c3b4bb0cb9d63aa18b1551c5 Mon Sep 17 00:00:00 2001 From: Markus Hofmann Date: Sun, 21 Sep 2025 18:28:39 +0200 Subject: [PATCH 3/5] [TASK] Introduce API v3 methods to ClientInterface Add the new muli lingual glossary methods to the ClientInterface and set the old ones deprecated. The deprecation is done, as a support for both v2 and v3 does not make sense and should be skipped soon. --- Classes/Client.php | 66 +++++++++++++++++++++++++++++++++++++ Classes/ClientInterface.php | 23 +++++++++++++ 2 files changed, 89 insertions(+) diff --git a/Classes/Client.php b/Classes/Client.php index ffe2fe6e..3b33869c 100644 --- a/Classes/Client.php +++ b/Classes/Client.php @@ -9,6 +9,9 @@ use DeepL\GlossaryInfo; use DeepL\GlossaryLanguagePair; use DeepL\Language; +use DeepL\MultilingualGlossaryDictionaryEntries; +use DeepL\MultilingualGlossaryDictionaryInfo; +use DeepL\MultilingualGlossaryInfo; use DeepL\TextResult; use DeepL\TranslateTextOptions; use DeepL\Usage; @@ -234,4 +237,67 @@ public function getUsage(): ?Usage return null; } + + /** + * @throws DeepLException + * @throws ApiKeyNotSetException + */ + public function createMultilingualGlossary(string $name, array $dictionaries = []): MultilingualGlossaryInfo + { + return $this->getTranslator()->createMultilingualGlossary($name, $dictionaries); + } + + /** + * @throws DeepLException + * @throws ApiKeyNotSetException + */ + public function updateMultilingualGlossary(string $glossaryId, array $dictionaries, string $newName = ''): MultilingualGlossaryInfo + { + return $this->getTranslator()->updateMultilingualGlossary( + $glossaryId, + $newName !== '' ? $newName : null, + $dictionaries + ); + } + + /** + * @throws DeepLException + * @throws ApiKeyNotSetException + */ + public function replaceMultilingualGlossary(string $glossaryId, MultilingualGlossaryDictionaryEntries $dictionaries): MultilingualGlossaryDictionaryInfo + { + return $this->getTranslator()->replaceMultilingualGlossaryDictionary($glossaryId, $dictionaries); + } + + /** + * @throws DeepLException + * @throws ApiKeyNotSetException + */ + public function deleteMultilingualGlossary(string $glossaryId): void + { + $this->getTranslator()->deleteMultilingualGlossary($glossaryId); + } + + /** + * @throws DeepLException + * @throws ApiKeyNotSetException + */ + public function listMultilingualGlossaries(): array + { + return $this->getTranslator()->listMultilingualGlossaries(); + } + + /** + * @throws DeepLException + * @throws ApiKeyNotSetException + */ + public function deleteGlossaryDictionary(string $glossaryId, string $sourceLanguage, string $targetLanguage): void + { + $this->getTranslator()->deleteMultilingualGlossaryDictionary( + $glossaryId, + null, + $sourceLanguage, + $targetLanguage + ); + } } diff --git a/Classes/ClientInterface.php b/Classes/ClientInterface.php index 10dffda3..cd2839c3 100644 --- a/Classes/ClientInterface.php +++ b/Classes/ClientInterface.php @@ -8,6 +8,9 @@ use DeepL\GlossaryInfo; use DeepL\GlossaryLanguagePair; use DeepL\Language; +use DeepL\MultilingualGlossaryDictionaryEntries; +use DeepL\MultilingualGlossaryDictionaryInfo; +use DeepL\MultilingualGlossaryInfo; use DeepL\TextResult; use DeepL\Usage; use Psr\Log\LoggerAwareInterface; @@ -92,4 +95,24 @@ public function getGlossaryEntries(string $glossaryId): ?GlossaryEntries; * @throws ApiKeyNotSetException */ public function getUsage(): ?Usage; + + /** + * @param MultilingualGlossaryDictionaryEntries[] $dictionaries + */ + public function createMultilingualGlossary(string $name, array $dictionaries = []): MultilingualGlossaryInfo; + + /** + * @param MultilingualGlossaryDictionaryEntries[] $dictionaries + */ + public function updateMultilingualGlossary(string $glossaryId, array $dictionaries, string $newName = ''): MultilingualGlossaryInfo; + public function replaceMultilingualGlossary(string $glossaryId, MultilingualGlossaryDictionaryEntries $dictionaries): MultilingualGlossaryDictionaryInfo; + + public function deleteMultilingualGlossary(string $glossaryId): void; + + public function deleteGlossaryDictionary(string $glossaryId, string $sourceLanguage, string $targetLanguage): void; + + /** + * @return MultilingualGlossaryInfo[] + */ + public function listMultilingualGlossaries(): array; } From e4c18e145ee98beb4f2c1aca9f63737bfb4aaea9 Mon Sep 17 00:00:00 2001 From: Markus Hofmann Date: Sun, 21 Sep 2025 18:38:23 +0200 Subject: [PATCH 4/5] [TASK] Change usage of assertions from static to $this As it is more common using `$this`, when calling Unit test assertions, change the php Code style fixer rule and let the command run. Used command: ``` Build/Scripts/runTests.sh -t 12 -p 8.1 -s cgl ``` --- Build/php-cs-fixer/php-cs-rules.php | 2 +- Tests/Functional/AbstractDeepLTestCase.php | 8 ++-- Tests/Functional/ClientTest.php | 36 +++++++------- .../ContentElementsInContainerTest.php | 2 +- ...nfigSupportedLanguageItemsProcFuncTest.php | 4 +- .../Form/User/HasFormalitySupportTest.php | 8 ++-- Tests/Functional/Hooks/TranslateHookTest.php | 14 +++--- ...lationWithModifiedTcaConfigurationTest.php | 2 +- .../LocalizationInlineRegressionTest.php | 2 +- .../PreviewTranslationInformationTest.php | 6 +-- .../Functional/Services/DeeplServiceTest.php | 48 +++++++++---------- .../Services/LanguageServiceTest.php | 24 +++++----- .../Functional/Services/UsageServiceTest.php | 14 +++--- .../Updates/FormalityUpgradeWizardTest.php | 16 +++---- Tests/Unit/Access/AccessRegistryTest.php | 6 +-- .../Access/AllowedTranslateAccessTest.php | 10 ++-- 16 files changed, 101 insertions(+), 101 deletions(-) diff --git a/Build/php-cs-fixer/php-cs-rules.php b/Build/php-cs-fixer/php-cs-rules.php index 2c17853b..f6dbe5ae 100644 --- a/Build/php-cs-fixer/php-cs-rules.php +++ b/Build/php-cs-fixer/php-cs-rules.php @@ -77,7 +77,7 @@ 'no_empty_phpdoc' => true, 'no_null_property_initialization' => true, 'php_unit_mock_short_will_return' => true, - 'php_unit_test_case_static_method_calls' => ['call_type' => 'static'], + 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], 'single_trait_insert_per_statement' => true, ]) ->setFinder($finder); diff --git a/Tests/Functional/AbstractDeepLTestCase.php b/Tests/Functional/AbstractDeepLTestCase.php index 5ea572fe..34a8d945 100644 --- a/Tests/Functional/AbstractDeepLTestCase.php +++ b/Tests/Functional/AbstractDeepLTestCase.php @@ -283,10 +283,10 @@ public function assertExceptionContains(string $needle, callable $function): Exc try { $function(); } catch (Exception $exception) { - static::assertStringContainsString($needle, $exception->getMessage()); + $this->assertStringContainsString($needle, $exception->getMessage()); return $exception; } - static::fail("Expected exception containing '$needle' but nothing was thrown"); + $this->fail("Expected exception containing '$needle' but nothing was thrown"); } /** @@ -297,10 +297,10 @@ public function assertExceptionClass(string $class, callable $function): Excepti try { $function(); } catch (Exception $exception) { - static::assertEquals($class, get_class($exception)); + $this->assertEquals($class, get_class($exception)); return $exception; } - static::fail("Expected exception of class '$class' but nothing was thrown"); + $this->fail("Expected exception of class '$class' but nothing was thrown"); } /** diff --git a/Tests/Functional/ClientTest.php b/Tests/Functional/ClientTest.php index 39c6d7d6..ebe4d6af 100644 --- a/Tests/Functional/ClientTest.php +++ b/Tests/Functional/ClientTest.php @@ -41,8 +41,8 @@ public function checkResponseFromTranslateContent(): void 'DE' ); - static::assertInstanceOf(TextResult::class, $response); - static::assertSame(self::EXAMPLE_TEXT['de'], $response->text); + $this->assertInstanceOf(TextResult::class, $response); + $this->assertSame(self::EXAMPLE_TEXT['de'], $response->text); } #[Test] @@ -51,8 +51,8 @@ public function checkResponseFromSupportedTargetLanguage(): void $client = $this->get(ClientInterface::class); $response = $client->getSupportedLanguageByType(); - static::assertIsArray($response); - static::assertContainsOnlyInstancesOf(Language::class, $response); + $this->assertIsArray($response); + $this->assertContainsOnlyInstancesOf(Language::class, $response); } #[Test] @@ -61,8 +61,8 @@ public function checkResponseFromGlossaryLanguagePairs(): void $client = $this->get(ClientInterface::class); $response = $client->getGlossaryLanguagePairs(); - static::assertIsArray($response); - static::assertContainsOnlyInstancesOf(GlossaryLanguagePair::class, $response); + $this->assertIsArray($response); + $this->assertContainsOnlyInstancesOf(GlossaryLanguagePair::class, $response); } #[Test] @@ -81,10 +81,10 @@ public function checkResponseFromCreateGlossary(): void ], ); - static::assertInstanceOf(GlossaryInfo::class, $response); - static::assertSame(1, $response->entryCount); - static::assertIsString($response->glossaryId); - static::assertInstanceOf(DateTime::class, $response->creationTime); + $this->assertInstanceOf(GlossaryInfo::class, $response); + $this->assertSame(1, $response->entryCount); + $this->assertIsString($response->glossaryId); + $this->assertInstanceOf(DateTime::class, $response->creationTime); } #[Test] @@ -93,8 +93,8 @@ public function checkResponseGetAllGlossaries(): void $client = $this->get(ClientInterface::class); $response = $client->getAllGlossaries(); - static::assertIsArray($response); - static::assertContainsOnlyInstancesOf(GlossaryInfo::class, $response); + $this->assertIsArray($response); + $this->assertContainsOnlyInstancesOf(GlossaryInfo::class, $response); } #[Test] @@ -115,9 +115,9 @@ public function checkResponseFromGetGlossary(): void $response = $client->getGlossary($glossary->glossaryId); - static::assertInstanceOf(GlossaryInfo::class, $response); - static::assertSame($glossary->glossaryId, $response->glossaryId); - static::assertSame(1, $response->entryCount); + $this->assertInstanceOf(GlossaryInfo::class, $response); + $this->assertSame($glossary->glossaryId, $response->glossaryId); + $this->assertSame(1, $response->entryCount); } #[Test] @@ -140,7 +140,7 @@ public function checkGlossaryDeletedNotCatchable(): void $client->deleteGlossary($glossaryId); - static::assertNull($client->getGlossary($glossaryId)); + $this->assertNull($client->getGlossary($glossaryId)); } #[Test] @@ -161,7 +161,7 @@ public function checkResponseFromGetGlossaryEntries(): void $response = $client->getGlossaryEntries($glossary->glossaryId); - static::assertInstanceOf(GlossaryEntries::class, $response); - static::assertSame(1, count($response->getEntries())); + $this->assertInstanceOf(GlossaryEntries::class, $response); + $this->assertSame(1, count($response->getEntries())); } } diff --git a/Tests/Functional/Container/ContentElementsInContainerTest.php b/Tests/Functional/Container/ContentElementsInContainerTest.php index 97703b55..09108ffb 100644 --- a/Tests/Functional/Container/ContentElementsInContainerTest.php +++ b/Tests/Functional/Container/ContentElementsInContainerTest.php @@ -118,7 +118,7 @@ public function containerAndInlineElementsAreTranslated(): void $dataHandler->start([], $cmdMap); $dataHandler->process_cmdmap(); - static::assertEmpty($dataHandler->errorLog); + $this->assertEmpty($dataHandler->errorLog); self::assertCSVDataSet(__DIR__ . '/Fixtures/Result/container_translated.csv'); } } diff --git a/Tests/Functional/Form/Items/SiteConfigSupportedLanguageItemsProcFuncTest.php b/Tests/Functional/Form/Items/SiteConfigSupportedLanguageItemsProcFuncTest.php index 6b38b6e6..c97af949 100644 --- a/Tests/Functional/Form/Items/SiteConfigSupportedLanguageItemsProcFuncTest.php +++ b/Tests/Functional/Form/Items/SiteConfigSupportedLanguageItemsProcFuncTest.php @@ -31,7 +31,7 @@ public function getSupportedLanguageFormFields(): void $func->getSupportedLanguageForField($fieldConfig); - static::assertArrayHasKey('items', $fieldConfig); - static::assertTrue((count($fieldConfig['items']) > 2)); + $this->assertArrayHasKey('items', $fieldConfig); + $this->assertTrue((count($fieldConfig['items']) > 2)); } } diff --git a/Tests/Functional/Form/User/HasFormalitySupportTest.php b/Tests/Functional/Form/User/HasFormalitySupportTest.php index 40694bd7..15832521 100644 --- a/Tests/Functional/Form/User/HasFormalitySupportTest.php +++ b/Tests/Functional/Form/User/HasFormalitySupportTest.php @@ -38,7 +38,7 @@ public function hasFormalitySupportIsSupported(): void ], ], $evaluateDisplayConditionsMock); - static::assertTrue($isFormalitySupported); + $this->assertTrue($isFormalitySupported); } #[Test] @@ -56,7 +56,7 @@ public function hasFormalitySupportIsNotSupported(): void ], ], $evaluateDisplayConditionsMock); - static::assertFalse($isFormalitySupported); + $this->assertFalse($isFormalitySupported); } #[Test] @@ -68,7 +68,7 @@ public function formalityIsNotSupportedWhenRecordNotExist(): void $isFormalitySupported = $subject->checkFormalitySupport([], $evaluateDisplayConditionsMock); - static::assertFalse($isFormalitySupported); + $this->assertFalse($isFormalitySupported); } #[Test] @@ -84,6 +84,6 @@ public function formalityIsNotSupportedWhenDeeplTargetLanguageNotExistOrEmpty(): ], ], $evaluateDisplayConditionsMock); - static::assertFalse($isFormalitySupported); + $this->assertFalse($isFormalitySupported); } } diff --git a/Tests/Functional/Hooks/TranslateHookTest.php b/Tests/Functional/Hooks/TranslateHookTest.php index 7782bb58..7f99cb8d 100644 --- a/Tests/Functional/Hooks/TranslateHookTest.php +++ b/Tests/Functional/Hooks/TranslateHookTest.php @@ -124,7 +124,7 @@ public function contentTranslateWithDeepl(): void 'DE', ); - static::assertSame($expectedTranslation, $content); + $this->assertSame($expectedTranslation, $content); } #[Test] @@ -149,7 +149,7 @@ public function contentNotTranslateWithDeeplWhenLanguageNotSupported(): void 'BS' ); - static::assertSame('', $content); + $this->assertSame('', $content); } #[Test] @@ -188,10 +188,10 @@ public function translateContentElementsAndUpdatePagesProperties(): void 1, )->fetchAssociative(); - static::assertIsArray($pageRow); - static::assertArrayHasKey('tx_wvdeepltranslate_content_not_checked', $pageRow); - static::assertSame(1, (int)$pageRow['tx_wvdeepltranslate_content_not_checked']); - static::assertArrayHasKey('tx_wvdeepltranslate_translated_time', $pageRow); - static::assertGreaterThan(0, (int)$pageRow['tx_wvdeepltranslate_translated_time']); + $this->assertIsArray($pageRow); + $this->assertArrayHasKey('tx_wvdeepltranslate_content_not_checked', $pageRow); + $this->assertSame(1, (int)$pageRow['tx_wvdeepltranslate_content_not_checked']); + $this->assertArrayHasKey('tx_wvdeepltranslate_translated_time', $pageRow); + $this->assertGreaterThan(0, (int)$pageRow['tx_wvdeepltranslate_translated_time']); } } diff --git a/Tests/Functional/Hooks/TranslationWithModifiedTcaConfigurationTest.php b/Tests/Functional/Hooks/TranslationWithModifiedTcaConfigurationTest.php index 6e3f139f..0362661c 100644 --- a/Tests/Functional/Hooks/TranslationWithModifiedTcaConfigurationTest.php +++ b/Tests/Functional/Hooks/TranslationWithModifiedTcaConfigurationTest.php @@ -119,7 +119,7 @@ public function pageIsBeingTranslated(): void $dataHandler->start([], $cmdMap); $dataHandler->process_cmdmap(); - static::assertEmpty($dataHandler->errorLog); + $this->assertEmpty($dataHandler->errorLog); self::assertCSVDataSet(__DIR__ . '/Fixtures/Results/page_translated.csv'); } } diff --git a/Tests/Functional/Regression/LocalizationInlineRegressionTest.php b/Tests/Functional/Regression/LocalizationInlineRegressionTest.php index 919a1459..2d8b2ca4 100644 --- a/Tests/Functional/Regression/LocalizationInlineRegressionTest.php +++ b/Tests/Functional/Regression/LocalizationInlineRegressionTest.php @@ -104,7 +104,7 @@ public function ensureInlineElementsTranslationOnLocalization(): void $dataHandler->start([], $commandMap); $dataHandler->process_cmdmap(); - static::assertEmpty($dataHandler->errorLog); + $this->assertEmpty($dataHandler->errorLog); self::assertCSVDataSet(__DIR__ . '/Fixtures/Results/pageWithMediaResult.csv'); } } diff --git a/Tests/Functional/Regression/PreviewTranslationInformationTest.php b/Tests/Functional/Regression/PreviewTranslationInformationTest.php index 757ef68b..44acef4d 100644 --- a/Tests/Functional/Regression/PreviewTranslationInformationTest.php +++ b/Tests/Functional/Regression/PreviewTranslationInformationTest.php @@ -125,10 +125,10 @@ public function previewTranslationInformationIsRenderedForTranslatedPage(): void $requestContext = (new InternalRequestContext())->withBackendUserId(1); $request = new InternalRequest('https://acme.com/de/artikel/'); $response = $this->executeFrontendSubRequest($request, $requestContext); - static::assertSame(200, $response->getStatusCode()); + $this->assertSame(200, $response->getStatusCode()); $content = (string)$response->getBody(); - static::assertNotEmpty($content); - static::assertStringContainsString($expectedContent, $content, 'preview translation label is rendered in frontend preview'); + $this->assertNotEmpty($content); + $this->assertStringContainsString($expectedContent, $content, 'preview translation label is rendered in frontend preview'); } } diff --git a/Tests/Functional/Services/DeeplServiceTest.php b/Tests/Functional/Services/DeeplServiceTest.php index b93bfe90..22cf9ad7 100644 --- a/Tests/Functional/Services/DeeplServiceTest.php +++ b/Tests/Functional/Services/DeeplServiceTest.php @@ -45,7 +45,7 @@ public function translateContentFromDeToEn(): void 'DE' ); - static::assertSame(self::EXAMPLE_TEXT['en'], $translateContent); + $this->assertSame(self::EXAMPLE_TEXT['en'], $translateContent); } /** @@ -65,7 +65,7 @@ public function translateContentFromEnToDe(): void 'EN' ); - static::assertSame($expectedTranslation, $translateContent); + $this->assertSame($expectedTranslation, $translateContent); } /** @@ -85,7 +85,7 @@ public function translateContentWithAutoDetectSourceParam(): void 'auto' ); - static::assertSame($expectedTranslation, $translateContent); + $this->assertSame($expectedTranslation, $translateContent); } #[Test] @@ -100,7 +100,7 @@ public function translateContentWithTranslateContextFromDeToEn(): void $translateContent = $deeplService->translateContent($translateContext); - static::assertSame('proton beam', $translateContent); + $this->assertSame('proton beam', $translateContent); } #[Test] @@ -115,7 +115,7 @@ public function translateContentWithTranslateContextFromEnToDe(): void $translateContent = $deeplService->translateContent($translateContext); - static::assertSame('Protonenstrahl', $translateContent); + $this->assertSame('Protonenstrahl', $translateContent); } #[Test] @@ -130,7 +130,7 @@ public function translateContentWithTranslateContextWithAutoDetectSourceParam(): $translateContent = $deeplService->translateContent($translateContext); - static::assertSame('Protonenstrahl', $translateContent); + $this->assertSame('Protonenstrahl', $translateContent); } #[Test] @@ -139,14 +139,14 @@ public function checkSupportedTargetLanguages(): void /** @var DeeplService $deeplService */ $deeplService = $this->get(DeeplService::class); - static::assertContainsOnlyInstancesOf(Language::class, $deeplService->getSupportLanguage()['target']); + $this->assertContainsOnlyInstancesOf(Language::class, $deeplService->getSupportLanguage()['target']); - static::assertEquals('EN-GB', $deeplService->detectTargetLanguage('EN-GB')->code); - static::assertEquals('EN-US', $deeplService->detectTargetLanguage('EN-US')->code); - static::assertEquals('DE', $deeplService->detectTargetLanguage('DE')->code); - static::assertEquals('UK', $deeplService->detectTargetLanguage('UK')->code); - static::assertNull($deeplService->detectTargetLanguage('EN')); - static::assertNull($deeplService->detectTargetLanguage('BS')); + $this->assertEquals('EN-GB', $deeplService->detectTargetLanguage('EN-GB')->code); + $this->assertEquals('EN-US', $deeplService->detectTargetLanguage('EN-US')->code); + $this->assertEquals('DE', $deeplService->detectTargetLanguage('DE')->code); + $this->assertEquals('UK', $deeplService->detectTargetLanguage('UK')->code); + $this->assertNull($deeplService->detectTargetLanguage('EN')); + $this->assertNull($deeplService->detectTargetLanguage('BS')); } #[Test] @@ -155,10 +155,10 @@ public function checkIsTargetLanguageSupported(): void /** @var DeeplService $deeplService */ $deeplService = $this->get(DeeplService::class); - static::assertTrue($deeplService->isTargetLanguageSupported('DE')); + $this->assertTrue($deeplService->isTargetLanguageSupported('DE')); // We should avoid using a real existing language here, as the tests will fail, // if the language gets supported by DeepL and the mock server is updated. - static::assertFalse($deeplService->isTargetLanguageSupported('BS')); + $this->assertFalse($deeplService->isTargetLanguageSupported('BS')); } #[Test] @@ -167,12 +167,12 @@ public function checkSupportedSourceLanguages(): void /** @var DeeplService $deeplService */ $deeplService = $this->get(DeeplService::class); - static::assertEquals('DE', $deeplService->detectSourceLanguage('DE')->code); - static::assertEquals('UK', $deeplService->detectSourceLanguage('UK')->code); - static::assertEquals('EN', $deeplService->detectSourceLanguage('EN')->code); - static::assertNull($deeplService->detectSourceLanguage('EN-GB')); - static::assertNull($deeplService->detectSourceLanguage('EN-US')); - static::assertNull($deeplService->detectSourceLanguage('BS')); + $this->assertEquals('DE', $deeplService->detectSourceLanguage('DE')->code); + $this->assertEquals('UK', $deeplService->detectSourceLanguage('UK')->code); + $this->assertEquals('EN', $deeplService->detectSourceLanguage('EN')->code); + $this->assertNull($deeplService->detectSourceLanguage('EN-GB')); + $this->assertNull($deeplService->detectSourceLanguage('EN-US')); + $this->assertNull($deeplService->detectSourceLanguage('BS')); } #[Test] @@ -181,7 +181,7 @@ public function checkIsSourceLanguageSupported(): void /** @var DeeplService $deeplService */ $deeplService = $this->get(DeeplService::class); - static::assertTrue($deeplService->isSourceLanguageSupported('DE')); + $this->assertTrue($deeplService->isSourceLanguageSupported('DE')); } #[Test] @@ -191,9 +191,9 @@ public function checkHasLanguageFormalitySupport(): void $deeplService = $this->get(DeeplService::class); $hasFormalitySupport = $deeplService->hasLanguageFormalitySupport('DE'); - static::assertTrue($hasFormalitySupport); + $this->assertTrue($hasFormalitySupport); $hasNotFormalitySupport = $deeplService->hasLanguageFormalitySupport('EN-GB'); - static::assertFalse($hasNotFormalitySupport); + $this->assertFalse($hasNotFormalitySupport); } } diff --git a/Tests/Functional/Services/LanguageServiceTest.php b/Tests/Functional/Services/LanguageServiceTest.php index e2f49fac..aaa86811 100644 --- a/Tests/Functional/Services/LanguageServiceTest.php +++ b/Tests/Functional/Services/LanguageServiceTest.php @@ -130,12 +130,12 @@ public function getSourceLanguageInformationIsValid(): void $sourceLanguageRecord = $languageService->getSourceLanguage($siteInformation); - static::assertArrayHasKey('uid', $sourceLanguageRecord); - static::assertArrayHasKey('title', $sourceLanguageRecord); - static::assertArrayHasKey('language_isocode', $sourceLanguageRecord); + $this->assertArrayHasKey('uid', $sourceLanguageRecord); + $this->assertArrayHasKey('title', $sourceLanguageRecord); + $this->assertArrayHasKey('language_isocode', $sourceLanguageRecord); - static::assertSame(0, $sourceLanguageRecord['uid']); - static::assertSame('EN', $sourceLanguageRecord['language_isocode']); + $this->assertSame(0, $sourceLanguageRecord['uid']); + $this->assertSame('EN', $sourceLanguageRecord['language_isocode']); } #[Test] @@ -149,7 +149,7 @@ public function setAutoDetectOptionForSourceLanguageNotSupported(): void $sourceLanguageRecord = $languageService->getSourceLanguage($siteInformation); - static::assertContains('auto', $sourceLanguageRecord); + $this->assertContains('auto', $sourceLanguageRecord); } #[Test] @@ -162,14 +162,14 @@ public function getTargetLanguageInformationIsValid(): void $siteInformation = $siteFinder->getSiteByPageId(1); $targetLanguageRecord = $languageService->getTargetLanguage($siteInformation, 2); - static::assertIsArray($targetLanguageRecord); + $this->assertIsArray($targetLanguageRecord); - static::assertArrayHasKey('uid', $targetLanguageRecord); - static::assertArrayHasKey('title', $targetLanguageRecord); - static::assertArrayHasKey('language_isocode', $targetLanguageRecord); + $this->assertArrayHasKey('uid', $targetLanguageRecord); + $this->assertArrayHasKey('title', $targetLanguageRecord); + $this->assertArrayHasKey('language_isocode', $targetLanguageRecord); - static::assertSame(2, $targetLanguageRecord['uid']); - static::assertSame('DE', $targetLanguageRecord['language_isocode']); + $this->assertSame(2, $targetLanguageRecord['uid']); + $this->assertSame('DE', $targetLanguageRecord['language_isocode']); } #[Test] diff --git a/Tests/Functional/Services/UsageServiceTest.php b/Tests/Functional/Services/UsageServiceTest.php index db040d91..367f8b09 100644 --- a/Tests/Functional/Services/UsageServiceTest.php +++ b/Tests/Functional/Services/UsageServiceTest.php @@ -35,7 +35,7 @@ public function classLoadable(): void { $usageService = $this->get(UsageService::class); - static::assertInstanceOf(UsageService::class, $usageService); + $this->assertInstanceOf(UsageService::class, $usageService); } #[Test] @@ -46,7 +46,7 @@ public function usageReturnsValue(): void $usage = $usageService->getCurrentUsage(); - static::assertInstanceOf(Usage::class, $usage); + $this->assertInstanceOf(Usage::class, $usage); } #[Test] @@ -55,7 +55,7 @@ public function limitExceedReturnsFalse(): void /** @var UsageService $usageService */ $usageService = $this->get(UsageService::class); - static::assertFalse($usageService->checkTranslateLimitWillBeExceeded('')); + $this->assertFalse($usageService->checkTranslateLimitWillBeExceeded('')); } #[Test] @@ -77,7 +77,7 @@ public function limitExceedReturnsTrueIfLimitIsReached(): void ); $isLimitExceeded = $usageService->checkTranslateLimitWillBeExceeded($translateContent); - static::assertTrue($isLimitExceeded); + $this->assertTrue($isLimitExceeded); } #[Test] @@ -99,9 +99,9 @@ public function checkHTMLMarkupsIsNotPartOfLimit(): void ); $usage = $usageService->getCurrentUsage(); - static::assertInstanceOf(Usage::class, $usage); + $this->assertInstanceOf(Usage::class, $usage); $character = $usage->character; - static::assertInstanceOf(UsageDetail::class, $character); - static::assertEquals(strlen($translateContent), $character->count); + $this->assertInstanceOf(UsageDetail::class, $character); + $this->assertEquals(strlen($translateContent), $character->count); } } diff --git a/Tests/Functional/Updates/FormalityUpgradeWizardTest.php b/Tests/Functional/Updates/FormalityUpgradeWizardTest.php index 2ddabd0d..bfa7280e 100644 --- a/Tests/Functional/Updates/FormalityUpgradeWizardTest.php +++ b/Tests/Functional/Updates/FormalityUpgradeWizardTest.php @@ -99,25 +99,25 @@ public function executeSuccessMigrationProcess(): void $wizard = GeneralUtility::makeInstance(FormalityUpgradeWizard::class); $outputMock = $this->createMock(OutputInterface::class); - $outputMock->expects(static::any()) + $outputMock->expects($this->any()) ->method('writeln'); $wizard->setOutput($outputMock); $executeUpdate = $wizard->executeUpdate(); - static::assertTrue($executeUpdate, 'Upgrade process was failed'); + $this->assertTrue($executeUpdate, 'Upgrade process was failed'); $siteConfiguration = GeneralUtility::makeInstance(SiteConfiguration::class); $loadedSiteConfiguration = $siteConfiguration->load('acme'); - static::assertArrayHasKey('languages', $loadedSiteConfiguration); + $this->assertArrayHasKey('languages', $loadedSiteConfiguration); - static::assertArrayHasKey('deeplTargetLanguage', $loadedSiteConfiguration['languages'][1]); - static::assertArrayNotHasKey('deeplFormality', $loadedSiteConfiguration['languages'][1], 'EN become formality support'); + $this->assertArrayHasKey('deeplTargetLanguage', $loadedSiteConfiguration['languages'][1]); + $this->assertArrayNotHasKey('deeplFormality', $loadedSiteConfiguration['languages'][1], 'EN become formality support'); - static::assertArrayHasKey('deeplTargetLanguage', $loadedSiteConfiguration['languages'][2]); - static::assertArrayHasKey('deeplFormality', $loadedSiteConfiguration['languages'][2], 'DE became not "deeplFormality"'); - static::assertEquals('default', $loadedSiteConfiguration['languages'][2]['deeplFormality'], 'DE became not formality support'); + $this->assertArrayHasKey('deeplTargetLanguage', $loadedSiteConfiguration['languages'][2]); + $this->assertArrayHasKey('deeplFormality', $loadedSiteConfiguration['languages'][2], 'DE became not "deeplFormality"'); + $this->assertEquals('default', $loadedSiteConfiguration['languages'][2]['deeplFormality'], 'DE became not formality support'); } } diff --git a/Tests/Unit/Access/AccessRegistryTest.php b/Tests/Unit/Access/AccessRegistryTest.php index 935ad61c..e838edba 100644 --- a/Tests/Unit/Access/AccessRegistryTest.php +++ b/Tests/Unit/Access/AccessRegistryTest.php @@ -21,12 +21,12 @@ public function registerAccessStoresTheAccessCorrectly(): void $identifier = 'testIdentifier'; $accessObject = $this->createMock(AccessItemInterface::class); - $accessObject->expects(static::once())->method('getIdentifier')->willReturn($identifier); + $accessObject->expects($this->once())->method('getIdentifier')->willReturn($identifier); $accessRegistry->addAccess($accessObject); $object = $accessRegistry->getAccess($identifier); - static::assertSame($accessObject, $object); + $this->assertSame($accessObject, $object); } #[Test] @@ -34,6 +34,6 @@ public function getAccessReturnsNullForNonExistentIdentifier(): void { $accessRegistry = new AccessRegistry(); - static::assertNull($accessRegistry->getAccess('nonExistentIdentifier')); + $this->assertNull($accessRegistry->getAccess('nonExistentIdentifier')); } } diff --git a/Tests/Unit/Access/AllowedTranslateAccessTest.php b/Tests/Unit/Access/AllowedTranslateAccessTest.php index a34f9377..eb6c661b 100644 --- a/Tests/Unit/Access/AllowedTranslateAccessTest.php +++ b/Tests/Unit/Access/AllowedTranslateAccessTest.php @@ -22,30 +22,30 @@ protected function setUp(): void #[Test] public function hasInterfaceImplementation(): void { - static::assertInstanceOf(AccessItemInterface::class, $this->accessInstance); + $this->assertInstanceOf(AccessItemInterface::class, $this->accessInstance); } #[Test] public function getIdentifier(): void { - static::assertSame('translateAllowed', $this->accessInstance->getIdentifier()); + $this->assertSame('translateAllowed', $this->accessInstance->getIdentifier()); } #[Test] public function getTitle(): void { - static::assertIsString($this->accessInstance->getTitle()); + $this->assertIsString($this->accessInstance->getTitle()); } #[Test] public function getDescription(): void { - static::assertIsString($this->accessInstance->getDescription()); + $this->assertIsString($this->accessInstance->getDescription()); } #[Test] public function getIconIdentifier(): void { - static::assertSame('deepl-logo', $this->accessInstance->getIconIdentifier()); + $this->assertSame('deepl-logo', $this->accessInstance->getIconIdentifier()); } } From 54a8467258729488803f27aa3a727bdac505c5fd Mon Sep 17 00:00:00 2001 From: Markus Hofmann Date: Wed, 8 Oct 2025 10:07:44 +0200 Subject: [PATCH 5/5] [WIP][TASK] Introduce DeepL Glossary v3 Currently work in progress --- Classes/AbstractClient.php | 53 ----- Classes/Client.php | 114 +++------ Classes/Client/DeepLAPIClient.php | 17 ++ Classes/Client/DeepLClientFactory.php | 14 ++ Classes/Client/DeepLClientInterface.php | 222 ++++++++++++++++++ Classes/Client/DummyTranslateClient.php | 90 +++++++ Classes/ClientConnectionTrait.php | 13 + Classes/ClientInterface.php | 57 +---- Classes/Configuration.php | 63 ++++- Classes/ConfigurationInterface.php | 13 + Classes/Service/DeeplService.php | 22 +- Classes/Service/UsageService.php | 6 +- Classes/TranslatorInterface.php | 34 +++ Classes/UsageInterface.php | 16 ++ Configuration/Services.yaml | 2 +- Tests/Functional/AbstractDeepLTestCase.php | 9 +- Tests/Functional/ClientTest.php | 18 +- .../Configuration/Services.php | 7 +- Tests/Unit/ClientTest.php | 15 +- ext_conf_template.txt | 18 +- 20 files changed, 576 insertions(+), 227 deletions(-) delete mode 100644 Classes/AbstractClient.php create mode 100644 Classes/Client/DeepLAPIClient.php create mode 100644 Classes/Client/DeepLClientFactory.php create mode 100644 Classes/Client/DeepLClientInterface.php create mode 100644 Classes/Client/DummyTranslateClient.php create mode 100644 Classes/ClientConnectionTrait.php create mode 100644 Classes/TranslatorInterface.php create mode 100644 Classes/UsageInterface.php diff --git a/Classes/AbstractClient.php b/Classes/AbstractClient.php deleted file mode 100644 index 1cda3a77..00000000 --- a/Classes/AbstractClient.php +++ /dev/null @@ -1,53 +0,0 @@ -configuration = $configuration; - } - - public function setLogger(LoggerInterface $logger): void - { - $this->logger = $logger; - } - - /** - * Wrapper function to handel ApiKey exception - * - * @throws ApiKeyNotSetException - */ - protected function getTranslator(): DeepLClient - { - if ($this->translator instanceof DeepLClient) { - return $this->translator; - } - if ($this->configuration->getApiKey() === '') { - throw new ApiKeyNotSetException('The api key ist not set', 1708081233823); - } - $options[DeepLClientOptions::HTTP_CLIENT] = GeneralUtility::makeInstance(GuzzleClientFactory::class)->getClient(); - $this->translator = new DeepLClient($this->configuration->getApiKey(), $options); - return $this->translator; - } - -} diff --git a/Classes/Client.php b/Classes/Client.php index 3b33869c..15511675 100644 --- a/Classes/Client.php +++ b/Classes/Client.php @@ -4,24 +4,41 @@ namespace WebVision\Deepltranslate\Core; +use DeepL\DeepLClient; +use DeepL\DeepLClientOptions; use DeepL\DeepLException; use DeepL\GlossaryEntries; use DeepL\GlossaryInfo; use DeepL\GlossaryLanguagePair; use DeepL\Language; -use DeepL\MultilingualGlossaryDictionaryEntries; -use DeepL\MultilingualGlossaryDictionaryInfo; -use DeepL\MultilingualGlossaryInfo; use DeepL\TextResult; use DeepL\TranslateTextOptions; use DeepL\Usage; +use Psr\Log\LoggerInterface; +use TYPO3\CMS\Core\Http\Client\GuzzleClientFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; use WebVision\Deepltranslate\Core\Exception\ApiKeyNotSetException; /** * @internal No public usage + * @todo split the client into two separate services? */ -final class Client extends AbstractClient +final class Client implements TranslatorInterface, UsageInterface { + private ?DeepLClient $translator; + public function __construct( + private readonly ConfigurationInterface $configuration, + private readonly LoggerInterface $logger + ) { + if ($this->configuration->getApiKey() === '') { + $this->logger->notice('DeepL API key is not set. DeepL translation is disabled'); + $this->translator = null; + return; + } + $options[DeepLClientOptions::HTTP_CLIENT] = GeneralUtility::makeInstance(GuzzleClientFactory::class)->getClient(); + $this->translator = new DeepLClient($this->configuration->getApiKey(), $options); + } + /** * @return TextResult|TextResult[]|null * @@ -33,7 +50,7 @@ public function translate( string $targetLang, string $glossary = '', string $formality = '' - ) { + ): array|TextResult|null { $options = [ TranslateTextOptions::FORMALITY => $formality ?: 'default', TranslateTextOptions::TAG_HANDLING => 'xml', @@ -44,7 +61,7 @@ public function translate( } try { - return $this->getTranslator()->translateText( + return $this->translator?->translateText( $content, $sourceLang, $targetLang, @@ -69,9 +86,9 @@ public function translate( public function getSupportedLanguageByType(string $type = 'target'): array { try { - return ($type === 'target') - ? $this->getTranslator()->getTargetLanguages() - : $this->getTranslator()->getSourceLanguages(); + return (($type === 'target') + ? $this->translator?->getTargetLanguages() + : $this->translator?->getSourceLanguages()) ?? []; } catch (DeepLException $exception) { $this->logger->error(sprintf( '%s (%d)', @@ -91,7 +108,7 @@ public function getSupportedLanguageByType(string $type = 'target'): array public function getGlossaryLanguagePairs(): array { try { - return $this->getTranslator()->getGlossaryLanguages(); + return $this->translator?->getGlossaryLanguages() ?? []; } catch (DeepLException $exception) { $this->logger->error(sprintf( '%s (%d)', @@ -111,7 +128,7 @@ public function getGlossaryLanguagePairs(): array public function getAllGlossaries(): array { try { - return $this->getTranslator()->listGlossaries(); + return $this->translator?->listGlossaries() ?? []; } catch (DeepLException $exception) { $this->logger->error(sprintf( '%s (%d)', @@ -129,7 +146,7 @@ public function getAllGlossaries(): array public function getGlossary(string $glossaryId): ?GlossaryInfo { try { - return $this->getTranslator()->getGlossary($glossaryId); + return $this->translator?->getGlossary($glossaryId); } catch (DeepLException $exception) { $this->logger->error(sprintf( '%s (%d)', @@ -167,12 +184,12 @@ public function createGlossary( $prepareEntriesForGlossary[$source] = $target; } try { - return $this->getTranslator()->createGlossary( + return $this->translator?->createGlossary( $glossaryName, $sourceLang, $targetLang, GlossaryEntries::fromEntries($prepareEntriesForGlossary) - ); + ) ?? throw new DeepLException(); } catch (DeepLException $e) { return new GlossaryInfo( '', @@ -192,7 +209,7 @@ public function createGlossary( public function deleteGlossary(string $glossaryId): void { try { - $this->getTranslator()->deleteGlossary($glossaryId); + $this->translator?->deleteGlossary($glossaryId); } catch (DeepLException $exception) { $this->logger->error(sprintf( '%s (%d)', @@ -208,7 +225,7 @@ public function deleteGlossary(string $glossaryId): void public function getGlossaryEntries(string $glossaryId): ?GlossaryEntries { try { - return $this->getTranslator()->getGlossaryEntries($glossaryId); + return $this->translator?->getGlossaryEntries($glossaryId); } catch (DeepLException $exception) { $this->logger->error(sprintf( '%s (%d)', @@ -226,7 +243,7 @@ public function getGlossaryEntries(string $glossaryId): ?GlossaryEntries public function getUsage(): ?Usage { try { - return $this->getTranslator()->getUsage(); + return $this->translator?->getUsage(); } catch (DeepLException $exception) { $this->logger->error(sprintf( '%s (%d)', @@ -237,67 +254,4 @@ public function getUsage(): ?Usage return null; } - - /** - * @throws DeepLException - * @throws ApiKeyNotSetException - */ - public function createMultilingualGlossary(string $name, array $dictionaries = []): MultilingualGlossaryInfo - { - return $this->getTranslator()->createMultilingualGlossary($name, $dictionaries); - } - - /** - * @throws DeepLException - * @throws ApiKeyNotSetException - */ - public function updateMultilingualGlossary(string $glossaryId, array $dictionaries, string $newName = ''): MultilingualGlossaryInfo - { - return $this->getTranslator()->updateMultilingualGlossary( - $glossaryId, - $newName !== '' ? $newName : null, - $dictionaries - ); - } - - /** - * @throws DeepLException - * @throws ApiKeyNotSetException - */ - public function replaceMultilingualGlossary(string $glossaryId, MultilingualGlossaryDictionaryEntries $dictionaries): MultilingualGlossaryDictionaryInfo - { - return $this->getTranslator()->replaceMultilingualGlossaryDictionary($glossaryId, $dictionaries); - } - - /** - * @throws DeepLException - * @throws ApiKeyNotSetException - */ - public function deleteMultilingualGlossary(string $glossaryId): void - { - $this->getTranslator()->deleteMultilingualGlossary($glossaryId); - } - - /** - * @throws DeepLException - * @throws ApiKeyNotSetException - */ - public function listMultilingualGlossaries(): array - { - return $this->getTranslator()->listMultilingualGlossaries(); - } - - /** - * @throws DeepLException - * @throws ApiKeyNotSetException - */ - public function deleteGlossaryDictionary(string $glossaryId, string $sourceLanguage, string $targetLanguage): void - { - $this->getTranslator()->deleteMultilingualGlossaryDictionary( - $glossaryId, - null, - $sourceLanguage, - $targetLanguage - ); - } } diff --git a/Classes/Client/DeepLAPIClient.php b/Classes/Client/DeepLAPIClient.php new file mode 100644 index 00000000..900b0048 --- /dev/null +++ b/Classes/Client/DeepLAPIClient.php @@ -0,0 +1,17 @@ +logger->info('Dummy DeepL called'); + return []; + } + + /** + * @inheritDoc + */ + public function getAllGlossaries(): array + { + $this->logger->info('Dummy DeepL called'); + return []; + } + + /** + * @inheritDoc + */ + public function getGlossary(string $glossaryId): ?GlossaryInfo + { + $this->logger->info('Dummy DeepL called'); + return null; + } + + /** + * @inheritDoc + */ + public function createGlossary(string $glossaryName, string $sourceLang, string $targetLang, array $entries): GlossaryInfo + { + $this->logger->info('Dummy DeepL called'); + return new GlossaryInfo('', '', false, $sourceLang, $targetLang, new \DateTime(), count($entries)); + } + + /** + * @inheritDoc + */ + public function deleteGlossary(string $glossaryId): void + { + $this->logger->info('Dummy DeepL called'); + } + + /** + * @inheritDoc + */ + public function getGlossaryEntries(string $glossaryId): ?GlossaryEntries + { + $this->logger->info('Dummy DeepL called'); + return null; + } + + /** + * @inheritDoc + */ + public function translate(string $content, ?string $sourceLang, string $targetLang, string $glossary = '', string $formality = ''): array|TextResult|null + { + $this->logger->info('Dummy DeepL called'); + return new TextResult($content, $sourceLang, 0); + } + + /** + * @inheritDoc + */ + public function getSupportedLanguageByType(string $type = 'target'): array + { + $this->logger->info('Dummy DeepL called'); + return []; + } +} diff --git a/Classes/ClientConnectionTrait.php b/Classes/ClientConnectionTrait.php new file mode 100644 index 00000000..1af947ae --- /dev/null +++ b/Classes/ClientConnectionTrait.php @@ -0,0 +1,13 @@ +get('deepltranslate_core'); $this->apiKey = (string)($extensionConfiguration['apiKey'] ?? ''); + $this->modelType = (string)($extensionConfiguration['modelType'] ?? 'prefer_quality_optimized'); + $this->splitSentences = (bool)($extensionConfiguration['splitSentences'] ?? true); + $this->preserveFormatting = (bool)($extensionConfiguration['preserverFormatting'] ?? false); + $this->ignoreTags = GeneralUtility::trimExplode(',', ($extensionConfiguration['ignoreTags'] ?? ''), true); + $this->nonSplittingTags = GeneralUtility::trimExplode(',', ($extensionConfiguration['nonSplittingTags'] ?? ''), true); + $this->splittingTags = GeneralUtility::trimExplode(',', ($extensionConfiguration['splittingTags'] ?? ''), true); + $this->outlineDetection = (bool)($extensionConfiguration['outlineDetection'] ?? true); } public function getApiKey(): string { return $this->apiKey; } + + public function getModelType(): string + { + return $this->modelType; + } + + public function isSplitSentenceEnabled(): bool + { + return $this->splitSentences; + } + + public function isPreserveFormattingEnabled(): bool + { + return $this->preserveFormatting; + } + + public function getIgnoreTags(): string + { + return implode(',', $this->ignoreTags); + } + + public function getNonSplittingTags(): string + { + return implode(',', $this->nonSplittingTags); + } + + public function getSplittingTags(): string + { + return implode(',', $this->splittingTags); + } + + public function isOutlineDetectionEnabled(): bool + { + return $this->outlineDetection; + } } diff --git a/Classes/ConfigurationInterface.php b/Classes/ConfigurationInterface.php index f08874de..c9ba0380 100644 --- a/Classes/ConfigurationInterface.php +++ b/Classes/ConfigurationInterface.php @@ -13,4 +13,17 @@ interface ConfigurationInterface { public function getApiKey(): string; + + public function getModelType(): string; + + public function isSplitSentenceEnabled(): bool; + public function isPreserveFormattingEnabled(): bool; + + public function getIgnoreTags(): string; + + public function getNonSplittingTags(): string; + + public function getSplittingTags(): string; + + public function isOutlineDetectionEnabled(): bool; } diff --git a/Classes/Service/DeeplService.php b/Classes/Service/DeeplService.php index 40e3aea7..d50a4194 100644 --- a/Classes/Service/DeeplService.php +++ b/Classes/Service/DeeplService.php @@ -5,30 +5,28 @@ namespace WebVision\Deepltranslate\Core\Service; use DeepL\Language; -use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; -use WebVision\Deepltranslate\Core\ClientInterface; use WebVision\Deepltranslate\Core\Domain\Dto\TranslateContext; use WebVision\Deepltranslate\Core\Event\DeepLGlossaryIdEvent; use WebVision\Deepltranslate\Core\Exception\ApiKeyNotSetException; +use WebVision\Deepltranslate\Core\TranslatorInterface; use WebVision\Deepltranslate\Core\Utility\DeeplBackendUtility; -final class DeeplService implements LoggerAwareInterface +final class DeeplService { - use LoggerAwareTrait; - private FrontendInterface $cache; - private ClientInterface $client; + private TranslatorInterface $client; private ProcessingInstruction $processingInstruction; public function __construct( FrontendInterface $cache, - ClientInterface $client, + TranslatorInterface $client, ProcessingInstruction $processingInstruction, - private readonly EventDispatcher $eventDispatcher + private readonly EventDispatcher $eventDispatcher, + private readonly LoggerInterface $logger ) { $this->cache = $cache; $this->client = $client; @@ -59,7 +57,7 @@ public function translateRequest( public function translateContent(TranslateContext $translateContext): string { if ($this->processingInstruction->isDeeplMode() === false) { - $this->logger?->warning('DeepL mode not set. Exit.'); + $this->logger->warning('DeepL mode not set. Exit.'); return $translateContext->getContent(); } // If the source language is set to Autodetect, no glossary can be detected. @@ -89,7 +87,7 @@ public function translateContent(TranslateContext $translateContext): string } if ($response === null) { - $this->logger?->warning('Translation not successful'); + $this->logger->warning('Translation not successful'); return ''; } @@ -225,7 +223,7 @@ private function loadSupportedLanguagesFromAPI(string $type = 'target'): array try { return $this->client->getSupportedLanguageByType($type); } catch (ApiKeyNotSetException $exception) { - $this->logger?->error(sprintf('%s (%d)', $exception->getMessage(), $exception->getCode())); + $this->logger->error(sprintf('%s (%d)', $exception->getMessage(), $exception->getCode())); return []; } } diff --git a/Classes/Service/UsageService.php b/Classes/Service/UsageService.php index 0bd4311e..b8b29193 100644 --- a/Classes/Service/UsageService.php +++ b/Classes/Service/UsageService.php @@ -8,16 +8,16 @@ use TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity; -use WebVision\Deepltranslate\Core\ClientInterface; use WebVision\Deepltranslate\Core\Event\Listener\UsageToolBarEventListener; use WebVision\Deepltranslate\Core\Hooks\UsageProcessAfterFinishHook; +use WebVision\Deepltranslate\Core\UsageInterface; final class UsageService implements UsageServiceInterface { - protected ClientInterface $client; + protected UsageInterface $client; public function __construct( - ClientInterface $client + UsageInterface $client ) { $this->client = $client; } diff --git a/Classes/TranslatorInterface.php b/Classes/TranslatorInterface.php new file mode 100644 index 00000000..ac545163 --- /dev/null +++ b/Classes/TranslatorInterface.php @@ -0,0 +1,34 @@ +method('getApiKey') ->willReturn(self::getInstanceIdentifier()); - $client = new Client($mockConfiguration); - $client->setLogger(new NullLogger()); + $client = new Client($mockConfiguration, new NullLogger()); // use closure to set private option for translation $translator = new DeepLClient(self::getInstanceIdentifier(), $mergedOptions); @@ -233,7 +233,8 @@ function (DeepLClient $translator) { /** @var Container $container */ $container = $this->getContainer(); - $container->set(ClientInterface::class, $client); + $container->set(TranslatorInterface::class, $client); + $container->set(UsageInterface::class, $client); } public static function readFile(string $filepath): string diff --git a/Tests/Functional/ClientTest.php b/Tests/Functional/ClientTest.php index ebe4d6af..619154d9 100644 --- a/Tests/Functional/ClientTest.php +++ b/Tests/Functional/ClientTest.php @@ -14,7 +14,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use WebVision\Deepltranslate\Core\Client; -use WebVision\Deepltranslate\Core\ClientInterface; +use WebVision\Deepltranslate\Core\TranslatorInterface; #[CoversClass(Client::class)] final class ClientTest extends AbstractDeepLTestCase @@ -34,7 +34,7 @@ protected function setUp(): void public function checkResponseFromTranslateContent(): void { $translateContent = self::EXAMPLE_TEXT['en']; - $client = $this->get(ClientInterface::class); + $client = $this->get(TranslatorInterface::class); $response = $client->translate( $translateContent, 'EN', @@ -48,7 +48,7 @@ public function checkResponseFromTranslateContent(): void #[Test] public function checkResponseFromSupportedTargetLanguage(): void { - $client = $this->get(ClientInterface::class); + $client = $this->get(TranslatorInterface::class); $response = $client->getSupportedLanguageByType(); $this->assertIsArray($response); @@ -58,7 +58,7 @@ public function checkResponseFromSupportedTargetLanguage(): void #[Test] public function checkResponseFromGlossaryLanguagePairs(): void { - $client = $this->get(ClientInterface::class); + $client = $this->get(TranslatorInterface::class); $response = $client->getGlossaryLanguagePairs(); $this->assertIsArray($response); @@ -68,7 +68,7 @@ public function checkResponseFromGlossaryLanguagePairs(): void #[Test] public function checkResponseFromCreateGlossary(): void { - $client = $this->get(ClientInterface::class); + $client = $this->get(TranslatorInterface::class); $response = $client->createGlossary( 'Deepl-Client-Create-Function-Test:' . __FUNCTION__, 'de', @@ -90,7 +90,7 @@ public function checkResponseFromCreateGlossary(): void #[Test] public function checkResponseGetAllGlossaries(): void { - $client = $this->get(ClientInterface::class); + $client = $this->get(TranslatorInterface::class); $response = $client->getAllGlossaries(); $this->assertIsArray($response); @@ -100,7 +100,7 @@ public function checkResponseGetAllGlossaries(): void #[Test] public function checkResponseFromGetGlossary(): void { - $client = $this->get(ClientInterface::class); + $client = $this->get(TranslatorInterface::class); $glossary = $client->createGlossary( 'Deepl-Client-Create-Function-Test:' . __FUNCTION__, 'de', @@ -123,7 +123,7 @@ public function checkResponseFromGetGlossary(): void #[Test] public function checkGlossaryDeletedNotCatchable(): void { - $client = $this->get(ClientInterface::class); + $client = $this->get(TranslatorInterface::class); $glossary = $client->createGlossary( 'Deepl-Client-Create-Function-Test' . __FUNCTION__, 'de', @@ -146,7 +146,7 @@ public function checkGlossaryDeletedNotCatchable(): void #[Test] public function checkResponseFromGetGlossaryEntries(): void { - $client = $this->get(ClientInterface::class); + $client = $this->get(TranslatorInterface::class); $glossary = $client->createGlossary( 'Deepl-Client-Create-Function-Test:' . __FUNCTION__, 'de', diff --git a/Tests/Functional/Fixtures/Extensions/test_services_override/Configuration/Services.php b/Tests/Functional/Fixtures/Extensions/test_services_override/Configuration/Services.php index 7cf0d4b7..b2e4da97 100644 --- a/Tests/Functional/Fixtures/Extensions/test_services_override/Configuration/Services.php +++ b/Tests/Functional/Fixtures/Extensions/test_services_override/Configuration/Services.php @@ -4,6 +4,8 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use WebVision\Deepltranslate\Core\ClientInterface; +use WebVision\Deepltranslate\Core\TranslatorInterface; +use WebVision\Deepltranslate\Core\UsageInterface; return function (ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder) { $services = $containerConfigurator->services(); @@ -16,6 +18,9 @@ // functional testcases can set a special configured or mocked service // instance for the alias. No need to have it public in general. $services - ->set(ClientInterface::class) + ->set(TranslatorInterface::class) + ->public(); + $services + ->set(UsageInterface::class) ->public(); }; diff --git a/Tests/Unit/ClientTest.php b/Tests/Unit/ClientTest.php index e47cd92c..4acc69e7 100644 --- a/Tests/Unit/ClientTest.php +++ b/Tests/Unit/ClientTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\NullLogger; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; use WebVision\Deepltranslate\Core\Client; use WebVision\Deepltranslate\Core\ConfigurationInterface; @@ -31,7 +32,7 @@ public function throwErrorGetSupportedLanguageByTypeWhenApiKeyNotSet(): void { /** @var ConfigurationInterface $configurationMock */ $configurationMock = $this->createMockConfigurationWithEmptyApiKey(); - $client = new Client($configurationMock); + $client = new Client($configurationMock, new NullLogger()); static::expectException(ApiKeyNotSetException::class); static::expectExceptionCode(1708081233823); @@ -45,7 +46,7 @@ public function throwErrorGetGlossaryLanguagePairsWhenApiKeyNotSet(): void { /** @var ConfigurationInterface $configurationMock */ $configurationMock = $this->createMockConfigurationWithEmptyApiKey(); - $client = new Client($configurationMock); + $client = new Client($configurationMock, new NullLogger()); static::expectException(ApiKeyNotSetException::class); static::expectExceptionCode(1708081233823); @@ -59,7 +60,7 @@ public function throwErrorCreateGlossaryEntriesWhenApiKeyNotSet(): void { /** @var ConfigurationInterface $configurationMock */ $configurationMock = $this->createMockConfigurationWithEmptyApiKey(); - $client = new Client($configurationMock); + $client = new Client($configurationMock, new NullLogger()); static::expectException(ApiKeyNotSetException::class); static::expectExceptionCode(1708081233823); @@ -83,7 +84,7 @@ public function throwErrorGetGlossaryWhenApiKeyNotSet(): void { /** @var ConfigurationInterface $configurationMock */ $configurationMock = $this->createMockConfigurationWithEmptyApiKey(); - $client = new Client($configurationMock); + $client = new Client($configurationMock, new NullLogger()); static::expectException(ApiKeyNotSetException::class); static::expectExceptionCode(1708081233823); @@ -97,7 +98,7 @@ public function throwErrorDeletedGlossaryWhenApiKeyNotSet(): void { /** @var ConfigurationInterface $configurationMock */ $configurationMock = $this->createMockConfigurationWithEmptyApiKey(); - $client = new Client($configurationMock); + $client = new Client($configurationMock, new NullLogger()); static::expectException(ApiKeyNotSetException::class); static::expectExceptionCode(1708081233823); @@ -111,7 +112,7 @@ public function throwErrorGlossaryEntriesWhenApiKeyNotSet(): void { /** @var ConfigurationInterface $configurationMock */ $configurationMock = $this->createMockConfigurationWithEmptyApiKey(); - $client = new Client($configurationMock); + $client = new Client($configurationMock, new NullLogger()); static::expectException(ApiKeyNotSetException::class); static::expectExceptionCode(1708081233823); @@ -125,7 +126,7 @@ public function throwErrorTranslationExceptionWhenApiKeyNotSet(): void { /** @var ConfigurationInterface $configurationMock */ $configurationMock = $this->createMockConfigurationWithEmptyApiKey(); - $client = new Client($configurationMock); + $client = new Client($configurationMock, new NullLogger()); static::expectException(ApiKeyNotSetException::class); static::expectExceptionCode(1708081233823); diff --git a/ext_conf_template.txt b/ext_conf_template.txt index 97df6e8a..7082b54d 100644 --- a/ext_conf_template.txt +++ b/ext_conf_template.txt @@ -1,2 +1,16 @@ -# cat=Settings/O; type=string; label= Deepl API Key -apiKey = +# cat=Settings//O; type=string; label=Deepl API Key +apiKey= +# cat=Api//1; type=options[Latency optimized=latency_optimized,Quality optimized=quality_optimized, Prefer quality optimized (with fallback)=prefer_quality_optimized]; label=DeepL model: Default prefer quality optimized +modelType=prefer_quality_optimized +# cat=Api//2; type=options[No splitting=0, Split at punctuation and newlines=1, Split only at new lines=nonewlines]; label=Enable Text splitting: Default "Split at punctuation and newlines" +splitSentences=1 +# cat=Api//3; type=boolean; label=Keep existing formatting +preserveFormatting=0 +# cat=Api//4; type=string; label=Exclude content from being translated in between these tags: Comma separated, Useful for placeholders, shortcodes, pre +ignoreTags= +#cat=Api//5; type=string; label=Prevent splitting inside these tags: Comma separated, Allows the API keeping the text unsplitted +nonSplittingTags= +#cat=Api//6; type=string; label=Split inside these tags: Comma separated, Defines tags, which should be splitted (p, div) +splittingTags= +# cat=Api//7; type=boolean; Enable automatic detection of document structure in XML/HTML +outlineDetection=1