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()
+ );
+ }
+}