Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d88294e

Browse filesBrowse files
committed
Implemented write method for Crodwin
1 parent db959f6 commit d88294e
Copy full SHA for d88294e

15 files changed

+216
-108
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Command/TranslationPullCommand.php
+5Lines 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 <fabien@symfony.com>
28+
*
29+
* @experimental in 5.2
30+
*/
2631
final class TranslationPullCommand extends Command
2732
{
2833
use TranslationTrait;

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Command/TranslationPushCommand.php
+5Lines 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 <fabien@symfony.com>
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php
-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/translation_providers.php
+2-2Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Response/MockResponse.php
+1-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProvider.php
+139-61Lines 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 <fabien@symfony.com>
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Bridge/Crowdin/CrowdinProviderFactory.php
+12-2Lines 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 <fabien@symfony.com>
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.