lionbridge_translation_provider-8.x-2.4/src/Plugin/tmgmt/Translator/LionbridgeTranslator.php

src/Plugin/tmgmt/Translator/LionbridgeTranslator.php
<?php

namespace Drupal\lionbridge_translation_provider\Plugin\tmgmt\Translator;

use Drupal\Component\Serialization\Json;
use Drupal\tmgmt\Entity\Job;
use Drupal\Core\Url;
use Drupal\lionbridge_translation_provider\LionbridgeConnector;
use Drupal\tmgmt\JobInterface;
use Drupal\tmgmt\TMGMTException;
use Drupal\tmgmt\TranslatorInterface;
use Drupal\tmgmt\TranslatorPluginBase;
use Drupal\tmgmt\Translator\AvailableResult;
use Drupal\tmgmt\Translator\TranslatableResult;
use \Drupal\Core\Utility\Error;

/**
 * Lionbridge provider.
 *
 * @TranslatorPlugin(
 *   id = "lionbridge",
 *   label = @Translation("Lionbridge translator"),
 *   description = @Translation("Lionbridge translator service."),
 *   ui = "Drupal\lionbridge_translation_provider\LionbridgeTranslatorUi",
 *   default_settings = {
 *     "po_number" = "123456"
 *   },
 * )
 */
class LionbridgeTranslator extends TranslatorPluginBase {

  /**
   * Translation keys.
   *
   * @var array
   */
  protected $translationKeys;

  /**
   * {@inheritdoc}
   */
  #[\ReturnTypeWillChange]
    public function requestTranslation(JobInterface $job) {
    if ($job->isUnprocessed()) {
      try {
        $this->submitJob($job);

        if (!$job->isRejected()) {
          // @todo: Unprocessed Jobs should not trigger success message.
          $job->submitted(t('Job has been submitted.'));
        }
      }
      catch (TMGMTException $e) {
        $logger = \Drupal::logger('Lionbridge Translator');
        Error::logException($logger, $e, "lionbridge_translation_provider.");
        $job->rejected('Job has been rejected with following error: @error',
          ['@error' => $e->getMessage()],
          'error'
        );
      }
    }

    if ($job->isActive()) {
      try {
        $this->fetchJob($job);
      }
      catch (TMGMTException $e) {
        $msg = t('Translation could not be completed: @message',
        ['@message' => $e->getMessage()],
        'error'
        );
        if (strlen($msg) > 200) {
          $msg = substr($msg, 0, 200);
        }
        $logger = \Drupal::logger('Lionbridge Translator');
        Error::logException($logger, $e, "lionbridge_translation_provider.");
        $job->addMessage($msg, [], 'error');
      }
    }
    }

    /**
     * {@inheritdoc}
     */
    #[\ReturnTypeWillChange]
    public function checkAvailable(TranslatorInterface $translator) {
      if ($translator->getSetting('access_key_id') && $translator->getSetting('access_key')) {
        return AvailableResult::yes();
      }

      return AvailableResult::no(t('Access key ID and access key are not set.'));
    }

    /**
     * {@inheritdoc}
     */
    #[\ReturnTypeWillChange]
    public function checkTranslatable(TranslatorInterface $translator, JobInterface $job) {
      if ($job->isUnprocessed() || $job->isRejected() || $job->isAborted()) {
        return TranslatableResult::no(t("Please use the ContentAPI connector."));
      }
      else {
        return TranslatableResult::yes();
      }
    }

    /**
     * {@inheritdoc}
     */
    #[\ReturnTypeWillChange]
    public function getSupportedRemoteLanguages(TranslatorInterface $translator) {
      if (!$translator->checkAvailable()->getSuccess()) {
        return parent::getSupportedRemoteLanguages($translator);
      }

      $api_client = new LionbridgeConnector($translator);
      $locales = $api_client->listLocales();
      $languages = [];
      foreach ($locales['Locale'] as $language) {
        $languages[$language['Code']] = $language['Code'];
      }

      return $languages;
    }

