Skip to content

Commit e984324

Browse files
committed
[Translation][Lokalise] fix "Project too big for sync export"
1 parent 4433ffc commit e984324

File tree

1 file changed

+112
-1
lines changed

1 file changed

+112
-1
lines changed

src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Translation\Bridge\Lokalise;
1313

1414
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\Translation\Exception\LogicException;
1516
use Symfony\Component\Translation\Exception\ProviderException;
1617
use Symfony\Component\Translation\Loader\LoaderInterface;
1718
use Symfony\Component\Translation\MessageCatalogueInterface;
@@ -31,6 +32,9 @@
3132
final class LokaliseProvider implements ProviderInterface
3233
{
3334
private const LOKALISE_GET_KEYS_LIMIT = 5000;
35+
private const ERROR_STATUS = [
36+
'PROJECT_TOO_BIG' => 413,
37+
];
3438

3539
private HttpClientInterface $client;
3640
private LoaderInterface $loader;
@@ -96,7 +100,7 @@ public function write(TranslatorBagInterface $translatorBag): void
96100
public function read(array $domains, array $locales): TranslatorBag
97101
{
98102
$translatorBag = new TranslatorBag();
99-
$translations = $this->exportFiles($locales, $domains);
103+
$translations = $this->exportFilesAsync($locales, $domains);
100104

101105
foreach ($translations as $locale => $files) {
102106
foreach ($files as $filename => $content) {
@@ -165,6 +169,9 @@ private function exportFiles(array $locales, array $domains): array
165169
}
166170

167171
if (200 !== $response->getStatusCode()) {
172+
if (($errorCode = $responseContent['error']['code'] ?? null) && self::ERROR_STATUS['PROJECT_TOO_BIG'] === $errorCode && \extension_loaded('zip')) {
173+
return $this->exportFilesAsync($locales, $domains);
174+
}
168175
throw new ProviderException(\sprintf('Unable to export translations from Lokalise: "%s".', $response->getContent(false)), $response);
169176
}
170177

@@ -176,6 +183,110 @@ private function exportFiles(array $locales, array $domains): array
176183
return array_combine($reformattedLanguages, $responseContent['files']);
177184
}
178185

186+
/**
187+
* @see https://developers.lokalise.com/reference/download-files-async
188+
*/
189+
private function exportFilesAsync(array $locales, array $domains): array
190+
{
191+
$response = $this->client->request('POST', 'files/async-download', [
192+
'json' => [
193+
'format' => 'symfony_xliff',
194+
'original_filenames' => true,
195+
'filter_langs' => array_values($locales),
196+
'filter_filenames' => array_map($this->getLokaliseFilenameFromDomain(...), $domains),
197+
'export_empty_as' => 'skip',
198+
'replace_breaks' => false,
199+
],
200+
]);
201+
202+
if (200 !== $response->getStatusCode()) {
203+
throw new ProviderException(\sprintf('Unable to export translations from Lokalise: "%s".', $response->getContent(false)), $response);
204+
}
205+
206+
$processId = $response->toArray()['process_id'];
207+
$attempt = 0;
208+
while (true) {
209+
$response = $this->client->request('GET', \sprintf('processes/%s', $processId));
210+
$process = $response->toArray()['process'];
211+
if ('failed' === $process['status']) {
212+
throw new ProviderException(\sprintf('Unable to export translations from Lokalise: "%s".', $response->getContent(false)), $response);
213+
}
214+
if ('finished' === $process['status']) {
215+
$downloadUrl = $process['details']['download_url'];
216+
break;
217+
}
218+
++$attempt;
219+
usleep(500000 * $attempt);
220+
}
221+
222+
try {
223+
$newfile = \sprintf('%s%s%s.zip', sys_get_temp_dir(), \DIRECTORY_SEPARATOR, uniqid());
224+
$extractPath = \sprintf('%s%s%s', sys_get_temp_dir(), \DIRECTORY_SEPARATOR, uniqid());
225+
226+
if (!copy($downloadUrl, $newfile)) {
227+
throw new LogicException(\sprintf('failed to copy "%s".', $downloadUrl));
228+
}
229+
230+
$zip = new \ZipArchive();
231+
if (!$zip->open($newfile, \ZipArchive::CREATE)) {
232+
throw new LogicException(\sprintf('failed to open zip file "%s".', $newfile));
233+
}
234+
235+
try {
236+
if (!$zip->extractTo($extractPath)) {
237+
throw new LogicException(\sprintf('failed to extract content from zip file "%s".', $newfile));
238+
}
239+
} finally {
240+
$zip->close();
241+
}
242+
243+
$fileContents = $this->getFileContents($extractPath);
244+
245+
// Lokalise returns languages with "-" separator, we need to reformat them to "_" separator.
246+
/** @var array<array-key, array-key> $reformattedLanguages */
247+
$reformattedLanguages = array_map(function ($language) {
248+
return str_replace('-', '_', $language);
249+
}, array_keys($fileContents));
250+
251+
return array_combine($reformattedLanguages, $fileContents);
252+
} finally {
253+
unlink($newfile);
254+
$this->removeDir($extractPath);
255+
}
256+
}
257+
258+
private function getFileContents(string $dirName, string $baseDirName = ''): array
259+
{
260+
$fileContents = [];
261+
foreach (scandir($dirName) as $filename) {
262+
if (\in_array($filename, ['.', '..'])) {
263+
continue;
264+
}
265+
$path = \sprintf('%s%s%s', $dirName, \DIRECTORY_SEPARATOR, $filename);
266+
if (is_dir($path)) {
267+
$fileContents = array_merge($fileContents, $this->getFileContents($path, $filename));
268+
continue;
269+
}
270+
$fileContents[$baseDirName][$filename]['content'] = file_get_contents($path);
271+
}
272+
273+
return $fileContents;
274+
}
275+
276+
private function removeDir(string $dir): void
277+
{
278+
$it = new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS);
279+
$files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST);
280+
foreach ($files as $file) {
281+
if ($file->isDir()) {
282+
rmdir($file->getPathname());
283+
} else {
284+
unlink($file->getPathname());
285+
}
286+
}
287+
rmdir($dir);
288+
}
289+
179290
private function createKeys(array $keys, string $domain): array
180291
{
181292
$keysToCreate = [];

0 commit comments

Comments
 (0)