Initial Drupal 11 with DDEV setup
This commit is contained in:
		
							
								
								
									
										51
									
								
								web/core/modules/locale/src/Controller/LocaleController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								web/core/modules/locale/src/Controller/LocaleController.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Controller;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Controller\ControllerBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Return response for manual check translations.
 | 
			
		||||
 */
 | 
			
		||||
class LocaleController extends ControllerBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks for translation updates and displays the translations status.
 | 
			
		||||
   *
 | 
			
		||||
   * Manually checks the translation status without the use of cron.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Symfony\Component\HttpFoundation\RedirectResponse
 | 
			
		||||
   *   A redirection to translations reports page.
 | 
			
		||||
   */
 | 
			
		||||
  public function checkTranslation() {
 | 
			
		||||
    $this->moduleHandler()->loadInclude('locale', 'inc', 'locale.compare');
 | 
			
		||||
 | 
			
		||||
    // Check translation status of all translatable project in all languages.
 | 
			
		||||
    // First we clear the cached list of projects. Although not strictly
 | 
			
		||||
    // necessary, this is helpful in case the project list is out of sync.
 | 
			
		||||
    locale_translation_flush_projects();
 | 
			
		||||
    locale_translation_check_projects();
 | 
			
		||||
 | 
			
		||||
    // Execute a batch if required. A batch is only used when remote files
 | 
			
		||||
    // are checked.
 | 
			
		||||
    if (batch_get()) {
 | 
			
		||||
      return batch_process('admin/reports/translations');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this->redirect('locale.translate_status');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Shows the string search screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array for the string search screen.
 | 
			
		||||
   */
 | 
			
		||||
  public function translatePage() {
 | 
			
		||||
    return [
 | 
			
		||||
      'filter' => $this->formBuilder()->getForm('Drupal\locale\Form\TranslateFilterForm'),
 | 
			
		||||
      'form' => $this->formBuilder()->getForm('Drupal\locale\Form\TranslateEditForm'),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,46 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\EventSubscriber;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
 | 
			
		||||
use Drupal\locale\LocaleEvents;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A subscriber invalidating cache tags when translating a string.
 | 
			
		||||
 */
 | 
			
		||||
class LocaleTranslationCacheTag implements EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The cache tags invalidator.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $cacheTagsInvalidator;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a LocaleTranslationCacheTag object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
 | 
			
		||||
   *   The cache tags invalidator.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(CacheTagsInvalidatorInterface $cache_tags_invalidator) {
 | 
			
		||||
    $this->cacheTagsInvalidator = $cache_tags_invalidator;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Invalidate cache tags whenever a string is translated.
 | 
			
		||||
   */
 | 
			
		||||
  public function saveTranslation() {
 | 
			
		||||
    $this->cacheTagsInvalidator->invalidateTags(['rendered', 'locale', 'library_info']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents(): array {
 | 
			
		||||
    $events[LocaleEvents::SAVE_TRANSLATION][] = ['saveTranslation'];
 | 
			
		||||
    return $events;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										187
									
								
								web/core/modules/locale/src/Form/ExportForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								web/core/modules/locale/src/Form/ExportForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,187 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Gettext\PoStreamWriter;
 | 
			
		||||
use Drupal\Core\File\FileSystemInterface;
 | 
			
		||||
use Drupal\Core\Form\FormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Language\LanguageInterface;
 | 
			
		||||
use Drupal\Core\Language\LanguageManagerInterface;
 | 
			
		||||
use Drupal\locale\PoDatabaseReader;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Form for the Gettext translation files export form.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
class ExportForm extends FormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The language manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Language\LanguageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The file system service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\File\FileSystemInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $fileSystem;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new ExportForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
 | 
			
		||||
   *   The language manager.
 | 
			
		||||
   * @param \Drupal\Core\File\FileSystemInterface $file_system
 | 
			
		||||
   *   The file system service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LanguageManagerInterface $language_manager, FileSystemInterface $file_system) {
 | 
			
		||||
    $this->languageManager = $language_manager;
 | 
			
		||||
    $this->fileSystem = $file_system;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('language_manager'),
 | 
			
		||||
      $container->get('file_system')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'locale_translate_export_form';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $languages = $this->languageManager->getLanguages();
 | 
			
		||||
    $language_options = [];
 | 
			
		||||
    foreach ($languages as $langcode => $language) {
 | 
			
		||||
      if (locale_is_translatable($langcode)) {
 | 
			
		||||
        $language_options[$langcode] = $language->getName();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $language_default = $this->languageManager->getDefaultLanguage();
 | 
			
		||||
 | 
			
		||||
    if (empty($language_options)) {
 | 
			
		||||
      $form['langcode'] = [
 | 
			
		||||
        '#type' => 'value',
 | 
			
		||||
        '#value' => LanguageInterface::LANGCODE_SYSTEM,
 | 
			
		||||
      ];
 | 
			
		||||
      $form['langcode_text'] = [
 | 
			
		||||
        '#type' => 'item',
 | 
			
		||||
        '#title' => $this->t('Language'),
 | 
			
		||||
        '#markup' => $this->t('No language available. The export will only contain source strings.'),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $form['langcode'] = [
 | 
			
		||||
        '#type' => 'select',
 | 
			
		||||
        '#title' => $this->t('Language'),
 | 
			
		||||
        '#options' => $language_options,
 | 
			
		||||
        '#default_value' => $language_default->getId(),
 | 
			
		||||
        '#empty_option' => $this->t('Source text only, no translations'),
 | 
			
		||||
        '#empty_value' => LanguageInterface::LANGCODE_SYSTEM,
 | 
			
		||||
      ];
 | 
			
		||||
      $form['content_options'] = [
 | 
			
		||||
        '#type' => 'details',
 | 
			
		||||
        '#title' => $this->t('Export options'),
 | 
			
		||||
        '#tree' => TRUE,
 | 
			
		||||
        '#states' => [
 | 
			
		||||
          'invisible' => [
 | 
			
		||||
            ':input[name="langcode"]' => ['value' => LanguageInterface::LANGCODE_SYSTEM],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
      $form['content_options']['not_customized'] = [
 | 
			
		||||
        '#type' => 'checkbox',
 | 
			
		||||
        '#title' => $this->t('Include non-customized translations'),
 | 
			
		||||
        '#default_value' => TRUE,
 | 
			
		||||
      ];
 | 
			
		||||
      $form['content_options']['customized'] = [
 | 
			
		||||
        '#type' => 'checkbox',
 | 
			
		||||
        '#title' => $this->t('Include customized translations'),
 | 
			
		||||
        '#default_value' => TRUE,
 | 
			
		||||
      ];
 | 
			
		||||
      $form['content_options']['not_translated'] = [
 | 
			
		||||
        '#type' => 'checkbox',
 | 
			
		||||
        '#title' => $this->t('Include untranslated text'),
 | 
			
		||||
        '#default_value' => TRUE,
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $form['actions'] = [
 | 
			
		||||
      '#type' => 'actions',
 | 
			
		||||
    ];
 | 
			
		||||
    $form['actions']['submit'] = [
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => $this->t('Export'),
 | 
			
		||||
    ];
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    // If template is required, language code is not given.
 | 
			
		||||
    if ($form_state->getValue('langcode') != LanguageInterface::LANGCODE_SYSTEM) {
 | 
			
		||||
      $language = $this->languageManager->getLanguage($form_state->getValue('langcode'));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $language = NULL;
 | 
			
		||||
    }
 | 
			
		||||
    $content_options = $form_state->getValue('content_options', []);
 | 
			
		||||
    $reader = new PoDatabaseReader();
 | 
			
		||||
    $language_name = '';
 | 
			
		||||
    if ($language != NULL) {
 | 
			
		||||
      $reader->setLangcode($language->getId());
 | 
			
		||||
      $reader->setOptions($content_options);
 | 
			
		||||
      $languages = $this->languageManager->getLanguages();
 | 
			
		||||
      $language_name = isset($languages[$language->getId()]) ? $languages[$language->getId()]->getName() : '';
 | 
			
		||||
      $filename = $language->getId() . '.po';
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // Template required.
 | 
			
		||||
      $filename = 'drupal.pot';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $item = $reader->readItem();
 | 
			
		||||
    if (!empty($item)) {
 | 
			
		||||
      $uri = $this->fileSystem->tempnam('temporary://', 'po_');
 | 
			
		||||
      $header = $reader->getHeader();
 | 
			
		||||
      $header->setProjectName($this->config('system.site')->get('name'));
 | 
			
		||||
      $header->setLanguageName($language_name);
 | 
			
		||||
 | 
			
		||||
      $writer = new PoStreamWriter();
 | 
			
		||||
      $writer->setURI($uri);
 | 
			
		||||
      $writer->setHeader($header);
 | 
			
		||||
 | 
			
		||||
      $writer->open();
 | 
			
		||||
      $writer->writeItem($item);
 | 
			
		||||
      $writer->writeItems($reader);
 | 
			
		||||
      $writer->close();
 | 
			
		||||
 | 
			
		||||
      $response = new BinaryFileResponse($uri);
 | 
			
		||||
      $response->setContentDisposition('attachment', $filename);
 | 
			
		||||
      $form_state->setResponse($response);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->messenger()->addStatus($this->t('Nothing to export.'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										202
									
								
								web/core/modules/locale/src/Form/ImportForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								web/core/modules/locale/src/Form/ImportForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,202 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\Environment;
 | 
			
		||||
use Drupal\Core\Form\FormBase;
 | 
			
		||||
use Drupal\Core\Extension\ModuleHandlerInterface;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\language\ConfigurableLanguageManagerInterface;
 | 
			
		||||
use Drupal\language\Entity\ConfigurableLanguage;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Form constructor for the translation import screen.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
class ImportForm extends FormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Uploaded file entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\file\Entity\File
 | 
			
		||||
   */
 | 
			
		||||
  protected $file;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The module handler service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $moduleHandler;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The configurable language manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\language\ConfigurableLanguageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('module_handler'),
 | 
			
		||||
      $container->get('language_manager')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a form for language import.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
 | 
			
		||||
   *   The module handler service.
 | 
			
		||||
   * @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
 | 
			
		||||
   *   The configurable language manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(ModuleHandlerInterface $module_handler, ConfigurableLanguageManagerInterface $language_manager) {
 | 
			
		||||
    $this->moduleHandler = $module_handler;
 | 
			
		||||
    $this->languageManager = $language_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'locale_translate_import_form';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Form constructor for the translation import screen.
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $languages = $this->languageManager->getLanguages();
 | 
			
		||||
 | 
			
		||||
    // Initialize a language list to the ones available, including English if we
 | 
			
		||||
    // are to translate Drupal to English as well.
 | 
			
		||||
    $existing_languages = [];
 | 
			
		||||
    foreach ($languages as $langcode => $language) {
 | 
			
		||||
      if (locale_is_translatable($langcode)) {
 | 
			
		||||
        $existing_languages[$langcode] = $language->getName();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If we have no languages available, present the list of predefined
 | 
			
		||||
    // languages only. If we do have already added languages, set up two option
 | 
			
		||||
    // groups with the list of existing and then predefined languages.
 | 
			
		||||
    if (empty($existing_languages)) {
 | 
			
		||||
      $language_options = $this->languageManager->getStandardLanguageListWithoutConfigured();
 | 
			
		||||
      $default = key($language_options);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $default = key($existing_languages);
 | 
			
		||||
      $language_options = [
 | 
			
		||||
        (string) $this->t('Existing languages') => $existing_languages,
 | 
			
		||||
        (string) $this->t('Languages not yet added') => $this->languageManager->getStandardLanguageListWithoutConfigured(),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $validators = [
 | 
			
		||||
      'FileExtension' => ['extensions' => 'po'],
 | 
			
		||||
      'FileSizeLimit' => ['fileLimit' => Environment::getUploadMaxSize()],
 | 
			
		||||
      'FileEncoding' => ['encodings' => ['UTF-8']],
 | 
			
		||||
    ];
 | 
			
		||||
    $form['file'] = [
 | 
			
		||||
      '#type' => 'file',
 | 
			
		||||
      '#title' => $this->t('Translation file'),
 | 
			
		||||
      '#description' => [
 | 
			
		||||
        '#theme' => 'file_upload_help',
 | 
			
		||||
        '#description' => $this->t('A Gettext Portable Object file.'),
 | 
			
		||||
        '#upload_validators' => $validators,
 | 
			
		||||
      ],
 | 
			
		||||
      '#size' => 50,
 | 
			
		||||
      '#upload_validators' => $validators,
 | 
			
		||||
      '#upload_location' => 'translations://',
 | 
			
		||||
      '#attributes' => ['class' => ['file-import-input']],
 | 
			
		||||
    ];
 | 
			
		||||
    $form['langcode'] = [
 | 
			
		||||
      '#type' => 'select',
 | 
			
		||||
      '#title' => $this->t('Language'),
 | 
			
		||||
      '#options' => $language_options,
 | 
			
		||||
      '#default_value' => $default,
 | 
			
		||||
      '#attributes' => ['class' => ['langcode-input']],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['customized'] = [
 | 
			
		||||
      '#title' => $this->t('Treat imported strings as custom translations'),
 | 
			
		||||
      '#type' => 'checkbox',
 | 
			
		||||
    ];
 | 
			
		||||
    $form['overwrite_options'] = [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#tree' => TRUE,
 | 
			
		||||
    ];
 | 
			
		||||
    $form['overwrite_options']['not_customized'] = [
 | 
			
		||||
      '#title' => $this->t('Overwrite non-customized translations'),
 | 
			
		||||
      '#type' => 'checkbox',
 | 
			
		||||
      '#states' => [
 | 
			
		||||
        'checked' => [
 | 
			
		||||
          ':input[name="customized"]' => ['checked' => TRUE],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $form['overwrite_options']['customized'] = [
 | 
			
		||||
      '#title' => $this->t('Overwrite existing customized translations'),
 | 
			
		||||
      '#type' => 'checkbox',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['actions'] = [
 | 
			
		||||
      '#type' => 'actions',
 | 
			
		||||
    ];
 | 
			
		||||
    $form['actions']['submit'] = [
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => $this->t('Import'),
 | 
			
		||||
    ];
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validateForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->file = _file_save_upload_from_form($form['file'], $form_state, 0);
 | 
			
		||||
 | 
			
		||||
    // Ensure we have the file uploaded.
 | 
			
		||||
    if (!$this->file) {
 | 
			
		||||
      $form_state->setErrorByName('file', $this->t('File to import not found.'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->moduleHandler->loadInclude('locale', 'translation.inc');
 | 
			
		||||
    // Add language, if not yet supported.
 | 
			
		||||
    $language = $this->languageManager->getLanguage($form_state->getValue('langcode'));
 | 
			
		||||
    if (empty($language)) {
 | 
			
		||||
      $language = ConfigurableLanguage::createFromLangcode($form_state->getValue('langcode'));
 | 
			
		||||
      $language->save();
 | 
			
		||||
      // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
 | 
			
		||||
      $this->messenger()->addStatus($this->t('The language %language has been created.', ['%language' => $this->t($language->label())]));
 | 
			
		||||
    }
 | 
			
		||||
    $options = array_merge(_locale_translation_default_update_options(), [
 | 
			
		||||
      'langcode' => $form_state->getValue('langcode'),
 | 
			
		||||
      'overwrite_options' => $form_state->getValue('overwrite_options'),
 | 
			
		||||
      'customized' => $form_state->getValue('customized') ? LOCALE_CUSTOMIZED : LOCALE_NOT_CUSTOMIZED,
 | 
			
		||||
    ]);
 | 
			
		||||
    $this->moduleHandler->loadInclude('locale', 'bulk.inc');
 | 
			
		||||
    $file = locale_translate_file_attach_properties($this->file, $options);
 | 
			
		||||
    $batch = locale_translate_batch_build([$file->uri => $file], $options);
 | 
			
		||||
    batch_set($batch);
 | 
			
		||||
 | 
			
		||||
    // Create or update all configuration translations for this language.
 | 
			
		||||
    if ($batch = locale_config_batch_update_components($options, [$form_state->getValue('langcode')])) {
 | 
			
		||||
      batch_set($batch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $form_state->setRedirect('locale.translate_page');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										131
									
								
								web/core/modules/locale/src/Form/LocaleSettingsForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								web/core/modules/locale/src/Form/LocaleSettingsForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,131 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\ConfigFormBase;
 | 
			
		||||
use Drupal\Core\Form\ConfigTarget;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Configure locale settings for this site.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
class LocaleSettingsForm extends ConfigFormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'locale_translate_settings';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getEditableConfigNames() {
 | 
			
		||||
    return ['locale.settings'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $config = $this->config('locale.settings');
 | 
			
		||||
 | 
			
		||||
    $form['update_interval_days'] = [
 | 
			
		||||
      '#type' => 'radios',
 | 
			
		||||
      '#title' => $this->t('Check for updates'),
 | 
			
		||||
      '#config_target' => 'locale.settings:translation.update_interval_days',
 | 
			
		||||
      '#options' => [
 | 
			
		||||
        '0' => $this->t('Never (manually)'),
 | 
			
		||||
        '7' => $this->t('Weekly'),
 | 
			
		||||
        '30' => $this->t('Monthly'),
 | 
			
		||||
      ],
 | 
			
		||||
      '#description' => $this->t('Select how frequently you want to check for new interface translations for your currently installed modules and themes. <a href=":url">Check updates now</a>.', [':url' => Url::fromRoute('locale.check_translation')->toString()]),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    if ($directory = $config->get('translation.path')) {
 | 
			
		||||
      $description = $this->t('Translation files are stored locally in the  %path directory. You can change this directory on the <a href=":url">File system</a> configuration page.', ['%path' => $directory, ':url' => Url::fromRoute('system.file_system_settings')->toString()]);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $description = $this->t('Translation files will not be stored locally. Change the Interface translation directory on the <a href=":url">File system configuration</a> page.', [':url' => Url::fromRoute('system.file_system_settings')->toString()]);
 | 
			
		||||
    }
 | 
			
		||||
    $form['#translation_directory'] = $directory;
 | 
			
		||||
    $form['use_source'] = [
 | 
			
		||||
      '#type' => 'radios',
 | 
			
		||||
      '#title' => $this->t('Translation source'),
 | 
			
		||||
      '#config_target' => 'locale.settings:translation.use_source',
 | 
			
		||||
      '#options' => [
 | 
			
		||||
        LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL => $this->t('Drupal translation server and local files'),
 | 
			
		||||
        LOCALE_TRANSLATION_USE_SOURCE_LOCAL => $this->t('Local files only'),
 | 
			
		||||
      ],
 | 
			
		||||
      '#description' => $this->t('The source of translation files for automatic interface translation.') . ' ' . $description,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['overwrite'] = [
 | 
			
		||||
      '#type' => 'radios',
 | 
			
		||||
      '#title' => $this->t('Import behavior'),
 | 
			
		||||
      '#options' => [
 | 
			
		||||
        LOCALE_TRANSLATION_OVERWRITE_NONE => $this->t("Don't overwrite existing translations."),
 | 
			
		||||
        LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED => $this->t('Only overwrite imported translations, customized translations are kept.'),
 | 
			
		||||
        LOCALE_TRANSLATION_OVERWRITE_ALL => $this->t('Overwrite existing translations.'),
 | 
			
		||||
      ],
 | 
			
		||||
      '#description' => $this->t('How to treat existing translations when automatically updating the interface translations.'),
 | 
			
		||||
      '#config_target' => new ConfigTarget(
 | 
			
		||||
        'locale.settings',
 | 
			
		||||
        [
 | 
			
		||||
          'translation.overwrite_customized',
 | 
			
		||||
          'translation.overwrite_not_customized',
 | 
			
		||||
        ],
 | 
			
		||||
        fromConfig: fn (bool $overwrite_customized, bool $overwrite_not_customized): string => match(TRUE) {
 | 
			
		||||
          $overwrite_not_customized == FALSE => LOCALE_TRANSLATION_OVERWRITE_NONE,
 | 
			
		||||
          $overwrite_customized == TRUE => LOCALE_TRANSLATION_OVERWRITE_ALL,
 | 
			
		||||
          default => LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED,
 | 
			
		||||
        },
 | 
			
		||||
        toConfig: fn (string $radio_option): array => match($radio_option) {
 | 
			
		||||
          LOCALE_TRANSLATION_OVERWRITE_ALL => [
 | 
			
		||||
            'translation.overwrite_customized' => TRUE,
 | 
			
		||||
            'translation.overwrite_not_customized' => TRUE,
 | 
			
		||||
          ],
 | 
			
		||||
          LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED => [
 | 
			
		||||
            'translation.overwrite_customized' => FALSE,
 | 
			
		||||
            'translation.overwrite_not_customized' => TRUE,
 | 
			
		||||
          ],
 | 
			
		||||
          LOCALE_TRANSLATION_OVERWRITE_NONE => [
 | 
			
		||||
            'translation.overwrite_customized' => FALSE,
 | 
			
		||||
            'translation.overwrite_not_customized' => FALSE,
 | 
			
		||||
          ],
 | 
			
		||||
        }
 | 
			
		||||
      ),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return parent::buildForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validateForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    parent::validateForm($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    if (empty($form['#translation_directory']) && $form_state->getValue('use_source') == LOCALE_TRANSLATION_USE_SOURCE_LOCAL) {
 | 
			
		||||
      $form_state->setErrorByName('use_source', $this->t('You have selected local translation source, but no <a href=":url">Interface translation directory</a> was configured.', [':url' => Url::fromRoute('system.file_system_settings')->toString()]));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    // Invalidate the cached translation status when the configuration setting
 | 
			
		||||
    // of 'use_source' changes.
 | 
			
		||||
    if ($form['use_source']['#default_value'] != $form_state->getValue('use_source')) {
 | 
			
		||||
      locale_translation_clear_status();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parent::submitForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										242
									
								
								web/core/modules/locale/src/Form/TranslateEditForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								web/core/modules/locale/src/Form/TranslateEditForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,242 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Gettext\PoItem;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Render\Element;
 | 
			
		||||
use Drupal\locale\SourceString;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a translation edit form.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
class TranslateEditForm extends TranslateFormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'locale_translate_edit_form';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $filter_values = $this->translateFilterValues();
 | 
			
		||||
    $langcode = $filter_values['langcode'];
 | 
			
		||||
 | 
			
		||||
    $this->languageManager->reset();
 | 
			
		||||
    $languages = $this->languageManager->getLanguages();
 | 
			
		||||
 | 
			
		||||
    $language_name = isset($langcode) ? $languages[$langcode]->getName() : "- None -";
 | 
			
		||||
 | 
			
		||||
    $form['#attached']['library'][] = 'locale/drupal.locale.admin';
 | 
			
		||||
 | 
			
		||||
    $form['langcode'] = [
 | 
			
		||||
      '#type' => 'value',
 | 
			
		||||
      '#value' => $filter_values['langcode'],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['strings'] = [
 | 
			
		||||
      '#type' => 'table',
 | 
			
		||||
      '#tree' => TRUE,
 | 
			
		||||
      '#language' => $language_name,
 | 
			
		||||
      '#header' => [
 | 
			
		||||
        $this->t('Source string'),
 | 
			
		||||
        $this->t('Translation for @language', ['@language' => $language_name]),
 | 
			
		||||
      ],
 | 
			
		||||
      '#empty' => $this->t('No strings available.'),
 | 
			
		||||
      '#attributes' => ['class' => ['locale-translate-edit-table']],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    if (isset($langcode)) {
 | 
			
		||||
      $strings = $this->translateFilterLoadStrings();
 | 
			
		||||
 | 
			
		||||
      $plurals = $this->getNumberOfPlurals($langcode);
 | 
			
		||||
 | 
			
		||||
      foreach ($strings as $string) {
 | 
			
		||||
        // Cast into source string, will do for our purposes.
 | 
			
		||||
        $source = new SourceString($string);
 | 
			
		||||
        // Split source to work with plural values.
 | 
			
		||||
        $source_array = $source->getPlurals();
 | 
			
		||||
        $translation_array = $string->getPlurals();
 | 
			
		||||
        if (count($source_array) == 1) {
 | 
			
		||||
          // Add original string value and mark as non-plural.
 | 
			
		||||
          $plural = FALSE;
 | 
			
		||||
          $form['strings'][$string->lid]['original'] = [
 | 
			
		||||
            '#type' => 'item',
 | 
			
		||||
            '#title' => $this->t('Source string (@language)', ['@language' => $this->t('Built-in English')]),
 | 
			
		||||
            '#title_display' => 'invisible',
 | 
			
		||||
            '#plain_text' => $source_array[0],
 | 
			
		||||
            '#prefix' => '<span lang="en">',
 | 
			
		||||
            '#suffix' => '</span>',
 | 
			
		||||
          ];
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          // Add original string value and mark as plural.
 | 
			
		||||
          $plural = TRUE;
 | 
			
		||||
          $original_singular = [
 | 
			
		||||
            '#type' => 'item',
 | 
			
		||||
            '#title' => $this->t('Singular form'),
 | 
			
		||||
            '#plain_text' => $source_array[0],
 | 
			
		||||
            '#prefix' => '<span class="visually-hidden">' . $this->t('Source string (@language)', ['@language' => $this->t('Built-in English')]) . '</span><span lang="en">',
 | 
			
		||||
            '#suffix' => '</span>',
 | 
			
		||||
          ];
 | 
			
		||||
          $original_plural = [
 | 
			
		||||
            '#type' => 'item',
 | 
			
		||||
            '#title' => $this->t('Plural form'),
 | 
			
		||||
            '#plain_text' => $source_array[1],
 | 
			
		||||
            '#prefix' => '<span lang="en">',
 | 
			
		||||
            '#suffix' => '</span>',
 | 
			
		||||
          ];
 | 
			
		||||
          $form['strings'][$string->lid]['original'] = [
 | 
			
		||||
            $original_singular,
 | 
			
		||||
            ['#markup' => '<br>'],
 | 
			
		||||
            $original_plural,
 | 
			
		||||
          ];
 | 
			
		||||
        }
 | 
			
		||||
        if (!empty($string->context)) {
 | 
			
		||||
          $form['strings'][$string->lid]['original'][] = [
 | 
			
		||||
            '#type' => 'inline_template',
 | 
			
		||||
            '#template' => '<br><small>{{ context_title }}: <span lang="en">{{ context }}</span></small>',
 | 
			
		||||
            '#context' => [
 | 
			
		||||
              'context_title' => $this->t('In Context'),
 | 
			
		||||
              'context' => $string->context,
 | 
			
		||||
            ],
 | 
			
		||||
          ];
 | 
			
		||||
        }
 | 
			
		||||
        // Approximate the number of rows to use in the default textarea.
 | 
			
		||||
        $rows = min(ceil(str_word_count($source_array[0]) / 12), 10);
 | 
			
		||||
        if (!$plural) {
 | 
			
		||||
          $form['strings'][$string->lid]['translations'][0] = [
 | 
			
		||||
            '#type' => 'textarea',
 | 
			
		||||
            '#title' => $this->t('Translated string (@language)', ['@language' => $language_name]),
 | 
			
		||||
            '#title_display' => 'invisible',
 | 
			
		||||
            '#rows' => $rows,
 | 
			
		||||
            '#default_value' => $translation_array[0],
 | 
			
		||||
            '#attributes' => ['lang' => $langcode],
 | 
			
		||||
          ];
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          // Add a textarea for each plural variant.
 | 
			
		||||
          for ($i = 0; $i < $plurals; $i++) {
 | 
			
		||||
            $form['strings'][$string->lid]['translations'][$i] = [
 | 
			
		||||
              '#type' => 'textarea',
 | 
			
		||||
              // @todo Should use better labels https://www.drupal.org/node/2499639
 | 
			
		||||
              '#title' => ($i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form')),
 | 
			
		||||
              '#rows' => $rows,
 | 
			
		||||
              '#default_value' => $translation_array[$i] ?? '',
 | 
			
		||||
              '#attributes' => ['lang' => $langcode],
 | 
			
		||||
              '#prefix' => $i == 0 ? ('<span class="visually-hidden">' . $this->t('Translated string (@language)', ['@language' => $language_name]) . '</span>') : '',
 | 
			
		||||
            ];
 | 
			
		||||
          }
 | 
			
		||||
          if ($plurals == 2) {
 | 
			
		||||
            // Simplify interface text for the most common case.
 | 
			
		||||
            $form['strings'][$string->lid]['translations'][1]['#title'] = $this->t('Plural form');
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (count(Element::children($form['strings']))) {
 | 
			
		||||
        $form['actions'] = ['#type' => 'actions'];
 | 
			
		||||
        $form['actions']['submit'] = [
 | 
			
		||||
          '#type' => 'submit',
 | 
			
		||||
          '#value' => $this->t('Save translations'),
 | 
			
		||||
        ];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $form['pager']['#type'] = 'pager';
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validateForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $langcode = $form_state->getValue('langcode');
 | 
			
		||||
    foreach ($form_state->getValue('strings') as $lid => $translations) {
 | 
			
		||||
      foreach ($translations['translations'] as $key => $value) {
 | 
			
		||||
        if (!locale_string_is_safe($value)) {
 | 
			
		||||
          $form_state->setErrorByName("strings][$lid][translations][$key", $this->t('The submitted string contains disallowed HTML: %string', ['%string' => $value]));
 | 
			
		||||
          $form_state->setErrorByName("translations][$langcode][$key", $this->t('The submitted string contains disallowed HTML: %string', ['%string' => $value]));
 | 
			
		||||
          $this->logger('locale')->warning('Attempted submission of a translation string with disallowed HTML: %string', ['%string' => $value]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $langcode = $form_state->getValue('langcode');
 | 
			
		||||
    $updated = [];
 | 
			
		||||
 | 
			
		||||
    // Preload all translations for strings in the form.
 | 
			
		||||
    $lids = array_keys($form_state->getValue('strings'));
 | 
			
		||||
    $existing_translation_objects = [];
 | 
			
		||||
    foreach ($this->localeStorage->getTranslations(['lid' => $lids, 'language' => $langcode, 'translated' => TRUE]) as $existing_translation_object) {
 | 
			
		||||
      $existing_translation_objects[$existing_translation_object->lid] = $existing_translation_object;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    foreach ($form_state->getValue('strings') as $lid => $new_translation) {
 | 
			
		||||
      $existing_translation = isset($existing_translation_objects[$lid]);
 | 
			
		||||
 | 
			
		||||
      // Plural translations are saved in a delimited string. To be able to
 | 
			
		||||
      // compare the new strings with the existing strings a string in the same
 | 
			
		||||
      // format is created.
 | 
			
		||||
      $new_translation_string_delimited = implode(PoItem::DELIMITER, $new_translation['translations']);
 | 
			
		||||
 | 
			
		||||
      // Generate an imploded string without delimiter, to be able to run
 | 
			
		||||
      // empty() on it.
 | 
			
		||||
      $new_translation_string = implode('', $new_translation['translations']);
 | 
			
		||||
 | 
			
		||||
      $is_changed = FALSE;
 | 
			
		||||
 | 
			
		||||
      if ($existing_translation && $existing_translation_objects[$lid]->translation != $new_translation_string_delimited) {
 | 
			
		||||
        // If there is an existing translation in the DB and the new translation
 | 
			
		||||
        // is not the same as the existing one.
 | 
			
		||||
        $is_changed = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!$existing_translation && !empty($new_translation_string)) {
 | 
			
		||||
        // Newly entered translation.
 | 
			
		||||
        $is_changed = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ($is_changed) {
 | 
			
		||||
        // Only update or insert if we have a value to use.
 | 
			
		||||
        $target = $existing_translation_objects[$lid] ?? $this->localeStorage->createTranslation(['lid' => $lid, 'language' => $langcode]);
 | 
			
		||||
        $target->setPlurals($new_translation['translations'])
 | 
			
		||||
          ->setCustomized()
 | 
			
		||||
          ->save();
 | 
			
		||||
        $updated[] = $target->getId();
 | 
			
		||||
      }
 | 
			
		||||
      if (empty($new_translation_string) && isset($existing_translation_objects[$lid])) {
 | 
			
		||||
        // Empty new translation entered: remove existing entry from database.
 | 
			
		||||
        $existing_translation_objects[$lid]->delete();
 | 
			
		||||
        $updated[] = $lid;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->messenger()->addStatus($this->t('The strings have been saved.'));
 | 
			
		||||
 | 
			
		||||
    // Keep the user on the current pager page.
 | 
			
		||||
    $page = $this->getRequest()->query->get('page');
 | 
			
		||||
    if (isset($page)) {
 | 
			
		||||
      $form_state->setRedirect(
 | 
			
		||||
        'locale.translate_page',
 | 
			
		||||
        [],
 | 
			
		||||
        ['page' => $page]
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($updated) {
 | 
			
		||||
      // Clear cache and force refresh of JavaScript translations.
 | 
			
		||||
      _locale_refresh_translations([$langcode], $updated);
 | 
			
		||||
      _locale_refresh_configuration([$langcode], $updated);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										105
									
								
								web/core/modules/locale/src/Form/TranslateFilterForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								web/core/modules/locale/src/Form/TranslateFilterForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,105 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a filtered translation edit form.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
class TranslateFilterForm extends TranslateFormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'locale_translate_filter_form';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $filters = $this->translateFilters();
 | 
			
		||||
    $filter_values = $this->translateFilterValues();
 | 
			
		||||
 | 
			
		||||
    $form['#attached']['library'][] = 'locale/drupal.locale.admin';
 | 
			
		||||
 | 
			
		||||
    $form['filters'] = [
 | 
			
		||||
      '#type' => 'details',
 | 
			
		||||
      '#title' => $this->t('Filter translatable strings'),
 | 
			
		||||
      '#open' => TRUE,
 | 
			
		||||
      '#attributes' => ['class' => ['clearfix']],
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($filters as $key => $filter) {
 | 
			
		||||
      // Special case for 'string' filter.
 | 
			
		||||
      if ($key == 'string') {
 | 
			
		||||
        $form['filters']['status']['string'] = [
 | 
			
		||||
          '#type' => 'search',
 | 
			
		||||
          '#title' => $filter['title'],
 | 
			
		||||
          '#description' => $filter['description'],
 | 
			
		||||
          '#default_value' => $filter_values[$key],
 | 
			
		||||
        ];
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $empty_option = $filter['options'][$filter['default']] ?? '- None -';
 | 
			
		||||
        $form['filters']['status'][$key] = [
 | 
			
		||||
          '#title' => $filter['title'],
 | 
			
		||||
          '#type' => 'select',
 | 
			
		||||
          '#empty_value' => $filter['default'],
 | 
			
		||||
          '#empty_option' => $empty_option,
 | 
			
		||||
          '#size' => 0,
 | 
			
		||||
          '#options' => $filter['options'],
 | 
			
		||||
          '#default_value' => $filter_values[$key],
 | 
			
		||||
        ];
 | 
			
		||||
        if (isset($filter['states'])) {
 | 
			
		||||
          $form['filters']['status'][$key]['#states'] = $filter['states'];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $form['filters']['actions'] = [
 | 
			
		||||
      '#type' => 'actions',
 | 
			
		||||
      '#attributes' => ['class' => ['container-inline']],
 | 
			
		||||
    ];
 | 
			
		||||
    $form['filters']['actions']['submit'] = [
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => $this->t('Filter'),
 | 
			
		||||
    ];
 | 
			
		||||
    if ($this->getRequest()->getSession()->has('locale_translate_filter')) {
 | 
			
		||||
      $form['filters']['actions']['reset'] = [
 | 
			
		||||
        '#type' => 'submit',
 | 
			
		||||
        '#value' => $this->t('Reset'),
 | 
			
		||||
        '#submit' => ['::resetForm'],
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $filters = $this->translateFilters();
 | 
			
		||||
    $session_filters = $this->getRequest()->getSession()->get('locale_translate_filter', []);
 | 
			
		||||
    foreach ($filters as $name => $filter) {
 | 
			
		||||
      if ($form_state->hasValue($name)) {
 | 
			
		||||
        $session_filters[$name] = trim($form_state->getValue($name));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $this->getRequest()->getSession()->set('locale_translate_filter', $session_filters);
 | 
			
		||||
    $form_state->setRedirect('locale.translate_page');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides a submit handler for the reset button.
 | 
			
		||||
   */
 | 
			
		||||
  public function resetForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->getRequest()->getSession()->remove('locale_translate_filter');
 | 
			
		||||
    $form_state->setRedirect('locale.translate_page');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										216
									
								
								web/core/modules/locale/src/Form/TranslateFormBase.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								web/core/modules/locale/src/Form/TranslateFormBase.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,216 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormBase;
 | 
			
		||||
use Drupal\Core\Language\LanguageManagerInterface;
 | 
			
		||||
use Drupal\locale\StringStorageInterface;
 | 
			
		||||
use Drupal\Core\State\StateInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the locale user interface translation form base.
 | 
			
		||||
 *
 | 
			
		||||
 * Provides methods for searching and filtering strings.
 | 
			
		||||
 */
 | 
			
		||||
abstract class TranslateFormBase extends FormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The locale storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\locale\StringStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $localeStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The state store.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\State\StateInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $state;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The language manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Language\LanguageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Filter values. Shared between objects that inherit this class.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array|null
 | 
			
		||||
   */
 | 
			
		||||
  protected static $filterValues;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new TranslationFormBase object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringStorageInterface $locale_storage
 | 
			
		||||
   *   The locale storage.
 | 
			
		||||
   * @param \Drupal\Core\State\StateInterface $state
 | 
			
		||||
   *   The state service.
 | 
			
		||||
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
 | 
			
		||||
   *   The language manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(StringStorageInterface $locale_storage, StateInterface $state, LanguageManagerInterface $language_manager) {
 | 
			
		||||
    $this->localeStorage = $locale_storage;
 | 
			
		||||
    $this->state = $state;
 | 
			
		||||
    $this->languageManager = $language_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('locale.storage'),
 | 
			
		||||
      $container->get('state'),
 | 
			
		||||
      $container->get('language_manager')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds a string search query and returns an array of string objects.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\locale\TranslationString[]
 | 
			
		||||
   *   Array of \Drupal\locale\TranslationString objects.
 | 
			
		||||
   */
 | 
			
		||||
  protected function translateFilterLoadStrings() {
 | 
			
		||||
    $filter_values = $this->translateFilterValues();
 | 
			
		||||
 | 
			
		||||
    // Language is sanitized to be one of the possible options in
 | 
			
		||||
    // translateFilterValues().
 | 
			
		||||
    $conditions = ['language' => $filter_values['langcode']];
 | 
			
		||||
    $options = ['pager limit' => 30, 'translated' => TRUE, 'untranslated' => TRUE];
 | 
			
		||||
 | 
			
		||||
    // Add translation status conditions and options.
 | 
			
		||||
    switch ($filter_values['translation']) {
 | 
			
		||||
      case 'translated':
 | 
			
		||||
        $conditions['translated'] = TRUE;
 | 
			
		||||
        if ($filter_values['customized'] != 'all') {
 | 
			
		||||
          $conditions['customized'] = $filter_values['customized'];
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'untranslated':
 | 
			
		||||
        $conditions['translated'] = FALSE;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!empty($filter_values['string'])) {
 | 
			
		||||
      $options['filters']['source'] = $filter_values['string'];
 | 
			
		||||
      if ($options['translated']) {
 | 
			
		||||
        $options['filters']['translation'] = $filter_values['string'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this->localeStorage->getTranslations($conditions, $options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds an array out of search criteria specified in request variables.
 | 
			
		||||
   *
 | 
			
		||||
   * @param bool $reset
 | 
			
		||||
   *   If the list of values should be reset.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The filter values.
 | 
			
		||||
   */
 | 
			
		||||
  protected function translateFilterValues($reset = FALSE) {
 | 
			
		||||
    if (!$reset && static::$filterValues) {
 | 
			
		||||
      return static::$filterValues;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $filter_values = [];
 | 
			
		||||
    $filters = $this->translateFilters();
 | 
			
		||||
    $request = $this->getRequest();
 | 
			
		||||
    $session_filters = $request->getSession()->get('locale_translate_filter', []);
 | 
			
		||||
    foreach ($filters as $key => $filter) {
 | 
			
		||||
      $filter_values[$key] = $filter['default'];
 | 
			
		||||
      // Let the filter defaults be overwritten by parameters in the URL.
 | 
			
		||||
      if ($request->query->has($key)) {
 | 
			
		||||
        // Only allow this value if it was among the options, or
 | 
			
		||||
        // if there were no fixed options to filter for.
 | 
			
		||||
        $value = $request->query->get($key);
 | 
			
		||||
        if (!isset($filter['options']) || isset($filter['options'][$value])) {
 | 
			
		||||
          $filter_values[$key] = $value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      elseif (isset($session_filters[$key])) {
 | 
			
		||||
        // Only allow this value if it was among the options, or
 | 
			
		||||
        // if there were no fixed options to filter for.
 | 
			
		||||
        if (!isset($filter['options']) || isset($filter['options'][$session_filters[$key]])) {
 | 
			
		||||
          $filter_values[$key] = $session_filters[$key];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return static::$filterValues = $filter_values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Lists locale translation filters that can be applied.
 | 
			
		||||
   */
 | 
			
		||||
  protected function translateFilters() {
 | 
			
		||||
    $filters = [];
 | 
			
		||||
 | 
			
		||||
    // Get all languages, except English.
 | 
			
		||||
    $this->languageManager->reset();
 | 
			
		||||
    $languages = $this->languageManager->getLanguages();
 | 
			
		||||
    $language_options = [];
 | 
			
		||||
    foreach ($languages as $langcode => $language) {
 | 
			
		||||
      if (locale_is_translatable($langcode)) {
 | 
			
		||||
        $language_options[$langcode] = $language->getName();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pick the current interface language code for the filter.
 | 
			
		||||
    $default_langcode = $this->languageManager->getCurrentLanguage()->getId();
 | 
			
		||||
    if (!isset($language_options[$default_langcode])) {
 | 
			
		||||
      $available_langcodes = array_keys($language_options);
 | 
			
		||||
      $default_langcode = array_shift($available_langcodes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $filters['string'] = [
 | 
			
		||||
      'title' => $this->t('String contains'),
 | 
			
		||||
      'description' => $this->t('Leave blank to show all strings. The search is case sensitive.'),
 | 
			
		||||
      'default' => '',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $filters['langcode'] = [
 | 
			
		||||
      'title' => $this->t('Translation language'),
 | 
			
		||||
      'options' => $language_options,
 | 
			
		||||
      'default' => $default_langcode,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $filters['translation'] = [
 | 
			
		||||
      'title' => $this->t('Search in'),
 | 
			
		||||
      'options' => [
 | 
			
		||||
        'all' => $this->t('Both translated and untranslated strings'),
 | 
			
		||||
        'translated' => $this->t('Only translated strings'),
 | 
			
		||||
        'untranslated' => $this->t('Only untranslated strings'),
 | 
			
		||||
      ],
 | 
			
		||||
      'default' => 'all',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $filters['customized'] = [
 | 
			
		||||
      'title' => $this->t('Translation type'),
 | 
			
		||||
      'options' => [
 | 
			
		||||
        'all' => $this->t('All'),
 | 
			
		||||
        LOCALE_NOT_CUSTOMIZED => $this->t('Non-customized translation'),
 | 
			
		||||
        LOCALE_CUSTOMIZED => $this->t('Customized translation'),
 | 
			
		||||
      ],
 | 
			
		||||
      'states' => [
 | 
			
		||||
        'visible' => [
 | 
			
		||||
          ':input[name=translation]' => ['value' => 'translated'],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'default' => 'all',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return $filters;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										291
									
								
								web/core/modules/locale/src/Form/TranslationStatusForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								web/core/modules/locale/src/Form/TranslationStatusForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,291 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Extension\ModuleHandlerInterface;
 | 
			
		||||
use Drupal\Core\Form\FormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\State\StateInterface;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a translation status form.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
class TranslationStatusForm extends FormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('module_handler'),
 | 
			
		||||
      $container->get('state'),
 | 
			
		||||
      $container->get('datetime.time'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a TranslationStatusForm object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
 | 
			
		||||
   *   A module handler.
 | 
			
		||||
   * @param \Drupal\Core\State\StateInterface $state
 | 
			
		||||
   *   The state service.
 | 
			
		||||
   * @param \Drupal\Component\Datetime\TimeInterface $time
 | 
			
		||||
   *   The time service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(
 | 
			
		||||
    protected ModuleHandlerInterface $moduleHandler,
 | 
			
		||||
    protected StateInterface $state,
 | 
			
		||||
    protected TimeInterface $time,
 | 
			
		||||
  ) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'locale_translation_status_form';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Form builder for displaying the current translation status.
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup forms
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $languages = locale_translatable_language_list();
 | 
			
		||||
    $status = locale_translation_get_status();
 | 
			
		||||
    $options = [];
 | 
			
		||||
    $languages_update = [];
 | 
			
		||||
    $languages_not_found = [];
 | 
			
		||||
    $projects_update = [];
 | 
			
		||||
    // Prepare information about projects which have available translation
 | 
			
		||||
    // updates.
 | 
			
		||||
    if ($languages && $status) {
 | 
			
		||||
      $updates = $this->prepareUpdateData($status);
 | 
			
		||||
 | 
			
		||||
      // Build data options for the select table.
 | 
			
		||||
      foreach ($updates as $langcode => $update) {
 | 
			
		||||
        $title = $languages[$langcode]->getName();
 | 
			
		||||
        $locale_translation_update_info = ['#theme' => 'locale_translation_update_info'];
 | 
			
		||||
        foreach (['updates', 'not_found'] as $update_status) {
 | 
			
		||||
          if (isset($update[$update_status])) {
 | 
			
		||||
            $locale_translation_update_info['#' . $update_status] = $update[$update_status];
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        $options[$langcode] = [
 | 
			
		||||
          'title' => [
 | 
			
		||||
            'data' => [
 | 
			
		||||
              '#title' => $title,
 | 
			
		||||
              '#plain_text' => $title,
 | 
			
		||||
            ],
 | 
			
		||||
          ],
 | 
			
		||||
          'status' => [
 | 
			
		||||
            'class' => ['description', 'priority-low'],
 | 
			
		||||
            'data' => $locale_translation_update_info,
 | 
			
		||||
          ],
 | 
			
		||||
        ];
 | 
			
		||||
        if (!empty($update['not_found'])) {
 | 
			
		||||
          $languages_not_found[$langcode] = $langcode;
 | 
			
		||||
        }
 | 
			
		||||
        if (!empty($update['updates'])) {
 | 
			
		||||
          $languages_update[$langcode] = $langcode;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // Sort the table data on language name.
 | 
			
		||||
      uasort($options, function ($a, $b) {
 | 
			
		||||
        return strcasecmp($a['title']['data']['#title'], $b['title']['data']['#title']);
 | 
			
		||||
      });
 | 
			
		||||
      $languages_not_found = array_diff($languages_not_found, $languages_update);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $last_checked = $this->state->get('locale.translation_last_checked');
 | 
			
		||||
    $form['last_checked'] = [
 | 
			
		||||
      '#theme' => 'locale_translation_last_check',
 | 
			
		||||
      '#last' => $last_checked,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $header = [
 | 
			
		||||
      'title' => [
 | 
			
		||||
        'data' => $this->t('Language'),
 | 
			
		||||
        'class' => ['title'],
 | 
			
		||||
      ],
 | 
			
		||||
      'status' => [
 | 
			
		||||
        'data' => $this->t('Status'),
 | 
			
		||||
        'class' => ['status', 'priority-low'],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    if (!$languages) {
 | 
			
		||||
      $empty = $this->t('No translatable languages available. <a href=":add_language">Add a language</a> first.', [
 | 
			
		||||
        ':add_language' => Url::fromRoute('entity.configurable_language.collection')->toString(),
 | 
			
		||||
      ]);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($status) {
 | 
			
		||||
      $empty = $this->t('All translations up to date.');
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $empty = $this->t('No translation status available. <a href=":check">Check manually</a>.', [
 | 
			
		||||
        ':check' => Url::fromRoute('locale.check_translation')->toString(),
 | 
			
		||||
      ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The projects which require an update. Used by the _submit callback.
 | 
			
		||||
    $form['projects_update'] = [
 | 
			
		||||
      '#type' => 'value',
 | 
			
		||||
      '#value' => $projects_update,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['langcodes'] = [
 | 
			
		||||
      '#type' => 'tableselect',
 | 
			
		||||
      '#header' => $header,
 | 
			
		||||
      '#options' => $options,
 | 
			
		||||
      '#default_value' => $languages_update,
 | 
			
		||||
      '#empty' => $empty,
 | 
			
		||||
      '#js_select' => TRUE,
 | 
			
		||||
      '#multiple' => TRUE,
 | 
			
		||||
      '#required' => TRUE,
 | 
			
		||||
      '#not_found' => $languages_not_found,
 | 
			
		||||
      '#after_build' => ['locale_translation_language_table'],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['#attached']['library'][] = 'locale/drupal.locale.admin';
 | 
			
		||||
 | 
			
		||||
    $form['actions'] = ['#type' => 'actions'];
 | 
			
		||||
    if ($languages_update) {
 | 
			
		||||
      $form['actions']['submit'] = [
 | 
			
		||||
        '#type' => 'submit',
 | 
			
		||||
        '#value' => $this->t('Update translations'),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Prepare information about projects with available translation updates.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $status
 | 
			
		||||
   *   Translation update status as an array keyed by Project ID and langcode.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Translation update status as an array keyed by language code and
 | 
			
		||||
   *   translation update status.
 | 
			
		||||
   */
 | 
			
		||||
  protected function prepareUpdateData(array $status) {
 | 
			
		||||
    $updates = [];
 | 
			
		||||
 | 
			
		||||
    // @todo Calling locale_translation_build_projects() is an expensive way to
 | 
			
		||||
    //   get a module name. In follow-up issue
 | 
			
		||||
    //   https://www.drupal.org/node/1842362 the project name will be stored to
 | 
			
		||||
    //   display use, like here.
 | 
			
		||||
    $this->moduleHandler->loadInclude('locale', 'compare.inc');
 | 
			
		||||
    $project_data = locale_translation_build_projects();
 | 
			
		||||
 | 
			
		||||
    foreach ($status as $project) {
 | 
			
		||||
      foreach ($project as $langcode => $project_info) {
 | 
			
		||||
        // No translation file found for this project-language combination.
 | 
			
		||||
        if (empty($project_info->type)) {
 | 
			
		||||
          $updates[$langcode]['not_found'][] = [
 | 
			
		||||
            'name' => $project_info->name == 'drupal' ? $this->t('Drupal core') : $project_data[$project_info->name]->info['name'],
 | 
			
		||||
            'version' => $project_info->version,
 | 
			
		||||
            'info' => $this->createInfoString($project_info),
 | 
			
		||||
          ];
 | 
			
		||||
        }
 | 
			
		||||
        // Translation update found for this project-language combination.
 | 
			
		||||
        elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE) {
 | 
			
		||||
          $local = $project_info->files[LOCALE_TRANSLATION_LOCAL] ?? NULL;
 | 
			
		||||
          $remote = $project_info->files[LOCALE_TRANSLATION_REMOTE] ?? NULL;
 | 
			
		||||
          $recent = _locale_translation_source_compare($local, $remote) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $remote : $local;
 | 
			
		||||
          $updates[$langcode]['updates'][] = [
 | 
			
		||||
            'name' => $project_info->name == 'drupal' ? $this->t('Drupal core') : $project_data[$project_info->name]->info['name'],
 | 
			
		||||
            'version' => $project_info->version,
 | 
			
		||||
            'timestamp' => $recent->timestamp,
 | 
			
		||||
          ];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $updates;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides debug info for projects in case translation files are not found.
 | 
			
		||||
   *
 | 
			
		||||
   * Translations files are being fetched either from Drupal translation server
 | 
			
		||||
   * and local files or only from the local filesystem depending on the
 | 
			
		||||
   * "Translation source" setting at admin/config/regional/translate/settings.
 | 
			
		||||
   * This method will produce debug information including the respective path(s)
 | 
			
		||||
   * based on this setting.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $project_info
 | 
			
		||||
   *   An array which is the project information of the source.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The string which contains debug information.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createInfoString($project_info) {
 | 
			
		||||
    $remote_path = $project_info->files['remote']->uri ?? FALSE;
 | 
			
		||||
    $local_path = $project_info->files['local']->uri ?? FALSE;
 | 
			
		||||
 | 
			
		||||
    if (locale_translation_use_remote_source() && $remote_path && $local_path) {
 | 
			
		||||
      return $this->t('File not found at %remote_path nor at %local_path', [
 | 
			
		||||
        '%remote_path' => $remote_path,
 | 
			
		||||
        '%local_path' => $local_path,
 | 
			
		||||
      ]);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($local_path) {
 | 
			
		||||
      return $this->t('File not found at %local_path', ['%local_path' => $local_path]);
 | 
			
		||||
    }
 | 
			
		||||
    return $this->t('Translation file location could not be determined.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validateForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    // Check if a language has been selected. 'tableselect' doesn't.
 | 
			
		||||
    if (!array_filter($form_state->getValue('langcodes'))) {
 | 
			
		||||
      $form_state->setErrorByName('', $this->t('Select a language to update.'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->moduleHandler->loadInclude('locale', 'fetch.inc');
 | 
			
		||||
    $this->moduleHandler->loadInclude('locale', 'bulk.inc');
 | 
			
		||||
 | 
			
		||||
    $langcodes = array_filter($form_state->getValue('langcodes'));
 | 
			
		||||
    $projects = array_filter($form_state->getValue('projects_update'));
 | 
			
		||||
 | 
			
		||||
    // Set the translation import options. This determines if existing
 | 
			
		||||
    // translations will be overwritten by imported strings.
 | 
			
		||||
    $options = _locale_translation_default_update_options();
 | 
			
		||||
 | 
			
		||||
    // If the status was updated recently we can immediately start fetching the
 | 
			
		||||
    // translation updates. If the status is expired we clear it and run a batch
 | 
			
		||||
    // to update the status and then fetch the translation updates.
 | 
			
		||||
    $last_checked = $this->state->get('locale.translation_last_checked');
 | 
			
		||||
    if ($last_checked < $this->time->getRequestTime() - LOCALE_TRANSLATION_STATUS_TTL) {
 | 
			
		||||
      locale_translation_clear_status();
 | 
			
		||||
      $batch = locale_translation_batch_update_build([], $langcodes, $options);
 | 
			
		||||
      batch_set($batch);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // Set a batch to download and import translations.
 | 
			
		||||
      $batch = locale_translation_batch_fetch_build($projects, $langcodes, $options);
 | 
			
		||||
      batch_set($batch);
 | 
			
		||||
      // Set a batch to update configuration as well.
 | 
			
		||||
      if ($batch = locale_config_batch_update_components($options, $langcodes)) {
 | 
			
		||||
        batch_set($batch);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										97
									
								
								web/core/modules/locale/src/Gettext.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								web/core/modules/locale/src/Gettext.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,97 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Gettext\PoStreamReader;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Static class providing Drupal specific Gettext functionality.
 | 
			
		||||
 *
 | 
			
		||||
 * The operations are related to pumping data from a source to a destination,
 | 
			
		||||
 * for example:
 | 
			
		||||
 * - Remote files http://*.po to memory
 | 
			
		||||
 * - File public://*.po to database
 | 
			
		||||
 */
 | 
			
		||||
class Gettext {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reads the given PO files into the database.
 | 
			
		||||
   *
 | 
			
		||||
   * @param object $file
 | 
			
		||||
   *   File object with a URI property pointing at the file's path.
 | 
			
		||||
   *   - "langcode": The language the strings will be added to.
 | 
			
		||||
   *   - "uri": File URI.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   An array with options that can have the following elements:
 | 
			
		||||
   *   - 'overwrite_options': Overwrite options array as defined in
 | 
			
		||||
   *     Drupal\locale\PoDatabaseWriter. Optional, defaults to an empty array.
 | 
			
		||||
   *   - 'customized': Flag indicating whether the strings imported from $file
 | 
			
		||||
   *     are customized translations or come from a community source. Use
 | 
			
		||||
   *     LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. Optional, defaults to
 | 
			
		||||
   *     LOCALE_NOT_CUSTOMIZED.
 | 
			
		||||
   *   - 'seek': Specifies from which position in the file should the reader
 | 
			
		||||
   *     start reading the next items. Optional, defaults to 0.
 | 
			
		||||
   *   - 'items': Specifies the number of items to read. Optional, defaults to
 | 
			
		||||
   *     -1, which means that all the items from the stream will be read.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Report array as defined in Drupal\locale\PoDatabaseWriter.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\locale\PoDatabaseWriter
 | 
			
		||||
   */
 | 
			
		||||
  public static function fileToDatabase($file, $options) {
 | 
			
		||||
    // Add the default values to the options array.
 | 
			
		||||
    $options += [
 | 
			
		||||
      'overwrite_options' => [],
 | 
			
		||||
      'customized' => LOCALE_NOT_CUSTOMIZED,
 | 
			
		||||
      'items' => -1,
 | 
			
		||||
      'seek' => 0,
 | 
			
		||||
    ];
 | 
			
		||||
    // Instantiate and initialize the stream reader for this file.
 | 
			
		||||
    $reader = new PoStreamReader();
 | 
			
		||||
    $reader->setLangcode($file->langcode);
 | 
			
		||||
    $reader->setURI($file->uri);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $reader->open();
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Exception $exception) {
 | 
			
		||||
      throw $exception;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $header = $reader->getHeader();
 | 
			
		||||
    if (!$header) {
 | 
			
		||||
      throw new \Exception('Missing or malformed header.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initialize the database writer.
 | 
			
		||||
    $writer = new PoDatabaseWriter();
 | 
			
		||||
    $writer->setLangcode($file->langcode);
 | 
			
		||||
    $writer_options = [
 | 
			
		||||
      'overwrite_options' => $options['overwrite_options'],
 | 
			
		||||
      'customized' => $options['customized'],
 | 
			
		||||
    ];
 | 
			
		||||
    $writer->setOptions($writer_options);
 | 
			
		||||
    $writer->setHeader($header);
 | 
			
		||||
 | 
			
		||||
    // Attempt to pipe all items from the file to the database.
 | 
			
		||||
    try {
 | 
			
		||||
      if ($options['seek']) {
 | 
			
		||||
        $reader->setSeek($options['seek']);
 | 
			
		||||
      }
 | 
			
		||||
      $writer->writeItems($reader, $options['items']);
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Exception $exception) {
 | 
			
		||||
      throw $exception;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Report back with an array of status information.
 | 
			
		||||
    $report = $writer->getReport();
 | 
			
		||||
 | 
			
		||||
    // Add the seek position to the report. This is useful for the batch
 | 
			
		||||
    // operation.
 | 
			
		||||
    $report['seek'] = $reader->getSeek();
 | 
			
		||||
    return $report;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										361
									
								
								web/core/modules/locale/src/Hook/LocaleHooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										361
									
								
								web/core/modules/locale/src/Hook/LocaleHooks.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,361 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Link;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Language\LanguageInterface;
 | 
			
		||||
use Drupal\Core\Asset\AttachedAssetsInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\language\ConfigurableLanguageInterface;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for locale.
 | 
			
		||||
 */
 | 
			
		||||
class LocaleHooks {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_help().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('help')]
 | 
			
		||||
  public function help($route_name, RouteMatchInterface $route_match): ?string {
 | 
			
		||||
    switch ($route_name) {
 | 
			
		||||
      case 'help.page.locale':
 | 
			
		||||
        $output = '';
 | 
			
		||||
        $output .= '<h2>' . $this->t('About') . '</h2>';
 | 
			
		||||
        $output .= '<p>' . $this->t('The Interface Translation module allows you to translate interface text (<em>strings</em>) into different languages, and to switch between them for the display of interface text. It uses the functionality provided by the <a href=":language">Language module</a>. For more information, see the <a href=":doc-url">online documentation for the Interface Translation module</a>.', [
 | 
			
		||||
          ':doc-url' => 'https://www.drupal.org/documentation/modules/locale/',
 | 
			
		||||
          ':language' => Url::fromRoute('help.page', [
 | 
			
		||||
            'name' => 'language',
 | 
			
		||||
          ])->toString(),
 | 
			
		||||
        ]) . '</p>';
 | 
			
		||||
        $output .= '<h2>' . $this->t('Uses') . '</h2>';
 | 
			
		||||
        $output .= '<dl>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('Importing translation files') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('Translation files with translated interface text are imported automatically when languages are added on the <a href=":languages">Languages</a> page, or when modules or themes are installed. On the <a href=":locale-settings">Interface translation settings</a> page, the <em>Translation source</em> can be restricted to local files only, or to include the <a href=":server">Drupal translation server</a>. Although modules and themes may not be fully translated in all languages, new translations become available frequently. You can specify whether and how often to check for translation file updates and whether to overwrite existing translations on the <a href=":locale-settings">Interface translation settings</a> page. You can also manually import a translation file on the <a href=":import">Interface translation import</a> page.', [
 | 
			
		||||
          ':import' => Url::fromRoute('locale.translate_import')->toString(),
 | 
			
		||||
          ':locale-settings' => Url::fromRoute('locale.settings')->toString(),
 | 
			
		||||
          ':languages' => Url::fromRoute('entity.configurable_language.collection')->toString(),
 | 
			
		||||
          ':server' => 'https://localize.drupal.org',
 | 
			
		||||
        ]) . '</dd>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('Checking the translation status') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('You can check how much of the interface on your site is translated into which language on the <a href=":languages">Languages</a> page. On the <a href=":translation-updates">Available translation updates</a> page, you can check whether interface translation updates are available on the <a href=":server">Drupal translation server</a>.', [
 | 
			
		||||
          ':languages' => Url::fromRoute('entity.configurable_language.collection')->toString(),
 | 
			
		||||
          ':translation-updates' => Url::fromRoute('locale.translate_status')->toString(),
 | 
			
		||||
          ':server' => 'https://localize.drupal.org',
 | 
			
		||||
        ]) . '<dd>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('Translating individual strings') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('You can translate individual strings directly on the <a href=":translate">User interface translation</a> page, or download the currently-used translation file for a specific language on the <a href=":export">Interface translation export</a> page. Once you have edited the translation file, you can then import it again on the <a href=":import">Interface translation import</a> page.', [
 | 
			
		||||
          ':translate' => Url::fromRoute('locale.translate_page')->toString(),
 | 
			
		||||
          ':export' => Url::fromRoute('locale.translate_export')->toString(),
 | 
			
		||||
          ':import' => Url::fromRoute('locale.translate_import')->toString(),
 | 
			
		||||
        ]) . '</dd>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('Overriding default English strings') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('If translation is enabled for English, you can <em>override</em> the default English interface text strings in your site with other English text strings on the <a href=":translate">User interface translation</a> page. Translation is off by default for English, but you can turn it on by visiting the <em>Edit language</em> page for <em>English</em> from the <a href=":languages">Languages</a> page.', [
 | 
			
		||||
          ':translate' => Url::fromRoute('locale.translate_page')->toString(),
 | 
			
		||||
          ':languages' => Url::fromRoute('entity.configurable_language.collection')->toString(),
 | 
			
		||||
        ]) . '</dd>';
 | 
			
		||||
        $output .= '</dl>';
 | 
			
		||||
        return $output;
 | 
			
		||||
 | 
			
		||||
      case 'entity.configurable_language.collection':
 | 
			
		||||
        return '<p>' . $this->t('Interface translations are automatically imported when a language is added, or when new modules or themes are installed. The report <a href=":update">Available translation updates</a> shows the status. Interface text can be customized in the <a href=":translate">user interface translation</a> page.', [
 | 
			
		||||
          ':update' => Url::fromRoute('locale.translate_status')->toString(),
 | 
			
		||||
          ':translate' => Url::fromRoute('locale.translate_page')->toString(),
 | 
			
		||||
        ]) . '</p>';
 | 
			
		||||
 | 
			
		||||
      case 'locale.translate_page':
 | 
			
		||||
        $output = '<p>' . $this->t('This page allows a translator to search for specific translated and untranslated strings, and is used when creating or editing translations. (Note: Because translation tasks involve many strings, it may be more convenient to <a title="User interface translation export" href=":export">export</a> strings for offline editing in a desktop Gettext translation editor.) Searches may be limited to strings in a specific language.', [':export' => Url::fromRoute('locale.translate_export')->toString()]) . '</p>';
 | 
			
		||||
        return $output;
 | 
			
		||||
 | 
			
		||||
      case 'locale.translate_import':
 | 
			
		||||
        $output = '<p>' . $this->t('Translation files are automatically downloaded and imported when <a title="Languages" href=":language">languages</a> are added, or when modules or themes are installed.', [
 | 
			
		||||
          ':language' => Url::fromRoute('entity.configurable_language.collection')->toString(),
 | 
			
		||||
        ]) . '</p>';
 | 
			
		||||
        $output .= '<p>' . $this->t('This page allows translators to manually import translated strings contained in a Gettext Portable Object (.po) file. Manual import may be used for customized translations or for the translation of custom modules and themes. To customize translations you can download a translation file from the <a href=":url">Drupal translation server</a> or <a title="User interface translation export" href=":export">export</a> translations from the site, customize the translations using a Gettext translation editor, and import the result using this page.', [
 | 
			
		||||
          ':url' => 'https://localize.drupal.org',
 | 
			
		||||
          ':export' => Url::fromRoute('locale.translate_export')->toString(),
 | 
			
		||||
        ]) . '</p>';
 | 
			
		||||
        $output .= '<p>' . $this->t('Note that importing large .po files may take several minutes.') . '</p>';
 | 
			
		||||
        return $output;
 | 
			
		||||
 | 
			
		||||
      case 'locale.translate_export':
 | 
			
		||||
        return '<p>' . $this->t('This page exports the translated strings used by your site. An export file may be in Gettext Portable Object (<em>.po</em>) form, which includes both the original string and the translation (used to share translations with others), or in Gettext Portable Object Template (<em>.pot</em>) form, which includes the original strings only (used to create new translations with a Gettext translation editor).') . '</p>';
 | 
			
		||||
    }
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_theme().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('theme')]
 | 
			
		||||
  public function theme() : array {
 | 
			
		||||
    return [
 | 
			
		||||
      'locale_translation_last_check' => [
 | 
			
		||||
        'variables' => [
 | 
			
		||||
          'last' => NULL,
 | 
			
		||||
        ],
 | 
			
		||||
        'file' => 'locale.pages.inc',
 | 
			
		||||
      ],
 | 
			
		||||
      'locale_translation_update_info' => [
 | 
			
		||||
        'variables' => [
 | 
			
		||||
          'updates' => [],
 | 
			
		||||
          'not_found' => [],
 | 
			
		||||
        ],
 | 
			
		||||
        'file' => 'locale.pages.inc',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_ENTITY_TYPE_insert() for 'configurable_language'.
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('configurable_language_insert')]
 | 
			
		||||
  public function configurableLanguageInsert(ConfigurableLanguageInterface $language): void {
 | 
			
		||||
    // @todo move these two cache clears out. See
 | 
			
		||||
    //   https://www.drupal.org/node/1293252.
 | 
			
		||||
    // Changing the language settings impacts the interface: clear render cache.
 | 
			
		||||
    \Drupal::cache('render')->deleteAll();
 | 
			
		||||
    // Force JavaScript translation file re-creation for the new language.
 | 
			
		||||
    _locale_invalidate_js($language->id());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_ENTITY_TYPE_update() for 'configurable_language'.
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('configurable_language_update')]
 | 
			
		||||
  public function configurableLanguageUpdate(ConfigurableLanguageInterface $language): void {
 | 
			
		||||
    // @todo move these two cache clears out. See
 | 
			
		||||
    //   https://www.drupal.org/node/1293252.
 | 
			
		||||
    // Changing the language settings impacts the interface: clear render cache.
 | 
			
		||||
    \Drupal::cache('render')->deleteAll();
 | 
			
		||||
    // Force JavaScript translation file re-creation for the modified language.
 | 
			
		||||
    _locale_invalidate_js($language->id());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_ENTITY_TYPE_delete() for 'configurable_language'.
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('configurable_language_delete')]
 | 
			
		||||
  public function configurableLanguageDelete(ConfigurableLanguageInterface $language): void {
 | 
			
		||||
    // Remove translations.
 | 
			
		||||
    \Drupal::service('locale.storage')->deleteTranslations(['language' => $language->id()]);
 | 
			
		||||
    // Remove interface translation files.
 | 
			
		||||
    \Drupal::moduleHandler()->loadInclude('locale', 'inc', 'locale.bulk');
 | 
			
		||||
    locale_translate_delete_translation_files([], [$language->id()]);
 | 
			
		||||
    // Remove translated configuration objects.
 | 
			
		||||
    \Drupal::service('locale.config_manager')->deleteLanguageTranslations($language->id());
 | 
			
		||||
    // Changing the language settings impacts the interface:
 | 
			
		||||
    _locale_invalidate_js($language->id());
 | 
			
		||||
    \Drupal::cache('render')->deleteAll();
 | 
			
		||||
    // Clear locale translation caches.
 | 
			
		||||
    locale_translation_status_delete_languages([$language->id()]);
 | 
			
		||||
    \Drupal::cache()->delete('locale:' . $language->id());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_modules_installed().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('modules_installed')]
 | 
			
		||||
  public function modulesInstalled($modules): void {
 | 
			
		||||
    $components['module'] = $modules;
 | 
			
		||||
    locale_system_update($components);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_module_preuninstall().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('module_preuninstall')]
 | 
			
		||||
  public function modulePreuninstall($module): void {
 | 
			
		||||
    $components['module'] = [$module];
 | 
			
		||||
    locale_system_remove($components);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_themes_installed().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('themes_installed')]
 | 
			
		||||
  public function themesInstalled($themes): void {
 | 
			
		||||
    $components['theme'] = $themes;
 | 
			
		||||
    locale_system_update($components);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_themes_uninstalled().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('themes_uninstalled')]
 | 
			
		||||
  public function themesUninstalled($themes): void {
 | 
			
		||||
    $components['theme'] = $themes;
 | 
			
		||||
    locale_system_remove($components);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_cron().
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\locale\Plugin\QueueWorker\LocaleTranslation
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('cron')]
 | 
			
		||||
  public function cron(): void {
 | 
			
		||||
    // Update translations only when an update frequency was set by the admin
 | 
			
		||||
    // and a translatable language was set.
 | 
			
		||||
    // Update tasks are added to the queue here but processed by Drupal's cron.
 | 
			
		||||
    if (\Drupal::config('locale.settings')->get('translation.update_interval_days') && locale_translatable_language_list()) {
 | 
			
		||||
      \Drupal::moduleHandler()->loadInclude('locale', 'inc', 'locale.translation');
 | 
			
		||||
      locale_cron_fill_queue();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_cache_flush().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('cache_flush')]
 | 
			
		||||
  public function cacheFlush(): void {
 | 
			
		||||
    \Drupal::state()->delete('system.javascript_parsed');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_js_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('js_alter')]
 | 
			
		||||
  public function jsAlter(&$javascript, AttachedAssetsInterface $assets, LanguageInterface $language): void {
 | 
			
		||||
    $files = [];
 | 
			
		||||
    foreach ($javascript as $item) {
 | 
			
		||||
      if (isset($item['type']) && $item['type'] == 'file') {
 | 
			
		||||
        // Ignore the JS translation placeholder file.
 | 
			
		||||
        if ($item['data'] === 'core/modules/locale/locale.translation.js') {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        $files[] = $item['data'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Replace the placeholder file with the actual JS translation file.
 | 
			
		||||
    $placeholder_file = 'core/modules/locale/locale.translation.js';
 | 
			
		||||
    if (isset($javascript[$placeholder_file])) {
 | 
			
		||||
      if ($translation_file = locale_js_translate($files, $language)) {
 | 
			
		||||
        $js_translation_asset =& $javascript[$placeholder_file];
 | 
			
		||||
        $js_translation_asset['data'] = $translation_file;
 | 
			
		||||
        // @todo Remove this when https://www.drupal.org/node/1945262 lands.
 | 
			
		||||
        // Decrease the weight so that the translation file is loaded first.
 | 
			
		||||
        $js_translation_asset['weight'] = $javascript['core/misc/drupal.js']['weight'] - 0.001;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // If no translation file exists, then remove the placeholder JS asset.
 | 
			
		||||
        unset($javascript[$placeholder_file]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_library_info_alter().
 | 
			
		||||
   *
 | 
			
		||||
   * Provides language support.
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('library_info_alter')]
 | 
			
		||||
  public function libraryInfoAlter(array &$libraries, $module): void {
 | 
			
		||||
    // When the locale module is enabled, we update the core/drupal library to
 | 
			
		||||
    // have a dependency on the locale/translations library, which provides
 | 
			
		||||
    // window.drupalTranslations, containing the translations for all strings in
 | 
			
		||||
    // JavaScript assets in the current language.
 | 
			
		||||
    // @see locale_js_alter()
 | 
			
		||||
    if ($module === 'core' && isset($libraries['drupal'])) {
 | 
			
		||||
      $libraries['drupal']['dependencies'][] = 'locale/translations';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_form_FORM_ID_alter() for language_admin_overview_form().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('form_language_admin_overview_form_alter')]
 | 
			
		||||
  public function formLanguageAdminOverviewFormAlter(&$form, FormStateInterface $form_state) : void {
 | 
			
		||||
    $languages = $form['languages']['#languages'];
 | 
			
		||||
    $total_strings = \Drupal::service('locale.storage')->countStrings();
 | 
			
		||||
    $stats = array_fill_keys(array_keys($languages), []);
 | 
			
		||||
    // If we have source strings, count translations and calculate progress.
 | 
			
		||||
    if (!empty($total_strings)) {
 | 
			
		||||
      $translations = \Drupal::service('locale.storage')->countTranslations();
 | 
			
		||||
      foreach ($translations as $langcode => $translated) {
 | 
			
		||||
        $stats[$langcode]['translated'] = $translated;
 | 
			
		||||
        if ($translated > 0) {
 | 
			
		||||
          $stats[$langcode]['ratio'] = round($translated / $total_strings * 100, 2);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    array_splice($form['languages']['#header'], -1, 0, ['translation-interface' => $this->t('Interface translation')]);
 | 
			
		||||
    foreach ($languages as $langcode => $language) {
 | 
			
		||||
      $stats[$langcode] += ['translated' => 0, 'ratio' => 0];
 | 
			
		||||
      if (!$language->isLocked() && locale_is_translatable($langcode)) {
 | 
			
		||||
        $form['languages'][$langcode]['locale_statistics'] = Link::fromTextAndUrl($this->t('@translated/@total (@ratio%)', [
 | 
			
		||||
          '@translated' => $stats[$langcode]['translated'],
 | 
			
		||||
          '@total' => $total_strings,
 | 
			
		||||
          '@ratio' => $stats[$langcode]['ratio'],
 | 
			
		||||
        ]), Url::fromRoute('locale.translate_page', [], ['query' => ['langcode' => $langcode]]))->toRenderable();
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $form['languages'][$langcode]['locale_statistics'] = ['#markup' => $this->t('not applicable')];
 | 
			
		||||
      }
 | 
			
		||||
      // #type = link doesn't work with #weight on table.
 | 
			
		||||
      // reset and set it back after locale_statistics to get it at the right
 | 
			
		||||
      // end.
 | 
			
		||||
      $operations = $form['languages'][$langcode]['operations'];
 | 
			
		||||
      unset($form['languages'][$langcode]['operations']);
 | 
			
		||||
      $form['languages'][$langcode]['operations'] = $operations;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_form_FORM_ID_alter() for language_admin_add_form().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('form_language_admin_add_form_alter')]
 | 
			
		||||
  public function formLanguageAdminAddFormAlter(&$form, FormStateInterface $form_state) : void {
 | 
			
		||||
    $form['predefined_submit']['#submit'][] = 'locale_form_language_admin_add_form_alter_submit';
 | 
			
		||||
    $form['custom_language']['submit']['#submit'][] = 'locale_form_language_admin_add_form_alter_submit';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_form_FORM_ID_alter() for language_admin_edit_form().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('form_language_admin_edit_form_alter')]
 | 
			
		||||
  public function formLanguageAdminEditFormAlter(&$form, FormStateInterface $form_state) : void {
 | 
			
		||||
    /** @var \Drupal\language\ConfigurableLanguageInterface $language */
 | 
			
		||||
    $language = $form_state->getFormObject()->getEntity();
 | 
			
		||||
    if ($language->id() == 'en') {
 | 
			
		||||
      $form['locale_translate_english'] = [
 | 
			
		||||
        '#title' => $this->t('Enable interface translation to English'),
 | 
			
		||||
        '#type' => 'checkbox',
 | 
			
		||||
        '#default_value' => \Drupal::configFactory()->getEditable('locale.settings')->get('translate_english'),
 | 
			
		||||
      ];
 | 
			
		||||
      $form['actions']['submit']['#submit'][] = 'locale_form_language_admin_edit_form_alter_submit';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_form_FORM_ID_alter() for system_file_system_settings().
 | 
			
		||||
   *
 | 
			
		||||
   * Add interface translation directory setting to directories configuration.
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('form_system_file_system_settings_alter')]
 | 
			
		||||
  public function formSystemFileSystemSettingsAlter(&$form, FormStateInterface $form_state) : void {
 | 
			
		||||
    $form['translation_path'] = [
 | 
			
		||||
      '#type' => 'textfield',
 | 
			
		||||
      '#title' => $this->t('Interface translations directory'),
 | 
			
		||||
      '#default_value' => \Drupal::configFactory()->getEditable('locale.settings')->get('translation.path'),
 | 
			
		||||
      '#maxlength' => 255,
 | 
			
		||||
      '#description' => $this->t('A local file system path where interface translation files will be stored.'),
 | 
			
		||||
      '#required' => TRUE,
 | 
			
		||||
      '#after_build' => [
 | 
			
		||||
        'system_check_directory',
 | 
			
		||||
      ],
 | 
			
		||||
      '#weight' => 10,
 | 
			
		||||
    ];
 | 
			
		||||
    if ($form['file_default_scheme']) {
 | 
			
		||||
      $form['file_default_scheme']['#weight'] = 20;
 | 
			
		||||
    }
 | 
			
		||||
    $form['#submit'][] = 'locale_system_file_system_settings_submit';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								web/core/modules/locale/src/Hook/LocaleRequirements.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								web/core/modules/locale/src/Hook/LocaleRequirements.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\Requirement\RequirementSeverity;
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
use Drupal\Core\Link;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Requirements for the Locale module.
 | 
			
		||||
 */
 | 
			
		||||
class LocaleRequirements {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_runtime_requirements().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('runtime_requirements')]
 | 
			
		||||
  public function runtime(): array {
 | 
			
		||||
    $requirements = [];
 | 
			
		||||
    $available_updates = [];
 | 
			
		||||
    $untranslated = [];
 | 
			
		||||
    $languages = locale_translatable_language_list();
 | 
			
		||||
 | 
			
		||||
    if ($languages) {
 | 
			
		||||
      // Determine the status of the translation updates per language.
 | 
			
		||||
      $status = locale_translation_get_status();
 | 
			
		||||
      if ($status) {
 | 
			
		||||
        foreach ($status as $project) {
 | 
			
		||||
          foreach ($project as $langcode => $project_info) {
 | 
			
		||||
            if (empty($project_info->type)) {
 | 
			
		||||
              $untranslated[$langcode] = $languages[$langcode]->getName();
 | 
			
		||||
            }
 | 
			
		||||
            elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE) {
 | 
			
		||||
              $available_updates[$langcode] = $languages[$langcode]->getName();
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($available_updates || $untranslated) {
 | 
			
		||||
          if ($available_updates) {
 | 
			
		||||
            $requirements['locale_translation'] = [
 | 
			
		||||
              'title' => $this->t('Translation update status'),
 | 
			
		||||
              'value' => Link::fromTextAndUrl($this->t('Updates available'), Url::fromRoute('locale.translate_status'))->toString(),
 | 
			
		||||
              'severity' => RequirementSeverity::Warning,
 | 
			
		||||
              'description' => $this->t('Updates available for: @languages. See the <a href=":updates">Available translation updates</a> page for more information.', ['@languages' => implode(', ', $available_updates), ':updates' => Url::fromRoute('locale.translate_status')->toString()]),
 | 
			
		||||
            ];
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            $requirements['locale_translation'] = [
 | 
			
		||||
              'title' => $this->t('Translation update status'),
 | 
			
		||||
              'value' => $this->t('Missing translations'),
 | 
			
		||||
              'severity' => RequirementSeverity::Info,
 | 
			
		||||
              'description' => $this->t('Missing translations for: @languages. See the <a href=":updates">Available translation updates</a> page for more information.', ['@languages' => implode(', ', $untranslated), ':updates' => Url::fromRoute('locale.translate_status')->toString()]),
 | 
			
		||||
            ];
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          $requirements['locale_translation'] = [
 | 
			
		||||
            'title' => $this->t('Translation update status'),
 | 
			
		||||
            'value' => $this->t('Up to date'),
 | 
			
		||||
            'severity' => RequirementSeverity::OK,
 | 
			
		||||
          ];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $requirements['locale_translation'] = [
 | 
			
		||||
          'title' => $this->t('Translation update status'),
 | 
			
		||||
          'value' => Link::fromTextAndUrl($this->t('Can not determine status'), Url::fromRoute('locale.translate_status'))->toString(),
 | 
			
		||||
          'severity' => RequirementSeverity::Warning,
 | 
			
		||||
          'description' => $this->t('No translation status is available. See the <a href=":updates">Available translation updates</a> page for more information.', [':updates' => Url::fromRoute('locale.translate_status')->toString()]),
 | 
			
		||||
        ];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $requirements;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								web/core/modules/locale/src/Hook/LocaleThemeHooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								web/core/modules/locale/src/Hook/LocaleThemeHooks.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
use Drupal\Core\Language\LanguageInterface;
 | 
			
		||||
use Drupal\Core\Language\LanguageManagerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for locale.
 | 
			
		||||
 */
 | 
			
		||||
class LocaleThemeHooks {
 | 
			
		||||
 | 
			
		||||
  public function __construct(
 | 
			
		||||
    protected readonly LanguageManagerInterface $languageManager,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_preprocess_HOOK() for node templates.
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('preprocess_node')]
 | 
			
		||||
  public function preprocessNode(&$variables): void {
 | 
			
		||||
    /** @var \Drupal\node\NodeInterface $node */
 | 
			
		||||
    $node = $variables['node'];
 | 
			
		||||
    if ($node->language()->getId() != LanguageInterface::LANGCODE_NOT_SPECIFIED) {
 | 
			
		||||
      $interface_language = $this->languageManager->getCurrentLanguage();
 | 
			
		||||
 | 
			
		||||
      $node_language = $node->language();
 | 
			
		||||
      if ($node_language->getId() != $interface_language->getId()) {
 | 
			
		||||
        // If the node language was different from the page language, we should
 | 
			
		||||
        // add markup to identify the language. Otherwise the page language is
 | 
			
		||||
        // inherited.
 | 
			
		||||
        $variables['attributes']['lang'] = $node_language->getId();
 | 
			
		||||
        if ($node_language->getDirection() != $interface_language->getDirection()) {
 | 
			
		||||
          // If text direction is different form the page's text direction, add
 | 
			
		||||
          // direction information as well.
 | 
			
		||||
          $variables['attributes']['dir'] = $node_language->getDirection();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										689
									
								
								web/core/modules/locale/src/LocaleConfigManager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										689
									
								
								web/core/modules/locale/src/LocaleConfigManager.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,689 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\NestedArray;
 | 
			
		||||
use Drupal\Core\Config\ConfigFactoryInterface;
 | 
			
		||||
use Drupal\Core\Config\ConfigManagerInterface;
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
use Drupal\Core\Config\TypedConfigManagerInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\Core\TypedData\TraversableTypedDataInterface;
 | 
			
		||||
use Drupal\Core\TypedData\TypedDataInterface;
 | 
			
		||||
use Drupal\language\ConfigurableLanguageManagerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Manages configuration supported in part by interface translation.
 | 
			
		||||
 *
 | 
			
		||||
 * This manager is responsible to update configuration overrides and active
 | 
			
		||||
 * translations when interface translation data changes. This allows Drupal to
 | 
			
		||||
 * translate user roles, views, blocks, etc. after Drupal has been installed
 | 
			
		||||
 * using the locale module's storage. When translations change in locale,
 | 
			
		||||
 * LocaleConfigManager::updateConfigTranslations() is invoked to update the
 | 
			
		||||
 * corresponding storage of the translation in the original config object or an
 | 
			
		||||
 * override.
 | 
			
		||||
 *
 | 
			
		||||
 * In turn when translated configuration or configuration language overrides are
 | 
			
		||||
 * changed, it is the responsibility of LocaleConfigSubscriber to update locale
 | 
			
		||||
 * storage.
 | 
			
		||||
 *
 | 
			
		||||
 * By design locale module only deals with sources in English.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\locale\LocaleConfigSubscriber
 | 
			
		||||
 */
 | 
			
		||||
class LocaleConfigManager {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The storage instance for reading configuration data.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $configStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The string storage for reading and writing translations.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\locale\StringStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $localeStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Array with preloaded string translations.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $translations;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The configuration factory.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ConfigFactoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $configFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The language manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\language\ConfigurableLanguageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The typed config manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\TypedConfigManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $typedConfigManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Whether or not configuration translations are being updated from locale.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   *
 | 
			
		||||
   * @see self::isUpdatingFromLocale()
 | 
			
		||||
   */
 | 
			
		||||
  protected $isUpdatingFromLocale = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The locale default config storage instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\locale\LocaleDefaultConfigStorage
 | 
			
		||||
   */
 | 
			
		||||
  protected $defaultConfigStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The configuration manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ConfigManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $configManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new typed configuration manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageInterface $config_storage
 | 
			
		||||
   *   The storage object to use for reading configuration data.
 | 
			
		||||
   * @param \Drupal\locale\StringStorageInterface $locale_storage
 | 
			
		||||
   *   The locale storage to use for reading string translations.
 | 
			
		||||
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
 | 
			
		||||
   *   The configuration factory.
 | 
			
		||||
   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
 | 
			
		||||
   *   The typed configuration manager.
 | 
			
		||||
   * @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
 | 
			
		||||
   *   The language manager.
 | 
			
		||||
   * @param \Drupal\locale\LocaleDefaultConfigStorage $default_config_storage
 | 
			
		||||
   *   The locale default configuration storage.
 | 
			
		||||
   * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
 | 
			
		||||
   *   The configuration manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(StorageInterface $config_storage, StringStorageInterface $locale_storage, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config, ConfigurableLanguageManagerInterface $language_manager, LocaleDefaultConfigStorage $default_config_storage, ConfigManagerInterface $config_manager) {
 | 
			
		||||
    $this->configStorage = $config_storage;
 | 
			
		||||
    $this->localeStorage = $locale_storage;
 | 
			
		||||
    $this->configFactory = $config_factory;
 | 
			
		||||
    $this->typedConfigManager = $typed_config;
 | 
			
		||||
    $this->languageManager = $language_manager;
 | 
			
		||||
    $this->defaultConfigStorage = $default_config_storage;
 | 
			
		||||
    $this->configManager = $config_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets array of translated strings for Locale translatable configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Configuration object name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Array of Locale translatable elements of the default configuration in
 | 
			
		||||
   *   $name.
 | 
			
		||||
   */
 | 
			
		||||
  public function getTranslatableDefaultConfig($name) {
 | 
			
		||||
    if ($this->isSupported($name)) {
 | 
			
		||||
      // Create typed configuration wrapper based on install storage data.
 | 
			
		||||
      $data = $this->defaultConfigStorage->read($name);
 | 
			
		||||
      $typed_config = $this->typedConfigManager->createFromNameAndData($name, $data);
 | 
			
		||||
      if ($typed_config instanceof TraversableTypedDataInterface) {
 | 
			
		||||
        return $this->getTranslatableData($typed_config);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets translatable configuration data for a typed configuration element.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\TypedData\TypedDataInterface $element
 | 
			
		||||
   *   Typed configuration element.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array|\Drupal\Core\StringTranslation\TranslatableMarkup
 | 
			
		||||
   *   A nested array matching the exact structure under $element with only the
 | 
			
		||||
   *   elements that are translatable wrapped into a TranslatableMarkup. If the
 | 
			
		||||
   *   provided $element is not traversable, the return value is a single
 | 
			
		||||
   *   TranslatableMarkup.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getTranslatableData(TypedDataInterface $element) {
 | 
			
		||||
    $translatable = [];
 | 
			
		||||
    if ($element instanceof TraversableTypedDataInterface) {
 | 
			
		||||
      foreach ($element as $key => $property) {
 | 
			
		||||
        $value = $this->getTranslatableData($property);
 | 
			
		||||
        if (!empty($value)) {
 | 
			
		||||
          $translatable[$key] = $value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // Something is only translatable by Locale if there is a string in the
 | 
			
		||||
      // first place.
 | 
			
		||||
      $value = $element->getValue();
 | 
			
		||||
      $definition = $element->getDataDefinition();
 | 
			
		||||
      if (!empty($definition['translatable']) && $value !== '' && $value !== NULL) {
 | 
			
		||||
        $options = [];
 | 
			
		||||
        if (isset($definition['translation context'])) {
 | 
			
		||||
          $options['context'] = $definition['translation context'];
 | 
			
		||||
        }
 | 
			
		||||
        // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
 | 
			
		||||
        return new TranslatableMarkup($value, [], $options);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $translatable;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Process the translatable data array with a given language.
 | 
			
		||||
   *
 | 
			
		||||
   * If the given language is translatable, will return the translated copy
 | 
			
		||||
   * which will only contain strings that had translations. If the given
 | 
			
		||||
   * language is English and is not translatable, will return a simplified
 | 
			
		||||
   * array of the English source strings only.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The configuration name.
 | 
			
		||||
   * @param array $active
 | 
			
		||||
   *   The active configuration data.
 | 
			
		||||
   * @param array|\Drupal\Core\StringTranslation\TranslatableMarkup[] $translatable
 | 
			
		||||
   *   The translatable array structure. A nested array matching the exact
 | 
			
		||||
   *   structure under of the default configuration for $name with only the
 | 
			
		||||
   *   elements that are translatable wrapped into a TranslatableMarkup.
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   The language code to process the array with.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Processed translatable data array. Will only contain translations
 | 
			
		||||
   *   different from source strings or in case of untranslatable English, the
 | 
			
		||||
   *   source strings themselves.
 | 
			
		||||
   *
 | 
			
		||||
   * @see self::getTranslatableData()
 | 
			
		||||
   */
 | 
			
		||||
  protected function processTranslatableData($name, array $active, array $translatable, $langcode) {
 | 
			
		||||
    $translated = [];
 | 
			
		||||
    foreach ($translatable as $key => $item) {
 | 
			
		||||
      if (!isset($active[$key])) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (is_array($item)) {
 | 
			
		||||
        // Only add this key if there was a translated value underneath.
 | 
			
		||||
        $value = $this->processTranslatableData($name, $active[$key], $item, $langcode);
 | 
			
		||||
        if (!empty($value)) {
 | 
			
		||||
          $translated[$key] = $value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        if (locale_is_translatable($langcode)) {
 | 
			
		||||
          $value = $this->translateString($name, $langcode, $item->getUntranslatedString(), $item->getOption('context'));
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          $value = $item->getUntranslatedString();
 | 
			
		||||
        }
 | 
			
		||||
        if (!empty($value)) {
 | 
			
		||||
          $translated[$key] = $value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $translated;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves translated configuration override.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Configuration object name.
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   Language code.
 | 
			
		||||
   * @param array $data
 | 
			
		||||
   *   Configuration data to be saved, that will be only the translated values.
 | 
			
		||||
   */
 | 
			
		||||
  protected function saveTranslationOverride($name, $langcode, array $data) {
 | 
			
		||||
    $this->isUpdatingFromLocale = TRUE;
 | 
			
		||||
    $this->languageManager->getLanguageConfigOverride($langcode, $name)->setData($data)->save();
 | 
			
		||||
    $this->isUpdatingFromLocale = FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves translated configuration data.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Configuration object name.
 | 
			
		||||
   * @param array $data
 | 
			
		||||
   *   Configuration data to be saved with translations merged in.
 | 
			
		||||
   */
 | 
			
		||||
  protected function saveTranslationActive($name, array $data) {
 | 
			
		||||
    $this->isUpdatingFromLocale = TRUE;
 | 
			
		||||
    $this->configFactory->getEditable($name)->setData($data)->save();
 | 
			
		||||
    $this->isUpdatingFromLocale = FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deletes translated configuration data.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Configuration object name.
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   Language code.
 | 
			
		||||
   */
 | 
			
		||||
  protected function deleteTranslationOverride($name, $langcode) {
 | 
			
		||||
    $this->isUpdatingFromLocale = TRUE;
 | 
			
		||||
    $this->languageManager->getLanguageConfigOverride($langcode, $name)->delete();
 | 
			
		||||
    $this->isUpdatingFromLocale = FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets configuration names associated with components.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $components
 | 
			
		||||
   *   (optional) An associative array containing component types as keys and
 | 
			
		||||
   *   lists of components as values. If not provided or is empty, the method
 | 
			
		||||
   *   returns all configuration names.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Array of configuration object names.
 | 
			
		||||
   */
 | 
			
		||||
  public function getComponentNames(array $components = []) {
 | 
			
		||||
    $components = array_filter($components);
 | 
			
		||||
    if ($components) {
 | 
			
		||||
      $names = [];
 | 
			
		||||
      foreach ($components as $type => $list) {
 | 
			
		||||
        // InstallStorage::getComponentNames returns a list of folders keyed by
 | 
			
		||||
        // config name.
 | 
			
		||||
        $names = array_merge($names, $this->defaultConfigStorage->getComponentNames($type, $list));
 | 
			
		||||
      }
 | 
			
		||||
      return $names;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return $this->defaultConfigStorage->listAll();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets configuration names associated with strings.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $lids
 | 
			
		||||
   *   Array with string identifiers.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Array of configuration object names.
 | 
			
		||||
   */
 | 
			
		||||
  public function getStringNames(array $lids) {
 | 
			
		||||
    $names = [];
 | 
			
		||||
    $locations = $this->localeStorage->getLocations(['sid' => $lids, 'type' => 'configuration']);
 | 
			
		||||
    foreach ($locations as $location) {
 | 
			
		||||
      $names[$location->name] = $location->name;
 | 
			
		||||
    }
 | 
			
		||||
    return $names;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deletes configuration for language.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   Language code to delete.
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteLanguageTranslations($langcode) {
 | 
			
		||||
    $this->isUpdatingFromLocale = TRUE;
 | 
			
		||||
    $storage = $this->languageManager->getLanguageConfigOverrideStorage($langcode);
 | 
			
		||||
    foreach ($storage->listAll() as $name) {
 | 
			
		||||
      $this->languageManager->getLanguageConfigOverride($langcode, $name)->delete();
 | 
			
		||||
    }
 | 
			
		||||
    $this->isUpdatingFromLocale = FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Translates string using the localization system.
 | 
			
		||||
   *
 | 
			
		||||
   * So far we only know how to translate strings from English so the source
 | 
			
		||||
   * string should be in English.
 | 
			
		||||
   * Unlike regular t() translations, strings will be added to the source
 | 
			
		||||
   * tables only if this is marked as default data.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Name of the configuration location.
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   Language code to translate to.
 | 
			
		||||
   * @param string $source
 | 
			
		||||
   *   The source string, should be English.
 | 
			
		||||
   * @param string $context
 | 
			
		||||
   *   The string context.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string|false
 | 
			
		||||
   *   Translated string if there is a translation, FALSE if not.
 | 
			
		||||
   */
 | 
			
		||||
  public function translateString($name, $langcode, $source, $context) {
 | 
			
		||||
    if ($source) {
 | 
			
		||||
      // If translations for a language have not been loaded yet.
 | 
			
		||||
      if (!isset($this->translations[$name][$langcode])) {
 | 
			
		||||
        // Preload all translations for this configuration name and language.
 | 
			
		||||
        $this->translations[$name][$langcode] = [];
 | 
			
		||||
        foreach ($this->localeStorage->getTranslations(['language' => $langcode, 'type' => 'configuration', 'name' => $name]) as $string) {
 | 
			
		||||
          $this->translations[$name][$langcode][$string->context][$string->source] = $string;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (!isset($this->translations[$name][$langcode][$context][$source])) {
 | 
			
		||||
        // There is no translation of the source string in this config location
 | 
			
		||||
        // to this language for this context.
 | 
			
		||||
        if ($translation = $this->localeStorage->findTranslation(['source' => $source, 'context' => $context, 'language' => $langcode])) {
 | 
			
		||||
          // Look for a translation of the string. It might have one, but not
 | 
			
		||||
          // be saved in this configuration location yet.
 | 
			
		||||
          // If the string has a translation for this context to this language,
 | 
			
		||||
          // save it in the configuration location so it can be looked up faster
 | 
			
		||||
          // next time.
 | 
			
		||||
          $this->localeStorage->createString((array) $translation)
 | 
			
		||||
            ->addLocation('configuration', $name)
 | 
			
		||||
            ->save();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          // No translation was found. Add the source to the configuration
 | 
			
		||||
          // location so it can be translated, and the string is faster to look
 | 
			
		||||
          // for next time.
 | 
			
		||||
          $translation = $this->localeStorage
 | 
			
		||||
            ->createString(['source' => $source, 'context' => $context])
 | 
			
		||||
            ->addLocation('configuration', $name)
 | 
			
		||||
            ->save();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add an entry, either the translation found, or a blank string object
 | 
			
		||||
        // to track the source string, to this configuration location, language,
 | 
			
		||||
        // and context.
 | 
			
		||||
        $this->translations[$name][$langcode][$context][$source] = $translation;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Return the string only when the string object had a translation.
 | 
			
		||||
      if ($this->translations[$name][$langcode][$context][$source]->isTranslation()) {
 | 
			
		||||
        return $this->translations[$name][$langcode][$context][$source]->getString();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reset static cache of configuration string translations.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function reset() {
 | 
			
		||||
    $this->translations = [];
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the translation object for the given source/context and language.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Name of the configuration location.
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   Language code to translate to.
 | 
			
		||||
   * @param string $source
 | 
			
		||||
   *   The source string, should be English.
 | 
			
		||||
   * @param string $context
 | 
			
		||||
   *   The string context.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\locale\TranslationString|false
 | 
			
		||||
   *   The translation object if the string was not empty or FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function getStringTranslation($name, $langcode, $source, $context) {
 | 
			
		||||
    if ($source) {
 | 
			
		||||
      $this->translateString($name, $langcode, $source, $context);
 | 
			
		||||
      if ($string = $this->translations[$name][$langcode][$context][$source]) {
 | 
			
		||||
        if (!$string->isTranslation()) {
 | 
			
		||||
          $conditions = ['lid' => $string->lid, 'language' => $langcode];
 | 
			
		||||
          $translation = $this->localeStorage->createTranslation($conditions);
 | 
			
		||||
          $this->translations[$name][$langcode][$context][$source] = $translation;
 | 
			
		||||
          return $translation;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          return $string;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks whether a language has configuration translation.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Configuration name.
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   A language code.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   A boolean indicating if a language has configuration translations.
 | 
			
		||||
   */
 | 
			
		||||
  public function hasTranslation($name, $langcode) {
 | 
			
		||||
    $translation = $this->languageManager->getLanguageConfigOverride($langcode, $name);
 | 
			
		||||
    return !$translation->isNew();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the original language code for this shipped configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The configuration name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return null|string
 | 
			
		||||
   *   Language code of the default configuration for $name. If the default
 | 
			
		||||
   *   configuration data for $name did not contain a language code, it is
 | 
			
		||||
   *   assumed to be English. The return value is NULL if no such default
 | 
			
		||||
   *   configuration exists.
 | 
			
		||||
   */
 | 
			
		||||
  public function getDefaultConfigLangcode($name) {
 | 
			
		||||
    // Config entities that do not have the 'default_config_hash' cannot be
 | 
			
		||||
    // shipped configuration regardless of whether there is a name match.
 | 
			
		||||
    // configurable_language entities are a special case since they can be
 | 
			
		||||
    // translated regardless of whether they are shipped if they in the standard
 | 
			
		||||
    // language list.
 | 
			
		||||
    $config_entity_type = $this->configManager->getEntityTypeIdByName($name);
 | 
			
		||||
    if (!$config_entity_type || $config_entity_type === 'configurable_language'
 | 
			
		||||
      || !empty($this->configFactory->get($name)->get('_core.default_config_hash'))
 | 
			
		||||
    ) {
 | 
			
		||||
      $shipped = $this->defaultConfigStorage->read($name);
 | 
			
		||||
      if (!empty($shipped)) {
 | 
			
		||||
        return !empty($shipped['langcode']) ? $shipped['langcode'] : 'en';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the current language code for this active configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The configuration name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return null|string
 | 
			
		||||
   *   Language code of the current active configuration for $name. If the
 | 
			
		||||
   *   configuration data for $name did not contain a language code, it is
 | 
			
		||||
   *   assumed to be English. The return value is NULL if no such active
 | 
			
		||||
   *   configuration exists.
 | 
			
		||||
   */
 | 
			
		||||
  public function getActiveConfigLangcode($name) {
 | 
			
		||||
    $active = $this->configStorage->read($name);
 | 
			
		||||
    if (!empty($active)) {
 | 
			
		||||
      return !empty($active['langcode']) ? $active['langcode'] : 'en';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Whether the given configuration is supported for interface translation.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The configuration name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if interface translation is supported.
 | 
			
		||||
   */
 | 
			
		||||
  public function isSupported($name) {
 | 
			
		||||
    return $this->getDefaultConfigLangcode($name) == 'en' && $this->configStorage->read($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates whether configuration translations are being updated from locale.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   Whether or not configuration translations are currently being updated.
 | 
			
		||||
   *   If TRUE, LocaleConfigManager is in control of the process and the
 | 
			
		||||
   *   reference data is locale's storage. Changes made to active configuration
 | 
			
		||||
   *   and overrides in this case should not feed back to locale storage.
 | 
			
		||||
   *   On the other hand, when not updating from locale and configuration
 | 
			
		||||
   *   translations change, we need to feed back to the locale storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function isUpdatingTranslationsFromLocale() {
 | 
			
		||||
    return $this->isUpdatingFromLocale;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Updates all configuration translations for the names / languages provided.
 | 
			
		||||
   *
 | 
			
		||||
   * To be used when interface translation changes result in the need to update
 | 
			
		||||
   * configuration translations to keep them in sync.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $names
 | 
			
		||||
   *   Array of names of configuration objects to update.
 | 
			
		||||
   * @param array $langcodes
 | 
			
		||||
   *   (optional) Array of language codes to update. Defaults to all
 | 
			
		||||
   *   configurable languages.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   Total number of configuration override and active configuration objects
 | 
			
		||||
   *   updated (saved or removed).
 | 
			
		||||
   */
 | 
			
		||||
  public function updateConfigTranslations(array $names, array $langcodes = []) {
 | 
			
		||||
    $langcodes = $langcodes ?: array_keys($this->languageManager->getLanguages());
 | 
			
		||||
    $count = 0;
 | 
			
		||||
    foreach ($names as $name) {
 | 
			
		||||
      $translatable = $this->getTranslatableDefaultConfig($name);
 | 
			
		||||
      if (empty($translatable)) {
 | 
			
		||||
        // If there is nothing translatable in this configuration or not
 | 
			
		||||
        // supported, skip it.
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $active_langcode = $this->getActiveConfigLangcode($name);
 | 
			
		||||
      $active = $this->configStorage->read($name);
 | 
			
		||||
 | 
			
		||||
      foreach ($langcodes as $langcode) {
 | 
			
		||||
        $processed = $this->processTranslatableData($name, $active, $translatable, $langcode);
 | 
			
		||||
        // If the language code is not the same as the active storage
 | 
			
		||||
        // language, we should update the configuration override.
 | 
			
		||||
        if ($langcode != $active_langcode) {
 | 
			
		||||
          $override = $this->languageManager->getLanguageConfigOverride($langcode, $name);
 | 
			
		||||
          // Filter out locale managed configuration keys so that translations
 | 
			
		||||
          // removed from Locale will be reflected in the config override.
 | 
			
		||||
          $data = $this->filterOverride($override->get(), $translatable);
 | 
			
		||||
          if (!empty($processed)) {
 | 
			
		||||
            // Merge in the Locale managed translations with existing data.
 | 
			
		||||
            $data = NestedArray::mergeDeepArray([$data, $processed], TRUE);
 | 
			
		||||
          }
 | 
			
		||||
          if (empty($data) && !$override->isNew()) {
 | 
			
		||||
            // The configuration override contains Locale overrides that no
 | 
			
		||||
            // longer exist.
 | 
			
		||||
            $this->deleteTranslationOverride($name, $langcode);
 | 
			
		||||
            $count++;
 | 
			
		||||
          }
 | 
			
		||||
          elseif (!empty($data)) {
 | 
			
		||||
            // Update translation data in configuration override.
 | 
			
		||||
            $this->saveTranslationOverride($name, $langcode, $data);
 | 
			
		||||
            $count++;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        elseif (locale_is_translatable($langcode)) {
 | 
			
		||||
          // If the language code is the active storage language, we should
 | 
			
		||||
          // update. If it is English, we should only update if English is also
 | 
			
		||||
          // translatable.
 | 
			
		||||
          $active = NestedArray::mergeDeepArray([$active, $processed], TRUE);
 | 
			
		||||
          $this->saveTranslationActive($name, $active);
 | 
			
		||||
          $count++;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $count;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Filters override data based on default translatable items.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $override_data
 | 
			
		||||
   *   Configuration override data.
 | 
			
		||||
   * @param array $translatable
 | 
			
		||||
   *   Translatable data array. @see self::getTranslatableData()
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Nested array of any items of $override_data which did not have keys in
 | 
			
		||||
   *   $translatable. May be empty if $override_data only had items which were
 | 
			
		||||
   *   also in $translatable.
 | 
			
		||||
   */
 | 
			
		||||
  protected function filterOverride(array $override_data, array $translatable) {
 | 
			
		||||
    $filtered_data = [];
 | 
			
		||||
    foreach ($override_data as $key => $value) {
 | 
			
		||||
      if (isset($translatable[$key])) {
 | 
			
		||||
        // If the translatable default configuration has this key, look further
 | 
			
		||||
        // for subkeys or ignore this element for scalar values.
 | 
			
		||||
        if (is_array($value)) {
 | 
			
		||||
          $value = $this->filterOverride($value, $translatable[$key]);
 | 
			
		||||
          if (!empty($value)) {
 | 
			
		||||
            $filtered_data[$key] = $value;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // If this key was not in the translatable default configuration,
 | 
			
		||||
        // keep it.
 | 
			
		||||
        $filtered_data[$key] = $value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $filtered_data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Updates default configuration when new modules or themes are installed.
 | 
			
		||||
   */
 | 
			
		||||
  public function updateDefaultConfigLangcodes() {
 | 
			
		||||
    $this->isUpdatingFromLocale = TRUE;
 | 
			
		||||
    // Need to rewrite some default configuration language codes if the default
 | 
			
		||||
    // site language is not English.
 | 
			
		||||
    $default_langcode = $this->languageManager->getDefaultLanguage()->getId();
 | 
			
		||||
    if ($default_langcode != 'en') {
 | 
			
		||||
      // Update active configuration copies of all prior shipped configuration
 | 
			
		||||
      // if they are still English. It is not enough to change configuration
 | 
			
		||||
      // shipped with the components just installed, because installing a
 | 
			
		||||
      // component such as views may bring in default configuration from prior
 | 
			
		||||
      // components.
 | 
			
		||||
      $names = $this->getComponentNames();
 | 
			
		||||
      foreach ($names as $name) {
 | 
			
		||||
        $config = $this->configFactory->reset($name)->getEditable($name);
 | 
			
		||||
        // Should only update if still exists in active configuration. If locale
 | 
			
		||||
        // module is enabled later, then some configuration may not exist
 | 
			
		||||
        // anymore.
 | 
			
		||||
        if (!$config->isNew()) {
 | 
			
		||||
          $typed_config = $this->typedConfigManager->createFromNameAndData($config->getName(), $config->getRawData());
 | 
			
		||||
          $langcode = $config->get('langcode');
 | 
			
		||||
          // Only set a `langcode` if this config actually contains translatable
 | 
			
		||||
          // data.
 | 
			
		||||
          // @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
 | 
			
		||||
          if (!empty($this->getTranslatableData($typed_config)) && (empty($langcode) || $langcode == 'en')) {
 | 
			
		||||
            $config->set('langcode', $default_langcode)->save();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $this->isUpdatingFromLocale = FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										241
									
								
								web/core/modules/locale/src/LocaleConfigSubscriber.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								web/core/modules/locale/src/LocaleConfigSubscriber.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,241 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\ConfigCrudEvent;
 | 
			
		||||
use Drupal\Core\Config\ConfigEvents;
 | 
			
		||||
use Drupal\Core\Config\ConfigFactoryInterface;
 | 
			
		||||
use Drupal\Core\Config\StorableConfigBase;
 | 
			
		||||
use Drupal\Core\Installer\InstallerKernel;
 | 
			
		||||
use Drupal\language\Config\LanguageConfigOverrideCrudEvent;
 | 
			
		||||
use Drupal\language\Config\LanguageConfigOverrideEvents;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Updates strings translation when configuration translations change.
 | 
			
		||||
 *
 | 
			
		||||
 * This reacts to the updates of translated active configuration and
 | 
			
		||||
 * configuration language overrides. When those updates involve configuration
 | 
			
		||||
 * which was available as default configuration, we need to feed back changes
 | 
			
		||||
 * to any item which was originally part of that configuration to the interface
 | 
			
		||||
 * translation storage. Those updated translations are saved as customized, so
 | 
			
		||||
 * further community translation updates will not undo user changes.
 | 
			
		||||
 *
 | 
			
		||||
 * This subscriber does not respond to deleting active configuration or deleting
 | 
			
		||||
 * configuration translations. The locale storage is additive and we cannot be
 | 
			
		||||
 * sure that only a given configuration translation used a source string. So
 | 
			
		||||
 * we should not remove the translations from locale storage in these cases. The
 | 
			
		||||
 * configuration or override would itself be deleted either way.
 | 
			
		||||
 *
 | 
			
		||||
 * By design locale module only deals with sources in English.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\locale\LocaleConfigManager
 | 
			
		||||
 */
 | 
			
		||||
class LocaleConfigSubscriber implements EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The configuration factory.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ConfigFactoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $configFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The typed configuration manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\locale\LocaleConfigManager
 | 
			
		||||
   */
 | 
			
		||||
  protected $localeConfigManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The language manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Language\LanguageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a LocaleConfigSubscriber.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
 | 
			
		||||
   *   The configuration factory.
 | 
			
		||||
   * @param \Drupal\locale\LocaleConfigManager $locale_config_manager
 | 
			
		||||
   *   The typed configuration manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(ConfigFactoryInterface $config_factory, LocaleConfigManager $locale_config_manager) {
 | 
			
		||||
    $this->configFactory = $config_factory;
 | 
			
		||||
    $this->localeConfigManager = $locale_config_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents(): array {
 | 
			
		||||
    $events[LanguageConfigOverrideEvents::SAVE_OVERRIDE] = 'onOverrideChange';
 | 
			
		||||
    $events[LanguageConfigOverrideEvents::DELETE_OVERRIDE] = 'onOverrideChange';
 | 
			
		||||
    $events[ConfigEvents::SAVE] = 'onConfigSave';
 | 
			
		||||
    return $events;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Updates the locale strings when a translated active configuration is saved.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
 | 
			
		||||
   *   The configuration event.
 | 
			
		||||
   */
 | 
			
		||||
  public function onConfigSave(ConfigCrudEvent $event) {
 | 
			
		||||
    // Only attempt to feed back configuration translation changes to locale if
 | 
			
		||||
    // the update itself was not initiated by locale data changes.
 | 
			
		||||
    if (!InstallerKernel::installationAttempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) {
 | 
			
		||||
      $config = $event->getConfig();
 | 
			
		||||
      $langcode = $config->get('langcode') ?: 'en';
 | 
			
		||||
      $this->updateLocaleStorage($config, $langcode);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Updates the locale strings when a configuration override is saved/deleted.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event
 | 
			
		||||
   *   The language configuration event.
 | 
			
		||||
   */
 | 
			
		||||
  public function onOverrideChange(LanguageConfigOverrideCrudEvent $event) {
 | 
			
		||||
    // Only attempt to feed back configuration override changes to locale if
 | 
			
		||||
    // the update itself was not initiated by locale data changes.
 | 
			
		||||
    if (!InstallerKernel::installationAttempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) {
 | 
			
		||||
      $translation_config = $event->getLanguageConfigOverride();
 | 
			
		||||
      $langcode = $translation_config->getLangcode();
 | 
			
		||||
      $reference_config = $this->configFactory->getEditable($translation_config->getName())->get();
 | 
			
		||||
      $this->updateLocaleStorage($translation_config, $langcode, $reference_config);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update locale storage based on configuration translations.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorableConfigBase $config
 | 
			
		||||
   *   Active configuration or configuration translation override.
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   The language code of $config.
 | 
			
		||||
   * @param array $reference_config
 | 
			
		||||
   *   (Optional) Reference configuration to check against if $config was an
 | 
			
		||||
   *   override. This allows us to update locale keys for data not in the
 | 
			
		||||
   *   override but still in the active configuration.
 | 
			
		||||
   */
 | 
			
		||||
  public function updateLocaleStorage(StorableConfigBase $config, $langcode, array $reference_config = []) {
 | 
			
		||||
    $name = $config->getName();
 | 
			
		||||
    if ($this->localeConfigManager->isSupported($name) && locale_is_translatable($langcode)) {
 | 
			
		||||
      $translatables = $this->localeConfigManager->getTranslatableDefaultConfig($name);
 | 
			
		||||
      $this->processTranslatableData($name, $config->get(), $translatables, $langcode, $reference_config);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Process the translatable data array with a given language.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The configuration name.
 | 
			
		||||
   * @param array $config
 | 
			
		||||
   *   The active configuration data or override data.
 | 
			
		||||
   * @param array|\Drupal\Core\StringTranslation\TranslatableMarkup[] $translatable
 | 
			
		||||
   *   The translatable array structure.
 | 
			
		||||
   *   @see \Drupal\locale\LocaleConfigManager::getTranslatableData()
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   The language code to process the array with.
 | 
			
		||||
   * @param array $reference_config
 | 
			
		||||
   *   (Optional) Reference configuration to check against if $config was an
 | 
			
		||||
   *   override. This allows us to update locale keys for data not in the
 | 
			
		||||
   *   override but still in the active configuration.
 | 
			
		||||
   */
 | 
			
		||||
  protected function processTranslatableData($name, array $config, array $translatable, $langcode, array $reference_config = []) {
 | 
			
		||||
    foreach ($translatable as $key => $item) {
 | 
			
		||||
      if (!isset($config[$key])) {
 | 
			
		||||
        if (isset($reference_config[$key])) {
 | 
			
		||||
          $this->resetExistingTranslations($name, $translatable[$key], $reference_config[$key], $langcode);
 | 
			
		||||
        }
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (is_array($item)) {
 | 
			
		||||
        $reference_config_item = $reference_config[$key] ?? [];
 | 
			
		||||
        $this->processTranslatableData($name, $config[$key], $item, $langcode, $reference_config_item);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $this->saveCustomizedTranslation($name, $item->getUntranslatedString(), $item->getOption('context'), $config[$key], $langcode);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reset existing locale translations to their source values.
 | 
			
		||||
   *
 | 
			
		||||
   * Goes through $translatable to reset any existing translations to the source
 | 
			
		||||
   * string, so prior translations would not reappear in the configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The configuration name.
 | 
			
		||||
   * @param array|\Drupal\Core\StringTranslation\TranslatableMarkup $translatable
 | 
			
		||||
   *   Either a possibly nested array with TranslatableMarkup objects at the
 | 
			
		||||
   *   leaf items or a TranslatableMarkup object directly.
 | 
			
		||||
   * @param array|string $reference_config
 | 
			
		||||
   *   Either a possibly nested array with strings at the leaf items or a string
 | 
			
		||||
   *   directly. Only those $translatable items that are also present in
 | 
			
		||||
   *   $reference_config will get translations reset.
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   The language code of the translation being processed.
 | 
			
		||||
   */
 | 
			
		||||
  protected function resetExistingTranslations($name, $translatable, $reference_config, $langcode) {
 | 
			
		||||
    if (is_array($translatable)) {
 | 
			
		||||
      foreach ($translatable as $key => $item) {
 | 
			
		||||
        if (isset($reference_config[$key])) {
 | 
			
		||||
          // Process further if the key still exists in the reference active
 | 
			
		||||
          // configuration and the default translation but not the current
 | 
			
		||||
          // configuration override.
 | 
			
		||||
          $this->resetExistingTranslations($name, $item, $reference_config[$key], $langcode);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    elseif (!is_array($reference_config)) {
 | 
			
		||||
      $this->saveCustomizedTranslation($name, $translatable->getUntranslatedString(), $translatable->getOption('context'), $reference_config, $langcode);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves a translation string and marks it as customized.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The configuration name.
 | 
			
		||||
   * @param string $source
 | 
			
		||||
   *   The source string value.
 | 
			
		||||
   * @param string $context
 | 
			
		||||
   *   The source string context.
 | 
			
		||||
   * @param string $new_translation
 | 
			
		||||
   *   The translation string.
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   The language code of the translation.
 | 
			
		||||
   */
 | 
			
		||||
  protected function saveCustomizedTranslation($name, $source, $context, $new_translation, $langcode) {
 | 
			
		||||
    $locale_translation = $this->localeConfigManager->getStringTranslation($name, $langcode, $source, $context);
 | 
			
		||||
    if (!empty($locale_translation)) {
 | 
			
		||||
      // If this code is triggered during installation never set the translation
 | 
			
		||||
      // to the source string.
 | 
			
		||||
      if (InstallerKernel::installationAttempted() && $source === $new_translation) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      // Save this translation as custom if it was a new translation and not the
 | 
			
		||||
      // same as the source. (The interface prefills translation values with the
 | 
			
		||||
      // source). Or if there was an existing (non-empty) translation and the
 | 
			
		||||
      // user changed it (even if it was changed back to the original value).
 | 
			
		||||
      // Otherwise the translation file would be overwritten with the locale
 | 
			
		||||
      // copy again later.
 | 
			
		||||
      $existing_translation = $locale_translation->getString();
 | 
			
		||||
      if (($locale_translation->isNew() && $source != $new_translation) ||
 | 
			
		||||
        (!$locale_translation->isNew() && ((empty($existing_translation) && $source != $new_translation) || ((!empty($existing_translation) && $new_translation != $existing_translation))))) {
 | 
			
		||||
        $locale_translation
 | 
			
		||||
          ->setString($new_translation)
 | 
			
		||||
          ->setCustomized(TRUE)
 | 
			
		||||
          ->save();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										161
									
								
								web/core/modules/locale/src/LocaleDefaultConfigStorage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								web/core/modules/locale/src/LocaleDefaultConfigStorage.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,161 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\ExtensionInstallStorage;
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
use Drupal\language\ConfigurableLanguageManagerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides access to default configuration for locale integration.
 | 
			
		||||
 *
 | 
			
		||||
 * Allows unified access to default configuration from one of three sources:
 | 
			
		||||
 * - Required default configuration (config/install/*)
 | 
			
		||||
 * - Optional default configuration (config/optional/*)
 | 
			
		||||
 * - Predefined languages mocked as default configuration (list defined in
 | 
			
		||||
 *   LocaleConfigManagerInterface::getStandardLanguageList())
 | 
			
		||||
 *
 | 
			
		||||
 * These sources are considered equal in terms of how locale module interacts
 | 
			
		||||
 * with them for translation. Their translatable source strings are exposed
 | 
			
		||||
 * for interface translation and participate in remote translation updates.
 | 
			
		||||
 */
 | 
			
		||||
class LocaleDefaultConfigStorage {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The storage instance for reading configuration data.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $configStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The language manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\language\ConfigurableLanguageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The storage instance for reading required default configuration data.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $requiredInstallStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The storage instance for reading optional default configuration data.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $optionalInstallStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a LocaleDefaultConfigStorage.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageInterface $config_storage
 | 
			
		||||
   *   The storage object to use for reading configuration data.
 | 
			
		||||
   * @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
 | 
			
		||||
   *   The language manager.
 | 
			
		||||
   * @param string $install_profile
 | 
			
		||||
   *   The current installation profile.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(StorageInterface $config_storage, ConfigurableLanguageManagerInterface $language_manager, $install_profile) {
 | 
			
		||||
    $this->configStorage = $config_storage;
 | 
			
		||||
    $this->languageManager = $language_manager;
 | 
			
		||||
 | 
			
		||||
    $this->requiredInstallStorage = new ExtensionInstallStorage($this->configStorage, ExtensionInstallStorage::CONFIG_INSTALL_DIRECTORY, ExtensionInstallStorage::DEFAULT_COLLECTION, TRUE, $install_profile);
 | 
			
		||||
    $this->optionalInstallStorage = new ExtensionInstallStorage($this->configStorage, ExtensionInstallStorage::CONFIG_OPTIONAL_DIRECTORY, ExtensionInstallStorage::DEFAULT_COLLECTION, TRUE, $install_profile);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Read a configuration from install storage or default languages.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Configuration object name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Configuration data from install storage or default language.
 | 
			
		||||
   */
 | 
			
		||||
  public function read($name) {
 | 
			
		||||
    if ($this->requiredInstallStorage->exists($name)) {
 | 
			
		||||
      return $this->requiredInstallStorage->read($name);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($this->optionalInstallStorage->exists($name)) {
 | 
			
		||||
      return $this->optionalInstallStorage->read($name);
 | 
			
		||||
    }
 | 
			
		||||
    elseif (str_starts_with($name, 'language.entity.')) {
 | 
			
		||||
      // Simulate default languages as if they were shipped as default
 | 
			
		||||
      // configuration.
 | 
			
		||||
      $langcode = str_replace('language.entity.', '', $name);
 | 
			
		||||
      $predefined_languages = $this->languageManager->getStandardLanguageList();
 | 
			
		||||
      if (isset($predefined_languages[$langcode])) {
 | 
			
		||||
        $data = $this->configStorage->read($name);
 | 
			
		||||
        $data['label'] = $predefined_languages[$langcode][0];
 | 
			
		||||
        return $data;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return the list of configuration in install storage and current languages.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   List of configuration in install storage and current languages.
 | 
			
		||||
   */
 | 
			
		||||
  public function listAll() {
 | 
			
		||||
    $languages = $this->predefinedConfiguredLanguages();
 | 
			
		||||
    return array_unique(
 | 
			
		||||
      array_merge(
 | 
			
		||||
        $this->requiredInstallStorage->listAll(),
 | 
			
		||||
        $this->optionalInstallStorage->listAll(),
 | 
			
		||||
        $languages
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get all configuration names and folders for a list of modules or themes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   Type of components: 'module' | 'theme' | 'profile'.
 | 
			
		||||
   * @param array $list
 | 
			
		||||
   *   Array of theme or module names.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Configuration names provided by that component. In case of language
 | 
			
		||||
   *   module this list is extended with configured languages that have
 | 
			
		||||
   *   predefined names as well.
 | 
			
		||||
   */
 | 
			
		||||
  public function getComponentNames($type, array $list) {
 | 
			
		||||
    $names = array_unique(
 | 
			
		||||
      array_merge(
 | 
			
		||||
        array_keys($this->requiredInstallStorage->getComponentNames($list)),
 | 
			
		||||
        array_keys($this->optionalInstallStorage->getComponentNames($list))
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
    if ($type == 'module' && in_array('language', $list)) {
 | 
			
		||||
      $languages = $this->predefinedConfiguredLanguages();
 | 
			
		||||
      $names = array_unique(array_merge($names, $languages));
 | 
			
		||||
    }
 | 
			
		||||
    return $names;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Compute the list of configuration names that match predefined languages.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The list of configuration names that match predefined languages.
 | 
			
		||||
   */
 | 
			
		||||
  protected function predefinedConfiguredLanguages() {
 | 
			
		||||
    $names = $this->configStorage->listAll('language.entity.');
 | 
			
		||||
    $predefined_languages = $this->languageManager->getStandardLanguageList();
 | 
			
		||||
    foreach ($names as $id => $name) {
 | 
			
		||||
      $langcode = str_replace('language.entity.', '', $name);
 | 
			
		||||
      if (!isset($predefined_languages[$langcode])) {
 | 
			
		||||
        unset($names[$id]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return array_values($names);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								web/core/modules/locale/src/LocaleEvent.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								web/core/modules/locale/src/LocaleEvent.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\EventDispatcher\Event;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a Locale event.
 | 
			
		||||
 */
 | 
			
		||||
class LocaleEvent extends Event {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The list of Language codes for updated translations.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $langCodes;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * List of string identifiers that have been updated / created.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected array $lids;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new LocaleEvent.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $lang_codes
 | 
			
		||||
   *   Language codes for updated translations.
 | 
			
		||||
   * @param array $lids
 | 
			
		||||
   *   (optional) List of string identifiers that have been updated / created.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $lang_codes, array $lids = []) {
 | 
			
		||||
    $this->langCodes = $lang_codes;
 | 
			
		||||
    $this->lids = $lids;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the language codes.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   $langCodes
 | 
			
		||||
   */
 | 
			
		||||
  public function getLangCodes() {
 | 
			
		||||
    return $this->langCodes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the string identifiers.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   $lids
 | 
			
		||||
   */
 | 
			
		||||
  public function getLids() {
 | 
			
		||||
    return $this->lids;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								web/core/modules/locale/src/LocaleEvents.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								web/core/modules/locale/src/LocaleEvents.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines events for locale translation.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\Core\Config\ConfigCrudEvent
 | 
			
		||||
 */
 | 
			
		||||
final class LocaleEvents {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The name of the event fired when saving a translated string.
 | 
			
		||||
   *
 | 
			
		||||
   * This event allows you to perform custom actions whenever a translated
 | 
			
		||||
   * string is saved.
 | 
			
		||||
   *
 | 
			
		||||
   * @Event
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\locale\EventSubscriber\LocaleTranslationCacheTag
 | 
			
		||||
   */
 | 
			
		||||
  const SAVE_TRANSLATION = 'locale.save_translation';
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										198
									
								
								web/core/modules/locale/src/LocaleLookup.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								web/core/modules/locale/src/LocaleLookup.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,198 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Gettext\PoItem;
 | 
			
		||||
use Drupal\Core\Cache\CacheBackendInterface;
 | 
			
		||||
use Drupal\Core\Cache\CacheCollector;
 | 
			
		||||
use Drupal\Core\Config\ConfigFactoryInterface;
 | 
			
		||||
use Drupal\Core\Language\LanguageManagerInterface;
 | 
			
		||||
use Drupal\Core\Lock\LockBackendInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RequestStack;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A cache collector to allow for dynamic building of the locale cache.
 | 
			
		||||
 */
 | 
			
		||||
class LocaleLookup extends CacheCollector {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A language code.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $langcode;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The msgctxt context.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $context;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The locale storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\locale\StringStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $stringStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The cache backend that should be used.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Cache\CacheBackendInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $cache;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The lock backend that should be used.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Lock\LockBackendInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $lock;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The configuration factory.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ConfigFactoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $configFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The language manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Language\LanguageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The request stack.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Symfony\Component\HttpFoundation\RequestStack
 | 
			
		||||
   */
 | 
			
		||||
  protected $requestStack;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a LocaleLookup object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   The language code.
 | 
			
		||||
   * @param string $context
 | 
			
		||||
   *   The string context.
 | 
			
		||||
   * @param \Drupal\locale\StringStorageInterface $string_storage
 | 
			
		||||
   *   The string storage.
 | 
			
		||||
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
 | 
			
		||||
   *   The cache backend.
 | 
			
		||||
   * @param \Drupal\Core\Lock\LockBackendInterface $lock
 | 
			
		||||
   *   The lock backend.
 | 
			
		||||
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
 | 
			
		||||
   *   The config factory.
 | 
			
		||||
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
 | 
			
		||||
   *   The language manager.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
 | 
			
		||||
   *   The request stack.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($langcode, $context, StringStorageInterface $string_storage, CacheBackendInterface $cache, LockBackendInterface $lock, ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager, RequestStack $request_stack) {
 | 
			
		||||
    $this->langcode = $langcode;
 | 
			
		||||
    $this->context = (string) $context;
 | 
			
		||||
    $this->stringStorage = $string_storage;
 | 
			
		||||
    $this->configFactory = $config_factory;
 | 
			
		||||
    $this->languageManager = $language_manager;
 | 
			
		||||
 | 
			
		||||
    $this->cache = $cache;
 | 
			
		||||
    $this->lock = $lock;
 | 
			
		||||
    $this->tags = ['locale'];
 | 
			
		||||
    $this->requestStack = $request_stack;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getCid() {
 | 
			
		||||
    if (!isset($this->cid)) {
 | 
			
		||||
      // Add the current user's role IDs to the cache key, this ensures that,
 | 
			
		||||
      // for example, strings for admin menu items and settings forms are not
 | 
			
		||||
      // cached for anonymous users.
 | 
			
		||||
      $user = \Drupal::currentUser();
 | 
			
		||||
      $rids = '';
 | 
			
		||||
      if ($user) {
 | 
			
		||||
        $roles = $user->getRoles();
 | 
			
		||||
        sort($roles);
 | 
			
		||||
        $rids = implode(':', $roles);
 | 
			
		||||
      }
 | 
			
		||||
      $this->cid = "locale:{$this->langcode}:{$this->context}:$rids";
 | 
			
		||||
 | 
			
		||||
      // Getting the roles from the current user might have resulted in t()
 | 
			
		||||
      // calls that attempted to get translations from the locale cache. In that
 | 
			
		||||
      // case they would not go into this method again as
 | 
			
		||||
      // CacheCollector::lazyLoadCache() already set the loaded flag. They would
 | 
			
		||||
      // however call resolveCacheMiss() and add that string to the list of
 | 
			
		||||
      // cache misses that need to be written into the cache. Prevent that by
 | 
			
		||||
      // resetting that list. All that happens in such a case are a few uncached
 | 
			
		||||
      // translation lookups.
 | 
			
		||||
      $this->keysToPersist = [];
 | 
			
		||||
    }
 | 
			
		||||
    return $this->cid;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function resolveCacheMiss($offset) {
 | 
			
		||||
    $translation = $this->stringStorage->findTranslation([
 | 
			
		||||
      'language' => $this->langcode,
 | 
			
		||||
      'source' => $offset,
 | 
			
		||||
      'context' => $this->context,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    if ($translation) {
 | 
			
		||||
      $value = !empty($translation->translation) ? $translation->translation : TRUE;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // We don't have the source string, update the {locales_source} table to
 | 
			
		||||
      // indicate the string is not translated.
 | 
			
		||||
      $this->stringStorage->createString([
 | 
			
		||||
        'source' => $offset,
 | 
			
		||||
        'context' => $this->context,
 | 
			
		||||
        'version' => \Drupal::VERSION,
 | 
			
		||||
      ])->addLocation('path', $this->requestStack->getCurrentRequest()->getRequestUri())->save();
 | 
			
		||||
      $value = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If there is no translation available for the current language then use
 | 
			
		||||
    // language fallback to try other translations.
 | 
			
		||||
    if ($value === TRUE) {
 | 
			
		||||
      $fallbacks = $this->languageManager->getFallbackCandidates(['langcode' => $this->langcode, 'operation' => 'locale_lookup', 'data' => $offset]);
 | 
			
		||||
      if (!empty($fallbacks)) {
 | 
			
		||||
        foreach ($fallbacks as $langcode) {
 | 
			
		||||
          $translation = $this->stringStorage->findTranslation([
 | 
			
		||||
            'language' => $langcode,
 | 
			
		||||
            'source' => $offset,
 | 
			
		||||
            'context' => $this->context,
 | 
			
		||||
          ]);
 | 
			
		||||
 | 
			
		||||
          if ($translation && !empty($translation->translation)) {
 | 
			
		||||
            $value = $translation->translation;
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (is_string($value) && str_contains($value, PoItem::DELIMITER)) {
 | 
			
		||||
      // Community translations imported from localize.drupal.org as well as
 | 
			
		||||
      // migrated translations may contain @count[number].
 | 
			
		||||
      $value = preg_replace('!@count\[\d+\]!', '@count', $value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->storage[$offset] = $value;
 | 
			
		||||
    // Disabling the usage of string caching allows a module to watch for
 | 
			
		||||
    // the exact list of strings used on a page. From a performance
 | 
			
		||||
    // perspective that is a really bad idea, so we have no user
 | 
			
		||||
    // interface for this. Be careful when turning this option off!
 | 
			
		||||
    if ($this->configFactory->get('locale.settings')->get('cache_strings')) {
 | 
			
		||||
      $this->persist($offset);
 | 
			
		||||
    }
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										192
									
								
								web/core/modules/locale/src/LocaleProjectStorage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								web/core/modules/locale/src/LocaleProjectStorage.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,192 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides the locale project storage system using a key value store.
 | 
			
		||||
 */
 | 
			
		||||
class LocaleProjectStorage implements LocaleProjectStorageInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The key value store to use.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $keyValueStore;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Static state cache.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $cache = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Cache status flag.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected bool $all = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sorted status flag.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected bool $sorted = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a State object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
 | 
			
		||||
   *   The key value store to use.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(KeyValueFactoryInterface $key_value_factory) {
 | 
			
		||||
    $this->keyValueStore = $key_value_factory->get('locale.project');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function get($key, $default = NULL) {
 | 
			
		||||
    $values = $this->getMultiple([$key]);
 | 
			
		||||
    return $values[$key] ?? $default;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getMultiple(array $keys) {
 | 
			
		||||
    $values = [];
 | 
			
		||||
    $load = [];
 | 
			
		||||
    foreach ($keys as $key) {
 | 
			
		||||
      // Check if we have a value in the cache.
 | 
			
		||||
      if (isset($this->cache[$key])) {
 | 
			
		||||
        $values[$key] = $this->cache[$key];
 | 
			
		||||
      }
 | 
			
		||||
      // Load the value if we don't have an explicit NULL value.
 | 
			
		||||
      elseif (!array_key_exists($key, $this->cache)) {
 | 
			
		||||
        $load[] = $key;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($load) {
 | 
			
		||||
      $loaded_values = $this->keyValueStore->getMultiple($load);
 | 
			
		||||
      foreach ($load as $key) {
 | 
			
		||||
        // If we find a value, even one that is NULL, add it to the cache and
 | 
			
		||||
        // return it.
 | 
			
		||||
        if (isset($loaded_values[$key])) {
 | 
			
		||||
          $values[$key] = $loaded_values[$key];
 | 
			
		||||
          $this->cache[$key] = $loaded_values[$key];
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          $this->cache[$key] = NULL;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function set($key, $value) {
 | 
			
		||||
    $this->setMultiple([$key => $value]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setMultiple(array $data) {
 | 
			
		||||
    foreach ($data as $key => $value) {
 | 
			
		||||
      $this->cache[$key] = $value;
 | 
			
		||||
    }
 | 
			
		||||
    $this->keyValueStore->setMultiple($data);
 | 
			
		||||
    $this->sorted = FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function delete($key) {
 | 
			
		||||
    $this->deleteMultiple([$key]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteMultiple(array $keys) {
 | 
			
		||||
    foreach ($keys as $key) {
 | 
			
		||||
      $this->cache[$key] = NULL;
 | 
			
		||||
    }
 | 
			
		||||
    $this->keyValueStore->deleteMultiple($keys);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function resetCache() {
 | 
			
		||||
    $this->cache = [];
 | 
			
		||||
    $this->sorted = $this->all = FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteAll() {
 | 
			
		||||
    $this->keyValueStore->deleteAll();
 | 
			
		||||
    $this->resetCache();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function disableAll() {
 | 
			
		||||
    $projects = $this->keyValueStore->getAll();
 | 
			
		||||
    foreach (array_keys($projects) as $key) {
 | 
			
		||||
      $projects[$key]['status'] = 0;
 | 
			
		||||
      if (isset($this->cache[$key])) {
 | 
			
		||||
        $this->cache[$key] = $projects[$key];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $this->keyValueStore->setMultiple($projects);
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function countProjects() {
 | 
			
		||||
    return count($this->getAll());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getAll() {
 | 
			
		||||
    if (!$this->all) {
 | 
			
		||||
      $this->cache = $this->keyValueStore->getAll();
 | 
			
		||||
      $this->all = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
    if (!$this->sorted) {
 | 
			
		||||
      // Work around PHP 8.3.0 - 8.3.3 bug by assigning $this->cache to a local
 | 
			
		||||
      // variable, see https://github.com/php/php-src/pull/13285.
 | 
			
		||||
      $cache = $this->cache;
 | 
			
		||||
      uksort($this->cache, function ($a, $b) use ($cache) {
 | 
			
		||||
        // Sort by weight, if available, and then by key. This allows locale
 | 
			
		||||
        // projects to set a weight, if required, and keeps the order consistent
 | 
			
		||||
        // regardless of whether the list is built from code or retrieve from
 | 
			
		||||
        // the database.
 | 
			
		||||
        $sort = (int) ($cache[$a]['weight'] ?? 0) <=> (int) ($cache[$b]['weight'] ?? 0);
 | 
			
		||||
        return $sort ?: strcmp($a, $b);
 | 
			
		||||
      });
 | 
			
		||||
      $this->sorted = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
    // Remove any NULL values as these are not valid projects.
 | 
			
		||||
    return array_filter($this->cache, fn ($value) => $value !== NULL);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								web/core/modules/locale/src/LocaleProjectStorageInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								web/core/modules/locale/src/LocaleProjectStorageInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,102 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the locale project storage interface.
 | 
			
		||||
 */
 | 
			
		||||
interface LocaleProjectStorageInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the stored value for a given key.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $key
 | 
			
		||||
   *   The key of the data to retrieve.
 | 
			
		||||
   * @param mixed $default
 | 
			
		||||
   *   The default value to use if the key is not found.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed
 | 
			
		||||
   *   The stored value, or the default value if no value exists.
 | 
			
		||||
   */
 | 
			
		||||
  public function get($key, $default = NULL);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a list of project records.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $keys
 | 
			
		||||
   *   A list of keys to retrieve.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array of items successfully returned, indexed by key.
 | 
			
		||||
   */
 | 
			
		||||
  public function getMultiple(array $keys);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates or updates the project record.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $key
 | 
			
		||||
   *   The key of the data to store.
 | 
			
		||||
   * @param mixed $value
 | 
			
		||||
   *   The data to store.
 | 
			
		||||
   */
 | 
			
		||||
  public function set($key, $value);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates or updates multiple project records.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $data
 | 
			
		||||
   *   An associative array of key/value pairs.
 | 
			
		||||
   */
 | 
			
		||||
  public function setMultiple(array $data);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deletes project records for a given key.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $key
 | 
			
		||||
   *   The key of the data to delete.
 | 
			
		||||
   */
 | 
			
		||||
  public function delete($key);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deletes multiple project records.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $keys
 | 
			
		||||
   *   A list of item names to delete.
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteMultiple(array $keys);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns all the project records.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array of items successfully returned, indexed by key.
 | 
			
		||||
   */
 | 
			
		||||
  public function getAll();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deletes all projects records.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array of items successfully returned, indexed by key.
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteAll();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Mark all projects as disabled.
 | 
			
		||||
   */
 | 
			
		||||
  public function disableAll();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Resets the project storage cache.
 | 
			
		||||
   */
 | 
			
		||||
  public function resetCache();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the count of project records.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The number of saved items.
 | 
			
		||||
   */
 | 
			
		||||
  public function countProjects();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										175
									
								
								web/core/modules/locale/src/LocaleTranslation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								web/core/modules/locale/src/LocaleTranslation.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,175 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\CacheBackendInterface;
 | 
			
		||||
use Drupal\Core\Config\ConfigFactoryInterface;
 | 
			
		||||
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 | 
			
		||||
use Drupal\Core\DestructableInterface;
 | 
			
		||||
use Drupal\Core\Language\LanguageInterface;
 | 
			
		||||
use Drupal\Core\Language\LanguageManagerInterface;
 | 
			
		||||
use Drupal\Core\Lock\LockBackendInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RequestStack;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * String translator using the locale module.
 | 
			
		||||
 *
 | 
			
		||||
 * Full featured translation system using locale's string storage and
 | 
			
		||||
 * database caching.
 | 
			
		||||
 */
 | 
			
		||||
class LocaleTranslation implements TranslatorInterface, DestructableInterface {
 | 
			
		||||
 | 
			
		||||
  use DependencySerializationTrait {
 | 
			
		||||
    __sleep as traitSleep;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Storage for strings.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\locale\StringStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $storage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The configuration factory.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ConfigFactoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $configFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Cached translations.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   *   Array of \Drupal\locale\LocaleLookup objects indexed by language code
 | 
			
		||||
   *   and context.
 | 
			
		||||
   */
 | 
			
		||||
  protected $translations = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The cache backend that should be used.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Cache\CacheBackendInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $cache;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The lock backend that should be used.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Lock\LockBackendInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $lock;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The translate english configuration value.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected $translateEnglish;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The language manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Language\LanguageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The request stack.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Symfony\Component\HttpFoundation\RequestStack
 | 
			
		||||
   */
 | 
			
		||||
  protected $requestStack;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a translator using a string storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringStorageInterface $storage
 | 
			
		||||
   *   Storage to use when looking for new translations.
 | 
			
		||||
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
 | 
			
		||||
   *   The cache backend.
 | 
			
		||||
   * @param \Drupal\Core\Lock\LockBackendInterface $lock
 | 
			
		||||
   *   The lock backend.
 | 
			
		||||
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
 | 
			
		||||
   *   The config factory.
 | 
			
		||||
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
 | 
			
		||||
   *   The language manager.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
 | 
			
		||||
   *   The request stack.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(StringStorageInterface $storage, CacheBackendInterface $cache, LockBackendInterface $lock, ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager, RequestStack $request_stack) {
 | 
			
		||||
    $this->storage = $storage;
 | 
			
		||||
    $this->cache = $cache;
 | 
			
		||||
    $this->lock = $lock;
 | 
			
		||||
    $this->configFactory = $config_factory;
 | 
			
		||||
    $this->languageManager = $language_manager;
 | 
			
		||||
    $this->requestStack = $request_stack;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getStringTranslation($langcode, $string, $context) {
 | 
			
		||||
    // If the language is not suitable for locale module, just return.
 | 
			
		||||
    if ($langcode == LanguageInterface::LANGCODE_SYSTEM || ($langcode == 'en' && !$this->canTranslateEnglish())) {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
    // Strings are cached by langcode, context and roles, using instances of the
 | 
			
		||||
    // LocaleLookup class to handle string lookup and caching.
 | 
			
		||||
    if (!isset($this->translations[$langcode][$context])) {
 | 
			
		||||
      $this->translations[$langcode][$context] = new LocaleLookup($langcode, $context, $this->storage, $this->cache, $this->lock, $this->configFactory, $this->languageManager, $this->requestStack);
 | 
			
		||||
    }
 | 
			
		||||
    $translation = $this->translations[$langcode][$context]->get($string);
 | 
			
		||||
    // If the translation is TRUE, no translation exists, but that string needs
 | 
			
		||||
    // to be stored in the persistent cache for performance reasons (so for
 | 
			
		||||
    // example, we don't have hundreds of queries to locale tables on each
 | 
			
		||||
    // request). That cache is persisted when the request ends, and the lookup
 | 
			
		||||
    // service is destroyed.
 | 
			
		||||
    return $translation === TRUE ? FALSE : $translation;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets translate english configuration value.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if english should be translated, FALSE if not.
 | 
			
		||||
   */
 | 
			
		||||
  protected function canTranslateEnglish() {
 | 
			
		||||
    if (!isset($this->translateEnglish)) {
 | 
			
		||||
      $this->translateEnglish = $this->configFactory->get('locale.settings')->get('translate_english');
 | 
			
		||||
    }
 | 
			
		||||
    return $this->translateEnglish;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function reset() {
 | 
			
		||||
    unset($this->translateEnglish);
 | 
			
		||||
    $this->translations = [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function destruct() {
 | 
			
		||||
    foreach ($this->translations as $context) {
 | 
			
		||||
      foreach ($context as $lookup) {
 | 
			
		||||
        if ($lookup instanceof DestructableInterface) {
 | 
			
		||||
          $lookup->destruct();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function __sleep(): array {
 | 
			
		||||
    // ::$translations is an array of LocaleLookup objects, which have the
 | 
			
		||||
    // database service injected and therefore cannot be serialized safely.
 | 
			
		||||
    return array_diff($this->traitSleep(), ['translations']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,118 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\Plugin\QueueWorker;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ModuleHandlerInterface;
 | 
			
		||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 | 
			
		||||
use Drupal\Core\Queue\Attribute\QueueWorker;
 | 
			
		||||
use Drupal\Core\Queue\QueueInterface;
 | 
			
		||||
use Drupal\Core\Queue\QueueWorkerBase;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Executes interface translation queue tasks.
 | 
			
		||||
 */
 | 
			
		||||
#[QueueWorker(
 | 
			
		||||
  id: 'locale_translation',
 | 
			
		||||
  title: new TranslatableMarkup('Update translations'),
 | 
			
		||||
  cron: ['time' => 30]
 | 
			
		||||
)]
 | 
			
		||||
class LocaleTranslation extends QueueWorkerBase implements ContainerFactoryPluginInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The module handler.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $moduleHandler;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The queue object.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Queue\QueueInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $queue;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new LocaleTranslation object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   A configuration array containing information about the plugin instance.
 | 
			
		||||
   * @param string $plugin_id
 | 
			
		||||
   *   The plugin ID for the plugin instance.
 | 
			
		||||
   * @param array $plugin_definition
 | 
			
		||||
   *   The plugin implementation definition.
 | 
			
		||||
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
 | 
			
		||||
   *   The module handler.
 | 
			
		||||
   * @param \Drupal\Core\Queue\QueueInterface $queue
 | 
			
		||||
   *   The queue object.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $configuration, $plugin_id, array $plugin_definition, ModuleHandlerInterface $module_handler, QueueInterface $queue) {
 | 
			
		||||
    parent::__construct($configuration, $plugin_id, $plugin_definition);
 | 
			
		||||
 | 
			
		||||
    $this->moduleHandler = $module_handler;
 | 
			
		||||
    $this->queue = $queue;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $configuration,
 | 
			
		||||
      $plugin_id,
 | 
			
		||||
      $plugin_definition,
 | 
			
		||||
      $container->get('module_handler'),
 | 
			
		||||
      $container->get('queue')->get('locale_translation', TRUE)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   *
 | 
			
		||||
   * The translation update functions executed here are batch operations which
 | 
			
		||||
   * are also used in translation update batches. The batch functions may need
 | 
			
		||||
   * to be executed multiple times to complete their task, typically this is the
 | 
			
		||||
   * translation import function. When a batch function is not finished, a new
 | 
			
		||||
   * queue task is created and added to the end of the queue. The batch context
 | 
			
		||||
   * data is needed to continue the batch task is stored in the queue with the
 | 
			
		||||
   * queue data.
 | 
			
		||||
   */
 | 
			
		||||
  public function processItem($data) {
 | 
			
		||||
    $this->moduleHandler->loadInclude('locale', 'batch.inc');
 | 
			
		||||
    [$function, $args] = $data;
 | 
			
		||||
 | 
			
		||||
    // We execute batch operation functions here to check, download and import
 | 
			
		||||
    // the translation files. Batch functions use a context variable as last
 | 
			
		||||
    // argument which is passed by reference. When a batch operation is called
 | 
			
		||||
    // for the first time a default batch context is created. When called
 | 
			
		||||
    // iterative (usually the batch import function) the batch context is passed
 | 
			
		||||
    // through via the queue and is part of the $data.
 | 
			
		||||
    $last = count($args) - 1;
 | 
			
		||||
    if (!is_array($args[$last]) || !isset($args[$last]['finished'])) {
 | 
			
		||||
      $batch_context = [
 | 
			
		||||
        'sandbox'  => [],
 | 
			
		||||
        'results'  => [],
 | 
			
		||||
        'finished' => 1,
 | 
			
		||||
        'message'  => '',
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $batch_context = $args[$last];
 | 
			
		||||
      unset($args[$last]);
 | 
			
		||||
    }
 | 
			
		||||
    $args = array_merge($args, [&$batch_context]);
 | 
			
		||||
 | 
			
		||||
    // Call the batch operation function.
 | 
			
		||||
    call_user_func_array($function, $args);
 | 
			
		||||
 | 
			
		||||
    // If the batch operation is not finished we create a new queue task to
 | 
			
		||||
    // continue the task. This is typically the translation import task.
 | 
			
		||||
    if ($batch_context['finished'] < 1) {
 | 
			
		||||
      unset($batch_context['strings']);
 | 
			
		||||
      $this->queue->createItem([$function, $args]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										112
									
								
								web/core/modules/locale/src/PluralFormula.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								web/core/modules/locale/src/PluralFormula.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,112 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Language\LanguageManagerInterface;
 | 
			
		||||
use Drupal\Core\State\StateInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Manages the storage of plural formula per language in state.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\locale\PoDatabaseWriter::setHeader()
 | 
			
		||||
 */
 | 
			
		||||
class PluralFormula implements PluralFormulaInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var \Drupal\Core\Language\LanguageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $languageManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var \Drupal\Core\State\StateInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $state;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The plural formula and count keyed by langcode.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   *
 | 
			
		||||
   * For example the structure looks like this:
 | 
			
		||||
   * @code
 | 
			
		||||
   * [
 | 
			
		||||
   *   'de' => [
 | 
			
		||||
   *     'plurals' => 2,
 | 
			
		||||
   *     'formula' => [
 | 
			
		||||
   *       // @todo Provide the formula.
 | 
			
		||||
   *     ]
 | 
			
		||||
   *   ],
 | 
			
		||||
   * ]
 | 
			
		||||
   * @endcode
 | 
			
		||||
   */
 | 
			
		||||
  protected $formulae;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
 | 
			
		||||
   *   The language manager.
 | 
			
		||||
   * @param \Drupal\Core\State\StateInterface $state
 | 
			
		||||
   *   The state.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(LanguageManagerInterface $language_manager, StateInterface $state) {
 | 
			
		||||
    $this->languageManager = $language_manager;
 | 
			
		||||
    $this->state = $state;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setPluralFormula($langcode, $plural_count, array $formula) {
 | 
			
		||||
    // Ensure that the formulae are loaded.
 | 
			
		||||
    $this->loadFormulae();
 | 
			
		||||
 | 
			
		||||
    $this->formulae[$langcode] = [
 | 
			
		||||
      'plurals' => $plural_count,
 | 
			
		||||
      'formula' => $formula,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->state->set('locale.translation.formulae', $this->formulae);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getNumberOfPlurals($langcode = NULL) {
 | 
			
		||||
    // Ensure that the formulae are loaded.
 | 
			
		||||
    $this->loadFormulae();
 | 
			
		||||
 | 
			
		||||
    // Set the langcode to use.
 | 
			
		||||
    $langcode = $langcode ?: $this->languageManager->getCurrentLanguage()->getId();
 | 
			
		||||
 | 
			
		||||
    // We assume 2 plurals if there is no explicit information yet.
 | 
			
		||||
    if (!isset($this->formulae[$langcode]['plurals'])) {
 | 
			
		||||
      return 2;
 | 
			
		||||
    }
 | 
			
		||||
    return $this->formulae[$langcode]['plurals'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormula($langcode) {
 | 
			
		||||
    $this->loadFormulae();
 | 
			
		||||
    return $this->formulae[$langcode]['formula'] ?? FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads the formulae and stores them on the PluralFormula object if not set.
 | 
			
		||||
   */
 | 
			
		||||
  protected function loadFormulae() {
 | 
			
		||||
    if (!isset($this->formulae)) {
 | 
			
		||||
      $this->formulae = $this->state->get('locale.translation.formulae', []);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function reset() {
 | 
			
		||||
    $this->formulae = NULL;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								web/core/modules/locale/src/PluralFormulaInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								web/core/modules/locale/src/PluralFormulaInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface for a service providing plural formulae.
 | 
			
		||||
 */
 | 
			
		||||
interface PluralFormulaInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   The language code to get the formula for.
 | 
			
		||||
   * @param int $plural_count
 | 
			
		||||
   *   The number of plural forms.
 | 
			
		||||
   * @param array $formula
 | 
			
		||||
   *   An array of formulae.
 | 
			
		||||
   *
 | 
			
		||||
   * @return self
 | 
			
		||||
   *   The PluralFormula object.
 | 
			
		||||
   */
 | 
			
		||||
  public function setPluralFormula($langcode, $plural_count, array $formula);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the number of plurals supported by a given language.
 | 
			
		||||
   *
 | 
			
		||||
   * @param null|string $langcode
 | 
			
		||||
   *   (optional) The language code. If not provided, the current language
 | 
			
		||||
   *   will be used.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   Number of plural variants supported by the given language.
 | 
			
		||||
   */
 | 
			
		||||
  public function getNumberOfPlurals($langcode = NULL);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the plural formula for a langcode.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $langcode
 | 
			
		||||
   *   The language code to get the formula for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of formulae.
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormula($langcode);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Resets the static formulae cache.
 | 
			
		||||
   *
 | 
			
		||||
   * @return self
 | 
			
		||||
   *   The PluralFormula object.
 | 
			
		||||
   */
 | 
			
		||||
  public function reset();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										171
									
								
								web/core/modules/locale/src/PoDatabaseReader.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								web/core/modules/locale/src/PoDatabaseReader.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,171 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Gettext\PoHeader;
 | 
			
		||||
use Drupal\Component\Gettext\PoItem;
 | 
			
		||||
use Drupal\Component\Gettext\PoReaderInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gettext PO reader working with the locale module database.
 | 
			
		||||
 */
 | 
			
		||||
class PoDatabaseReader implements PoReaderInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An associative array indicating which type of strings should be read.
 | 
			
		||||
   *
 | 
			
		||||
   * Elements of the array:
 | 
			
		||||
   *  - not_customized: boolean indicating if not customized strings should be
 | 
			
		||||
   *    read.
 | 
			
		||||
   *  - customized: boolean indicating if customized strings should be read.
 | 
			
		||||
   *  - no_translated: boolean indicating if non-translated should be read.
 | 
			
		||||
   *
 | 
			
		||||
   * The three options define three distinct sets of strings, which combined
 | 
			
		||||
   * cover all strings.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  private $options;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Language code of the language being read from the database.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  private $langcode;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Store the result of the query so it can be iterated later.
 | 
			
		||||
   *
 | 
			
		||||
   * @var resource
 | 
			
		||||
   */
 | 
			
		||||
  private $result;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor, initializes with default options.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct() {
 | 
			
		||||
    $this->setOptions([]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getLangcode() {
 | 
			
		||||
    return $this->langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setLangcode($langcode) {
 | 
			
		||||
    $this->langcode = $langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the options used by the reader.
 | 
			
		||||
   */
 | 
			
		||||
  public function getOptions() {
 | 
			
		||||
    return $this->options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the options for the current reader.
 | 
			
		||||
   */
 | 
			
		||||
  public function setOptions(array $options) {
 | 
			
		||||
    $options += [
 | 
			
		||||
      'customized' => FALSE,
 | 
			
		||||
      'not_customized' => FALSE,
 | 
			
		||||
      'not_translated' => FALSE,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->options = $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getHeader() {
 | 
			
		||||
    return new PoHeader($this->getLangcode());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoMetadataInterface::setHeader().
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   *   Always, because you cannot set the PO header of a reader.
 | 
			
		||||
   */
 | 
			
		||||
  public function setHeader(PoHeader $header) {
 | 
			
		||||
    throw new \Exception('You cannot set the PO header in a reader.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds and executes a database query based on options set earlier.
 | 
			
		||||
   */
 | 
			
		||||
  private function loadStrings() {
 | 
			
		||||
    $langcode = $this->langcode;
 | 
			
		||||
    $options = $this->options;
 | 
			
		||||
    $conditions = [];
 | 
			
		||||
 | 
			
		||||
    if (array_sum($options) == 0) {
 | 
			
		||||
      // If user asked to not include anything in the translation files,
 | 
			
		||||
      // that would not make sense, so just fall back on providing a template.
 | 
			
		||||
      $langcode = NULL;
 | 
			
		||||
      // Force option to get both translated and untranslated strings.
 | 
			
		||||
      $options['not_translated'] = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
    // Build and execute query to collect source strings and translations.
 | 
			
		||||
    if (!empty($langcode)) {
 | 
			
		||||
      $conditions['language'] = $langcode;
 | 
			
		||||
      // Translate some options into field conditions.
 | 
			
		||||
      if ($options['customized']) {
 | 
			
		||||
        if (!$options['not_customized']) {
 | 
			
		||||
          // Filter for customized strings only.
 | 
			
		||||
          $conditions['customized'] = LOCALE_CUSTOMIZED;
 | 
			
		||||
        }
 | 
			
		||||
        // Else no filtering needed in this case.
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        if ($options['not_customized']) {
 | 
			
		||||
          // Filter for non-customized strings only.
 | 
			
		||||
          $conditions['customized'] = LOCALE_NOT_CUSTOMIZED;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          // Filter for strings without translation.
 | 
			
		||||
          $conditions['translated'] = FALSE;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (!$options['not_translated']) {
 | 
			
		||||
        // Filter for string with translation.
 | 
			
		||||
        $conditions['translated'] = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
      return \Drupal::service('locale.storage')->getTranslations($conditions);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // If no language, we don't need any of the target fields.
 | 
			
		||||
      return \Drupal::service('locale.storage')->getStrings($conditions);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the database result resource for the given language and options.
 | 
			
		||||
   */
 | 
			
		||||
  private function readString() {
 | 
			
		||||
    if (!isset($this->result)) {
 | 
			
		||||
      $this->result = $this->loadStrings();
 | 
			
		||||
    }
 | 
			
		||||
    return array_shift($this->result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function readItem() {
 | 
			
		||||
    if ($string = $this->readString()) {
 | 
			
		||||
      $values = (array) $string;
 | 
			
		||||
      $po_item = new PoItem();
 | 
			
		||||
      $po_item->setFromArray($values);
 | 
			
		||||
      return $po_item;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										294
									
								
								web/core/modules/locale/src/PoDatabaseWriter.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								web/core/modules/locale/src/PoDatabaseWriter.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,294 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Gettext\PoHeader;
 | 
			
		||||
use Drupal\Component\Gettext\PoItem;
 | 
			
		||||
use Drupal\Component\Gettext\PoReaderInterface;
 | 
			
		||||
use Drupal\Component\Gettext\PoWriterInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gettext PO writer working with the locale module database.
 | 
			
		||||
 */
 | 
			
		||||
class PoDatabaseWriter implements PoWriterInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An associative array indicating what data should be overwritten, if any.
 | 
			
		||||
   *
 | 
			
		||||
   * Elements of the array:
 | 
			
		||||
   * - overwrite_options
 | 
			
		||||
   *   - not_customized: boolean indicating that not customized strings should
 | 
			
		||||
   *     be overwritten.
 | 
			
		||||
   *   - customized: boolean indicating that customized strings should be
 | 
			
		||||
   *     overwritten.
 | 
			
		||||
   * - customized: the strings being imported should be saved as customized.
 | 
			
		||||
   *     One of LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  private $options;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Language code of the language being written to the database.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  private $langcode;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Header of the po file written to the database.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Component\Gettext\PoHeader
 | 
			
		||||
   */
 | 
			
		||||
  private $header;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Associative array summarizing the number of changes done.
 | 
			
		||||
   *
 | 
			
		||||
   * Keys for the array:
 | 
			
		||||
   *  - additions: number of source strings newly added
 | 
			
		||||
   *  - updates: number of translations updated
 | 
			
		||||
   *  - deletes: number of translations deleted
 | 
			
		||||
   *  - skips: number of strings skipped due to disallowed HTML
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  private $report;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor, initialize reporting array.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct() {
 | 
			
		||||
    $this->setReport();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getLangcode() {
 | 
			
		||||
    return $this->langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setLangcode($langcode) {
 | 
			
		||||
    $this->langcode = $langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the report of the write operations.
 | 
			
		||||
   */
 | 
			
		||||
  public function getReport() {
 | 
			
		||||
    return $this->report;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the report array of write operations.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $report
 | 
			
		||||
   *   Associative array with result information.
 | 
			
		||||
   */
 | 
			
		||||
  public function setReport($report = []) {
 | 
			
		||||
    $report += [
 | 
			
		||||
      'additions' => 0,
 | 
			
		||||
      'updates' => 0,
 | 
			
		||||
      'deletes' => 0,
 | 
			
		||||
      'skips' => 0,
 | 
			
		||||
      'strings' => [],
 | 
			
		||||
    ];
 | 
			
		||||
    $this->report = $report;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the options used by the writer.
 | 
			
		||||
   */
 | 
			
		||||
  public function getOptions() {
 | 
			
		||||
    return $this->options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the options for the current writer.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   An associative array containing:
 | 
			
		||||
   *   - overwrite_options: An array of options. Each option contains:
 | 
			
		||||
   *     - not_customized: Boolean indicating that not customized strings should
 | 
			
		||||
   *       be overwritten.
 | 
			
		||||
   *     - customized: Boolean indicating that customized strings should be
 | 
			
		||||
   *       overwritten.
 | 
			
		||||
   *   - customized: The strings being imported should be saved as customized.
 | 
			
		||||
   *     One of LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
 | 
			
		||||
   */
 | 
			
		||||
  public function setOptions(array $options) {
 | 
			
		||||
    if (!isset($options['overwrite_options'])) {
 | 
			
		||||
      $options['overwrite_options'] = [];
 | 
			
		||||
    }
 | 
			
		||||
    $options['overwrite_options'] += [
 | 
			
		||||
      'not_customized' => FALSE,
 | 
			
		||||
      'customized' => FALSE,
 | 
			
		||||
    ];
 | 
			
		||||
    $options += [
 | 
			
		||||
      'customized' => LOCALE_NOT_CUSTOMIZED,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->options = $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getHeader() {
 | 
			
		||||
    return $this->header;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements Drupal\Component\Gettext\PoMetadataInterface::setHeader().
 | 
			
		||||
   *
 | 
			
		||||
   * Sets the header and configure Drupal accordingly.
 | 
			
		||||
   *
 | 
			
		||||
   * Before being able to process the given header we need to know in what
 | 
			
		||||
   * context this database write is done. For this the options must be set.
 | 
			
		||||
   *
 | 
			
		||||
   * A langcode is required to set the current header's PluralForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Component\Gettext\PoHeader $header
 | 
			
		||||
   *   Header metadata.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   */
 | 
			
		||||
  public function setHeader(PoHeader $header) {
 | 
			
		||||
    $this->header = $header;
 | 
			
		||||
    $locale_plurals = \Drupal::state()->get('locale.translation.plurals', []);
 | 
			
		||||
 | 
			
		||||
    // Check for options.
 | 
			
		||||
    $options = $this->getOptions();
 | 
			
		||||
    if (empty($options)) {
 | 
			
		||||
      throw new \Exception('Options should be set before assigning a PoHeader.');
 | 
			
		||||
    }
 | 
			
		||||
    $overwrite_options = $options['overwrite_options'];
 | 
			
		||||
 | 
			
		||||
    // Check for langcode.
 | 
			
		||||
    $langcode = $this->langcode;
 | 
			
		||||
    if (empty($langcode)) {
 | 
			
		||||
      throw new \Exception('Langcode should be set before assigning a PoHeader.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (array_sum($overwrite_options) || empty($locale_plurals[$langcode]['plurals'])) {
 | 
			
		||||
      // Get and store the plural formula if available.
 | 
			
		||||
      $plural = $header->getPluralForms();
 | 
			
		||||
      if (isset($plural) && $p = $header->parsePluralForms($plural)) {
 | 
			
		||||
        [$nplurals, $formula] = $p;
 | 
			
		||||
        \Drupal::service('locale.plural.formula')->setPluralFormula($langcode, $nplurals, $formula);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function writeItem(PoItem $item) {
 | 
			
		||||
    if ($item->isPlural()) {
 | 
			
		||||
      $item->setSource(implode(PoItem::DELIMITER, $item->getSource()));
 | 
			
		||||
      $item->setTranslation(implode(PoItem::DELIMITER, $item->getTranslation()));
 | 
			
		||||
    }
 | 
			
		||||
    $this->importString($item);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function writeItems(PoReaderInterface $reader, $count = -1) {
 | 
			
		||||
    // Processing multiple writes in a transaction is quicker than committing
 | 
			
		||||
    // each individual write.
 | 
			
		||||
    $transaction = \Drupal::database()->startTransaction();
 | 
			
		||||
    $forever = $count == -1;
 | 
			
		||||
    while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
 | 
			
		||||
      $this->writeItem($item);
 | 
			
		||||
    }
 | 
			
		||||
    // Commit the transaction.
 | 
			
		||||
    unset($transaction);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Imports one string into the database.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Component\Gettext\PoItem $item
 | 
			
		||||
   *   The item being imported.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The string ID of the existing string modified or the new string added.
 | 
			
		||||
   */
 | 
			
		||||
  private function importString(PoItem $item) {
 | 
			
		||||
    // Initialize overwrite options if not set.
 | 
			
		||||
    $this->options['overwrite_options'] += [
 | 
			
		||||
      'not_customized' => FALSE,
 | 
			
		||||
      'customized' => FALSE,
 | 
			
		||||
    ];
 | 
			
		||||
    $overwrite_options = $this->options['overwrite_options'];
 | 
			
		||||
    $customized = $this->options['customized'];
 | 
			
		||||
 | 
			
		||||
    $context = $item->getContext();
 | 
			
		||||
    $source = $item->getSource();
 | 
			
		||||
    $translation = $item->getTranslation();
 | 
			
		||||
 | 
			
		||||
    // Look up the source string and any existing translation.
 | 
			
		||||
    $strings = \Drupal::service('locale.storage')->getTranslations([
 | 
			
		||||
      'language' => $this->langcode,
 | 
			
		||||
      'source' => $source,
 | 
			
		||||
      'context' => $context,
 | 
			
		||||
    ]);
 | 
			
		||||
    $string = reset($strings);
 | 
			
		||||
 | 
			
		||||
    if (!empty($translation)) {
 | 
			
		||||
      // Skip this string unless it passes a check for dangerous code.
 | 
			
		||||
      if (!locale_string_is_safe($translation)) {
 | 
			
		||||
        \Drupal::logger('locale')->error('Import of string "%string" was skipped because of disallowed or malformed HTML.', ['%string' => $translation]);
 | 
			
		||||
        $this->report['skips']++;
 | 
			
		||||
        return 0;
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($string) {
 | 
			
		||||
        $string->setString($translation);
 | 
			
		||||
        if ($string->isNew()) {
 | 
			
		||||
          // No translation in this language.
 | 
			
		||||
          $string->setValues([
 | 
			
		||||
            'language' => $this->langcode,
 | 
			
		||||
            'customized' => $customized,
 | 
			
		||||
          ]);
 | 
			
		||||
          $string->save();
 | 
			
		||||
          $this->report['additions']++;
 | 
			
		||||
        }
 | 
			
		||||
        elseif ($overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
 | 
			
		||||
          // Translation exists, only overwrite if instructed.
 | 
			
		||||
          $string->customized = $customized;
 | 
			
		||||
          $string->save();
 | 
			
		||||
          $this->report['updates']++;
 | 
			
		||||
        }
 | 
			
		||||
        $this->report['strings'][] = $string->getId();
 | 
			
		||||
        return $string->lid;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // No such source string in the database yet.
 | 
			
		||||
        $string = \Drupal::service('locale.storage')->createString(['source' => $source, 'context' => $context])
 | 
			
		||||
          ->save();
 | 
			
		||||
        \Drupal::service('locale.storage')->createTranslation([
 | 
			
		||||
          'lid' => $string->getId(),
 | 
			
		||||
          'language' => $this->langcode,
 | 
			
		||||
          'translation' => $translation,
 | 
			
		||||
          'customized' => $customized,
 | 
			
		||||
        ])->save();
 | 
			
		||||
 | 
			
		||||
        $this->report['additions']++;
 | 
			
		||||
        $this->report['strings'][] = $string->getId();
 | 
			
		||||
        return $string->lid;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($string && !$string->isNew() && $overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
 | 
			
		||||
      // Empty translation, remove existing if instructed.
 | 
			
		||||
      $string->delete();
 | 
			
		||||
      $this->report['deletes']++;
 | 
			
		||||
      $this->report['strings'][] = $string->lid;
 | 
			
		||||
      return $string->lid;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								web/core/modules/locale/src/SourceString.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								web/core/modules/locale/src/SourceString.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the locale source string object.
 | 
			
		||||
 *
 | 
			
		||||
 * This class represents a module-defined string value that is to be translated.
 | 
			
		||||
 * This string must at least contain a 'source' field, which is the raw source
 | 
			
		||||
 * value, and is assumed to be in English language.
 | 
			
		||||
 */
 | 
			
		||||
class SourceString extends StringBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isSource() {
 | 
			
		||||
    return isset($this->source);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isTranslation() {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getString() {
 | 
			
		||||
    return $this->source ?? '';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setString($string) {
 | 
			
		||||
    $this->source = $string;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isNew() {
 | 
			
		||||
    return empty($this->lid);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,57 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale\StreamWrapper;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\StreamWrapper\LocalStream;
 | 
			
		||||
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a Drupal translations (translations://) stream wrapper class.
 | 
			
		||||
 *
 | 
			
		||||
 * Provides support for storing translation files.
 | 
			
		||||
 */
 | 
			
		||||
class TranslationsStream extends LocalStream {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getType() {
 | 
			
		||||
    return StreamWrapperInterface::LOCAL_HIDDEN;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getName() {
 | 
			
		||||
    return $this->t('Translation files');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDescription() {
 | 
			
		||||
    return $this->t('Translation files');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getDirectoryPath() {
 | 
			
		||||
    return \Drupal::config('locale.settings')->get('translation.path');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * phpcs:ignore Drupal.Files.LineLength
 | 
			
		||||
   * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::getExternalUrl().
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \LogicException
 | 
			
		||||
   *   PO files URL should not be public.
 | 
			
		||||
   */
 | 
			
		||||
  public function getExternalUrl() {
 | 
			
		||||
    throw new \LogicException('PO files URL should not be public.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										208
									
								
								web/core/modules/locale/src/StringBase.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								web/core/modules/locale/src/StringBase.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,208 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Gettext\PoItem;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the locale string base class.
 | 
			
		||||
 *
 | 
			
		||||
 * This is the base class to be used for locale string objects and contains
 | 
			
		||||
 * the common properties and methods for source and translation strings.
 | 
			
		||||
 */
 | 
			
		||||
abstract class StringBase implements StringInterface {
 | 
			
		||||
  /**
 | 
			
		||||
   * The string identifier.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  public $lid;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The string locations indexed by type.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $locations;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The source string.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $source;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The string context.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $context;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The string version.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $version;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The locale storage this string comes from or is to be saved to.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\locale\StringStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $storage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new locale string object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param object|array $values
 | 
			
		||||
   *   Object or array with initial values.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($values = []) {
 | 
			
		||||
    $this->setValues((array) $values);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getId() {
 | 
			
		||||
    return $this->lid ?? NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setId($lid) {
 | 
			
		||||
    $this->lid = $lid;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getVersion() {
 | 
			
		||||
    return $this->version ?? NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setVersion($version) {
 | 
			
		||||
    $this->version = $version;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getPlurals() {
 | 
			
		||||
    return explode(PoItem::DELIMITER, $this->getString());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setPlurals($plurals) {
 | 
			
		||||
    $this->setString(implode(PoItem::DELIMITER, $plurals));
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorage() {
 | 
			
		||||
    return $this->storage ?? NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setStorage($storage) {
 | 
			
		||||
    $this->storage = $storage;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setValues(array $values, $override = TRUE) {
 | 
			
		||||
    foreach ($values as $key => $value) {
 | 
			
		||||
      if (property_exists($this, $key) && ($override || !isset($this->$key))) {
 | 
			
		||||
        $this->$key = $value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getValues(array $fields) {
 | 
			
		||||
    $values = [];
 | 
			
		||||
    foreach ($fields as $field) {
 | 
			
		||||
      if (isset($this->$field)) {
 | 
			
		||||
        $values[$field] = $this->$field;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getLocations($check_only = FALSE) {
 | 
			
		||||
    if (!isset($this->locations) && !$check_only) {
 | 
			
		||||
      $this->locations = [];
 | 
			
		||||
      foreach ($this->getStorage()->getLocations(['sid' => $this->getId()]) as $location) {
 | 
			
		||||
        $this->locations[$location->type][$location->name] = $location->lid;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->locations ?? [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function addLocation($type, $name) {
 | 
			
		||||
    $this->locations[$type][$name] = TRUE;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function hasLocation($type, $name) {
 | 
			
		||||
    $locations = $this->getLocations();
 | 
			
		||||
    return isset($locations[$type]) ? !empty($locations[$type][$name]) : FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function save() {
 | 
			
		||||
    if ($storage = $this->getStorage()) {
 | 
			
		||||
      $storage->save($this);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      throw new StringStorageException('The string cannot be saved because its not bound to a storage: ' . $this->getString());
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function delete() {
 | 
			
		||||
    if (!$this->isNew()) {
 | 
			
		||||
      if ($storage = $this->getStorage()) {
 | 
			
		||||
        $storage->delete($this);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        throw new StringStorageException('The string cannot be deleted because its not bound to a storage: ' . $this->getString());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										545
									
								
								web/core/modules/locale/src/StringDatabaseStorage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										545
									
								
								web/core/modules/locale/src/StringDatabaseStorage.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,545 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Database\Connection;
 | 
			
		||||
use Drupal\Core\Database\Query\PagerSelectExtender;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a class to store localized strings in the database.
 | 
			
		||||
 */
 | 
			
		||||
class StringDatabaseStorage implements StringStorageInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The database connection.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Database\Connection
 | 
			
		||||
   */
 | 
			
		||||
  protected $connection;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Additional database connection options to use in queries.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $options = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new StringDatabaseStorage class.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Database\Connection $connection
 | 
			
		||||
   *   A Database connection to use for reading and writing configuration data.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   (optional) Any additional database connection options to use in queries.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(Connection $connection, array $options = []) {
 | 
			
		||||
    $this->connection = $connection;
 | 
			
		||||
    $this->options = $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getStrings(array $conditions = [], array $options = []) {
 | 
			
		||||
    return $this->dbStringLoad($conditions, $options, 'Drupal\locale\SourceString');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getTranslations(array $conditions = [], array $options = []) {
 | 
			
		||||
    return $this->dbStringLoad($conditions, ['translation' => TRUE] + $options, 'Drupal\locale\TranslationString');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function findString(array $conditions) {
 | 
			
		||||
    $values = $this->dbStringSelect($conditions)
 | 
			
		||||
      ->execute()
 | 
			
		||||
      ->fetchAssoc();
 | 
			
		||||
 | 
			
		||||
    if (!empty($values)) {
 | 
			
		||||
      $string = new SourceString($values);
 | 
			
		||||
      $string->setStorage($this);
 | 
			
		||||
      return $string;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function findTranslation(array $conditions) {
 | 
			
		||||
    $values = $this->dbStringSelect($conditions, ['translation' => TRUE])
 | 
			
		||||
      ->execute()
 | 
			
		||||
      ->fetchAssoc();
 | 
			
		||||
 | 
			
		||||
    if (!empty($values)) {
 | 
			
		||||
      $string = new TranslationString($values);
 | 
			
		||||
      $this->checkVersion($string, \Drupal::VERSION);
 | 
			
		||||
      $string->setStorage($this);
 | 
			
		||||
      return $string;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getLocations(array $conditions = []) {
 | 
			
		||||
    $query = $this->connection->select('locales_location', 'l', $this->options)
 | 
			
		||||
      ->fields('l');
 | 
			
		||||
    foreach ($conditions as $field => $value) {
 | 
			
		||||
      // Cast scalars to array so we can consistently use an IN condition.
 | 
			
		||||
      $query->condition('l.' . $field, (array) $value, 'IN');
 | 
			
		||||
    }
 | 
			
		||||
    return $query->execute()->fetchAll();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function countStrings() {
 | 
			
		||||
    return $this->dbExecute("SELECT COUNT(*) FROM {locales_source}")->fetchField();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function countTranslations() {
 | 
			
		||||
    return $this->dbExecute("SELECT t.language, COUNT(*) AS translated FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid GROUP BY t.language")->fetchAllKeyed();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function save($string) {
 | 
			
		||||
    if ($string->isNew()) {
 | 
			
		||||
      $result = $this->dbStringInsert($string);
 | 
			
		||||
      if ($string->isSource() && $result) {
 | 
			
		||||
        // Only for source strings, we set the locale identifier.
 | 
			
		||||
        $string->setId($result);
 | 
			
		||||
      }
 | 
			
		||||
      $string->setStorage($this);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->dbStringUpdate($string);
 | 
			
		||||
    }
 | 
			
		||||
    // Update locations if they come with the string.
 | 
			
		||||
    $this->updateLocation($string);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update locations for string.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringInterface $string
 | 
			
		||||
   *   The string object.
 | 
			
		||||
   */
 | 
			
		||||
  protected function updateLocation($string) {
 | 
			
		||||
    if ($locations = $string->getLocations(TRUE)) {
 | 
			
		||||
      $created = FALSE;
 | 
			
		||||
      foreach ($locations as $type => $location) {
 | 
			
		||||
        foreach ($location as $name => $lid) {
 | 
			
		||||
          // Make sure that the name isn't longer than 255 characters.
 | 
			
		||||
          $name = substr($name, 0, 255);
 | 
			
		||||
          if (!$lid) {
 | 
			
		||||
            $this->dbDelete('locales_location', ['sid' => $string->getId(), 'type' => $type, 'name' => $name])
 | 
			
		||||
              ->execute();
 | 
			
		||||
          }
 | 
			
		||||
          elseif ($lid === TRUE) {
 | 
			
		||||
            // This is a new location to add, take care not to duplicate.
 | 
			
		||||
            $this->connection->merge('locales_location', $this->options)
 | 
			
		||||
              ->keys(['sid' => $string->getId(), 'type' => $type, 'name' => $name])
 | 
			
		||||
              ->fields(['version' => \Drupal::VERSION])
 | 
			
		||||
              ->execute();
 | 
			
		||||
            $created = TRUE;
 | 
			
		||||
          }
 | 
			
		||||
          // Loaded locations have 'lid' integer value, nor FALSE, nor TRUE.
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if ($created) {
 | 
			
		||||
        // As we've set a new location, check string version too.
 | 
			
		||||
        $this->checkVersion($string, \Drupal::VERSION);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks whether the string version matches a given version, fix it if not.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringInterface $string
 | 
			
		||||
   *   The string object.
 | 
			
		||||
   * @param string $version
 | 
			
		||||
   *   Drupal version to check against.
 | 
			
		||||
   */
 | 
			
		||||
  protected function checkVersion($string, $version) {
 | 
			
		||||
    if ($string->getId() && $string->getVersion() != $version) {
 | 
			
		||||
      $string->setVersion($version);
 | 
			
		||||
      $this->connection->update('locales_source', $this->options)
 | 
			
		||||
        ->condition('lid', $string->getId())
 | 
			
		||||
        ->fields(['version' => $version])
 | 
			
		||||
        ->execute();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function delete($string) {
 | 
			
		||||
    if ($keys = $this->dbStringKeys($string)) {
 | 
			
		||||
      $this->dbDelete('locales_target', $keys)->execute();
 | 
			
		||||
      if ($string->isSource()) {
 | 
			
		||||
        $this->dbDelete('locales_source', $keys)->execute();
 | 
			
		||||
        $this->dbDelete('locales_location', $keys)->execute();
 | 
			
		||||
        $string->setId(NULL);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      throw new StringStorageException('The string cannot be deleted because it lacks some key fields: ' . $string->getString());
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteStrings($conditions) {
 | 
			
		||||
    $lids = $this->dbStringSelect($conditions, ['fields' => ['lid']])->execute()->fetchCol();
 | 
			
		||||
    if ($lids) {
 | 
			
		||||
      $this->dbDelete('locales_target', ['lid' => $lids])->execute();
 | 
			
		||||
      $this->dbDelete('locales_source', ['lid' => $lids])->execute();
 | 
			
		||||
      $this->dbDelete('locales_location', ['sid' => $lids])->execute();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteTranslations($conditions) {
 | 
			
		||||
    $this->dbDelete('locales_target', $conditions)->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function createString($values = []) {
 | 
			
		||||
    return new SourceString($values + ['storage' => $this]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function createTranslation($values = []) {
 | 
			
		||||
    return new TranslationString($values + [
 | 
			
		||||
      'storage' => $this,
 | 
			
		||||
      'is_new' => TRUE,
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets table alias for field.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $field
 | 
			
		||||
   *   One of the field names of the locales_source, locates_location,
 | 
			
		||||
   *   locales_target tables to find the table alias for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   One of the following values:
 | 
			
		||||
   *   - 's' for "source", "context", "version" (locales_source table fields).
 | 
			
		||||
   *   - 'l' for "type", "name" (locales_location table fields)
 | 
			
		||||
   *   - 't' for "language", "translation", "customized" (locales_target
 | 
			
		||||
   *     table fields)
 | 
			
		||||
   */
 | 
			
		||||
  protected function dbFieldTable($field) {
 | 
			
		||||
    if (in_array($field, ['language', 'translation', 'customized'])) {
 | 
			
		||||
      return 't';
 | 
			
		||||
    }
 | 
			
		||||
    elseif (in_array($field, ['type', 'name'])) {
 | 
			
		||||
      return 'l';
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return 's';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets table name for storing string object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringInterface $string
 | 
			
		||||
   *   The string object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string|null
 | 
			
		||||
   *   The table name.
 | 
			
		||||
   */
 | 
			
		||||
  protected function dbStringTable($string) {
 | 
			
		||||
    if ($string->isSource()) {
 | 
			
		||||
      return 'locales_source';
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($string->isTranslation()) {
 | 
			
		||||
      return 'locales_target';
 | 
			
		||||
    }
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets keys values that are in a database table.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringInterface $string
 | 
			
		||||
   *   The string object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Array with key fields if the string has all keys, or empty array if not.
 | 
			
		||||
   */
 | 
			
		||||
  protected function dbStringKeys($string) {
 | 
			
		||||
    if ($string->isSource()) {
 | 
			
		||||
      $keys = ['lid'];
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($string->isTranslation()) {
 | 
			
		||||
      $keys = ['lid', 'language'];
 | 
			
		||||
    }
 | 
			
		||||
    if (!empty($keys) && ($values = $string->getValues($keys)) && count($keys) == count($values)) {
 | 
			
		||||
      return $values;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads multiple string objects.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $conditions
 | 
			
		||||
   *   Any of the conditions used by dbStringSelect().
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   Any of the options used by dbStringSelect().
 | 
			
		||||
   * @param string $class
 | 
			
		||||
   *   Class name to use for fetching returned objects.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\locale\StringInterface[]
 | 
			
		||||
   *   Array of objects of the class requested.
 | 
			
		||||
   */
 | 
			
		||||
  protected function dbStringLoad(array $conditions, array $options, $class) {
 | 
			
		||||
    $strings = [];
 | 
			
		||||
    $result = $this->dbStringSelect($conditions, $options)->execute();
 | 
			
		||||
    foreach ($result as $item) {
 | 
			
		||||
      /** @var \Drupal\locale\StringInterface $string */
 | 
			
		||||
      $string = new $class($item);
 | 
			
		||||
      $string->setStorage($this);
 | 
			
		||||
      $strings[] = $string;
 | 
			
		||||
    }
 | 
			
		||||
    return $strings;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds a SELECT query with multiple conditions and fields.
 | 
			
		||||
   *
 | 
			
		||||
   * The query uses both 'locales_source' and 'locales_target' tables.
 | 
			
		||||
   * Note that by default, as we are selecting both translated and untranslated
 | 
			
		||||
   * strings target field's conditions will be modified to match NULL rows too.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $conditions
 | 
			
		||||
   *   An associative array with field => value conditions that may include
 | 
			
		||||
   *   NULL values. If a language condition is included it will be used for
 | 
			
		||||
   *   joining the 'locales_target' table.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   An associative array of additional options. It may contain any of the
 | 
			
		||||
   *   options used by Drupal\locale\StringStorageInterface::getStrings() and
 | 
			
		||||
   *   these additional ones:
 | 
			
		||||
   *   - 'translation', Whether to include translation fields too. Defaults to
 | 
			
		||||
   *     FALSE.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Database\Query\Select
 | 
			
		||||
   *   Query object with all the tables, fields and conditions.
 | 
			
		||||
   */
 | 
			
		||||
  protected function dbStringSelect(array $conditions, array $options = []) {
 | 
			
		||||
    // Start building the query with source table and check whether we need to
 | 
			
		||||
    // join the target table too.
 | 
			
		||||
    $query = $this->connection->select('locales_source', 's', $this->options)
 | 
			
		||||
      ->fields('s');
 | 
			
		||||
 | 
			
		||||
    // Figure out how to join and translate some options into conditions.
 | 
			
		||||
    if (isset($conditions['translated'])) {
 | 
			
		||||
      // This is a meta-condition we need to translate into simple ones.
 | 
			
		||||
      if ($conditions['translated']) {
 | 
			
		||||
        // Select only translated strings.
 | 
			
		||||
        $join = 'innerJoin';
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // Select only untranslated strings.
 | 
			
		||||
        $join = 'leftJoin';
 | 
			
		||||
        $conditions['translation'] = NULL;
 | 
			
		||||
      }
 | 
			
		||||
      unset($conditions['translated']);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $join = !empty($options['translation']) ? 'leftJoin' : FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($join) {
 | 
			
		||||
      if (isset($conditions['language'])) {
 | 
			
		||||
        // If we've got a language condition, we use it for the join.
 | 
			
		||||
        $query->$join('locales_target', 't', "t.lid = s.lid AND t.language = :langcode", [
 | 
			
		||||
          ':langcode' => $conditions['language'],
 | 
			
		||||
        ]);
 | 
			
		||||
        unset($conditions['language']);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // Since we don't have a language, join with locale id only.
 | 
			
		||||
        $query->$join('locales_target', 't', "t.lid = s.lid");
 | 
			
		||||
      }
 | 
			
		||||
      if (!empty($options['translation'])) {
 | 
			
		||||
        // We cannot just add all fields because 'lid' may get null values.
 | 
			
		||||
        $query->fields('t', ['language', 'translation', 'customized']);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If we have conditions for location's type or name, then we need the
 | 
			
		||||
    // location table, for which we add a subquery. We cast any scalar value to
 | 
			
		||||
    // array so we can consistently use IN conditions.
 | 
			
		||||
    if (isset($conditions['type']) || isset($conditions['name'])) {
 | 
			
		||||
      $subquery = $this->connection->select('locales_location', 'l', $this->options)
 | 
			
		||||
        ->fields('l', ['sid']);
 | 
			
		||||
      foreach (['type', 'name'] as $field) {
 | 
			
		||||
        if (isset($conditions[$field])) {
 | 
			
		||||
          $subquery->condition('l.' . $field, (array) $conditions[$field], 'IN');
 | 
			
		||||
          unset($conditions[$field]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      $query->condition('s.lid', $subquery, 'IN');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add conditions for both tables.
 | 
			
		||||
    foreach ($conditions as $field => $value) {
 | 
			
		||||
      $table_alias = $this->dbFieldTable($field);
 | 
			
		||||
      $field_alias = $table_alias . '.' . $field;
 | 
			
		||||
      if (is_null($value)) {
 | 
			
		||||
        $query->isNull($field_alias);
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($table_alias == 't' && $join === 'leftJoin') {
 | 
			
		||||
        // Conditions for target fields when doing an outer join only make
 | 
			
		||||
        // sense if we add also OR field IS NULL.
 | 
			
		||||
        $query->condition(($this->connection->condition('OR'))
 | 
			
		||||
          ->condition($field_alias, (array) $value, 'IN')
 | 
			
		||||
          ->isNull($field_alias)
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $query->condition($field_alias, (array) $value, 'IN');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Process other options, string filter, query limit, etc.
 | 
			
		||||
    if (!empty($options['filters'])) {
 | 
			
		||||
      if (count($options['filters']) > 1) {
 | 
			
		||||
        $filter = $this->connection->condition('OR');
 | 
			
		||||
        $query->condition($filter);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // If we have a single filter, just add it to the query.
 | 
			
		||||
        $filter = $query;
 | 
			
		||||
      }
 | 
			
		||||
      foreach ($options['filters'] as $field => $string) {
 | 
			
		||||
        $filter->condition($this->dbFieldTable($field) . '.' . $field, '%' . $this->connection->escapeLike($string) . '%', 'LIKE');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!empty($options['pager limit'])) {
 | 
			
		||||
      $query = $query->extend(PagerSelectExtender::class)->limit($options['pager limit']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $query;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a database record for a string object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringInterface $string
 | 
			
		||||
   *   The string object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool|int
 | 
			
		||||
   *   If the operation failed, returns FALSE.
 | 
			
		||||
   *   If it succeeded returns the last insert ID of the query, if one exists.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\locale\StringStorageException
 | 
			
		||||
   *   If the string is not suitable for this storage, an exception is thrown.
 | 
			
		||||
   */
 | 
			
		||||
  protected function dbStringInsert($string) {
 | 
			
		||||
    if ($string->isSource()) {
 | 
			
		||||
      $string->setValues(['context' => '', 'version' => 'none'], FALSE);
 | 
			
		||||
      $fields = $string->getValues(['source', 'context', 'version']);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($string->isTranslation()) {
 | 
			
		||||
      $string->setValues(['customized' => 0], FALSE);
 | 
			
		||||
      $fields = $string->getValues(['lid', 'language', 'translation', 'customized']);
 | 
			
		||||
    }
 | 
			
		||||
    if (!empty($fields)) {
 | 
			
		||||
      return $this->connection->insert($this->dbStringTable($string), $this->options)
 | 
			
		||||
        ->fields($fields)
 | 
			
		||||
        ->execute();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      throw new StringStorageException('The string cannot be saved: ' . $string->getString());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Updates string object in the database.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringInterface $string
 | 
			
		||||
   *   The string object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool|int
 | 
			
		||||
   *   If the record update failed, returns FALSE. If it succeeded, returns
 | 
			
		||||
   *   SAVED_NEW or SAVED_UPDATED.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\locale\StringStorageException
 | 
			
		||||
   *   If the string is not suitable for this storage, an exception is thrown.
 | 
			
		||||
   */
 | 
			
		||||
  protected function dbStringUpdate($string) {
 | 
			
		||||
    if ($string->isSource()) {
 | 
			
		||||
      $values = $string->getValues(['source', 'context', 'version']);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($string->isTranslation()) {
 | 
			
		||||
      $values = $string->getValues(['translation', 'customized']);
 | 
			
		||||
    }
 | 
			
		||||
    if (!empty($values) && $keys = $this->dbStringKeys($string)) {
 | 
			
		||||
      return $this->connection->merge($this->dbStringTable($string), $this->options)
 | 
			
		||||
        ->keys($keys)
 | 
			
		||||
        ->fields($values)
 | 
			
		||||
        ->execute();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      throw new StringStorageException('The string cannot be updated: ' . $string->getString());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates delete query.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $table
 | 
			
		||||
   *   The table name.
 | 
			
		||||
   * @param array $keys
 | 
			
		||||
   *   Array with object keys indexed by field name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Database\Query\Delete
 | 
			
		||||
   *   Returns a new Delete object for the injected database connection.
 | 
			
		||||
   */
 | 
			
		||||
  protected function dbDelete($table, $keys) {
 | 
			
		||||
    $query = $this->connection->delete($table, $this->options);
 | 
			
		||||
    foreach ($keys as $field => $value) {
 | 
			
		||||
      if (!is_array($value)) {
 | 
			
		||||
        $value = [$value];
 | 
			
		||||
      }
 | 
			
		||||
      $query->condition($field, $value, 'IN');
 | 
			
		||||
    }
 | 
			
		||||
    return $query;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Executes an arbitrary SELECT query string with the injected options.
 | 
			
		||||
   */
 | 
			
		||||
  protected function dbExecute($query, array $args = []) {
 | 
			
		||||
    return $this->connection->query($query, $args, $this->options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										217
									
								
								web/core/modules/locale/src/StringInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								web/core/modules/locale/src/StringInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,217 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the locale string interface.
 | 
			
		||||
 */
 | 
			
		||||
interface StringInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the string unique identifier.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The string identifier.
 | 
			
		||||
   */
 | 
			
		||||
  public function getId();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the string unique identifier.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $id
 | 
			
		||||
   *   The string identifier.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setId($id);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the string version.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Version identifier.
 | 
			
		||||
   */
 | 
			
		||||
  public function getVersion();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the string version.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $version
 | 
			
		||||
   *   Version identifier.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setVersion($version);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets plain string contained in this object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The string contained in this object.
 | 
			
		||||
   */
 | 
			
		||||
  public function getString();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the string contained in this object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   String to set as value.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setString($string);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Splits string to work with plural values.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Array of strings that are plural variants.
 | 
			
		||||
   */
 | 
			
		||||
  public function getPlurals();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets this string using array of plural values.
 | 
			
		||||
   *
 | 
			
		||||
   * Serializes plural variants in one string glued by
 | 
			
		||||
   * \Drupal\Component\Gettext\PoItem::DELIMITER.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $plurals
 | 
			
		||||
   *   Array of strings with plural variants.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setPlurals($plurals);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the string storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\locale\StringStorageInterface
 | 
			
		||||
   *   The storage used for this string.
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorage();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the string storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringStorageInterface $storage
 | 
			
		||||
   *   The storage to use for this string.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setStorage($storage);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks whether the object is not saved to storage yet.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the object exists in the storage, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function isNew();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks whether the object is a source string.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the object is a source string, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function isSource();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks whether the object is a translation string.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the object is a translation string, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function isTranslation();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets an array of values as object properties.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   Array with values indexed by property name.
 | 
			
		||||
   * @param bool $override
 | 
			
		||||
   *   (optional) Whether to override already set fields, defaults to TRUE.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function setValues(array $values, $override = TRUE);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets field values that are set for given field names.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $fields
 | 
			
		||||
   *   Array of field names.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Array of field values indexed by field name.
 | 
			
		||||
   */
 | 
			
		||||
  public function getValues(array $fields);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets location information for this string.
 | 
			
		||||
   *
 | 
			
		||||
   * Locations are arbitrary pairs of type and name strings, used to store
 | 
			
		||||
   * information about the origins of the string, like the file name it
 | 
			
		||||
   * was found on, the path on which it was discovered, etc.
 | 
			
		||||
   *
 | 
			
		||||
   * A string can have any number of locations since the same string may be
 | 
			
		||||
   * found on different places of Drupal code and configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @param bool $check_only
 | 
			
		||||
   *   (optional) Set to TRUE to get only new locations added during the
 | 
			
		||||
   *   current page request and not loading all existing locations.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Location ids indexed by type and name.
 | 
			
		||||
   */
 | 
			
		||||
  public function getLocations($check_only = FALSE);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds a location for this string.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   Location type that may be any arbitrary string. Types used in Drupal
 | 
			
		||||
   *   core are: 'javascript', 'path', 'code', 'configuration'.
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Location name. Drupal path in case of online discovered translations,
 | 
			
		||||
   *   file path in case of imported strings, configuration name for strings
 | 
			
		||||
   *   that come from configuration, etc.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function addLocation($type, $name);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks whether the string has a given location.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   Location type.
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Location name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the string has a location with this type and name.
 | 
			
		||||
   */
 | 
			
		||||
  public function hasLocation($type, $name);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves string object to storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\locale\StringStorageException
 | 
			
		||||
   *   In case of failures, an exception is thrown.
 | 
			
		||||
   */
 | 
			
		||||
  public function save();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deletes string object from storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\locale\StringStorageException
 | 
			
		||||
   *   In case of failures, an exception is thrown.
 | 
			
		||||
   */
 | 
			
		||||
  public function delete();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								web/core/modules/locale/src/StringStorageException.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								web/core/modules/locale/src/StringStorageException.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines an exception thrown when storage operations fail.
 | 
			
		||||
 */
 | 
			
		||||
class StringStorageException extends \Exception {}
 | 
			
		||||
							
								
								
									
										180
									
								
								web/core/modules/locale/src/StringStorageInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								web/core/modules/locale/src/StringStorageInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,180 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the locale string storage interface.
 | 
			
		||||
 */
 | 
			
		||||
interface StringStorageInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads multiple source string objects.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $conditions
 | 
			
		||||
   *   (optional) Array with conditions that will be used to filter the strings
 | 
			
		||||
   *   returned and may include any of the following elements:
 | 
			
		||||
   *   - Any simple field value indexed by field name.
 | 
			
		||||
   *   - 'translated', TRUE to get only translated strings or FALSE to get only
 | 
			
		||||
   *     untranslated strings. If not set it returns both translated and
 | 
			
		||||
   *     untranslated strings that fit the other conditions.
 | 
			
		||||
   *   Defaults to no conditions which means that it will load all strings.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   (optional) An associative array of additional options. It may contain
 | 
			
		||||
   *   any of the following optional keys:
 | 
			
		||||
   *   - 'filters': Array of string filters indexed by field name.
 | 
			
		||||
   *   - 'pager limit': Use pager and set this limit value.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Array of \Drupal\locale\StringInterface objects matching the conditions.
 | 
			
		||||
   */
 | 
			
		||||
  public function getStrings(array $conditions = [], array $options = []);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads multiple string translation objects.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $conditions
 | 
			
		||||
   *   (optional) Array with conditions that will be used to filter the strings
 | 
			
		||||
   *   returned and may include all of the conditions defined by getStrings().
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   (optional) An associative array of additional options. It may contain
 | 
			
		||||
   *   any of the options defined by getStrings().
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\locale\StringInterface[]
 | 
			
		||||
   *   Array of \Drupal\locale\StringInterface objects matching the conditions.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\locale\StringStorageInterface::getStrings()
 | 
			
		||||
   */
 | 
			
		||||
  public function getTranslations(array $conditions = [], array $options = []);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads string location information.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $conditions
 | 
			
		||||
   *   (optional) Array with conditions to filter the locations that may be any
 | 
			
		||||
   *   of the following elements:
 | 
			
		||||
   *   - 'sid', The string identifier.
 | 
			
		||||
   *   - 'type', The location type.
 | 
			
		||||
   *   - 'name', The location name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\locale\StringInterface[]
 | 
			
		||||
   *   Array of \Drupal\locale\StringInterface objects matching the conditions.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\locale\StringStorageInterface::getStrings()
 | 
			
		||||
   */
 | 
			
		||||
  public function getLocations(array $conditions = []);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads a string source object, fast query.
 | 
			
		||||
   *
 | 
			
		||||
   * These 'fast query' methods are the ones in the critical path and their
 | 
			
		||||
   * implementation must be optimized for speed, as they may run many times
 | 
			
		||||
   * in a single page request.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $conditions
 | 
			
		||||
   *   (optional) Array with conditions that will be used to filter the strings
 | 
			
		||||
   *   returned and may include all of the conditions defined by getStrings().
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\locale\SourceString|null
 | 
			
		||||
   *   Minimal TranslationString object if found, NULL otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function findString(array $conditions);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads a string translation object, fast query.
 | 
			
		||||
   *
 | 
			
		||||
   * This function must only be used when actually translating strings as it
 | 
			
		||||
   * will have the effect of updating the string version. For other purposes
 | 
			
		||||
   * the getTranslations() method should be used instead.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $conditions
 | 
			
		||||
   *   (optional) Array with conditions that will be used to filter the strings
 | 
			
		||||
   *   returned and may include all of the conditions defined by getStrings().
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\locale\TranslationString|null
 | 
			
		||||
   *   Minimal TranslationString object if found, NULL otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function findTranslation(array $conditions);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Save string object to storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringInterface $string
 | 
			
		||||
   *   The string object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   *   The called object.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\locale\StringStorageException
 | 
			
		||||
   *   In case of failures, an exception is thrown.
 | 
			
		||||
   */
 | 
			
		||||
  public function save($string);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete string from storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\locale\StringInterface $string
 | 
			
		||||
   *   The string object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   *   The called object.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\locale\StringStorageException
 | 
			
		||||
   *   In case of failures, an exception is thrown.
 | 
			
		||||
   */
 | 
			
		||||
  public function delete($string);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deletes source strings and translations using conditions.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $conditions
 | 
			
		||||
   *   Array with simple field conditions for source strings.
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteStrings($conditions);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deletes translations using conditions.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $conditions
 | 
			
		||||
   *   Array with simple field conditions for string translations.
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteTranslations($conditions);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Counts source strings.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The number of source strings contained in the storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function countStrings();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Counts translations.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The number of translations for each language indexed by language code.
 | 
			
		||||
   */
 | 
			
		||||
  public function countTranslations();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a source string object bound to this storage but not saved.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   (optional) Array with initial values. Defaults to empty array.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\locale\SourceString
 | 
			
		||||
   *   New source string object.
 | 
			
		||||
   */
 | 
			
		||||
  public function createString($values = []);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a string translation object bound to this storage but not saved.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   (optional) Array with initial values. Defaults to empty array.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\locale\TranslationString
 | 
			
		||||
   *   New string translation object.
 | 
			
		||||
   */
 | 
			
		||||
  public function createTranslation($values = []);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										123
									
								
								web/core/modules/locale/src/TranslationString.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								web/core/modules/locale/src/TranslationString.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,123 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\locale;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the locale translation string object.
 | 
			
		||||
 *
 | 
			
		||||
 * This class represents a translation of a source string to a given language,
 | 
			
		||||
 * thus it must have at least a 'language' which is the language code and a
 | 
			
		||||
 * 'translation' property which is the translated text of the source string
 | 
			
		||||
 * in the specified language.
 | 
			
		||||
 */
 | 
			
		||||
class TranslationString extends StringBase {
 | 
			
		||||
  /**
 | 
			
		||||
   * The language code.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $language;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The string translation.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $translation;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Integer indicating whether this string is customized.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  public $customized;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Boolean indicating whether the string object is new.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected $isNew;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($values = []) {
 | 
			
		||||
    parent::__construct($values);
 | 
			
		||||
    if (!isset($this->isNew)) {
 | 
			
		||||
      // We mark the string as not new if it is a complete translation.
 | 
			
		||||
      // This will work when loading from database, otherwise the storage
 | 
			
		||||
      // controller that creates the string object must handle it.
 | 
			
		||||
      $this->isNew = !$this->isTranslation();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the string as customized / not customized.
 | 
			
		||||
   *
 | 
			
		||||
   * @param bool $customized
 | 
			
		||||
   *   (optional) Whether the string is customized or not. Defaults to TRUE.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   *   The called object.
 | 
			
		||||
   */
 | 
			
		||||
  public function setCustomized($customized = TRUE) {
 | 
			
		||||
    $this->customized = $customized ? LOCALE_CUSTOMIZED : LOCALE_NOT_CUSTOMIZED;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isSource() {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isTranslation() {
 | 
			
		||||
    return !empty($this->lid) && !empty($this->language) && isset($this->translation);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getString() {
 | 
			
		||||
    return $this->translation ?? '';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setString($string) {
 | 
			
		||||
    $this->translation = $string;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function isNew() {
 | 
			
		||||
    return $this->isNew;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function save() {
 | 
			
		||||
    parent::save();
 | 
			
		||||
    $this->isNew = FALSE;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function delete() {
 | 
			
		||||
    parent::delete();
 | 
			
		||||
    $this->isNew = TRUE;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user