    /**
     * Submits a job to Lionbridge translation service.
     *
     * This does the work of actually submitting the translation job to the
     * Lionbridge translation service. There are 3 major parts to submitting a
     * translations job. Each part has been broken out into an individual
     * function to make the code more readable.
     *
     * 1. Create a JSON encoded file that contains all the translatable content
     *    and send it to Lionbridge. If this is successful, an array containing
     *    the details of the uploaded file is returned.
     *
     * 2. Create a project using the settings from the job settings page and the
     *    AssetID of the array returned from the successful file upload. If there
     *    were more than one file uploaded, multiple file asset ID's could be
     *    used in a project, however, in this model there is only 1 asset ID for
     *    a given project.
     *
     * 3. Generate a quote for the translation job using the Project ID from the
     *    array returned from a successful project creation request.
     *
     * NOTE: Translation Memory update job files are XML encoded.
     *
     * @param \Drupal\tmgmt\JobInterface $job
     *   The job object.
     *
     * @throws \Drupal\tmgmt\TMGMTException
     */
    #[\ReturnTypeWillChange]
    public function submitJob(JobInterface $job) {
      $translator = $job->getTranslator();
      $api_client = new LionbridgeConnector($translator);
      $items = $job->getItems();
      $file_assets = [];
      $source_language = $translator->mapToRemoteLanguage($job->getSourceLangcode());
      $target_language = $translator->mapToRemoteLanguage($job->getTargetLangcode());

      if ($translator->getSetting('notification_url')) {
        $project_complete_url = $translator->getSetting('notification_url');
      }
      else {
        $project_complete_url = Url::fromRoute('lionbridge_translation_provider.lionbridge_translation_provider_project_complete_callback', [], [
          'query' => ['secret' => $job->getSetting('secret')],
          'absolute' => TRUE,
        ])->toString();
      }

      $service_type = $translator->getSetting('service_type');

      switch ($service_type) {
        case 'tm_update':
          // If service type is an update, then we send a xml file with the
          // content that was translated and updated locally.
          // Load the entity and check if there is a translation already.
          $job_item = reset($items);
          $storage = \Drupal::entityTypeManager()->getStorage($job_item->getItemType());
          $entity = $storage->load($job_item->getItemId());

          // If there is translated data, load it.
          if ($entity->hasTranslation($job->getTargetLangcode())) {
            // The $job_item already has the data with the original content to be
            // translated, here we load the source plugin and the already
            // translated entity and format the translated entity just like it is
            // already formatted in the job.
            $source_plugin = $job_item->getSourcePlugin();
            $translated_entity = $entity->getTranslation($job->getTargetLangcode());
            $translated_data = $source_plugin->extractTranslatableData($translated_entity);
          }
          else {
            throw new TMGMTException('Source is required to have translated content for language requested.');
          }

          // Get the item path, e.g. node/15, taxonomy/1, etc.
          $item_path = $entity->toUrl()->getInternalPath();

          // Build data to be added to the xml file.
          $update_data = [
            'theme' => 'lionbridge_update_content',
            'source_language' => $source_language,
            'target_language' => $target_language,
            'item_path' => $item_path,
          ];

          $file_name = $this->generateFileName($job->label(), $source_language, $target_language);

          $file_asset = $this->generateUpdateFile($file_name, $update_data, $job->getData(), $translated_data, $api_client);
          if (isset($file_asset['Errors']['Error'])) {
            throw new TMGMTException($file_asset['Errors']['Error']['DetailedMessage']);
          }

          $file_assets[] = $file_asset['AssetID'];

          break;

        default:
          // If service is of type translation, then we send a json file with the
          // translation and a xml file for a new project.
          // This is the name of the JSON file that will be sent to Lionbridge.
          $file_name = $this->generateFileName($job->label(), $source_language, $target_language);

          $file_asset = $this->generateFile($file_name, $source_language, $job->getData(), $api_client);
          if (isset($file_asset['Errors']['Error'])) {
            throw new TMGMTException($file_asset['Errors']['Error']['DetailedMessage']);
          }

          $file_assets[] = $file_asset['AssetID'];

          break;
      }

      // Create a new project with the file asset, send it to Lionbridge. This
      // will create a new project and return the project XML in the form of an
      // array.
      $service_id = $translator->getSetting('service');

      $project_data = [
        'theme' => 'lionbridge_add_project',
        'project_title' => $job->getSetting('project_title') . ' to ' . $target_language,
        'service_id' => $service_id,
        'source_language' => $source_language,
        'target_languages' => [$target_language],
        'file_assets' => $file_assets,
      ];

      $project = $this->addProject($project_data, $api_client);

      // Check if there are errors in the project.
      if (isset($project['Errors']['Error']) && !empty($project['Errors']['Error'])) {
        throw new TMGMTException($project['Errors']['Error']['DetailedMessage']);
      }

      // Get a quote for the project.
      $quote = $this->generateQuote($project['ProjectID'], $project_complete_url, $api_client);

      if (isset($quote['Errors']) && !empty($quote['Errors'])) {
        throw new TMGMTException($quote['Errors']['Error']['DetailedMessage']);
      }

      // Set the job reference to the Quote ID.
      $job->set('reference', $quote['QuoteID']);

      // Add project ID to `tmgmt_remote` table.
      foreach ($this->translationKeys as $translation_key) {
        $items[$translation_key['tjiid']]->addRemoteMapping(
        $translation_key['data_item_key'],
        $project['ProjectID']
        );
      }

    }

