Lukasss93/laravel-larex-crowdin

View on GitHub
src/Importers/CrowdinImporter.php

Summary

Maintainability
A
3 hrs
Test Coverage
A
100%
<?php

namespace Lukasss93\LarexCrowdin\Importers;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Lukasss93\Larex\Console\LarexImportCommand;
use Lukasss93\Larex\Contracts\Importer;
use Lukasss93\LarexCrowdin\Support\Crowdin\Crowdin;

class CrowdinImporter implements Importer
{
    /**
     * @inheritDoc
     */
    public static function description(): string
    {
        return 'Import data from a Crowdin project to CSV';
    }

    /**
     * @inheritDoc
     */
    public function handle(LarexImportCommand $command): Collection
    {
        $include = Str::of($command->option('include'))->explode(',')->reject(fn ($i) => empty($i));
        $exclude = Str::of($command->option('exclude'))->explode(',')->reject(fn ($i) => empty($i));

        $command->info('');

        /** @var Crowdin $crowdin */
        $crowdin = app(Crowdin::class);

        //get project
        $command->warn('Getting project informations...');
        $project = $crowdin->project->get(config('larex-crowdin.project_id'));
        $projectID = $project->getId();
        $command->info("Project: [$projectID] {$project->getName()}");

        //get languages
        $sourceLanguage = $project->getSourceLanguageId();
        $targetLanguages = collect($project->getTargetLanguageIds())->sort()->values();
        $command->info("Source language: $sourceLanguage");
        $command->info('');

        //list source files
        $command->warn('Getting project source files list...');
        $files = $crowdin->fileList($projectID);
        $filesCount = count($files);
        $command->info("Project source files found: $filesCount");
        $command->info('');

        if ($filesCount === 0) {
            return collect();
        }

        //filter $targetLanguages by include/exclude option
        $targetLanguages = $targetLanguages
            ->when($include->isNotEmpty(), fn (Collection $c) => $c->intersect($include))
            ->when($exclude->isNotEmpty(), fn (Collection $c) => $c->diff($exclude));

        //download translation files per language
        $command->warn('Downloading project translation files...');
        $translations = [];
        $bar = $command->getOutput()->createProgressBar($targetLanguages->count());
        foreach ($targetLanguages as $languageID) {
            $export = $crowdin->translation->exportProjectTranslation($projectID, [
                'targetLanguageId' => $languageID,
                'format' => 'xliff',
                'skipUntranslatedStrings' => true,
            ]);
            $xliff = Http::get($export->getUrl())->body();
            $translations[$languageID] = $this->parseXliff($xliff);
            $bar->advance();
        }
        $bar->finish();
        $command->info('');
        $command->info('');

        //download source files
        $command->warn('Downloading project source files...');

        /** @var Collection<int,array<string,string>> $rows */
        $rows = collect();

        $bar = $command->getOutput()->createProgressBar(count($files));
        foreach ($files as $file) {
            $download = $crowdin->file->download($projectID, $file->getId());

            $group = pathinfo($file->getName(), PATHINFO_FILENAME);
            $content = collect(Arr::dot(Http::get($download->getUrl())->json()));

            $rows = $rows->merge($content->map(function ($item, $key) use (
                $group,
                $sourceLanguage,
                $targetLanguages,
                $translations
            ) {
                $out = [
                    'group' => $group,
                    'key' => $key,
                    $sourceLanguage => $item,
                ];

                foreach ($targetLanguages as $languageID) {
                    $out[$languageID] = $translations[$languageID][$group][$key] ?? null;
                }

                return $out;
            })->values());

            $bar->advance();
        }
        $bar->finish();
        $command->info('');
        $command->info('');

        return $rows;
    }

    protected function parseXliff(string $content): array
    {
        $output = [];

        $xml = simplexml_load_string($content);

        foreach ($xml->file as $group) {
            $group = (array)$group;
            $groupName = (string)Str::of($group['@attributes']['original'])
                ->substr(1)
                ->replace('.json', '');

            $body = (array)$group['body'];

            if (!array_key_exists('trans-unit', $body)) {
                continue;
            }

            if (!is_array($body['trans-unit'])) {
                $body['trans-unit'] = [$body['trans-unit']];
            }

            $output[$groupName] = collect($body['trans-unit'] ?? [])
                ->mapWithKeys(function ($item) {
                    $item = (array)$item;
                    $key = Str::start($item['@attributes']['resname'], '.');
                    $value = $item['target'];

                    return [substr($key, 1) => $value];
                })->toArray();
        }

        return $output;
    }
}