Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d88294e

Browse files
committed
Implemented write method for Crodwin
1 parent db959f6 commit d88294e

15 files changed

+216
-108
lines changed

src/Symfony/Bundle/FrameworkBundle/Command/TranslationPullCommand.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
use Symfony\Component\Translation\TranslationProviders;
2424
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
2525

26+
/**
27+
* @author Fabien Potencier <[email protected]>
28+
*
29+
* @experimental in 5.2
30+
*/
2631
final class TranslationPullCommand extends Command
2732
{
2833
use TranslationTrait;

src/Symfony/Bundle/FrameworkBundle/Command/TranslationPushCommand.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
2222
use Symfony\Component\Translation\TranslationProviders;
2323

24+
/**
25+
* @author Fabien Potencier <[email protected]>
26+
*
27+
* @experimental in 5.2
28+
*/
2429
final class TranslationPushCommand extends Command
2530
{
2631
use TranslationTrait;

src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
use Symfony\Component\Translation\Extractor\ExtractorInterface;
2929
use Symfony\Component\Translation\Extractor\PhpExtractor;
3030
use Symfony\Component\Translation\Formatter\MessageFormatter;
31-
use Symfony\Component\Translation\Loader\ArrayLoader;
3231
use Symfony\Component\Translation\Loader\CsvFileLoader;
3332
use Symfony\Component\Translation\Loader\IcuDatFileLoader;
3433
use Symfony\Component\Translation\Loader\IcuResFileLoader;

src/Symfony/Bundle/FrameworkBundle/Resources/config/translation_providers.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

1414
use Symfony\Component\Translation\Bridge\Crowdin\CrowdinProviderFactory;
15-
use Symfony\Component\Translation\Bridge\Phrase\PhraseProviderFactory;
1615
use Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory;
1716
use Symfony\Component\Translation\Bridge\PoEditor\PoEditorProviderFactory;
1817
use Symfony\Component\Translation\Bridge\Transifex\TransifexProviderFactory;
@@ -38,6 +37,7 @@
3837
->set('translation.provider_factory.crowdin', CrowdinProviderFactory::class)
3938
->args([
4039
service('translation.loader.xliff_raw'),
40+
service('translation.dumper.xliff'),
4141
])
4242
->parent('translation.provider_factory.abstract')
4343
->tag('translation.provider_factory')
@@ -52,7 +52,7 @@
5252
->set('translation.provider_factory.transifex', TransifexProviderFactory::class)
5353
->args([
5454
service('translation.loader.xliff_raw'),
55-
service('slugger')
55+
service('slugger'),
5656
])
5757
->parent('translation.provider_factory.abstract')
5858
->tag('translation.provider_factory')

src/Symfony/Component/HttpClient/Response/MockResponse.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class MockResponse implements ResponseInterface, StreamableInterface
4141
/**
4242
* @param string|string[]|iterable $body The response body as a string or an iterable of strings,
4343
* yielding an empty string simulates an idle timeout,
44-
* exceptions are turned to TransportException
44+
* exceptions are turned to ProviderException
4545
*
4646
* @see ResponseInterface::getInfo() for possible info, e.g. "response_headers"
4747
*/

src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProvider.php

Lines changed: 139 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\HttpFoundation\Response;
16-
use Symfony\Component\Translation\Exception\TransportException;
16+
use Symfony\Component\Translation\Dumper\XliffFileDumper;
17+
use Symfony\Component\Translation\Exception\ProviderException;
1718
use Symfony\Component\Translation\Loader\LoaderInterface;
19+
use Symfony\Component\Translation\MessageCatalogue;
1820
use Symfony\Component\Translation\Provider\AbstractProvider;
1921
use Symfony\Component\Translation\TranslatorBag;
2022
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -23,9 +25,6 @@
2325
* @author Fabien Potencier <[email protected]>
2426
*
2527
* @experimental in 5.2
26-
*
27-
* In Crowdin:
28-
* Source strings refers to Symfony's translation keys
2928
*/
3029
final class CrowdinProvider extends AbstractProvider
3130
{
@@ -36,14 +35,17 @@ final class CrowdinProvider extends AbstractProvider
3635
private $loader;
3736
private $logger;
3837
private $defaultLocale;
38+
private $xliffFileDumper;
39+
private $files = [];
3940

40-
public function __construct(string $projectId, string $token, HttpClientInterface $client = null, LoaderInterface $loader = null, LoggerInterface $logger = null, string $defaultLocale = null)
41+
public function __construct(string $projectId, string $token, HttpClientInterface $client = null, LoaderInterface $loader = null, LoggerInterface $logger = null, string $defaultLocale = null, XliffFileDumper $xliffFileDumper = null)
4142
{
4243
$this->projectId = $projectId;
4344
$this->token = $token;
4445
$this->loader = $loader;
4546
$this->logger = $logger;
4647
$this->defaultLocale = $defaultLocale;
48+
$this->xliffFileDumper = $xliffFileDumper;
4749

4850
parent::__construct($client);
4951
}
@@ -60,15 +62,19 @@ public function getName(): string
6062

6163
public function write(TranslatorBag $translations, bool $override = false): void
6264
{
63-
foreach ($translations->getCatalogues() as $catalogue) {
64-
foreach ($catalogue->all() as $domain => $messages) {
65-
$locale = $catalogue->getLocale();
66-
67-
// check if domain exists, if not, create it
68-
69-
foreach ($messages as $id => $message) {
70-
$this->addString($id);
71-
$this->addTranslation($id, $message, $locale);
65+
foreach($translations->getDomains() as $domain) {
66+
foreach ($translations->getCatalogues() as $catalogue) {
67+
$content = $this->xliffFileDumper->formatCatalogue($catalogue, $domain);
68+
$fileId = $this->getFileId($domain);
69+
70+
if ($catalogue->getLocale() === $this->defaultLocale) {
71+
if (!$fileId) {
72+
$this->addFile($domain, $content);
73+
} else {
74+
$this->updateFile($fileId, $domain, $content);
75+
}
76+
} else {
77+
$this->uploadTranslations($fileId, $domain, $content, $catalogue->getLocale());
7278
}
7379
}
7480
}
@@ -79,21 +85,10 @@ public function write(TranslatorBag $translations, bool $override = false): void
7985
*/
8086
public function read(array $domains, array $locales): TranslatorBag
8187
{
82-
$filter = $domains ? implode(',', $domains) : '*';
8388
$translatorBag = new TranslatorBag();
8489

8590
foreach ($locales as $locale) {
86-
$fileId = $this->getFileId();
87-
88-
$responseContent = $response->getContent(false);
89-
90-
if (Response::HTTP_OK !== $response->getStatusCode()) {
91-
throw new TransportException('Unable to read the Loco response: '.$responseContent, $response);
92-
}
93-
94-
foreach ($domains as $domain) {
95-
$translatorBag->addCatalogue($this->loader->load($responseContent, $locale, $domain));
96-
}
91+
// TODO: Implement read() method.
9792
}
9893

9994
return $translatorBag;
@@ -107,65 +102,148 @@ public function delete(TranslatorBag $translations): void
107102
protected function getDefaultHeaders(): array
108103
{
109104
return [
110-
'Authorization' => 'Bearer ' . $this->token,
105+
'Authorization' => 'Bearer '.$this->token,
111106
];
112107
}
113108

109+
private function getFileId(string $domain): ?int
110+
{
111+
if (isset($this->files[$domain])) {
112+
return $this->files[$domain];
113+
}
114+
115+
try {
116+
$files = $this->getFilesList();
117+
} catch (ProviderException $e) {
118+
return null;
119+
}
120+
121+
foreach($files as $file) {
122+
if ($file['data']['name'] === sprintf('%s.%s', $domain, 'xlf')) {
123+
return $this->files[$domain] = (int) $file['data']['id'];
124+
}
125+
}
126+
127+
return null;
128+
}
129+
114130
/**
115-
* This function allows creation of a new translation key.
116-
*
117-
* @see https://support.crowdin.com/api/v2/#operation/api.projects.strings.post
131+
* @see https://support.crowdin.com/api/v2/#operation/api.projects.files.post
118132
*/
119-
private function addString(string $id): void
133+
private function addFile(string $domain, string $content): void
120134
{
121-
$response = $this->client->request('POST', sprintf('https://%s/projects/%s/strings', $this->getEndpoint(), $this->projectId), [
122-
'headers' => $this->getDefaultHeaders(),
123-
'body' => [
124-
'text' => $id,
125-
'identifier' => $id,
126-
],
135+
$storageId = $this->addStorage($domain, $content);
136+
$response = $this->client->request('POST', sprintf('https://%s/projects/%s/files', $this->getEndpoint(), $this->projectId), [
137+
'headers' => array_merge($this->getDefaultHeaders(), [
138+
'Content-Type' => 'application/json',
139+
]),
140+
'body' => json_encode([
141+
'storageId' => $storageId,
142+
'name' => sprintf('%s.%s', $domain, 'xlf'),
143+
]),
127144
]);
128145

129-
if (Response::HTTP_CONFLICT === $response->getStatusCode()) {
130-
$this->logger->warning(sprintf('Translation key (%s) already exists in Crowdin.', $id), [
131-
'id' => $id,
132-
]);
133-
} elseif (Response::HTTP_CREATED !== $response->getStatusCode()) {
134-
throw new TransportException(sprintf('Unable to add new translation key (%s) to Crowdin: (status code: "%s") "%s".', $id, $response->getStatusCode(), $response->getContent(false)), $response);
146+
if (Response::HTTP_CREATED !== $response->getStatusCode()) {
147+
throw new ProviderException(sprintf('Unable to add a File in Crowdin for domain "%s".', $domain), $response);
135148
}
149+
150+
$this->files[$domain] = (int) json_decode($response->getContent(), true)['data']['id'];
136151
}
137152

138153
/**
139-
* This function allows translation of a message.
140-
*
141-
* @see https://support.crowdin.com/api/v2/#operation/api.projects.translations.post
154+
* @see https://support.crowdin.com/api/v2/#operation/api.projects.files.put
142155
*/
143-
private function addTranslation(string $id, string $message, string $locale): void
156+
private function updateFile(int $fileId, string $domain, string $content): void
144157
{
145-
$response = $this->client->request('POST', sprintf('https://%s/projects/%s/translations', $this->getEndpoint(), $this->projectId), [
146-
'headers' => $this->getDefaultHeaders(),
147-
'body' => [
148-
'stringId' => $id,
149-
'languageId' => $locale,
150-
'text' => $message,
151-
],
158+
$storageId = $this->addStorage($domain, $content);
159+
$response = $this->client->request('PUT', sprintf('https://%s/projects/%s/files/%d', $this->getEndpoint(), $this->projectId, $fileId), [
160+
'headers' => array_merge($this->getDefaultHeaders(), [
161+
'Content-Type' => 'application/json',
162+
]),
163+
'body' => json_encode([
164+
'storageId' => $storageId,
165+
]),
166+
]);
167+
168+
if (Response::HTTP_OK !== $response->getStatusCode()) {
169+
throw new ProviderException(
170+
sprintf('Unable to update file in Crowdin for file ID "%d" and domain "%s".', $fileId, $domain),
171+
$response
172+
);
173+
}
174+
}
175+
176+
/**
177+
* @see https://support.crowdin.com/api/v2/#operation/api.projects.translations.postOnLanguage
178+
*/
179+
private function uploadTranslations(?int $fileId, string $domain, string $content, string $locale): void
180+
{
181+
if (!$fileId) {
182+
return;
183+
}
184+
185+
$storageId = $this->addStorage($domain, $content);
186+
$response = $this->client->request('POST', sprintf('https://%s/projects/%s/translations/%s', $this->getEndpoint(), $this->projectId, $locale), [
187+
'headers' => array_merge($this->getDefaultHeaders(), [
188+
'Content-Type' => 'application/json',
189+
]),
190+
'body' => json_encode([
191+
'storageId' => $storageId,
192+
'fileId' => $fileId,
193+
]),
194+
]);
195+
196+
if (Response::HTTP_OK !== $response->getStatusCode()) {
197+
throw new ProviderException(
198+
sprintf('Unable to upload translations to Crowdin for domain "%s" and locale "%s".', $domain, $locale),
199+
$response
200+
);
201+
}
202+
}
203+
204+
/**
205+
* @see https://support.crowdin.com/api/v2/#operation/api.storages.post
206+
*/
207+
private function addStorage(string $domain, string $content): int
208+
{
209+
$response = $this->client->request('POST', sprintf('https://%s/storages', $this->getEndpoint()), [
210+
'headers' => array_merge($this->getDefaultHeaders(), [
211+
'Crowdin-API-FileName' => urlencode(sprintf('%s.%s', $domain, 'xlf')),
212+
'Content-Type' => 'application/octet-stream',
213+
]),
214+
'body' => $content,
152215
]);
153216

154217
if (Response::HTTP_CREATED !== $response->getStatusCode()) {
155-
throw new TransportException(sprintf('Unable to add new translation message "%s" (for key: "%s") to Crowdin: (status code: "%s") "%s".', $message, $id, $response->getStatusCode(), $response->getContent(false)), $response);
218+
throw new ProviderException(sprintf('Unable to add a Storage in Crowdin for domain "%s".', $domain), $response);
156219
}
220+
221+
$storage = json_decode($response->getContent(), true);
222+
223+
return $storage['data']['id'];
157224
}
158225

159226
/**
160-
* @todo: Not sure at all of this
227+
* @see https://support.crowdin.com/api/v2/#operation/api.projects.files.getMany
161228
*/
162-
private function getFileId(): int
229+
private function getFilesList(): array
163230
{
164-
$response = $this->client->request('GET', sprintf('https://%s/projects/%s/files', $this->getEndpoint(), $this->projectId), [
165-
'headers' => $this->getDefaultHeaders(),
231+
$response = $this->client->request('GET', sprintf('https://%s/projects/%d/files', $this->getEndpoint(), $this->projectId), [
232+
'headers' => array_merge($this->getDefaultHeaders(), [
233+
'Content-Type' => 'application/json',
234+
]),
166235
]);
167-
$files = json_decode($response->getContent());
168236

169-
return $files->data[0]->data->id;
237+
if (Response::HTTP_OK !== $response->getStatusCode()) {
238+
throw new ProviderException('Unable to list Crowdin files.', $response);
239+
}
240+
241+
$files = json_decode($response->getContent(), true)['data'];
242+
243+
if (count($files) === 0) {
244+
throw new ProviderException('Crowdin files list is empty.', $response);
245+
}
246+
247+
return $files;
170248
}
171249
}

src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProviderFactory.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,33 @@
1212
namespace Symfony\Component\Translation\Bridge\Crowdin;
1313

1414
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\Translation\Dumper\XliffFileDumper;
1516
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
1617
use Symfony\Component\Translation\Loader\LoaderInterface;
1718
use Symfony\Component\Translation\Provider\AbstractProviderFactory;
1819
use Symfony\Component\Translation\Provider\Dsn;
1920
use Symfony\Component\Translation\Provider\ProviderInterface;
2021
use Symfony\Contracts\HttpClient\HttpClientInterface;
2122

23+
/**
24+
* @author Fabien Potencier <[email protected]>
25+
*
26+
* @experimental in 5.2
27+
*/
2228
final class CrowdinProviderFactory extends AbstractProviderFactory
2329
{
2430
/** @var LoaderInterface */
2531
private $loader;
2632

27-
public function __construct(HttpClientInterface $client = null, LoggerInterface $logger = null, string $defaultLocale = null, LoaderInterface $loader = null)
33+
/** @var XliffFileDumper */
34+
private $xliffFileDumper;
35+
36+
public function __construct(HttpClientInterface $client = null, LoggerInterface $logger = null, string $defaultLocale = null, LoaderInterface $loader = null, XliffFileDumper $xliffFileDumper = null)
2837
{
2938
parent::__construct($client, $logger, $defaultLocale);
3039

3140
$this->loader = $loader;
41+
$this->xliffFileDumper = $xliffFileDumper;
3242
}
3343

3444
/**
@@ -37,7 +47,7 @@ public function __construct(HttpClientInterface $client = null, LoggerInterface
3747
public function create(Dsn $dsn): ProviderInterface
3848
{
3949
if ('crowdin' === $dsn->getScheme()) {
40-
return (new CrowdinProvider($this->getUser($dsn), $this->getPassword($dsn), $this->client, $this->loader, $this->logger, $this->defaultLocale))
50+
return (new CrowdinProvider($this->getUser($dsn), $this->getPassword($dsn), $this->client, $this->loader, $this->logger, $this->defaultLocale, $this->xliffFileDumper))
4151
->setHost('default' === $dsn->getHost() ? null : $dsn->getHost())
4252
->setPort($dsn->getPort())
4353
;

0 commit comments

Comments
 (0)