    /**
     * Gets status of job items from Lionbridge and downloads translated items.
     *
     * @param \Drupal\tmgmt\JobInterface $job
     *   The translation job object.
     *
     * @return bool
     *   Completion indicator.
     *
     * @throws \Drupal\tmgmt\TMGMTException
     */
    #[\ReturnTypeWillChange]
    public function fetchJob(JobInterface $job) {
      $translator = $job->getTranslator();
      $api_client = new LionbridgeConnector($translator);
      if ($job->getReference()) {
        $quote = $api_client->getQuote($job->getReference());
        if (!$quote || isset($quote['Error'])) {
          throw new TMGMTException('Could not get quote.');
        }

        $translation_complete = FALSE;
        $service_type = $translator->getSetting('service_type');

        switch ($quote['Status']) {
          case LionbridgeConnector::QUOTE_STATUS_ERROR:
            $job->addMessage('An error occurred with this quote.');
            $this->abortTranslation($job);
            break;

          case LionbridgeConnector::QUOTE_STATUS_PENDING:
            // If this is a pending quote, add a message and a link to approve.
            $job->addMessage(
            'Quote ready for approval: <a href=":url" target="_blank">View on Lionbridge</a>', [
              ':url' => Url::fromUri($api_client->getEndpoint() . '/project/' . $quote['QuoteID'] . '/details')->toString(),
            ]
            );
            break;

          case LionbridgeConnector::QUOTE_STATUS_CALCULATING:
            $job->addMessage('Calculating quote...');
            break;

          case LionbridgeConnector::QUOTE_STATUS_AUTHORIZED:
            if ($service_type == 'tm_update') {
              $job->addMessage('Translation Memory Update in progress.');
            }
            else {
              $job->addMessage('Translation in progress.');
            }
            break;

          case LionbridgeConnector::QUOTE_STATUS_COMPLETE:
            // If it's a TM update, complete job items.
            if ($service_type == 'tm_update') {
              foreach ($job->getItems() as $job_item) {
                $variables = [
                  '@source' => $job_item->getSourceLabel(),
                  '@language' => $job->getTargetLanguage()->getName(),
                ];
                $job_item->accepted('Translation Memory Update of "@source" for language @language is finished.', $variables);
              }
            }
            else {
              // Get the file which contains translations for all job items.
              // Loop through the remotes, populate translated data.
              $asset_id = $quote['Projects']['Project']['Files']['File']['AssetID'];
              $data_json = $api_client->getFileTranslation($asset_id, $translator->mapToRemoteLanguage($job->getTargetLangcode()));
              $data = Json::decode($data_json);

              if (!$data) {
                $data_error_message = json_last_error_msg();
                throw new TMGMTException($data_error_message);
              }
              // Batch process the storing of the translated data.
              batch_set(_lionbridge_translation_provider_batch($job, $data));
            }

            $translation_complete = TRUE;
            break;

        }
        return $translation_complete;
      }
    }

    /**
     * {@inheritdoc}
     *
     * @todo: Jobs with authorized Quotes should not be able to be aborted.
     */
    #[\ReturnTypeWillChange]
    public function abortTranslation(JobInterface $job) {
      $translator = $job->getTranslator();
      $api_client = new LionbridgeConnector($translator);
      $quote = $api_client->getQuote($job->getReference());

      // If the status is Pending reject the quote on Lionbridge.
      if ($quote && empty($quote['Errors']) && $quote['Status'] == LionbridgeConnector::QUOTE_STATUS_PENDING) {
        $api_client->rejectQuote($quote['QuoteID']);
      }

      $job->aborted();
      return TRUE;
    }

    /**
     * Sends a file to the Lionbridge translation service.
     */
    protected function addFile($translation, LionbridgeConnector $api_client) {
      return $api_client->addFile(
      $translation['source_language'],
      $translation['fileName'],
      $translation['content_type'],
      $translation['file_content']
      );
    }

    /**
     * Sends a create project request to the Lionbridge translation service.
     */
    protected function addProject($project_data, LionbridgeConnector $api_client) {
      return $api_client->addProject(
      $project_data['project_title'],
      $project_data['service_id'],
      $project_data['source_language'],
      $project_data['target_languages'],
      $project_data['file_assets']
      );
    }

