l10n_server-2.x-dev/l10n_community/src/Controller/L10nCommunityLanguagesController.php

l10n_community/src/Controller/L10nCommunityLanguagesController.php
<?php

declare(strict_types=1);

namespace Drupal\l10n_community\Controller;

use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\Renderer;
use Drupal\Core\Url;
use Drupal\group\Entity\Group;
use Drupal\l10n_community\L10nTranslator;
use Drupal\l10n_server\Entity\L10nServerProject;
use Drupal\l10n_server\Entity\L10nServerProjectInterface;
use Drupal\l10n_server\Entity\L10nServerReleaseInterface;
use Drupal\l10n_server\Entity\L10nServerStringInterface;
use Drupal\l10n_server\Entity\L10nServerTranslationInterface;
use Drupal\l10n_server\L10nPo;
use Drupal\language\Entity\ConfigurableLanguage;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;

/**
 * Returns responses for Localization community UI routes.
 */
class L10nCommunityLanguagesController extends ControllerBase {

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected Connection $connection;

  /**
   * The config manager.
   *
   * @var \Drupal\Core\Config\ConfigManagerInterface
   */
  protected ConfigManagerInterface $configManager;

  /**
   * Renderer service.
   *
   * @var Drupal\Core\Render\Renderer
   */
  protected Renderer $renderer;

  /**
   * Translator service.
   *
   * @var Drupal\l10n_community\L10nTranslator
   */
  protected L10nTranslator $translator;

  /**
   * L10n helper.
   *
   * @var \Drupal\l10n_server\L10nPo
   */
  protected $l10nPo;

