eZ Platform Discussions

Create translations command

version22

#1

Bonjour à tous :slight_smile:

I would like to share with you this small, unpretentious script that allows you to create translations of content in a tree structure.

His arguments are:
locationId : The root in which the script will look for Content
from: The language in which the script will search for Content
to: The language in which the script will create the translation.

The script must not touch Content that already exists in the to language.
I would like to mention one more thing. This script create translations. He doesn’t translate.

If you see any improvements to be made, I’ll take it.

<?php
/* For eZ Platform 2.2 */
namespace Smile\ToolsBundle\Command;

use eZ\Publish\API\Repository\Repository;
use eZ\Publish\API\Repository\Values\Content\Content;
use eZ\Publish\API\Repository\Values\Content\Query;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class TranslateSubtreeCommand
 * @package Smile\ToolsBundle\Command
 */

class TranslateSubtreeCommand extends SmileContainerAwareCommand
{

    private $time_debug = false;
    private $memory_debug = false;
    private $start = false;
    private $default_size = 50;
    private $from = 'eng-GB';
    private $to = 'fre-FR';
    private $errors = [];
    private $dry_run = false;

    protected function configure()
    {
        $this
            ->setName('smile:tools:translate-subtree')
            ->setDescription('Traduit les contenus fils de locationId.')
            ->addArgument('locationId', InputArgument::REQUIRED, 'Location id ou remote location id')
            ->addArgument('from', InputArgument::REQUIRED, 'Language-code. Exemple : '.$this->from)
            ->addArgument('to', InputArgument::REQUIRED, 'Language-code. Exemple : '.$this->to)
            ->addOption('time-debug', 't', InputOption::VALUE_NONE, "Add Time debug")
            ->addOption('memory-debug', 'm', InputOption::VALUE_NONE, "Add memory debug TODO") // TODO
            ->addOption('size', 's', InputOption::VALUE_OPTIONAL, "Size. Default:$this->default_size")
            ->addOption('dry-run', 'd', InputOption::VALUE_OPTIONAL, "Dry-run mode")
        ;
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return int|null|void
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
     * @throws \Exception
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->start = time();

        $this->logAsSuperAdmin();

        $locationId = $input->getArgument('locationId');
        $this->from = $input->getArgument('from');
        $this->to   = $input->getArgument('to');

        $this->time_debug   = $input->getOption('time-debug');
        $this->memory_debug = $input->getOption('memory-debug');
        $this->dry_run      = $input->getOption('dry-run');

        $size = (int)$input->getOption('size');
        $size = $size ? $size : $this->default_size;

        if (is_numeric($locationId)) {
            $location = $this->getRepository()->getLocationService()->loadLocation($locationId);
        } else {
            $location = $this->getRepository()->getLocationService()->loadLocationByRemoteId($locationId);
        }

        $query = new Query();
        $query->filter = new Query\Criterion\LogicalAnd([
            new Query\Criterion\Subtree($location->pathString),
            new Query\Criterion\LanguageCode($this->from),
            new Query\Criterion\LogicalNot(new Query\Criterion\LanguageCode($this->to)),
        ]);

        // First request with limit = 0 for count.
        $query->limit = 0;
        $searchResult = $this->getRepository()->getSearchService()->findContent($query);
        $TT = $searchResult->totalCount;
        $output->writeln("$TT location(s) to translate.");

        $query->limit = $size;
        $ii = 0;
        $II = str_pad($ii, strlen("$TT"));

        do {
            $searchResult = $this->getRepository()->getSearchService()->findContent($query);

            foreach ($searchResult->searchHits as $hit) {
                $ii++;
                /** @var Content $content */
                $content = $hit->valueObject;
                $II = str_pad($ii, strlen("$TT"));
                if ($output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) {
                    $output->write('.');
                }
                if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
                    $output->writeln("[$II/$TT] C:$content->id L:{$content->contentInfo->mainLocationId} {$content->getName()}" // TODO
                        . $this->timeDebug($ii, $TT)
                        . $this->memoryDebug()
                    );
                }
                try {
                    $this->translateContent($content, $this->from, $this->to);
                } catch (\Exception $e) {
                    $this->errors[] = $content->id;
                    if ($output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) {
                        $output->writeln('');
                    }
                    $output->writeln("<fg=red>{$e->getMessage()}</>");
                    $query->offset++;
                }
            }
            if ($output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) {
                $output->writeln(" [$II/$TT]"
                    . $this->timeDebug($ii, $TT)
                    . $this->memoryDebug()
                );
            }

        } while ($searchResult->searchHits && $ii < $TT);

        if ($this->errors) {
            $output->writeln("<fg=red>".count($this->errors)."</>");
        }

        $output->writeln('END');
    }

    /**
     * @param Content $content
     * @param $from
     * @param $to
     * @return Content
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
     */
    private function translateContent(Content $content, $from, $to)
    {
        // create a content draft from the current published version
        $contentDraft = $this->getRepository()->getContentService()->createContentDraft( $content->versionInfo->contentInfo );

        // instantiate a content update struct and set the new fields
        $contentStruct = $this->getRepository()->getContentService()->newContentUpdateStruct();
        $contentStruct->initialLanguageCode = $to; // set language for new version

        foreach($content->getFields() as $field) {
            $contentStruct->setField($field->fieldDefIdentifier, $field->value);
        }

        if (!$this->dry_run) {
            $contentDraft = $this->getRepository()->getContentService()->updateContent($contentDraft->versionInfo, $contentStruct);
            $content = $this->getRepository()->getContentService()->publishVersion($contentDraft->versionInfo);
        }

        return $content;
    }

    /**
     * @return Repository|object
     */
    protected function getRepository()
    {
        return $this->getContainer()->get('ezpublish.api.repository');
    }

    private function memoryDebug()
    {
        if ( ! ( $this->memory_debug) ) {
            return '';
        }
        return " (TODO memory-debug)"; //TODO
    }

    private function timeDebug($ii, $TT)
    {
        if ( ! ($ii && $TT && $this->time_debug && $this->start) ) {
            return '';
        }

        $time = max(1, time() - $this->start);      // Temp déjà passé (Minimum:1)
        $restant = $TT - $ii;                                       // Nombre d'éléments restant à traité.
        $moyen = ceil($ii/$time);                            // Temps moyen par élément déjà traité.
        $tr = $moyen * $restant;                                    // Temp restant
        $return  = "{$tr}sec";
        if ($tr > 60) {
            $min = floor($tr/60);
            $sec = $tr - ($min * 60);
            $return  = "{$min}min {$sec}sec";
            if ($min > 60) {
                $h = floor($min/60);
                $min = $min - ($h * 60);
                $return  = " ({$h}h {$min}min {$sec}sec";
            }
        }
        $return  = " (Temp restant: $return)";
        return $return;
    }
}



#2

Thanks again for sharing @remy_php! Much appreciated.


#3

hey thanks @remy_php it reminds me this: https://github.com/ezsystems/ezplatform-automated-translation/blob/master/bundle/Command/TranslateContentCommand.php

Which is a command that would translate a content to another language using Google Translate :wink:

Might be good to mix them both :wink:


#4

https://jira.ez.no/browse/EZP-26616