    /**
     * Send Memory Update in xml to Lionbridge.
     *
     * @param string $file_name
     *   The file name.
     * @param array $update_data
     *   The data to be used in render array to form file content.
     * @param array $entity_data
     *   The source entity data.
     * @param array $translated_entity_data
     *   The translated entity data.
     * @param \Drupal\lionbridge_translation_provider\LionbridgeConnector $api_client
     *   The API client.
     *
     * @return array
     *   The information about the file returned from the API client.
     */
    #[\ReturnTypeWillChange]
    public function generateUpdateFile($file_name, array $update_data, array $entity_data, array $translated_entity_data, LionbridgeConnector $api_client) {
      // Get the content to be translated and the already translated content and
      // build an array of "content_corrections" that will be sent to Lionbridge.
      $content_corrections = [];

      foreach ([
        'source_content' => reset($entity_data),
        'target_content' => $translated_entity_data
        ] as $key => $data_to_flat) {
        if ($data_to_flat == NULL) {
          $data_flat = NULL;
        }
        else {
          $data_flat = array_filter(
            \Drupal::service('tmgmt.data')->flatten($data_to_flat),
            [
              \Drupal::service('tmgmt.data'),
              'filterData'
            ]
          );
        }
        foreach ($data_flat as $flat_key => $value) {
          $flat_key = str_replace(['[', ']'], ':', $flat_key);
          $content_corrections[$flat_key][$key] = trim(str_replace(['“', '”'], '\"', htmlspecialchars($value['#text'])));
        }
      }

      foreach ($content_corrections as $data_item_key => $value) {
        // Preserve the original data.
        $this->translationKeys[] = [
          'tjiid' => key($entity_data),
          'data_item_key' => $data_item_key,
        ];
      }

      // Compose render array with the translation update properties and values.
      $update_xml = [
        '#theme' => $update_data['theme'],
        '#source_language' => $update_data['source_language'],
        '#target_language' => $update_data['target_language'],
        '#item_path' => $update_data['item_path'],
        '#content_corrections' => $content_corrections,
      ];

      // Generate update file content.
      $file_content = \Drupal::service('renderer')->render($update_xml)->__toString();

      $file_data = [
        'source_language' => $update_data['source_language'],
        'fileName' => urlencode($file_name . '.xml'),
        'content_type' => 'text/xml',
        'file_content' => $file_content,
      ];

      return $this->addFile($file_data, $api_client);
    }

    /**
     * Gets a quote from the Lionbridge translation service.
     */
    protected function generateQuote($project_id, $notification_url, LionbridgeConnector $api_client) {
      return $api_client->generateQuote($project_id, $notification_url);
    }

    /**
     * Create JSON encoded file.
     *
     * @param string $file_name
     *   The lionbridge connector.
     * @param string $source_language
     *   The source language.
     * @param mixed $data
     *   The data to be flattened.
     * @param \Drupal\lionbridge_translation_provider\LionbridgeConnector $api_client
     *   The API client.
     *
     * @return array
     *   The created file.
     */
    protected function generateFile($file_name, $source_language, $data, LionbridgeConnector $api_client) {
      $translatable_content = [];

      $data_flat = array_filter(
        \Drupal::service('tmgmt.data')->flatten($data),
        [
          \Drupal::service('tmgmt.data'),
          'filterData'
        ]
      );

      foreach ($data_flat as $key => $value) {
        [$tjiid, $data_item_key] = explode('][', $key, 2);

        // Preserve the original data.
        $this->translationKeys[] = [
          'tjiid' => $tjiid,
          'data_item_key' => $data_item_key,
        ];

        // This is the actual text sent to Lionbridge for translation.
        $translatable_content[$tjiid][$data_item_key] = str_replace(['“', '”'], '\"', $value['#text']);
      }

      $file_content = json_encode($translatable_content, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

      $translation = [
        'source_language' => $source_language,
        'fileName' => urlencode($file_name . '.json'),
        'content_type' => 'application/json',
        'file_content' => $file_content,
      ];

      return $this->addFile($translation, $api_client);
    }

    /**
     * Create a file name.
     *
     * @param string $file_name
     *   The file name.
     * @param string $source_language
     *   The source language code.
     * @param string $target_language
     *   The target language code.
     *
     * @return string
     *   The generated file name.
     */
    protected function generateFileName($file_name, $source_language, $target_language) {
      $length = 0;
      $file_name_string = '';
      $string = strtolower($file_name);

      if (preg_match_all('/(\w+)/is', $string, $matches)) {
        foreach ($matches[0] as $match) {
          $length += strlen($match);

          if ($length < 45) {
            $file_name_string .= $match . '-';
          }
        }

        $file_name_string .= $source_language . '-' . $target_language;
        return $file_name_string;
      }

      // Something went wrong, just return $string.
      return $string;
    }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc