diff --git a/src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProvider.php b/src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProvider.php index 23113bd237b74..75f01b4c29df1 100644 --- a/src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProvider.php +++ b/src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProvider.php @@ -56,10 +56,13 @@ public function __toString(): string public function write(TranslatorBagInterface $translatorBag): void { $fileList = $this->getFileList(); + $languageMapping = $this->getLanguageMapping(); $responses = []; foreach ($translatorBag->getCatalogues() as $catalogue) { + $locale = $catalogue->getLocale(); + foreach ($catalogue->getDomains() as $domain) { if (0 === \count($catalogue->all($domain))) { continue; @@ -86,7 +89,7 @@ public function write(TranslatorBagInterface $translatorBag): void continue; } - $responses[] = $this->uploadTranslations($fileId, $domain, $content, $catalogue->getLocale()); + $responses[] = $this->uploadTranslations($fileId, $domain, $content, $languageMapping[$locale] ?? $locale); } } } @@ -105,12 +108,11 @@ public function write(TranslatorBagInterface $translatorBag): void public function read(array $domains, array $locales): TranslatorBag { $fileList = $this->getFileList(); + $languageMapping = $this->getLanguageMapping(); $translatorBag = new TranslatorBag(); $responses = []; - $localeLanguageMap = $this->mapLocalesToLanguageId($locales); - foreach ($domains as $domain) { $fileId = $this->getFileIdByDomain($fileList, $domain); @@ -120,7 +122,7 @@ public function read(array $domains, array $locales): TranslatorBag foreach ($locales as $locale) { if ($locale !== $this->defaultLocale) { - $response = $this->exportProjectTranslations($localeLanguageMap[$locale], $fileId); + $response = $this->exportProjectTranslations($languageMapping[$locale] ?? $locale, $fileId); } else { $response = $this->downloadSourceFile($fileId); } @@ -406,37 +408,24 @@ private function getFileList(): array return $result; } - private function mapLocalesToLanguageId(array $locales): array + private function getLanguageMapping(): array { /** - * We cannot query by locales, we need to fetch all and filter out the relevant ones. - * - * @see https://developer.crowdin.com/api/v2/#operation/api.languages.getMany (Crowdin API) - * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.languages.getMany (Crowdin Enterprise API) + * @see https://developer.crowdin.com/api/v2/#operation/api.projects.get (Crowdin API) + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.projects.get (Crowdin Enterprise API) */ - $response = $this->client->request('GET', '../../languages?limit=500'); + $response = $this->client->request('GET', ''); if (200 !== $response->getStatusCode()) { - throw new ProviderException('Unable to list set languages.', $response); + throw new ProviderException('Unable to get project info.', $response); } - $localeLanguageMap = []; - foreach ($response->toArray()['data'] as $language) { - foreach (['locale', 'osxLocale', 'id'] as $key) { - if (\in_array($language['data'][$key], $locales, true)) { - $localeLanguageMap[$language['data'][$key]] = $language['data']['id']; - } - } - } - - if (\count($localeLanguageMap) !== \count($locales)) { - $message = implode('", "', array_diff($locales, array_keys($localeLanguageMap))); - $message = sprintf('Unable to find all requested locales: "%s" not found.', $message); - $this->logger->error($message); - - throw new ProviderException($message, $response); + $projectInfo = $response->toArray()['data']; + $mapping = []; + foreach ($projectInfo['languageMapping'] ?? [] as $key => $value) { + $mapping[$value['locale']] = $key; } - return $localeLanguageMap; + return $mapping; } } diff --git a/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php b/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php index b9e28fd163508..828277b614e05 100644 --- a/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php +++ b/src/Symfony/Component/Translation/Bridge/Crowdin/Tests/CrowdinProviderTest.php @@ -110,6 +110,12 @@ public function testCompleteWriteProcessAddFiles() return new MockResponse(json_encode(['data' => []])); }, + 'getProject' => function (string $method, string $url): ResponseInterface { + $this->assertSame('GET', $method); + $this->assertSame('https://api.crowdin.com/api/v2/projects/1/', $url); + + return new MockResponse(json_encode(['data' => ['languageMapping' => []]])); + }, 'addStorage' => function (string $method, string $url, array $options = []) use ($expectedMessagesFileContent): ResponseInterface { $this->assertSame('POST', $method); $this->assertSame('https://api.crowdin.com/api/v2/storages', $url); @@ -188,6 +194,12 @@ public function testWriteAddFileServerError() return new MockResponse(json_encode(['data' => []])); }, + 'getProject' => function (string $method, string $url): ResponseInterface { + $this->assertSame('GET', $method); + $this->assertSame('https://api.crowdin.com/api/v2/projects/1/', $url); + + return new MockResponse(json_encode(['data' => ['languageMapping' => []]])); + }, 'addStorage' => function (string $method, string $url, array $options = []) use ($expectedMessagesFileContent): ResponseInterface { $this->assertSame('POST', $method); $this->assertSame('https://api.crowdin.com/api/v2/storages', $url); @@ -260,6 +272,12 @@ public function testWriteUpdateFileServerError() ], ])); }, + 'getProject' => function (string $method, string $url): ResponseInterface { + $this->assertSame('GET', $method); + $this->assertSame('https://api.crowdin.com/api/v2/projects/1/', $url); + + return new MockResponse(json_encode(['data' => ['languageMapping' => []]])); + }, 'addStorage' => function (string $method, string $url, array $options = []) use ($expectedMessagesFileContent): ResponseInterface { $this->assertSame('POST', $method); $this->assertSame('https://api.crowdin.com/api/v2/storages', $url); @@ -349,6 +367,12 @@ public function testWriteUploadTranslationsServerError() ], ])); }, + 'getProject' => function (string $method, string $url): ResponseInterface { + $this->assertSame('GET', $method); + $this->assertSame('https://api.crowdin.com/api/v2/projects/1/', $url); + + return new MockResponse(json_encode(['data' => ['languageMapping' => []]])); + }, 'addStorage' => function (string $method, string $url, array $options = []) use ($expectedMessagesFileContent): ResponseInterface { $this->assertSame('POST', $method); $this->assertSame('https://api.crowdin.com/api/v2/storages', $url); @@ -442,6 +466,12 @@ public function testCompleteWriteProcessUpdateFiles() ], ])); }, + 'getProject' => function (string $method, string $url): ResponseInterface { + $this->assertSame('GET', $method); + $this->assertSame('https://api.crowdin.com/api/v2/projects/1/', $url); + + return new MockResponse(json_encode(['data' => ['languageMapping' => []]])); + }, 'addStorage' => function (string $method, string $url, array $options = []) use ($expectedMessagesFileContent): ResponseInterface { $this->assertSame('POST', $method); $this->assertSame('https://api.crowdin.com/api/v2/storages', $url); @@ -512,6 +542,20 @@ public function testCompleteWriteProcessAddFileAndUploadTranslations(TranslatorB ], ])); }, + 'getProject' => function (string $method, string $url): ResponseInterface { + $this->assertSame('GET', $method); + $this->assertSame('https://api.crowdin.com/api/v2/projects/1/', $url); + + return new MockResponse(json_encode([ + 'data' => [ + 'languageMapping' => [ + 'pt-PT' => [ + 'locale' => 'pt', + ], + ], + ], + ])); + }, 'addStorage' => function (string $method, string $url, array $options = []) use ($expectedMessagesFileContent): ResponseInterface { $this->assertSame('POST', $method); $this->assertSame('https://api.crowdin.com/api/v2/storages', $url); @@ -542,6 +586,22 @@ public function testCompleteWriteProcessAddFileAndUploadTranslations(TranslatorB $this->assertSame(sprintf('https://api.crowdin.com/api/v2/projects/1/translations/%s', $expectedLocale), $url); $this->assertSame('{"storageId":19,"fileId":12}', $options['body']); + return new MockResponse(); + }, + 'addStorage3' => function (string $method, string $url, array $options = []) use ($expectedMessagesTranslationsContent): ResponseInterface { + $this->assertSame('POST', $method); + $this->assertSame('https://api.crowdin.com/api/v2/storages', $url); + $this->assertSame('Content-Type: application/octet-stream', $options['normalized_headers']['content-type'][0]); + $this->assertSame('Crowdin-API-FileName: messages.xlf', $options['normalized_headers']['crowdin-api-filename'][0]); + $this->assertStringMatchesFormat($expectedMessagesTranslationsContent, $options['body']); + + return new MockResponse(json_encode(['data' => ['id' => 19]], ['http_code' => 201])); + }, + 'uploadTranslations2' => function (string $method, string $url, array $options = []) use ($expectedLocale): ResponseInterface { + $this->assertSame('POST', $method); + $this->assertSame(sprintf('https://api.crowdin.com/api/v2/projects/1/translations/%s', $expectedLocale), $url); + $this->assertSame('{"storageId":19,"fileId":12}', $options['body']); + return new MockResponse(); }, ]; @@ -582,6 +642,33 @@ public static function getResponsesForProcessAddFileAndUploadTranslations(): \Ge +XLIFF + ]; + + $translatorBagPt = new TranslatorBag(); + $translatorBagPt->addCatalogue($arrayLoader->load([ + 'a' => 'trans_en_a', + ], 'en')); + $translatorBagPt->addCatalogue($arrayLoader->load([ + 'a' => 'trans_pt_a', + ], 'pt')); + + yield [$translatorBagPt, 'pt-PT', <<<'XLIFF' + + + +
+ +
+ + + a + trans_pt_a + + +
+
+ XLIFF ]; @@ -632,25 +719,15 @@ public function testReadForOneLocaleAndOneDomain(string $locale, string $domain, ], ])); }, - 'listLanguages' => function (string $method, string $url, array $options = []): ResponseInterface { + 'getProject' => function (string $method, string $url): ResponseInterface { $this->assertSame('GET', $method); - $this->assertSame('https://api.crowdin.com/api/v2/languages?limit=500', $url); - $this->assertSame('Authorization: Bearer API_TOKEN', $options['normalized_headers']['authorization'][0]); + $this->assertSame('https://api.crowdin.com/api/v2/projects/1/', $url); return new MockResponse(json_encode([ 'data' => [ - [ - 'data' => [ - 'id' => 'en-GB', - 'osxLocale' => 'en_GB', - 'locale' => 'en-GB', - ], - ], - [ - 'data' => [ - 'id' => 'fr', - 'osxLocale' => 'fr_FR', - 'locale' => 'fr-FR', + 'languageMapping' => [ + 'pt-PT' => [ + 'locale' => 'pt', ], ], ], @@ -770,25 +847,15 @@ public function testReadForDefaultLocaleAndOneDomain(string $locale, string $dom ], ])); }, - 'listLanguages' => function (string $method, string $url, array $options = []): ResponseInterface { + 'getProject' => function (string $method, string $url): ResponseInterface { $this->assertSame('GET', $method); - $this->assertSame('https://api.crowdin.com/api/v2/languages?limit=500', $url); - $this->assertSame('Authorization: Bearer API_TOKEN', $options['normalized_headers']['authorization'][0]); + $this->assertSame('https://api.crowdin.com/api/v2/projects/1/', $url); return new MockResponse(json_encode([ 'data' => [ - [ - 'data' => [ - 'id' => 'en', - 'osxLocale' => 'en_GB', - 'locale' => 'en-GB', - ], - ], - [ - 'data' => [ - 'id' => 'fr', - 'osxLocale' => 'fr_FR', - 'locale' => 'fr-FR', + 'languageMapping' => [ + 'pt-PT' => [ + 'locale' => 'pt', ], ], ], @@ -874,25 +941,15 @@ public function testReadServerException() ], ])); }, - 'listLanguages' => function (string $method, string $url, array $options = []): ResponseInterface { + 'getProject' => function (string $method, string $url): ResponseInterface { $this->assertSame('GET', $method); - $this->assertSame('https://api.crowdin.com/api/v2/languages?limit=500', $url); - $this->assertSame('Authorization: Bearer API_TOKEN', $options['normalized_headers']['authorization'][0]); + $this->assertSame('https://api.crowdin.com/api/v2/projects/1/', $url); return new MockResponse(json_encode([ 'data' => [ - [ - 'data' => [ - 'id' => 'en', - 'osxLocale' => 'en_GB', - 'locale' => 'en-GB', - ], - ], - [ - 'data' => [ - 'id' => 'fr', - 'osxLocale' => 'fr_FR', - 'locale' => 'fr-FR', + 'languageMapping' => [ + 'pt-PT' => [ + 'locale' => 'pt', ], ], ], @@ -933,25 +990,15 @@ public function testReadDownloadServerException() ], ])); }, - 'listLanguages' => function (string $method, string $url, array $options = []): ResponseInterface { + 'getProject' => function (string $method, string $url): ResponseInterface { $this->assertSame('GET', $method); - $this->assertSame('https://api.crowdin.com/api/v2/languages?limit=500', $url); - $this->assertSame('Authorization: Bearer API_TOKEN', $options['normalized_headers']['authorization'][0]); + $this->assertSame('https://api.crowdin.com/api/v2/projects/1/', $url); return new MockResponse(json_encode([ 'data' => [ - [ - 'data' => [ - 'id' => 'en', - 'osxLocale' => 'en_GB', - 'locale' => 'en-GB', - ], - ], - [ - 'data' => [ - 'id' => 'fr', - 'osxLocale' => 'fr_FR', - 'locale' => 'fr-FR', + 'languageMapping' => [ + 'pt-PT' => [ + 'locale' => 'pt', ], ], ],