diff --git a/src/CrowdinApiClient/Api/TranslationApi.php b/src/CrowdinApiClient/Api/TranslationApi.php index 478f5075..f05d6b61 100644 --- a/src/CrowdinApiClient/Api/TranslationApi.php +++ b/src/CrowdinApiClient/Api/TranslationApi.php @@ -7,6 +7,7 @@ use CrowdinApiClient\Model\DownloadFileTranslation; use CrowdinApiClient\Model\PreTranslation; use CrowdinApiClient\Model\PreTranslationReport; +use CrowdinApiClient\Model\TranslationAlignment; use CrowdinApiClient\Model\TranslationProjectBuild; use CrowdinApiClient\Model\TranslationProjectDirectory; use CrowdinApiClient\ModelCollection; @@ -269,4 +270,23 @@ public function exportProjectTranslation(int $projectId, array $params = []): Do $path = sprintf('projects/%d/translations/exports', $projectId); return $this->_post($path, DownloadFile::class, $params); } + + /** + * Translation Alignment + * @link https://developer.crowdin.com/api/v2/#operation/api.projects.translations.alignment.post API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.projects.translations.alignment.post API Documentation Enterprise + * + * @param int $projectId + * @param array $params + * string $params[sourceLanguageId] required
+ * string $params[targetLanguageId] required
+ * string $params[text] required + * + * @return TranslationAlignment|null + */ + public function alignment(int $projectId, array $params): ?TranslationAlignment + { + $path = sprintf('projects/%d/translations/alignment', $projectId); + return $this->_post($path, TranslationAlignment::class, $params); + } } diff --git a/src/CrowdinApiClient/Api/TranslationMemoryApi.php b/src/CrowdinApiClient/Api/TranslationMemoryApi.php index b2d7601e..72a5ce15 100644 --- a/src/CrowdinApiClient/Api/TranslationMemoryApi.php +++ b/src/CrowdinApiClient/Api/TranslationMemoryApi.php @@ -2,8 +2,10 @@ namespace CrowdinApiClient\Api; +use CrowdinApiClient\Http\ResponseDecorator\ResponseModelListDecorator; use CrowdinApiClient\Model\DownloadFile; use CrowdinApiClient\Model\TranslationMemory; +use CrowdinApiClient\Model\TranslationMemoryConcordance; use CrowdinApiClient\Model\TranslationMemoryExport; use CrowdinApiClient\Model\TranslationMemoryImport; use CrowdinApiClient\ModelCollection; @@ -165,4 +167,32 @@ public function checkImportStatus(int $translationMemoryId, string $importId): ? $path = sprintf('tms/%d/imports/%s', $translationMemoryId, $importId); return $this->_get($path, TranslationMemoryImport::class); } + + /** + * Concordance search in TMs + * @link https://developer.crowdin.com/api/v2/#operation/api.projects.tms.concordance.post API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.projects.tms.concordance.post API Documentation Enterprise + * + * @param int $projectId + * @param array $params + * string $params[sourceLanguageId] required
+ * string $params[targetLanguageId] required
+ * bool $params[autoSubstitution] required Improves TM suggestions
+ * int $params[minRelevant] required Show TM suggestions with specified minimum match (1-100)
+ * string[] $params[expressions] required Note: Can't be used with expression in same request
+ * string $params[expression] Deprecated Note: Can't be used with expressions in same request + * @return ModelCollection|null + */ + public function concordance(int $projectId, array $params): ?ModelCollection + { + return $this->client->apiRequest( + 'post', + sprintf('projects/%d/tms/concordance', $projectId), + new ResponseModelListDecorator(TranslationMemoryConcordance::class), + [ + 'body' => json_encode($params), + 'headers' => $this->getHeaders(), + ] + ); + } } diff --git a/src/CrowdinApiClient/Model/Alignment.php b/src/CrowdinApiClient/Model/Alignment.php new file mode 100644 index 00000000..e0a7c3c1 --- /dev/null +++ b/src/CrowdinApiClient/Model/Alignment.php @@ -0,0 +1,101 @@ +sourceWord = (string)$this->getDataProperty('sourceWord'); + $this->sourceLemma = (string)$this->getDataProperty('sourceLemma'); + $this->targetWord = (string)$this->getDataProperty('targetWord'); + $this->targetLemma = (string)$this->getDataProperty('targetLemma'); + $this->match = (int)$this->getDataProperty('match'); + $this->probability = (float)$this->getDataProperty('probability'); + } + + /** + * @return string + */ + public function getSourceWord(): string + { + return $this->sourceWord; + } + + /** + * @return string + */ + public function getSourceLemma(): string + { + return $this->sourceLemma; + } + + /** + * @return string + */ + public function getTargetWord(): string + { + return $this->targetWord; + } + + /** + * @return string + */ + public function getTargetLemma(): string + { + return $this->targetLemma; + } + + /** + * @return int + */ + public function getMatch(): int + { + return $this->match; + } + + /** + * @return float + */ + public function getProbability(): float + { + return $this->probability; + } +} diff --git a/src/CrowdinApiClient/Model/TranslationAlignment.php b/src/CrowdinApiClient/Model/TranslationAlignment.php new file mode 100644 index 00000000..1c3744fd --- /dev/null +++ b/src/CrowdinApiClient/Model/TranslationAlignment.php @@ -0,0 +1,36 @@ +words = array_map( + static function (array $word): WordAlignment { + return new WordAlignment($word); + }, + (array)$this->getDataProperty('words') + ); + } + + /** + * @return WordAlignment[] + */ + public function getWords(): array + { + return $this->words; + } +} diff --git a/src/CrowdinApiClient/Model/TranslationMemoryConcordance.php b/src/CrowdinApiClient/Model/TranslationMemoryConcordance.php new file mode 100644 index 00000000..a040116d --- /dev/null +++ b/src/CrowdinApiClient/Model/TranslationMemoryConcordance.php @@ -0,0 +1,100 @@ +tm = new TranslationMemory($this->getDataProperty('tm')); + $this->recordId = (int)$this->getDataProperty('recordId'); + $this->source = (string)$this->getDataProperty('source'); + $this->target = (string)$this->getDataProperty('target'); + $this->relevant = (int)$this->getDataProperty('relevant'); + $this->substituted = $this->getDataProperty('substituted'); + $this->updatedAt = $this->getDataProperty('updatedAt'); + } + + public function getTm(): TranslationMemory + { + return $this->tm; + } + + public function getRecordId(): int + { + return $this->recordId; + } + + public function getSource(): string + { + return $this->source; + } + + public function getTarget(): string + { + return $this->target; + } + + public function getRelevant(): int + { + return $this->relevant; + } + + /** + * @return string|null + */ + public function getSubstituted(): ?string + { + return $this->substituted; + } + + /** + * @return string|null + */ + public function getUpdatedAt(): ?string + { + return $this->updatedAt; + } +} diff --git a/src/CrowdinApiClient/Model/WordAlignment.php b/src/CrowdinApiClient/Model/WordAlignment.php new file mode 100644 index 00000000..396198dc --- /dev/null +++ b/src/CrowdinApiClient/Model/WordAlignment.php @@ -0,0 +1,50 @@ +text = (string)$this->getDataProperty('text'); + $this->alignments = array_map( + static function (array $alignment): Alignment { + return new Alignment($alignment); + }, + (array)$this->getDataProperty('alignments') + ); + } + + /** + * @return string + */ + public function getText(): string + { + return $this->text; + } + + /** + * @return Alignment[] + */ + public function getAlignments(): array + { + return $this->alignments; + } +} diff --git a/tests/CrowdinApiClient/Api/TranslationApiTest.php b/tests/CrowdinApiClient/Api/TranslationApiTest.php index 9cb60697..2d457c5b 100644 --- a/tests/CrowdinApiClient/Api/TranslationApiTest.php +++ b/tests/CrowdinApiClient/Api/TranslationApiTest.php @@ -2,12 +2,15 @@ namespace CrowdinApiClient\Tests\Api; +use CrowdinApiClient\Model\Alignment; use CrowdinApiClient\Model\DownloadFile; use CrowdinApiClient\Model\PreTranslation; use CrowdinApiClient\Model\PreTranslationReport; use CrowdinApiClient\Model\PreTranslationReportFile; use CrowdinApiClient\Model\PreTranslationReportLanguage; +use CrowdinApiClient\Model\TranslationAlignment; use CrowdinApiClient\Model\TranslationProjectBuild; +use CrowdinApiClient\Model\WordAlignment; use CrowdinApiClient\ModelCollection; class TranslationApiTest extends AbstractTestApi @@ -476,4 +479,56 @@ public function testExportProjectTranslation(): void $file->getUrl() ); } + + public function testAlignment(): void + { + $params = [ + 'sourceLanguageId' => 'en', + 'targetLanguageId' => 'de', + 'text' => 'Your password has been reset successfully!', + ]; + + $this->mockRequest([ + 'path' => '/projects/8/translations/alignment', + 'method' => 'post', + 'response' => json_encode([ + 'data' => [ + 'words' => [ + [ + 'text' => 'password', + 'alignments' => [ + [ + 'sourceWord' => 'Password', + 'sourceLemma' => 'password', + 'targetWord' => 'Пароль', + 'targetLemma' => 'пароль', + 'match' => 2, + 'probability' => 2.0, + ], + ], + ], + ], + ], + ]), + 'options' => [ + 'body' => [ + 'sourceLanguageId' => 'en', + 'targetLanguageId' => 'de', + 'text' => 'Your password has been reset successfully!', + ], + ], + ]); + + $alignment = $this->crowdin->translation->alignment(8, $params); + + $this->assertInstanceOf(TranslationAlignment::class, $alignment); + $this->assertIsArray($alignment->getWords()); + $this->assertCount(1, $alignment->getWords()); + $this->assertInstanceOf(WordAlignment::class, $alignment->getWords()[0]); + $this->assertEquals('password', $alignment->getWords()[0]->getText()); + $this->assertCount(1, $alignment->getWords()[0]->getAlignments()); + $this->assertInstanceOf(Alignment::class, $alignment->getWords()[0]->getAlignments()[0]); + $this->assertEquals('Password', $alignment->getWords()[0]->getAlignments()[0]->getSourceWord()); + $this->assertEquals('Пароль', $alignment->getWords()[0]->getAlignments()[0]->getTargetWord()); + } } diff --git a/tests/CrowdinApiClient/Api/TranslationMemoryApiTest.php b/tests/CrowdinApiClient/Api/TranslationMemoryApiTest.php index e3ab9f7e..25078d20 100644 --- a/tests/CrowdinApiClient/Api/TranslationMemoryApiTest.php +++ b/tests/CrowdinApiClient/Api/TranslationMemoryApiTest.php @@ -4,6 +4,7 @@ use CrowdinApiClient\Model\DownloadFile; use CrowdinApiClient\Model\TranslationMemory; +use CrowdinApiClient\Model\TranslationMemoryConcordance; use CrowdinApiClient\Model\TranslationMemoryExport; use CrowdinApiClient\Model\TranslationMemoryImport; use CrowdinApiClient\ModelCollection; @@ -299,4 +300,56 @@ public function testClear() $this->mockRequestDelete('/tms/4/segments'); $this->crowdin->translationMemory->clear(4); } + + public function testConcordance(): void + { + $params = [ + 'sourceLanguageId' => 'en', + 'targetLanguageId' => 'de', + 'autoSubstitution' => true, + 'minRelevant' => 60, + 'expressions' => [ + 'Welcome!', + 'Save as...', + 'View', + 'About...', + ], + ]; + + $this->mockRequest([ + 'path' => '/projects/4/tms/concordance', + 'method' => 'post', + 'body' => json_encode($params), + 'response' => '{ + "data": [ + { + "data": { + "tm": { + "id": 4, + "name": "Knowledge Base TM" + }, + "recordId": 34, + "source": "Welcome!", + "target": "Ласкаво просимо!", + "relevant": 100, + "substituted": "62→100", + "updatedAt": "2022-09-28T12:29:34+00:00" + } + } + ], + "pagination": { + "offset": 0, + "limit": 25 + } + }', + ]); + + /** @var TranslationMemoryConcordance[] $concordance */ + $concordance = $this->crowdin->translationMemory->concordance(4, $params); + + $this->assertInstanceOf(ModelCollection::class, $concordance); + $this->assertCount(1, $concordance); + $this->assertInstanceOf(TranslationMemoryConcordance::class, $concordance[0]); + $this->assertEquals(4, $concordance[0]->getTm()->getId()); + } } diff --git a/tests/CrowdinApiClient/Model/AlignmentTest.php b/tests/CrowdinApiClient/Model/AlignmentTest.php new file mode 100644 index 00000000..998e6548 --- /dev/null +++ b/tests/CrowdinApiClient/Model/AlignmentTest.php @@ -0,0 +1,44 @@ + 'Password', + 'sourceLemma' => 'password', + 'targetWord' => 'Пароль', + 'targetLemma' => 'пароль', + 'match' => 2, + 'probability' => 2.0, + ]; + + public function testLoadData(): void + { + $this->alignment = new Alignment($this->data); + $this->checkData(); + } + + public function checkData(): void + { + $this->assertEquals($this->data['sourceWord'], $this->alignment->getSourceWord()); + $this->assertEquals($this->data['sourceLemma'], $this->alignment->getSourceLemma()); + $this->assertEquals($this->data['targetWord'], $this->alignment->getTargetWord()); + $this->assertEquals($this->data['targetLemma'], $this->alignment->getTargetLemma()); + $this->assertEquals($this->data['match'], $this->alignment->getMatch()); + $this->assertEquals($this->data['probability'], $this->alignment->getProbability()); + } +} diff --git a/tests/CrowdinApiClient/Model/TranslationAlignmentTest.php b/tests/CrowdinApiClient/Model/TranslationAlignmentTest.php new file mode 100644 index 00000000..e521fdde --- /dev/null +++ b/tests/CrowdinApiClient/Model/TranslationAlignmentTest.php @@ -0,0 +1,54 @@ + [ + [ + 'text' => 'password', + 'alignments' => [ + [ + 'sourceWord' => 'Password', + 'sourceLemma' => 'password', + 'targetWord' => 'Пароль', + 'targetLemma' => 'пароль', + 'match' => 2, + 'probability' => 2.0, + ], + ], + ], + ], + ]; + + public function testLoadData(): void + { + $this->translationAlignment = new TranslationAlignment($this->data); + $this->checkData(); + } + + public function checkData(): void + { + $this->assertIsArray($this->translationAlignment->getWords()); + $this->assertCount(count($this->data['words']), $this->translationAlignment->getWords()); + $this->assertContainsOnlyInstancesOf( + WordAlignment::class, + $this->translationAlignment->getWords() + ); + } +} diff --git a/tests/CrowdinApiClient/Model/TranslationMemoryConcordanceTest.php b/tests/CrowdinApiClient/Model/TranslationMemoryConcordanceTest.php new file mode 100644 index 00000000..77d6ac8a --- /dev/null +++ b/tests/CrowdinApiClient/Model/TranslationMemoryConcordanceTest.php @@ -0,0 +1,52 @@ + [ + 'id' => 4, + 'name' => 'Knowledge Base TM', + ], + 'recordId' => 34, + 'source' => 'Welcome!', + 'target' => 'Ласкаво просимо!', + 'relevant' => 100, + 'substituted' => '62→100', + 'updatedAt' => '2022-09-28T12:29:34+00:00', + ]; + + public function testLoadData(): void + { + $this->translationMemoryConcordance = new TranslationMemoryConcordance($this->data); + $this->checkData(); + } + + public function checkData(): void + { + $this->assertInstanceOf(TranslationMemory::class, $this->translationMemoryConcordance->getTm()); + $this->assertEquals($this->data['tm']['id'], $this->translationMemoryConcordance->getTm()->getId()); + $this->assertEquals($this->data['tm']['name'], $this->translationMemoryConcordance->getTm()->getName()); + $this->assertEquals($this->data['recordId'], $this->translationMemoryConcordance->getRecordId()); + $this->assertEquals($this->data['source'], $this->translationMemoryConcordance->getSource()); + $this->assertEquals($this->data['target'], $this->translationMemoryConcordance->getTarget()); + $this->assertEquals($this->data['relevant'], $this->translationMemoryConcordance->getRelevant()); + $this->assertEquals($this->data['substituted'], $this->translationMemoryConcordance->getSubstituted()); + $this->assertEquals($this->data['updatedAt'], $this->translationMemoryConcordance->getUpdatedAt()); + } +} diff --git a/tests/CrowdinApiClient/Model/WordAlignmentTest.php b/tests/CrowdinApiClient/Model/WordAlignmentTest.php new file mode 100644 index 00000000..eb98ae41 --- /dev/null +++ b/tests/CrowdinApiClient/Model/WordAlignmentTest.php @@ -0,0 +1,51 @@ + 'password', + 'alignments' => [ + [ + 'sourceWord' => 'Password', + 'sourceLemma' => 'password', + 'targetWord' => 'Пароль', + 'targetLemma' => 'пароль', + 'match' => 2, + 'probability' => 2.0, + ], + ], + ]; + + public function testLoadData(): void + { + $this->wordAlignment = new WordAlignment($this->data); + $this->checkData(); + } + + public function checkData(): void + { + $this->assertEquals($this->data['text'], $this->wordAlignment->getText()); + $this->assertIsArray($this->wordAlignment->getAlignments()); + $this->assertCount(count($this->data['alignments']), $this->wordAlignment->getAlignments()); + $this->assertContainsOnlyInstancesOf( + Alignment::class, + $this->wordAlignment->getAlignments() + ); + } +}