translation/laravel

View on GitHub
src/Service/Sync.php

Summary

Maintainability
C
1 day
Test Coverage
F
46%
<?php

namespace Tio\Laravel\Service;

use Tio\Laravel\POExtractor;
use Tio\Laravel\GettextPOGenerator;
use Tio\Laravel\SourcePOGenerator;
use Tio\Laravel\TranslationSaver;
use Tio\Laravel\GettextTranslationSaver;
use GuzzleHttp\Client;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Log;

class Sync
{
    private $config;

    /**
     * @var SourcePOGenerator
     */
    private $poGenerator;

    /**
     * @var GettextPOGenerator
     */
    private $gettextPoGenerator;

    /**
     * @var POExtractor
     */
    private $poExtractor;

    /**
     * @var TranslationSaver
     */
    private $translationSaver;

    /**
     * @var GettextTranslationSaver
     */
    private $gettextTranslationSaver;

    public function __construct(
        Application $application,
        SourcePOGenerator $poGenerator,
        GettextPOGenerator $gettextPoGenerator,
        POExtractor $poExtractor,
        TranslationSaver $translationSaver,
        GettextTranslationSaver $gettextTranslationSaver
    ) {
        $this->config = $application['config']['translation'];
        $this->poGenerator = $poGenerator;
        $this->gettextPoGenerator = $gettextPoGenerator;
        $this->poExtractor = $poExtractor;
        $this->translationSaver = $translationSaver;
        $this->gettextTranslationSaver = $gettextTranslationSaver;
    }

    public function call($command, $options = [ 'purge' => false, 'show_purgeable' => false ])
    {
        $client = new Client(['base_uri' => $this->url()]);
        $body = $this->createBody($options['purge']);

        $responseData = $this->makeRequest($client, $body, $command);

        // Save new key/values sent from backend
        foreach ($this->targetLocales() as $locale) {
            $this->translationSaver->call(
                $locale,
                $this->poExtractor->call($responseData['yaml_po_data_' . $locale])
            );
        }

        // Save new po files created and sent from backend
        foreach ($this->targetLocales() as $locale) {
            $this->gettextTranslationSaver->call(
                $locale,
                $responseData['po_data_' . $locale]
            );
        }

        if ($options['show_purgeable']) {
            $this->displayUnusedSegments($responseData, $command, $options['show_purgeable'], $options['purge']);
        }

        $this->displayInfoProjectUrl($responseData, $command);
    }

    private function createBody($purge = false)
    {
        $locale = $this->sourceLocale();

        $formData = [
            'client' => 'laravel',
            'version' => '1.23',
            'source_language' => $locale
        ];

        if ($purge) {
            $formData['purge'] = 'true';
        }

        // keys from PHP translation files
        $formData['yaml_pot_data'] = $this->poGenerator->call($locale);

        // sources from Gettext
        $gettextPoData = $this->gettextPoGenerator->call([]);
        $formData['pot_data'] = $gettextPoData['pot_data'];

        $body = http_build_query($formData);

        foreach ($this->targetLocales() as $locale) {
            $body = $body . "&target_languages[]=$locale";
        }

        return $body;
    }

    private function makeRequest($client, $body, $command)
    {
        try {
            $response = $client->request(
                'POST', '', [
                'headers' => [
                    'Content-Type' => 'application/x-www-form-urlencoded'
                ],
                'body' => $body
                ]
            );

            return json_decode($response->getBody()->getContents(), true);
        } catch (RequestException $e) {
            if ($e->hasResponse()) {
                $responseData = json_decode($e->getResponse()->getBody()->getContents(), true);
                $this->displayErrorAndExit($responseData['error'], $command);
            }
            else {
                $this->displayErrorAndExit($e->getMessage(), $command);
            }
        }
    }

    private function displayUnusedSegments($responseData, $command, $showPurgeable, $purge)
    {
        $unusedSegments = collect($responseData['unused_segments']);

        $yamlUnusedSegments = $unusedSegments->filter(
            function ($unusedSegment) {
                return $unusedSegment['kind'] == 'yaml';
            }
        );

        $gettextUnusedSegments = $unusedSegments->filter(
            function ($unusedSegment) {
                return $unusedSegment['kind'] == 'gettext';
            }
        );

        $yamlSize    = $yamlUnusedSegments->count();
        $gettextSize = $gettextUnusedSegments->count();
        $totalSize   = $yamlSize + $gettextSize;

        // Quick unused segments summary for simple "sync"
        if (!$showPurgeable && !$purge) {
            if ($totalSize > 0) {
                $sum = $yamlSize + $gettextSize;

                $command->line("");
                $command->line("----------");
                $command->line("{$sum} keys/strings are in Translation.io but not in your current branch.");
                $command->line('Execute "php artisan translation:sync_and_show_purgeable" to list these keys/strings.');
            }
        }
        // Complete summary for sync_and_show_purgeable or sync_and_purge
        else {
            $text = "";

            if ($purge) {
                $text = "were removed from Translation.io to match your current branch:";
            }
            else if ($showPurgeable) {
                $text = "are in Translation.io but not in your current branch:";
            }

            if ($yamlSize > 0) {
                $keysText = $yamlSize == 1 ? 'key' : 'keys';

                $command->line("");
                $command->line("----------");
                $command->line("{$yamlSize} YAML {$keysText} {$text}");
                $command->line("");

                $yamlUnusedSegments->each(
                    function ($yamlUnusedSegment) use ($command) {
                        $command->line("[{$yamlUnusedSegment['languages']}] [{$yamlUnusedSegment['msgctxt']}] \"{$yamlUnusedSegment['msgid']}\"");
                    }
                );
            }

            if ($gettextSize > 0) {
                $stringsText = $gettextSize == 1 ? 'string' : 'strings';

                $command->line("");
                $command->line("----------");
                $command->line("{$gettextSize} GetText {$stringsText} {$text}");
                $command->line("");

                $gettextUnusedSegments->each(
                    function ($gettextUnusedSegment) use ($command) {
                        $command->line("[{$gettextUnusedSegment['languages']}] \"{$gettextUnusedSegment['msgid']}\"");
                    }
                );
            }

            // Special message for when nothing need to be purged
            if ($totalSize == 0) {
                $command->line("");
                $command->line("----------");
                $command->line("Nothing to purge: all the keys/strings in Translation.io are also in your current branch.");
            }

            // Special message when sync_and_show_purgeable and unused segments
            if ($showPurgeable && $totalSize > 0) {
                $command->line("");
                $command->line("----------");
                $command->line("If you know what you are doing, you can remove them using \"php artisan translation:sync_and_purge\".");
            }
        }
    }

    private function displayInfoProjectUrl($responseData, $command)
    {
        $command->info("Sync ended with success");
        $command->line("----------");
        $command->info("Use this URL to translate: {$responseData['project_url']}");
        $command->line("----------");
    }


    private function displayErrorAndExit($error, $command)
    {
        $command->line("----------");
        $command->error("Error: {$error}");
        $command->line("----------");

        throw new \Exception($error);
    }

    private function sourceLocale()
    {
        return $this->config['source_locale'];
    }

    private function targetLocales()
    {
        return $this->config['target_locales'];
    }

    private function url()
    {
        return 'https://translation.io/api/projects/' . $this->config['key'] . '/sync';
    }
}