Move all files to 2017/
This commit is contained in:
parent
ac7370f67f
commit
2875863330
15717 changed files with 0 additions and 0 deletions
|
|
@ -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']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[LocaleEvents::SAVE_TRANSLATION][] = ['saveTranslation'];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
187
2017/web/core/modules/locale/src/Form/ExportForm.php
Normal file
187
2017/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.'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
199
2017/web/core/modules/locale/src/Form/ImportForm.php
Normal file
199
2017/web/core/modules/locale/src/Form/ImportForm.php
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale\Form;
|
||||
|
||||
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 = [
|
||||
'file_validate_extensions' => ['po'],
|
||||
'file_validate_size' => [file_upload_max_size()],
|
||||
];
|
||||
$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();
|
||||
$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');
|
||||
}
|
||||
|
||||
}
|
||||
142
2017/web/core/modules/locale/src/Form/LocaleSettingsForm.php
Normal file
142
2017/web/core/modules/locale/src/Form/LocaleSettingsForm.php
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* 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'),
|
||||
'#default_value' => $config->get('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' => $this->url('locale.check_translation')]),
|
||||
];
|
||||
|
||||
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' => $this->url('system.file_system_settings')]);
|
||||
}
|
||||
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' => $this->url('system.file_system_settings')]);
|
||||
}
|
||||
$form['#translation_directory'] = $directory;
|
||||
$form['use_source'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Translation source'),
|
||||
'#default_value' => $config->get('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,
|
||||
];
|
||||
|
||||
if ($config->get('translation.overwrite_not_customized') == FALSE) {
|
||||
$default = LOCALE_TRANSLATION_OVERWRITE_NONE;
|
||||
}
|
||||
elseif ($config->get('translation.overwrite_customized') == TRUE) {
|
||||
$default = LOCALE_TRANSLATION_OVERWRITE_ALL;
|
||||
}
|
||||
else {
|
||||
$default = LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED;
|
||||
}
|
||||
$form['overwrite'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Import behavior'),
|
||||
'#default_value' => $default,
|
||||
'#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.'),
|
||||
];
|
||||
|
||||
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' => $this->url('system.file_system_settings')]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$values = $form_state->getValues();
|
||||
|
||||
$config = $this->config('locale.settings');
|
||||
$config->set('translation.update_interval_days', $values['update_interval_days'])->save();
|
||||
$config->set('translation.use_source', $values['use_source'])->save();
|
||||
|
||||
switch ($values['overwrite']) {
|
||||
case LOCALE_TRANSLATION_OVERWRITE_ALL:
|
||||
$config
|
||||
->set('translation.overwrite_customized', TRUE)
|
||||
->set('translation.overwrite_not_customized', TRUE)
|
||||
->save();
|
||||
break;
|
||||
|
||||
case LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED:
|
||||
$config
|
||||
->set('translation.overwrite_customized', FALSE)
|
||||
->set('translation.overwrite_not_customized', TRUE)
|
||||
->save();
|
||||
break;
|
||||
|
||||
case LOCALE_TRANSLATION_OVERWRITE_NONE:
|
||||
$config
|
||||
->set('translation.overwrite_customized', FALSE)
|
||||
->set('translation.overwrite_not_customized', FALSE)
|
||||
->save();
|
||||
break;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
241
2017/web/core/modules/locale/src/Form/TranslateEditForm.php
Normal file
241
2017/web/core/modules/locale/src/Form/TranslateEditForm.php
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale\Form;
|
||||
|
||||
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();
|
||||
|
||||
$langname = 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' => $langname,
|
||||
'#header' => [
|
||||
$this->t('Source string'),
|
||||
$this->t('Translation for @language', ['@language' => $langname]),
|
||||
],
|
||||
'#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],
|
||||
'#preffix' => '<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],
|
||||
'#preffix' => '<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' => $langname]),
|
||||
'#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' => isset($translation_array[$i]) ? $translation_array[$i] : '',
|
||||
'#attributes' => ['lang' => $langcode],
|
||||
'#prefix' => $i == 0 ? ('<span class="visually-hidden">' . $this->t('Translated string (@language)', ['@language' => $langname]) . '</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(LOCALE_PLURAL_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 = isset($existing_translation_objects[$lid]) ? $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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
102
2017/web/core/modules/locale/src/Form/TranslateFilterForm.php
Normal file
102
2017/web/core/modules/locale/src/Form/TranslateFilterForm.php
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<?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,
|
||||
];
|
||||
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 = isset($filter['options'][$filter['default']]) ? $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 (!empty($_SESSION['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();
|
||||
foreach ($filters as $name => $filter) {
|
||||
if ($form_state->hasValue($name)) {
|
||||
$_SESSION['locale_translate_filter'][$name] = $form_state->getValue($name);
|
||||
}
|
||||
}
|
||||
$form_state->setRedirect('locale.translate_page');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a submit handler for the reset button.
|
||||
*/
|
||||
public function resetForm(array &$form, FormStateInterface $form_state) {
|
||||
$_SESSION['locale_translate_filter'] = [];
|
||||
$form_state->setRedirect('locale.translate_page');
|
||||
}
|
||||
|
||||
}
|
||||
214
2017/web/core/modules/locale/src/Form/TranslateFormBase.php
Normal file
214
2017/web/core/modules/locale/src/Form/TranslateFormBase.php
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
<?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();
|
||||
foreach ($filters as $key => $filter) {
|
||||
$filter_values[$key] = $filter['default'];
|
||||
// Let the filter defaults be overwritten by parameters in the URL.
|
||||
if ($this->getRequest()->query->has($key)) {
|
||||
// Only allow this value if it was among the options, or
|
||||
// if there were no fixed options to filter for.
|
||||
$value = $this->getRequest()->query->get($key);
|
||||
if (!isset($filter['options']) || isset($filter['options'][$value])) {
|
||||
$filter_values[$key] = $value;
|
||||
}
|
||||
}
|
||||
elseif (isset($_SESSION['locale_translate_filter'][$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['locale_translate_filter'][$key]])) {
|
||||
$filter_values[$key] = $_SESSION['locale_translate_filter'][$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;
|
||||
}
|
||||
|
||||
}
|
||||
298
2017/web/core/modules/locale/src/Form/TranslationStatusForm.php
Normal file
298
2017/web/core/modules/locale/src/Form/TranslationStatusForm.php
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale\Form;
|
||||
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a translation status form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TranslationStatusForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The Drupal state storage service.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('module_handler'),
|
||||
$container->get('state')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a TranslationStatusForm object.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* A module handler.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state service.
|
||||
*/
|
||||
public function __construct(ModuleHandlerInterface $module_handler, StateInterface $state) {
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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' => $this->url('entity.configurable_language.collection'),
|
||||
]);
|
||||
}
|
||||
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' => $this->url('locale.check_translation'),
|
||||
]);
|
||||
}
|
||||
|
||||
// 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_id => $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 = isset($project_info->files[LOCALE_TRANSLATION_LOCAL]) ? $project_info->files[LOCALE_TRANSLATION_LOCAL] : NULL;
|
||||
$remote = isset($project_info->files[LOCALE_TRANSLATION_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 = isset($project_info->files['remote']->uri) ? $project_info->files['remote']->uri : FALSE;
|
||||
$local_path = isset($project_info->files['local']->uri) ? $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 an 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 < REQUEST_TIME - 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
2017/web/core/modules/locale/src/Gettext.php
Normal file
97
2017/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 an 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;
|
||||
}
|
||||
|
||||
}
|
||||
24
2017/web/core/modules/locale/src/Locale.php
Normal file
24
2017/web/core/modules/locale/src/Locale.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
/**
|
||||
* Static service container wrapper for locale.
|
||||
*/
|
||||
class Locale {
|
||||
|
||||
/**
|
||||
* Returns the locale configuration manager service.
|
||||
*
|
||||
* Use the locale config manager service for creating locale-wrapped typed
|
||||
* configuration objects.
|
||||
*
|
||||
* @see \Drupal\Core\TypedData\TypedDataManager::create()
|
||||
*
|
||||
* @return \Drupal\locale\LocaleConfigManager
|
||||
*/
|
||||
public static function config() {
|
||||
return \Drupal::service('locale.config_manager');
|
||||
}
|
||||
|
||||
}
|
||||
651
2017/web/core/modules/locale/src/LocaleConfigManager.php
Normal file
651
2017/web/core/modules/locale/src/LocaleConfigManager.php
Normal file
|
|
@ -0,0 +1,651 @@
|
|||
<?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.
|
||||
*
|
||||
* @see self::isUpdatingFromLocale()
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
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'];
|
||||
}
|
||||
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) Array of component lists indexed by type. If not present or it
|
||||
* is an empty array, it will update all components.
|
||||
*
|
||||
* @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 ? $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;
|
||||
}
|
||||
|
||||
}
|
||||
228
2017/web/core/modules/locale/src/LocaleConfigSubscriber.php
Normal file
228
2017/web/core/modules/locale/src/LocaleConfigSubscriber.php
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
<?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\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;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
$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 (!drupal_installation_attempted() && !$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 (!drupal_installation_attempted() && !$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.
|
||||
*/
|
||||
protected 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 = isset($reference_config[$key]) ? $reference_config[$key] : [];
|
||||
$this->processTranslatableData($name, $config[$key], $item, $langcode, $reference_config);
|
||||
}
|
||||
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)) {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
159
2017/web/core/modules/locale/src/LocaleDefaultConfigStorage.php
Normal file
159
2017/web/core/modules/locale/src/LocaleDefaultConfigStorage.php
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
<?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.
|
||||
*/
|
||||
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 (strpos($name, 'language.entity.') === 0) {
|
||||
// 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($type, $list)),
|
||||
array_keys($this->optionalInstallStorage->getComponentNames($type, $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);
|
||||
}
|
||||
|
||||
}
|
||||
57
2017/web/core/modules/locale/src/LocaleEvent.php
Normal file
57
2017/web/core/modules/locale/src/LocaleEvent.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Symfony\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 $original;
|
||||
|
||||
/**
|
||||
* 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
2017/web/core/modules/locale/src/LocaleEvents.php
Normal file
24
2017/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';
|
||||
|
||||
}
|
||||
193
2017/web/core/modules/locale/src/LocaleLookup.php
Normal file
193
2017/web/core/modules/locale/src/LocaleLookup.php
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
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 Drupal\Core\StringTranslation\PluralTranslatableMarkup;
|
||||
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 = $user ? implode(':', $user->getRoles()) : '';
|
||||
$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) && strpos($value, PluralTranslatableMarkup::DELIMITER) !== FALSE) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
169
2017/web/core/modules/locale/src/LocaleProjectStorage.php
Normal file
169
2017/web/core/modules/locale/src/LocaleProjectStorage.php
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
<?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 static $all = 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 isset($values[$key]) ? $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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 = [];
|
||||
static::$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($cache[$key])) {
|
||||
$cache[$key] = $projects[$key];
|
||||
}
|
||||
}
|
||||
$this->keyValueStore->setMultiple($projects);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function countProjects() {
|
||||
return count($this->getAll());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAll() {
|
||||
if (!static::$all) {
|
||||
$this->cache = $this->keyValueStore->getAll();
|
||||
static::$all = TRUE;
|
||||
}
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
156
2017/web/core/modules/locale/src/LocaleTranslation.php
Normal file
156
2017/web/core/modules/locale/src/LocaleTranslation.php
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale\Plugin\QueueWorker;
|
||||
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Queue\QueueInterface;
|
||||
use Drupal\Core\Queue\QueueWorkerBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Executes interface translation queue tasks.
|
||||
*
|
||||
* @QueueWorker(
|
||||
* id = "locale_translation",
|
||||
* title = @Translation("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');
|
||||
list($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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
111
2017/web/core/modules/locale/src/PluralFormula.php
Normal file
111
2017/web/core/modules/locale/src/PluralFormula.php
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<?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.
|
||||
*
|
||||
* For example the structure looks like this:
|
||||
* @code
|
||||
* [
|
||||
* 'de' => [
|
||||
* 'plurals' => 2,
|
||||
* 'formula' => [
|
||||
* // @todo
|
||||
* ]
|
||||
* ],
|
||||
* ]
|
||||
* @endcode
|
||||
* @var array
|
||||
*/
|
||||
protected $formulae;
|
||||
|
||||
/**
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* @param \Drupal\Core\State\StateInterface $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 isset($this->formulae[$langcode]['formula']) ? $this->formulae[$langcode]['formula'] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the formulae and stores them on the PluralFormula object if not set.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
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
2017/web/core/modules/locale/src/PluralFormulaInterface.php
Normal file
54
2017/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
2017/web/core/modules/locale/src/PoDatabaseReader.php
Normal file
171
2017/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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
289
2017/web/core/modules/locale/src/PoDatabaseWriter.php
Normal file
289
2017/web/core/modules/locale/src/PoDatabaseWriter.php
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
<?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)) {
|
||||
list($nplurals, $formula) = $p;
|
||||
\Drupal::service('locale.plural.formula')->setPluralFormula($langcode, $nplurals, $formula);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeItem(PoItem $item) {
|
||||
if ($item->isPlural()) {
|
||||
$item->setSource(implode(LOCALE_PLURAL_DELIMITER, $item->getSource()));
|
||||
$item->setTranslation(implode(LOCALE_PLURAL_DELIMITER, $item->getTranslation()));
|
||||
}
|
||||
$this->importString($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeItems(PoReaderInterface $reader, $count = -1) {
|
||||
$forever = $count == -1;
|
||||
while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
|
||||
$this->writeItem($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
2017/web/core/modules/locale/src/SourceString.php
Normal file
50
2017/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 isset($this->source) ? $this->source : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setString($string) {
|
||||
$this->source = $string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isNew() {
|
||||
return empty($this->lid);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale\StreamWrapper;
|
||||
|
||||
use Drupal\Core\StreamWrapper\LocalStream;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
|
||||
|
||||
/**
|
||||
* Defines a Drupal translations (translations://) stream wrapper class.
|
||||
*
|
||||
* Provides support for storing translation files.
|
||||
*/
|
||||
class TranslationsStream extends LocalStream {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getType() {
|
||||
return StreamWrapperInterface::LOCAL_HIDDEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName() {
|
||||
return t('Translation files');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return t('Translation files');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDirectoryPath() {
|
||||
return \Drupal::config('locale.settings')->get('translation.path');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.');
|
||||
}
|
||||
|
||||
}
|
||||
206
2017/web/core/modules/locale/src/StringBase.php
Normal file
206
2017/web/core/modules/locale/src/StringBase.php
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
/**
|
||||
* 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 isset($this->lid) ? $this->lid : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setId($lid) {
|
||||
$this->lid = $lid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVersion() {
|
||||
return isset($this->version) ? $this->version : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setVersion($version) {
|
||||
$this->version = $version;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPlurals() {
|
||||
return explode(LOCALE_PLURAL_DELIMITER, $this->getString());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPlurals($plurals) {
|
||||
$this->setString(implode(LOCALE_PLURAL_DELIMITER, $plurals));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStorage() {
|
||||
return isset($this->storage) ? $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 isset($this->locations) ? $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;
|
||||
}
|
||||
|
||||
}
|
||||
541
2017/web/core/modules/locale/src/StringDatabaseStorage.php
Normal file
541
2017/web/core/modules/locale/src/StringDatabaseStorage.php
Normal file
|
|
@ -0,0 +1,541 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* The table name.
|
||||
*/
|
||||
protected function dbStringTable($string) {
|
||||
if ($string->isSource()) {
|
||||
return 'locales_source';
|
||||
}
|
||||
elseif ($string->isTranslation()) {
|
||||
return 'locales_target';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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((new 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 = new 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, '%' . db_like($string) . '%', 'LIKE');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($options['pager limit'])) {
|
||||
$query = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender')->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) {
|
||||
$query->condition($field, $value);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
216
2017/web/core/modules/locale/src/StringInterface.php
Normal file
216
2017/web/core/modules/locale/src/StringInterface.php
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
<?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 LOCALE_PLURAL_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();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
/**
|
||||
* Defines an exception thrown when storage operations fail.
|
||||
*/
|
||||
class StringStorageException extends \Exception {}
|
||||
180
2017/web/core/modules/locale/src/StringStorageInterface.php
Normal file
180
2017/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 \Drupal\locale\StringStorageInterface
|
||||
* 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 \Drupal\locale\StringStorageInterface
|
||||
* 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
2017/web/core/modules/locale/src/TranslationString.php
Normal file
123
2017/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 \Drupal\locale\TranslationString
|
||||
* 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 isset($this->translation) ? $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 a new issue