  /**
   * The controller constructor.
   *
   * @param \Drupal\Core\Database\Connection $connection
   *   Database connection.
   * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
   *   The config manager.
   * @param \Drupal\Core\Render\Renderer $renderer
   *   The renderer service.
   * @param \Drupal\l10n_community\L10nTranslator $translator
   *   The translator service.
   * @param \Drupal\l10n_server\L10nPo $l10n_po
   *   L10n helper.
   */
  public function __construct(
      Connection $connection,
      ConfigManagerInterface $config_manager,
      Renderer $renderer,
      L10nTranslator $translator,
      L10nPo $l10n_po
  ) {
    $this->connection = $connection;
    $this->configManager = $config_manager;
    $this->renderer = $renderer;
    $this->translator = $translator;
    $this->l10nPo = $l10n_po;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(
      ContainerInterface $container
  ) {
    return new static(
      $container->get('database'),
      $container->get('config.manager'),
      $container->get('renderer'),
      $container->get('l10n_community.translator'),
      $container->get('l10n_server.po')
    );
  }

  /**
   * Order listing table by column for language overview columns.
   */
  public static function sortByColumnLanguage($a, $b) {
    $sortkey = ($_GET['order'] == t('Language') ? 0 : ($_GET['order'] == t('Contributors') ? 2 : 1));
    if (@$a[$sortkey]['sortdata'] == @$b[$sortkey]['sortdata']) {
      return 0;
    }
    return ((@$a[$sortkey]['sortdata'] < @$b[$sortkey]['sortdata']) ? -1 : 1) * ($_GET['sort'] == 'asc' ? 1 : -1);
  }

  /**
   * Builds the response.
   */
  public function explore() {
    /** @var \Drupal\l10n_community\L10nStatistics $statistics */
    $statistics = \Drupal::service('l10n_community.statistics');

    // Checking whether we have languages to translate to.
    if (!$languages = \Drupal::languageManager()->getLanguages()) {
      $build['content'] = [
        '#type' => 'item',
        '#markup' => $this->t('No languages to list.'),
      ];
      return $build;
    }

    // Checking whether we have strings to translate.
    if (!$num_source = $statistics->getStringCount()) {
      $build['content'] = [
        '#type' => 'item',
        '#markup' => $this->t('No strings to translate.'),
      ];
      return $build;
    }

    // Generate listing of all languages with summaries. The list of languages
    // is relatively "short", compared to projects, so we don't need a pager
    // here.
    $table_rows = [];
    $string_counts = $statistics->getLanguagesStringCount();
    foreach ($languages as $langcode => $language) {
      // Need to load this again to get access to the third party settings :/.
      $language = ConfigurableLanguage::load($langcode);

      if (!$language->getThirdPartySetting('l10n_pconfig', 'formula')) {
        $table_rows[] = [
          [
            'data' => t('@language', [
              '@language' => $language->getName(),
            ]),
            'sortdata' => t('@language', [
              '@language' => $language->getName(),
            ]),
            'class' => ['rowhead'],
          ],
          [
            'data' => t('Uninitialized plural formula. Please set up the plural formula in <a href="@language-config">the langauge configuration</a> or alternatively <a href="@import-url">import a valid interface translation</a> for Drupal in this language.', [
              '@import-url' => Url::fromUri('internal:/admin/structure/translate/import')->toString(),
              '@language-config' => Url::fromUri('internal:/admin/config/regional/language')->toString(),
            ]),
            'class' => ['error'],
          ],
          ['data' => ''],
        ];
      }
      else {
        $stats = $statistics->getLanguageStatisticsByLanguage($langcode);
        $progress = [
          'data' => [
            '#theme' => 'l10n_community_progress_columns',
            '#sum' => $stats['strings'],
            '#translated' => $stats['translations'],
            '#has_suggestion' => $stats['suggestions'],
          ],
        ];
        $table_rows[] = [
          [
            'data' => new FormattableMarkup('<a href=":link">@name</a>', [
              ':link' => Url::fromUri('internal:/translate/languages/' . $langcode)->toString(),
              '@name' => $language->getName(),
            ]),
            'sortdata' => t('@language', [
              '@language' => $language->getName(),
            ]),
            'class' => ['rowhead'],
          ],
          [
            'data' => $progress,
            'sortdata' => ($num_source == 0 ? 0 : round(@$string_counts[$langcode]['translations'] / $num_source * 100, 2)),
          ],
          [
            'data' => $stats['users'],
            'sortdata' => $stats['users'],
          ],
        ];
      }
    }

    if (!empty($_GET['sort']) && !empty($_GET['order'])) {
      usort($table_rows, static::class . '::sortByColumnLanguage');
    }

    $header = [
      [
        'data' => t('Language'),
        'class' => ['rowhead'],
        'field' => 'language',
      ],
      [
        'data' => t('Overall progress'),
        'field' => 'progress',
      ],
      [
        'data' => t('Contributors'),
        'field' => 'contributors',
      ],
    ];
    $build['content'] = [
      '#type' => 'table',
      '#header' => $header,
      '#rows' => $table_rows,
      '#attributes' => [
        'class' => [
          'l10n-community-overview l10n-community-highlighted',
        ],
      ],
      '#cache' => [
        'max-age' => Cache::PERMANENT,
      ],
    ];
    $build['#attached']['library'][] = 'l10n_community/tables';
    return $build;
  }

  /**
   * Builds the response.
   *
   * @param \Drupal\group\Entity\Group $group
   *   The group entity.
   *
   * @return array
   *   An associative render array.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
   */
  public function translate(Group $group): array {
    /** @var \Drupal\l10n_community\L10nTranslator $translator */
    $translator = \Drupal::service('l10n_community.translator');
    $langcode = $group->get('field_translation_language')->first()->getValue()['target_id'];
    $language = \Drupal::languageManager()->getLanguage($langcode);

    $filters = self::buildFilterValues($_GET);
    $drupal_settings = [
      'l10nServerURLs' => $this->addUrlModifiers($langcode, $filters),
    ];
    $strings = $translator->getStrings($langcode, $filters, $filters['limit']);

    // Add RTL style if the current language's direction is RTL.
    if ($language->getDirection() == LanguageInterface::DIRECTION_RTL) {
      $build['#attached']['library'][] = 'l10n_community/editor-rtl';
    }

    // Set the most appropriate title.
    if ($filters['project']) {
      $project = L10nServerProject::load($filters['project']);
      $build['#title'] = $this->t('Translate %project to @language', [
        '%project' => $project->label(),
        '@language' => $language->getName(),
      ]);
    }
    else {
      $build['#title'] = $this->t('Translate to @language', [
        '@language' => $language->getName(),
      ]);
    }

    // Add the filter form.
    $build['filter'] = \Drupal::formBuilder()->getForm('Drupal\l10n_community\Form\FilterForm');

    // Output the actual strings.
    if (!count($strings)) {
      \Drupal::messenger()->addError($this->t('No strings found with this filter. Try adjusting the filter options.'));
    }
    else {
      $build['translate_form'] = \Drupal::formBuilder()->getForm('Drupal\l10n_community\Form\TranslateForm', $language, $filters, $strings);
    }

    $build['#attached']['drupalSettings'] = $drupal_settings;

    return $build;
  }

  /**
   * Title callback.
   *
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
   *   Page title.
   */
  public function translateTitle() {
    return $this->t('Translate');
  }

  /**
   * Builds the response.
   *
   * @param \Drupal\group\Entity\Group $group
   *   The group entity.
   *
   * @return array
   *   An associative render array.
   */
  public function import(Group $group): array {
    $group = \Drupal::routeMatch()->getParameter('group');
    $langcode = $group->get('field_translation_language')->first()->getValue()['target_id'];
    return \Drupal::formBuilder()->getForm('Drupal\l10n_community\Form\ImportForm', $langcode);
  }

  /**
   * Title callback.
   *
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
   *   Page title.
   *
   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
   */
  public function importTitle() {
    /** @var \Drupal\group\Entity\Group $group */
    $group = \Drupal::routeMatch()->getParameter('group');
    $langcode = $group->get('field_translation_language')->first()->getValue()['target_id'];
    $language = \Drupal::languageManager()->getLanguage($langcode);
    return $this->t('Import to @language', ['@language' => $language->getName()]);
  }

  /**
   * Builds the response.
   *
   * @param \Drupal\group\Entity\Group $group
   *   The group entity.
   *
   * @return array
   *   An associative render array.
   *
   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
   */
  public function export(Group $group): array {
    $langcode = $group->get('field_translation_language')->first()->getValue()['target_id'];
    return \Drupal::formBuilder()->getForm('Drupal\l10n_community\Form\ExportForm', NULL, $langcode);
  }

  /**
   * Title callback.
   *
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
   *   Page title.
   */
  public function exportTitle() {
    /** @var \Drupal\group\Entity\Group $group */
    $group = \Drupal::routeMatch()->getParameter('group');
    $langcode = $group->get('field_translation_language')->first()->getValue()['target_id'];
    $language = \Drupal::languageManager()->getLanguage($langcode);
    return $this->t('Export @language translations', ['@language' => $language->getName()]);
  }

  /**
   * Builds the response.
   *
   * @param \Drupal\group\Entity\Group $group
   * @param \Drupal\l10n_server\Entity\L10nServerProjectInterface $project
   * @param \Drupal\l10n_server\Entity\L10nServerReleaseInterface $release
   *
   * @return array
   *
   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
   */
  public function reset(Group $group, L10nServerProjectInterface $project, L10nServerReleaseInterface $release) {
    $langcode = $group->get('field_translation_language')->first()->getValue()['target_id'];
    $language = \Drupal::languageManager()->getLanguage($langcode);
    $build['content'] = [
      '#type' => 'item',
      '#markup' => __METHOD__ . '::' . $group->label() . '::' . $project->label() . '::' . $release->label() . '::' . $language->getId(),
    ];
    return $build;
  }

  /**
   * Check and sanitize arguments and build filter array.
   *
   * @param array $params
   *   Associative array with unsanitized values.
   *
   * @return array
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public static function buildFilterValues(array $params) {
    $project = $release = NULL;

    // Convert array representation of flags to one integer.
    if (isset($params['status']) && is_array($params['status'])) {
      if (isset($params['status']['suggestion'])) {
        $params['status'] = ((int) $params['status']['translation']) | ((int) $params['status']['suggestion']);
      }
      else {
        $params['status'] = (int) $params['status']['translation'];
      }
    }

    $filter = [
      'project' => NULL,
      'status' => isset($params['status']) ? (int) $params['status'] : 0,
      'release' => 'all',
      'search' => !empty($params['search']) ? (string) $params['search'] : '',
      'author' => NULL,
      // Dropdown, validated by form API.
      'context' => isset($params['context']) ? (string) $params['context'] : 'all',
      'limit' => (isset($params['limit']) && in_array($params['limit'], [5, 10, 20, 30, 50])) ? (int) $params['limit'] : 10,
      'sid' => (!empty($params['sid']) && is_numeric($params['sid'])) ? $params['sid'] : 0,
    ];

    if (isset($params['author'])) {
      $user_storage = \Drupal::entityTypeManager()
        ->getStorage('user');
      if (is_numeric($params['author'])) {
        $user = $user_storage->load($params['author']);
      }
      else {
        $users = $user_storage->loadByProperties(['name' => $params['author']]);
        $user = reset($users);
      }
      if (isset($user)) {
        $filter['author'] = $user->id();
      }
    }

    // The project can be a dropdown or text field depending on number of
    // projects. So we need to sanitize its value.
    if (isset($params['project'])) {
      // Try to load project by uri or id, but give URI priority. URI is used
      // to shorten the URL and have simple redirects. ID is used if the
      // filter form was submitted.
      $project_storage = \Drupal::entityTypeManager()
        ->getStorage('l10n_server_project');

      if (is_numeric($params['project'])) {
        $project = $project_storage->load($params['project']);
      }
      else {
        $projects = $project_storage->loadByProperties(['uri' => $params['project']]);
        $project = reset($projects);
      }

      /** @var \Drupal\l10n_server\Entity\L10nServerProject $project */
      if ($project instanceof \Drupal\l10n_server\Entity\L10nServerProject) {
        $filter['project'] = $project->id();

        $release_storage = \Drupal::entityTypeManager()
          ->getStorage('l10n_server_release');
        if (isset($params['release'])
            && ($releases = $release_storage->loadByProperties(['pid' => $project->id(), 'rid' => $params['release']]))) {
          // Allow to select this release, if belongs to current project only.
          $filter['release'] = $params['release'];
        }
      }
    }
    return $filter;
  }

  /**
   * Generate and add JS for URL replacements.
   *
   * These ensure we keep filter values.
   *
   * @param string $langcode
   * @param array $filters
   *
   * @return array
   */
  public function addUrlModifiers(string $langcode, array $filters) {
    $filters = self::flatFilters($filters);
    $urls = [
      'translate/languages/' . $langcode . '/translate',
      'translate/languages/' . $langcode . '/export',
    ];
    $replacements = [];
    foreach ($urls as $url) {
      $replacements[Url::fromUri('internal:/' . $url)->toString()] = Url::fromUri('internal:/' . $url, ['query' => $filters])->toString();
    }
    return $replacements;
  }

  /**
   * Replace complex data filters (objects or arrays) with string representations.
   *
   * @param array $filters
   *   Associative array with filters passed.
   *
   * @return array
   *   The modified filter array only containing string and number values.
   */
  public static function flatFilters(array $filters) {
//    foreach (['project' => 'uri', 'author' => 'name'] as $name => $key) {
//      if (!empty($filters[$name])) {
//        $filters[$name] = isset($filters[$name]->$key) ?? $filters[$name]->$key;
//      }
//    }
    return $filters;
  }

  /**
   * Provides full history information about translation.
   *
   * This callback is invoked from AJAX.
   *
   * @param \Drupal\l10n_server\Entity\L10nServerTranslationInterface $translation
   *   Translation entity.
   *
   * @return \Symfony\Component\HttpFoundation\Response
   *   Response to AJAX call.
   */
  public function translationDetails(L10nServerTranslationInterface $translation) {
    $string = $translation->sid->entity;

    $uid_submitted = 0;
    $history_list = [];
    foreach ($translation->getHistory() as $item) {
      $username = $item->uid_action?->entity?->label() ?? '';
      // @todo séparer cette fonction du formulaire ?
      $history_list[] = $this->l10nPo->translateByline($username, $item->uid_action->target_id, $item->time_action->value, $item->medium_action->value, $item->type_action->value);
      if ($item->type_action->value == L10N_SERVER_ACTION_ADD) {
        // Remember the first uid who submitted this. This will get overwritten,
        // and since we are going backwards in time, the last value kept will be
        // the first uid who submitted this string. We need this in case it is
        // different from the user we used for attribution, so we can display
        // the difference to the translator/moderator.
        $uid_submitted = $item->uid_action->value;
      }
    }
    if (!empty($uid_submitted) && ($uid_submitted != $string->uid_entered->value)) {
      // Turns out the user used for attribution is different from the user used
      // for the string storage.
      array_unshift($history_list, $this->t('by @author', [
        '@author' => $string->uid_entered->entity->toLink()->toString(),
      ]));
    }
    $output = [
      '#theme' => 'item_list',
      '#items' => $history_list,
    ];

    return new Response($this->renderer->renderPlain($output));
  }

  /**
   * Generate a list of projects and releases where a string appears.
   *
   * We could provide much more information (down to line numbers of files), but
   * usability should also be kept in mind. It is possible to investigate hidden
   * information sources though, like tooltips on the release titles presented.
   *
   * This callback is invoked from AJAX.
   *
   * @param \Drupal\l10n_server\Entity\L10nServerStringInterface $string
   *   String entity.
   *
   * @return \Symfony\Component\HttpFoundation\Response
   *   Response to AJAX call.
   */
  public function sourceDetails(L10nServerStringInterface $string) {
    $version_list = [];
    $project_list = [];
    $previous_project = '';
    foreach ($this->translator->getSourceDetails($string->id()) as $instance) {
      $release_info = $instance->version . ' <span title="' . $this->formatPlural($instance->occurrence_count, 'Appears once in this release.', 'Appears @count times in this release.') . '">(' . $instance->occurrence_count . ')</span>';
      if ($instance->project_title != $previous_project) {
        if (!empty($version_list)) {
          $project_list[] = ['#markup' => implode(', ', $version_list)];
        }
        $version_list = ['<em>' . $instance->project_title . ':</em> ' . $release_info];
      }
      else {
        $version_list[] = $release_info;
      }
      $previous_project = $instance->project_title;
    }
    $project_list[] = ['#markup' => implode(', ', $version_list)];
    $usage_list = [
      '#theme' => 'item_list',
      '#items' => $project_list,
    ];

    return new Response($this->renderer->renderPlain($usage_list));
  }

}

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

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