<?php
declare(strict_types=1);
namespace Alko\AutoTranslateBundle\EventSubscriber;
use Alko\AutoTranslateBundle\Message\TranslateObjectMessage;
use Alko\AutoTranslateBundle\Service\TranslationService;
use Pimcore\Model\DataObject\AbstractObject;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Workflow\Event\TransitionEvent;
/**
* Listens to workflow transitions and triggers AI translation
*
* Hooks into the existing translation_workflow to automatically
* translate content when PM starts the translation process.
*/
class WorkflowTranslationSubscriber implements EventSubscriberInterface
{
private TranslationService $translationService;
private MessageBusInterface $messageBus;
private LoggerInterface $logger;
private bool $asyncMode;
private bool $enabled;
public function __construct(
TranslationService $translationService,
MessageBusInterface $messageBus,
LoggerInterface $logger,
bool $asyncMode = true,
bool $enabled = true
) {
$this->translationService = $translationService;
$this->messageBus = $messageBus;
$this->logger = $logger;
$this->asyncMode = $asyncMode;
$this->enabled = $enabled;
}
/** Subscribe to the "to_translate" workflow transition event */
public static function getSubscribedEvents(): array
{
return [
'workflow.translation_workflow.transition.to_translate' => ['onStartTranslation', 0],
];
}
/**
* Handle the to_translate transition
*
* This is triggered when a PM clicks "To translate the element" in the workflow.
* We intercept this to auto-translate content BEFORE notifications are sent.
*/
public function onStartTranslation(TransitionEvent $event): void
{
if (!$this->enabled) {
$this->logger->debug('AI Translation is disabled, skipping auto-translation');
return;
}
$subject = $event->getSubject();
if (!$subject instanceof AbstractObject) {
$this->logger->debug('Workflow subject is not a DataObject, skipping');
return;
}
$className = $subject->getClassName();
// Check if this entity type is supported
if (!$this->translationService->isEntitySupported($className)) {
$this->logger->debug('Entity type not configured for auto-translation', [
'class' => $className,
'object_id' => $subject->getId()
]);
return;
}
$this->logger->info('Translation workflow triggered - starting AI auto-translation', [
'object_id' => $subject->getId(),
'class' => $className,
'async_mode' => $this->asyncMode,
'transition' => $event->getTransition()->getName()
]);
try {
if ($this->asyncMode) {
// Queue for asynchronous processing
$this->queueForAsyncTranslation($subject);
} else {
// Synchronous translation (blocks the workflow transition)
$this->performSyncTranslation($subject);
}
} catch (\Exception $e) {
$this->logger->error('AI auto-translation failed', [
'object_id' => $subject->getId(),
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// Translation failure should not block the workflow; manual translation remains as fallback
}
}
/**
* Queue object for asynchronous translation via Symfony Messenger
*/
private function queueForAsyncTranslation(AbstractObject $object): void
{
$message = new TranslateObjectMessage(
$object->getId(),
$object->getClassName(),
$this->translationService->getTargetLanguages()
);
$this->messageBus->dispatch($message);
$this->logger->info('Object queued for async translation', [
'object_id' => $object->getId(),
'class' => $object->getClassName(),
'language_count' => count($this->translationService->getTargetLanguages())
]);
}
/**
* Perform synchronous translation (blocks until complete)
*/
private function performSyncTranslation(AbstractObject $object): void
{
$startTime = microtime(true);
$results = $this->translationService->translateObject($object);
$duration = microtime(true) - $startTime;
$successCount = 0;
$failureCount = 0;
$totalTokens = 0;
foreach ($results as $result) {
if ($result->isSuccess()) {
$successCount++;
$totalTokens += $result->getTokensUsed();
} else {
$failureCount++;
}
}
$this->logger->info('Synchronous translation completed', [
'object_id' => $object->getId(),
'duration_seconds' => round($duration, 2),
'success_count' => $successCount,
'failure_count' => $failureCount,
'total_tokens' => $totalTokens
]);
}
}