diff --git a/core/core.services.yml b/core/core.services.yml index 53a2725..dcb195e5 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -260,8 +260,6 @@ services: config.storage.schema: class: Drupal\Core\Config\ExtensionInstallStorage arguments: ['@config.storage', 'config/schema'] - config.storage.installer: - class: Drupal\Core\Config\InstallStorage config.typed: class: Drupal\Core\Config\TypedConfigManager arguments: ['@config.storage', '@config.storage.schema', '@cache.discovery', '@module_handler'] diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php index 3d5adad..c180847 100644 --- a/core/lib/Drupal/Core/Language/LanguageManager.php +++ b/core/lib/Drupal/Core/Language/LanguageManager.php @@ -233,27 +233,16 @@ public function getLanguageSwitchLinks($type, Url $url) { } /** - * Some common languages with their English and native names. - * - * Language codes are defined by the W3C language tags document for - * interoperability. Language codes typically have a language and, optionally, - * a script or regional variant name. See: - * http://www.w3.org/International/articles/language-tags/ for more - * information. - * - * This list is based on languages available from localize.drupal.org. See - * http://localize.drupal.org/issues for information on how to add languages - * there. - * - * The "Left-to-right marker" comments and the enclosed UTF-8 markers are to - * make otherwise strange looking PHP syntax natural (to not be displayed in - * right to left). See http://drupal.org/node/128866#comment-528929. - * - * @return array - * An array of language code to language name information. - * Language name information itself is an array of English and native names. + * @inheritdoc */ public static function getStandardLanguageList() { + // This list is based on languages available from localize.drupal.org. See + // http://localize.drupal.org/issues for information on how to add languages + // there. + // + // The "Left-to-right marker" comments and the enclosed UTF-8 markers are to + // make otherwise strange looking PHP syntax natural (to not be displayed in + // right to left). See http://drupal.org/node/128866#comment-528929. return array( 'af' => array('Afrikaans', 'Afrikaans'), 'am' => array('Amharic', 'አማርኛ'), diff --git a/core/lib/Drupal/Core/Language/LanguageManagerInterface.php b/core/lib/Drupal/Core/Language/LanguageManagerInterface.php index c29dd33..b536a00 100644 --- a/core/lib/Drupal/Core/Language/LanguageManagerInterface.php +++ b/core/lib/Drupal/Core/Language/LanguageManagerInterface.php @@ -208,4 +208,19 @@ public function setConfigOverrideLanguage(LanguageInterface $language = NULL); */ public function getConfigOverrideLanguage(); + /** + * Some common languages with their English and native names. + * + * Language codes are defined by the W3C language tags document for + * interoperability. Language codes typically have a language and, optionally, + * a script or regional variant name. See: + * http://www.w3.org/International/articles/language-tags/ for more + * information. + * + * @return array + * An array of language code to language name information. Language name + * information itself is an array of English and native names. + */ + public static function getStandardLanguageList(); + } diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 356f68b..58a913a 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -28,13 +28,13 @@ function book_help($route_name, RouteMatchInterface $route_match) { switch ($route_name) { case 'help.page.book': $output = '
' . t('The Book module is used for creating structured, multi-page content, such as site resource guides, manuals, and wikis. It allows you to create content that has chapters, sections, subsections, or any similarly-tiered structure. For more information, see the online documentation for the Book module.', array('!book' => 'https://drupal.org/documentation/modules/book')) . '
'; + $output .= '' . t('The Book module is used for creating structured, multi-page content, such as site resource guides, manuals, and wikis. It allows you to create content that has chapters, sections, subsections, or any similarly-tiered structure. For more information, see the online documentation for the Book module.', ['!book' => 'https://drupal.org/documentation/modules/book']) . '
'; $output .= '' . t('The book module offers a means to organize a collection of related content pages, collectively known as a book. When viewed, this content automatically displays links to adjacent book pages, providing a simple navigation system for creating and reviewing structured content.') . '
'; case 'entity.node.book_outline_form': - return '' . t('The outline feature allows you to include pages in the Book hierarchy, as well as move them within the hierarchy or to reorder an entire book.', array('!book' => \Drupal::url('book.render'), '!book-admin' => \Drupal::url('book.admin'))) . '
'; + return '' . t('The outline feature allows you to include pages in the Book hierarchy, as well as move them within the hierarchy or to reorder an entire book.', ['!book' => \Drupal::url('book.render'), '!book-admin' => \Drupal::url('book.admin')]) . '
'; } } @@ -54,27 +54,27 @@ function book_help($route_name, RouteMatchInterface $route_match) { * Implements hook_theme(). */ function book_theme() { - return array( - 'book_navigation' => array( - 'variables' => array('book_link' => NULL), - ), - 'book_tree' => array( + return [ + 'book_navigation' => [ + 'variables' => ['book_link' => NULL], + ], + 'book_tree' => [ 'render element' => 'tree', - ), - 'book_link' => array( + ], + 'book_link' => [ 'render element' => 'element', 'function' => 'theme_book_link', - ), - 'book_export_html' => array( - 'variables' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL), - ), - 'book_all_books_block' => array( + ], + 'book_export_html' => [ + 'variables' => ['title' => NULL, 'contents' => NULL, 'depth' => NULL], + ], + 'book_all_books_block' => [ 'render element' => 'book_menus', - ), - 'book_node_export_html' => array( - 'variables' => array('node' => NULL, 'children' => NULL), - ), - ); + ], + 'book_node_export_html' => [ + 'variables' => ['node' => NULL, 'children' => NULL] + ], + ]; } /** @@ -100,31 +100,31 @@ function book_node_links_alter(array &$node_links, NodeInterface $node, array &$ $child_type = \Drupal::config('book.settings')->get('child_type'); $access_control_handler = \Drupal::entityManager()->getAccessControlHandler('node'); if (($account->hasPermission('add content to books') || $account->hasPermission('administer book outlines')) && $access_control_handler->createAccess($child_type) && $node->isPublished() && $node->book['depth'] < BookManager::BOOK_MAX_DEPTH) { - $links['book_add_child'] = array( + $links['book_add_child'] = [ 'title' => t('Add child page'), 'url' => Url::fromRoute('node.add', ['node_type' => $child_type], ['query' => ['parent' => $node->id()]]), - ); + ]; } if ($account->hasPermission('access printer-friendly version')) { - $links['book_printer'] = array( + $links['book_printer'] = [ 'title' => t('Printer-friendly version'), 'url' => Url::fromRoute('book.export', [ 'type' => 'html', 'node' => $node->id(), ]), - 'attributes' => array('title' => t('Show a printer-friendly version of this book page and its sub-pages.')) - ); + 'attributes' => ['title' => t('Show a printer-friendly version of this book page and its sub-pages.')] + ]; } } } if (!empty($links)) { - $node_links['book'] = array( + $node_links['book'] = [ '#theme' => 'links__node__book', '#links' => $links, - '#attributes' => array('class' => array('links', 'inline')), - ); + '#attributes' => ['class' => ['links', 'inline']], + ]; } } } @@ -151,17 +151,17 @@ function book_form_node_form_alter(&$form, FormStateInterface $form_state, $form $collapsed = !($node->isNew() && !empty($node->book['pid'])); $form = \Drupal::service('book.manager')->addFormElements($form, $form_state, $node, $account, $collapsed); // The "js-hide" class hides submit button when Javascript is enabled. - $form['book']['pick-book'] = array( + $form['book']['pick-book'] = [ '#type' => 'submit', '#value' => t('Change book (update list of parents)'), - '#submit' => array('book_pick_book_nojs_submit'), + '#submit' => ['book_pick_book_nojs_submit'], '#weight' => 20, - '#attributes' => array( - 'class' => array( + '#attributes' => [ + 'class' => [ 'js-hide', - ), - ), - ); + ], + ], + ]; $form['#entity_builders'][] = 'book_node_builder'; } } @@ -235,8 +235,8 @@ function book_node_view(array &$build, EntityInterface $node, EntityViewDisplayI if (!$book_node->access()) { return; } - $book_navigation = array( '#theme' => 'book_navigation', '#book_link' => $node->book); - $build['book_navigation'] = array( + $book_navigation = [ '#theme' => 'book_navigation', '#book_link' => $node->book]; + $build['book_navigation'] = [ '#markup' => drupal_render($book_navigation), '#weight' => 100, '#attached' => [ @@ -249,7 +249,7 @@ function book_node_view(array &$build, EntityInterface $node, EntityViewDisplayI '#cache' => [ 'tags' => $node->getEntityType()->getListCacheTags(), ], - ); + ]; } } } @@ -341,10 +341,10 @@ function book_form_node_delete_confirm_alter(&$form, FormStateInterface $form_st $node = Node::load($form['nid']['#value']); if (isset($node->book) && $node->book['has_children']) { - $form['book_warning'] = array( - '#markup' => '' . t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', array('%title' => $node->label())) . '
', + $form['book_warning'] = [ + '#markup' => '' . t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', ['%title' => $node->label()]) . '
', '#weight' => -10, - ); + ]; } } @@ -367,11 +367,11 @@ function template_preprocess_book_all_books_block(&$variables) { $elements = $variables['book_menus']; $variables['book_menus'] = array(); foreach (Element::children($elements) as $index) { - $variables['book_menus'][] = array( + $variables['book_menus'][] = [ 'id' => $index, 'menu' => $elements[$index], 'title' => $elements[$index]['#book_title'], - ); + ]; } } @@ -391,7 +391,7 @@ function template_preprocess_book_navigation(&$variables) { // Provide extra variables for themers. Not needed by default. $variables['book_id'] = $book_link['bid']; $variables['book_title'] = SafeMarkup::checkPlain($book_link['link_title']); - $variables['book_url'] = \Drupal::url('entity.node.canonical', array('node' => $book_link['bid'])); + $variables['book_url'] = \Drupal::url('entity.node.canonical', ['node' => $book_link['bid']]); $variables['current_depth'] = $book_link['depth']; $variables['tree'] = ''; @@ -404,11 +404,11 @@ function template_preprocess_book_navigation(&$variables) { $build = array(); if ($prev = $book_outline->prevLink($book_link)) { - $prev_href = \Drupal::url('entity.node.canonical', array('node' => $prev['nid'])); - $build['#attached']['html_head_link'][][] = array( + $prev_href = \Drupal::url('entity.node.canonical', ['node' => $prev['nid']]); + $build['#attached']['html_head_link'][][] = [ 'rel' => 'prev', 'href' => $prev_href, - ); + ]; $variables['prev_url'] = $prev_href; $variables['prev_title'] = SafeMarkup::checkPlain($prev['title']); } @@ -416,21 +416,21 @@ function template_preprocess_book_navigation(&$variables) { /** @var \Drupal\book\BookManagerInterface $book_manager */ $book_manager = \Drupal::service('book.manager'); if ($book_link['pid'] && $parent = $book_manager->loadBookLink($book_link['pid'])) { - $parent_href = \Drupal::url('entity.node.canonical', array('node' => $book_link['pid'])); - $build['#attached']['html_head_link'][][] = array( + $parent_href = \Drupal::url('entity.node.canonical', ['node' => $book_link['pid']]); + $build['#attached']['html_head_link'][][] = [ 'rel' => 'up', 'href' => $parent_href, - ); + ]; $variables['parent_url'] = $parent_href; $variables['parent_title'] = SafeMarkup::checkPlain($parent['title']); } if ($next = $book_outline->nextLink($book_link)) { - $next_href = \Drupal::url('entity.node.canonical', array('node' => $next['nid'])); - $build['#attached']['html_head_link'][][] = array( + $next_href = \Drupal::url('entity.node.canonical', ['node' => $next['nid']]); + $build['#attached']['html_head_link'][][] = [ 'rel' => 'next', 'href' => $next_href, - ); + ]; $variables['next_url'] = $next_href; $variables['next_title'] = SafeMarkup::checkPlain($next['title']); } @@ -442,7 +442,7 @@ function template_preprocess_book_navigation(&$variables) { $variables['has_links'] = FALSE; // Link variables to filter for values and set state of the flag variable. - $links = array('prev_url', 'prev_title', 'parent_url', 'parent_title', 'next_url', 'next_title'); + $links = ['prev_url', 'prev_title', 'parent_url', 'parent_title', 'next_url', 'next_title']; foreach ($links as $link) { if (isset($variables[$link])) { // Flag when there is a value. diff --git a/core/modules/config/src/Tests/ConfigInstallTest.php b/core/modules/config/src/Tests/ConfigInstallTest.php index fe9a6b6..00ec4fa 100644 --- a/core/modules/config/src/Tests/ConfigInstallTest.php +++ b/core/modules/config/src/Tests/ConfigInstallTest.php @@ -7,6 +7,7 @@ namespace Drupal\config\Tests; +use Drupal\Core\Config\InstallStorage; use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Config\UnmetDependenciesException; @@ -223,7 +224,8 @@ public function testDependencyChecking() { function testLanguage() { $this->installModules(['config_test_language']); // Test imported configuration with implicit language code. - $data = $this->container->get('config.storage.installer')->read('config_test.dynamic.dotted.english'); + $storage = new InstallStorage(); + $data = $storage->read('config_test.dynamic.dotted.english'); $this->assertTrue(!isset($data['langcode'])); $this->assertEqual( $this->config('config_test.dynamic.dotted.english')->get('langcode'), @@ -231,7 +233,7 @@ function testLanguage() { ); // Test imported configuration with explicit language code. - $data = $this->container->get('config.storage.installer')->read('config_test.dynamic.dotted.french'); + $data = $storage->read('config_test.dynamic.dotted.french'); $this->assertEqual($data['langcode'], 'fr'); $this->assertEqual( $this->config('config_test.dynamic.dotted.french')->get('langcode'), diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 237ef21..8329ce1 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -405,23 +405,30 @@ function locale_system_update(array $components) { // Skip running the translation imports if in the installer, // because it would break out of the installer flow. We have // built-in support for translation imports in the installer. - if (!drupal_installation_attempted() && locale_translatable_language_list() && \Drupal::config('locale.settings')->get('translation.import_enabled')) { - module_load_include('compare.inc', 'locale'); - - // Update the list of translatable projects and start the import batch. - // Only when new projects are added the update batch will be triggered. Not - // each enabled module will introduce a new project. E.g. sub modules. - $projects = array_keys(locale_translation_build_projects()); - if ($list = array_intersect($list, $projects)) { - module_load_include('fetch.inc', 'locale'); - // Get translation status of the projects, download and update - // translations. - $options = _locale_translation_default_update_options(); - $batch = locale_translation_batch_update_build($list, array(), $options); - batch_set($batch); + if (!drupal_installation_attempted() && locale_translatable_language_list()) { + if (\Drupal::config('locale.settings')->get('translation.import_enabled')) { + module_load_include('compare.inc', 'locale'); + + // Update the list of translatable projects and start the import batch. + // Only when new projects are added the update batch will be triggered. + // Not each enabled module will introduce a new project. E.g. sub modules. + $projects = array_keys(locale_translation_build_projects()); + if ($list = array_intersect($list, $projects)) { + module_load_include('fetch.inc', 'locale'); + // Get translation status of the projects, download and update + // translations. + $options = _locale_translation_default_update_options(); + $batch = locale_translation_batch_update_build($list, array(), $options); + batch_set($batch); + } } + + // Construct a batch to update configuration for all components. Installing + // this component may have installed configuration from any number of other + // components. Do this even if import is not enabled because parsing new + // configuration may expose new source strings. \Drupal::moduleHandler()->loadInclude('locale', 'bulk.inc'); - if ($batch = locale_config_batch_update_components(array(), array(), $components)) { + if ($batch = locale_config_batch_update_components(array())) { batch_set($batch); } } diff --git a/core/modules/locale/locale.services.yml b/core/modules/locale/locale.services.yml index 284878b..d9a2258 100644 --- a/core/modules/locale/locale.services.yml +++ b/core/modules/locale/locale.services.yml @@ -1,7 +1,11 @@ services: + locale.default.config.storage: + class: Drupal\locale\LocaleDefaultConfigStorage + arguments: ['@config.storage', '@language_manager'] + public: false locale.config_manager: class: Drupal\locale\LocaleConfigManager - arguments: ['@config.storage', '@config.storage.installer', '@locale.storage', '@config.factory', '@config.typed', '@language_manager'] + arguments: ['@config.storage', '@locale.storage', '@config.factory', '@config.typed', '@language_manager', '@locale.default.config.storage'] locale.storage: class: Drupal\locale\StringDatabaseStorage arguments: ['@database'] diff --git a/core/modules/locale/src/LocaleConfigManager.php b/core/modules/locale/src/LocaleConfigManager.php index da21bca..d96c758 100644 --- a/core/modules/locale/src/LocaleConfigManager.php +++ b/core/modules/locale/src/LocaleConfigManager.php @@ -9,6 +9,7 @@ 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; @@ -45,13 +46,6 @@ class LocaleConfigManager { protected $configStorage; /** - * The storage instance for reading default configuration data. - * - * @var \Drupal\Core\Config\StorageInterface - */ - protected $installStorage; - - /** * The string storage for reading and writing translations. * * @var \Drupal\locale\StringStorageInterface; @@ -96,13 +90,17 @@ class LocaleConfigManager { 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\Core\Config\StorageInterface $install_storage - * The storage object to use for reading default configuration - * data. * @param \Drupal\locale\StringStorageInterface $locale_storage * The locale storage to use for reading string translations. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory @@ -111,14 +109,16 @@ class LocaleConfigManager { * 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, StorageInterface $install_storage, StringStorageInterface $locale_storage, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config, ConfigurableLanguageManagerInterface $language_manager) { + 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->installStorage = $install_storage; $this->localeStorage = $locale_storage; $this->configFactory = $config_factory; $this->typedConfigManager = $typed_config; $this->languageManager = $language_manager; + $this->defaultConfigStorage = $default_config_storage; } /** @@ -133,7 +133,7 @@ public function __construct(StorageInterface $config_storage, StorageInterface $ public function getTranslatableDefaultConfig($name) { if ($this->isSupported($name)) { // Create typed configuration wrapper based on install storage data. - $data = $this->installStorageRead($name); + $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); @@ -293,12 +293,12 @@ public function getComponentNames(array $components = array()) { foreach ($components as $type => $list) { // InstallStorage::getComponentNames returns a list of folders keyed by // config name. - $names = array_merge($names, $this->installStorageComponents($type, $list)); + $names = array_merge($names, $this->defaultConfigStorage->getComponentNames($type, $list)); } return $names; } else { - return $this->installStorageAll(); + return $this->defaultConfigStorage->listAll(); } } @@ -474,7 +474,7 @@ public function hasTranslation($name, $langcode) { * configuration exists. */ public function getDefaultConfigLangcode($name) { - $shipped = $this->installStorageRead($name); + $shipped = $this->defaultConfigStorage->read($name); if (!empty($shipped)) { return !empty($shipped['langcode']) ? $shipped['langcode'] : 'en'; } @@ -635,81 +635,4 @@ protected function filterOverride(array $override_data, array $translatable) { return $filtered_data; } - /** - * 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. - */ - protected function installStorageRead($name) { - if ($this->installStorage->exists($name)) { - return $this->installStorage->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. - */ - protected function installStorageAll() { - $languages = $this->predefinedConfiguredLanguages(); - return array_unique(array_merge($this->installStorage->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. - */ - protected function installStorageComponents($type, array $list) { - $names = array_keys($this->installStorage->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); - } - } diff --git a/core/modules/locale/src/LocaleDefaultConfigStorage.php b/core/modules/locale/src/LocaleDefaultConfigStorage.php new file mode 100644 index 0000000..5828f61 --- /dev/null +++ b/core/modules/locale/src/LocaleDefaultConfigStorage.php @@ -0,0 +1,165 @@ +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); + } + +} + diff --git a/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php b/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php index 24017fd..4564a5a 100644 --- a/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php +++ b/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php @@ -22,7 +22,7 @@ class LocaleConfigSubscriberTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['language', 'locale']; + public static $modules = ['language', 'locale', 'system']; /** * The configurable language manager used in this test. @@ -61,6 +61,7 @@ protected function setUp() { $this->setUpDefaultLanguage(); $this->installSchema('locale', ['locales_source', 'locales_target', 'locales_location']); + $this->installSchema('system', ['queue']); $this->setupLanguages(); diff --git a/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php b/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php index 4535b9b..f0bf723 100644 --- a/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php +++ b/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php @@ -19,6 +19,13 @@ class LocaleConfigTranslationTest extends WebTestBase { /** + * The language code used. + * + * @var string + */ + protected $langcode; + + /** * Modules to enable. * * @var array @@ -38,28 +45,28 @@ protected function setUp() { $this->config('locale.settings') ->set('translation.import_enabled', TRUE) ->save(); - } - /** - * Tests basic configuration translation. - */ - public function testConfigTranslation() { // Add custom language. - $langcode = 'xx'; + $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' => $langcode, + '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 = array("prefix[$langcode]" => $langcode); + $edit = array("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')); @@ -69,7 +76,7 @@ public function testConfigTranslation() { $message = $this->randomMachineName(20); $search = array( 'string' => $string->source, - 'langcode' => $langcode, + 'langcode' => $this->langcode, 'translation' => 'all', ); $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter')); @@ -82,7 +89,7 @@ public function testConfigTranslation() { $this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations')); // Get translation and check we've only got the message. - $translation = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'system.maintenance')->get(); + $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); @@ -93,7 +100,7 @@ public function testConfigTranslation() { // Translate using the UI so configuration is refreshed. $search = array( 'string' => $string->source, - 'langcode' => $langcode, + 'langcode' => $this->langcode, 'translation' => 'all', ); $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter')); @@ -105,12 +112,12 @@ public function testConfigTranslation() { ); $this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations')); - $translation = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'core.date_format.medium')->get(); + $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, $langcode); + $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. @@ -127,7 +134,7 @@ public function testConfigTranslation() { $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(array('language' => $langcode, 'type' => 'configuration', 'name' => 'image.style.medium')); + $translations = $this->storage->getTranslations(array('language' => $this->langcode, 'type' => 'configuration', 'name' => 'image.style.medium')); $this->assertEqual(count($translations), 1); $translation = reset($translations); $this->assertEqual($translation->source, $string->source); @@ -137,7 +144,7 @@ public function testConfigTranslation() { $image_style_label = $this->randomMachineName(20); $search = array( 'string' => $string->source, - 'langcode' => $langcode, + 'langcode' => $this->langcode, 'translation' => 'all', ); $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter')); @@ -149,12 +156,12 @@ public function testConfigTranslation() { $this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations')); // Check the right single translation has been created. - $translations = $this->storage->getTranslations(array('language' => $langcode, 'type' => 'configuration', 'name' => 'image.style.medium')); + $translations = $this->storage->getTranslations(array('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($langcode, 'image.style.medium')->get(); + $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. @@ -169,7 +176,7 @@ public function testConfigTranslation() { $category_label = $this->randomMachineName(20); $search = array( 'string' => 'Website feedback', - 'langcode' => $langcode, + 'langcode' => $this->langcode, 'translation' => 'all', ); $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter')); @@ -183,7 +190,7 @@ public function testConfigTranslation() { // 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($langcode . '/contact/feedback'); + $this->drupalGet($this->langcode . '/contact/feedback'); $this->assertText($category_label); // Check if the UI does not show the translated String. @@ -191,4 +198,54 @@ public function testConfigTranslation() { $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', array('modules[Core][node][enable]' => "1"), t('Save configuration')); + $this->drupalPostForm(NULL, array(), t('Continue')); + $this->rebuildContainer(); + $this->assertNodeConfig(TRUE, FALSE); + // Enable the views module (which node provides some optional config for). + $this->drupalPostForm('admin/modules', array('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(array('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(array('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.'); + } + } + }