Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
56
core/modules/locale/src/Controller/LocaleController.php
Normal file
56
core/modules/locale/src/Controller/LocaleController.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Controller\LocaleController.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
/**
|
||||
* 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 array(
|
||||
'filter' => $this->formBuilder()->getForm('Drupal\locale\Form\TranslateFilterForm'),
|
||||
'form' => $this->formBuilder()->getForm('Drupal\locale\Form\TranslateEditForm'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\EventSubscriber\LocaleTranslationCacheTag.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
179
core/modules/locale/src/Form/ExportForm.php
Normal file
179
core/modules/locale/src/Form/ExportForm.php
Normal file
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Form\ExportForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Form;
|
||||
|
||||
use Drupal\Component\Gettext\PoStreamWriter;
|
||||
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.
|
||||
*/
|
||||
class ExportForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Constructs a new ExportForm.
|
||||
*
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
*/
|
||||
public function __construct(LanguageManagerInterface $language_manager) {
|
||||
$this->languageManager = $language_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('language_manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'locale_translate_export_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$languages = $this->languageManager->getLanguages();
|
||||
$language_options = array();
|
||||
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'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => LanguageInterface::LANGCODE_SYSTEM,
|
||||
);
|
||||
$form['langcode_text'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Language'),
|
||||
'#markup' => $this->t('No language available. The export will only contain source strings.'),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$form['langcode'] = array(
|
||||
'#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'] = array(
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Export options'),
|
||||
'#collapsed' => TRUE,
|
||||
'#tree' => TRUE,
|
||||
'#states' => array(
|
||||
'invisible' => array(
|
||||
':input[name="langcode"]' => array('value' => LanguageInterface::LANGCODE_SYSTEM),
|
||||
),
|
||||
),
|
||||
);
|
||||
$form['content_options']['not_customized'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Include non-customized translations'),
|
||||
'#default_value' => TRUE,
|
||||
);
|
||||
$form['content_options']['customized'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Include customized translations'),
|
||||
'#default_value' => TRUE,
|
||||
);
|
||||
$form['content_options']['not_translated'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Include untranslated text'),
|
||||
'#default_value' => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
$form['actions'] = array(
|
||||
'#type' => 'actions',
|
||||
);
|
||||
$form['actions']['submit'] = array(
|
||||
'#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', array());
|
||||
$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 = 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 {
|
||||
drupal_set_message($this->t('Nothing to export.'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
200
core/modules/locale/src/Form/ImportForm.php
Normal file
200
core/modules/locale/src/Form/ImportForm.php
Normal file
|
@ -0,0 +1,200 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Form\ImportForm.
|
||||
*/
|
||||
|
||||
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.
|
||||
*/
|
||||
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 = array();
|
||||
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 = array(
|
||||
$this->t('Existing languages') => $existing_languages,
|
||||
$this->t('Languages not yet added') => $this->languageManager->getStandardLanguageListWithoutConfigured(),
|
||||
);
|
||||
}
|
||||
|
||||
$validators = array(
|
||||
'file_validate_extensions' => array('po'),
|
||||
'file_validate_size' => array(file_upload_max_size()),
|
||||
);
|
||||
$form['file'] = array(
|
||||
'#type' => 'file',
|
||||
'#title' => $this->t('Translation file'),
|
||||
'#description' => array(
|
||||
'#theme' => 'file_upload_help',
|
||||
'#description' => $this->t('A Gettext Portable Object file.'),
|
||||
'#upload_validators' => $validators,
|
||||
),
|
||||
'#size' => 50,
|
||||
'#upload_validators' => $validators,
|
||||
'#attributes' => array('class' => array('file-import-input')),
|
||||
);
|
||||
$form['langcode'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Language'),
|
||||
'#options' => $language_options,
|
||||
'#default_value' => $default,
|
||||
'#attributes' => array('class' => array('langcode-input')),
|
||||
);
|
||||
|
||||
$form['customized'] = array(
|
||||
'#title' => $this->t('Treat imported strings as custom translations'),
|
||||
'#type' => 'checkbox',
|
||||
);
|
||||
$form['overwrite_options'] = array(
|
||||
'#type' => 'container',
|
||||
'#tree' => TRUE,
|
||||
);
|
||||
$form['overwrite_options']['not_customized'] = array(
|
||||
'#title' => $this->t('Overwrite non-customized translations'),
|
||||
'#type' => 'checkbox',
|
||||
'#states' => array(
|
||||
'checked' => array(
|
||||
':input[name="customized"]' => array('checked' => TRUE),
|
||||
),
|
||||
),
|
||||
);
|
||||
$form['overwrite_options']['customized'] = array(
|
||||
'#title' => $this->t('Overwrite existing customized translations'),
|
||||
'#type' => 'checkbox',
|
||||
);
|
||||
|
||||
$form['actions'] = array(
|
||||
'#type' => 'actions',
|
||||
);
|
||||
$form['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Import'),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->file = file_save_upload('file', $form['file']['#upload_validators'], 'translations://', 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) {
|
||||
\Drupal::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();
|
||||
drupal_set_message($this->t('The language %language has been created.', array('%language' => $this->t($language->label()))));
|
||||
}
|
||||
$options = array_merge(_locale_translation_default_update_options(), array(
|
||||
'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(array($file->uri => $file), $options);
|
||||
batch_set($batch);
|
||||
|
||||
// Create or update all configuration translations for this language.
|
||||
\Drupal::moduleHandler()->loadInclude('locale', 'bulk.inc');
|
||||
if ($batch = locale_config_batch_update_components($options, array($form_state->getValue('langcode')))) {
|
||||
batch_set($batch);
|
||||
}
|
||||
|
||||
$form_state->setRedirect('locale.translate_page');
|
||||
}
|
||||
}
|
144
core/modules/locale/src/Form/LocaleSettingsForm.php
Normal file
144
core/modules/locale/src/Form/LocaleSettingsForm.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Form\LocaleSettingsForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Configure locale settings for this site.
|
||||
*/
|
||||
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'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Check for updates'),
|
||||
'#default_value' => $config->get('translation.update_interval_days'),
|
||||
'#options' => array(
|
||||
'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>.', array('@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.', array('%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.', array('@url' => $this->url('system.file_system_settings')));
|
||||
}
|
||||
$form['#translation_directory'] = $directory;
|
||||
$form['use_source'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Translation source'),
|
||||
'#default_value' => $config->get('translation.use_source'),
|
||||
'#options' => array(
|
||||
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'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Import behavior'),
|
||||
'#default_value' => $default,
|
||||
'#options' => array(
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Form\FormInterface::validateForm().
|
||||
*/
|
||||
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.', array('@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);
|
||||
}
|
||||
|
||||
}
|
239
core/modules/locale/src/Form/TranslateEditForm.php
Normal file
239
core/modules/locale/src/Form/TranslateEditForm.php
Normal file
|
@ -0,0 +1,239 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Form\TranslateEditForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Form;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\locale\SourceString;
|
||||
|
||||
/**
|
||||
* Defines a translation edit form.
|
||||
*/
|
||||
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'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $filter_values['langcode'],
|
||||
);
|
||||
|
||||
$form['strings'] = array(
|
||||
'#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'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Source string (@language)', array('@language' => $this->t('Built-in English'))),
|
||||
'#title_display' => 'invisible',
|
||||
'#markup' => '<span lang="en">' . SafeMarkup::checkPlain($source_array[0]) . '</span>',
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Add original string value and mark as plural.
|
||||
$plural = TRUE;
|
||||
$original_singular = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Singular form'),
|
||||
'#markup' => '<span lang="en">' . SafeMarkup::checkPlain($source_array[0]) . '</span>',
|
||||
'#prefix' => '<span class="visually-hidden">' . $this->t('Source string (@language)', array('@language' => $this->t('Built-in English'))) . '</span>',
|
||||
];
|
||||
$original_plural = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Plural form'),
|
||||
'#markup' => '<span lang="en">' . SafeMarkup::checkPlain($source_array[1]) . '</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] = array(
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Translated string (@language)', array('@language' => $langname)),
|
||||
'#title_display' => 'invisible',
|
||||
'#rows' => $rows,
|
||||
'#default_value' => $translation_array[0],
|
||||
'#attributes' => array('lang' => $langcode),
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Add a textarea for each plural variant.
|
||||
for ($i = 0; $i < $plurals; $i++) {
|
||||
$form['strings'][$string->lid]['translations'][$i] = array(
|
||||
'#type' => 'textarea',
|
||||
'#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' => array('lang' => $langcode),
|
||||
'#prefix' => $i == 0 ? ('<span class="visually-hidden">' . $this->t('Translated string (@language)', array('@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'] = array('#type' => 'actions');
|
||||
$form['actions']['submit'] = array(
|
||||
'#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', array('%string' => $value)));
|
||||
$form_state->setErrorByName("translations][$langcode][$key", $this->t('The submitted string contains disallowed HTML: %string', array('%string' => $value)));
|
||||
$this->logger('locale')->warning('Attempted submission of a translation string with disallowed HTML: %string', array('%string' => $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$langcode = $form_state->getValue('langcode');
|
||||
$updated = array();
|
||||
|
||||
// Preload all translations for strings in the form.
|
||||
$lids = array_keys($form_state->getValue('strings'));
|
||||
$existing_translation_objects = array();
|
||||
foreach ($this->localeStorage->getTranslations(array('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(array('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;
|
||||
}
|
||||
}
|
||||
|
||||
drupal_set_message($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',
|
||||
array(),
|
||||
array('page' => $page)
|
||||
);
|
||||
}
|
||||
|
||||
if ($updated) {
|
||||
// Clear cache and force refresh of JavaScript translations.
|
||||
_locale_refresh_translations(array($langcode), $updated);
|
||||
_locale_refresh_configuration(array($langcode), $updated);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
105
core/modules/locale/src/Form/TranslateFilterForm.php
Normal file
105
core/modules/locale/src/Form/TranslateFilterForm.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Form\TranslateFilterForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a filtered translation edit form.
|
||||
*/
|
||||
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'] = array(
|
||||
'#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'] = array(
|
||||
'#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] = array(
|
||||
'#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'] = array(
|
||||
'#type' => 'actions',
|
||||
'#attributes' => array('class' => array('container-inline')),
|
||||
);
|
||||
$form['filters']['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Filter'),
|
||||
);
|
||||
if (!empty($_SESSION['locale_translate_filter'])) {
|
||||
$form['filters']['actions']['reset'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Reset'),
|
||||
'#submit' => array('::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'] = array();
|
||||
$form_state->setRedirect('locale.translate_page');
|
||||
}
|
||||
|
||||
}
|
219
core/modules/locale/src/Form/TranslateFormBase.php
Normal file
219
core/modules/locale/src/Form/TranslateFormBase.php
Normal file
|
@ -0,0 +1,219 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Form\TranslateFormBase.
|
||||
*/
|
||||
|
||||
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 = array('language' => $filter_values['langcode']);
|
||||
$options = array('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 = array();
|
||||
$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 = array();
|
||||
|
||||
// Get all languages, except English.
|
||||
$this->languageManager->reset();
|
||||
$languages = $this->languageManager->getLanguages();
|
||||
$language_options = array();
|
||||
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'] = array(
|
||||
'title' => $this->t('String contains'),
|
||||
'description' => $this->t('Leave blank to show all strings. The search is case sensitive.'),
|
||||
'default' => '',
|
||||
);
|
||||
|
||||
$filters['langcode'] = array(
|
||||
'title' => $this->t('Translation language'),
|
||||
'options' => $language_options,
|
||||
'default' => $default_langcode,
|
||||
);
|
||||
|
||||
$filters['translation'] = array(
|
||||
'title' => $this->t('Search in'),
|
||||
'options' => array(
|
||||
'all' => $this->t('Both translated and untranslated strings'),
|
||||
'translated' => $this->t('Only translated strings'),
|
||||
'untranslated' => $this->t('Only untranslated strings'),
|
||||
),
|
||||
'default' => 'all',
|
||||
);
|
||||
|
||||
$filters['customized'] = array(
|
||||
'title' => $this->t('Translation type'),
|
||||
'options' => array(
|
||||
'all' => $this->t('All'),
|
||||
LOCALE_NOT_CUSTOMIZED => $this->t('Non-customized translation'),
|
||||
LOCALE_CUSTOMIZED => $this->t('Customized translation'),
|
||||
),
|
||||
'states' => array(
|
||||
'visible' => array(
|
||||
':input[name=translation]' => array('value' => 'translated'),
|
||||
),
|
||||
),
|
||||
'default' => 'all',
|
||||
);
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
}
|
309
core/modules/locale/src/Form/TranslationStatusForm.php
Normal file
309
core/modules/locale/src/Form/TranslationStatusForm.php
Normal file
|
@ -0,0 +1,309 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Form\TranslationStatusForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Form;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
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.
|
||||
*/
|
||||
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 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 = array();
|
||||
$languages_update = array();
|
||||
$languages_not_found = array();
|
||||
$projects_update = array();
|
||||
// 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 = SafeMarkup::checkPlain($languages[$langcode]->getName());
|
||||
$locale_translation_update_info = array('#theme' => 'locale_translation_update_info');
|
||||
foreach (array('updates', 'not_found') as $update_status) {
|
||||
if (isset($update[$update_status])) {
|
||||
$locale_translation_update_info['#' . $update_status] = $update[$update_status];
|
||||
}
|
||||
}
|
||||
$options[$langcode] = array(
|
||||
'title' => array(
|
||||
'class' => array('label'),
|
||||
'data' => array(
|
||||
'#title' => $title,
|
||||
'#markup' => $title,
|
||||
),
|
||||
),
|
||||
'status' => array(
|
||||
'class' => array('description', 'priority-low'),
|
||||
'data' => drupal_render($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'] = array(
|
||||
'#theme' => 'locale_translation_last_check',
|
||||
'#last' => $last_checked,
|
||||
);
|
||||
|
||||
$header = array(
|
||||
'title' => array(
|
||||
'data' => $this->t('Language'),
|
||||
'class' => array('title'),
|
||||
),
|
||||
'status' => array(
|
||||
'data' => $this->t('Status'),
|
||||
'class' => array('status', 'priority-low'),
|
||||
),
|
||||
);
|
||||
|
||||
if (!$languages) {
|
||||
$empty = $this->t('No translatable languages available. <a href="@add_language">Add a language</a> first.', array(
|
||||
'@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>.', array(
|
||||
'@check' => $this->url('locale.check_translation'),
|
||||
));
|
||||
}
|
||||
|
||||
// The projects which require an update. Used by the _submit callback.
|
||||
$form['projects_update'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $projects_update,
|
||||
);
|
||||
|
||||
$form['langcodes'] = array(
|
||||
'#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' => array('locale_translation_language_table'),
|
||||
);
|
||||
|
||||
$form['#attached']['library'][] = 'locale/drupal.locale.admin';
|
||||
|
||||
$form['actions'] = array('#type' => 'actions');
|
||||
if ($languages_update) {
|
||||
$form['actions']['submit'] = array(
|
||||
'#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 = array();
|
||||
|
||||
// @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'][] = array(
|
||||
'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'][] = array(
|
||||
'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.
|
||||
*
|
||||
* Translations for development versions are never fetched, so the debug info
|
||||
* for that is a fixed message.
|
||||
*
|
||||
* @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 (strpos($project_info->version, 'dev') !== FALSE) {
|
||||
return $this->t('No translation files are provided for development releases.');
|
||||
}
|
||||
if (locale_translation_use_remote_source() && $remote_path && $local_path) {
|
||||
return $this->t('File not found at %remote_path nor at %local_path', array(
|
||||
'%remote_path' => $remote_path,
|
||||
'%local_path' => $local_path,
|
||||
));
|
||||
}
|
||||
elseif ($local_path) {
|
||||
return $this->t('File not found at %local_path', array('%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(array(), $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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
103
core/modules/locale/src/Gettext.php
Normal file
103
core/modules/locale/src/Gettext.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Gettext.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Drupal\Component\Gettext\PoStreamReader;
|
||||
use Drupal\Component\Gettext\PoMemoryWriter;
|
||||
use Drupal\locale\PoDatabaseWriter;
|
||||
|
||||
/**
|
||||
* 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 += array(
|
||||
'overwrite_options' => array(),
|
||||
'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 = array(
|
||||
'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;
|
||||
}
|
||||
}
|
28
core/modules/locale/src/Locale.php
Normal file
28
core/modules/locale/src/Locale.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Locale.
|
||||
*/
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
638
core/modules/locale/src/LocaleConfigManager.php
Normal file
638
core/modules/locale/src/LocaleConfigManager.php
Normal file
|
@ -0,0 +1,638 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\LocaleConfigManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Drupal\Core\StringTranslation\TranslationWrapper;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function __construct(StorageInterface $config_storage, StringStorageInterface $locale_storage, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config, ConfigurableLanguageManagerInterface $language_manager, LocaleDefaultConfigStorage $default_config_storage) {
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets array of translation wrappers for translatable configuration.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration object name.
|
||||
*
|
||||
* @return array
|
||||
* Array of 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);
|
||||
$type_definition = $this->typedConfigManager->getDefinition($name);
|
||||
$data_definition = $this->typedConfigManager->buildDataDefinition($type_definition, $data);
|
||||
$typed_config = $this->typedConfigManager->create($data_definition, $data);
|
||||
if ($typed_config instanceof TraversableTypedDataInterface) {
|
||||
return $this->getTranslatableData($typed_config);
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets translatable configuration data for a typed configuration element.
|
||||
*
|
||||
* @param \Drupal\Core\TypedData\TypedDataInterface $element
|
||||
* Typed configuration element.
|
||||
*
|
||||
* @return array|\Drupal\Core\StringTranslation\TranslationWrapper
|
||||
* A nested array matching the exact structure under $element with only the
|
||||
* elements that are translatable wrapped into a TranslationWrapper. If the
|
||||
* provided $element is not traversable, the return value is a single
|
||||
* TranslationWrapper.
|
||||
*/
|
||||
protected function getTranslatableData(TypedDataInterface $element) {
|
||||
$translatable = array();
|
||||
if ($element instanceof TraversableTypedDataInterface) {
|
||||
foreach ($element as $key => $property) {
|
||||
$value = $this->getTranslatableData($property);
|
||||
if (!empty($value)) {
|
||||
$translatable[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$definition = $element->getDataDefinition();
|
||||
if (!empty($definition['translatable'])) {
|
||||
$options = array();
|
||||
if (isset($definition['translation context'])) {
|
||||
$options['context'] = $definition['translation context'];
|
||||
}
|
||||
return new TranslationWrapper($element->getValue(), array(), $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\TranslationWrapper[] $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 TranslationWrapper.
|
||||
* @see self::getTranslatableData().
|
||||
* @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.
|
||||
*/
|
||||
protected function processTranslatableData($name, array $active, array $translatable, $langcode) {
|
||||
$translated = array();
|
||||
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 = array()) {
|
||||
$components = array_filter($components);
|
||||
if ($components) {
|
||||
$names = array();
|
||||
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 = array();
|
||||
$locations = $this->localeStorage->getLocations(array('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] = array();
|
||||
foreach ($this->localeStorage->getTranslations(array('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(array('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(array('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 = array();
|
||||
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 = array('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) {
|
||||
$shipped = $this->defaultConfigStorage->read($name);
|
||||
if (!empty($shipped)) {
|
||||
return !empty($shipped['langcode']) ? $shipped['langcode'] : 'en';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = array()) {
|
||||
$langcodes = $langcodes ? $langcodes : array_keys($this->languageManager->getLanguages());
|
||||
$count = 0;
|
||||
foreach ($names as $name) {
|
||||
$translatable = $this->getTranslatableDefaultConfig($name);
|
||||
if (empty($translatable)) {
|
||||
// If there is nothing translatable in this configuration or not
|
||||
// supported, skip it.
|
||||
continue;
|
||||
}
|
||||
|
||||
$active_langcode = $this->getActiveConfigLangcode($name);
|
||||
$active = $this->configStorage->read($name);
|
||||
|
||||
foreach ($langcodes as $langcode) {
|
||||
$processed = $this->processTranslatableData($name, $active, $translatable, $langcode);
|
||||
if ($langcode != $active_langcode) {
|
||||
// If the language code is not the same as the active storage
|
||||
// language, we should update a configuration override.
|
||||
if (!empty($processed)) {
|
||||
// Update translation data in configuration override.
|
||||
$this->saveTranslationOverride($name, $langcode, $processed);
|
||||
$count++;
|
||||
}
|
||||
else {
|
||||
$override = $this->languageManager->getLanguageConfigOverride($langcode, $name);
|
||||
if (!$override->isNew()) {
|
||||
$data = $this->filterOverride($override->get(), $translatable);
|
||||
if (empty($data)) {
|
||||
// Delete language override if there is no data left at all.
|
||||
// This means all prior translations in the override were locale
|
||||
// managed.
|
||||
$this->deleteTranslationOverride($name, $langcode);
|
||||
$count++;
|
||||
}
|
||||
else {
|
||||
// If there were translatable elements besides locale managed
|
||||
// items, save with only those, and remove the ones managed
|
||||
// by locale only.
|
||||
$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(array($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 = array();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
232
core/modules/locale/src/LocaleConfigSubscriber.php
Normal file
232
core/modules/locale/src/LocaleConfigSubscriber.php
Normal file
|
@ -0,0 +1,232 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\LocaleConfigSubscriber.
|
||||
*/
|
||||
|
||||
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 = array()) {
|
||||
$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\TranslationWrapper[] $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 = array()) {
|
||||
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] : array();
|
||||
$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\TranslationWrapper $translatable
|
||||
* Either a possibly nested array with TranslationWrapper objects at the
|
||||
* leaf items or a TranslationWrapper 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
165
core/modules/locale/src/LocaleDefaultConfigStorage.php
Normal file
165
core/modules/locale/src/LocaleDefaultConfigStorage.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\LocaleDefaultConfigStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
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) {
|
||||
$this->configStorage = $config_storage;
|
||||
$this->languageManager = $language_manager;
|
||||
|
||||
$this->requiredInstallStorage = new InstallStorage();
|
||||
$this->optionalInstallStorage = new InstallStorage(InstallStorage::CONFIG_OPTIONAL_DIRECTORY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
62
core/modules/locale/src/LocaleEvent.php
Normal file
62
core/modules/locale/src/LocaleEvent.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\LocaleEvent.
|
||||
*/
|
||||
|
||||
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 = array()) {
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
29
core/modules/locale/src/LocaleEvents.php
Normal file
29
core/modules/locale/src/LocaleEvents.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\LocaleEvents.
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
}
|
191
core/modules/locale/src/LocaleLookup.php
Normal file
191
core/modules/locale/src/LocaleLookup.php
Normal file
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\LocaleLookup.
|
||||
*/
|
||||
|
||||
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 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 = array('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(':', array_keys($user->getRoles())) : '0';
|
||||
$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 = array();
|
||||
}
|
||||
return $this->cid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function resolveCacheMiss($offset) {
|
||||
$translation = $this->stringStorage->findTranslation(array(
|
||||
'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(array(
|
||||
'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(array('langcode' => $this->langcode, 'operation' => 'locale_lookup', 'data' => $offset));
|
||||
if (!empty($fallbacks)) {
|
||||
foreach ($fallbacks as $langcode) {
|
||||
$translation = $this->stringStorage->findTranslation(array(
|
||||
'language' => $langcode,
|
||||
'source' => $offset,
|
||||
'context' => $this->context,
|
||||
));
|
||||
|
||||
if ($translation && !empty($translation->translation)) {
|
||||
$value = $translation->translation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
173
core/modules/locale/src/LocaleProjectStorage.php
Normal file
173
core/modules/locale/src/LocaleProjectStorage.php
Normal file
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\LocaleProjectStorage.
|
||||
*/
|
||||
|
||||
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 = array();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function __construct(KeyValueFactoryInterface $key_value_factory) {
|
||||
$this->keyValueStore = $key_value_factory->get('locale.project');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key, $default = NULL) {
|
||||
$values = $this->getMultiple(array($key));
|
||||
return isset($values[$key]) ? $values[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMultiple(array $keys) {
|
||||
$values = array();
|
||||
$load = array();
|
||||
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(array($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(array($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 = array();
|
||||
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;
|
||||
}
|
||||
}
|
106
core/modules/locale/src/LocaleProjectStorageInterface.php
Normal file
106
core/modules/locale/src/LocaleProjectStorageInterface.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\LocaleProjectStorageInterface.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
161
core/modules/locale/src/LocaleTranslation.php
Normal file
161
core/modules/locale/src/LocaleTranslation.php
Normal file
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\LocaleTranslation.
|
||||
*/
|
||||
|
||||
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 = array();
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destruct() {
|
||||
foreach ($this->translations as $context) {
|
||||
foreach ($context as $lookup) {
|
||||
if ($lookup instanceof DestructableInterface) {
|
||||
$lookup->destruct();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
122
core/modules/locale/src/Plugin/QueueWorker/LocaleTranslation.php
Normal file
122
core/modules/locale/src/Plugin/QueueWorker/LocaleTranslation.php
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Plugin\QueueWorker\LocaleTranslation.
|
||||
*/
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
177
core/modules/locale/src/PoDatabaseReader.php
Normal file
177
core/modules/locale/src/PoDatabaseReader.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\PoDatabaseReader.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Drupal\Component\Gettext\PoHeader;
|
||||
use Drupal\Component\Gettext\PoItem;
|
||||
use Drupal\Component\Gettext\PoReaderInterface;
|
||||
use Drupal\locale\TranslationString;
|
||||
|
||||
/**
|
||||
* 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(array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface::getLangcode().
|
||||
*/
|
||||
public function getLangcode() {
|
||||
return $this->langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface::setLangcode().
|
||||
*/
|
||||
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 += array(
|
||||
'customized' => FALSE,
|
||||
'not_customized' => FALSE,
|
||||
'not_translated' => FALSE,
|
||||
);
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface::getHeader().
|
||||
*/
|
||||
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 = array();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoReaderInterface::readItem().
|
||||
*/
|
||||
public function readItem() {
|
||||
if ($string = $this->readString()) {
|
||||
$values = (array) $string;
|
||||
$po_item = new PoItem();
|
||||
$po_item->setFromArray($values);
|
||||
return $po_item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
290
core/modules/locale/src/PoDatabaseWriter.php
Normal file
290
core/modules/locale/src/PoDatabaseWriter.php
Normal file
|
@ -0,0 +1,290 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\PoDatabaseWriter.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Drupal\Component\Gettext\PoHeader;
|
||||
use Drupal\Component\Gettext\PoItem;
|
||||
use Drupal\Component\Gettext\PoReaderInterface;
|
||||
use Drupal\Component\Gettext\PoWriterInterface;
|
||||
use Drupal\locale\SourceString;
|
||||
use Drupal\locale\TranslationString;
|
||||
|
||||
/**
|
||||
* 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:
|
||||
* - override_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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface::getLangcode().
|
||||
*/
|
||||
public function getLangcode() {
|
||||
return $this->langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface::setLangcode().
|
||||
*/
|
||||
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 = array()) {
|
||||
$report += array(
|
||||
'additions' => 0,
|
||||
'updates' => 0,
|
||||
'deletes' => 0,
|
||||
'skips' => 0,
|
||||
'strings' => array(),
|
||||
);
|
||||
$this->report = $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options used by the writer.
|
||||
*/
|
||||
public function getOptions() {
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options for the current writer.
|
||||
*/
|
||||
public function setOptions(array $options) {
|
||||
if (!isset($options['overwrite_options'])) {
|
||||
$options['overwrite_options'] = array();
|
||||
}
|
||||
$options['overwrite_options'] += array(
|
||||
'not_customized' => FALSE,
|
||||
'customized' => FALSE,
|
||||
);
|
||||
$options += array(
|
||||
'customized' => LOCALE_NOT_CUSTOMIZED,
|
||||
);
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface::getHeader().
|
||||
*/
|
||||
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') ?: array();
|
||||
|
||||
// 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;
|
||||
$locale_plurals[$langcode] = array(
|
||||
'plurals' => $nplurals,
|
||||
'formula' => $formula,
|
||||
);
|
||||
\Drupal::state()->set('locale.translation.plurals', $locale_plurals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoWriterInterface::writeItem().
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoWriterInterface::writeItems().
|
||||
*/
|
||||
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'] += array(
|
||||
'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(array(
|
||||
'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.', array('%string' => $translation));
|
||||
$this->report['skips']++;
|
||||
return 0;
|
||||
}
|
||||
elseif ($string) {
|
||||
$string->setString($translation);
|
||||
if ($string->isNew()) {
|
||||
// No translation in this language.
|
||||
$string->setValues(array(
|
||||
'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(array('source' => $source, 'context' => $context))
|
||||
->save();
|
||||
\Drupal::service('locale.storage')->createTranslation(array(
|
||||
'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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
56
core/modules/locale/src/SourceString.php
Normal file
56
core/modules/locale/src/SourceString.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\SourceString.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Drupal\locale\LocaleString;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::isSource().
|
||||
*/
|
||||
public function isSource() {
|
||||
return isset($this->source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::isTranslation().
|
||||
*/
|
||||
public function isTranslation() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\LocaleString::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
return isset($this->source) ? $this->source : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\LocaleString::setString().
|
||||
*/
|
||||
public function setString($string) {
|
||||
$this->source = $string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\LocaleString::isNew().
|
||||
*/
|
||||
public function isNew() {
|
||||
return empty($this->lid);
|
||||
}
|
||||
|
||||
}
|
57
core/modules/locale/src/StreamWrapper/TranslationsStream.php
Normal file
57
core/modules/locale/src/StreamWrapper/TranslationsStream.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\StreamWrapper\TranslationsStream.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\StreamWrapper;
|
||||
|
||||
use Drupal\Core\Annotation\StreamWrapper;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\StreamWrapper\LocalStream::getDirectoryPath()
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
function getExternalUrl() {
|
||||
throw new \LogicException('PO files URL should not be public.');
|
||||
}
|
||||
}
|
217
core/modules/locale/src/StringBase.php
Normal file
217
core/modules/locale/src/StringBase.php
Normal file
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\StringBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* 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 integer
|
||||
*/
|
||||
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 = array()) {
|
||||
$this->setValues((array) $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::getId().
|
||||
*/
|
||||
public function getId() {
|
||||
return isset($this->lid) ? $this->lid : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::setId().
|
||||
*/
|
||||
public function setId($lid) {
|
||||
$this->lid = $lid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::getVersion().
|
||||
*/
|
||||
public function getVersion() {
|
||||
return isset($this->version) ? $this->version : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::setVersion().
|
||||
*/
|
||||
public function setVersion($version) {
|
||||
$this->version = $version;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::getPlurals().
|
||||
*/
|
||||
public function getPlurals() {
|
||||
return explode(LOCALE_PLURAL_DELIMITER, $this->getString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::setPlurals().
|
||||
*/
|
||||
public function setPlurals($plurals) {
|
||||
$this->setString(implode(LOCALE_PLURAL_DELIMITER, $plurals));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::getStorage().
|
||||
*/
|
||||
public function getStorage() {
|
||||
return isset($this->storage) ? $this->storage : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::setStorage().
|
||||
*/
|
||||
public function setStorage($storage) {
|
||||
$this->storage = $storage;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::setValues().
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::getValues().
|
||||
*/
|
||||
public function getValues(array $fields) {
|
||||
$values = array();
|
||||
foreach ($fields as $field) {
|
||||
if (isset($this->$field)) {
|
||||
$values[$field] = $this->$field;
|
||||
}
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::getLocation().
|
||||
*/
|
||||
public function getLocations($check_only = FALSE) {
|
||||
if (!isset($this->locations) && !$check_only) {
|
||||
$this->locations = array();
|
||||
foreach ($this->getStorage()->getLocations(array('sid' => $this->getId())) as $location) {
|
||||
$this->locations[$location->type][$location->name] = $location->lid;
|
||||
}
|
||||
}
|
||||
return isset($this->locations) ? $this->locations : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::addLocation().
|
||||
*/
|
||||
public function addLocation($type, $name) {
|
||||
$this->locations[$type][$name] = TRUE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::hasLocation().
|
||||
*/
|
||||
public function hasLocation($type, $name) {
|
||||
$locations = $this->getLocations();
|
||||
return isset($locations[$type]) ? !empty($locations[$type][$name]) : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\LocaleString::save().
|
||||
*/
|
||||
public function save() {
|
||||
if ($storage = $this->getStorage()) {
|
||||
$storage->save($this);
|
||||
}
|
||||
else {
|
||||
throw new StringStorageException(SafeMarkup::format('The string cannot be saved because its not bound to a storage: @string', array(
|
||||
'@string' => $this->getString(),
|
||||
)));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\LocaleString::delete().
|
||||
*/
|
||||
public function delete() {
|
||||
if (!$this->isNew()) {
|
||||
if ($storage = $this->getStorage()) {
|
||||
$storage->delete($this);
|
||||
}
|
||||
else {
|
||||
throw new StringStorageException(SafeMarkup::format('The string cannot be deleted because its not bound to a storage: @string', array(
|
||||
'@string' => $this->getString(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
550
core/modules/locale/src/StringDatabaseStorage.php
Normal file
550
core/modules/locale/src/StringDatabaseStorage.php
Normal file
|
@ -0,0 +1,550 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\StringDatabaseStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
|
||||
/**
|
||||
* 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 = array()) {
|
||||
$this->connection = $connection;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStrings(array $conditions = array(), array $options = array()) {
|
||||
return $this->dbStringLoad($conditions, $options, 'Drupal\locale\SourceString');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTranslations(array $conditions = array(), array $options = array()) {
|
||||
return $this->dbStringLoad($conditions, array('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, array('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 = array()) {
|
||||
$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', array('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(array('sid' => $string->getId(), 'type' => $type, 'name' => $name))
|
||||
->fields(array('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(array('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(format_string('The string cannot be deleted because it lacks some key fields: @string', array(
|
||||
'@string' => $string->getString(),
|
||||
)));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteStrings($conditions) {
|
||||
$lids = $this->dbStringSelect($conditions, array('fields' => array('lid')))->execute()->fetchCol();
|
||||
if ($lids) {
|
||||
$this->dbDelete('locales_target', array('lid' => $lids))->execute();
|
||||
$this->dbDelete('locales_source', array('lid' => $lids))->execute();
|
||||
$this->dbDelete('locales_location', array('sid' => $lids))->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteTranslations($conditions) {
|
||||
$this->dbDelete('locales_target', $conditions)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createString($values = array()) {
|
||||
return new SourceString($values + array('storage' => $this));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createTranslation($values = array()) {
|
||||
return new TranslationString($values + array(
|
||||
'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, array('language', 'translation', 'customized'))) {
|
||||
return 't';
|
||||
}
|
||||
elseif (in_array($field, array('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 = array('lid');
|
||||
}
|
||||
elseif ($string->isTranslation()) {
|
||||
$keys = array('lid', 'language');
|
||||
}
|
||||
if (!empty($keys) && ($values = $string->getValues($keys)) && count($keys) == count($values)) {
|
||||
return $values;
|
||||
}
|
||||
else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
$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 = array()) {
|
||||
// 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", array(
|
||||
':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', array('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', array('sid'));
|
||||
foreach (array('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(db_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 = db_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(array('context' => '', 'version' => 'none'), FALSE);
|
||||
$fields = $string->getValues(array('source', 'context', 'version'));
|
||||
}
|
||||
elseif ($string->isTranslation()) {
|
||||
$string->setValues(array('customized' => 0), FALSE);
|
||||
$fields = $string->getValues(array('lid', 'language', 'translation', 'customized'));
|
||||
}
|
||||
if (!empty($fields)) {
|
||||
return $this->connection->insert($this->dbStringTable($string), $this->options)
|
||||
->fields($fields)
|
||||
->execute();
|
||||
}
|
||||
else {
|
||||
throw new StringStorageException(format_string('The string cannot be saved: @string', array(
|
||||
'@string' => $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(array('source', 'context', 'version'));
|
||||
}
|
||||
elseif ($string->isTranslation()) {
|
||||
$values = $string->getValues(array('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(format_string('The string cannot be updated: @string', array(
|
||||
'@string' => $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 = array()) {
|
||||
return $this->connection->query($query, $args, $this->options);
|
||||
}
|
||||
}
|
221
core/modules/locale/src/StringInterface.php
Normal file
221
core/modules/locale/src/StringInterface.php
Normal file
|
@ -0,0 +1,221 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\StringInterface.
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
}
|
13
core/modules/locale/src/StringStorageException.php
Normal file
13
core/modules/locale/src/StringStorageException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\StringStorageException.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale;
|
||||
|
||||
/**
|
||||
* Defines an exception thrown when storage operations fail.
|
||||
*/
|
||||
class StringStorageException extends \Exception {}
|
184
core/modules/locale/src/StringStorageInterface.php
Normal file
184
core/modules/locale/src/StringStorageInterface.php
Normal file
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\StringStorageInterface.
|
||||
*/
|
||||
|
||||
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(), array $options = array());
|
||||
|
||||
/**
|
||||
* 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(), array $options = array());
|
||||
|
||||
/**
|
||||
* 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 = array());
|
||||
|
||||
/**
|
||||
* 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 = array());
|
||||
|
||||
/**
|
||||
* 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 = array());
|
||||
}
|
64
core/modules/locale/src/Tests/LocaleConfigManagerTest.php
Normal file
64
core/modules/locale/src/Tests/LocaleConfigManagerTest.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleConfigManagerTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the locale config manager operates correctly.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigManagerTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* A list of modules to install for this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('language', 'locale', 'locale_test');
|
||||
|
||||
/**
|
||||
* Tests hasTranslation().
|
||||
*/
|
||||
public function testHasTranslation() {
|
||||
$this->installSchema('locale', array('locales_location', 'locales_source', 'locales_target'));
|
||||
$this->installConfig(array('locale_test'));
|
||||
$locale_config_manager = \Drupal::service('locale.config_manager');
|
||||
|
||||
$language = ConfigurableLanguage::createFromLangcode('de');
|
||||
$language->save();
|
||||
$result = $locale_config_manager->hasTranslation('locale_test.no_translation', $language->getId());
|
||||
$this->assertFalse($result, 'There is no translation for locale_test.no_translation configuration.');
|
||||
|
||||
$result = $locale_config_manager->hasTranslation('locale_test.translation', $language->getId());
|
||||
$this->assertTrue($result, 'There is a translation for locale_test.translation configuration.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getStringTranslation().
|
||||
*/
|
||||
public function testGetStringTranslation() {
|
||||
$this->installSchema('locale', array('locales_location', 'locales_source', 'locales_target'));
|
||||
$this->installConfig(array('locale_test'));
|
||||
|
||||
$locale_config_manager = \Drupal::service('locale.config_manager');
|
||||
|
||||
$language = ConfigurableLanguage::createFromLangcode('de');
|
||||
$language->save();
|
||||
|
||||
$translation_before = $locale_config_manager->getStringTranslation('locale_test.no_translation', $language->getId(), 'Test', '');
|
||||
$this->assertTrue($translation_before->isNew());
|
||||
$translation_before->setString('translation')->save();
|
||||
|
||||
$translation_after = $locale_config_manager->getStringTranslation('locale_test.no_translation', $language->getId(), 'Test', '');
|
||||
$this->assertFalse($translation_after->isNew());
|
||||
$translation_after->setString('updated_translation')->save();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleConfigSubscriberForeignTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests default configuration handling with a foreign default language.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigSubscriberForeignTest extends LocaleConfigSubscriberTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defaultLanguageData() {
|
||||
$data = Language::$defaultValues;
|
||||
$data['id'] = 'hu';
|
||||
$data['name'] = 'Hungarian';
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpLanguages() {
|
||||
parent::setUpLanguages();
|
||||
ConfigurableLanguage::createFromLangcode('hu')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpLocale() {
|
||||
parent::setUpLocale();
|
||||
$this->setUpTranslation('locale_test.translation', 'test', 'English test', 'Hungarian test', 'hu', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the language of default configuration was updated.
|
||||
*/
|
||||
public function testDefaultConfigLanguage() {
|
||||
$this->assertEqual('hu', $this->configFactory->getEditable('locale_test.no_translation')->get('langcode'));
|
||||
$this->assertEqual('hu', $this->configFactory->getEditable('locale_test.translation')->get('langcode'));
|
||||
$this->assertEqual($this->configFactory->getEditable('locale_test.translation')->get('test'), 'Hungarian test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating translations of shipped configuration.
|
||||
*/
|
||||
public function testCreateActiveTranslation() {
|
||||
$config_name = 'locale_test.no_translation';
|
||||
$this->saveLanguageActive($config_name, 'test', 'Test (Hungarian)', 'hu');
|
||||
$this->assertTranslation($config_name, 'Test (Hungarian)', 'hu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests importing community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleCreateActiveTranslation() {
|
||||
$config_name = 'locale_test.no_translation';
|
||||
$this->saveLocaleTranslationData($config_name, 'test', 'Test', 'Test (Hungarian)', 'hu', TRUE);
|
||||
$this->assertTranslation($config_name, 'Test (Hungarian)', 'hu', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating translations of shipped configuration.
|
||||
*/
|
||||
public function testUpdateActiveTranslation() {
|
||||
$config_name = 'locale_test.translation';
|
||||
$this->saveLanguageActive($config_name, 'test', 'Updated Hungarian test', 'hu');
|
||||
$this->assertTranslation($config_name, 'Updated Hungarian test', 'hu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleUpdateActiveTranslation() {
|
||||
$config_name = 'locale_test.translation';
|
||||
$this->saveLocaleTranslationData($config_name, 'test', 'English test', 'Updated Hungarian test', 'hu', TRUE);
|
||||
$this->assertTranslation($config_name, 'Updated Hungarian test', 'hu', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting a translation override.
|
||||
*/
|
||||
public function testDeleteTranslation() {
|
||||
$config_name = 'locale_test.translation';
|
||||
$this->deleteLanguageOverride($config_name, 'test', 'English test', 'de');
|
||||
// The German translation in this case will be forced to the Hungarian
|
||||
// source so its not overwritten with locale data later.
|
||||
$this->assertTranslation($config_name, 'Hungarian test', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting translations of shipped configuration.
|
||||
*/
|
||||
public function testDeleteActiveTranslation() {
|
||||
$config_name = 'locale_test.translation';
|
||||
$this->configFactory->getEditable($config_name)->delete();
|
||||
// Deleting active configuration should not change the locale translation.
|
||||
$this->assertTranslation($config_name, 'Hungarian test', 'hu', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleDeleteActiveTranslation() {
|
||||
$config_name = 'locale_test.translation';
|
||||
$this->deleteLocaleTranslationData($config_name, 'test', 'English test', 'hu');
|
||||
// Deleting the locale translation should not change active config.
|
||||
$this->assertEqual($this->configFactory->getEditable($config_name)->get('test'), 'Hungarian test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that adding English creates a translation override.
|
||||
*/
|
||||
public function testEnglish() {
|
||||
$config_name = 'locale_test.translation';
|
||||
ConfigurableLanguage::createFromLangcode('en')->save();
|
||||
// Adding a language on the UI would normally call updateConfigTranslations.
|
||||
$this->localeConfigManager->updateConfigTranslations(array($config_name), array('en'));
|
||||
$this->assertConfigOverride($config_name, 'test', 'English test', 'en');
|
||||
|
||||
$this->configFactory->getEditable('locale.settings')->set('translate_english', TRUE)->save();
|
||||
$this->saveLocaleTranslationData($config_name, 'test', 'English test', 'Updated English test', 'en');
|
||||
$this->assertTranslation($config_name, 'Updated English test', 'en', FALSE);
|
||||
|
||||
$this->saveLanguageOverride($config_name, 'test', 'Updated English', 'en');
|
||||
$this->assertTranslation($config_name, 'Updated English', 'en');
|
||||
|
||||
$this->deleteLocaleTranslationData($config_name, 'test', 'English test', 'en');
|
||||
$this->assertNoConfigOverride($config_name, 'en');
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a language override.
|
||||
*
|
||||
* This will invoke LocaleConfigSubscriber through the event dispatcher. To
|
||||
* make sure the configuration was persisted correctly, the configuration
|
||||
* value is checked. Because LocaleConfigSubscriber temporarily disables the
|
||||
* override state of the configuration factory we check that the correct value
|
||||
* is restored afterwards.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $value
|
||||
* The configuration value to save.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
protected function saveLanguageActive($config_name, $key, $value, $langcode) {
|
||||
$this
|
||||
->configFactory
|
||||
->getEditable($config_name)
|
||||
->set($key, $value)
|
||||
->save();
|
||||
$this->assertActiveConfig($config_name, $key, $value, $langcode);
|
||||
}
|
||||
|
||||
}
|
493
core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php
Normal file
493
core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php
Normal file
|
@ -0,0 +1,493 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleConfigSubscriberTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\locale\Locale;
|
||||
use Drupal\locale\StringInterface;
|
||||
use Drupal\locale\TranslationString;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that shipped configuration translations are updated correctly.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigSubscriberTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['language', 'locale', 'system'];
|
||||
|
||||
/**
|
||||
* The configurable language manager used in this test.
|
||||
*
|
||||
* @var \Drupal\language\ConfigurableLanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The configuration factory used in this test.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The string storage used in this test.
|
||||
*
|
||||
* @var \Drupal\locale\StringStorageInterface;
|
||||
*/
|
||||
protected $stringStorage;
|
||||
|
||||
/**
|
||||
* The locale configuration manager used in this test.
|
||||
*
|
||||
* @var \Drupal\locale\LocaleConfigManager
|
||||
*/
|
||||
protected $localeConfigManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpDefaultLanguage();
|
||||
|
||||
$this->installSchema('locale', ['locales_source', 'locales_target', 'locales_location']);
|
||||
$this->installSchema('system', ['queue']);
|
||||
|
||||
$this->setupLanguages();
|
||||
|
||||
$this->enableModules(['locale_test']);
|
||||
$this->installConfig(['locale_test']);
|
||||
// Simulate this hook invoked which would happen if in a non-kernel test
|
||||
// or normal environment.
|
||||
// @see locale_modules_installed()
|
||||
// @see locale_system_update()
|
||||
locale_system_set_config_langcodes();
|
||||
$langcodes = array_keys(\Drupal::languageManager()->getLanguages());
|
||||
$names = \Drupal\locale\Locale::config()->getComponentNames();
|
||||
Locale::config()->updateConfigTranslations($names, $langcodes);
|
||||
|
||||
$this->configFactory = $this->container->get('config.factory');
|
||||
$this->stringStorage = $this->container->get('locale.storage');
|
||||
$this->localeConfigManager = $this->container->get('locale.config_manager');
|
||||
$this->languageManager = $this->container->get('language_manager');
|
||||
|
||||
$this->setUpLocale();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up default language for this test.
|
||||
*/
|
||||
protected function setUpDefaultLanguage() {
|
||||
// Keep the default English.
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up languages needed for this test.
|
||||
*/
|
||||
protected function setUpLanguages() {
|
||||
ConfigurableLanguage::createFromLangcode('de')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the locale storage strings to be in line with configuration.
|
||||
*/
|
||||
protected function setUpLocale() {
|
||||
// Set up the locale database the same way we have in the config samples.
|
||||
$this->setUpNoTranslation('locale_test.no_translation', 'test', 'Test', 'de');
|
||||
$this->setUpTranslation('locale_test.translation', 'test', 'English test', 'German test', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating translations of shipped configuration.
|
||||
*/
|
||||
public function testCreateTranslation() {
|
||||
$config_name = 'locale_test.no_translation';
|
||||
|
||||
$this->saveLanguageOverride($config_name, 'test', 'Test (German)', 'de');
|
||||
$this->assertTranslation($config_name, 'Test (German)', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests importing community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleCreateTranslation() {
|
||||
$config_name = 'locale_test.no_translation';
|
||||
|
||||
$this->saveLocaleTranslationData($config_name, 'test', 'Test', 'Test (German)', 'de');
|
||||
$this->assertTranslation($config_name, 'Test (German)', 'de', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating translations of shipped configuration.
|
||||
*/
|
||||
public function testUpdateTranslation() {
|
||||
$config_name = 'locale_test.translation';
|
||||
|
||||
$this->saveLanguageOverride($config_name, 'test', 'Updated German test', 'de');
|
||||
$this->assertTranslation($config_name, 'Updated German test', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleUpdateTranslation() {
|
||||
$config_name = 'locale_test.translation';
|
||||
|
||||
$this->saveLocaleTranslationData($config_name, 'test', 'English test', 'Updated German test', 'de');
|
||||
$this->assertTranslation($config_name, 'Updated German test', 'de', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting translations of shipped configuration.
|
||||
*/
|
||||
public function testDeleteTranslation() {
|
||||
$config_name = 'locale_test.translation';
|
||||
|
||||
$this->deleteLanguageOverride($config_name, 'test', 'English test', 'de');
|
||||
// Instead of deleting the translation, we need to keep a translation with
|
||||
// the source value and mark it as customized to prevent the deletion being
|
||||
// reverted by importing community translations.
|
||||
$this->assertTranslation($config_name, 'English test', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleDeleteTranslation() {
|
||||
$config_name = 'locale_test.translation';
|
||||
|
||||
$this->deleteLocaleTranslationData($config_name, 'test', 'English test', 'de');
|
||||
$this->assertNoTranslation($config_name, 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a configuration string without a translation.
|
||||
*
|
||||
* The actual configuration is already available by installing locale_test
|
||||
* module, as it is done in LocaleConfigSubscriberTest::setUp(). This sets up
|
||||
* the necessary source string and verifies that everything is as expected to
|
||||
* avoid false positives.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $source
|
||||
* The source string.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
protected function setUpNoTranslation($config_name, $key, $source, $langcode) {
|
||||
$this->localeConfigManager->updateConfigTranslations(array($config_name), array($langcode));
|
||||
$this->assertNoConfigOverride($config_name, $key, $source, $langcode);
|
||||
$this->assertNoTranslation($config_name, $langcode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up a configuration string with a translation.
|
||||
*
|
||||
* The actual configuration is already available by installing locale_test
|
||||
* module, as it is done in LocaleConfigSubscriberTest::setUp(). This sets up
|
||||
* the necessary source and translation strings and verifies that everything
|
||||
* is as expected to avoid false positives.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $source
|
||||
* The source string.
|
||||
* @param string $translation
|
||||
* The translation string.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
* @param bool $is_active
|
||||
* Whether the update will affect the active configuration.
|
||||
*/
|
||||
protected function setUpTranslation($config_name, $key, $source, $translation, $langcode, $is_active = FALSE) {
|
||||
// Create source and translation strings for the configuration value and add
|
||||
// the configuration name as a location. This would be performed by
|
||||
// locale_translate_batch_import() invoking
|
||||
// LocaleConfigManager::updateConfigTranslations() normally.
|
||||
$this->localeConfigManager->reset();
|
||||
$this->localeConfigManager
|
||||
->getStringTranslation($config_name, $langcode, $source, '')
|
||||
->setString($translation)
|
||||
->setCustomized(FALSE)
|
||||
->save();
|
||||
$this->configFactory->reset($config_name);
|
||||
$this->localeConfigManager->reset();
|
||||
$this->localeConfigManager->updateConfigTranslations(array($config_name), array($langcode));
|
||||
|
||||
if ($is_active) {
|
||||
$this->assertActiveConfig($config_name, $key, $translation, $langcode);
|
||||
}
|
||||
else {
|
||||
$this->assertConfigOverride($config_name, $key, $translation, $langcode);
|
||||
}
|
||||
$this->assertTranslation($config_name, $translation, $langcode, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a language override.
|
||||
*
|
||||
* This will invoke LocaleConfigSubscriber through the event dispatcher. To
|
||||
* make sure the configuration was persisted correctly, the configuration
|
||||
* value is checked. Because LocaleConfigSubscriber temporarily disables the
|
||||
* override state of the configuration factory we check that the correct value
|
||||
* is restored afterwards.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $value
|
||||
* The configuration value to save.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
protected function saveLanguageOverride($config_name, $key, $value, $langcode) {
|
||||
$translation_override = $this->languageManager
|
||||
->getLanguageConfigOverride($langcode, $config_name);
|
||||
$translation_override
|
||||
->set($key, $value)
|
||||
->save();
|
||||
$this->configFactory->reset($config_name);
|
||||
|
||||
$this->assertConfigOverride($config_name, $key, $value, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves translation data from locale module.
|
||||
*
|
||||
* This will invoke LocaleConfigSubscriber through the event dispatcher. To
|
||||
* make sure the configuration was persisted correctly, the configuration
|
||||
* value is checked. Because LocaleConfigSubscriber temporarily disables the
|
||||
* override state of the configuration factory we check that the correct value
|
||||
* is restored afterwards.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $source
|
||||
* The source string.
|
||||
* @param string $translation
|
||||
* The translation string to save.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
* @param bool $is_active
|
||||
* Whether the update will affect the active configuration.
|
||||
*/
|
||||
protected function saveLocaleTranslationData($config_name, $key, $source, $translation, $langcode, $is_active = FALSE) {
|
||||
$this->localeConfigManager->reset();
|
||||
$this->localeConfigManager
|
||||
->getStringTranslation($config_name, $langcode, $source, '')
|
||||
->setString($translation)
|
||||
->save();
|
||||
$this->localeConfigManager->reset();
|
||||
$this->localeConfigManager->updateConfigTranslations(array($config_name), array($langcode));
|
||||
$this->configFactory->reset($config_name);
|
||||
|
||||
if ($is_active) {
|
||||
$this->assertActiveConfig($config_name, $key, $translation, $langcode);
|
||||
}
|
||||
else {
|
||||
$this->assertConfigOverride($config_name, $key, $translation, $langcode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a language override.
|
||||
*
|
||||
* This will invoke LocaleConfigSubscriber through the event dispatcher. To
|
||||
* make sure the configuration was persisted correctly, the configuration
|
||||
* value is checked. Because LocaleConfigSubscriber temporarily disables the
|
||||
* override state of the configuration factory we check that the correct value
|
||||
* is restored afterwards.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $source_value
|
||||
* The source configuration value to verify the correct value is returned
|
||||
* from the configuration factory after the deletion.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
protected function deleteLanguageOverride($config_name, $key, $source_value, $langcode) {
|
||||
$translation_override = $this->languageManager
|
||||
->getLanguageConfigOverride($langcode, $config_name);
|
||||
$translation_override
|
||||
->clear($key)
|
||||
->save();
|
||||
$this->configFactory->reset($config_name);
|
||||
|
||||
$this->assertNoConfigOverride($config_name, $key, $source_value, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes translation data from locale module.
|
||||
*
|
||||
* This will invoke LocaleConfigSubscriber through the event dispatcher. To
|
||||
* make sure the configuration was persisted correctly, the configuration
|
||||
* value is checked. Because LocaleConfigSubscriber temporarily disables the
|
||||
* override state of the configuration factory we check that the correct value
|
||||
* is restored afterwards.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $source_value
|
||||
* The source configuration value to verify the correct value is returned
|
||||
* from the configuration factory after the deletion.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
protected function deleteLocaleTranslationData($config_name, $key, $source_value, $langcode) {
|
||||
$this->localeConfigManager
|
||||
->getStringTranslation($config_name, $langcode, $source_value, '')
|
||||
->delete();
|
||||
$this->localeConfigManager->reset();
|
||||
$this->localeConfigManager->updateConfigTranslations(array($config_name), array($langcode));
|
||||
$this->configFactory->reset($config_name);
|
||||
|
||||
$this->assertNoConfigOverride($config_name, $key, $source_value, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures configuration override is not present anymore.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertNoConfigOverride($config_name, $langcode) {
|
||||
$config_langcode = $this->configFactory->getEditable($config_name)->get('langcode');
|
||||
$override = $this->languageManager->getLanguageConfigOverride($langcode, $config_name);
|
||||
return $this->assertNotEqual($config_langcode, $langcode) && $this->assertEqual($override->isNew(), TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures configuration was saved correctly.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $value
|
||||
* The configuration value.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertConfigOverride($config_name, $key, $value, $langcode) {
|
||||
$config_langcode = $this->configFactory->getEditable($config_name)->get('langcode');
|
||||
$override = $this->languageManager->getLanguageConfigOverride($langcode, $config_name);
|
||||
return $this->assertNotEqual($config_langcode, $langcode) && $this->assertEqual($override->get($key), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures configuration was saved correctly.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $value
|
||||
* The configuration value.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertActiveConfig($config_name, $key, $value, $langcode) {
|
||||
$config = $this->configFactory->getEditable($config_name);
|
||||
return
|
||||
$this->assertEqual($config->get('langcode'), $langcode) &&
|
||||
$this->assertIdentical($config->get($key), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures no translation exists.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertNoTranslation($config_name, $langcode) {
|
||||
$strings = $this->stringStorage->getTranslations([
|
||||
'type' => 'configuration',
|
||||
'name' => $config_name,
|
||||
'language' => $langcode,
|
||||
'translated' => TRUE,
|
||||
]);
|
||||
return $this->assertIdentical([], $strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a translation exists and is marked as customized.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $translation
|
||||
* The translation.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
* @param bool $customized
|
||||
* Whether or not the string should be asserted to be customized or not
|
||||
* customized.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertTranslation($config_name, $translation, $langcode, $customized = TRUE) {
|
||||
// Make sure a string exists.
|
||||
$strings = $this->stringStorage->getTranslations([
|
||||
'type' => 'configuration',
|
||||
'name' => $config_name,
|
||||
'language' => $langcode,
|
||||
'translated' => TRUE,
|
||||
]);
|
||||
$pass = $this->assertIdentical(1, count($strings));
|
||||
$string = reset($strings);
|
||||
if ($this->assertTrue($string instanceof StringInterface)) {
|
||||
/** @var \Drupal\locale\StringInterface $string */
|
||||
$pass = $pass && $this->assertIdentical($translation, $string->getString());
|
||||
$pass = $pass && $this->assertTrue($string->isTranslation());
|
||||
if ($this->assertTrue($string instanceof TranslationString)) {
|
||||
/** @var \Drupal\locale\TranslationString $string */
|
||||
// Make sure the string is marked as customized so that it does not get
|
||||
// overridden when the string translations are updated.
|
||||
return $pass && $this->assertEqual($customized, $string->customized);
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleConfigTranslationImportTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Tests translation update's effects on configuration translations.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigTranslationImportTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('language', 'update', 'locale_test_translate');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'administer permissions'));
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Update module should not go out to d.o to check for updates. We override
|
||||
// the url to an invalid update source. No update data will be found.
|
||||
$this->config('update.settings')->set('fetch.url', (string) Url::fromRoute('<front>')->setAbsolute()->toString())->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update changes configuration translations if enabled after language.
|
||||
*/
|
||||
public function testConfigTranslationImport() {
|
||||
|
||||
// Add a language. The Afrikaans translation file of locale_test_translate
|
||||
// (test.af.po) has been prepared with a configuration translation.
|
||||
ConfigurableLanguage::createFromLangcode('af')->save();
|
||||
|
||||
// Enable locale module.
|
||||
$this->container->get('module_installer')->install(array('locale'));
|
||||
$this->resetAll();
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->save();
|
||||
|
||||
// Add translation permissions now that the locale module has been enabled.
|
||||
$edit = array(
|
||||
'authenticated[translate interface]' => 'translate interface',
|
||||
);
|
||||
$this->drupalPostForm('admin/people/permissions', $edit, t('Save permissions'));
|
||||
|
||||
// Check and update the translation status. This will import the Afrikaans
|
||||
// translations of locale_test_translate module.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
|
||||
// Override the Drupal core translation status to be up to date.
|
||||
// Drupal core should not be a subject in this test.
|
||||
$status = locale_translation_get_status();
|
||||
$status['drupal']['af']->type = 'current';
|
||||
\Drupal::state()->set('locale.translation_status', $status);
|
||||
|
||||
$this->drupalPostForm('admin/reports/translations', array(), t('Update translations'));
|
||||
|
||||
// Check if configuration translations have been imported.
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('af', 'system.maintenance');
|
||||
$this->assertEqual($override->get('message'), 'Ons is tans besig met onderhoud op @site. Wees asseblief geduldig, ons sal binnekort weer terug wees.');
|
||||
}
|
||||
|
||||
}
|
251
core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
Normal file
251
core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
Normal file
|
@ -0,0 +1,251 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleConfigTranslationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\core\language\languageInterface;
|
||||
|
||||
/**
|
||||
* Tests translation of configuration strings.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigTranslationTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* The language code used.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $langcode;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale', 'contact', 'contact_test');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Add a default locale storage for all these tests.
|
||||
$this->storage = $this->container->get('locale.storage');
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->save();
|
||||
|
||||
// Add custom language.
|
||||
$this->langcode = 'xx';
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface', 'administer modules', 'access site-wide contact form', 'administer contact forms', 'administer site configuration'));
|
||||
$this->drupalLogin($admin_user);
|
||||
$name = $this->randomMachineName(16);
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $this->langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
// Set path prefix.
|
||||
$edit = ["prefix[$this->langcode]" => $this->langcode];
|
||||
$this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic configuration translation.
|
||||
*/
|
||||
public function testConfigTranslation() {
|
||||
// Check that the maintenance message exists and create translation for it.
|
||||
$source = '@site is currently under maintenance. We should be back shortly. Thank you for your patience.';
|
||||
$string = $this->storage->findString(array('source' => $source, 'context' => '', 'type' => 'configuration'));
|
||||
$this->assertTrue($string, 'Configuration strings have been created upon installation.');
|
||||
|
||||
// Translate using the UI so configuration is refreshed.
|
||||
$message = $this->randomMachineName(20);
|
||||
$search = array(
|
||||
'string' => $string->source,
|
||||
'langcode' => $this->langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$textareas = $this->xpath('//textarea');
|
||||
$textarea = current($textareas);
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => $message,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
|
||||
// Get translation and check we've only got the message.
|
||||
$translation = \Drupal::languageManager()->getLanguageConfigOverride($this->langcode, 'system.maintenance')->get();
|
||||
$this->assertEqual(count($translation), 1, 'Got the right number of properties after translation.');
|
||||
$this->assertEqual($translation['message'], $message);
|
||||
|
||||
// Check default medium date format exists and create a translation for it.
|
||||
$string = $this->storage->findString(array('source' => 'D, m/d/Y - H:i', 'context' => 'PHP date format', 'type' => 'configuration'));
|
||||
$this->assertTrue($string, 'Configuration date formats have been created upon installation.');
|
||||
|
||||
// Translate using the UI so configuration is refreshed.
|
||||
$search = array(
|
||||
'string' => $string->source,
|
||||
'langcode' => $this->langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$textareas = $this->xpath('//textarea');
|
||||
$textarea = current($textareas);
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => 'D',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
|
||||
$translation = \Drupal::languageManager()->getLanguageConfigOverride($this->langcode, 'core.date_format.medium')->get();
|
||||
$this->assertEqual($translation['pattern'], 'D', 'Got the right date format pattern after translation.');
|
||||
|
||||
// Formatting the date 8 / 27 / 1985 @ 13:37 EST with pattern D should
|
||||
// display "Tue".
|
||||
$formatted_date = format_date(494015820, $type = 'medium', NULL, NULL, $this->langcode);
|
||||
$this->assertEqual($formatted_date, 'Tue', 'Got the right formatted date using the date format translation pattern.');
|
||||
|
||||
// Assert strings from image module config are not available.
|
||||
$string = $this->storage->findString(array('source' => 'Medium (220×220)', 'context' => '', 'type' => 'configuration'));
|
||||
$this->assertFalse($string, 'Configuration strings have been created upon installation.');
|
||||
|
||||
// Enable the image module.
|
||||
$this->drupalPostForm('admin/modules', array('modules[Field types][image][enable]' => "1"), t('Save configuration'));
|
||||
$this->rebuildContainer();
|
||||
|
||||
$string = $this->storage->findString(array('source' => 'Medium (220×220)', 'context' => '', 'type' => 'configuration'));
|
||||
$this->assertTrue($string, 'Configuration strings have been created upon installation.');
|
||||
$locations = $string->getLocations();
|
||||
$this->assertTrue(isset($locations['configuration']) && isset($locations['configuration']['image.style.medium']), 'Configuration string has been created with the right location');
|
||||
|
||||
// Check the string is unique and has no translation yet.
|
||||
$translations = $this->storage->getTranslations(['language' => $this->langcode, 'type' => 'configuration', 'name' => 'image.style.medium']);
|
||||
$this->assertEqual(count($translations), 1);
|
||||
$translation = reset($translations);
|
||||
$this->assertEqual($translation->source, $string->source);
|
||||
$this->assertTrue(empty($translation->translation));
|
||||
|
||||
// Translate using the UI so configuration is refreshed.
|
||||
$image_style_label = $this->randomMachineName(20);
|
||||
$search = array(
|
||||
'string' => $string->source,
|
||||
'langcode' => $this->langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => $image_style_label,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
|
||||
// Check the right single translation has been created.
|
||||
$translations = $this->storage->getTranslations(['language' => $this->langcode, 'type' => 'configuration', 'name' => 'image.style.medium']);
|
||||
$translation = reset($translations);
|
||||
$this->assertTrue(count($translations) == 1 && $translation->source == $string->source && $translation->translation == $image_style_label, 'Got only one translation for image configuration.');
|
||||
|
||||
// Try more complex configuration data.
|
||||
$translation = \Drupal::languageManager()->getLanguageConfigOverride($this->langcode, 'image.style.medium')->get();
|
||||
$this->assertEqual($translation['label'], $image_style_label, 'Got the right translation for image style name after translation');
|
||||
|
||||
// Uninstall the module.
|
||||
$this->drupalPostForm('admin/modules/uninstall', array('uninstall[image]' => "image"), t('Uninstall'));
|
||||
$this->drupalPostForm(NULL, array(), t('Uninstall'));
|
||||
|
||||
// Ensure that the translated configuration has been removed.
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('xx', 'image.style.medium');
|
||||
$this->assertTrue($override->isNew(), 'Translated configuration for image module removed.');
|
||||
|
||||
// Translate default category using the UI so configuration is refreshed.
|
||||
$category_label = $this->randomMachineName(20);
|
||||
$search = array(
|
||||
'string' => 'Website feedback',
|
||||
'langcode' => $this->langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => $category_label,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
|
||||
// Check if this category displayed in this language will use the
|
||||
// translation. This test ensures the entity loaded from the request
|
||||
// upcasting will already work.
|
||||
$this->drupalGet($this->langcode . '/contact/feedback');
|
||||
$this->assertText($category_label);
|
||||
|
||||
// Check if the UI does not show the translated String.
|
||||
$this->drupalGet('admin/structure/contact/manage/feedback');
|
||||
$this->assertFieldById('edit-label', 'Website feedback', 'Translation is not loaded for Edit Form.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test translatability of optional configuration in locale.
|
||||
*/
|
||||
public function testOptionalConfiguration() {
|
||||
$this->assertNodeConfig(FALSE, FALSE);
|
||||
// Enable the node module.
|
||||
$this->drupalPostForm('admin/modules', ['modules[Core][node][enable]' => "1"], t('Save configuration'));
|
||||
$this->drupalPostForm(NULL, [], t('Continue'));
|
||||
$this->rebuildContainer();
|
||||
$this->assertNodeConfig(TRUE, FALSE);
|
||||
// Enable the views module (which node provides some optional config for).
|
||||
$this->drupalPostForm('admin/modules', ['modules[Core][views][enable]' => "1"], t('Save configuration'));
|
||||
$this->rebuildContainer();
|
||||
$this->assertNodeConfig(TRUE, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that node configuration source strings are made available in locale.
|
||||
*
|
||||
* @param bool $required
|
||||
* Whether to assume a sample of the required default configuration is
|
||||
* present.
|
||||
* @param bool $optional
|
||||
* Whether to assume a sample of the optional default configuration is
|
||||
* present.
|
||||
*/
|
||||
protected function assertNodeConfig($required, $optional) {
|
||||
// Check the required default configuration in node module.
|
||||
$string = $this->storage->findString(['source' => 'Make content sticky', 'context' => '', 'type' => 'configuration']);
|
||||
if ($required) {
|
||||
$this->assertFalse($this->config('system.action.node_make_sticky_action')->isNew());
|
||||
$this->assertTrue($string, 'Node action text can be found with node module.');
|
||||
}
|
||||
else {
|
||||
$this->assertTrue($this->config('system.action.node_make_sticky_action')->isNew());
|
||||
$this->assertFalse($string, 'Node action text can not be found without node module.');
|
||||
}
|
||||
|
||||
// Check the optional default configuration in node module.
|
||||
$string = $this->storage->findString(['source' => 'No front page content has been created yet.', 'context' => '', 'type' => 'configuration']);
|
||||
if ($optional) {
|
||||
$this->assertFalse($this->config('views.view.frontpage')->isNew());
|
||||
$this->assertTrue($string, 'Node view text can be found with node and views modules.');
|
||||
}
|
||||
else {
|
||||
$this->assertTrue($this->config('views.view.frontpage')->isNew());
|
||||
$this->assertFalse($string, 'Node view text can not be found without node and/or views modules.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
202
core/modules/locale/src/Tests/LocaleContentTest.php
Normal file
202
core/modules/locale/src/Tests/LocaleContentTest.php
Normal file
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleContentTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
/**
|
||||
* Tests you can enable multilingual support on content types and configure a
|
||||
* language for a node.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleContentTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'locale');
|
||||
|
||||
/**
|
||||
* Verifies that machine name fields are always LTR.
|
||||
*/
|
||||
public function testMachineNameLTR() {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages', 'administer site configuration'));
|
||||
|
||||
// Log in as admin.
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Verify that the machine name field is LTR for a new content type.
|
||||
$this->drupalGet('admin/structure/types/add');
|
||||
$this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL, 'The machine name field is LTR when no additional language is configured.');
|
||||
|
||||
// Install the Arabic language (which is RTL) and configure as the default.
|
||||
$edit = array();
|
||||
$edit['predefined_langcode'] = 'ar';
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
$edit = array(
|
||||
'site_default_language' => 'ar',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language', $edit, t('Save configuration'));
|
||||
|
||||
// Verify that the machine name field is still LTR for a new content type.
|
||||
$this->drupalGet('admin/structure/types/add');
|
||||
$this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL, 'The machine name field is LTR when the default language is RTL.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a content type can be set to multilingual and language is present.
|
||||
*/
|
||||
public function testContentTypeLanguageConfiguration() {
|
||||
$type1 = $this->drupalCreateContentType();
|
||||
$type2 = $this->drupalCreateContentType();
|
||||
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
|
||||
// User to create a node.
|
||||
$web_user = $this->drupalCreateUser(array("create {$type1->id()} content", "create {$type2->id()} content", "edit any {$type2->id()} content"));
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
// Code for the language.
|
||||
$langcode = 'xx';
|
||||
// The English name for the language.
|
||||
$name = $this->randomMachineName(16);
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
|
||||
// Set the content type to use multilingual support.
|
||||
$this->drupalGet("admin/structure/types/manage/{$type2->id()}");
|
||||
$this->assertText(t('Language settings'), 'Multilingual support widget present on content type configuration form.');
|
||||
$edit = array(
|
||||
'language_configuration[language_alterable]' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm("admin/structure/types/manage/{$type2->id()}", $edit, t('Save content type'));
|
||||
$this->assertRaw(t('The content type %type has been updated.', array('%type' => $type2->label())));
|
||||
$this->drupalLogout();
|
||||
\Drupal::languageManager()->reset();
|
||||
|
||||
// Verify language selection is not present on the node add form.
|
||||
$this->drupalLogin($web_user);
|
||||
$this->drupalGet("node/add/{$type1->id()}");
|
||||
// Verify language select list is not present.
|
||||
$this->assertNoFieldByName('langcode[0][value]', NULL, 'Language select not present on the node add form.');
|
||||
|
||||
// Verify language selection appears on the node add form.
|
||||
$this->drupalGet("node/add/{$type2->id()}");
|
||||
// Verify language select list is present.
|
||||
$this->assertFieldByName('langcode[0][value]', NULL, 'Language select present on the node add form.');
|
||||
// Ensure language appears.
|
||||
$this->assertText($name, 'Language present.');
|
||||
|
||||
// Create a node.
|
||||
$node_title = $this->randomMachineName();
|
||||
$node_body = $this->randomMachineName();
|
||||
$edit = array(
|
||||
'type' => $type2->id(),
|
||||
'title' => $node_title,
|
||||
'body' => array(array('value' => $node_body)),
|
||||
'langcode' => $langcode,
|
||||
);
|
||||
$node = $this->drupalCreateNode($edit);
|
||||
// Edit the content and ensure correct language is selected.
|
||||
$path = 'node/' . $node->id() . '/edit';
|
||||
$this->drupalGet($path);
|
||||
$this->assertRaw('<option value="' . $langcode . '" selected="selected">' . $name . '</option>', 'Correct language selected.');
|
||||
// Ensure we can change the node language.
|
||||
$edit = array(
|
||||
'langcode[0][value]' => 'en',
|
||||
);
|
||||
$this->drupalPostForm($path, $edit, t('Save'));
|
||||
$this->assertRaw(t('%title has been updated.', array('%title' => $node_title)));
|
||||
|
||||
$this->drupalLogout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a dir and lang tags exist in node's attributes.
|
||||
*/
|
||||
public function testContentTypeDirLang() {
|
||||
$type = $this->drupalCreateContentType();
|
||||
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
|
||||
// User to create a node.
|
||||
$web_user = $this->drupalCreateUser(array("create {$type->id()} content", "edit own {$type->id()} content"));
|
||||
|
||||
// Login as admin.
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Install Arabic language.
|
||||
$edit = array();
|
||||
$edit['predefined_langcode'] = 'ar';
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
// Install Spanish language.
|
||||
$edit = array();
|
||||
$edit['predefined_langcode'] = 'es';
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
\Drupal::languageManager()->reset();
|
||||
|
||||
// Set the content type to use multilingual support.
|
||||
$this->drupalGet("admin/structure/types/manage/{$type->id()}");
|
||||
$edit = array(
|
||||
'language_configuration[language_alterable]' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm("admin/structure/types/manage/{$type->id()}", $edit, t('Save content type'));
|
||||
$this->assertRaw(t('The content type %type has been updated.', array('%type' => $type->label())));
|
||||
$this->drupalLogout();
|
||||
|
||||
// Login as web user to add new node.
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Create three nodes: English, Arabic and Spanish.
|
||||
$nodes = array();
|
||||
foreach (array('en', 'es', 'ar') as $langcode) {
|
||||
$nodes[$langcode] = $this->drupalCreateNode(array(
|
||||
'langcode' => $langcode,
|
||||
'type' => $type->id(),
|
||||
'promote' => NODE_PROMOTED,
|
||||
));
|
||||
}
|
||||
|
||||
// Check if English node does not have lang tag.
|
||||
$this->drupalGet('node/' . $nodes['en']->id());
|
||||
$element = $this->cssSelect('article.node[lang="en"]');
|
||||
$this->assertTrue(empty($element), 'The lang tag has not been assigned to the English node.');
|
||||
|
||||
// Check if English node does not have dir tag.
|
||||
$element = $this->cssSelect('article.node[dir="ltr"]');
|
||||
$this->assertTrue(empty($element), 'The dir tag has not been assigned to the English node.');
|
||||
|
||||
// Check if Arabic node has lang="ar" & dir="rtl" tags.
|
||||
$this->drupalGet('node/' . $nodes['ar']->id());
|
||||
$element = $this->cssSelect('article.node[lang="ar"][dir="rtl"]');
|
||||
$this->assertTrue(!empty($element), 'The lang and dir tags have been assigned correctly to the Arabic node.');
|
||||
|
||||
// Check if Spanish node has lang="es" tag.
|
||||
$this->drupalGet('node/' . $nodes['es']->id());
|
||||
$element = $this->cssSelect('article.node[lang="es"]');
|
||||
$this->assertTrue(!empty($element), 'The lang tag has been assigned correctly to the Spanish node.');
|
||||
|
||||
// Check if Spanish node does not have dir="ltr" tag.
|
||||
$element = $this->cssSelect('article.node[lang="es"][dir="ltr"]');
|
||||
$this->assertTrue(empty($element), 'The dir tag has not been assigned to the Spanish node.');
|
||||
}
|
||||
|
||||
}
|
181
core/modules/locale/src/Tests/LocaleExportTest.php
Normal file
181
core/modules/locale/src/Tests/LocaleExportTest.php
Normal file
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleExportTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests the exportation of locale files.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleExportTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale');
|
||||
|
||||
/**
|
||||
* A user able to create languages and export translations.
|
||||
*/
|
||||
protected $adminUser = NULL;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Copy test po files to the translations directory.
|
||||
file_unmanaged_copy(drupal_get_path('module', 'locale') . '/tests/test.de.po', 'translations://', FILE_EXISTS_REPLACE);
|
||||
file_unmanaged_copy(drupal_get_path('module', 'locale') . '/tests/test.xx.po', 'translations://', FILE_EXISTS_REPLACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test exportation of translations.
|
||||
*/
|
||||
public function testExportTranslation() {
|
||||
// First import some known translations.
|
||||
// This will also automatically add the 'fr' language.
|
||||
$name = tempnam('temporary://', "po_") . '.po';
|
||||
file_put_contents($name, $this->getPoFile());
|
||||
$this->drupalPostForm('admin/config/regional/translate/import', array(
|
||||
'langcode' => 'fr',
|
||||
'files[file]' => $name,
|
||||
), t('Import'));
|
||||
drupal_unlink($name);
|
||||
|
||||
// Get the French translations.
|
||||
$this->drupalPostForm('admin/config/regional/translate/export', array(
|
||||
'langcode' => 'fr',
|
||||
), t('Export'));
|
||||
|
||||
// Ensure we have a translation file.
|
||||
$this->assertRaw('# French translation of Drupal', 'Exported French translation file.');
|
||||
// Ensure our imported translations exist in the file.
|
||||
$this->assertRaw('msgstr "lundi"', 'French translations present in exported file.');
|
||||
|
||||
// Import some more French translations which will be marked as customized.
|
||||
$name = tempnam('temporary://', "po2_") . '.po';
|
||||
file_put_contents($name, $this->getCustomPoFile());
|
||||
$this->drupalPostForm('admin/config/regional/translate/import', array(
|
||||
'langcode' => 'fr',
|
||||
'files[file]' => $name,
|
||||
'customized' => 1,
|
||||
), t('Import'));
|
||||
drupal_unlink($name);
|
||||
|
||||
// Create string without translation in the locales_source table.
|
||||
$this->container
|
||||
->get('locale.storage')
|
||||
->createString()
|
||||
->setString('February')
|
||||
->save();
|
||||
|
||||
// Export only customized French translations.
|
||||
$this->drupalPostForm('admin/config/regional/translate/export', array(
|
||||
'langcode' => 'fr',
|
||||
'content_options[not_customized]' => FALSE,
|
||||
'content_options[customized]' => TRUE,
|
||||
'content_options[not_translated]' => FALSE,
|
||||
), t('Export'));
|
||||
|
||||
// Ensure we have a translation file.
|
||||
$this->assertRaw('# French translation of Drupal', 'Exported French translation file with only customized strings.');
|
||||
// Ensure the customized translations exist in the file.
|
||||
$this->assertRaw('msgstr "janvier"', 'French custom translation present in exported file.');
|
||||
// Ensure no untranslated strings exist in the file.
|
||||
$this->assertNoRaw('msgid "February"', 'Untranslated string not present in exported file.');
|
||||
|
||||
// Export only untranslated French translations.
|
||||
$this->drupalPostForm('admin/config/regional/translate/export', array(
|
||||
'langcode' => 'fr',
|
||||
'content_options[not_customized]' => FALSE,
|
||||
'content_options[customized]' => FALSE,
|
||||
'content_options[not_translated]' => TRUE,
|
||||
), t('Export'));
|
||||
|
||||
// Ensure we have a translation file.
|
||||
$this->assertRaw('# French translation of Drupal', 'Exported French translation file with only untranslated strings.');
|
||||
// Ensure no customized translations exist in the file.
|
||||
$this->assertNoRaw('msgstr "janvier"', 'French custom translation not present in exported file.');
|
||||
// Ensure the untranslated strings exist in the file, and with right quotes.
|
||||
$this->assertRaw($this->getUntranslatedString(), 'Empty string present in exported file.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test exportation of translation template file.
|
||||
*/
|
||||
public function testExportTranslationTemplateFile() {
|
||||
// Load an admin page with JavaScript so _drupal_add_library() fires at
|
||||
// least once and _locale_parse_js_file() gets to run at least once so that
|
||||
// the locales_source table gets populated with something.
|
||||
$this->drupalGet('admin/config/regional/language');
|
||||
// Get the translation template file.
|
||||
$this->drupalPostForm('admin/config/regional/translate/export', array(), t('Export'));
|
||||
// Ensure we have a translation file.
|
||||
$this->assertRaw('# LANGUAGE translation of PROJECT', 'Exported translation template file.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a proper .po file.
|
||||
*/
|
||||
public function getPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "lundi"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file which strings will be marked
|
||||
* as customized.
|
||||
*/
|
||||
public function getCustomPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "January"
|
||||
msgstr "janvier"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file fragment with an untranslated string.
|
||||
*
|
||||
* @return string
|
||||
* A .po file fragment with an untranslated string.
|
||||
*/
|
||||
public function getUntranslatedString() {
|
||||
return <<< EOF
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
EOF;
|
||||
}
|
||||
|
||||
}
|
61
core/modules/locale/src/Tests/LocaleFileSystemFormTest.php
Normal file
61
core/modules/locale/src/Tests/LocaleFileSystemFormTest.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleFileSystemFormTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests the locale functionality in the altered file settings form.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleFileSystemFormTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(){
|
||||
parent::setUp();
|
||||
$account = $this->drupalCreateUser(array('administer site configuration'));
|
||||
$this->drupalLogin($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation directory settings on the file settings form.
|
||||
*/
|
||||
function testFileConfigurationPage() {
|
||||
// By default there should be no setting for the translation directory.
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->assertNoFieldByName('translation_path');
|
||||
|
||||
// With locale module installed, the setting should appear.
|
||||
$module_installer = $this->container->get('module_installer');
|
||||
$module_installer->install(['locale']);
|
||||
$this->rebuildContainer();
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->assertFieldByName('translation_path');
|
||||
|
||||
// The setting should persist.
|
||||
$translation_path = $this->publicFilesDirectory . '/translations_changed';
|
||||
$fields = array(
|
||||
'translation_path' => $translation_path
|
||||
);
|
||||
$this->drupalPostForm(NULL, $fields, t('Save configuration'));
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->assertFieldByName('translation_path', $translation_path);
|
||||
$this->assertEqual($translation_path, $this->config('locale.settings')->get('translation.path'));
|
||||
}
|
||||
|
||||
}
|
644
core/modules/locale/src/Tests/LocaleImportFunctionalTest.php
Normal file
644
core/modules/locale/src/Tests/LocaleImportFunctionalTest.php
Normal file
|
@ -0,0 +1,644 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleImportFunctionalTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
/**
|
||||
* Tests the import of locale files.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleImportFunctionalTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale', 'dblog');
|
||||
|
||||
/**
|
||||
* A user able to create languages and import translations.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A user able to create languages, import translations and access site
|
||||
* reports.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $adminUserAccessSiteReports;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Copy test po files to the translations directory.
|
||||
file_unmanaged_copy(drupal_get_path('module', 'locale') . '/tests/test.de.po', 'translations://', FILE_EXISTS_REPLACE);
|
||||
file_unmanaged_copy(drupal_get_path('module', 'locale') . '/tests/test.xx.po', 'translations://', FILE_EXISTS_REPLACE);
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
|
||||
$this->adminUserAccessSiteReports = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages', 'access site reports'));
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test import of standalone .po files.
|
||||
*/
|
||||
public function testStandalonePoFile() {
|
||||
// Try importing a .po file.
|
||||
$this->importPoFile($this->getPoFile(), array(
|
||||
'langcode' => 'fr',
|
||||
));
|
||||
$this->config('locale.settings');
|
||||
// The import should automatically create the corresponding language.
|
||||
$this->assertRaw(t('The language %language has been created.', array('%language' => 'French')), 'The language has been automatically created.');
|
||||
|
||||
// The import should have created 8 strings.
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 8, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
|
||||
|
||||
// This import should have saved plural forms to have 2 variants.
|
||||
$locale_plurals = \Drupal::state()->get('locale.translation.plurals') ?: array();
|
||||
$this->assert($locale_plurals['fr']['plurals'] == 2, 'Plural number initialized.');
|
||||
|
||||
// Ensure we were redirected correctly.
|
||||
$this->assertUrl(\Drupal::url('locale.translate_page', [], ['absolute' => TRUE]), [], 'Correct page redirection.');
|
||||
|
||||
// Try importing a .po file with invalid tags.
|
||||
$this->importPoFile($this->getBadPoFile(), array(
|
||||
'langcode' => 'fr',
|
||||
));
|
||||
|
||||
// The import should have created 1 string and rejected 2.
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
|
||||
|
||||
$skip_message = \Drupal::translation()->formatPlural(2, 'One translation string was skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.', array('@url' => \Drupal::url('dblog.overview')));
|
||||
$this->assertRaw($skip_message, 'Unsafe strings were skipped.');
|
||||
|
||||
// Repeat the process with a user that can access site reports, and this
|
||||
// time the different warnings must contain links to the log.
|
||||
$this->drupalLogin($this->adminUserAccessSiteReports);
|
||||
|
||||
// Try importing a .po file with invalid tags.
|
||||
$this->importPoFile($this->getBadPoFile(), array(
|
||||
'langcode' => 'fr',
|
||||
));
|
||||
|
||||
$skip_message = \Drupal::translation()->formatPlural(2, 'One translation string was skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', '@count translation strings were skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', array('@url' => \Drupal::url('dblog.overview')));
|
||||
$this->assertRaw($skip_message, 'Unsafe strings were skipped.');
|
||||
|
||||
// Check empty files import with a user that cannot access site reports..
|
||||
$this->drupalLogin($this->adminUser);
|
||||
// Try importing a zero byte sized .po file.
|
||||
$this->importPoFile($this->getEmptyPoFile(), array(
|
||||
'langcode' => 'fr',
|
||||
));
|
||||
// The import should have created 0 string and rejected 0.
|
||||
$this->assertRaw(t('One translation file could not be imported. See the log for details.'), 'The empty translation file import reported no translations imported.');
|
||||
|
||||
// Repeat the process with a user that can access site reports, and this
|
||||
// time the different warnings must contain links to the log.
|
||||
$this->drupalLogin($this->adminUserAccessSiteReports);
|
||||
// Try importing a zero byte sized .po file.
|
||||
$this->importPoFile($this->getEmptyPoFile(), array(
|
||||
'langcode' => 'fr',
|
||||
));
|
||||
// The import should have created 0 string and rejected 0.
|
||||
$this->assertRaw(t('One translation file could not be imported. <a href="@url">See the log</a> for details.', array('@url' => \Drupal::url('dblog.overview'))), 'The empty translation file import reported no translations imported.');
|
||||
|
||||
// Try importing a .po file which doesn't exist.
|
||||
$name = $this->randomMachineName(16);
|
||||
$this->drupalPostForm('admin/config/regional/translate/import', array(
|
||||
'langcode' => 'fr',
|
||||
'files[file]' => $name,
|
||||
), t('Import'));
|
||||
$this->assertUrl(\Drupal::url('locale.translate_import', [], ['absolute' => TRUE]), [], 'Correct page redirection.');
|
||||
$this->assertText(t('File to import not found.'), 'File to import not found message.');
|
||||
|
||||
// Try importing a .po file with overriding strings, and ensure existing
|
||||
// strings are kept.
|
||||
$this->importPoFile($this->getOverwritePoFile(), array(
|
||||
'langcode' => 'fr',
|
||||
));
|
||||
|
||||
// The import should have created 1 string.
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
|
||||
// Ensure string wasn't overwritten.
|
||||
$search = array(
|
||||
'string' => 'Montag',
|
||||
'langcode' => 'fr',
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText(t('No strings available.'), 'String not overwritten by imported string.');
|
||||
|
||||
// This import should not have changed number of plural forms.
|
||||
$locale_plurals = \Drupal::state()->get('locale.translation.plurals') ?: array();
|
||||
$this->assert($locale_plurals['fr']['plurals'] == 2, 'Plural numbers untouched.');
|
||||
|
||||
// Try importing a .po file with overriding strings, and ensure existing
|
||||
// strings are overwritten.
|
||||
$this->importPoFile($this->getOverwritePoFile(), array(
|
||||
'langcode' => 'fr',
|
||||
'overwrite_options[not_customized]' => TRUE,
|
||||
));
|
||||
|
||||
// The import should have updated 2 strings.
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), 'The translation file was successfully imported.');
|
||||
// Ensure string was overwritten.
|
||||
$search = array(
|
||||
'string' => 'Montag',
|
||||
'langcode' => 'fr',
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertNoText(t('No strings available.'), 'String overwritten by imported string.');
|
||||
// This import should have changed number of plural forms.
|
||||
$locale_plurals = \Drupal::state()->get('locale.translation.plurals') ?: array();
|
||||
$this->assert($locale_plurals['fr']['plurals'] == 3, 'Plural numbers changed.');
|
||||
|
||||
// Importing a .po file and mark its strings as customized strings.
|
||||
$this->importPoFile($this->getCustomPoFile(), array(
|
||||
'langcode' => 'fr',
|
||||
'customized' => TRUE,
|
||||
));
|
||||
|
||||
// The import should have created 6 strings.
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 6, '%update' => 0, '%delete' => 0)), 'The customized translation file was successfully imported.');
|
||||
|
||||
// The database should now contain 6 customized strings (two imported
|
||||
// strings are not translated).
|
||||
$count = db_query('SELECT COUNT(*) FROM {locales_target} WHERE customized = :custom', array(':custom' => 1))->fetchField();
|
||||
$this->assertEqual($count, 6, 'Customized translations successfully imported.');
|
||||
|
||||
// Try importing a .po file with overriding strings, and ensure existing
|
||||
// customized strings are kept.
|
||||
$this->importPoFile($this->getCustomOverwritePoFile(), array(
|
||||
'langcode' => 'fr',
|
||||
'overwrite_options[not_customized]' => TRUE,
|
||||
'overwrite_options[customized]' => FALSE,
|
||||
));
|
||||
|
||||
// The import should have created 1 string.
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The customized translation file was successfully imported.');
|
||||
// Ensure string wasn't overwritten.
|
||||
$search = array(
|
||||
'string' => 'januari',
|
||||
'langcode' => 'fr',
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText(t('No strings available.'), 'Customized string not overwritten by imported string.');
|
||||
|
||||
// Try importing a .po file with overriding strings, and ensure existing
|
||||
// customized strings are overwritten.
|
||||
$this->importPoFile($this->getCustomOverwritePoFile(), array(
|
||||
'langcode' => 'fr',
|
||||
'overwrite_options[not_customized]' => FALSE,
|
||||
'overwrite_options[customized]' => TRUE,
|
||||
));
|
||||
|
||||
// The import should have updated 2 strings.
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), 'The customized translation file was successfully imported.');
|
||||
// Ensure string was overwritten.
|
||||
$search = array(
|
||||
'string' => 'januari',
|
||||
'langcode' => 'fr',
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertNoText(t('No strings available.'), 'Customized string overwritten by imported string.');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test msgctxt context support.
|
||||
*/
|
||||
public function testLanguageContext() {
|
||||
// Try importing a .po file.
|
||||
$this->importPoFile($this->getPoFileWithContext(), array(
|
||||
'langcode' => 'hr',
|
||||
));
|
||||
|
||||
$this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', 'Long month name context is working.');
|
||||
$this->assertIdentical(t('May', array(), array('langcode' => 'hr')), 'Svi.', 'Default context is working.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test empty msgstr at end of .po file see #611786.
|
||||
*/
|
||||
public function testEmptyMsgstr() {
|
||||
$langcode = 'hu';
|
||||
|
||||
// Try importing a .po file.
|
||||
$this->importPoFile($this->getPoFileWithMsgstr(), array(
|
||||
'langcode' => $langcode,
|
||||
));
|
||||
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
|
||||
$this->assertIdentical(t('Operations', array(), array('langcode' => $langcode)), 'Műveletek', 'String imported and translated.');
|
||||
|
||||
// Try importing a .po file.
|
||||
$this->importPoFile($this->getPoFileWithEmptyMsgstr(), array(
|
||||
'langcode' => $langcode,
|
||||
'overwrite_options[not_customized]' => TRUE,
|
||||
));
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 0, '%update' => 0, '%delete' => 1)), 'The translation file was successfully imported.');
|
||||
|
||||
$str = "Operations";
|
||||
$search = array(
|
||||
'string' => $str,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText($str, 'Search found the string as untranslated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests .po file import with configuration translation.
|
||||
*/
|
||||
public function testConfigPoFile() {
|
||||
// Values for translations to assert. Config key, original string,
|
||||
// translation and config property name.
|
||||
$config_strings = array(
|
||||
'system.maintenance' => array(
|
||||
'@site is currently under maintenance. We should be back shortly. Thank you for your patience.',
|
||||
'@site karbantartás alatt áll. Rövidesen visszatérünk. Köszönjük a türelmet.',
|
||||
'message',
|
||||
),
|
||||
'user.role.anonymous' => array(
|
||||
'Anonymous user',
|
||||
'Névtelen felhasználó',
|
||||
'label',
|
||||
),
|
||||
);
|
||||
|
||||
// Add custom language for testing.
|
||||
$langcode = 'xx';
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $this->randomMachineName(16),
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
|
||||
// Check for the source strings we are going to translate. Adding the
|
||||
// custom language should have made the process to export configuration
|
||||
// strings to interface translation executed.
|
||||
$locale_storage = $this->container->get('locale.storage');
|
||||
foreach ($config_strings as $config_string) {
|
||||
$string = $locale_storage->findString(array('source' => $config_string[0], 'context' => '', 'type' => 'configuration'));
|
||||
$this->assertTrue($string, 'Configuration strings have been created upon installation.');
|
||||
}
|
||||
|
||||
// Import a .po file to translate.
|
||||
$this->importPoFile($this->getPoFileWithConfig(), array(
|
||||
'langcode' => $langcode,
|
||||
));
|
||||
|
||||
// Translations got recorded in the interface translation system.
|
||||
foreach ($config_strings as $config_string) {
|
||||
$search = array(
|
||||
'string' => $config_string[0],
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText($config_string[1], format_string('Translation of @string found.', array('@string' => $config_string[0])));
|
||||
}
|
||||
|
||||
// Test that translations got recorded in the config system.
|
||||
$overrides = \Drupal::service('language.config_factory_override');
|
||||
foreach ($config_strings as $config_key => $config_string) {
|
||||
$override = $overrides->getOverride($langcode, $config_key);
|
||||
$this->assertEqual($override->get($config_string[2]), $config_string[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests .po file import with user.settings configuration.
|
||||
*/
|
||||
public function testConfigtranslationImportingPoFile() {
|
||||
// Set the language code.
|
||||
$langcode = 'de';
|
||||
|
||||
// Import a .po file to translate.
|
||||
$this->importPoFile($this->getPoFileWithConfigDe(), array(
|
||||
'langcode' => $langcode));
|
||||
|
||||
// Check that the 'Anonymous' string is translated.
|
||||
$config = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'user.settings');
|
||||
$this->assertEqual($config->get('anonymous'), 'Anonymous German');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the translation are imported when a new language is created.
|
||||
*/
|
||||
public function testCreatedLanguageTranslation() {
|
||||
// Import a .po file to add de language.
|
||||
$this->importPoFile($this->getPoFileWithConfigDe(), array('langcode' => 'de'));
|
||||
|
||||
// Get the language.entity.de label and check it's been translated.
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('de', 'language.entity.de');
|
||||
$this->assertEqual($override->get('label'), 'Deutsch');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function: import a standalone .po file in a given language.
|
||||
*
|
||||
* @param string $contents
|
||||
* Contents of the .po file to import.
|
||||
* @param array $options
|
||||
* (optional) Additional options to pass to the translation import form.
|
||||
*/
|
||||
public function importPoFile($contents, array $options = array()) {
|
||||
$name = tempnam('temporary://', "po_") . '.po';
|
||||
file_put_contents($name, $contents);
|
||||
$options['files[file]'] = $name;
|
||||
$this->drupalPostForm('admin/config/regional/translate/import', $options, t('Import'));
|
||||
drupal_unlink($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a proper .po file.
|
||||
*/
|
||||
public function getPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "One sheep"
|
||||
msgid_plural "@count sheep"
|
||||
msgstr[0] "un mouton"
|
||||
msgstr[1] "@count moutons"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "lundi"
|
||||
|
||||
msgid "Tuesday"
|
||||
msgstr "mardi"
|
||||
|
||||
msgid "Wednesday"
|
||||
msgstr "mercredi"
|
||||
|
||||
msgid "Thursday"
|
||||
msgstr "jeudi"
|
||||
|
||||
msgid "Friday"
|
||||
msgstr "vendredi"
|
||||
|
||||
msgid "Saturday"
|
||||
msgstr "samedi"
|
||||
|
||||
msgid "Sunday"
|
||||
msgstr "dimanche"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a empty .po file.
|
||||
*/
|
||||
public function getEmptyPoFile() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a bad .po file.
|
||||
*/
|
||||
public function getBadPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Save configuration"
|
||||
msgstr "Enregistrer la configuration"
|
||||
|
||||
msgid "edit"
|
||||
msgstr "modifier<img SRC="javascript:alert(\'xss\');">"
|
||||
|
||||
msgid "delete"
|
||||
msgstr "supprimer<script>alert('xss');</script>"
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a proper .po file for testing.
|
||||
*/
|
||||
public function getOverwritePoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "Montag"
|
||||
|
||||
msgid "Day"
|
||||
msgstr "Jour"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file which strings will be marked
|
||||
* as customized.
|
||||
*/
|
||||
public function getCustomPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "One dog"
|
||||
msgid_plural "@count dogs"
|
||||
msgstr[0] "un chien"
|
||||
msgstr[1] "@count chiens"
|
||||
|
||||
msgid "January"
|
||||
msgstr "janvier"
|
||||
|
||||
msgid "February"
|
||||
msgstr "février"
|
||||
|
||||
msgid "March"
|
||||
msgstr "mars"
|
||||
|
||||
msgid "April"
|
||||
msgstr "avril"
|
||||
|
||||
msgid "June"
|
||||
msgstr "juin"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file for testing customized strings.
|
||||
*/
|
||||
public function getCustomOverwritePoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "January"
|
||||
msgstr "januari"
|
||||
|
||||
msgid "February"
|
||||
msgstr "februari"
|
||||
|
||||
msgid "July"
|
||||
msgstr "juillet"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with context.
|
||||
*/
|
||||
public function getPoFileWithContext() {
|
||||
// Croatian (code hr) is one of the languages that have a different
|
||||
// form for the full name and the abbreviated name for the month of May.
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
|
||||
|
||||
msgctxt "Long month name"
|
||||
msgid "May"
|
||||
msgstr "Svibanj"
|
||||
|
||||
msgid "May"
|
||||
msgstr "Svi."
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with an empty last item.
|
||||
*/
|
||||
public function getPoFileWithEmptyMsgstr() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Operations"
|
||||
msgstr ""
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with an empty last item.
|
||||
*/
|
||||
public function getPoFileWithMsgstr() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Operations"
|
||||
msgstr "Műveletek"
|
||||
|
||||
msgid "Will not appear in Drupal core, so we can ensure the test passes"
|
||||
msgstr ""
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with configuration translations.
|
||||
*/
|
||||
public function getPoFileWithConfig() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "@site is currently under maintenance. We should be back shortly. Thank you for your patience."
|
||||
msgstr "@site karbantartás alatt áll. Rövidesen visszatérünk. Köszönjük a türelmet."
|
||||
|
||||
msgid "Anonymous user"
|
||||
msgstr "Névtelen felhasználó"
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with configuration translations.
|
||||
*/
|
||||
public function getPoFileWithConfigDe() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Anonymous"
|
||||
msgstr "Anonymous German"
|
||||
|
||||
msgid "German"
|
||||
msgstr "Deutsch"
|
||||
|
||||
EOF;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleJavascriptTranslationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Tests parsing js files for translatable strings.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleJavascriptTranslationTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale');
|
||||
|
||||
public function testFileParsing() {
|
||||
$filename = drupal_get_path('module', 'locale') . '/tests/locale_test.js';
|
||||
|
||||
// Parse the file to look for source strings.
|
||||
_locale_parse_js_file($filename);
|
||||
|
||||
// Get all of the source strings that were found.
|
||||
$strings = $this->container
|
||||
->get('locale.storage')
|
||||
->getStrings(array(
|
||||
'type' => 'javascript',
|
||||
'name' => $filename,
|
||||
));
|
||||
|
||||
$source_strings = array();
|
||||
foreach ($strings as $string) {
|
||||
$source_strings[$string->source] = $string->context;
|
||||
}
|
||||
|
||||
$etx = LOCALE_PLURAL_DELIMITER;
|
||||
// List of all strings that should be in the file.
|
||||
$test_strings = array(
|
||||
'Standard Call t' => '',
|
||||
'Whitespace Call t' => '',
|
||||
|
||||
'Single Quote t' => '',
|
||||
"Single Quote \\'Escaped\\' t" => '',
|
||||
'Single Quote Concat strings t' => '',
|
||||
|
||||
'Double Quote t' => '',
|
||||
"Double Quote \\\"Escaped\\\" t" => '',
|
||||
'Double Quote Concat strings t' => '',
|
||||
|
||||
'Context !key Args t' => 'Context string',
|
||||
|
||||
'Context Unquoted t' => 'Context string unquoted',
|
||||
'Context Single Quoted t' => 'Context string single quoted',
|
||||
'Context Double Quoted t' => 'Context string double quoted',
|
||||
|
||||
"Standard Call plural{$etx}Standard Call @count plural" => '',
|
||||
"Whitespace Call plural{$etx}Whitespace Call @count plural" => '',
|
||||
|
||||
"Single Quote plural{$etx}Single Quote @count plural" => '',
|
||||
"Single Quote \\'Escaped\\' plural{$etx}Single Quote \\'Escaped\\' @count plural" => '',
|
||||
|
||||
"Double Quote plural{$etx}Double Quote @count plural" => '',
|
||||
"Double Quote \\\"Escaped\\\" plural{$etx}Double Quote \\\"Escaped\\\" @count plural" => '',
|
||||
|
||||
"Context !key Args plural{$etx}Context !key Args @count plural" => 'Context string',
|
||||
|
||||
"Context Unquoted plural{$etx}Context Unquoted @count plural" => 'Context string unquoted',
|
||||
"Context Single Quoted plural{$etx}Context Single Quoted @count plural" => 'Context string single quoted',
|
||||
"Context Double Quoted plural{$etx}Context Double Quoted @count plural" => 'Context string double quoted',
|
||||
);
|
||||
|
||||
// Assert that all strings were found properly.
|
||||
foreach ($test_strings as $str => $context) {
|
||||
$args = array('%source' => $str, '%context' => $context);
|
||||
|
||||
// Make sure that the string was found in the file.
|
||||
$this->assertTrue(isset($source_strings[$str]), SafeMarkup::format('Found source string: %source', $args));
|
||||
|
||||
// Make sure that the proper context was matched.
|
||||
$message = $context ? SafeMarkup::format('Context for %source is %context', $args) : SafeMarkup::format('Context for %source is blank', $args);
|
||||
$this->assertTrue(isset($source_strings[$str]) && $source_strings[$str] === $context, $message);
|
||||
}
|
||||
|
||||
$this->assertEqual(count($source_strings), count($test_strings), 'Found correct number of source strings.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert translations JS is added before drupal.js, because it depends on it.
|
||||
*/
|
||||
public function testLocaleTranslationJsDependencies() {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface'));
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
// Code for the language.
|
||||
$langcode = 'es';
|
||||
// The English name for the language.
|
||||
$name = $this->randomMachineName(16);
|
||||
// The domain prefix.
|
||||
$prefix = $langcode;
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
|
||||
// Set path prefix.
|
||||
$edit = array("prefix[$langcode]" => $prefix);
|
||||
$this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
|
||||
|
||||
// This forces locale.admin.js string sources to be imported, which contains
|
||||
// the next translation.
|
||||
$this->drupalGet($prefix . '/admin/config/regional/translate');
|
||||
|
||||
// Translate a string in locale.admin.js to our new language.
|
||||
$strings = \Drupal::service('locale.storage')
|
||||
->getStrings(array(
|
||||
'source' => 'Show description',
|
||||
'type' => 'javascript',
|
||||
'name' => 'core/modules/locale/locale.admin.js',
|
||||
));
|
||||
$string = $strings[0];
|
||||
|
||||
$this->drupalPostForm(NULL, ['string' => 'Show description'], t('Filter'));
|
||||
$edit = ['strings[' . $string->lid . '][translations][0]' => $this->randomString(16)];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save translations'));
|
||||
|
||||
// Calculate the filename of the JS including the translations.
|
||||
$js_translation_files = \Drupal::state()->get('locale.translation.javascript');
|
||||
$js_filename = $prefix . '_' . $js_translation_files[$prefix] . '.js';
|
||||
|
||||
// Assert translations JS is included before drupal.js.
|
||||
$this->assertTrue(strpos($this->content, $js_filename) < strpos($this->content, 'core/misc/drupal.js'), 'Translations are included before Drupal.t.');
|
||||
}
|
||||
|
||||
}
|
39
core/modules/locale/src/Tests/LocaleLibraryAlterTest.php
Normal file
39
core/modules/locale/src/Tests/LocaleLibraryAlterTest.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleLibraryAlterTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests localization of the JavaScript libraries.
|
||||
*
|
||||
* Currently, only the jQuery datepicker is localized using Drupal translations.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleLibraryAlterTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale');
|
||||
|
||||
/**
|
||||
* Verifies that the datepicker can be localized.
|
||||
*
|
||||
* @see locale_library_alter()
|
||||
*/
|
||||
public function testLibraryAlter() {
|
||||
$assets = new AttachedAssets();
|
||||
$assets->setLibraries(['core/jquery.ui.datepicker']);
|
||||
$js_assets = $this->container->get('asset.resolver')->getJsAssets($assets, FALSE)[1];
|
||||
$this->assertTrue(array_key_exists('core/modules/locale/locale.datepicker.js', $js_assets), 'locale.datepicker.js added to scripts.');
|
||||
}
|
||||
}
|
63
core/modules/locale/src/Tests/LocaleLocaleLookupTest.php
Normal file
63
core/modules/locale/src/Tests/LocaleLocaleLookupTest.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleLocaleLookupTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests LocaleLookup.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleLocaleLookupTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale', 'locale_test');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Change the language default object to different values.
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
$this->config('system.site')->set('default_langcode', 'fr')->save();
|
||||
|
||||
$this->drupalLogin($this->rootUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that there are no circular dependencies.
|
||||
*/
|
||||
public function testCircularDependency() {
|
||||
// Ensure that we can enable early_translation_test on a non-english site.
|
||||
$this->drupalPostForm('admin/modules', array('modules[Testing][early_translation_test][enable]' => TRUE), t('Save configuration'));
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test language fallback defaults.
|
||||
*/
|
||||
public function testLanguageFallbackDefaults() {
|
||||
$this->drupalGet('');
|
||||
// Ensure state of fallback languages persisted by
|
||||
// locale_test_language_fallback_candidates_locale_lookup_alter() is empty.
|
||||
$this->assertEqual(\Drupal::state()->get('locale.test_language_fallback_candidates_locale_lookup_alter_candidates'), array());
|
||||
// Make sure there is enough information provided for alter hooks.
|
||||
$context = \Drupal::state()->get('locale.test_language_fallback_candidates_locale_lookup_alter_context');
|
||||
$this->assertEqual($context['langcode'], 'fr');
|
||||
$this->assertEqual($context['operation'], 'locale_lookup');
|
||||
}
|
||||
|
||||
}
|
155
core/modules/locale/src/Tests/LocalePathTest.php
Normal file
155
core/modules/locale/src/Tests/LocalePathTest.php
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocalePathTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests you can configure a language for individual URL aliases.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocalePathTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'locale', 'path', 'views');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
|
||||
$this->config('system.site')->set('page.front', '/node')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a language can be associated with a path alias.
|
||||
*/
|
||||
public function testPathLanguageConfiguration() {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'create page content', 'administer url aliases', 'create url aliases', 'access administration pages', 'access content overview'));
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
// Code for the language.
|
||||
$langcode = 'xx';
|
||||
// The English name for the language.
|
||||
$name = $this->randomMachineName(16);
|
||||
// The domain prefix.
|
||||
$prefix = $langcode;
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
|
||||
// Set path prefix.
|
||||
$edit = array("prefix[$langcode]" => $prefix);
|
||||
$this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
|
||||
|
||||
// Check that the "xx" front page is readily available because path prefix
|
||||
// negotiation is pre-configured.
|
||||
$this->drupalGet($prefix);
|
||||
$this->assertText(t('Welcome to Drupal'), 'The "xx" front page is readibly available.');
|
||||
|
||||
// Create a node.
|
||||
$node = $this->drupalCreateNode(array('type' => 'page'));
|
||||
|
||||
// Create a path alias in default language (English).
|
||||
$path = 'admin/config/search/path/add';
|
||||
$english_path = $this->randomMachineName(8);
|
||||
$edit = array(
|
||||
'source' => '/node/' . $node->id(),
|
||||
'alias' => '/' . $english_path,
|
||||
'langcode' => 'en',
|
||||
);
|
||||
$this->drupalPostForm($path, $edit, t('Save'));
|
||||
|
||||
// Create a path alias in new custom language.
|
||||
$custom_language_path = $this->randomMachineName(8);
|
||||
$edit = array(
|
||||
'source' => '/node/' . $node->id(),
|
||||
'alias' => '/' . $custom_language_path,
|
||||
'langcode' => $langcode,
|
||||
);
|
||||
$this->drupalPostForm($path, $edit, t('Save'));
|
||||
|
||||
// Confirm English language path alias works.
|
||||
$this->drupalGet($english_path);
|
||||
$this->assertText($node->label(), 'English alias works.');
|
||||
|
||||
// Confirm custom language path alias works.
|
||||
$this->drupalGet($prefix . '/' . $custom_language_path);
|
||||
$this->assertText($node->label(), 'Custom language alias works.');
|
||||
|
||||
// Create a custom path.
|
||||
$custom_path = $this->randomMachineName(8);
|
||||
|
||||
// Check priority of language for alias by source path.
|
||||
$edit = array(
|
||||
'source' => '/node/' . $node->id(),
|
||||
'alias' => '/' . $custom_path,
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
);
|
||||
$this->container->get('path.alias_storage')->save($edit['source'], $edit['alias'], $edit['langcode']);
|
||||
$lookup_path = $this->container->get('path.alias_manager')->getAliasByPath('/node/' . $node->id(), 'en');
|
||||
$this->assertEqual('/' . $english_path, $lookup_path, 'English language alias has priority.');
|
||||
// Same check for language 'xx'.
|
||||
$lookup_path = $this->container->get('path.alias_manager')->getAliasByPath('/node/' . $node->id(), $prefix);
|
||||
$this->assertEqual('/' . $custom_language_path, $lookup_path, 'Custom language alias has priority.');
|
||||
$this->container->get('path.alias_storage')->delete($edit);
|
||||
|
||||
// Create language nodes to check priority of aliases.
|
||||
$first_node = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1, 'langcode' => 'en'));
|
||||
$second_node = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1, 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED));
|
||||
|
||||
// Assign a custom path alias to the first node with the English language.
|
||||
$edit = array(
|
||||
'source' => '/node/' . $first_node->id(),
|
||||
'alias' => '/' . $custom_path,
|
||||
'langcode' => $first_node->language()->getId(),
|
||||
);
|
||||
$this->container->get('path.alias_storage')->save($edit['source'], $edit['alias'], $edit['langcode']);
|
||||
|
||||
// Assign a custom path alias to second node with
|
||||
// LanguageInterface::LANGCODE_NOT_SPECIFIED.
|
||||
$edit = array(
|
||||
'source' => '/node/' . $second_node->id(),
|
||||
'alias' => '/' . $custom_path,
|
||||
'langcode' => $second_node->language()->getId(),
|
||||
);
|
||||
$this->container->get('path.alias_storage')->save($edit['source'], $edit['alias'], $edit['langcode']);
|
||||
|
||||
// Test that both node titles link to our path alias.
|
||||
$this->drupalGet('admin/content');
|
||||
$custom_path_url = Url::fromUserInput('/' . $custom_path)->toString();
|
||||
$elements = $this->xpath('//a[@href=:href and normalize-space(text())=:title]', array(':href' => $custom_path_url, ':title' => $first_node->label()));
|
||||
$this->assertTrue(!empty($elements), 'First node links to the path alias.');
|
||||
$elements = $this->xpath('//a[@href=:href and normalize-space(text())=:title]', array(':href' => $custom_path_url, ':title' => $second_node->label()));
|
||||
$this->assertTrue(!empty($elements), 'Second node links to the path alias.');
|
||||
|
||||
// Confirm that the custom path leads to the first node.
|
||||
$this->drupalGet($custom_path);
|
||||
$this->assertText($first_node->label(), 'Custom alias returns first node.');
|
||||
|
||||
// Confirm that the custom path with prefix leads to the second node.
|
||||
$this->drupalGet($prefix . '/' . $custom_path);
|
||||
$this->assertText($second_node->label(), 'Custom alias with prefix returns second node.');
|
||||
|
||||
}
|
||||
}
|
370
core/modules/locale/src/Tests/LocalePluralFormatTest.php
Normal file
370
core/modules/locale/src/Tests/LocalePluralFormatTest.php
Normal file
|
@ -0,0 +1,370 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocalePluralFormatTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests plural handling for various languages.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocalePluralFormatTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale_get_plural() and \Drupal::translation()->formatPlural()
|
||||
* functionality.
|
||||
*/
|
||||
public function testGetPluralFormat() {
|
||||
// Import some .po files with formulas to set up the environment.
|
||||
// These will also add the languages to the system.
|
||||
$this->importPoFile($this->getPoFileWithSimplePlural(), array(
|
||||
'langcode' => 'fr',
|
||||
));
|
||||
$this->importPoFile($this->getPoFileWithComplexPlural(), array(
|
||||
'langcode' => 'hr',
|
||||
));
|
||||
|
||||
// Attempt to import some broken .po files as well to prove that these
|
||||
// will not overwrite the proper plural formula imported above.
|
||||
$this->importPoFile($this->getPoFileWithMissingPlural(), array(
|
||||
'langcode' => 'fr',
|
||||
'overwrite_options[not_customized]' => TRUE,
|
||||
));
|
||||
$this->importPoFile($this->getPoFileWithBrokenPlural(), array(
|
||||
'langcode' => 'hr',
|
||||
'overwrite_options[not_customized]' => TRUE,
|
||||
));
|
||||
|
||||
// Reset static caches from locale_get_plural() to ensure we get fresh data.
|
||||
drupal_static_reset('locale_get_plural');
|
||||
drupal_static_reset('locale_get_plural:plurals');
|
||||
drupal_static_reset('locale');
|
||||
|
||||
// Expected plural translation strings for each plural index.
|
||||
$plural_strings = array(
|
||||
// English is not imported in this case, so we assume built-in text
|
||||
// and formulas.
|
||||
'en' => array(
|
||||
0 => '1 hour',
|
||||
1 => '@count hours',
|
||||
),
|
||||
'fr' => array(
|
||||
0 => '@count heure',
|
||||
1 => '@count heures',
|
||||
),
|
||||
'hr' => array(
|
||||
0 => '@count sat',
|
||||
1 => '@count sata',
|
||||
2 => '@count sati',
|
||||
),
|
||||
// Hungarian is not imported, so it should assume the same text as
|
||||
// English, but it will always pick the plural form as per the built-in
|
||||
// logic, so only index -1 is relevant with the plural value.
|
||||
'hu' => array(
|
||||
0 => '1 hour',
|
||||
-1 => '@count hours',
|
||||
),
|
||||
);
|
||||
|
||||
// Expected plural indexes precomputed base on the plural formulas with
|
||||
// given $count value.
|
||||
$plural_tests = array(
|
||||
'en' => array(
|
||||
1 => 0,
|
||||
0 => 1,
|
||||
5 => 1,
|
||||
123 => 1,
|
||||
235 => 1,
|
||||
),
|
||||
'fr' => array(
|
||||
1 => 0,
|
||||
0 => 0,
|
||||
5 => 1,
|
||||
123 => 1,
|
||||
235 => 1,
|
||||
),
|
||||
'hr' => array(
|
||||
1 => 0,
|
||||
21 => 0,
|
||||
0 => 2,
|
||||
2 => 1,
|
||||
8 => 2,
|
||||
123 => 1,
|
||||
235 => 2,
|
||||
),
|
||||
'hu' => array(
|
||||
1 => -1,
|
||||
21 => -1,
|
||||
0 => -1,
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($plural_tests as $langcode => $tests) {
|
||||
foreach ($tests as $count => $expected_plural_index) {
|
||||
// Assert that the we get the right plural index.
|
||||
$this->assertIdentical(locale_get_plural($count, $langcode), $expected_plural_index, 'Computed plural index for ' . $langcode . ' for count ' . $count . ' is ' . $expected_plural_index);
|
||||
// Assert that the we get the right translation for that. Change the
|
||||
// expected index as per the logic for translation lookups.
|
||||
$expected_plural_index = ($count == 1) ? 0 : $expected_plural_index;
|
||||
$expected_plural_string = str_replace('@count', $count, $plural_strings[$langcode][$expected_plural_index]);
|
||||
$this->assertIdentical(\Drupal::translation()->formatPlural($count, '1 hour', '@count hours', array(), array('langcode' => $langcode)), $expected_plural_string, 'Plural translation of 1 hours / @count hours for count ' . $count . ' in ' . $langcode . ' is ' . $expected_plural_string);
|
||||
// DO NOT use translation to pass into formatPluralTranslated() this
|
||||
// way. It is designed to be used with *already* translated text like
|
||||
// settings from configuration. We use PHP translation here just because
|
||||
// we have the expected result data in that format.
|
||||
$this->assertIdentical(\Drupal::translation()->formatPluralTranslated($count, \Drupal::translation()->translate('1 hour' . LOCALE_PLURAL_DELIMITER . '@count hours', array(), array('langcode' => $langcode)), array(), array('langcode' => $langcode)), $expected_plural_string, 'Translated plural lookup of 1 hours / @count hours for count ' . $count . ' in ' . $langcode . ' is ' . $expected_plural_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests plural editing and export functionality.
|
||||
*/
|
||||
public function testPluralEditExport() {
|
||||
// Import some .po files with formulas to set up the environment.
|
||||
// These will also add the languages to the system.
|
||||
$this->importPoFile($this->getPoFileWithSimplePlural(), array(
|
||||
'langcode' => 'fr',
|
||||
));
|
||||
$this->importPoFile($this->getPoFileWithComplexPlural(), array(
|
||||
'langcode' => 'hr',
|
||||
));
|
||||
|
||||
// Get the French translations.
|
||||
$this->drupalPostForm('admin/config/regional/translate/export', array(
|
||||
'langcode' => 'fr',
|
||||
), t('Export'));
|
||||
// Ensure we have a translation file.
|
||||
$this->assertRaw('# French translation of Drupal', 'Exported French translation file.');
|
||||
// Ensure our imported translations exist in the file.
|
||||
$this->assertRaw("msgid \"Monday\"\nmsgstr \"lundi\"", 'French translations present in exported file.');
|
||||
// Check for plural export specifically.
|
||||
$this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count heure\"\nmsgstr[1] \"@count heures\"", 'Plural translations exported properly.');
|
||||
|
||||
// Get the Croatian translations.
|
||||
$this->drupalPostForm('admin/config/regional/translate/export', array(
|
||||
'langcode' => 'hr',
|
||||
), t('Export'));
|
||||
// Ensure we have a translation file.
|
||||
$this->assertRaw('# Croatian translation of Drupal', 'Exported Croatian translation file.');
|
||||
// Ensure our imported translations exist in the file.
|
||||
$this->assertRaw("msgid \"Monday\"\nmsgstr \"Ponedjeljak\"", 'Croatian translations present in exported file.');
|
||||
// Check for plural export specifically.
|
||||
$this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata\"\nmsgstr[2] \"@count sati\"", 'Plural translations exported properly.');
|
||||
|
||||
// Check if the source appears on the translation page.
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->assertText("1 hour");
|
||||
$this->assertText("@count hours");
|
||||
|
||||
// Look up editing page for this plural string and check fields.
|
||||
$path = 'admin/config/regional/translate/';
|
||||
$search = array(
|
||||
'langcode' => 'hr',
|
||||
);
|
||||
$this->drupalPostForm($path, $search, t('Filter'));
|
||||
// Labels for plural editing elements.
|
||||
$this->assertText('Singular form');
|
||||
$this->assertText('First plural form');
|
||||
$this->assertText('2. plural form');
|
||||
$this->assertNoText('3. plural form');
|
||||
|
||||
// Plural values for langcode hr.
|
||||
$this->assertText('@count sat');
|
||||
$this->assertText('@count sata');
|
||||
$this->assertText('@count sati');
|
||||
|
||||
// Edit langcode hr translations and see if that took effect.
|
||||
$lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", array(':source' => "1 hour" . LOCALE_PLURAL_DELIMITER . "@count hours"))->fetchField();
|
||||
$edit = array(
|
||||
"strings[$lid][translations][1]" => '@count sata edited',
|
||||
);
|
||||
$this->drupalPostForm($path, $edit, t('Save translations'));
|
||||
|
||||
$search = array(
|
||||
'langcode' => 'fr',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
// Plural values for the langcode fr.
|
||||
$this->assertText('@count heure');
|
||||
$this->assertText('@count heures');
|
||||
$this->assertNoText('2. plural form');
|
||||
|
||||
// Edit langcode fr translations and see if that took effect.
|
||||
$edit = array(
|
||||
"strings[$lid][translations][0]" => '@count heure edited',
|
||||
);
|
||||
$this->drupalPostForm($path, $edit, t('Save translations'));
|
||||
|
||||
// Inject a plural source string to the database. We need to use a specific
|
||||
// langcode here because the language will be English by default and will
|
||||
// not save our source string for performance optimization if we do not ask
|
||||
// specifically for a language.
|
||||
\Drupal::translation()->formatPlural(1, '1 day', '@count days', array(), array('langcode' => 'fr'));
|
||||
$lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", array(':source' => "1 day" . LOCALE_PLURAL_DELIMITER . "@count days"))->fetchField();
|
||||
// Look up editing page for this plural string and check fields.
|
||||
$search = array(
|
||||
'string' => '1 day',
|
||||
'langcode' => 'fr',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
|
||||
// Save complete translations for the string in langcode fr.
|
||||
$edit = array(
|
||||
"strings[$lid][translations][0]" => '1 jour',
|
||||
"strings[$lid][translations][1]" => '@count jours',
|
||||
);
|
||||
$this->drupalPostForm($path, $edit, t('Save translations'));
|
||||
|
||||
// Save complete translations for the string in langcode hr.
|
||||
$search = array(
|
||||
'string' => '1 day',
|
||||
'langcode' => 'hr',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
|
||||
$edit = array(
|
||||
"strings[$lid][translations][0]" => '@count dan',
|
||||
"strings[$lid][translations][1]" => '@count dana',
|
||||
"strings[$lid][translations][2]" => '@count dana',
|
||||
);
|
||||
$this->drupalPostForm($path, $edit, t('Save translations'));
|
||||
|
||||
// Get the French translations.
|
||||
$this->drupalPostForm('admin/config/regional/translate/export', array(
|
||||
'langcode' => 'fr',
|
||||
), t('Export'));
|
||||
// Check for plural export specifically.
|
||||
$this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count heure edited\"\nmsgstr[1] \"@count heures\"", 'Edited French plural translations for hours exported properly.');
|
||||
$this->assertRaw("msgid \"1 day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"1 jour\"\nmsgstr[1] \"@count jours\"", 'Added French plural translations for days exported properly.');
|
||||
|
||||
// Get the Croatian translations.
|
||||
$this->drupalPostForm('admin/config/regional/translate/export', array(
|
||||
'langcode' => 'hr',
|
||||
), t('Export'));
|
||||
// Check for plural export specifically.
|
||||
$this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata edited\"\nmsgstr[2] \"@count sati\"", 'Edited Croatian plural translations exported properly.');
|
||||
$this->assertRaw("msgid \"1 day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"@count dan\"\nmsgstr[1] \"@count dana\"\nmsgstr[2] \"@count dana\"", 'Added Croatian plural translations exported properly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a standalone .po file in a given language.
|
||||
*
|
||||
* @param string $contents
|
||||
* Contents of the .po file to import.
|
||||
* @param array $options
|
||||
* Additional options to pass to the translation import form.
|
||||
*/
|
||||
public function importPoFile($contents, array $options = array()) {
|
||||
$name = tempnam('temporary://', "po_") . '.po';
|
||||
file_put_contents($name, $contents);
|
||||
$options['files[file]'] = $name;
|
||||
$this->drupalPostForm('admin/config/regional/translate/import', $options, t('Import'));
|
||||
drupal_unlink($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file with a simple plural formula.
|
||||
*/
|
||||
public function getPoFileWithSimplePlural() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "1 hour"
|
||||
msgid_plural "@count hours"
|
||||
msgstr[0] "@count heure"
|
||||
msgstr[1] "@count heures"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "lundi"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file with a complex plural formula.
|
||||
*/
|
||||
public function getPoFileWithComplexPlural() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
|
||||
|
||||
msgid "1 hour"
|
||||
msgid_plural "@count hours"
|
||||
msgstr[0] "@count sat"
|
||||
msgstr[1] "@count sata"
|
||||
msgstr[2] "@count sati"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "Ponedjeljak"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file with a missing plural formula.
|
||||
*/
|
||||
public function getPoFileWithMissingPlural() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "lundi"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file with a broken plural formula.
|
||||
*/
|
||||
public function getPoFileWithBrokenPlural() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: broken, will not parse\\n"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "Ponedjeljak"
|
||||
EOF;
|
||||
}
|
||||
}
|
209
core/modules/locale/src/Tests/LocaleStringTest.php
Normal file
209
core/modules/locale/src/Tests/LocaleStringTest.php
Normal file
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleStringTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests the locale string storage, string objects and data API.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleStringTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale');
|
||||
|
||||
/**
|
||||
* The locale storage.
|
||||
*
|
||||
* @var \Drupal\locale\StringStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Add a default locale storage for all these tests.
|
||||
$this->storage = $this->container->get('locale.storage');
|
||||
// Create two languages: Spanish and German.
|
||||
foreach (array('es', 'de') as $langcode) {
|
||||
ConfigurableLanguage::createFromLangcode($langcode)->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CRUD API.
|
||||
*/
|
||||
public function testStringCRUDAPI() {
|
||||
// Create source string.
|
||||
$source = $this->buildSourceString();
|
||||
$source->save();
|
||||
$this->assertTrue($source->lid, format_string('Successfully created string %string', array('%string' => $source->source)));
|
||||
|
||||
// Load strings by lid and source.
|
||||
$string1 = $this->storage->findString(array('lid' => $source->lid));
|
||||
$this->assertEqual($source, $string1, 'Successfully retrieved string by identifier.');
|
||||
$string2 = $this->storage->findString(array('source' => $source->source, 'context' => $source->context));
|
||||
$this->assertEqual($source, $string2, 'Successfully retrieved string by source and context.');
|
||||
$string3 = $this->storage->findString(array('source' => $source->source, 'context' => ''));
|
||||
$this->assertFalse($string3, 'Cannot retrieve string with wrong context.');
|
||||
|
||||
// Check version handling and updating.
|
||||
$this->assertEqual($source->version, 'none', 'String originally created without version.');
|
||||
$string = $this->storage->findTranslation(array('lid' => $source->lid));
|
||||
$this->assertEqual($string->version, \Drupal::VERSION, 'Checked and updated string version to Drupal version.');
|
||||
|
||||
// Create translation and find it by lid and source.
|
||||
$langcode = 'es';
|
||||
$translation = $this->createTranslation($source, $langcode);
|
||||
$this->assertEqual($translation->customized, LOCALE_NOT_CUSTOMIZED, 'Translation created as not customized by default.');
|
||||
$string1 = $this->storage->findTranslation(array('language' => $langcode, 'lid' => $source->lid));
|
||||
$this->assertEqual($string1->translation, $translation->translation, 'Successfully loaded translation by string identifier.');
|
||||
$string2 = $this->storage->findTranslation(array('language' => $langcode, 'source' => $source->source, 'context' => $source->context));
|
||||
$this->assertEqual($string2->translation, $translation->translation, 'Successfully loaded translation by source and context.');
|
||||
$translation
|
||||
->setCustomized()
|
||||
->save();
|
||||
$translation = $this->storage->findTranslation(array('language' => $langcode, 'lid' => $source->lid));
|
||||
$this->assertEqual($translation->customized, LOCALE_CUSTOMIZED, 'Translation successfully marked as customized.');
|
||||
|
||||
// Delete translation.
|
||||
$translation->delete();
|
||||
$deleted = $this->storage->findTranslation(array('language' => $langcode, 'lid' => $source->lid));
|
||||
$this->assertFalse(isset($deleted->translation), 'Successfully deleted translation string.');
|
||||
|
||||
// Create some translations and then delete string and all of its
|
||||
// translations.
|
||||
$lid = $source->lid;
|
||||
$this->createAllTranslations($source);
|
||||
$search = $this->storage->getTranslations(array('lid' => $source->lid));
|
||||
$this->assertEqual(count($search), 3, 'Created and retrieved all translations for our source string.');
|
||||
|
||||
$source->delete();
|
||||
$string = $this->storage->findString(array('lid' => $lid));
|
||||
$this->assertFalse($string, 'Successfully deleted source string.');
|
||||
$deleted = $search = $this->storage->getTranslations(array('lid' => $lid));
|
||||
$this->assertFalse($deleted, 'Successfully deleted all translation strings.');
|
||||
|
||||
// Tests that locations of different types and arbitrary lengths can be
|
||||
// added to a source string. Too long locations will be cut off.
|
||||
$source_string = $this->buildSourceString();
|
||||
$source_string->addLocation('javascript', $this->randomString(8));
|
||||
$source_string->addLocation('configuration', $this->randomString(50));
|
||||
$source_string->addLocation('code', $this->randomString(100));
|
||||
$source_string->addLocation('path', $location = $this->randomString(300));
|
||||
$source_string->save();
|
||||
|
||||
$rows = db_query('SELECT * FROM {locales_location} WHERE sid = :sid', array(':sid' => $source_string->lid))->fetchAllAssoc('type');
|
||||
$this->assertEqual(count($rows), 4, '4 source locations have been persisted.');
|
||||
$this->assertEqual($rows['path']->name, substr($location, 0, 255), 'Too long location has been limited to 255 characters.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Search API loading multiple objects.
|
||||
*/
|
||||
public function testStringSearchAPI() {
|
||||
$language_count = 3;
|
||||
// Strings 1 and 2 will have some common prefix.
|
||||
// Source 1 will have all translations, not customized.
|
||||
// Source 2 will have all translations, customized.
|
||||
// Source 3 will have no translations.
|
||||
$prefix = $this->randomMachineName(100);
|
||||
$source1 = $this->buildSourceString(array('source' => $prefix . $this->randomMachineName(100)))->save();
|
||||
$source2 = $this->buildSourceString(array('source' => $prefix . $this->randomMachineName(100)))->save();
|
||||
$source3 = $this->buildSourceString()->save();
|
||||
// Load all source strings.
|
||||
$strings = $this->storage->getStrings(array());
|
||||
$this->assertEqual(count($strings), 3, 'Found 3 source strings in the database.');
|
||||
// Load all source strings matching a given string.
|
||||
$filter_options['filters'] = array('source' => $prefix);
|
||||
$strings = $this->storage->getStrings(array(), $filter_options);
|
||||
$this->assertEqual(count($strings), 2, 'Found 2 strings using some string filter.');
|
||||
|
||||
// Not customized translations.
|
||||
$translate1 = $this->createAllTranslations($source1);
|
||||
// Customized translations.
|
||||
$this->createAllTranslations($source2, array('customized' => LOCALE_CUSTOMIZED));
|
||||
// Try quick search function with different field combinations.
|
||||
$langcode = 'es';
|
||||
$found = $this->storage->findTranslation(array('language' => $langcode, 'source' => $source1->source, 'context' => $source1->context));
|
||||
$this->assertTrue($found && isset($found->language) && isset($found->translation) && !$found->isNew(), 'Translation found searching by source and context.');
|
||||
$this->assertEqual($found->translation, $translate1[$langcode]->translation, 'Found the right translation.');
|
||||
// Now try a translation not found.
|
||||
$found = $this->storage->findTranslation(array('language' => $langcode, 'source' => $source3->source, 'context' => $source3->context));
|
||||
$this->assertTrue($found && $found->lid == $source3->lid && !isset($found->translation) && $found->isNew(), 'Translation not found but source string found.');
|
||||
|
||||
// Load all translations. For next queries we'll be loading only translated
|
||||
// strings.
|
||||
$translations = $this->storage->getTranslations(array('translated' => TRUE));
|
||||
$this->assertEqual(count($translations), 2 * $language_count, 'Created and retrieved all translations for source strings.');
|
||||
|
||||
// Load all customized translations.
|
||||
$translations = $this->storage->getTranslations(array('customized' => LOCALE_CUSTOMIZED, 'translated' => TRUE));
|
||||
$this->assertEqual(count($translations), $language_count, 'Retrieved all customized translations for source strings.');
|
||||
|
||||
// Load all Spanish customized translations.
|
||||
$translations = $this->storage->getTranslations(array('language' => 'es', 'customized' => LOCALE_CUSTOMIZED, 'translated' => TRUE));
|
||||
$this->assertEqual(count($translations), 1, 'Found only Spanish and customized translations.');
|
||||
|
||||
// Load all source strings without translation (1).
|
||||
$translations = $this->storage->getStrings(array('translated' => FALSE));
|
||||
$this->assertEqual(count($translations), 1, 'Found 1 source string without translations.');
|
||||
|
||||
// Load Spanish translations using string filter.
|
||||
$filter_options['filters'] = array('source' => $prefix);
|
||||
$translations = $this->storage->getTranslations(array('language' => 'es'), $filter_options);
|
||||
$this->assertEqual(count($translations), 2, 'Found 2 translations using some string filter.');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates random source string object.
|
||||
*
|
||||
* @return \Drupal\locale\StringInterface
|
||||
* A locale string.
|
||||
*/
|
||||
public function buildSourceString($values = array()) {
|
||||
return $this->storage->createString($values += array(
|
||||
'source' => $this->randomMachineName(100),
|
||||
'context' => $this->randomMachineName(20),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates translations for source string and all languages.
|
||||
*/
|
||||
public function createAllTranslations($source, $values = array()) {
|
||||
$list = array();
|
||||
/* @var $language_manager \Drupal\Core\Language\LanguageManagerInterface */
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
foreach ($language_manager->getLanguages() as $language) {
|
||||
$list[$language->getId()] = $this->createTranslation($source, $language->getId(), $values);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates single translation for source string.
|
||||
*/
|
||||
public function createTranslation($source, $langcode, $values = array()) {
|
||||
return $this->storage->createTranslation($values + array(
|
||||
'lid' => $source->lid,
|
||||
'language' => $langcode,
|
||||
'translation' => $this->randomMachineName(100),
|
||||
))->save();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleTranslateStringTourTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\tour\Tests\TourTestBase;
|
||||
|
||||
/**
|
||||
* Tests the Translate Interface tour.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslateStringTourTest extends TourTestBase {
|
||||
|
||||
/**
|
||||
* An admin user with administrative permissions to translate.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale', 'tour');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->adminUser = $this->drupalCreateUser(array('translate interface', 'access tour', 'administer languages'));
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale tour tip availability.
|
||||
*/
|
||||
public function testTranslateStringTourTips() {
|
||||
// Add another language so there are no missing form items.
|
||||
$edit = array();
|
||||
$edit['predefined_langcode'] = 'es';
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->assertTourTips();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleTranslatedSchemaDefinitionTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Adds and configures languages to check field schema definition.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslatedSchemaDefinitionTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('language', 'locale', 'node');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
$this->config('system.site')->set('default_langcode', 'fr')->save();
|
||||
// Make sure new entity type definitions are processed.
|
||||
\Drupal::service('entity.definition_update_manager')->applyUpdates();
|
||||
// Clear all caches so that the base field definition, its cache in the
|
||||
// entity manager, the t() cache, etc. are all cleared.
|
||||
drupal_flush_all_caches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that translated field descriptions do not affect the update system.
|
||||
*/
|
||||
function testTranslatedSchemaDefinition() {
|
||||
/** @var \Drupal\locale\StringDatabaseStorage $stringStorage */
|
||||
$stringStorage = \Drupal::service('locale.storage');
|
||||
|
||||
$source = $stringStorage->createString(array(
|
||||
'source' => 'The node ID.',
|
||||
))->save();
|
||||
|
||||
$stringStorage->createTranslation(array(
|
||||
'lid' => $source->lid,
|
||||
'language' => 'fr',
|
||||
'translation' => 'Translated node ID',
|
||||
))->save();
|
||||
|
||||
// Ensure that the field is translated when access through the API.
|
||||
$this->assertEqual('Translated node ID', \Drupal::entityManager()->getBaseFieldDefinitions('node')['nid']->getDescription());
|
||||
|
||||
// Assert there are no updates.
|
||||
$this->assertFalse(\Drupal::service('entity.definition_update_manager')->needsUpdates());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that translations do not affect the update system.
|
||||
*/
|
||||
function testTranslatedUpdate() {
|
||||
// Visit the update page to collect any strings that may be translatable.
|
||||
$user = $this->drupalCreateUser(array('administer software updates'));
|
||||
$this->drupalLogin($user);
|
||||
$update_url = $GLOBALS['base_url'] . '/update.php';
|
||||
$this->drupalGet($update_url, array('external' => TRUE));
|
||||
|
||||
/** @var \Drupal\locale\StringDatabaseStorage $stringStorage */
|
||||
$stringStorage = \Drupal::service('locale.storage');
|
||||
$sources = $stringStorage->getStrings();
|
||||
|
||||
// Translate all source strings found.
|
||||
foreach ($sources as $source) {
|
||||
$stringStorage->createTranslation(array(
|
||||
'lid' => $source->lid,
|
||||
'language' => 'fr',
|
||||
'translation' => $this->randomMachineName(100),
|
||||
))->save();
|
||||
}
|
||||
|
||||
// Ensure that there are no updates just due to translations. Check for
|
||||
// markup and a link instead of specific text because text may be
|
||||
// translated.
|
||||
$this->drupalGet($update_url . '/selection', array('external' => TRUE));
|
||||
$this->assertRaw('messages--status', 'No pending updates.');
|
||||
$this->assertNoLinkByHref('fr/update.php/run', 'No link to run updates.');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleTranslationProjectsTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests locale translation project handling.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslationProjectsTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['locale'];
|
||||
|
||||
/**
|
||||
* The module handler used in this test.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The locale project storage used in this test.
|
||||
*
|
||||
* @var \Drupal\locale\LocaleProjectStorageInterface
|
||||
*/
|
||||
protected $projectStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->moduleHandler = $this->container->get('module_handler');
|
||||
$this->projectStorage = $this->container->get('locale.project');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests locale_translation_clear_cache_projects().
|
||||
*/
|
||||
public function testLocaleTranslationClearCacheProjects() {
|
||||
$this->moduleHandler->loadInclude('locale', 'inc', 'locale.translation');
|
||||
|
||||
$expected = [];
|
||||
$this->assertIdentical($expected, locale_translation_get_projects());
|
||||
|
||||
$this->projectStorage->set('foo', []);
|
||||
$expected['foo'] = new \stdClass();
|
||||
$this->assertEqual($expected, locale_translation_get_projects());
|
||||
|
||||
$this->projectStorage->set('bar', []);
|
||||
locale_translation_clear_cache_projects();
|
||||
$expected['bar'] = new \stdClass();
|
||||
$this->assertEqual($expected, locale_translation_get_projects());
|
||||
}
|
||||
|
||||
}
|
546
core/modules/locale/src/Tests/LocaleTranslationUiTest.php
Normal file
546
core/modules/locale/src/Tests/LocaleTranslationUiTest.php
Normal file
|
@ -0,0 +1,546 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleTranslationUiTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Adds a new locale and translates its name. Checks the validation of
|
||||
* translation strings and search results.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslationUiTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale');
|
||||
|
||||
/**
|
||||
* Enable interface translation to English.
|
||||
*/
|
||||
public function testEnglishTranslation() {
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->drupalPostForm('admin/config/regional/language/edit/en', array('locale_translate_english' => TRUE), t('Save language'));
|
||||
$this->assertLinkByHref('/admin/config/regional/translate?langcode=en', 0, 'Enabled interface translation to English.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a language and tests string translation by users with the appropriate permissions.
|
||||
*/
|
||||
public function testStringTranslation() {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
|
||||
// User to translate and delete string.
|
||||
$translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
|
||||
// Code for the language.
|
||||
$langcode = 'xx';
|
||||
// The English name for the language. This will be translated.
|
||||
$name = $this->randomMachineName(16);
|
||||
// This will be the translation of $name.
|
||||
$translation = $this->randomMachineName(16);
|
||||
$translation_to_en = $this->randomMachineName(16);
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
// Add string.
|
||||
t($name, array(), array('langcode' => $langcode));
|
||||
// Reset locale cache.
|
||||
$this->container->get('string_translation')->reset();
|
||||
$this->assertRaw('"edit-languages-' . $langcode . '-weight"', 'Language code found.');
|
||||
$this->assertText(t($name), 'Test language added.');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Search for the name and translate it.
|
||||
$this->drupalLogin($translate_user);
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText($name, 'Search found the string as untranslated.');
|
||||
|
||||
// No t() here, it's surely not translated yet.
|
||||
$this->assertText($name, 'name found on edit screen.');
|
||||
$this->assertNoOption('edit-langcode', 'en', 'No way to translate the string to English.');
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalPostForm('admin/config/regional/language/edit/en', array('locale_translate_english' => TRUE), t('Save language'));
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($translate_user);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText($name, 'Search found the string as untranslated.');
|
||||
|
||||
// Assume this is the only result, given the random name.
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => $translation,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
$this->assertText(t('The strings have been saved.'), 'The strings have been saved.');
|
||||
$url_bits = explode('?', $this->getUrl());
|
||||
$this->assertEqual($url_bits[0], \Drupal::url('locale.translate_page', array(), array('absolute' => TRUE)), 'Correct page redirection.');
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertRaw($translation, 'Non-English translation properly saved.');
|
||||
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => 'en',
|
||||
'translation' => 'untranslated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => $translation_to_en,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => 'en',
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertRaw($translation_to_en, 'English translation properly saved.');
|
||||
|
||||
$this->assertTrue($name != $translation && t($name, array(), array('langcode' => $langcode)) == $translation, 't() works for non-English.');
|
||||
// Refresh the locale() cache to get fresh data from t() below. We are in
|
||||
// the same HTTP request and therefore t() is not refreshed by saving the
|
||||
// translation above.
|
||||
$this->container->get('string_translation')->reset();
|
||||
// Now we should get the proper fresh translation from t().
|
||||
$this->assertTrue($name != $translation_to_en && t($name, array(), array('langcode' => 'en')) == $translation_to_en, 't() works for English.');
|
||||
$this->assertTrue(t($name, array(), array('langcode' => LanguageInterface::LANGCODE_SYSTEM)) == $name, 't() works for LanguageInterface::LANGCODE_SYSTEM.');
|
||||
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => 'en',
|
||||
'translation' => 'untranslated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText(t('No strings available.'), 'String is translated.');
|
||||
|
||||
// Test invalidation of 'rendered' cache tag after string translation.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('xx/user/login');
|
||||
$this->assertText('Enter the password that accompanies your username.');
|
||||
|
||||
$this->drupalLogin($translate_user);
|
||||
$search = array(
|
||||
'string' => 'accompanies your username',
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => 'Please enter your Llama username.',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('xx/user/login');
|
||||
$this->assertText('Please enter your Llama username.');
|
||||
|
||||
// Delete the language.
|
||||
$this->drupalLogin($admin_user);
|
||||
$path = 'admin/config/regional/language/delete/' . $langcode;
|
||||
// This a confirm form, we do not need any fields changed.
|
||||
$this->drupalPostForm($path, array(), t('Delete'));
|
||||
// We need raw here because %language and %langcode will add HTML.
|
||||
$t_args = array('%language' => $name, '%langcode' => $langcode);
|
||||
$this->assertRaw(t('The %language (%langcode) language has been removed.', $t_args), 'The test language has been removed.');
|
||||
// Reload to remove $name.
|
||||
$this->drupalGet($path);
|
||||
// Verify that language is no longer found.
|
||||
$this->assertResponse(404, 'Language no longer found.');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Delete the string.
|
||||
$this->drupalLogin($translate_user);
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => 'en',
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
// Assume this is the only result, given the random name.
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => '',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
$this->assertRaw($name, 'The strings have been saved.');
|
||||
$this->drupalLogin($translate_user);
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => 'en',
|
||||
'translation' => 'untranslated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertNoText(t('No strings available.'), 'The translation has been removed');
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds a language and checks that the JavaScript translation files are
|
||||
* properly created and rebuilt on deletion.
|
||||
*/
|
||||
public function testJavaScriptTranslation() {
|
||||
$user = $this->drupalCreateUser(array('translate interface', 'administer languages', 'access administration pages'));
|
||||
$this->drupalLogin($user);
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
$langcode = 'xx';
|
||||
// The English name for the language. This will be translated.
|
||||
$name = $this->randomMachineName(16);
|
||||
|
||||
// Add custom language.
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
$this->container->get('language_manager')->reset();
|
||||
|
||||
// Build the JavaScript translation file.
|
||||
|
||||
// Retrieve the source string of the first string available in the
|
||||
// {locales_source} table and translate it.
|
||||
$source = db_select('locales_source', 'l')
|
||||
->fields('l', array('source'))
|
||||
->condition('l.source', '%.js%', 'LIKE')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
|
||||
$search = array(
|
||||
'string' => $source,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => $this->randomMachineName(),
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
|
||||
// Trigger JavaScript translation parsing and building.
|
||||
_locale_rebuild_js($langcode);
|
||||
|
||||
$locale_javascripts = \Drupal::state()->get('locale.translation.javascript') ?: array();
|
||||
$js_file = 'public://' . $config->get('javascript.directory') . '/' . $langcode . '_' . $locale_javascripts[$langcode] . '.js';
|
||||
$this->assertTrue($result = file_exists($js_file), SafeMarkup::format('JavaScript file created: %file', array('%file' => $result ? $js_file : 'not found')));
|
||||
|
||||
// Test JavaScript translation rebuilding.
|
||||
file_unmanaged_delete($js_file);
|
||||
$this->assertTrue($result = !file_exists($js_file), SafeMarkup::format('JavaScript file deleted: %file', array('%file' => $result ? $js_file : 'found')));
|
||||
_locale_rebuild_js($langcode);
|
||||
$this->assertTrue($result = file_exists($js_file), SafeMarkup::format('JavaScript file rebuilt: %file', array('%file' => $result ? $js_file : 'not found')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the validation of the translation input.
|
||||
*/
|
||||
public function testStringValidation() {
|
||||
// User to add language and strings.
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface'));
|
||||
$this->drupalLogin($admin_user);
|
||||
$langcode = 'xx';
|
||||
// The English name for the language. This will be translated.
|
||||
$name = $this->randomMachineName(16);
|
||||
|
||||
// These will be the invalid translations of $name.
|
||||
$key = $this->randomMachineName(16);
|
||||
$bad_translations[$key] = "<script>alert('xss');</script>" . $key;
|
||||
$key = $this->randomMachineName(16);
|
||||
$bad_translations[$key] = '<img SRC="javascript:alert(\'xss\');">' . $key;
|
||||
$key = $this->randomMachineName(16);
|
||||
$bad_translations[$key] = '<<SCRIPT>alert("xss");//<</SCRIPT>' . $key;
|
||||
$key = $this->randomMachineName(16);
|
||||
$bad_translations[$key] = "<BODY ONLOAD=alert('xss')>" . $key;
|
||||
|
||||
// Add custom language.
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
// Add string.
|
||||
t($name, array(), array('langcode' => $langcode));
|
||||
// Reset locale cache.
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
// Find the edit path.
|
||||
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
foreach ($bad_translations as $translation) {
|
||||
$edit = array(
|
||||
$lid => $translation,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
// Check for a form error on the textarea.
|
||||
$form_class = $this->xpath('//form[@id="locale-translate-edit-form"]//textarea/@class');
|
||||
$this->assertNotIdentical(FALSE, strpos($form_class[0], 'error'), 'The string was rejected as unsafe.');
|
||||
$this->assertNoText(t('The string has been saved.'), 'The string was not saved.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation search form.
|
||||
*/
|
||||
public function testStringSearch() {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
|
||||
// User to translate and delete string.
|
||||
$translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
|
||||
|
||||
// Code for the language.
|
||||
$langcode = 'xx';
|
||||
// The English name for the language. This will be translated.
|
||||
$name = $this->randomMachineName(16);
|
||||
// This will be the translation of $name.
|
||||
$translation = $this->randomMachineName(16);
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => 'yy',
|
||||
'label' => $this->randomMachineName(16),
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
|
||||
// Add string.
|
||||
t($name, array(), array('langcode' => $langcode));
|
||||
// Reset locale cache.
|
||||
$this->container->get('string_translation')->reset();
|
||||
$this->drupalLogout();
|
||||
|
||||
// Search for the name.
|
||||
$this->drupalLogin($translate_user);
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
// assertText() seems to remove the input field where $name always could be
|
||||
// found, so this is not a false assert. See how assertNoText succeeds
|
||||
// later.
|
||||
$this->assertText($name, 'Search found the string.');
|
||||
|
||||
// Ensure untranslated string doesn't appear if searching on 'only
|
||||
// translated strings'.
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText(t('No strings available.'), "Search didn't find the string.");
|
||||
|
||||
// Ensure untranslated string appears if searching on 'only untranslated
|
||||
// strings'.
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertNoText(t('No strings available.'), 'Search found the string.');
|
||||
|
||||
// Add translation.
|
||||
// Assume this is the only result, given the random name.
|
||||
// We save the lid from the path.
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => $translation,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
|
||||
// Ensure translated string does appear if searching on 'only
|
||||
// translated strings'.
|
||||
$search = array(
|
||||
'string' => $translation,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertNoText(t('No strings available.'), 'Search found the translation.');
|
||||
|
||||
// Ensure translated source string doesn't appear if searching on 'only
|
||||
// untranslated strings'.
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText(t('No strings available.'), "Search didn't find the source string.");
|
||||
|
||||
// Ensure translated string doesn't appear if searching on 'only
|
||||
// untranslated strings'.
|
||||
$search = array(
|
||||
'string' => $translation,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText(t('No strings available.'), "Search didn't find the translation.");
|
||||
|
||||
// Ensure translated string does appear if searching on the custom language.
|
||||
$search = array(
|
||||
'string' => $translation,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertNoText(t('No strings available.'), 'Search found the translation.');
|
||||
|
||||
// Ensure translated string doesn't appear if searching in System (English).
|
||||
$search = array(
|
||||
'string' => $translation,
|
||||
'langcode' => 'yy',
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText(t('No strings available.'), "Search didn't find the translation.");
|
||||
|
||||
// Search for a string that isn't in the system.
|
||||
$unavailable_string = $this->randomMachineName(16);
|
||||
$search = array(
|
||||
'string' => $unavailable_string,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText(t('No strings available.'), "Search didn't find the invalid string.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that only changed strings are saved customized when edited.
|
||||
*/
|
||||
public function testUICustomizedStrings() {
|
||||
$user = $this->drupalCreateUser(array('translate interface', 'administer languages', 'access administration pages'));
|
||||
$this->drupalLogin($user);
|
||||
ConfigurableLanguage::createFromLangcode('de')->save();
|
||||
|
||||
// Create test source string.
|
||||
$string = $this->container->get('locale.storage')->createString(array(
|
||||
'source' => $this->randomMachineName(100),
|
||||
'context' => $this->randomMachineName(20),
|
||||
))->save();
|
||||
|
||||
// Create translation for new string and save it as non-customized.
|
||||
$translation = $this->container->get('locale.storage')->createTranslation(array(
|
||||
'lid' => $string->lid,
|
||||
'language' => 'de',
|
||||
'translation' => $this->randomMachineName(100),
|
||||
'customized' => 0,
|
||||
))->save();
|
||||
|
||||
// Reset locale cache.
|
||||
$this->container->get('string_translation')->reset();
|
||||
|
||||
// Ensure non-customized translation string does appear if searching
|
||||
// non-customized translation.
|
||||
$search = array(
|
||||
'string' => $string->getString(),
|
||||
'langcode' => 'de',
|
||||
'translation' => 'translated',
|
||||
'customized' => '0',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
|
||||
$this->assertText($translation->getString(), 'Translation is found in search result.');
|
||||
|
||||
// Submit the translations without changing the translation.
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => $translation->getString(),
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
|
||||
// Ensure unchanged translation string does appear if searching
|
||||
// non-customized translation.
|
||||
$search = array(
|
||||
'string' => $string->getString(),
|
||||
'langcode' => 'de',
|
||||
'translation' => 'translated',
|
||||
'customized' => '0',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText($string->getString(), 'Translation is not marked as customized.');
|
||||
|
||||
// Submit the translations with a new translation.
|
||||
$textarea = current($this->xpath('//textarea'));
|
||||
$lid = (string) $textarea[0]['name'];
|
||||
$edit = array(
|
||||
$lid => $this->randomMachineName(100),
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||
|
||||
// Ensure changed translation string does appear if searching customized
|
||||
// translation.
|
||||
$search = array(
|
||||
'string' => $string->getString(),
|
||||
'langcode' => 'de',
|
||||
'translation' => 'translated',
|
||||
'customized' => '1',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText($string->getString(), "Translation is marked as customized.");
|
||||
}
|
||||
}
|
313
core/modules/locale/src/Tests/LocaleUpdateBase.php
Normal file
313
core/modules/locale/src/Tests/LocaleUpdateBase.php
Normal file
|
@ -0,0 +1,313 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleUpdateBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\Core\StreamWrapper\PublicStream;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Base class for testing updates to string translations.
|
||||
*/
|
||||
abstract class LocaleUpdateBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Timestamp for an old translation.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $timestampOld;
|
||||
|
||||
/**
|
||||
* Timestamp for a medium aged translation.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $timestampMedium;
|
||||
|
||||
/**
|
||||
* Timestamp for a new translation.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $timestampNew;
|
||||
|
||||
/**
|
||||
* Timestamp for current time.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $timestampNow;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('update', 'update_test', 'locale', 'locale_test');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Update module should not go out to d.o to check for updates. We override
|
||||
// the url to the default update_test xml path. But without providing
|
||||
// a mock xml file, no update data will be found.
|
||||
$this->config('update.settings')->set('fetch.url', Url::fromRoute('update_test.update_test', [], ['absolute' => TRUE])->toString())->save();
|
||||
|
||||
// Setup timestamps to identify old and new translation sources.
|
||||
$this->timestampOld = REQUEST_TIME - 300;
|
||||
$this->timestampMedium = REQUEST_TIME - 200;
|
||||
$this->timestampNew = REQUEST_TIME - 100;
|
||||
$this->timestampNow = REQUEST_TIME;
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the default translations directory.
|
||||
*
|
||||
* @param string $path
|
||||
* Path of the translations directory relative to the drupal installation
|
||||
* directory.
|
||||
*/
|
||||
protected function setTranslationsDirectory($path) {
|
||||
file_prepare_directory($path, FILE_CREATE_DIRECTORY);
|
||||
$this->config('locale.settings')->set('translation.path', $path)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a language.
|
||||
*
|
||||
* @param string $langcode
|
||||
* The language code of the language to add.
|
||||
*/
|
||||
protected function addLanguage($langcode) {
|
||||
$edit = array('predefined_langcode' => $langcode);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
$this->container->get('language_manager')->reset();
|
||||
$this->assertTrue(\Drupal::languageManager()->getLanguage($langcode), SafeMarkup::format('Language %langcode added.', array('%langcode' => $langcode)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a translation file and tests its timestamp.
|
||||
*
|
||||
* @param string $path
|
||||
* Path of the file relative to the public file path.
|
||||
* @param string $filename
|
||||
* Name of the file to create.
|
||||
* @param int $timestamp
|
||||
* (optional) Timestamp to set the file to. Defaults to current time.
|
||||
* @param array $translations
|
||||
* (optional) Array of source/target value translation strings. Only
|
||||
* singular strings are supported, no plurals. No double quotes are allowed
|
||||
* in source and translations strings.
|
||||
*/
|
||||
protected function makePoFile($path, $filename, $timestamp = NULL, array $translations = array()) {
|
||||
$timestamp = $timestamp ? $timestamp : REQUEST_TIME;
|
||||
$path = 'public://' . $path;
|
||||
$text = '';
|
||||
$po_header = <<<EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
EOF;
|
||||
|
||||
// Convert array of translations to Gettext source and translation strings.
|
||||
if ($translations) {
|
||||
foreach ($translations as $source => $target) {
|
||||
$text .= 'msgid "' . $source . '"' . "\n";
|
||||
$text .= 'msgstr "' . $target . '"' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
file_prepare_directory($path, FILE_CREATE_DIRECTORY);
|
||||
$file = entity_create('file', array(
|
||||
'uid' => 1,
|
||||
'filename' => $filename,
|
||||
'uri' => $path . '/' . $filename,
|
||||
'filemime' => 'text/x-gettext-translation',
|
||||
'timestamp' => $timestamp,
|
||||
'status' => FILE_STATUS_PERMANENT,
|
||||
));
|
||||
file_put_contents($file->getFileUri(), $po_header . $text);
|
||||
touch(drupal_realpath($file->getFileUri()), $timestamp);
|
||||
$file->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the environment containing local and remote translation files.
|
||||
*
|
||||
* Update tests require a simulated environment for local and remote files.
|
||||
* Normally remote files are located at a remote server (e.g. ftp.drupal.org).
|
||||
* For testing we can not rely on this. A directory in the file system of the
|
||||
* test site is designated for remote files and is addressed using an absolute
|
||||
* URL. Because Drupal does not allow files with a po extension to be accessed
|
||||
* (denied in .htaccess) the translation files get a _po extension. Another
|
||||
* directory is designated for local translation files.
|
||||
*
|
||||
* The environment is set up with the following files. File creation times are
|
||||
* set to create different variations in test conditions.
|
||||
* contrib_module_one
|
||||
* - remote file: timestamp new
|
||||
* - local file: timestamp old
|
||||
* contrib_module_two
|
||||
* - remote file: timestamp old
|
||||
* - local file: timestamp new
|
||||
* contrib_module_three
|
||||
* - remote file: timestamp old
|
||||
* - local file: timestamp old
|
||||
* custom_module_one
|
||||
* - local file: timestamp new
|
||||
* Time stamp of current translation set by setCurrentTranslations() is always
|
||||
* timestamp medium. This makes it easy to predict which translation will be
|
||||
* imported.
|
||||
*/
|
||||
protected function setTranslationFiles() {
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
// A flag is set to let the locale_test module replace the project data with
|
||||
// a set of test projects which match the below project files.
|
||||
\Drupal::state()->set('locale.test_projects_alter', TRUE);
|
||||
|
||||
// Setup the environment.
|
||||
$public_path = PublicStream::basePath();
|
||||
$this->setTranslationsDirectory($public_path . '/local');
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po')->save();
|
||||
|
||||
// Setting up sets of translations for the translation files.
|
||||
$translations_one = array('January' => 'Januar_1', 'February' => 'Februar_1', 'March' => 'Marz_1');
|
||||
$translations_two = array('February' => 'Februar_2', 'March' => 'Marz_2', 'April' => 'April_2');
|
||||
$translations_three = array('April' => 'April_3', 'May' => 'Mai_3', 'June' => 'Juni_3');
|
||||
|
||||
// Add a number of files to the local file system to serve as remote
|
||||
// translation server and match the project definitions set in
|
||||
// locale_test_locale_translation_projects_alter().
|
||||
$this->makePoFile('remote/8.x/contrib_module_one', 'contrib_module_one-8.x-1.1.de._po', $this->timestampNew, $translations_one);
|
||||
$this->makePoFile('remote/8.x/contrib_module_two', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestampOld, $translations_two);
|
||||
$this->makePoFile('remote/8.x/contrib_module_three', 'contrib_module_three-8.x-1.0.de._po', $this->timestampOld, $translations_three);
|
||||
|
||||
// Add a number of files to the local file system to serve as local
|
||||
// translation files and match the project definitions set in
|
||||
// locale_test_locale_translation_projects_alter().
|
||||
$this->makePoFile('local', 'contrib_module_one-8.x-1.1.de._po', $this->timestampOld, $translations_one);
|
||||
$this->makePoFile('local', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestampNew, $translations_two);
|
||||
$this->makePoFile('local', 'contrib_module_three-8.x-1.0.de._po', $this->timestampOld, $translations_three);
|
||||
$this->makePoFile('local', 'custom_module_one.de.po', $this->timestampNew);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup existing translations in the database and set up the status of
|
||||
* existing translations.
|
||||
*/
|
||||
protected function setCurrentTranslations() {
|
||||
// Add non customized translations to the database.
|
||||
$langcode = 'de';
|
||||
$context = '';
|
||||
$non_customized_translations = array(
|
||||
'March' => 'Marz',
|
||||
'June' => 'Juni',
|
||||
);
|
||||
foreach ($non_customized_translations as $source => $translation) {
|
||||
$string = $this->container->get('locale.storage')->createString(array(
|
||||
'source' => $source,
|
||||
'context' => $context,
|
||||
))
|
||||
->save();
|
||||
$this->container->get('locale.storage')->createTranslation(array(
|
||||
'lid' => $string->getId(),
|
||||
'language' => $langcode,
|
||||
'translation' => $translation,
|
||||
'customized' => LOCALE_NOT_CUSTOMIZED,
|
||||
))->save();
|
||||
}
|
||||
|
||||
// Add customized translations to the database.
|
||||
$customized_translations = array(
|
||||
'January' => 'Januar_customized',
|
||||
'February' => 'Februar_customized',
|
||||
'May' => 'Mai_customized',
|
||||
);
|
||||
foreach ($customized_translations as $source => $translation) {
|
||||
$string = $this->container->get('locale.storage')->createString(array(
|
||||
'source' => $source,
|
||||
'context' => $context,
|
||||
))
|
||||
->save();
|
||||
$this->container->get('locale.storage')->createTranslation(array(
|
||||
'lid' => $string->getId(),
|
||||
'language' => $langcode,
|
||||
'translation' => $translation,
|
||||
'customized' => LOCALE_CUSTOMIZED,
|
||||
))->save();
|
||||
}
|
||||
|
||||
// Add a state of current translations in locale_files.
|
||||
$default = array(
|
||||
'langcode' => $langcode,
|
||||
'uri' => '',
|
||||
'timestamp' => $this->timestampMedium,
|
||||
'last_checked' => $this->timestampMedium,
|
||||
);
|
||||
$data[] = array(
|
||||
'project' => 'contrib_module_one',
|
||||
'filename' => 'contrib_module_one-8.x-1.1.de._po',
|
||||
'version' => '8.x-1.1',
|
||||
);
|
||||
$data[] = array(
|
||||
'project' => 'contrib_module_two',
|
||||
'filename' => 'contrib_module_two-8.x-2.0-beta4.de._po',
|
||||
'version' => '8.x-2.0-beta4',
|
||||
);
|
||||
$data[] = array(
|
||||
'project' => 'contrib_module_three',
|
||||
'filename' => 'contrib_module_three-8.x-1.0.de._po',
|
||||
'version' => '8.x-1.0',
|
||||
);
|
||||
$data[] = array(
|
||||
'project' => 'custom_module_one',
|
||||
'filename' => 'custom_module_one.de.po',
|
||||
'version' => '',
|
||||
);
|
||||
foreach ($data as $file) {
|
||||
$file = array_merge($default, $file);
|
||||
db_insert('locale_file')->fields($file)->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the translation of a string.
|
||||
*
|
||||
* @param string $source
|
||||
* Translation source string.
|
||||
* @param string $translation
|
||||
* Translation to check. Use empty string to check for a not existing
|
||||
* translation.
|
||||
* @param string $langcode
|
||||
* Language code of the language to translate to.
|
||||
* @param string $message
|
||||
* (optional) A message to display with the assertion.
|
||||
*/
|
||||
protected function assertTranslation($source, $translation, $langcode, $message = '') {
|
||||
$db_translation = db_query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', array(':source' => $source, ':langcode' => $langcode))->fetchField();
|
||||
$db_translation = $db_translation == FALSE ? '' : $db_translation;
|
||||
$this->assertEqual($translation, $db_translation, $message ? $message : format_string('Correct translation of %source (%language)', array('%source' => $source, '%language' => $langcode)));
|
||||
}
|
||||
}
|
114
core/modules/locale/src/Tests/LocaleUpdateCronTest.php
Normal file
114
core/modules/locale/src/Tests/LocaleUpdateCronTest.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleUpdateCronTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
/**
|
||||
* Tests for using cron to update project interface translations.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleUpdateCronTest extends LocaleUpdateBase {
|
||||
|
||||
protected $batchOutput = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->addLanguage('de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests interface translation update using cron.
|
||||
*/
|
||||
public function testUpdateCron() {
|
||||
// Set a flag to let the locale_test module replace the project data with a
|
||||
// set of test projects.
|
||||
\Drupal::state()->set('locale.test_projects_alter', TRUE);
|
||||
|
||||
// Setup local and remote translations files.
|
||||
$this->setTranslationFiles();
|
||||
$this->config('locale.settings')->set('translation.default_filename', '%project-%version.%language._po')->save();
|
||||
|
||||
// Update translations using batch to ensure a clean test starting point.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$this->drupalPostForm('admin/reports/translations', array(), t('Update translations'));
|
||||
|
||||
// Store translation status for comparison.
|
||||
$initial_history = locale_translation_get_file_history();
|
||||
|
||||
// Prepare for test: Simulate new translations being available.
|
||||
// Change the last updated timestamp of a translation file.
|
||||
$contrib_module_two_uri = 'public://local/contrib_module_two-8.x-2.0-beta4.de._po';
|
||||
touch(drupal_realpath($contrib_module_two_uri), REQUEST_TIME);
|
||||
|
||||
// Prepare for test: Simulate that the file has not been checked for a long
|
||||
// time. Set the last_check timestamp to zero.
|
||||
$query = db_update('locale_file');
|
||||
$query->fields(array('last_checked' => 0));
|
||||
$query->condition('project', 'contrib_module_two');
|
||||
$query->condition('langcode', 'de');
|
||||
$query->execute();
|
||||
|
||||
// Test: Disable cron update and verify that no tasks are added to the
|
||||
// queue.
|
||||
$edit = array(
|
||||
'update_interval_days' => 0,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate/settings', $edit, t('Save configuration'));
|
||||
|
||||
// Execute locale cron tasks to add tasks to the queue.
|
||||
locale_cron();
|
||||
|
||||
// Check whether no tasks are added to the queue.
|
||||
$queue = \Drupal::queue('locale_translation', TRUE);
|
||||
$this->assertEqual($queue->numberOfItems(), 0, 'Queue is empty');
|
||||
|
||||
// Test: Enable cron update and check if update tasks are added to the
|
||||
// queue.
|
||||
// Set cron update to Weekly.
|
||||
$edit = array(
|
||||
'update_interval_days' => 7,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate/settings', $edit, t('Save configuration'));
|
||||
|
||||
// Execute locale cron tasks to add tasks to the queue.
|
||||
locale_cron();
|
||||
|
||||
// Check whether tasks are added to the queue.
|
||||
$queue = \Drupal::queue('locale_translation', TRUE);
|
||||
$this->assertEqual($queue->numberOfItems(), 3, 'Queue holds tasks for one project.');
|
||||
$item = $queue->claimItem();
|
||||
$queue->releaseItem($item);
|
||||
$this->assertEqual($item->data[1][0], 'contrib_module_two', 'Queue holds tasks for contrib module one.');
|
||||
|
||||
// Test: Run cron for a second time and check if tasks are not added to
|
||||
// the queue twice.
|
||||
locale_cron();
|
||||
|
||||
// Check whether no more tasks are added to the queue.
|
||||
$queue = \Drupal::queue('locale_translation', TRUE);
|
||||
$this->assertEqual($queue->numberOfItems(), 3, 'Queue holds tasks for one project.');
|
||||
|
||||
// Ensure last checked is updated to a greater time than the initial value.
|
||||
sleep(1);
|
||||
// Test: Execute cron and check if tasks are executed correctly.
|
||||
// Run cron to process the tasks in the queue.
|
||||
$this->cronRun();
|
||||
|
||||
drupal_static_reset('locale_translation_get_file_history');
|
||||
$history = locale_translation_get_file_history();
|
||||
$initial = $initial_history['contrib_module_two']['de'];
|
||||
$current = $history['contrib_module_two']['de'];
|
||||
$this->assertTrue($current->timestamp > $initial->timestamp, 'Timestamp is updated');
|
||||
$this->assertTrue($current->last_checked > $initial->last_checked, 'Last checked is updated');
|
||||
}
|
||||
}
|
120
core/modules/locale/src/Tests/LocaleUpdateInterfaceTest.php
Normal file
120
core/modules/locale/src/Tests/LocaleUpdateInterfaceTest.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleUpdateInterfaceTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Tests for the user interface of project interface translations.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleUpdateInterfaceTest extends LocaleUpdateBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('locale_test_translate');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the user interfaces of the interface translation update system.
|
||||
*
|
||||
* Testing the Available updates summary on the side wide status page and the
|
||||
* Available translation updates page.
|
||||
*/
|
||||
public function testInterface() {
|
||||
// No language added.
|
||||
// Check status page and Available translation updates page.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertNoText(t('Translation update status'), 'No status message');
|
||||
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertRaw(t('No translatable languages available. <a href="@add_language">Add a language</a> first.', array('@add_language' => \Drupal::url('entity.configurable_language.collection'))), 'Language message');
|
||||
|
||||
// Add German language.
|
||||
$this->addLanguage('de');
|
||||
|
||||
// Override Drupal core translation status as 'up-to-date'.
|
||||
$status = locale_translation_get_status();
|
||||
$status['drupal']['de']->type = 'current';
|
||||
\Drupal::state()->set('locale.translation_status', $status);
|
||||
|
||||
// One language added, all translations up to date.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertText(t('Translation update status'), 'Status message');
|
||||
$this->assertText(t('Up to date'), 'Translations up to date');
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertText(t('All translations up to date.'), 'Translations up to date');
|
||||
|
||||
// Set locale_test_translate module to have a local translation available.
|
||||
$status = locale_translation_get_status();
|
||||
$status['locale_test_translate']['de']->type = 'local';
|
||||
\Drupal::state()->set('locale.translation_status', $status);
|
||||
|
||||
// Check if updates are available for German.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertText(t('Translation update status'), 'Status message');
|
||||
$this->assertRaw(t('Updates available for: @languages. See the <a href="@updates">Available translation updates</a> page for more information.', array('@languages' => t('German'), '@updates' => \Drupal::url('locale.translate_status'))), 'Updates available message');
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertText(t('Updates for: @modules', array('@modules' => 'Locale test translate')), 'Translations available');
|
||||
|
||||
// Set locale_test_translate module to have a dev release and no
|
||||
// translation found.
|
||||
$status = locale_translation_get_status();
|
||||
$status['locale_test_translate']['de']->version = '1.3-dev';
|
||||
$status['locale_test_translate']['de']->type = '';
|
||||
\Drupal::state()->set('locale.translation_status', $status);
|
||||
|
||||
// Check if no updates were found.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertText(t('Translation update status'), 'Status message');
|
||||
$this->assertRaw(t('Missing translations for: @languages. See the <a href="@updates">Available translation updates</a> page for more information.', array('@languages' => t('German'), '@updates' => \Drupal::url('locale.translate_status'))), 'Missing translations message');
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertText(t('Missing translations for one project'), 'No translations found');
|
||||
$this->assertText(SafeMarkup::format('@module (@version). !info', array('@module' => 'Locale test translate', '@version' => '1.3-dev', '!info' => t('No translation files are provided for development releases.'))), 'Release details');
|
||||
$this->assertText(t('No translation files are provided for development releases.'), 'Release info');
|
||||
|
||||
// Override Drupal core translation status as 'no translations found'.
|
||||
$status = locale_translation_get_status();
|
||||
$status['drupal']['de']->type = '';
|
||||
$status['drupal']['de']->timestamp = 0;
|
||||
$status['drupal']['de']->version = '8.1.1';
|
||||
\Drupal::state()->set('locale.translation_status', $status);
|
||||
|
||||
// Check if Drupal core is not translated.
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertText(t('Missing translations for 2 projects'), 'No translations found');
|
||||
$this->assertText(t('@module (@version).', array('@module' => t('Drupal core'), '@version' => '8.1.1')), 'Release details');
|
||||
|
||||
// Override Drupal core translation status as 'translations available'.
|
||||
$status = locale_translation_get_status();
|
||||
$status['drupal']['de']->type = 'local';
|
||||
$status['drupal']['de']->files['local']->timestamp = REQUEST_TIME;
|
||||
$status['drupal']['de']->files['local']->info['version'] = '8.1.1';
|
||||
\Drupal::state()->set('locale.translation_status', $status);
|
||||
|
||||
// Check if translations are available for Drupal core.
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertText(t('Updates for: !project', array('!project' => t('Drupal core'))), 'Translations found');
|
||||
$this->assertText(SafeMarkup::format('@module (@date)', array('@module' => t('Drupal core'), '@date' => format_date(REQUEST_TIME, 'html_date'))), 'Core translation update');
|
||||
$update_button = $this->xpath('//input[@type="submit"][@value="' . t('Update translations') . '"]');
|
||||
$this->assertTrue($update_button, 'Update translations button');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleUpdateNotDevelopmentReleaseTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Test for finding the first available normal core release version,
|
||||
* in case of core is a development release.
|
||||
*
|
||||
* @group language
|
||||
*/
|
||||
class LocaleUpdateNotDevelopmentReleaseTest extends WebTestBase {
|
||||
|
||||
public static $modules = array('update', 'locale', 'locale_test_not_development_release');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
module_load_include('compare.inc', 'locale');
|
||||
$admin_user = $this->drupalCreateUser(array('administer modules', 'administer languages', 'access administration pages', 'translate interface'));
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', array('predefined_langcode' => 'hu'), t('Add language'));
|
||||
}
|
||||
|
||||
public function testLocaleUpdateNotDevelopmentRelease() {
|
||||
// Set available Drupal releases for test.
|
||||
$available = array(
|
||||
'title' => 'Drupal core',
|
||||
'short_name' => 'drupal',
|
||||
'type' => 'project_core',
|
||||
'api_version' => '8.x',
|
||||
'project_status' => 'unsupported',
|
||||
'link' => 'https://www.drupal.org/project/drupal',
|
||||
'terms' => '',
|
||||
'releases' => array(
|
||||
'8.0.0-alpha110' => array(
|
||||
'name' => 'drupal 8.0.0-alpha110',
|
||||
'version' => '8.0.0-alpha110',
|
||||
'tag' => '8.0.0-alpha110',
|
||||
'version_major' => '8',
|
||||
'version_minor' => '0',
|
||||
'version_patch' => '0',
|
||||
'version_extra' => 'alpha110',
|
||||
'status' => 'published',
|
||||
'release_link' => 'https://www.drupal.org/node/2316617',
|
||||
'download_link' => 'http://ftp.drupal.org/files/projects/drupal-8.0.0-alpha110.tar.gz',
|
||||
'date' => '1407344628',
|
||||
'mdhash' => '9d71afdd0ce541f2ff5ca2fbbca00df7',
|
||||
'filesize' => '9172832',
|
||||
'files' => '',
|
||||
'terms' => array(),
|
||||
),
|
||||
'8.0.0-alpha100' => array(
|
||||
'name' => 'drupal 8.0.0-alpha100',
|
||||
'version' => '8.0.0-alpha100',
|
||||
'tag' => '8.0.0-alpha100',
|
||||
'version_major' => '8',
|
||||
'version_minor' => '0',
|
||||
'version_patch' => '0',
|
||||
'version_extra' => 'alpha100',
|
||||
'status' => 'published',
|
||||
'release_link' => 'https://www.drupal.org/node/2316617',
|
||||
'download_link' => 'http://ftp.drupal.org/files/projects/drupal-8.0.0-alpha100.tar.gz',
|
||||
'date' => '1407344628',
|
||||
'mdhash' => '9d71afdd0ce541f2ff5ca2fbbca00df7',
|
||||
'filesize' => '9172832',
|
||||
'files' => '',
|
||||
'terms' => array(),
|
||||
),
|
||||
),
|
||||
);
|
||||
$available['last_fetch'] = REQUEST_TIME;
|
||||
\Drupal::keyValueExpirable('update_available_releases')->setWithExpire('drupal', $available, 10);
|
||||
$projects = locale_translation_build_projects();
|
||||
$this->verbose($projects['drupal']->info['version']);
|
||||
$this->assertEqual($projects['drupal']->info['version'], '8.0.0-alpha110', 'The first release with the same major release number which is not a development release.');
|
||||
}
|
||||
}
|
447
core/modules/locale/src/Tests/LocaleUpdateTest.php
Normal file
447
core/modules/locale/src/Tests/LocaleUpdateTest.php
Normal file
|
@ -0,0 +1,447 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\Tests\LocaleUpdateTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\locale\Tests;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
/**
|
||||
* Tests for updating the interface translations of projects.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleUpdateTest extends LocaleUpdateBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
module_load_include('compare.inc', 'locale');
|
||||
module_load_include('fetch.inc', 'locale');
|
||||
$admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
|
||||
$this->drupalLogin($admin_user);
|
||||
// We use German as test language. This language must match the translation
|
||||
// file that come with the locale_test module (test.de.po) and can therefore
|
||||
// not be chosen randomly.
|
||||
$this->addLanguage('de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a list of translatable projects gets build.
|
||||
*/
|
||||
public function testUpdateProjects() {
|
||||
module_load_include('compare.inc', 'locale');
|
||||
|
||||
// Make the test modules look like a normal custom module. i.e. make the
|
||||
// modules not hidden. locale_test_system_info_alter() modifies the project
|
||||
// info of the locale_test and locale_test_translate modules.
|
||||
\Drupal::state()->set('locale.test_system_info_alter', TRUE);
|
||||
$this->resetAll();
|
||||
|
||||
// Check if interface translation data is collected from hook_info.
|
||||
$projects = locale_translation_project_list();
|
||||
$this->assertFalse(isset($projects['locale_test_translate']), 'Hidden module not found');
|
||||
$this->assertEqual($projects['locale_test']['info']['interface translation server pattern'], 'core/modules/locale/test/test.%language.po', 'Interface translation parameter found in project info.');
|
||||
$this->assertEqual($projects['locale_test']['name'], 'locale_test', format_string('%key found in project info.', array('%key' => 'interface translation project')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if local or remote translation sources are detected.
|
||||
*
|
||||
* The translation status process by default checks the status of the
|
||||
* installed projects. For testing purpose a predefined set of modules with
|
||||
* fixed file names and release versions is used. This custom project
|
||||
* definition is applied using a hook_locale_translation_projects_alter
|
||||
* implementation in the locale_test module.
|
||||
*
|
||||
* This test generates a set of local and remote translation files in their
|
||||
* respective local and remote translation directory. The test checks whether
|
||||
* the most recent files are selected in the different check scenarios: check
|
||||
* for local files only, check for both local and remote files.
|
||||
*/
|
||||
public function testUpdateCheckStatus() {
|
||||
// Case when contributed modules are absent.
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertText(t('Missing translations for one project'));
|
||||
|
||||
$config = $this->config('locale.settings');
|
||||
// Set a flag to let the locale_test module replace the project data with a
|
||||
// set of test projects.
|
||||
\Drupal::state()->set('locale.test_projects_alter', TRUE);
|
||||
|
||||
// Create local and remote translations files.
|
||||
$this->setTranslationFiles();
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po')->save();
|
||||
|
||||
// Set the test conditions.
|
||||
$edit = array(
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_LOCAL,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate/settings', $edit, t('Save configuration'));
|
||||
|
||||
// Get status of translation sources at local file system.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$result = locale_translation_get_status();
|
||||
$this->assertEqual($result['contrib_module_one']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_one found');
|
||||
$this->assertEqual($result['contrib_module_one']['de']->timestamp, $this->timestampOld, 'Translation timestamp found');
|
||||
$this->assertEqual($result['contrib_module_two']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_two found');
|
||||
$this->assertEqual($result['contrib_module_two']['de']->timestamp, $this->timestampNew, 'Translation timestamp found');
|
||||
$this->assertEqual($result['locale_test']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of locale_test found');
|
||||
$this->assertEqual($result['custom_module_one']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of custom_module_one found');
|
||||
|
||||
// Set the test conditions.
|
||||
$edit = array(
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate/settings', $edit, t('Save configuration'));
|
||||
|
||||
// Get status of translation sources at both local and remote locations.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$result = locale_translation_get_status();
|
||||
$this->assertEqual($result['contrib_module_one']['de']->type, LOCALE_TRANSLATION_REMOTE, 'Translation of contrib_module_one found');
|
||||
$this->assertEqual($result['contrib_module_one']['de']->timestamp, $this->timestampNew, 'Translation timestamp found');
|
||||
$this->assertEqual($result['contrib_module_two']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_two found');
|
||||
$this->assertEqual($result['contrib_module_two']['de']->timestamp, $this->timestampNew, 'Translation timestamp found');
|
||||
$this->assertEqual($result['contrib_module_three']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_three found');
|
||||
$this->assertEqual($result['contrib_module_three']['de']->timestamp, $this->timestampOld, 'Translation timestamp found');
|
||||
$this->assertEqual($result['locale_test']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of locale_test found');
|
||||
$this->assertEqual($result['custom_module_one']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of custom_module_one found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation import from remote sources.
|
||||
*
|
||||
* Test conditions:
|
||||
* - Source: remote and local files
|
||||
* - Import overwrite: all existing translations
|
||||
*/
|
||||
public function testUpdateImportSourceRemote() {
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
// Build the test environment.
|
||||
$this->setTranslationFiles();
|
||||
$this->setCurrentTranslations();
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po');
|
||||
|
||||
// Set the update conditions for this test.
|
||||
$edit = array(
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
|
||||
'overwrite' => LOCALE_TRANSLATION_OVERWRITE_ALL,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate/settings', $edit, t('Save configuration'));
|
||||
|
||||
// Get the translation status.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
|
||||
// Check the status on the Available translation status page.
|
||||
$this->assertRaw('<label for="edit-langcodes-de" class="visually-hidden">Update German</label>', 'German language found');
|
||||
$this->assertText('Updates for: Contributed module one, Contributed module two, Custom module one, Locale test', 'Updates found');
|
||||
$this->assertText('Contributed module one (' . format_date($this->timestampNow, 'html_date') . ')', 'Updates for Contrib module one');
|
||||
$this->assertText('Contributed module two (' . format_date($this->timestampNew, 'html_date') . ')', 'Updates for Contrib module two');
|
||||
|
||||
// Execute the translation update.
|
||||
$this->drupalPostForm('admin/reports/translations', array(), t('Update translations'));
|
||||
|
||||
// Check if the translation has been updated, using the status cache.
|
||||
$status = locale_translation_get_status();
|
||||
$this->assertEqual($status['contrib_module_one']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_one found');
|
||||
$this->assertEqual($status['contrib_module_two']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_two found');
|
||||
$this->assertEqual($status['contrib_module_three']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_three found');
|
||||
|
||||
// Check the new translation status.
|
||||
// The static cache needs to be flushed first to get the most recent data
|
||||
// from the database. The function was called earlier during this test.
|
||||
drupal_static_reset('locale_translation_get_file_history');
|
||||
$history = locale_translation_get_file_history();
|
||||
$this->assertTrue($history['contrib_module_one']['de']->timestamp >= $this->timestampNow, 'Translation of contrib_module_one is imported');
|
||||
$this->assertTrue($history['contrib_module_one']['de']->last_checked >= $this->timestampNow, 'Translation of contrib_module_one is updated');
|
||||
$this->assertEqual($history['contrib_module_two']['de']->timestamp, $this->timestampNew, 'Translation of contrib_module_two is imported');
|
||||
$this->assertTrue($history['contrib_module_two']['de']->last_checked >= $this->timestampNow, 'Translation of contrib_module_two is updated');
|
||||
$this->assertEqual($history['contrib_module_three']['de']->timestamp, $this->timestampMedium, 'Translation of contrib_module_three is not imported');
|
||||
$this->assertEqual($history['contrib_module_three']['de']->last_checked, $this->timestampMedium, 'Translation of contrib_module_three is not updated');
|
||||
|
||||
// Check whether existing translations have (not) been overwritten.
|
||||
$this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_1', 'Translation of January');
|
||||
$this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_2', 'Translation of February');
|
||||
$this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz_2', 'Translation of March');
|
||||
$this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
|
||||
$this->assertEqual(t('May', array(), array('langcode' => 'de')), 'Mai_customized', 'Translation of May');
|
||||
$this->assertEqual(t('June', array(), array('langcode' => 'de')), 'Juni', 'Translation of June');
|
||||
$this->assertEqual(t('Monday', array(), array('langcode' => 'de')), 'Montag', 'Translation of Monday');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation import from local sources.
|
||||
*
|
||||
* Test conditions:
|
||||
* - Source: local files only
|
||||
* - Import overwrite: all existing translations
|
||||
*/
|
||||
public function testUpdateImportSourceLocal() {
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
// Build the test environment.
|
||||
$this->setTranslationFiles();
|
||||
$this->setCurrentTranslations();
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po');
|
||||
|
||||
// Set the update conditions for this test.
|
||||
$edit = array(
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_LOCAL,
|
||||
'overwrite' => LOCALE_TRANSLATION_OVERWRITE_ALL,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate/settings', $edit, t('Save configuration'));
|
||||
|
||||
// Execute the translation update.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$this->drupalPostForm('admin/reports/translations', array(), t('Update translations'));
|
||||
|
||||
// Check if the translation has been updated, using the status cache.
|
||||
$status = locale_translation_get_status();
|
||||
$this->assertEqual($status['contrib_module_one']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_one found');
|
||||
$this->assertEqual($status['contrib_module_two']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_two found');
|
||||
$this->assertEqual($status['contrib_module_three']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_three found');
|
||||
|
||||
// Check the new translation status.
|
||||
// The static cache needs to be flushed first to get the most recent data
|
||||
// from the database. The function was called earlier during this test.
|
||||
drupal_static_reset('locale_translation_get_file_history');
|
||||
$history = locale_translation_get_file_history();
|
||||
$this->assertTrue($history['contrib_module_one']['de']->timestamp >= $this->timestampMedium, 'Translation of contrib_module_one is imported');
|
||||
$this->assertEqual($history['contrib_module_one']['de']->last_checked, $this->timestampMedium, 'Translation of contrib_module_one is updated');
|
||||
$this->assertEqual($history['contrib_module_two']['de']->timestamp, $this->timestampNew, 'Translation of contrib_module_two is imported');
|
||||
$this->assertTrue($history['contrib_module_two']['de']->last_checked >= $this->timestampNow, 'Translation of contrib_module_two is updated');
|
||||
$this->assertEqual($history['contrib_module_three']['de']->timestamp, $this->timestampMedium, 'Translation of contrib_module_three is not imported');
|
||||
$this->assertEqual($history['contrib_module_three']['de']->last_checked, $this->timestampMedium, 'Translation of contrib_module_three is not updated');
|
||||
|
||||
// Check whether existing translations have (not) been overwritten.
|
||||
$this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_customized', 'Translation of January');
|
||||
$this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_2', 'Translation of February');
|
||||
$this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz_2', 'Translation of March');
|
||||
$this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
|
||||
$this->assertEqual(t('May', array(), array('langcode' => 'de')), 'Mai_customized', 'Translation of May');
|
||||
$this->assertEqual(t('June', array(), array('langcode' => 'de')), 'Juni', 'Translation of June');
|
||||
$this->assertEqual(t('Monday', array(), array('langcode' => 'de')), 'Montag', 'Translation of Monday');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation import and only overwrite non-customized translations.
|
||||
*
|
||||
* Test conditions:
|
||||
* - Source: remote and local files
|
||||
* - Import overwrite: only overwrite non-customized translations
|
||||
*/
|
||||
public function testUpdateImportModeNonCustomized() {
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
// Build the test environment.
|
||||
$this->setTranslationFiles();
|
||||
$this->setCurrentTranslations();
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po');
|
||||
|
||||
// Set the test conditions.
|
||||
$edit = array(
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
|
||||
'overwrite' => LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate/settings', $edit, t('Save configuration'));
|
||||
|
||||
// Execute translation update.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$this->drupalPostForm('admin/reports/translations', array(), t('Update translations'));
|
||||
|
||||
// Check whether existing translations have (not) been overwritten.
|
||||
$this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_customized', 'Translation of January');
|
||||
$this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_customized', 'Translation of February');
|
||||
$this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz_2', 'Translation of March');
|
||||
$this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
|
||||
$this->assertEqual(t('May', array(), array('langcode' => 'de')), 'Mai_customized', 'Translation of May');
|
||||
$this->assertEqual(t('June', array(), array('langcode' => 'de')), 'Juni', 'Translation of June');
|
||||
$this->assertEqual(t('Monday', array(), array('langcode' => 'de')), 'Montag', 'Translation of Monday');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation import and don't overwrite any translation.
|
||||
*
|
||||
* Test conditions:
|
||||
* - Source: remote and local files
|
||||
* - Import overwrite: don't overwrite any existing translation
|
||||
*/
|
||||
public function testUpdateImportModeNone() {
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
// Build the test environment.
|
||||
$this->setTranslationFiles();
|
||||
$this->setCurrentTranslations();
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po');
|
||||
|
||||
// Set the test conditions.
|
||||
$edit = array(
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
|
||||
'overwrite' => LOCALE_TRANSLATION_OVERWRITE_NONE,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate/settings', $edit, t('Save configuration'));
|
||||
|
||||
// Execute translation update.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$this->drupalPostForm('admin/reports/translations', array(), t('Update translations'));
|
||||
|
||||
// Check whether existing translations have (not) been overwritten.
|
||||
$this->assertTranslation('January', 'Januar_customized', 'de');
|
||||
$this->assertTranslation('February', 'Februar_customized', 'de');
|
||||
$this->assertTranslation('March', 'Marz', 'de');
|
||||
$this->assertTranslation('April', 'April_2', 'de');
|
||||
$this->assertTranslation('May', 'Mai_customized', 'de');
|
||||
$this->assertTranslation('June', 'Juni', 'de');
|
||||
$this->assertTranslation('Monday', 'Montag', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests automatic translation import when a module is enabled.
|
||||
*/
|
||||
public function testEnableUninstallModule() {
|
||||
// Make the hidden test modules look like a normal custom module.
|
||||
\Drupal::state()->set('locale.test_system_info_alter', TRUE);
|
||||
|
||||
// Check if there is no translation yet.
|
||||
$this->assertTranslation('Tuesday', '', 'de');
|
||||
|
||||
// Enable a module.
|
||||
$edit = array(
|
||||
'modules[Testing][locale_test_translate][enable]' => 'locale_test_translate',
|
||||
);
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
|
||||
|
||||
// Check if translations have been imported.
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.',
|
||||
array('%number' => 7, '%update' => 0, '%delete' => 0)), 'One translation file imported.');
|
||||
$this->assertTranslation('Tuesday', 'Dienstag', 'de');
|
||||
|
||||
$edit = array(
|
||||
'uninstall[locale_test_translate]' => 1,
|
||||
);
|
||||
$this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall'));
|
||||
$this->drupalPostForm(NULL, array(), t('Uninstall'));
|
||||
|
||||
// Check if the file data is removed from the database.
|
||||
$history = locale_translation_get_file_history();
|
||||
$this->assertFalse(isset($history['locale_test_translate']), 'Project removed from the file history');
|
||||
$projects = locale_translation_get_projects();
|
||||
$this->assertFalse(isset($projects['locale_test_translate']), 'Project removed from the project list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests automatic translation import when a language is added.
|
||||
*
|
||||
* When a language is added, the system will check for translations files of
|
||||
* enabled modules and will import them. When a language is removed the system
|
||||
* will remove all translations of that language from the database.
|
||||
*/
|
||||
public function testEnableLanguage() {
|
||||
// Make the hidden test modules look like a normal custom module.
|
||||
\Drupal::state()->set('locale.test_system_info_alter', TRUE);
|
||||
|
||||
// Enable a module.
|
||||
$edit = array(
|
||||
'modules[Testing][locale_test_translate][enable]' => 'locale_test_translate',
|
||||
);
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
|
||||
|
||||
// Check if there is no Dutch translation yet.
|
||||
$this->assertTranslation('Extraday', '', 'nl');
|
||||
$this->assertTranslation('Tuesday', 'Dienstag', 'de');
|
||||
|
||||
// Add a language.
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'nl',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
// Check if the right number of translations are added.
|
||||
$this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.',
|
||||
array('%number' => 8, '%update' => 0, '%delete' => 0)), 'One language added.');
|
||||
$this->assertTranslation('Extraday', 'extra dag', 'nl');
|
||||
|
||||
// Check if the language data is added to the database.
|
||||
$result = db_query("SELECT project FROM {locale_file} WHERE langcode='nl'")->fetchField();
|
||||
$this->assertTrue($result, 'Files added to file history');
|
||||
|
||||
// Remove a language.
|
||||
$this->drupalPostForm('admin/config/regional/language/delete/nl', array(), t('Delete'));
|
||||
|
||||
// Check if the language data is removed from the database.
|
||||
$result = db_query("SELECT project FROM {locale_file} WHERE langcode='nl'")->fetchField();
|
||||
$this->assertFalse($result, 'Files removed from file history');
|
||||
|
||||
// Check that the Dutch translation is gone.
|
||||
$this->assertTranslation('Extraday', '', 'nl');
|
||||
$this->assertTranslation('Tuesday', 'Dienstag', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests automatic translation import when a custom language is added.
|
||||
*/
|
||||
public function testEnableCustomLanguage() {
|
||||
// Make the hidden test modules look like a normal custom module.
|
||||
\Drupal::state()->set('locale.test_system_info_alter', TRUE);
|
||||
|
||||
// Enable a module.
|
||||
$edit = array(
|
||||
'modules[Testing][locale_test_translate][enable]' => 'locale_test_translate',
|
||||
);
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
|
||||
|
||||
// Create a custom language with language code 'xx' and a random
|
||||
// name.
|
||||
$langcode = 'xx';
|
||||
$name = $this->randomMachineName(16);
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
|
||||
|
||||
// Ensure the translation file is automatically imported when the language
|
||||
// was added.
|
||||
$this->assertText(t('One translation file imported.'), 'Language file automatically imported.');
|
||||
$this->assertText(t('One translation string was skipped because of disallowed or malformed HTML'), 'Language file automatically imported.');
|
||||
|
||||
// Ensure the strings were successfully imported.
|
||||
$search = array(
|
||||
'string' => 'lundi',
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertNoText(t('No strings available.'), 'String successfully imported.');
|
||||
|
||||
// Ensure the multiline string was imported.
|
||||
$search = array(
|
||||
'string' => 'Source string for multiline translation',
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText('Multiline translation string to make sure that import works with it.', 'String successfully imported.');
|
||||
|
||||
// Ensure 'Allowed HTML source string' was imported but the translation for
|
||||
// 'Another allowed HTML source string' was not because it contains invalid
|
||||
// HTML.
|
||||
$search = array(
|
||||
'string' => 'HTML source string',
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
$this->assertText('Allowed HTML source string', 'String successfully imported.');
|
||||
$this->assertNoText('Another allowed HTML source string', 'String with disallowed translation not imported.');
|
||||
}
|
||||
|
||||
}
|
128
core/modules/locale/src/TranslationString.php
Normal file
128
core/modules/locale/src/TranslationString.php
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\locale\TranslationString.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Overrides Drupal\locale\StringBase::__construct().
|
||||
*/
|
||||
public function __construct($values = array()) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::isSource().
|
||||
*/
|
||||
public function isSource() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::isTranslation().
|
||||
*/
|
||||
public function isTranslation() {
|
||||
return !empty($this->lid) && !empty($this->language) && isset($this->translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
return isset($this->translation) ? $this->translation : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::setString().
|
||||
*/
|
||||
public function setString($string) {
|
||||
$this->translation = $string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::isNew().
|
||||
*/
|
||||
public function isNew() {
|
||||
return $this->isNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::save().
|
||||
*/
|
||||
public function save() {
|
||||
parent::save();
|
||||
$this->isNew = FALSE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\locale\StringInterface::delete().
|
||||
*/
|
||||
public function delete() {
|
||||
parent::delete();
|
||||
$this->isNew = TRUE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue