Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,8 @@
langcode: en
status: true
dependencies: { }
id: en
label: English
direction: ltr
weight: 0
locked: false

View file

@ -0,0 +1,8 @@
langcode: en
status: true
dependencies: { }
id: und
label: 'Not specified'
direction: ltr
weight: 2
locked: true

View file

@ -0,0 +1,8 @@
langcode: en
status: true
dependencies: { }
id: zxx
label: 'Not applicable'
direction: ltr
weight: 3
locked: true

View file

@ -0,0 +1,13 @@
# Browsers use different language codes to refer to the same languages,
# these defaults handles the most common cases.
map:
no: 'nb' # Norwegian
pt: 'pt-pt' # Portuguese
zh: 'zh-hans' # Default Chinese to simplified script
zh-tw: 'zh-hant' # Taiwan Chinese in traditional script
zh-hk: 'zh-hant' # Hong Kong Chinese in traditional script
zh-mo: 'zh-hant' # Macao Chinese in traditional script
zh-cht: 'zh-hant' # traditional Chinese
zh-cn: 'zh-hans' # PRC Mainland Chinese in simplified script
zh-sg: 'zh-hans' # Singapore Chinese in simplified script
zh-chs: 'zh-hans' # simplified Chinese

View file

@ -0,0 +1,9 @@
session:
parameter: language
url:
source: path_prefix
prefixes:
en: ''
domains:
en: ''
selected_langcode: site_default

View file

@ -0,0 +1,17 @@
all:
- language_interface
- language_content
- language_url
configurable:
- language_interface
negotiation:
language_content:
enabled:
language-interface: 0
language_url:
enabled:
language-url: 0
language-url-fallback: 1
language_interface:
enabled:
language-url: 0

View file

@ -0,0 +1,29 @@
langcode: en
status: true
dependencies: { }
id: language-add
label: 'Adding languages'
module: language
routes:
- route_name: language.add
tips:
language-add-overview:
id: language-add-overview
plugin: text
label: 'Adding languages'
body: '<p>This page provides the ability to add common languages to your site.</p><p>If the desired language is not available, you can add a custom language.</p>'
weight: 1
language-add-choose:
id: language-add-choose
plugin: text
label: 'Select language'
body: '<p>Choose a language from the list, or choose "Custom language..." at the end of the list.</p><p>Click the "Add language" button when you are done choosing your language.</p><p>When adding a custom language, you will get an additional form where you can provide the name, code, and direction of the language.</p>'
weight: 2
attributes:
data-id: edit-predefined-langcode
language-add-continue:
id: language-add-continue
plugin: text
label: 'Continuing on'
body: '<p>Now that you have an overview of the "Add languages" feature, you can continue by:<ul><li>Adding a language</li><li>Adding a custom language</li><li><a href="[site:url]admin/config/regional/language">Viewing configured languages</a></li></ul></p>'
weight: 3

View file

@ -0,0 +1,45 @@
langcode: en
status: true
dependencies: { }
id: language-edit
label: 'Editing languages'
module: language
routes:
- route_name: entity.configurable_language.edit_form
tips:
language-edit-overview:
id: language-edit-overview
plugin: text
label: 'Editing languages'
body: '<p>This page provides the ability to edit a language on your site, including custom languages.</p>'
weight: 1
language-edit-langcode:
id: language-edit-langcode
plugin: text
label: 'Language code'
body: '<p>You cannot change the code of a language on the site, since it is used by the system to keep track of the language.</p>'
weight: 2
attributes:
data-id: edit-langcode-view
language-edit-label:
id: language-edit-label
plugin: text
label: 'Language name'
body: '<p>The language name is used throughout the site for all users and is written in English. Names of built-in languages can be translated using the Interface Translation module, and names of both built-in and custom languages can be translated using the Configuration Translation module.</p>'
weight: 3
attributes:
data-id: edit-label
language-edit-direction:
id: language-edit-direction
plugin: text
label: 'Language direction'
body: '<p>Choose if the language is a "Left to right" or "Right to left" language.</p><p>Note that not all themes support "Right to left" layouts, so test your theme if you are using "Right to left".</p>'
weight: 4
attributes:
data-id: edit-direction--wrapper--description
language-edit-continue:
id: language-edit-continue
plugin: text
label: 'Continuing on'
body: '<p>Now that you have an overview of the "Edit language" feature, you can continue by:<ul><li>Editing a language</li><li><a href="[site:url]admin/config/regional/language">Viewing configured languages</a></li></ul></p>'
weight: 5

View file

@ -0,0 +1,53 @@
langcode: en
status: true
dependencies: { }
id: language
label: Language
module: language
routes:
- route_name: entity.configurable_language.collection
tips:
language-overview:
id: language-overview
plugin: text
label: Languages
body: '<p>The "Languages" page allows you to add, edit, delete, and reorder languages for the site.</p>'
weight: 1
language-add:
id: language-add
plugin: text
label: 'Adding languages'
body: '<p>To add more languages to your site, click the "Add language" button.</p><p>Added languages will be displayed in the language list and can then be edited or deleted.</p>'
weight: 2
attributes:
data-class: button-action
language-reorder:
id: language-reorder
plugin: text
label: 'Reordering languages'
body: '<p>To reorder the languages on your site, use the drag icons next to each language.</p><p>The order shown here is the display order for language lists on the site such as in the language switcher blocks provided by the Interface Translation and Content Translation modules.</p><p>When you are done with reordering the languages, click the "Save configuration" button for the changes to take effect.</p>'
weight: 3
attributes:
data-class: draggable
language-default:
id: language-default
plugin: text
label: 'Set a language as default'
body: '<p>You can change the default language of the site by choosing one of your configured languages as default. The site will use the default language in situations where no choice is made but a language should be set, for example as the language of the displayed interface.</p>'
weight: 4
attributes:
data-class: js-form-item-site-default-language
language-operations:
id: language-operations
plugin: text
label: 'Modifying languages'
body: '<p>Operations are provided for editing and deleting your languages.</p><p>You can edit the name and the direction of the language.</p><p>Deleted languages can be added back at a later time. Deleting a language will remove all interface translations associated with it, and content in this language will be set to be language neutral. Note that you cannot delete the default language of the site.</p>'
weight: 5
attributes:
data-class: dropbutton-wrapper
language-continue:
id: language-continue
plugin: text
label: 'Continuing on'
body: '<p>Now that you have an overview of the "Languages" page, you can continue by:<ul><li><a href="[site:url]admin/config/regional/language/add">Adding a language</a></li><li>Reordering languages</li><li>Editing a language</li><li>Deleting a language</li></ul></p>'
weight: 6

View file

@ -0,0 +1,133 @@
# Schema for the configuration files of the Language module.
language_type_negotiation:
type: mapping
label: 'Language negotiation per type setting'
mapping:
enabled:
type: sequence
label: 'Enabled negotiators'
sequence:
type: integer
label: Weight
method_weights:
type: sequence
label: 'Negotiator weights'
sequence:
type: integer
label: Weight
language.types:
type: config_object
label: 'Language types'
mapping:
all:
type: sequence
label: 'All language types'
sequence:
type: string
label: 'Language type'
configurable:
type: sequence
label: 'Configurable language types'
sequence:
type: string
label: 'Language type'
negotiation:
type: sequence
label: 'Language negotiation per type settings'
sequence:
type: language_type_negotiation
label: 'Language negotiation per type setting'
language.negotiation:
type: config_object
label: 'Language detection methods'
mapping:
session:
type: mapping
label: 'Session'
mapping:
parameter:
type: string
label: 'Request/session parameter'
url:
type: mapping
label: 'Language from the URL (Path prefix or domain).'
mapping:
source:
type: string
label: 'Part of the URL that determines language'
prefixes:
type: sequence
label: 'Path prefix configuration'
sequence:
type: string
label: 'Path prefix'
domains:
type: sequence
label: 'Domain configuration'
sequence:
type: string
label: 'Domain'
selected_langcode:
type: string
label: 'Selected language'
language.mappings:
type: config_object
label: 'Language mapping'
mapping:
map:
type: sequence
sequence:
type: string
label: 'Language'
language.entity.*:
type: config_entity
label: 'Language'
mapping:
id:
type: string
label: 'ID'
label:
type: label
label: 'Label'
direction:
type: string
label: 'Direction'
weight:
type: integer
label: 'Weight'
locked:
type: boolean
label: 'Locked'
language.content_settings.*.*:
type: config_entity
label: 'Content Language Settings'
mapping:
id:
type: string
label: 'ID'
target_entity_type_id:
type: string
label: 'Entity Type ID'
target_bundle:
type: string
label: 'Bundle'
default_langcode:
type: string
label: 'Default language'
language_alterable:
type: boolean
label: 'Allow to alter the language'
condition.plugin.language:
type: condition.plugin
mapping:
langcodes:
type: sequence
sequence:
type: string

View file

@ -0,0 +1,11 @@
/**
* @file
* Styles for the content language administration page.
*/
#language-content-settings-form table .bundle {
width: 25%;
}
#language-content-settings-form table .operations {
width: 75%;
}

View file

@ -0,0 +1,148 @@
<?php
/**
* @file
* Administration functions for language.module.
*/
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
/**
* Prepares variables for language negotiation configuration form.
*
* Default template: language-content-configuration-form.html.twig.
*
* @param array $variables
* An associative array containing:
* - form: A render element representing the form.
*/
function template_preprocess_language_negotiation_configure_form(&$variables) {
$form =& $variables['form'];
$variables['language_types'] = array();
foreach ($form['#language_types'] as $type) {
$header = array(
t('Detection method'),
t('Description'),
t('Enabled'),
t('Weight'),
);
// If there is at least one operation enabled show the operation column.
if ($form[$type]['#show_operations']) {
$header[] = t('Operations');
}
$table = array(
'#type' => 'table',
'#header' => $header,
'#attributes' => array('id' => 'language-negotiation-methods-' . $type),
'#tabledrag' => array(
array(
'action' => 'order',
'relationship' => 'sibling',
'group' => 'language-method-weight-' . $type,
),
),
);
foreach ($form[$type]['title'] as $id => $element) {
// Do not take form control structures.
if (is_array($element) && Element::child($id)) {
$table[$id]['#attributes']['class'][] = 'draggable';
$table[$id]['#weight'] = $element['#weight'];
$table[$id]['title'] = array(
'#prefix' => '<strong>',
$form[$type]['title'][$id],
'#suffix' => '</strong>',
);
$table[$id]['description'] = $form[$type]['description'][$id];
$table[$id]['enabled'] = $form[$type]['enabled'][$id];
$table[$id]['weight'] = $form[$type]['weight'][$id];
if ($form[$type]['#show_operations']) {
$table[$id]['operation'] = $form[$type]['operation'][$id];
}
// Unset to prevent rendering along with children.
unset($form[$type]['title'][$id]);
unset($form[$type]['description'][$id]);
unset($form[$type]['enabled'][$id]);
unset($form[$type]['weight'][$id]);
unset($form[$type]['operation'][$id]);
}
}
// Unset configurable to prevent rendering twice with children.
$configurable = isset($form[$type]['configurable']) ? $form[$type]['configurable'] : NULL;
unset($form[$type]['configurable']);
$variables['language_types'][] = array(
'type' => $type,
'title' => $form[$type]['#title'],
'description' => $form[$type]['#description'],
'configurable' => $configurable,
'table' => $table,
'children' => $form[$type],
'attributes' => new Attribute(),
);
// Prevent the type from rendering with the remaining form child elements.
unset($form[$type]);
}
$variables['children'] = $form;
}
/**
* Prepares variables for language content settings table templates.
*
* Default template: language-content-settings-table.html.twig.
*
* @param array $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #bundle_label, #title.
*/
function template_preprocess_language_content_settings_table(&$variables) {
// Add a render element representing the bundle language settings table.
$element = $variables['element'];
$header = array(
array(
'data' => $element['#bundle_label'],
'class' => array('bundle'),
),
array(
'data' => t('Configuration'),
'class' => array('operations'),
),
);
$rows = array();
foreach (Element::children($element) as $bundle) {
$rows[$bundle] = array(
'data' => array(
array(
'data' => array(
'#prefix' => '<label>',
'#suffix' => '</label>',
'#plain_text' => $element[$bundle]['settings']['#label'],
),
'class' => array('bundle'),
),
array(
'data' => $element[$bundle]['settings'],
'class' => array('operations'),
),
),
'class' => array('bundle-settings'),
);
}
$variables['title'] = $element['#title'];
$variables['build'] = array(
'#header' => $header,
'#rows' => $rows,
'#type' => 'table',
);
}

View file

@ -0,0 +1,43 @@
/**
* @file
* Language admin behavior.
*/
(function ($, Drupal) {
'use strict';
/**
* Makes language negotiation inherit user interface negotiation.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attach behavior to language negotiation admin user interface.
*/
Drupal.behaviors.negotiationLanguage = {
attach: function () {
var $configForm = $('#language-negotiation-configure-form');
var inputSelector = 'input[name$="[configurable]"]';
// Given a customization checkbox derive the language type being changed.
function toggleTable(checkbox) {
var $checkbox = $(checkbox);
// Get the language detection type such as Interface text language
// detection or Content language detection.
$checkbox.closest('.table-language-group')
.find('table, .tabledrag-toggle-weight')
.toggle($checkbox.prop('checked'));
}
// Bind hide/show and rearrange customization checkboxes.
$configForm.once('negotiation-language-admin-bind').on('change', inputSelector, function (event) {
toggleTable(event.target);
});
// Initially, hide language detection types that are not customized.
$configForm.find(inputSelector + ':not(:checked)').each(function (index, element) {
toggleTable(element);
});
}
};
})(jQuery, Drupal);

View file

@ -0,0 +1,116 @@
<?php
/**
* @file
* Hooks provided by the Language module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Define language types.
*
* @return array
* An associative array of language type definitions. The keys are the
* identifiers, which are also used as names for global variables representing
* the types in the bootstrap phase. The values are associative arrays that
* may contain the following elements:
* - name: The human-readable language type identifier.
* - description: A description of the language type.
* - locked: A boolean indicating if the user can choose whether to configure
* the language type or not using the UI.
* - fixed: A fixed array of language negotiation method identifiers to use to
* initialize this language. If locked is set to TRUE and fixed is set, it
* will always use the specified methods in the given priority order. If not
* present and locked is TRUE then language-interface will be
* used.
*
* @todo Rename the 'fixed' key to something more meaningful, for instance
* 'negotiation settings'. See https://www.drupal.org/node/2166879.
*
* @see hook_language_types_info_alter()
* @ingroup language_negotiation
*/
function hook_language_types_info() {
return array(
'custom_language_type' => array(
'name' => t('Custom language'),
'description' => t('A custom language type.'),
'locked' => FALSE,
),
'fixed_custom_language_type' => array(
'locked' => TRUE,
'fixed' => array('custom_language_negotiation_method'),
),
);
}
/**
* Perform alterations on language types.
*
* @param array $language_types
* Array of language type definitions.
*
* @see hook_language_types_info()
* @ingroup language_negotiation
*/
function hook_language_types_info_alter(array &$language_types) {
if (isset($language_types['custom_language_type'])) {
$language_types['custom_language_type_custom']['description'] = t('A far better description.');
}
}
/**
* Perform alterations on language negotiation methods.
*
* @param array $negotiation_info
* Array of language negotiation method definitions.
*
* @ingroup language_negotiation
*/
function hook_language_negotiation_info_alter(array &$negotiation_info) {
if (isset($negotiation_info['custom_language_method'])) {
$negotiation_info['custom_language_method']['config'] = 'admin/config/regional/language/detection/custom-language-method';
}
}
/**
* Allow modules to alter the language fallback candidates.
*
* @param array $candidates
* An array of language codes whose order will determine the language fallback
* order.
* @param array $context
* A language fallback context.
*
* @see \Drupal\Core\Language\LanguageManagerInterface::getFallbackCandidates()
*/
function hook_language_fallback_candidates_alter(array &$candidates, array $context) {
$candidates = array_reverse($candidates);
}
/**
* Allow modules to alter the fallback candidates for specific operations.
*
* @param array $candidates
* An array of language codes whose order will determine the language fallback
* order.
* @param array $context
* A language fallback context.
*
* @see \Drupal\Core\Language\LanguageManagerInterface::getFallbackCandidates()
*/
function hook_language_fallback_candidates_OPERATION_alter(array &$candidates, array $context) {
// We know that the current OPERATION deals with entities so no need to check
// here.
if ($context['data']->getEntityTypeId() == 'node') {
$candidates = array_reverse($candidates);
}
}
/**
* @} End of "addtogroup hooks".
*/

View file

@ -0,0 +1,7 @@
name: Language
type: module
description: 'Allows users to configure languages and apply them to content.'
package: Multilingual
version: VERSION
core: 8.x
configure: entity.configurable_language.collection

View file

@ -0,0 +1,13 @@
<?php
/**
* @file
* Update functions for Language module.
*/
/**
* Rebuild the container as services changed.
*/
function language_update_8001() {
\Drupal::service('kernel')->invalidateContainer();
}

View file

@ -0,0 +1,11 @@
drupal.language.admin:
version: VERSION
js:
language.admin.js: {}
css:
theme:
css/language.admin.css: {}
dependencies:
- core/jquery
- core/drupal
- core/jquery.once

View file

@ -0,0 +1,5 @@
language_add_local_action:
route_name: language.add
title: 'Add language'
appears_on:
- entity.configurable_language.collection

View file

@ -0,0 +1,11 @@
entity.configurable_language.collection:
title: Languages
description: 'Configure languages for content, interface, and configuration.'
route_name: entity.configurable_language.collection
parent: system.admin_config_regional
language.content_settings_page:
title: 'Content language'
description: 'Configure language support for content.'
route_name: language.content_settings_page
parent: system.admin_config_regional
weight: 10

View file

@ -0,0 +1,13 @@
entity.configurable_language.collection:
title: 'List'
route_name: entity.configurable_language.collection
base_route: entity.configurable_language.collection
language.negotiation:
title: 'Detection and selection'
route_name: language.negotiation
base_route: entity.configurable_language.collection
entity.configurable_language.edit_form:
title: 'Edit'
route_name: entity.configurable_language.edit_form
base_route: entity.configurable_language.edit_form

View file

@ -0,0 +1,497 @@
<?php
/**
* @file
* Add language handling functionality to Drupal.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\language\ConfigurableLanguageInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrlFallback;
/**
* Implements hook_help().
*/
function language_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.language':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Language module allows you to configure the languages used on your site, and provides information for the <a href=":content">Content Translation</a>, <a href=":interface">Interface Translation</a>, and <a href=":configuration">Configuration Translation</a> modules, if they are enabled. For more information, see the <a href=":doc_url">online documentation for the Language module</a>.', array(':doc_url' => 'https://www.drupal.org/documentation/modules/language', ':content' => (\Drupal::moduleHandler()->moduleExists('content_translation')) ? \Drupal::url('help.page', array('name' => 'content_translation')) : '#', ':interface' => (\Drupal::moduleHandler()->moduleExists('locale')) ? \Drupal::url('help.page', array('name' => 'locale')) : '#', ':configuration' => (\Drupal::moduleHandler()->moduleExists('config_translation')) ? \Drupal::url('help.page', array('name' => 'config_translation')) : '#')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Adding languages') . '</dt>';
$output .= '<dd>' . t('You can add languages on the <a href=":language_list">Languages</a> page by selecting <em>Add language</em> and choosing a language from the drop-down menu. This language is then displayed in the languages list, where it can be configured further. If the <a href=":interface">Interface translation module</a> is enabled, and the <em>translation server</em> is set as a translation source, then the interface translation for this language is automatically downloaded as well.', array(':language_list' => \Drupal::url('entity.configurable_language.collection'), ':interface' => (\Drupal::moduleHandler()->moduleExists('locale')) ? \Drupal::url('help.page', array('name' => 'locale')) : '#')) . '</dd>';
$output .= '<dt>' . t('Adding custom languages') . '</dt>';
$output .= '<dd>' . t('You can add a language that is not provided in the drop-down list by choosing <em>Custom language</em> at the end of the list. You then have to configure its language code, name, and direction in the form provided.') . '</dd>';
$output .= '<dt>' . t('Configuring content languages') . '</dt>';
$output .= '<dd>' . t('By default, content is created in the site\'s default language and no language selector is displayed on content creation pages. On the <a href=":content_language">Content language</a> page you can customize the language configuration for any supported content entity on your site (for example for content types or menu links). After choosing an entity, you are provided with a drop-down menu to set the default language and a check-box to display language selectors.', array(':content_language' => \Drupal::url('language.content_settings_page'))) . '</dd>';
$output .= '<dt>' . t('Adding a language switcher block') . '</dt>';
$output .= '<dd>' . t('If the Block module is enabled, then you can add a language switcher block on the <a href=":blocks">Block layout</a> page to allow users to switch between languages.', array(':blocks' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#')) . '</dd>';
$output .= '<dt>' . t('Making a block visible per language') . '</dt>';
$output .= '<dd>' . t('If the Block module is enabled, then the Language module allows you to set the visibility of a block based on selected languages on the <a href=":blocks">Block layout</a> page.', array(':blocks' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#')) . '</dd>';
$output .= '<dt>' . t('Choosing user languages') . '</dt>';
$output .= '<dd>' . t('Users can choose a <em>Site language</em> on their profile page. This language is used for email messages, and can be used by modules to determine a user\'s language. It can also be used for interface text, if the <em>User</em> method is enabled as a <em>Detection and selection</em> method (see below). Administrative users can choose a separate <em>Administration pages language</em> for the interface text on administration pages. This configuration is only available on the user\'s profile page if the <em>Account administration pages</em> method is enabled (see below).') . '</dd>';
$output .= '<dt>' . t('Language detection and selection') . '</dt>';
$output .= '<dd>' . t('The <a href=":detection">Detection and selection</a> page provides several methods for deciding which language is used for displaying interface text. When a method detects and selects an interface language, then the following methods in the list are not applied. You can order them by importance, with your preferred method at the top of the list, followed by one or several fall-back methods.', array(':detection' => \Drupal::url('language.negotiation')));
$output .= '<ul><li>' . t('<em>URL</em> sets the interface language based on a path prefix or domain (for example specifying <em>de</em> for German would result in URLs like <em>example.com/de/contact</em>). The default language does not require a path prefix, but can have one assigned as well. If the language detection is done by domain name, a domain needs to be specified for each language.') . '</li>';
$output .= '<li>' . t('<em>Session</em> determines the interface language from a request or session parameter (for example <em>example.com?language=de</em> would set the interface language to German based on the use of <em>de</em> as the <em>language</em> parameter).') . '</li>';
$output .= '<li>' . t('<em>User</em> follows the language configuration set on the user\'s profile page.') . '</li>';
$output .= '<li>' . t('<em>Browser</em> sets the interface language based on the browser\'s language settings. Since browsers use different language codes to refer to the same languages, you can add and edit languages codes to map the browser language codes to the <a href=":language_list">language codes</a> used on your site.', array(':language_list' => \Drupal::url('entity.configurable_language.collection'))) . '</li>';
$output .= '<li>' . t('<em>Account administration pages</em> follows the configuration set as <em>Administration pages language</em> on the profile page of an administrative user. This method is similar to the <em>User</em> method, but only sets the interface text language on administration pages, independent of the interface text language on other pages.') . '</li>';
$output .= '<li>' . t('<em>Selected language</em> allows you to specify the site\'s default language or a specific language as the fall-back language. This method should be listed last.') . '</li></ul></dd>';
$output .= '</dl>';
return $output;
case 'entity.configurable_language.collection':
$output = '<p>' . t('Reorder the configured languages to set their order in the language switcher block and, when editing content, in the list of selectable languages. This ordering does not impact <a href=":detection">detection and selection</a>.', array(':detection' => \Drupal::url('language.negotiation'))) . '</p>';
$output .= '<p>' . t('The site default language can also be set. It is not recommended to change the default language on a working site. <a href=":language-detection">Configure the Selected language</a> setting on the detection and selection page to change the fallback language for language selection.', array(':language-detection' => \Drupal::url('language.negotiation'))) . '</p>';
return $output;
case 'language.add':
return '<p>' . t('Add a language to be supported by your site. If your desired language is not available, pick <em>Custom language...</em> at the end and provide a language code and other details manually.') . '</p>';
case 'language.negotiation':
$output = '<p>' . t('Define how to decide which language is used to display page elements (primarily text provided by modules, such as field labels and help text). This decision is made by evaluating a series of detection methods for languages; the first detection method that gets a result will determine which language is used for that type of text. Be aware that some language detection methods are unreliable under certain conditions, such as browser detection when page-caching is enabled and a user is not currently logged in. Define the order of evaluation of language detection methods on this page. The default language can be changed in the <a href=":admin-change-language">list of languages</a>.', array(':admin-change-language' => \Drupal::url('entity.configurable_language.collection'))) . '</p>';
return $output;
case 'language.negotiation_session':
$output = '<p>' . t('Determine the language from a request/session parameter. Example: "http://example.com?language=de" sets language to German based on the use of "de" within the "language" parameter.') . '</p>';
return $output;
case 'language.negotiation_browser':
$output = '<p>' . t('Browsers use different language codes to refer to the same languages. Internally, a best effort is made to determine the correct language based on the code that the browser sends. You can add and edit additional mappings from browser language codes to <a href=":configure-languages">site languages</a>.', array(':configure-languages' => \Drupal::url('entity.configurable_language.collection'))) . '</p>';
return $output;
case 'language.negotiation_selected':
$output = '<p>' . t('Changing the selected language here (and leaving this option as the last among the detection and selection options) is the easiest way to change the fallback language for the website, if you need to change how your site works by default (e.g., when using an empty path prefix or using the default domain). <a href=":admin-change-language">Changing the site\'s default language</a> itself might have other undesired side effects.', array(':admin-change-language' => \Drupal::url('entity.configurable_language.collection'))) . '</p>';
return $output;
case 'entity.block.edit_form':
if (($block = $route_match->getParameter('block')) && $block->getPluginId() == 'language_block:language_interface') {
return '<p>' . t('With multiple languages configured, registered users can select their preferred language and authors can assign a specific language to content.') . '</p>';
}
break;
case 'block.admin_add':
if ($route_match->getParameter('plugin_id') == 'language_block:language_interface') {
return '<p>' . t('With multiple languages configured, registered users can select their preferred language and authors can assign a specific language to content.') . '</p>';
}
break;
case 'language.content_settings_page':
return t('Change language settings for <em>content types</em>, <em>taxonomy vocabularies</em>, <em>user profiles</em>, or any other supported element on your site. By default, language settings hide the language selector and the language is the site\'s default language.');
}
}
/**
* Implements hook_theme().
*/
function language_theme() {
return array(
'language_negotiation_configure_form' => array(
'render element' => 'form',
'file' => 'language.admin.inc',
),
'language_content_settings_table' => array(
'render element' => 'element',
'file' => 'language.admin.inc',
),
);
}
/**
* Implements hook_element_info_alter().
*
* @see \Drupal\Core\Render\Element\LanguageSelect
* @see \Drupal\Core\Render\Element\Select
*/
function language_element_info_alter(&$type) {
// Alter the language_select element so that it will be rendered like a select
// field.
if (isset($type['language_select'])) {
if (!isset($type['language_select']['#process'])) {
$type['language_select']['#process'] = array();
}
if (!isset($type['language_select']['#theme_wrappers'])) {
$type['language_select']['#theme_wrappers'] = array();
}
$type['language_select']['#process'] = array_merge($type['language_select']['#process'], array(
'language_process_language_select',
array('Drupal\Core\Render\Element\Select', 'processSelect'),
array('Drupal\Core\Render\Element\RenderElement', 'processAjaxForm'),
));
$type['language_select']['#theme'] = 'select';
$type['language_select']['#theme_wrappers'] = array_merge($type['language_select']['#theme_wrappers'], array('form_element'));
$type['language_select']['#languages'] = LanguageInterface::STATE_CONFIGURABLE;
$type['language_select']['#multiple'] = FALSE;
}
}
/**
* Processes a language select list form element.
*
* @param array $element
* The form element to process.
*
* @return array $element
* The processed form element.
*/
function language_process_language_select($element) {
// Don't set the options if another module (translation for example) already
// set the options.
if (!isset($element['#options'])) {
$element['#options'] = array();
foreach (\Drupal::languageManager()->getLanguages($element['#languages']) as $langcode => $language) {
$element['#options'][$langcode] = $language->isLocked() ? t('- @name -', array('@name' => $language->getName())) : $language->getName();
}
}
return $element;
}
/**
* Implements hook_entity_base_field_info_alter().
*/
function language_entity_base_field_info_alter(&$fields) {
foreach ($fields as $definition) {
// Set configurable form display for language fields with display options.
if ($definition->getType() == 'language') {
foreach (array('form', 'view') as $type) {
if ($definition->getDisplayOptions($type)) {
// The related configurations will be purged manually on Language
// module uninstallation. @see language_modules_uninstalled().
$definition->setDisplayConfigurable($type, TRUE);
}
}
}
}
}
/**
* Submit handler for the forms that have a language_configuration element.
*/
function language_configuration_element_submit(&$form, FormStateInterface $form_state) {
// Iterate through all the language_configuration elements and save their
// values.
// In case we are editing a bundle, we must check the new bundle name,
// because e.g. hook_ENTITY_update fired before.
if ($language = $form_state->get('language')) {
foreach ($language as $element_name => $values) {
$entity_type_id = $values['entity_type'];
$bundle = $values['bundle'];
$form_object = $form_state->getFormObject();
if ($form_object instanceof EntityFormInterface) {
/** @var EntityFormInterface $form_object */
$entity = $form_object->getEntity();
if ($entity->getEntityType()->getBundleOf()) {
$bundle = $entity->id();
$language[$element_name]['bundle'] = $bundle;
}
}
$config = ContentLanguageSettings::loadByEntityTypeBundle($entity_type_id, $bundle);
$config->setDefaultLangcode($form_state->getValue(array($element_name, 'langcode')));
$config->setLanguageAlterable($form_state->getValue(array($element_name, 'language_alterable')));
$config->save();
// Set the form_state languaged with the updated bundle.
$form_state->set('language', $language);
}
}
}
/**
* Implements hook_entity_bundle_delete().
*/
function language_entity_bundle_delete($entity_type_id, $bundle) {
// Remove the content language settings associated with the bundle.
$settings = ContentLanguageSettings::loadByEntityTypeBundle($entity_type_id, $bundle);
if (!$settings->isNew()) {
$settings->delete();
}
}
/**
* Returns the default language code assigned to an entity type and a bundle.
*
* @param string $entity_type
* The entity type.
* @param string $bundle
* The bundle name.
*
* @return string
* The language code.
*/
function language_get_default_langcode($entity_type, $bundle) {
$configuration = ContentLanguageSettings::loadByEntityTypeBundle($entity_type, $bundle);
$default_value = NULL;
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
switch ($configuration->getDefaultLangcode()) {
case LanguageInterface::LANGCODE_SITE_DEFAULT:
$default_value = \Drupal::languageManager()->getDefaultLanguage()->getId();
break;
case 'current_interface':
$default_value = $language_interface->getId();
break;
case 'authors_default':
$user = \Drupal::currentUser();
$language_code = $user->getPreferredLangcode();
if (!empty($language_code)) {
$default_value = $language_code;
}
else {
$default_value = $language_interface->getId();
}
break;
}
if ($default_value) {
return $default_value;
}
// If we still do not have a default value, just return the value stored in
// the configuration; it has to be an actual language code.
return $configuration->getDefaultLangcode();
}
/**
* Reads language prefixes and uses the langcode if no prefix is set.
*
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
* Use \Drupal::config('language.negotiation')->get('url.prefixes') instead.
*/
function language_negotiation_url_prefixes() {
return \Drupal::config('language.negotiation')->get('url.prefixes');
}
/**
* Update the list of prefixes from the installed languages.
*/
function language_negotiation_url_prefixes_update() {
$config = \Drupal::configFactory()->getEditable('language.negotiation');
$prefixes = $config->get('url.prefixes');
foreach (\Drupal::languageManager()->getLanguages() as $language) {
// The prefix for this language should be updated if it's not assigned yet
// or the prefix is set to the empty string.
if (empty($prefixes[$language->getId()])) {
// For the default language, set the prefix to the empty string,
// otherwise use the langcode.
$prefixes[$language->getId()] = $language->isDefault() ? '' : $language->getId();
}
// Otherwise we keep the configured prefix.
}
$config->set('url.prefixes', $prefixes)->save();
}
/**
* Reads language domains.
*
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
* Use \Drupal::config('language.negotiation')->get('url.domains') instead.
*/
function language_negotiation_url_domains() {
return \Drupal::config('language.negotiation')->get('url.domains');
}
/**
* Implements hook_modules_installed().
*/
function language_modules_installed($modules) {
if (!in_array('language', $modules)) {
// Since newly (un)installed modules may change the default settings for
// non-locked language types (e.g. content language), we need to resave the
// language type configuration.
/** @var \Drupal\language\LanguageNegotiatorInterface $negotiator */
$negotiator = \Drupal::service('language_negotiator');
$configurable = \Drupal::config('language.types')->get('configurable');
$negotiator->updateConfiguration($configurable);
$negotiator->purgeConfiguration();
}
else {
// In language_entity_base_field_info_alter() we are altering view/form
// display definitions to make language fields display configurable. Since
// this is not a hard dependency, and thus is not detected by the config
// system, we have to clean up the related values manually.
foreach (array('entity_view_display', 'entity_form_display') as $key) {
$displays = \Drupal::entityManager()->getStorage($key)->loadMultiple();
/** @var \Drupal\Core\Entity\Display\EntityDisplayInterface $display */
foreach ($displays as $display) {
$display->save();
}
}
}
}
/**
* Implements hook_modules_uninstalled().
*/
function language_modules_uninstalled($modules) {
language_modules_installed($modules);
}
/**
* Implements hook_ENTITY_TYPE_insert() for 'configurable_language'.
*/
function language_configurable_language_insert(ConfigurableLanguageInterface $language) {
if ($language->isLocked()) {
return;
}
// Add language to the list of language domains.
$config = \Drupal::configFactory()->getEditable('language.negotiation');
$domains = $config->get('url.domains');
$domains[$language->id()] = '';
$config->set('url.domains', $domains)->save();
}
/**
* Implements hook_ENTITY_TYPE_delete() for 'configurable_language'.
*/
function language_configurable_language_delete(ConfigurableLanguageInterface $language) {
// Remove language from language prefix list.
$config = \Drupal::configFactory()->getEditable('language.negotiation');
$prefixes = $config->get('url.prefixes');
unset($prefixes[$language->id()]);
$config->set('url.prefixes', $prefixes)->save();
// Remove language from language domain list.
$config = \Drupal::configFactory()->getEditable('language.negotiation');
$domains = $config->get('url.domains');
unset($domains[$language->id()]);
$config->set('url.domains', $domains)->save();
}
/**
* Implements hook_preprocess_HOOK() for block templates.
*/
function language_preprocess_block(&$variables) {
if ($variables['configuration']['provider'] == 'language') {
$variables['attributes']['role'] = 'navigation';
}
}
/**
* Returns language mappings between browser and Drupal language codes.
*
* @return array
* An array containing browser language codes as keys with corresponding
* Drupal language codes as values.
*/
function language_get_browser_drupal_langcode_mappings() {
$config = \Drupal::config('language.mappings');
if ($config->isNew()) {
return array();
}
return $config->get('map');
}
/**
* Implements hook_form_alter().
*/
function language_form_alter(&$form, FormStateInterface $form_state) {
// Content entity forms may have added a langcode field. But content language
// configuration should decide if it should be exposed or not in the forms.
$form_object = $form_state->getFormObject();
if ($form_object instanceof ContentEntityFormInterface && $form_object->getEntity()->getEntityType()->hasKey('langcode')) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $form_object->getEntity();
$entity_type = $entity->getEntityType();
$langcode_key = $entity_type->getKey('langcode');
if (isset($form[$langcode_key])) {
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle($entity->getEntityTypeId(), $entity->bundle());
$form[$langcode_key]['#access'] = $language_configuration->isLanguageAlterable();
}
}
}
/**
* Implements hook_field_info_alter().
*/
function language_field_info_alter(&$info) {
// Change the default behavior of language field.
$info['language']['class'] = '\Drupal\language\DefaultLanguageItem';
}
/**
* Implements hook_entity_field_access()
*/
function language_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
// Only allow edit access on a langcode field if the entity it is attached to
// is configured to have an alterable language. Also without items we can not
// decide whether or not to allow access.
if ($items && $operation == 'edit') {
// Check if we are dealing with a langcode field.
$langcode_key = $items->getEntity()->getEntityType()->getKey('langcode');
if ($field_definition->getName() == $langcode_key) {
// Grant access depending on whether the entity language can be altered.
$entity = $items->getEntity();
$config = ContentLanguageSettings::loadByEntityTypeBundle($entity->getEntityTypeId(), $entity->bundle());
return AccessResult::forbiddenIf(!$config->isLanguageAlterable());
}
}
return AccessResult::neutral();
}
/**
* Implements hook_tour_tips_alter().
*/
function language_tour_tips_alter(array &$tour_tips, EntityInterface $entity) {
foreach ($tour_tips as $tour_tip) {
if ($tour_tip->get('id') == 'language-overview') {
$additional_overview = '';
if (Drupal::service('module_handler')->moduleExists('locale')) {
$additional_overview = t("This page also provides an overview of how much of the site's interface has been translated for each configured language.");
}
else {
$additional_overview = t("If the Interface Translation module is enabled, this page will provide an overview of how much of the site's interface has been translated for each configured language.");
}
$tour_tip->set('body', $tour_tip->get('body') . '<p>' . $additional_overview . '</p>');
}
elseif ($tour_tip->get('id') == 'language-continue') {
$additional_continue = '';
$additional_modules = array();
if (!Drupal::service('module_handler')->moduleExists('locale')) {
$additional_modules[] = Drupal::service('module_handler')->getName('locale');
}
if (!Drupal::service('module_handler')->moduleExists('content_translation')) {
$additional_modules[] = Drupal::service('module_handler')->getName('content_translation');
}
if (!empty($additional_modules)) {
$additional_continue = t('Depending on your site features, additional modules that you might want to enable are:') . '<ul>';
foreach ($additional_modules as $additional_module) {
$additional_continue .= '<li>' . $additional_module . '</li>';
}
$additional_continue .= '</ul>';
}
if (!empty($additional_continue)) {
$tour_tip->set('body', $tour_tip->get('body') . '<p>' . $additional_continue . '</p>');
}
}
}
}
/**
* Implements hook_language_types_info_alter().
*
* We can't set the fixed properties in \Drupal\Core\Language\LanguageManager,
* where the rest of the properties for the default language types are defined.
* The LanguageNegation classes are only loaded when the language module is
* enabled and we can't be sure of that in the LanguageManager.
*/
function language_language_types_info_alter(array &$language_types) {
$language_types[LanguageInterface::TYPE_CONTENT]['fixed'] = [LanguageNegotiationUI::METHOD_ID];
$language_types[LanguageInterface::TYPE_URL]['fixed'] = [LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationUrlFallback::METHOD_ID];
}

View file

@ -0,0 +1,2 @@
administer languages:
title: 'Administer languages'

View file

@ -0,0 +1,87 @@
language.negotiation_url:
path: '/admin/config/regional/language/detection/url'
defaults:
_form: 'Drupal\language\Form\NegotiationUrlForm'
_title: 'URL language detection configuration'
requirements:
_permission: 'administer languages'
language.negotiation_session:
path: '/admin/config/regional/language/detection/session'
defaults:
_form: 'Drupal\language\Form\NegotiationSessionForm'
_title: 'Session language detection configuration'
requirements:
_permission: 'administer languages'
language.negotiation_selected:
path: '/admin/config/regional/language/detection/selected'
defaults:
_form: 'Drupal\language\Form\NegotiationSelectedForm'
_title: 'Selected language configuration'
requirements:
_permission: 'administer languages'
language.add:
path: '/admin/config/regional/language/add'
defaults:
_entity_form: 'configurable_language.add'
_title: 'Add language'
requirements:
_entity_create_access: 'configurable_language'
entity.configurable_language.edit_form:
path: '/admin/config/regional/language/edit/{configurable_language}'
defaults:
_entity_form: 'configurable_language.edit'
_title: 'Edit language'
requirements:
_entity_access: 'configurable_language.update'
entity.configurable_language.collection:
path: '/admin/config/regional/language'
defaults:
_entity_list: 'configurable_language'
_title: 'Languages'
requirements:
_permission: 'administer languages'
entity.configurable_language.delete_form:
path: '/admin/config/regional/language/delete/{configurable_language}'
defaults:
_entity_form: 'configurable_language.delete'
_title: 'Delete language'
requirements:
_entity_access: 'configurable_language.delete'
language.negotiation:
path: '/admin/config/regional/language/detection'
defaults:
_form: '\Drupal\language\Form\NegotiationConfigureForm'
_title: 'Detection and selection'
requirements:
_permission: 'administer languages'
language.negotiation_browser:
path: '/admin/config/regional/language/detection/browser'
defaults:
_form: '\Drupal\language\Form\NegotiationBrowserForm'
_title: 'Browser language detection configuration'
requirements:
_permission: 'administer languages'
language.negotiation_browser_delete:
path: '/admin/config/regional/language/detection/browser/delete/{browser_langcode}'
defaults:
_form: '\Drupal\language\Form\NegotiationBrowserDeleteForm'
_title: 'Delete'
requirements:
_permission: 'administer languages'
language.content_settings_page:
path: '/admin/config/regional/content-language'
defaults:
_title: 'Content language'
_form: 'Drupal\language\Form\ContentLanguageSettingsForm'
requirements:
_permission: 'administer languages'

View file

@ -0,0 +1,26 @@
services:
plugin.manager.language_negotiation_method:
class: Drupal\language\LanguageNegotiationMethodManager
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
language_negotiator:
class: Drupal\language\LanguageNegotiator
arguments: ['@language_manager', '@plugin.manager.language_negotiation_method', '@config.factory', '@settings', '@request_stack']
calls:
- [initLanguageManager]
language.config_subscriber:
class: Drupal\language\EventSubscriber\ConfigSubscriber
arguments: ['@language_manager', '@language.default', '@config.factory', '@language_negotiator']
tags:
- { name: event_subscriber }
language.config_factory_override:
class: Drupal\language\Config\LanguageConfigFactoryOverride
arguments: ['@config.storage', '@event_dispatcher', '@config.typed']
tags:
- { name: config.factory.override, priority: -254 }
- { name: event_subscriber }
language_converter:
class: Drupal\language\LanguageConverter
arguments: ['@language_manager']
tags:
- { name: paramconverter }
lazy: true

View file

@ -0,0 +1,47 @@
id: d6_language_content_settings
label: Drupal 6 language content settings
migration_tags:
- Drupal 6
source:
plugin: d6_language_content_settings
constants:
target_type: 'node'
process:
# Ignore i18n_node_options_[node_type] options not available in Drupal 8,
# i18n_required_node and i18n_newnode_current
target_bundle: type
target_entity_type_id: 'constants/target_type'
default_langcode:
-
plugin: static_map
source: language_content_type
map:
0: NULL
1: 'current_interface'
2: 'current_interface'
-
plugin: skip_on_empty
method: row
language_alterable:
plugin: static_map
source: i18n_lock_node
map:
0: true
1: false
'third_party_settings/content_translation/enabled':
plugin: static_map
source: language_content_type
map:
# In the case of being 0, it will be skipped. We are not actually setting
# a null value.
0: NULL
1: false
2: true
destination:
plugin: entity:language_content_settings
content_translation_update_definitions:
- node
migration_dependencies:
required:
- d6_node_type

View file

@ -0,0 +1,34 @@
id: d6_language_negotiation_settings
label: Language negotiation settings
migration_tags:
- Drupal 6
source:
plugin: variable
variables:
- language_negotiation
process:
session/parameter:
plugin: default_value
default_value: 'language'
selected_langcode:
plugin: default_value
default_value: 'site_default'
url/source:
plugin: static_map
source: language_negotiation
default_value: path_prefix
map:
# LANGUAGE_NEGOTIATION_NONE = 0
# LANGUAGE_NEGOTIATION_PATH_DEFAULT = 1
# LANGUAGE_NEGOTIATION_PATH = 2
# LANGUAGE_NEGOTIATION_DOMAIN = 3
0: path_prefix
1: path_prefix
2: path_prefix
3: domain
destination:
plugin: config
config_name: language.negotiation
migration_dependencies:
required:
- language

View file

@ -0,0 +1,52 @@
id: d6_language_types
label: Language types
migration_tags:
- Drupal 6
source:
plugin: variable
variables:
- language_negotiation
process:
all:
plugin: default_value
default_value:
- 'language_interface'
- 'language_content'
- 'language_url'
configurable:
plugin: default_value
default_value:
- 'language_interface'
negotiation/language_content/enabled:
plugin: default_value
default_value:
'language-interface': 0
negotiation/language_url/enabled:
plugin: default_value
default_value:
'language-url': 0
'language-url-fallback': 1
negotiation/language_interface/enabled:
plugin: static_map
source: language_negotiation
map:
# LANGUAGE_NEGOTIATION_NONE = 0
# LANGUAGE_NEGOTIATION_PATH_DEFAULT = 1
# LANGUAGE_NEGOTIATION_PATH = 2
# LANGUAGE_NEGOTIATION_DOMAIN = 3
0:
'language-selected': 0
1:
'language-url': 0
'language-selected': 1
2:
'language-url': 0
'language-user': 1
'language-browser': 2
'language-selected': 3
3:
'language-url': 0
'language-selected': 1
destination:
plugin: config
config_name: language.types

View file

@ -0,0 +1,46 @@
id: d7_language_content_settings
label: Drupal 7 language content settings
migration_tags:
- Drupal 7
source:
plugin: d7_language_content_settings
constants:
target_type: 'node'
process:
# Ignore i18n_node_options_[node_type] options not available in Drupal 8,
# i18n_required_node and i18n_newnode_current
target_bundle: type
target_entity_type_id: 'constants/target_type'
default_langcode:
-
plugin: static_map
source: language_content_type
map:
0: NULL
1: 'current_interface'
2: 'current_interface'
-
plugin: skip_on_empty
method: row
language_alterable:
plugin: static_map
source: i18n_lock_node
map:
0: true
1: false
'third_party_settings/content_translation/enabled':
plugin: static_map
source: language_content_type
map:
# In the case of being 0, it will be skipped. We are not actually setting
# a null value.
0: NULL
1: false
2: true
destination:
plugin: entity:language_content_settings
content_translation_update_definitions:
- node
migration_dependencies:
required:
- d7_node_type

View file

@ -0,0 +1,32 @@
id: d7_language_negotiation_settings
label: Language negotiation settings
migration_tags:
- Drupal 7
source:
plugin: variable
variables:
- locale_language_negotiation_session_param
- locale_language_negotiation_url_part
process:
session/parameter:
plugin: default_value
source: locale_language_negotiation_session_param
default_value: 'language'
selected_langcode:
plugin: default_value
default_value: 'site_default'
url/source:
plugin: static_map
source: locale_language_negotiation_url_part
default_value: path_prefix
map:
# LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX = 0
# LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN = 1
0: path_prefix
1: domain
destination:
plugin: config
config_name: language.negotiation
migration_dependencies:
required:
- language

View file

@ -0,0 +1,40 @@
id: d7_language_types
label: Language types
migration_tags:
- Drupal 7
source:
plugin: variable
variables:
- language_types
- language_negotiation_language
- language_negotiation_language_content
- language_negotiation_language_url
- locale_language_providers_weight_language
- locale_language_providers_weight_language_content
- locale_language_providers_weight_language_url
process:
all:
plugin: language_types
source: language_types
configurable:
plugin: language_types
source: language_types
filter_configurable: true
negotiation/language_content:
plugin: language_negotiation
source:
- language_negotiation_language_content
- locale_language_providers_weight_language_content
negotiation/language_url:
plugin: language_negotiation
source:
- language_negotiation_language_url
- locale_language_providers_weight_language_url
negotiation/language_interface:
plugin: language_negotiation
source:
- language_negotiation_language
- locale_language_providers_weight_language
destination:
plugin: config
config_name: language.types

View file

@ -0,0 +1,38 @@
id: default_language
label: Default language
migration_tags:
- Drupal 6
- Drupal 7
source:
plugin: variable
variables:
- language_default
process:
default_langcode:
-
plugin: default_value
source: language_default
default_value:
'language': 'en'
# Encode and decode to turn the default_language variable, which is
# an stdClass, into an array so it can be passed to extract.
-
plugin: callback
callable:
- '\Drupal\Component\Serialization\Json'
- 'encode'
-
plugin: callback
callable:
- '\Drupal\Component\Serialization\Json'
- 'decode'
-
plugin: extract
index:
- language
destination:
plugin: default_langcode
config_name: system.site
migration_dependencies:
required:
- language

View file

@ -0,0 +1,19 @@
id: language
label: Languages
migration_tags:
- Drupal 6
- Drupal 7
source:
plugin: language
process:
id: language
label: name
direction:
plugin: static_map
source: direction
map:
0: ltr
1: rtl
weight: weight
destination:
plugin: entity:configurable_language

View file

@ -0,0 +1,26 @@
id: language_prefixes_and_domains
label: Language prefixes and domains
migration_tags:
- Drupal 6
- Drupal 7
source:
plugin: language
fetch_all: true
domain_negotiation: true
process:
url/prefixes:
plugin: array_build
source: languages
key: language
value: prefix
url/domains:
plugin: language_domains
source: languages
key: language
value: domain
destination:
plugin: config
config_name: language.negotiation
migration_dependencies:
required:
- language

View file

@ -0,0 +1,77 @@
<?php
namespace Drupal\language\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a language negotiation annotation object.
*
* Plugin Namespace: Plugin\LanguageNegotiation
*
* For a working example, see
* \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationBrowser.
*
* @see \Drupal\language\LanguageNegotiator
* @see \Drupal\language\LanguageNegotiationMethodManager
* @see \Drupal\language\LanguageNegotiationMethodInterface
* @see hook_language_negotiation_info_alter()
* @see plugin_api
*
* @Annotation
*/
class LanguageNegotiation extends Plugin {
/**
* The language negotiation plugin ID.
*
* @var string
*/
public $id;
/**
* An array of allowed language types.
*
* If a language negotiation plugin does not specify which language types it
* should be used with, it will be available for all the configurable
* language types.
*
* @var string[]
* An array of language types, such as the
* \Drupal\Core\Language\LanguageInterface::TYPE_* constants.
*/
public $types;
/**
* The default weight of the language negotiation plugin.
*
* @var int
*/
public $weight;
/**
* The human-readable name of the language negotiation plugin.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $name;
/**
* The description of the language negotiation plugin.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $description;
/**
* The route pointing to the plugin's configuration page.
*
* @var string
*/
public $config_route_name;
}

View file

@ -0,0 +1,46 @@
<?php
namespace Drupal\language\Config;
/**
* Provides a common trait for working with language override collection names.
*/
trait LanguageConfigCollectionNameTrait {
/**
* Creates a configuration collection name based on a language code.
*
* @param string $langcode
* The language code.
*
* @return string
* The configuration collection name for a language code.
*/
protected function createConfigCollectionName($langcode) {
return 'language.' . $langcode;
}
/**
* Converts a configuration collection name to a language code.
*
* @param string $collection
* The configuration collection name.
*
* @return string
* The language code of the collection.
*
* @throws \InvalidArgumentException
* Exception thrown if the provided collection name is not in the format
* "language.LANGCODE".
*
* @see self::createConfigCollectionName()
*/
protected function getLangcodeFromCollectionName($collection) {
preg_match('/^language\.(.*)$/', $collection, $matches);
if (!isset($matches[1])) {
throw new \InvalidArgumentException("'$collection' is not a valid language override collection");
}
return $matches[1];
}
}

View file

@ -0,0 +1,232 @@
<?php
namespace Drupal\language\Config;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigCollectionInfo;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigFactoryOverrideBase;
use Drupal\Core\Config\ConfigRenameEvent;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Language\LanguageDefault;
use Drupal\Core\Language\LanguageInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Provides language overrides for the configuration factory.
*/
class LanguageConfigFactoryOverride extends ConfigFactoryOverrideBase implements LanguageConfigFactoryOverrideInterface, EventSubscriberInterface {
use LanguageConfigCollectionNameTrait;
/**
* The configuration storage.
*
* Do not access this directly. Should be accessed through self::getStorage()
* so that the cache of storages per langcode is used.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $baseStorage;
/**
* An array of configuration storages keyed by langcode.
*
* @var \Drupal\Core\Config\StorageInterface[]
*/
protected $storages;
/**
* The typed config manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/**
* An event dispatcher instance to use for configuration events.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The language object used to override configuration data.
*
* @var \Drupal\Core\Language\LanguageInterface
*/
protected $language;
/**
* Constructs the LanguageConfigFactoryOverride object.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* The configuration storage engine.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* An event dispatcher instance to use for configuration events.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager.
*/
public function __construct(StorageInterface $storage, EventDispatcherInterface $event_dispatcher, TypedConfigManagerInterface $typed_config) {
$this->baseStorage = $storage;
$this->eventDispatcher = $event_dispatcher;
$this->typedConfigManager = $typed_config;
}
/**
* {@inheritdoc}
*/
public function loadOverrides($names) {
if ($this->language) {
$storage = $this->getStorage($this->language->getId());
return $storage->readMultiple($names);
}
return array();
}
/**
* {@inheritdoc}
*/
public function getOverride($langcode, $name) {
$storage = $this->getStorage($langcode);
$data = $storage->read($name);
$override = new LanguageConfigOverride(
$name,
$storage,
$this->typedConfigManager,
$this->eventDispatcher
);
if (!empty($data)) {
$override->initWithData($data);
}
return $override;
}
/**
* {@inheritdoc}
*/
public function getStorage($langcode) {
if (!isset($this->storages[$langcode])) {
$this->storages[$langcode] = $this->baseStorage->createCollection($this->createConfigCollectionName($langcode));
}
return $this->storages[$langcode];
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return $this->language ? $this->language->getId() : NULL;
}
/**
* {@inheritdoc}
*/
public function getLanguage() {
return $this->language;
}
/**
* {@inheritdoc}
*/
public function setLanguage(LanguageInterface $language = NULL) {
$this->language = $language;
return $this;
}
/**
* {@inheritdoc}
*/
public function setLanguageFromDefault(LanguageDefault $language_default = NULL) {
$this->language = $language_default ? $language_default->get() : NULL;
return $this;
}
/**
* {@inheritdoc}
*/
public function installLanguageOverrides($langcode) {
/** @var \Drupal\Core\Config\ConfigInstallerInterface $config_installer */
$config_installer = \Drupal::service('config.installer');
$config_installer->installCollectionDefaultConfig($this->createConfigCollectionName($langcode));
}
/**
* {@inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
$langcode = $this->getLangcodeFromCollectionName($collection);
return $this->getOverride($langcode, $name);
}
/**
* {@inheritdoc}
*/
public function addCollections(ConfigCollectionInfo $collection_info) {
foreach (\Drupal::languageManager()->getLanguages() as $language) {
$collection_info->addCollection($this->createConfigCollectionName($language->getId()), $this);
}
}
/**
* {@inheritdoc}
*/
public function onConfigSave(ConfigCrudEvent $event) {
$config = $event->getConfig();
$name = $config->getName();
foreach (\Drupal::languageManager()->getLanguages() as $language) {
$config_translation = $this->getOverride($language->getId(), $name);
if (!$config_translation->isNew()) {
$this->filterOverride($config, $config_translation);
}
}
}
/**
* {@inheritdoc}
*/
public function onConfigRename(ConfigRenameEvent $event) {
$config = $event->getConfig();
$name = $config->getName();
$old_name = $event->getOldName();
foreach (\Drupal::languageManager()->getLanguages() as $language) {
$config_translation = $this->getOverride($language->getId(), $old_name);
if (!$config_translation->isNew()) {
$saved_config = $config_translation->get();
$storage = $this->getStorage($language->getId());
$storage->write($name, $saved_config);
$config_translation->delete();
}
}
}
/**
* {@inheritdoc}
*/
public function onConfigDelete(ConfigCrudEvent $event) {
$config = $event->getConfig();
$name = $config->getName();
foreach (\Drupal::languageManager()->getLanguages() as $language) {
$config_translation = $this->getOverride($language->getId(), $name);
if (!$config_translation->isNew()) {
$config_translation->delete();
}
}
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
$metadata = new CacheableMetadata();
if ($this->language) {
$metadata->setCacheContexts(['languages:language_interface']);
}
return $metadata;
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\language\Config;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageDefault;
/**
* Defines the interface for a configuration factory language override object.
*/
interface LanguageConfigFactoryOverrideInterface extends ConfigFactoryOverrideInterface {
/**
* Gets the language object used to override configuration data.
*
* @return \Drupal\Core\Language\LanguageInterface
* The language object used to override configuration data.
*/
public function getLanguage();
/**
* Sets the language to be used in configuration overrides.
*
* @param \Drupal\Core\Language\LanguageInterface $language
* The language object used to override configuration data.
*
* @return $this
*/
public function setLanguage(LanguageInterface $language = NULL);
/**
* Sets the language to be used in configuration overrides from the default.
*
* @param \Drupal\Core\Language\LanguageDefault $language_default
* The default language.
*
* @return $this
*/
public function setLanguageFromDefault(LanguageDefault $language_default = NULL);
/**
* Get language override for given language and configuration name.
*
* @param string $langcode
* Language code.
* @param string $name
* Configuration name.
*
* @return \Drupal\Core\Config\Config
* Configuration override object.
*/
public function getOverride($langcode, $name);
/**
* Returns the storage instance for a particular langcode.
*
* @param string $langcode
* Language code.
*
* @return \Drupal\Core\Config\StorageInterface
* The storage instance for a particular langcode.
*/
public function getStorage($langcode);
/**
* Installs available language configuration overrides for a given langcode.
*
* @param string $langcode
* Language code.
*/
public function installLanguageOverrides($langcode);
}

View file

@ -0,0 +1,93 @@
<?php
namespace Drupal\language\Config;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\StorableConfigBase;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Defines language configuration overrides.
*/
class LanguageConfigOverride extends StorableConfigBase {
use LanguageConfigCollectionNameTrait;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* Constructs a language override object.
*
* @param string $name
* The name of the configuration object being overridden.
* @param \Drupal\Core\Config\StorageInterface $storage
* A storage controller object to use for reading and writing the
* configuration override.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
*/
public function __construct($name, StorageInterface $storage, TypedConfigManagerInterface $typed_config, EventDispatcherInterface $event_dispatcher) {
$this->name = $name;
$this->storage = $storage;
$this->typedConfigManager = $typed_config;
$this->eventDispatcher = $event_dispatcher;
}
/**
* {@inheritdoc}
*/
public function save($has_trusted_data = FALSE) {
if (!$has_trusted_data) {
// @todo Use configuration schema to validate.
// https://www.drupal.org/node/2270399
// Perform basic data validation.
foreach ($this->data as $key => $value) {
$this->validateValue($key, $value);
}
}
$this->storage->write($this->name, $this->data);
// Invalidate the cache tags not only when updating, but also when creating,
// because a language config override object uses the same cache tag as the
// default configuration object. Hence creating a language override is like
// an update of configuration, but only for a specific language.
Cache::invalidateTags($this->getCacheTags());
$this->isNew = FALSE;
$this->eventDispatcher->dispatch(LanguageConfigOverrideEvents::SAVE_OVERRIDE, new LanguageConfigOverrideCrudEvent($this));
$this->originalData = $this->data;
return $this;
}
/**
* {@inheritdoc}
*/
public function delete() {
$this->data = array();
$this->storage->delete($this->name);
Cache::invalidateTags($this->getCacheTags());
$this->isNew = TRUE;
$this->eventDispatcher->dispatch(LanguageConfigOverrideEvents::DELETE_OVERRIDE, new LanguageConfigOverrideCrudEvent($this));
$this->originalData = $this->data;
return $this;
}
/**
* Returns the language code of this language override.
*
* @return string
* The language code.
*/
public function getLangcode() {
return $this->getLangcodeFromCollectionName($this->getStorage()->getCollectionName());
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\language\Config;
use Symfony\Component\EventDispatcher\Event;
/**
* Provides a language override event for event listeners.
*
* @see \Drupal\Core\Config\ConfigCrudEvent
*/
class LanguageConfigOverrideCrudEvent extends Event {
/**
* Configuration object.
*
* @var \Drupal\language\Config\LanguageConfigOverride
*/
protected $override;
/**
* Constructs a configuration event object.
*
* @param \Drupal\language\Config\LanguageConfigOverride $override
* Configuration object.
*/
public function __construct(LanguageConfigOverride $override) {
$this->override = $override;
}
/**
* Gets configuration object.
*
* @return \Drupal\language\Config\LanguageConfigOverride
* The configuration object that caused the event to fire.
*/
public function getLanguageConfigOverride() {
return $this->override;
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\language\Config;
/**
* Defines events for language configuration overrides.
*
* @see \Drupal\Core\Config\ConfigCrudEvent
*/
final class LanguageConfigOverrideEvents {
/**
* The name of the event fired when saving the configuration override.
*
* This event allows you to perform custom actions whenever a language config
* override is saved. The event listener method receives a
* \Drupal\language\Config\LanguageConfigOverrideCrudEvent instance.
*
* @Event
*
* @see \Drupal\language\Config\LanguageConfigOverrideCrudEvent
* @see \Drupal\language\Config\LanguageConfigOverride::save()
* @see \Drupal\locale\LocaleConfigSubscriber
*/
const SAVE_OVERRIDE = 'language.save_override';
/**
* The name of the event fired when deleting the configuration override.
*
* This event allows you to perform custom actions whenever a language config
* override is deleted. The event listener method receives a
* \Drupal\language\Config\LanguageConfigOverrideCrudEvent instance.
*
* @Event
*
* @see \Drupal\language\Config\LanguageConfigOverrideCrudEvent
* @see \Drupal\language\Config\LanguageConfigOverride::delete()
* @see \Drupal\locale\LocaleConfigSubscriber
*/
const DELETE_OVERRIDE = 'language.delete_override';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\language;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Language\LanguageInterface;
/**
* Provides an interface defining a language entity.
*/
interface ConfigurableLanguageInterface extends ConfigEntityInterface, LanguageInterface {
/**
* Sets the name of the language.
*
* @param string $name
* The human-readable English name of the language.
*
* @return $this
*/
public function setName($name);
/**
* Sets the weight of the language.
*
* @param int $weight
* The weight, used to order languages with larger positive weights sinking
* items toward the bottom of lists.
*
* @return $this
*/
public function setWeight($weight);
}

View file

@ -0,0 +1,483 @@
<?php
namespace Drupal\language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageDefault;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\language\Config\LanguageConfigFactoryOverrideInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Overrides default LanguageManager to provide configured languages.
*/
class ConfigurableLanguageManager extends LanguageManager implements ConfigurableLanguageManagerInterface {
/**
* The configuration storage service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The language configuration override service.
*
* @var \Drupal\language\Config\LanguageConfigFactoryOverrideInterface
*/
protected $configFactoryOverride;
/**
* The request object.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The language negotiator.
*
* @var \Drupal\language\LanguageNegotiatorInterface
*/
protected $negotiator;
/**
* Local cache for language type configuration data.
*
* @var array
*/
protected $languageTypes;
/**
* Local cache for language type information.
*
* @var array
*/
protected $languageTypesInfo;
/**
* An array of language objects keyed by language type.
*
* @var \Drupal\Core\Language\LanguageInterface[]
*/
protected $negotiatedLanguages;
/**
* An array of language negotiation method IDs keyed by language type.
*
* @var array
*/
protected $negotiatedMethods;
/**
* Whether or not the language manager has been initialized.
*
* @var bool
*/
protected $initialized = FALSE;
/**
* Whether already in the process of language initialization.
*
* @var bool
*/
protected $initializing = FALSE;
/**
* {@inheritdoc}
*/
public static function rebuildServices() {
\Drupal::service('kernel')->invalidateContainer();
}
/**
* Constructs a new ConfigurableLanguageManager object.
*
* @param \Drupal\Core\Language\LanguageDefault $default_language
* The default language service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\language\Config\LanguageConfigFactoryOverrideInterface $config_override
* The language configuration override service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack object.
*/
public function __construct(LanguageDefault $default_language, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, LanguageConfigFactoryOverrideInterface $config_override, RequestStack $request_stack) {
$this->defaultLanguage = $default_language;
$this->configFactory = $config_factory;
$this->moduleHandler = $module_handler;
$this->configFactoryOverride = $config_override;
$this->requestStack = $request_stack;
}
/**
* {@inheritdoc}
*/
public function init() {
if (!$this->initialized) {
foreach ($this->getDefinedLanguageTypes() as $type) {
$this->getCurrentLanguage($type);
}
$this->initialized = TRUE;
}
}
/**
* {@inheritdoc}
*/
public function isMultilingual() {
return count($this->getLanguages(LanguageInterface::STATE_CONFIGURABLE)) > 1;
}
/**
* {@inheritdoc}
*/
public function getLanguageTypes() {
$this->loadLanguageTypesConfiguration();
return $this->languageTypes['configurable'];
}
/**
* {@inheritdoc}
*/
public function getDefinedLanguageTypes() {
$this->loadLanguageTypesConfiguration();
return $this->languageTypes['all'];
}
/**
* Retrieves language types from the configuration storage.
*
* @return array
* An array of language type names.
*/
protected function loadLanguageTypesConfiguration() {
if (!$this->languageTypes) {
$this->languageTypes = $this->configFactory->get('language.types')->get() ?: array('configurable' => array(), 'all' => parent::getLanguageTypes());
}
return $this->languageTypes;
}
/**
* {@inheritdoc}
*/
public function getDefinedLanguageTypesInfo() {
if (!isset($this->languageTypesInfo)) {
$defaults = parent::getDefinedLanguageTypesInfo();
$info = $this->moduleHandler->invokeAll('language_types_info');
$language_info = $info + $defaults;
// Let other modules alter the list of language types.
$this->moduleHandler->alter('language_types_info', $language_info);
$this->languageTypesInfo = $language_info;
}
return $this->languageTypesInfo;
}
/**
* {@inheritdoc}
*/
public function saveLanguageTypesConfiguration(array $values) {
$config = $this->configFactory->getEditable('language.types');
if (isset($values['configurable'])) {
$config->set('configurable', $values['configurable']);
}
if (isset($values['all'])) {
$config->set('all', $values['all']);
}
$config->save();
}
/**
* {@inheritdoc}
*/
public function getCurrentLanguage($type = LanguageInterface::TYPE_INTERFACE) {
if (!isset($this->negotiatedLanguages[$type])) {
// Ensure we have a valid value for this language type.
$this->negotiatedLanguages[$type] = $this->getDefaultLanguage();
if ($this->negotiator && $this->isMultilingual()) {
if (!$this->initializing) {
$this->initializing = TRUE;
$negotiation = $this->negotiator->initializeType($type);
$this->negotiatedLanguages[$type] = reset($negotiation);
$this->negotiatedMethods[$type] = key($negotiation);
$this->initializing = FALSE;
}
// If the current interface language needs to be retrieved during
// initialization we return the system language. This way string
// translation calls happening during initialization will return the
// original strings which can be translated by calling them again
// afterwards. This can happen for instance while parsing negotiation
// method definitions.
elseif ($type == LanguageInterface::TYPE_INTERFACE) {
return new Language(array('id' => LanguageInterface::LANGCODE_SYSTEM));
}
}
}
return $this->negotiatedLanguages[$type];
}
/**
* {@inheritdoc}
*/
public function reset($type = NULL) {
if (!isset($type)) {
$this->initialized = FALSE;
$this->negotiatedLanguages = array();
$this->negotiatedMethods = array();
$this->languageTypes = NULL;
$this->languageTypesInfo = NULL;
$this->languages = array();
if ($this->negotiator) {
$this->negotiator->reset();
}
}
elseif (isset($this->negotiatedLanguages[$type])) {
unset($this->negotiatedLanguages[$type]);
unset($this->negotiatedMethods[$type]);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getNegotiator() {
return $this->negotiator;
}
/**
* {@inheritdoc}
*/
public function setNegotiator(LanguageNegotiatorInterface $negotiator) {
$this->negotiator = $negotiator;
$this->initialized = FALSE;
$this->negotiatedLanguages = array();
}
/**
* {@inheritdoc}
*/
public function getLanguages($flags = LanguageInterface::STATE_CONFIGURABLE) {
// If a config override is set, cache using that language's ID.
if ($override_language = $this->getConfigOverrideLanguage()) {
$static_cache_id = $override_language->getId();
}
else {
$static_cache_id = $this->getCurrentLanguage()->getId();
}
if (!isset($this->languages[$static_cache_id][$flags])) {
// Initialize the language list with the default language and default
// locked languages. These cannot be removed. This serves as a fallback
// list if this method is invoked while the language module is installed
// and the configuration entities for languages are not yet fully
// imported.
$default = $this->getDefaultLanguage();
$languages = array($default->getId() => $default);
$languages += $this->getDefaultLockedLanguages($default->getWeight());
// Load configurable languages on top of the defaults. Ideally this could
// use the entity API to load and instantiate ConfigurableLanguage
// objects. However the entity API depends on the language system, so that
// would result in infinite loops. We use the configuration system
// directly and instantiate runtime Language objects. When language
// entities are imported those cover the default and locked languages, so
// site-specific configuration will prevail over the fallback values.
// Having them in the array already ensures if this is invoked in the
// middle of importing language configuration entities, the defaults are
// always present.
$config_ids = $this->configFactory->listAll('language.entity.');
foreach ($this->configFactory->loadMultiple($config_ids) as $config) {
$data = $config->get();
$data['name'] = $data['label'];
$languages[$data['id']] = new Language($data);
}
Language::sort($languages);
// Filter the full list of languages based on the value of $flags.
$this->languages[$static_cache_id][$flags] = $this->filterLanguages($languages, $flags);
}
return $this->languages[$static_cache_id][$flags];
}
/**
* {@inheritdoc}
*/
public function getNativeLanguages() {
$languages = $this->getLanguages(LanguageInterface::STATE_CONFIGURABLE);
$natives = array();
$original_language = $this->getConfigOverrideLanguage();
foreach ($languages as $langcode => $language) {
$this->setConfigOverrideLanguage($language);
$natives[$langcode] = ConfigurableLanguage::load($langcode);
}
$this->setConfigOverrideLanguage($original_language);
Language::sort($natives);
return $natives;
}
/**
* {@inheritdoc}
*/
public function updateLockedLanguageWeights() {
// Get the weight of the last configurable language.
$configurable_languages = $this->getLanguages(LanguageInterface::STATE_CONFIGURABLE);
$max_weight = end($configurable_languages)->getWeight();
$locked_languages = $this->getLanguages(LanguageInterface::STATE_LOCKED);
// Update locked language weights to maintain the existing order, if
// necessary.
if (reset($locked_languages)->getWeight() <= $max_weight) {
foreach ($locked_languages as $language) {
// Update system languages weight.
$max_weight++;
ConfigurableLanguage::load($language->getId())
->setWeight($max_weight)
->save();
}
}
}
/**
* {@inheritdoc}
*/
public function getFallbackCandidates(array $context = array()) {
if ($this->isMultilingual()) {
$candidates = array();
if (empty($context['operation']) || $context['operation'] != 'locale_lookup') {
// If the fallback context is not locale_lookup, initialize the
// candidates with languages ordered by weight and add
// LanguageInterface::LANGCODE_NOT_SPECIFIED at the end. Interface
// translation fallback should only be based on explicit configuration
// gathered via the alter hooks below.
$candidates = array_keys($this->getLanguages());
$candidates[] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
$candidates = array_combine($candidates, $candidates);
// The first candidate should always be the desired language if
// specified.
if (!empty($context['langcode'])) {
$candidates = array($context['langcode'] => $context['langcode']) + $candidates;
}
}
// Let other modules hook in and add/change candidates.
$type = 'language_fallback_candidates';
$types = array();
if (!empty($context['operation'])) {
$types[] = $type . '_' . $context['operation'];
}
$types[] = $type;
$this->moduleHandler->alter($types, $candidates, $context);
}
else {
$candidates = parent::getFallbackCandidates($context);
}
return $candidates;
}
/**
* {@inheritdoc}
*/
public function getLanguageSwitchLinks($type, Url $url) {
$links = FALSE;
if ($this->negotiator) {
foreach ($this->negotiator->getNegotiationMethods($type) as $method_id => $method) {
$reflector = new \ReflectionClass($method['class']);
if ($reflector->implementsInterface('\Drupal\language\LanguageSwitcherInterface')) {
$result = $this->negotiator->getNegotiationMethodInstance($method_id)->getLanguageSwitchLinks($this->requestStack->getCurrentRequest(), $type, $url);
if (!empty($result)) {
// Allow modules to provide translations for specific links.
$this->moduleHandler->alter('language_switch_links', $result, $type, $path);
$links = (object) array('links' => $result, 'method_id' => $method_id);
break;
}
}
}
}
return $links;
}
/**
* {@inheritdoc}
*/
public function setConfigOverrideLanguage(LanguageInterface $language = NULL) {
$this->configFactoryOverride->setLanguage($language);
return $this;
}
/**
* {@inheritdoc}
*/
public function getConfigOverrideLanguage() {
return $this->configFactoryOverride->getLanguage();
}
/**
* {@inheritdoc}
*/
public function getLanguageConfigOverride($langcode, $name) {
return $this->configFactoryOverride->getOverride($langcode, $name);
}
/**
* {@inheritdoc}
*/
public function getLanguageConfigOverrideStorage($langcode) {
return $this->configFactoryOverride->getStorage($langcode);
}
/**
* {@inheritdoc}
*/
public function getStandardLanguageListWithoutConfigured() {
$languages = $this->getLanguages();
$predefined = $this->getStandardLanguageList();
foreach ($predefined as $key => $value) {
if (isset($languages[$key])) {
unset($predefined[$key]);
continue;
}
$predefined[$key] = new TranslatableMarkup($value[0]);
}
natcasesort($predefined);
return $predefined;
}
/**
* {@inheritdoc}
*/
public function getNegotiatedLanguageMethod($type = LanguageInterface::TYPE_INTERFACE) {
if (isset($this->negotiatedLanguages[$type]) && isset($this->negotiatedMethods[$type])) {
return $this->negotiatedMethods[$type];
}
}
}

View file

@ -0,0 +1,106 @@
<?php
namespace Drupal\language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
/**
* Common interface for language negotiation services.
*/
interface ConfigurableLanguageManagerInterface extends LanguageManagerInterface {
/**
* Rebuild the container to register services needed on multilingual sites.
*/
public static function rebuildServices();
/**
* Returns the language negotiator.
*
* @return \Drupal\language\LanguageNegotiatorInterface
* The language negotiator.
*/
public function getNegotiator();
/**
* Injects the language negotiator.
*
* @param \Drupal\language\LanguageNegotiatorInterface $negotiator
* The language negotiator.
*/
public function setNegotiator(LanguageNegotiatorInterface $negotiator);
/**
* Returns all the defined language types including fixed ones.
*
* A language type maybe configurable or fixed. A fixed language type is a
* type whose language negotiation methods are module-defined and not altered
* through the user interface.
*
* @return array
* An array of language type machine names.
*/
public function getDefinedLanguageTypes();
/**
* Stores language types configuration.
*
* @param array $config
* An indexed array with the following keys_
* - configurable: an array of configurable language type names.
* - all: an array of all the defined language type names.
*/
public function saveLanguageTypesConfiguration(array $config);
/**
* Updates locked system language weights.
*/
public function updateLockedLanguageWeights();
/**
* Gets a language config override object.
*
* @param string $langcode
* The language code for the override.
* @param string $name
* The language configuration object name.
*
* @return \Drupal\language\Config\LanguageConfigOverride
* The language config override object.
*/
public function getLanguageConfigOverride($langcode, $name);
/**
* Gets a language configuration override storage object.
*
* @param string $langcode
* The language code for the override.
*
* @return \Drupal\Core\Config\StorageInterface $storage
* A storage object to use for reading and writing the
* configuration override.
*/
public function getLanguageConfigOverrideStorage($langcode);
/**
* Returns the standard language list excluding already configured languages.
*
* @return array
* A list of standard language names keyed by langcode.
*/
public function getStandardLanguageListWithoutConfigured();
/**
* Gets the negotiated language method ID.
*
* @param string $type
* (optional) The language type; e.g., the interface or the content
* language.
*
* @return string
* The negotiated language method ID.
*/
public function getNegotiatedLanguageMethod($type = LanguageInterface::TYPE_INTERFACE);
}

View file

@ -0,0 +1,8 @@
<?php
namespace Drupal\language;
/**
* Exception thrown by ContentLanguageSettings when target bundle is not set.
*/
class ContentLanguageSettingsException extends \RuntimeException {}

View file

@ -0,0 +1,78 @@
<?php
namespace Drupal\language;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface defining language settings for content entities.
*/
interface ContentLanguageSettingsInterface extends ConfigEntityInterface {
/**
* Gets the entity type ID this config applies to.
*
* @return string
*/
public function getTargetEntityTypeId();
/**
* Gets the bundle this config applies to.
*
* @return string
*/
public function getTargetBundle();
/**
* Sets the bundle this config applies to.
*
* @param string $target_bundle
* The bundle.
*
* @return $this
*/
public function setTargetBundle($target_bundle);
/**
* Sets the default language code.
*
* @param string $default_langcode
* The default language code.
*
* @return $this
*/
public function setDefaultLangcode($default_langcode);
/**
* Gets the default language code.
*
* @return string
*/
public function getDefaultLangcode();
/**
* Sets if the language must be alterable or not.
*
* @param bool $language_alterable
* Flag indicating if the language must be alterable.
*
* @return $this
*/
public function setLanguageAlterable($language_alterable);
/**
* Checks if the language is alterable or not.
*
* @return bool
*/
public function isLanguageAlterable();
/**
* Checks if this config object contains the default values in every property.
*
* @return bool
* True if all the properties contain the default values. False otherwise.
*/
public function isDefaultConfiguration();
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\language;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\LanguageItem;
use Drupal\Core\Language\Language;
/**
* Alternative plugin implementation of the 'language' field type.
*
* Replaces the Core 'language' entity field type implementation, changes the
* default values used.
*
* Required settings are:
* - target_type: The entity type to reference.
*
* @see language_field_info_alter().
*/
class DefaultLanguageItem extends LanguageItem {
/**
* {@inheritdoc}
*/
public function applyDefaultValue($notify = TRUE) {
// Default to LANGCODE_NOT_SPECIFIED.
$langcode = Language::LANGCODE_NOT_SPECIFIED;
if ($entity = $this->getEntity()) {
$langcode = $this->getDefaultLangcode($entity);
}
// Always notify otherwise default langcode will not be set correctly.
$this->setValue(array('value' => $langcode), TRUE);
return $this;
}
/**
* Provides default language code of given entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity whose language code to be loaded.
*
* @return string
* A string language code.
*/
public function getDefaultLangcode(EntityInterface $entity) {
return language_get_default_langcode($entity->getEntityTypeId(), $entity->bundle());
}
}

View file

@ -0,0 +1,115 @@
<?php
namespace Drupal\language\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\Element\FormElement;
/**
* Provides language element configuration.
*
* @FormElement("language_configuration")
*/
class LanguageConfiguration extends FormElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#input' => TRUE,
'#tree' => TRUE,
'#process' => array(
array($class, 'processLanguageConfiguration'),
),
);
}
/**
* Process handler for the language_configuration form element.
*/
public static function processLanguageConfiguration(&$element, FormStateInterface $form_state, &$form) {
$options = isset($element['#options']) ? $element['#options'] : array();
// Avoid validation failure since we are moving the '#options' key in the
// nested 'language' select element.
unset($element['#options']);
/** @var \Drupal\language\Entity\ContentLanguageSettings $default_config */
$default_config = $element['#default_value'];
$element['langcode'] = array(
'#type' => 'select',
'#title' => t('Default language'),
'#options' => $options + static::getDefaultOptions(),
'#description' => t('Explanation of the language options is found on the <a href=":languages_list_page">languages list page</a>.', array(':languages_list_page' => \Drupal::url('entity.configurable_language.collection'))),
'#default_value' => ($default_config != NULL) ? $default_config->getDefaultLangcode() : LanguageInterface::LANGCODE_SITE_DEFAULT,
);
$element['language_alterable'] = array(
'#type' => 'checkbox',
'#title' => t('Show language selector on create and edit pages'),
'#default_value' => ($default_config != NULL) ? $default_config->isLanguageAlterable() : FALSE,
);
// Add the entity type and bundle information to the form if they are set.
// They will be used, in the submit handler, to generate the names of the
// configuration entities that will store the settings and are a way to uniquely
// identify the entity.
$language = $form_state->get('language') ?: [];
$language += array(
$element['#name'] => array(
'entity_type' => $element['#entity_information']['entity_type'],
'bundle' => $element['#entity_information']['bundle'],
),
);
$form_state->set('language', $language);
// Do not add the submit callback for the language content settings page,
// which is handled separately.
if ($form['#form_id'] != 'language_content_settings_form') {
// Determine where to attach the language_configuration element submit
// handler.
// @todo Form API: Allow form widgets/sections to declare #submit
// handlers.
$submit_name = isset($form['actions']['save_continue']) ? 'save_continue' : 'submit';
if (isset($form['actions'][$submit_name]['#submit']) && array_search('language_configuration_element_submit', $form['actions'][$submit_name]['#submit']) === FALSE) {
$form['actions'][$submit_name]['#submit'][] = 'language_configuration_element_submit';
}
elseif (array_search('language_configuration_element_submit', $form['#submit']) === FALSE) {
$form['#submit'][] = 'language_configuration_element_submit';
}
}
return $element;
}
/**
* Returns the default options for the language configuration form element.
*
* @return array
* An array containing the default options.
*/
protected static function getDefaultOptions() {
$language_options = array(
LanguageInterface::LANGCODE_SITE_DEFAULT => t("Site's default language (@language)", array('@language' => static::languageManager()->getDefaultLanguage()->getName())),
'current_interface' => t('Interface text language selected for page'),
'authors_default' => t("Author's preferred language"),
);
$languages = static::languageManager()->getLanguages(LanguageInterface::STATE_ALL);
foreach ($languages as $langcode => $language) {
$language_options[$langcode] = $language->isLocked() ? t('- @name -', array('@name' => $language->getName())) : $language->getName();
}
return $language_options;
}
/**
* Wraps the language manager.
*
* @return \Drupal\Core\Language\LanguageManagerInterface
*/
protected static function languageManager() {
return \Drupal::languageManager();
}
}

View file

@ -0,0 +1,264 @@
<?php
namespace Drupal\language\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\language\ConfigurableLanguageManager;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Drupal\language\Exception\DeleteDefaultLanguageException;
use Drupal\language\ConfigurableLanguageInterface;
/**
* Defines the ConfigurableLanguage entity.
*
* @ConfigEntityType(
* id = "configurable_language",
* label = @Translation("Language"),
* handlers = {
* "list_builder" = "Drupal\language\LanguageListBuilder",
* "access" = "Drupal\language\LanguageAccessControlHandler",
* "form" = {
* "add" = "Drupal\language\Form\LanguageAddForm",
* "edit" = "Drupal\language\Form\LanguageEditForm",
* "delete" = "Drupal\language\Form\LanguageDeleteForm"
* }
* },
* admin_permission = "administer languages",
* config_prefix = "entity",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "weight" = "weight"
* },
* links = {
* "delete-form" = "/admin/config/regional/language/delete/{configurable_language}",
* "edit-form" = "/admin/config/regional/language/edit/{configurable_language}",
* "collection" = "/admin/config/regional/language",
* }
* )
*/
class ConfigurableLanguage extends ConfigEntityBase implements ConfigurableLanguageInterface {
/**
* The language ID (machine name).
*
* @var string
*/
protected $id;
/**
* The human-readable label for the language.
*
* @var string
*/
protected $label;
/**
* The direction of language, either DIRECTION_LTR or DIRECTION_RTL.
*
* @var int
*/
protected $direction = self::DIRECTION_LTR;
/**
* The weight of the language, used in lists of languages.
*
* @var int
*/
protected $weight = 0;
/**
* Locked languages cannot be edited.
*
* @var bool
*/
protected $locked = FALSE;
/**
* Used during saving to detect when the site becomes multilingual.
*
* This property is not saved to the language entity, but is needed for
* detecting when to rebuild the services.
*
* @see \Drupal\language\Entity\ConfigurableLanguage::preSave()
* @see \Drupal\language\Entity\ConfigurableLanguage::postSave()
*
* @var bool
*/
protected $preSaveMultilingual;
/**
* {@inheritdoc}
*/
public function isDefault() {
return static::getDefaultLangcode() == $this->id();
}
/**
* {@inheritdoc}
*/
public function isLocked() {
return (bool) $this->locked;
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
// Store whether or not the site is already multilingual so that we can
// rebuild services if necessary during
// \Drupal\language\Entity\ConfigurableLanguage::postSave().
$this->preSaveMultilingual = \Drupal::languageManager()->isMultilingual();
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
$language_manager = \Drupal::languageManager();
$language_manager->reset();
if (!$this->isLocked() && $language_manager instanceof ConfigurableLanguageManagerInterface && !$this->isSyncing()) {
$language_manager->updateLockedLanguageWeights();
}
// Update URL Prefixes for all languages after the
// LanguageManagerInterface::getLanguages() cache is flushed.
language_negotiation_url_prefixes_update();
// If after adding this language the site will become multilingual, we need
// to rebuild language services.
if (!$this->preSaveMultilingual && !$update && $language_manager instanceof ConfigurableLanguageManagerInterface) {
$language_manager::rebuildServices();
}
if (!$update) {
// Install any available language configuration overrides for the language.
\Drupal::service('language.config_factory_override')->installLanguageOverrides($this->id());
}
}
/**
* {@inheritdoc}
*
* @throws \DeleteDefaultLanguageException
* Exception thrown if we're trying to delete the default language entity.
* This is not allowed as a site must have a default language.
*/
public static function preDelete(EntityStorageInterface $storage, array $entities) {
$default_langcode = static::getDefaultLangcode();
foreach ($entities as $entity) {
if ($entity->id() == $default_langcode && !$entity->isUninstalling()) {
throw new DeleteDefaultLanguageException('Can not delete the default language');
}
}
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
$language_manager = \Drupal::languageManager();
$language_manager->reset();
$entity = reset($entities);
if ($language_manager instanceof ConfigurableLanguageManagerInterface && !$entity->isUninstalling() && !$entity->isSyncing()) {
$language_manager->updateLockedLanguageWeights();
}
// If after deleting this language the site will become monolingual, we need
// to rebuild language services.
if (!\Drupal::languageManager()->isMultilingual()) {
ConfigurableLanguageManager::rebuildServices();
}
}
/**
* Gets the default langcode.
*
* @return string
* The current default langcode.
*/
protected static function getDefaultLangcode() {
$language = \Drupal::service('language.default')->get();
return $language->getId();
}
/**
* {@inheritdoc}
*/
public function getName() {
return $this->label();
}
/**
* {@inheritdoc}
*/
public function setName($name) {
$this->label = $name;
return $this;
}
/**
* {@inheritdoc}
*/
public function getId() {
return $this->id();
}
/**
* {@inheritdoc}
*/
public function getDirection() {
return $this->direction;
}
/**
* {@inheritdoc}
*/
public function getWeight() {
return $this->weight;
}
/**
* {@inheritdoc}
*/
public function setWeight($weight) {
$this->weight = $weight;
return $this;
}
/**
* Creates a configurable language object from a langcode.
*
* @param string $langcode
* The language code to use to create the object.
*
* @return $this
*
* @see \Drupal\Core\Language\LanguageManager::getStandardLanguageList()
*/
public static function createFromLangcode($langcode) {
$standard_languages = LanguageManager::getStandardLanguageList();
if (!isset($standard_languages[$langcode])) {
// Drupal does not know about this language, so we set its values with the
// best guess. The user will be able to edit afterwards.
return static::create(array(
'id' => $langcode,
'label' => $langcode,
));
}
else {
// A known predefined language, details will be filled in properly.
return static::create(array(
'id' => $langcode,
'label' => $standard_languages[$langcode][0],
'direction' => isset($standard_languages[$langcode][2]) ? $standard_languages[$langcode][2] : static::DIRECTION_LTR,
));
}
}
}

View file

@ -0,0 +1,202 @@
<?php
namespace Drupal\language\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\ContentLanguageSettingsException;
use Drupal\language\ContentLanguageSettingsInterface;
/**
* Defines the ContentLanguageSettings entity.
*
* @ConfigEntityType(
* id = "language_content_settings",
* label = @Translation("Content Language Settings"),
* admin_permission = "administer languages",
* config_prefix = "content_settings",
* entity_keys = {
* "id" = "id"
* },
* list_cache_tags = { "rendered" }
* )
*/
class ContentLanguageSettings extends ConfigEntityBase implements ContentLanguageSettingsInterface {
/**
* The id. Combination of $target_entity_type_id.$target_bundle.
*
* @var string
*/
protected $id;
/**
* The entity type ID (machine name).
*
* @var string
*/
protected $target_entity_type_id;
/**
* The bundle (machine name).
*
* @var string
*/
protected $target_bundle;
/**
* The default language code.
*
* @var string
*/
protected $default_langcode = LanguageInterface::LANGCODE_SITE_DEFAULT;
/**
* Indicates if the language is alterable or not.
*
* @var bool
*/
protected $language_alterable = FALSE;
/**
* Constructs a ContentLanguageSettings object.
*
* In most cases, Field entities are created via
* FieldConfig::create($values), where $values is the same
* parameter as in this constructor.
*
* @param array $values
* An array of the referring entity bundle with:
* - target_entity_type_id: The entity type.
* - target_bundle: The bundle.
* Other array elements will be used to set the corresponding properties on
* the class; see the class property documentation for details.
*
* @see entity_create()
*/
public function __construct(array $values, $entity_type = 'language_content_settings') {
if (empty($values['target_entity_type_id'])) {
throw new ContentLanguageSettingsException('Attempt to create content language settings without a target_entity_type_id.');
}
if (empty($values['target_bundle'])) {
throw new ContentLanguageSettingsException('Attempt to create content language settings without a target_bundle.');
}
parent::__construct($values, $entity_type);
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->target_entity_type_id . '.' . $this->target_bundle;
}
/**
* {@inheritdoc}
*/
public function getTargetEntityTypeId() {
return $this->target_entity_type_id;
}
/**
* {@inheritdoc}
*/
public function getTargetBundle() {
return $this->target_bundle;
}
/**
* {@inheritdoc}
*/
public function setTargetBundle($target_bundle) {
$this->target_bundle = $target_bundle;
return $this;
}
/**
* {@inheritdoc}
*/
public function setDefaultLangcode($default_langcode) {
$this->default_langcode = $default_langcode;
return $this;
}
/**
* {@inheritdoc}
*/
public function getDefaultLangcode() {
return $this->default_langcode;
}
/**
* {@inheritdoc}
*/
public function setLanguageAlterable($language_alterable) {
$this->language_alterable = $language_alterable;
return $this;
}
/**
* {@inheritdoc}
*/
public function isLanguageAlterable() {
return $this->language_alterable;
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
$this->id = $this->id();
parent::preSave($storage);
}
/**
* {@inheritdoc}
*/
public function isDefaultConfiguration() {
return (!$this->language_alterable && $this->default_langcode == LanguageInterface::LANGCODE_SITE_DEFAULT);
}
/**
* Loads a content language config entity based on the entity type and bundle.
*
* @param string $entity_type_id
* ID of the entity type.
* @param string $bundle
* Bundle name.
*
* @return $this
* The content language config entity if one exists. Otherwise, returns
* default values.
*/
public static function loadByEntityTypeBundle($entity_type_id, $bundle) {
if ($entity_type_id == NULL || $bundle == NULL) {
return NULL;
}
$config = \Drupal::entityManager()->getStorage('language_content_settings')->load($entity_type_id . '.' . $bundle);
if ($config == NULL) {
$config = ContentLanguageSettings::create(['target_entity_type_id' => $entity_type_id, 'target_bundle' => $bundle]);
}
return $config;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
// Create dependency on the bundle.
$entity_type = \Drupal::entityManager()->getDefinition($this->target_entity_type_id);
$bundle_config_dependency = $entity_type->getBundleConfigDependency($this->target_bundle);
$this->addDependency($bundle_config_dependency['type'], $bundle_config_dependency['name']);
return $this;
}
}

View file

@ -0,0 +1,148 @@
<?php
namespace Drupal\language\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageDefault;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Drupal\language\ConfigurableLanguageManager;
use Drupal\language\HttpKernel\PathProcessorLanguage;
use Drupal\language\LanguageNegotiatorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Deletes the container if default language has changed.
*/
class ConfigSubscriber implements EventSubscriberInterface {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The default language.
*
* @var \Drupal\Core\Language\LanguageDefault
*/
protected $languageDefault;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The language negotiator.
*
* @var \Drupal\language\LanguageNegotiatorInterface
*/
protected $languageNegotiator;
/**
* The language path processor.
*
* @var \Drupal\language\HttpKernel\PathProcessorLanguage
*/
protected $pathProcessorLanguage;
/**
* Constructs a new class object.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Language\LanguageDefault $language_default
* The default language.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\language\LanguageNegotiatorInterface $language_negotiator
* The language negotiator.
*/
public function __construct(LanguageManagerInterface $language_manager, LanguageDefault $language_default, ConfigFactoryInterface $config_factory, LanguageNegotiatorInterface $language_negotiator) {
$this->languageManager = $language_manager;
$this->languageDefault = $language_default;
$this->configFactory = $config_factory;
$this->languageNegotiator = $language_negotiator;
}
/**
* Causes the container to be rebuilt on the next request.
*
* This event subscriber assumes that the new default langcode and old default
* langcode are valid langcodes. If the schema definition of either
* system.site:default_langcode or language.negotiation::url.prefixes changes
* then this event must be changed to work with both the old and new schema
* definition so this event is update safe.
*
* @param ConfigCrudEvent $event
* The configuration event.
*/
public function onConfigSave(ConfigCrudEvent $event) {
$saved_config = $event->getConfig();
if ($saved_config->getName() == 'system.site' && $event->isChanged('default_langcode')) {
$new_default_langcode = $saved_config->get('default_langcode');
$default_language = $this->configFactory->get('language.entity.' . $new_default_langcode);
// During an import the language might not exist yet.
if (!$default_language->isNew()) {
$this->languageDefault->set(new Language($default_language->get()));
$this->languageManager->reset();
// Directly update language negotiation settings instead of calling
// language_negotiation_url_prefixes_update() to ensure that the code
// obeys the hook_update_N() restrictions.
$negotiation_config = $this->configFactory->getEditable('language.negotiation');
$negotiation_changed = FALSE;
$url_prefixes = $negotiation_config->get('url.prefixes');
$old_default_langcode = $saved_config->getOriginal('default_langcode');
if (empty($url_prefixes[$old_default_langcode])) {
$negotiation_config->set('url.prefixes.' . $old_default_langcode, $old_default_langcode);
$negotiation_changed = TRUE;
}
if (empty($url_prefixes[$new_default_langcode])) {
$negotiation_config->set('url.prefixes.' . $new_default_langcode, '');
$negotiation_changed = TRUE;
}
if ($negotiation_changed) {
$negotiation_config->save(TRUE);
}
}
// Trigger a container rebuild on the next request by invalidating it.
ConfigurableLanguageManager::rebuildServices();
}
elseif ($saved_config->getName() == 'language.types' && $event->isChanged('negotiation')) {
// If the negotiation configuration changed the language negotiator and
// the language path processor have to be reset so that they regenerate
// the method instances and also sort them accordingly to the new config.
$this->languageNegotiator->reset();
if (isset($this->pathProcessorLanguage)) {
$this->pathProcessorLanguage->reset();
}
}
}
/**
* Injects the language path processors on multilingual site configuration.
*
* @param \Drupal\language\HttpKernel\PathProcessorLanguage $path_processor_language
* The language path processor.
*/
public function setPathProcessorLanguage(PathProcessorLanguage $path_processor_language) {
$this->pathProcessorLanguage = $path_processor_language;
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[ConfigEvents::SAVE][] = array('onConfigSave', 0);
return $events;
}
}

View file

@ -0,0 +1,100 @@
<?php
namespace Drupal\language\EventSubscriber;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Drupal\language\LanguageNegotiatorInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Sets the $request property on the language manager.
*/
class LanguageRequestSubscriber implements EventSubscriberInterface {
/**
* The language manager service.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The language negotiator.
*
* @var \Drupal\language\LanguageNegotiatorInterface
*/
protected $negotiator;
/**
* The translation service.
*
* @var \Drupal\Core\StringTranslation\Translator\TranslatorInterface;
*/
protected $translation;
/**
* The current active user.
*
* @return \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a LanguageRequestSubscriber object.
*
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language manager service.
* @param \Drupal\language\LanguageNegotiatorInterface $negotiator
* The language negotiator.
* @param \Drupal\Core\StringTranslation\Translator\TranslatorInterface $translation;
* The translation service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current active user.
*/
public function __construct(ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, TranslatorInterface $translation, AccountInterface $current_user) {
$this->languageManager = $language_manager;
$this->negotiator = $negotiator;
$this->translation = $translation;
$this->currentUser = $current_user;
}
/**
* Sets the request on the language manager.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function onKernelRequestLanguage(GetResponseEvent $event) {
if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
$request = $event->getRequest();
$this->negotiator->setCurrentUser($this->currentUser);
$this->negotiator->reset();
if ($this->languageManager instanceof ConfigurableLanguageManagerInterface) {
$this->languageManager->setNegotiator($this->negotiator);
$this->languageManager->setConfigOverrideLanguage($this->languageManager->getCurrentLanguage());
}
// After the language manager has initialized, set the default langcode
// for the string translations.
$langcode = $this->languageManager->getCurrentLanguage()->getId();
$this->translation->setDefaultLangcode($langcode);
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onKernelRequestLanguage', 255);
return $events;
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace Drupal\language\Exception;
/**
* Exception thrown when deleting the default language.
*/
class DeleteDefaultLanguageException extends LanguageException {}

View file

@ -0,0 +1,8 @@
<?php
namespace Drupal\language\Exception;
/**
* A base exception thrown in any language system operations.
*/
class LanguageException extends \RuntimeException {}

View file

@ -0,0 +1,157 @@
<?php
namespace Drupal\language\Form;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure the content language settings for this site.
*/
class ContentLanguageSettingsForm extends FormBase {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a ContentLanguageSettingsForm object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_content_settings_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$entity_types = $this->entityManager->getDefinitions();
$labels = array();
$default = array();
$bundles = $this->entityManager->getAllBundleInfo();
$language_configuration = array();
foreach ($entity_types as $entity_type_id => $entity_type) {
if (!$entity_type instanceof ContentEntityTypeInterface || !$entity_type->hasKey('langcode') || !isset($bundles[$entity_type_id])) {
continue;
}
$labels[$entity_type_id] = $entity_type->getLabel() ?: $entity_type_id;
$default[$entity_type_id] = FALSE;
// Check whether we have any custom setting.
foreach ($bundles[$entity_type_id] as $bundle => $bundle_info) {
$config = ContentLanguageSettings::loadByEntityTypeBundle($entity_type_id, $bundle);
if (!$config->isDefaultConfiguration()) {
$default[$entity_type_id] = $entity_type_id;
}
$language_configuration[$entity_type_id][$bundle] = $config;
}
}
asort($labels);
$form = array(
'#labels' => $labels,
'#attached' => array(
'library' => array(
'language/drupal.language.admin',
),
),
'#attributes' => array(
'class' => 'language-content-settings-form',
),
);
$form['entity_types'] = array(
'#title' => $this->t('Custom language settings'),
'#type' => 'checkboxes',
'#options' => $labels,
'#default_value' => $default,
);
$form['settings'] = array('#tree' => TRUE);
foreach ($labels as $entity_type_id => $label) {
$entity_type = $entity_types[$entity_type_id];
$form['settings'][$entity_type_id] = array(
'#title' => $label,
'#type' => 'container',
'#entity_type' => $entity_type_id,
'#theme' => 'language_content_settings_table',
'#bundle_label' => $entity_type->getBundleLabel() ?: $label,
'#states' => array(
'visible' => array(
':input[name="entity_types[' . $entity_type_id . ']"]' => array('checked' => TRUE),
),
),
);
foreach ($bundles[$entity_type_id] as $bundle => $bundle_info) {
$form['settings'][$entity_type_id][$bundle]['settings'] = array(
'#type' => 'item',
'#label' => $bundle_info['label'],
'language' => array(
'#type' => 'language_configuration',
'#entity_information' => array(
'entity_type' => $entity_type_id,
'bundle' => $bundle,
),
'#default_value' => $language_configuration[$entity_type_id][$bundle],
),
);
}
}
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save configuration'),
'#button_type' => 'primary',
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
foreach ($form_state->getValue('settings') as $entity_type => $entity_settings) {
foreach ($entity_settings as $bundle => $bundle_settings) {
$config = ContentLanguageSettings::loadByEntityTypeBundle($entity_type, $bundle);
$config->setDefaultLangcode($bundle_settings['settings']['language']['langcode'])
->setLanguageAlterable($bundle_settings['settings']['language']['language_alterable'])
->save();
}
}
drupal_set_message($this->t('Settings successfully updated.'));
}
}

View file

@ -0,0 +1,165 @@
<?php
namespace Drupal\language\Form;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Controller for language addition forms.
*/
class LanguageAddForm extends LanguageFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
// @todo Remove in favour of base method.
return 'language_admin_add_form';
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form['#title'] = $this->t('Add language');
$predefined_languages = $this->languageManager->getStandardLanguageListWithoutConfigured();
$predefined_languages['custom'] = $this->t('Custom language...');
$predefined_default = $form_state->getValue('predefined_langcode', key($predefined_languages));
$form['predefined_langcode'] = array(
'#type' => 'select',
'#title' => $this->t('Language name'),
'#default_value' => $predefined_default,
'#options' => $predefined_languages,
);
$form['predefined_submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Add language'),
'#name' => 'add_language',
'#limit_validation_errors' => array(array('predefined_langcode'), array('predefined_submit')),
'#states' => array(
'invisible' => array(
'select#edit-predefined-langcode' => array('value' => 'custom'),
),
),
'#validate' => array('::validatePredefined'),
'#submit' => array('::submitForm', '::save'),
'#button_type' => 'primary',
);
$custom_language_states_conditions = array(
'select#edit-predefined-langcode' => array('value' => 'custom'),
);
$form['custom_language'] = array(
'#type' => 'container',
'#states' => array(
'visible' => $custom_language_states_conditions,
),
);
$this->commonForm($form['custom_language']);
$form['custom_language']['langcode']['#states'] = array(
'required' => $custom_language_states_conditions,
);
$form['custom_language']['label']['#states'] = array(
'required' => $custom_language_states_conditions,
);
$form['custom_language']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Add custom language'),
'#name' => 'add_custom_language',
'#validate' => array('::validateCustom'),
'#submit' => array('::submitForm', '::save'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
parent::save($form, $form_state);
$t_args = array('%language' => $this->entity->label(), '%langcode' => $this->entity->id());
$this->logger('language')->notice('The %language (%langcode) language has been created.', $t_args);
drupal_set_message($this->t('The language %language has been created and can now be used.', $t_args));
if ($this->moduleHandler->moduleExists('block')) {
// Tell the user they have the option to add a language switcher block
// to their theme so they can switch between the languages.
drupal_set_message($this->t('Use one of the language switcher blocks to allow site visitors to switch between languages. You can enable these blocks on the <a href=":block-admin">block administration page</a>.', array(':block-admin' => $this->url('block.admin_display'))));
}
$form_state->setRedirectUrl($this->entity->urlInfo('collection'));
}
/**
* {@inheritdoc}
*/
public function actions(array $form, FormStateInterface $form_state) {
// No actions needed.
return array();
}
/**
* Validates the language addition form on custom language button.
*/
public function validateCustom(array $form, FormStateInterface $form_state) {
if ($form_state->getValue('predefined_langcode') == 'custom') {
$langcode = $form_state->getValue('langcode');
// Reuse the editing form validation routine if we add a custom language.
$this->validateCommon($form['custom_language'], $form_state);
if ($language = $this->languageManager->getLanguage($langcode)) {
$form_state->setErrorByName('langcode', $this->t('The language %language (%langcode) already exists.', array('%language' => $language->getName(), '%langcode' => $langcode)));
}
}
else {
$form_state->setErrorByName('predefined_langcode', $this->t('Use the <em>Add language</em> button to save a predefined language.'));
}
}
/**
* Element specific validator for the Add language button.
*/
public function validatePredefined($form, FormStateInterface $form_state) {
$langcode = $form_state->getValue('predefined_langcode');
if ($langcode == 'custom') {
$form_state->setErrorByName('predefined_langcode', $this->t('Fill in the language details and save the language with <em>Add custom language</em>.'));
}
else {
if ($language = $this->languageManager->getLanguage($langcode)) {
$form_state->setErrorByName('predefined_langcode', $this->t('The language %language (%langcode) already exists.', array('%language' => $language->getName(), '%langcode' => $langcode)));
}
}
}
/**
* {@inheritdoc}
*/
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
$langcode = $form_state->getValue('predefined_langcode');
if ($langcode == 'custom') {
$langcode = $form_state->getValue('langcode');
$label = $form_state->getValue('label');
$direction = $form_state->getValue('direction');
}
else {
$standard_languages = LanguageManager::getStandardLanguageList();
$label = $standard_languages[$langcode][0];
$direction = isset($standard_languages[$langcode][2]) ? $standard_languages[$langcode][2] : ConfigurableLanguage::DIRECTION_LTR;
}
$entity->set('id', $langcode);
$entity->set('label', $label);
$entity->set('direction', $direction);
// There is no weight on the edit form. Fetch all configurable languages
// ordered by weight and set the new language to be placed after them.
$languages = \Drupal::languageManager()->getLanguages(ConfigurableLanguage::STATE_CONFIGURABLE);
$last_language = end($languages);
$entity->setWeight($last_language->getWeight() + 1);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\language\Form;
use Drupal\Core\Entity\EntityDeleteForm;
/**
* Defines a confirmation form for deleting a language entity.
*/
class LanguageDeleteForm extends EntityDeleteForm {
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('Deleting a language will remove all interface translations associated with it, and content in this language will be set to be language neutral. This action cannot be undone.');
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_delete_form';
}
/**
* {@inheritdoc}
*/
protected function getDeletionMessage() {
return $this->t('The %language (%langcode) language has been removed.', array('%language' => $this->entity->label(), '%langcode' => $this->entity->id()));
}
/**
* {@inheritdoc}
*/
public function logDeletionMessage() {
$this->logger('language')->notice('The %language (%langcode) language has been removed.', array('%language' => $this->entity->label(), '%langcode' => $this->entity->id()));
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\language\Form;
use Drupal\Core\Form\FormStateInterface;
/**
* Controller for language edit forms.
*/
class LanguageEditForm extends LanguageFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
// @todo Remove in favour of base method.
return 'language_admin_edit_form';
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$this->commonForm($form);
return parent::form($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function actions(array $form, FormStateInterface $form_state) {
$actions['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save language'),
'#validate' => array('::validateCommon'),
'#submit' => array('::submitForm', '::save'),
);
return $actions;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
parent::save($form, $form_state);
$form_state->setRedirectUrl($this->entity->urlInfo('collection'));
$this->logger('language')->notice('The %language (%langcode) language has been updated.', array('%language' => $this->entity->label(), '%langcode' => $this->entity->id()));
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace Drupal\language\Form;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base form for language add and edit forms.
*/
abstract class LanguageFormBase extends EntityForm {
/**
* The configurable language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a ContentEntityForm object.
*
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The configurable language manager.
*/
public function __construct(ConfigurableLanguageManagerInterface $language_manager) {
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('language_manager')
);
}
/**
* Common elements of the language addition and editing form.
*/
public function commonForm(array &$form) {
/* @var $language \Drupal\language\ConfigurableLanguageInterface */
$language = $this->entity;
if ($language->getId()) {
$form['langcode_view'] = array(
'#type' => 'item',
'#title' => $this->t('Language code'),
'#markup' => $language->id()
);
$form['langcode'] = array(
'#type' => 'value',
'#value' => $language->id()
);
}
else {
$form['langcode'] = array(
'#type' => 'textfield',
'#title' => $this->t('Language code'),
'#maxlength' => 12,
'#required' => TRUE,
'#default_value' => '',
'#disabled' => FALSE,
'#description' => $this->t('Use language codes as <a href=":w3ctags">defined by the W3C</a> for interoperability. <em>Examples: "en", "en-gb" and "zh-hant".</em>', array(':w3ctags' => 'http://www.w3.org/International/articles/language-tags/')),
);
}
$form['label'] = array(
'#type' => 'textfield',
'#title' => $this->t('Language name'),
'#maxlength' => 64,
'#default_value' => $language->label(),
'#required' => TRUE,
);
$form['direction'] = array(
'#type' => 'radios',
'#title' => $this->t('Direction'),
'#required' => TRUE,
'#description' => $this->t('Direction that text in this language is presented.'),
'#default_value' => $language->getDirection(),
'#options' => array(
LanguageInterface::DIRECTION_LTR => $this->t('Left to right'),
LanguageInterface::DIRECTION_RTL => $this->t('Right to left'),
),
);
return $form;
}
/**
* Validates the language editing element.
*/
public function validateCommon(array $form, FormStateInterface $form_state) {
// Ensure sane field values for langcode and name.
if (!isset($form['langcode_view']) && !preg_match('@^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$@', $form_state->getValue('langcode'))) {
$form_state->setErrorByName('langcode', $this->t('%field must be a valid language tag as <a href=":url">defined by the W3C</a>.', array(
'%field' => $form['langcode']['#title'],
':url' => 'http://www.w3.org/International/articles/language-tags/',
)));
}
if ($form_state->getValue('label') != Html::escape($form_state->getValue('label'))) {
$form_state->setErrorByName('label', $this->t('%field cannot contain any markup.', array('%field' => $form['label']['#title'])));
}
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace Drupal\language\Form;
use Drupal\Core\Form\ConfigFormBaseTrait;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Defines a confirmation form for deleting a browser language negotiation mapping.
*/
class NegotiationBrowserDeleteForm extends ConfirmFormBase {
use ConfigFormBaseTrait;
/**
* The browser language code to be deleted.
*
* @var string
*/
protected $browserLangcode;
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.mappings'];
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete %browser_langcode?', array('%browser_langcode' => $this->browserLangcode));
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('language.negotiation_browser');
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_browser_delete_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $browser_langcode = NULL) {
$this->browserLangcode = $browser_langcode;
$form = parent::buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('language.mappings')
->clear('map.' . $this->browserLangcode)
->save();
$args = array(
'%browser' => $this->browserLangcode,
);
$this->logger('language')->notice('The browser language detection mapping for the %browser browser language code has been deleted.', $args);
drupal_set_message($this->t('The mapping for the %browser browser language code has been deleted.', $args));
$form_state->setRedirect('language.negotiation_browser');
}
}

View file

@ -0,0 +1,215 @@
<?php
namespace Drupal\language\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure the browser language negotiation method for this site.
*/
class NegotiationBrowserForm extends ConfigFormBase {
/**
* The configurable language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* {@inheritdoc}
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler
*/
public function __construct(ConfigFactoryInterface $config_factory, ConfigurableLanguageManagerInterface $language_manager ) {
parent::__construct($config_factory);
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_browser_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.mappings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = array();
// Initialize a language list to the ones available, including English.
$languages = $this->languageManager->getLanguages();
$existing_languages = array();
foreach ($languages as $langcode => $language) {
$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();
}
else {
$language_options = array(
(string) $this->t('Existing languages') => $existing_languages,
(string) $this->t('Languages not yet added') => $this->languageManager->getStandardLanguageListWithoutConfigured(),
);
}
$form['mappings'] = [
'#type' => 'table',
'#header' => [
$this->t('Browser language code'),
$this->t('Site language'),
$this->t('Operations'),
],
'#attributes' => ['id' => 'language-negotiation-browser'],
'#empty' => $this->t('No browser language mappings available.'),
];
$mappings = $this->language_get_browser_drupal_langcode_mappings();
foreach ($mappings as $browser_langcode => $drupal_langcode) {
$form['mappings'][$browser_langcode] = array(
'browser_langcode' => array(
'#title' => $this->t('Browser language code'),
'#title_display' => 'invisible',
'#type' => 'textfield',
'#default_value' => $browser_langcode,
'#size' => 20,
'#required' => TRUE,
),
'drupal_langcode' => array(
'#title' => $this->t('Site language'),
'#title_display' => 'invisible',
'#type' => 'select',
'#options' => $language_options,
'#default_value' => $drupal_langcode,
'#required' => TRUE,
),
);
// Operations column.
$form['mappings'][$browser_langcode]['operations'] = [
'#type' => 'operations',
'#links' => [],
];
$form['mappings'][$browser_langcode]['operations']['#links']['delete'] = [
'title' => $this->t('Delete'),
'url' => Url::fromRoute('language.negotiation_browser_delete', ['browser_langcode' => $browser_langcode]),
];
}
// Add empty row.
$form['new_mapping'] = array(
'#type' => 'details',
'#title' => $this->t('Add a new mapping'),
'#tree' => TRUE,
);
$form['new_mapping']['browser_langcode'] = array(
'#type' => 'textfield',
'#title' => $this->t('Browser language code'),
'#description' => $this->t('Use language codes as <a href=":w3ctags">defined by the W3C</a> for interoperability. <em>Examples: "en", "en-gb" and "zh-hant".</em>', array(':w3ctags' => 'http://www.w3.org/International/articles/language-tags/')),
'#size' => 20,
);
$form['new_mapping']['drupal_langcode'] = array(
'#type' => 'select',
'#title' => $this->t('Site language'),
'#options' => $language_options,
);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// Array to check if all browser language codes are unique.
$unique_values = array();
// Check all mappings.
if ($form_state->hasValue('mappings')) {
$mappings = $form_state->getValue('mappings');
foreach ($mappings as $key => $data) {
// Make sure browser_langcode is unique.
if (array_key_exists($data['browser_langcode'], $unique_values)) {
$form_state->setErrorByName('mappings][new_mapping][browser_langcode', $this->t('Browser language codes must be unique.'));
}
elseif (preg_match('/[^a-z\-]/', $data['browser_langcode'])) {
$form_state->setErrorByName('mappings][new_mapping][browser_langcode', $this->t('Browser language codes can only contain lowercase letters and a hyphen(-).'));
}
$unique_values[$data['browser_langcode']] = $data['drupal_langcode'];
}
}
// Check new mapping.
$data = $form_state->getValue('new_mapping');
if (!empty($data['browser_langcode'])) {
// Make sure browser_langcode is unique.
if (array_key_exists($data['browser_langcode'], $unique_values)) {
$form_state->setErrorByName('mappings][' . $key . '][browser_langcode', $this->t('Browser language codes must be unique.'));
}
elseif (preg_match('/[^a-z\-]/', $data['browser_langcode'])) {
$form_state->setErrorByName('mappings][' . $key . '][browser_langcode', $this->t('Browser language codes can only contain lowercase letters and a hyphen(-).'));
}
$unique_values[$data['browser_langcode']] = $data['drupal_langcode'];
}
$form_state->set('mappings', $unique_values);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$mappings = $form_state->get('mappings');
if (!empty($mappings)) {
$config = $this->config('language.mappings');
$config->setData(['map' => $mappings]);
$config->save();
}
parent::submitForm($form, $form_state);
}
/**
* Retrieves the browser's langcode mapping configuration array.
*
* @return array
* The browser's langcode mapping configuration array.
*/
protected function language_get_browser_drupal_langcode_mappings() {
$config = $this->config('language.mappings');
if ($config->isNew()) {
return array();
}
return $config->get('map');
}
}

View file

@ -0,0 +1,337 @@
<?php
namespace Drupal\language\Form;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Drupal\language\LanguageNegotiatorInterface;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure the selected language negotiation method for this site.
*/
class NegotiationConfigureForm extends ConfigFormBase {
/**
* Stores the configuration object for language.types.
*
* @var \Drupal\Core\Config\Config
*/
protected $languageTypes;
/**
* The language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The language negotiator.
*
* @var \Drupal\language\LanguageNegotiatorInterface
*/
protected $negotiator;
/**
* The block manager.
*
* @var \Drupal\Core\Block\BlockManagerInterface
*/
protected $blockManager;
/**
* The block storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface|null
*/
protected $blockStorage;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* Constructs a NegotiationConfigureForm object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\language\LanguageNegotiatorInterface $negotiator
* The language negotiation methods manager.
* @param \Drupal\Core\Block\BlockManagerInterface $block_manager
* The block manager.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Drupal\Core\Entity\EntityStorageInterface $block_storage
* The block storage, or NULL if not available.
*/
public function __construct(ConfigFactoryInterface $config_factory, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, BlockManagerInterface $block_manager, ThemeHandlerInterface $theme_handler, EntityStorageInterface $block_storage = NULL) {
parent::__construct($config_factory);
$this->languageTypes = $this->config('language.types');
$this->languageManager = $language_manager;
$this->negotiator = $negotiator;
$this->blockManager = $block_manager;
$this->themeHandler = $theme_handler;
$this->blockStorage = $block_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$entity_manager = $container->get('entity.manager');
$block_storage = $entity_manager->hasHandler('block', 'storage') ? $entity_manager->getStorage('block') : NULL;
return new static(
$container->get('config.factory'),
$container->get('language_manager'),
$container->get('language_negotiator'),
$container->get('plugin.manager.block'),
$container->get('theme_handler'),
$block_storage
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.types'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$configurable = $this->languageTypes->get('configurable');
$form = array(
'#theme' => 'language_negotiation_configure_form',
'#language_types_info' => $this->languageManager->getDefinedLanguageTypesInfo(),
'#language_negotiation_info' => $this->negotiator->getNegotiationMethods(),
);
$form['#language_types'] = array();
foreach ($form['#language_types_info'] as $type => $info) {
// Show locked language types only if they are configurable.
if (empty($info['locked']) || in_array($type, $configurable)) {
$form['#language_types'][] = $type;
}
}
foreach ($form['#language_types'] as $type) {
$this->configureFormTable($form, $type);
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#button_type' => 'primary',
'#value' => $this->t('Save settings'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$configurable_types = $form['#language_types'];
$stored_values = $this->languageTypes->get('configurable');
$customized = array();
$method_weights_type = array();
foreach ($configurable_types as $type) {
$customized[$type] = in_array($type, $stored_values);
$method_weights = array();
$enabled_methods = $form_state->getValue(array($type, 'enabled'));
$enabled_methods[LanguageNegotiationSelected::METHOD_ID] = TRUE;
$method_weights_input = $form_state->getValue(array($type, 'weight'));
if ($form_state->hasValue(array($type, 'configurable'))) {
$customized[$type] = !$form_state->isValueEmpty(array($type, 'configurable'));
}
foreach ($method_weights_input as $method_id => $weight) {
if ($enabled_methods[$method_id]) {
$method_weights[$method_id] = $weight;
}
}
$method_weights_type[$type] = $method_weights;
$this->languageTypes->set('negotiation.' . $type . '.method_weights', $method_weights_input)->save();
}
// Update non-configurable language types and the related language
// negotiation configuration.
$this->negotiator->updateConfiguration(array_keys(array_filter($customized)));
// Update the language negotiations after setting the configurability.
foreach ($method_weights_type as $type => $method_weights) {
$this->negotiator->saveConfiguration($type, $method_weights);
}
// Clear block definitions cache since the available blocks and their names
// may have been changed based on the configurable types.
if ($this->blockStorage) {
// If there is an active language switcher for a language type that has
// been made not configurable, deactivate it first.
$non_configurable = array_keys(array_diff($customized, array_filter($customized)));
$this->disableLanguageSwitcher($non_configurable);
}
$this->blockManager->clearCachedDefinitions();
$form_state->setRedirect('language.negotiation');
drupal_set_message($this->t('Language detection configuration saved.'));
}
/**
* Builds a language negotiation method configuration table.
*
* @param array $form
* The language negotiation configuration form.
* @param string $type
* The language type to generate the table for.
*/
protected function configureFormTable(array &$form, $type) {
$info = $form['#language_types_info'][$type];
$table_form = array(
'#title' => $this->t('@type language detection', array('@type' => $info['name'])),
'#tree' => TRUE,
'#description' => $info['description'],
'#language_negotiation_info' => array(),
'#show_operations' => FALSE,
'weight' => array('#tree' => TRUE),
);
// Only show configurability checkbox for the unlocked language types.
if (empty($info['locked'])) {
$configurable = $this->languageTypes->get('configurable');
$table_form['configurable'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Customize %language_name language detection to differ from Interface text language detection settings', array('%language_name' => $info['name'])),
'#default_value' => in_array($type, $configurable),
'#attributes' => array('class' => array('language-customization-checkbox')),
'#attached' => array(
'library' => array(
'language/drupal.language.admin'
),
),
);
}
$negotiation_info = $form['#language_negotiation_info'];
$enabled_methods = $this->languageTypes->get('negotiation.' . $type . '.enabled') ?: array();
$methods_weight = $this->languageTypes->get('negotiation.' . $type . '.method_weights') ?: array();
// Add missing data to the methods lists.
foreach ($negotiation_info as $method_id => $method) {
if (!isset($methods_weight[$method_id])) {
$methods_weight[$method_id] = isset($method['weight']) ? $method['weight'] : 0;
}
}
// Order methods list by weight.
asort($methods_weight);
foreach ($methods_weight as $method_id => $weight) {
// A language method might be no more available if the defining module has
// been disabled after the last configuration saving.
if (!isset($negotiation_info[$method_id])) {
continue;
}
$enabled = isset($enabled_methods[$method_id]);
$method = $negotiation_info[$method_id];
// List the method only if the current type is defined in its 'types' key.
// If it is not defined default to all the configurable language types.
$types = array_flip(isset($method['types']) ? $method['types'] : $form['#language_types']);
if (isset($types[$type])) {
$table_form['#language_negotiation_info'][$method_id] = $method;
$method_name = $method['name'];
$table_form['weight'][$method_id] = array(
'#type' => 'weight',
'#title' => $this->t('Weight for @title language detection method', array('@title' => Unicode::strtolower($method_name))),
'#title_display' => 'invisible',
'#default_value' => $weight,
'#attributes' => array('class' => array("language-method-weight-$type")),
'#delta' => 20,
);
$table_form['title'][$method_id] = array('#plain_text' => $method_name);
$table_form['enabled'][$method_id] = array(
'#type' => 'checkbox',
'#title' => $this->t('Enable @title language detection method', array('@title' => Unicode::strtolower($method_name))),
'#title_display' => 'invisible',
'#default_value' => $enabled,
);
if ($method_id === LanguageNegotiationSelected::METHOD_ID) {
$table_form['enabled'][$method_id]['#default_value'] = TRUE;
$table_form['enabled'][$method_id]['#attributes'] = array('disabled' => 'disabled');
}
$table_form['description'][$method_id] = array('#markup' => $method['description']);
$config_op = array();
if (isset($method['config_route_name'])) {
$config_op['configure'] = array(
'title' => $this->t('Configure'),
'url' => Url::fromRoute($method['config_route_name']),
);
// If there is at least one operation enabled show the operation
// column.
$table_form['#show_operations'] = TRUE;
}
$table_form['operation'][$method_id] = array(
'#type' => 'operations',
'#links' => $config_op,
);
}
}
$form[$type] = $table_form;
}
/**
* Disables the language switcher blocks.
*
* @param array $language_types
* An array containing all language types whose language switchers need to
* be disabled.
*/
protected function disableLanguageSwitcher(array $language_types) {
$theme = $this->themeHandler->getDefault();
$blocks = $this->blockStorage->loadByProperties(array('theme' => $theme));
foreach ($language_types as $language_type) {
foreach ($blocks as $block) {
if ($block->getPluginId() == 'language_block:' . $language_type) {
$block->delete();
}
}
}
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace Drupal\language\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Form\ConfigFormBase;
/**
* Configure the selected language negotiation method for this site.
*/
class NegotiationSelectedForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_selected_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.negotiation'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('language.negotiation');
$form['selected_langcode'] = array(
'#type' => 'language_select',
'#title' => $this->t('Language'),
'#languages' => LanguageInterface::STATE_CONFIGURABLE | LanguageInterface::STATE_SITE_DEFAULT,
'#default_value' => $config->get('selected_langcode'),
);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('language.negotiation')
->set('selected_langcode', $form_state->getValue('selected_langcode'))
->save();
parent::submitForm($form, $form_state);
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\language\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configure the session language negotiation method for this site.
*/
class NegotiationSessionForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_session_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.negotiation'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('language.negotiation');
$form['language_negotiation_session_param'] = array(
'#title' => $this->t('Request/session parameter'),
'#type' => 'textfield',
'#default_value' => $config->get('session.parameter'),
'#description' => $this->t('Name of the request/session parameter used to determine the desired language.'),
);
$form_state->setRedirect('language.negotiation');
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('language.negotiation')
->set('session.parameter', $form_state->getValue('language_negotiation_session_param'))
->save();
parent::submitForm($form, $form_state);
}
}

View file

@ -0,0 +1,217 @@
<?php
namespace Drupal\language\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
/**
* Configure the URL language negotiation method for this site.
*/
class NegotiationUrlForm extends ConfigFormBase {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new NegotiationUrlForm object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager) {
parent::__construct($config_factory);
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_url_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.negotiation'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
global $base_url;
$config = $this->config('language.negotiation');
$form['language_negotiation_url_part'] = array(
'#title' => $this->t('Part of the URL that determines language'),
'#type' => 'radios',
'#options' => array(
LanguageNegotiationUrl::CONFIG_PATH_PREFIX => $this->t('Path prefix'),
LanguageNegotiationUrl::CONFIG_DOMAIN => $this->t('Domain'),
),
'#default_value' => $config->get('url.source'),
);
$form['prefix'] = array(
'#type' => 'details',
'#tree' => TRUE,
'#title' => $this->t('Path prefix configuration'),
'#open' => TRUE,
'#description' => $this->t('Language codes or other custom text to use as a path prefix for URL language detection. For the selected fallback language, this value may be left blank. <strong>Modifying this value may break existing URLs. Use with caution in a production environment.</strong> Example: Specifying "deutsch" as the path prefix code for German results in URLs like "example.com/deutsch/contact".'),
'#states' => array(
'visible' => array(
':input[name="language_negotiation_url_part"]' => array(
'value' => (string) LanguageNegotiationUrl::CONFIG_PATH_PREFIX,
),
),
),
);
$form['domain'] = array(
'#type' => 'details',
'#tree' => TRUE,
'#title' => $this->t('Domain configuration'),
'#open' => TRUE,
'#description' => $this->t('The domain names to use for these languages. <strong>Modifying this value may break existing URLs. Use with caution in a production environment.</strong> Example: Specifying "de.example.com" as language domain for German will result in a URL like "http://de.example.com/contact".'),
'#states' => array(
'visible' => array(
':input[name="language_negotiation_url_part"]' => array(
'value' => (string) LanguageNegotiationUrl::CONFIG_DOMAIN,
),
),
),
);
$languages = $this->languageManager->getLanguages();
$prefixes = $config->get('url.prefixes');
$domains = $config->get('url.domains');
foreach ($languages as $langcode => $language) {
$t_args = array('%language' => $language->getName(), '%langcode' => $language->getId());
$form['prefix'][$langcode] = array(
'#type' => 'textfield',
'#title' => $language->isDefault() ? $this->t('%language (%langcode) path prefix (Default language)', $t_args) : $this->t('%language (%langcode) path prefix', $t_args),
'#maxlength' => 64,
'#default_value' => isset($prefixes[$langcode]) ? $prefixes[$langcode] : '',
'#field_prefix' => $base_url . '/',
);
$form['domain'][$langcode] = array(
'#type' => 'textfield',
'#title' => $this->t('%language (%langcode) domain', array('%language' => $language->getName(), '%langcode' => $language->getId())),
'#maxlength' => 128,
'#default_value' => isset($domains[$langcode]) ? $domains[$langcode] : '',
);
}
$form_state->setRedirect('language.negotiation');
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$languages = $this->languageManager->getLanguages();
// Count repeated values for uniqueness check.
$count = array_count_values($form_state->getValue('prefix'));
$default_langcode = $this->config('language.negotiation')->get('selected_langcode');
if ($default_langcode == LanguageInterface::LANGCODE_SITE_DEFAULT) {
$default_langcode = $this->languageManager->getDefaultLanguage()->getId();
}
foreach ($languages as $langcode => $language) {
$value = $form_state->getValue(array('prefix', $langcode));
if ($value === '') {
if (!($default_langcode == $langcode) && $form_state->getValue('language_negotiation_url_part') == LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
// Throw a form error if the prefix is blank for a non-default language,
// although it is required for selected negotiation type.
$form_state->setErrorByName("prefix][$langcode", $this->t('The prefix may only be left blank for the <a href=":url">selected detection fallback language.</a>', [
':url' => $this->getUrlGenerator()->generate('language.negotiation_selected'),
]));
}
}
elseif (strpos($value, '/') !== FALSE) {
// Throw a form error if the string contains a slash,
// which would not work.
$form_state->setErrorByName("prefix][$langcode", $this->t('The prefix may not contain a slash.'));
}
elseif (isset($count[$value]) && $count[$value] > 1) {
// Throw a form error if there are two languages with the same
// domain/prefix.
$form_state->setErrorByName("prefix][$langcode", $this->t('The prefix for %language, %value, is not unique.', array('%language' => $language->getName(), '%value' => $value)));
}
}
// Count repeated values for uniqueness check.
$count = array_count_values($form_state->getValue('domain'));
foreach ($languages as $langcode => $language) {
$value = $form_state->getValue(array('domain', $langcode));
if ($value === '') {
if ($form_state->getValue('language_negotiation_url_part') == LanguageNegotiationUrl::CONFIG_DOMAIN) {
// Throw a form error if the domain is blank for a non-default language,
// although it is required for selected negotiation type.
$form_state->setErrorByName("domain][$langcode", $this->t('The domain may not be left blank for %language.', array('%language' => $language->getName())));
}
}
elseif (isset($count[$value]) && $count[$value] > 1) {
// Throw a form error if there are two languages with the same
// domain/domain.
$form_state->setErrorByName("domain][$langcode", $this->t('The domain for %language, %value, is not unique.', array('%language' => $language->getName(), '%value' => $value)));
}
}
// Domain names should not contain protocol and/or ports.
foreach ($languages as $langcode => $language) {
$value = $form_state->getValue(array('domain', $langcode));
if (!empty($value)) {
// Ensure we have exactly one protocol when checking the hostname.
$host = 'http://' . str_replace(array('http://', 'https://'), '', $value);
if (parse_url($host, PHP_URL_HOST) != $value) {
$form_state->setErrorByName("domain][$langcode", $this->t('The domain for %language may only contain the domain name, not a trailing slash, protocol and/or port.', ['%language' => $language->getName()]));
}
}
}
parent::validateForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Save selected format (prefix or domain).
$this->config('language.negotiation')
->set('url.source', $form_state->getValue('language_negotiation_url_part'))
// Save new domain and prefix values.
->set('url.prefixes', $form_state->getValue('prefix'))
->set('url.domains', $form_state->getValue('domain'))
->save();
parent::submitForm($form, $form_state);
}
}

View file

@ -0,0 +1,180 @@
<?php
namespace Drupal\language\HttpKernel;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Drupal\language\EventSubscriber\ConfigSubscriber;
use Drupal\language\LanguageNegotiatorInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Session\AccountInterface;
/**
* Processes the inbound path using path alias lookups.
*/
class PathProcessorLanguage implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
/**
* A config factory for retrieving required config settings.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $config;
/**
* Language manager for retrieving the url language type.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The language negotiator.
*
* @var \Drupal\language\LanguageNegotiatorInterface
*/
protected $negotiator;
/**
* Local cache for language path processors.
*
* @var array
*/
protected $processors;
/**
* Flag indicating whether the site is multilingual.
*
* @var bool
*/
protected $multilingual;
/**
* The language configuration event subscriber.
*
* @var \Drupal\language\EventSubscriber\ConfigSubscriber
*/
protected $configSubscriber;
/**
* Constructs a PathProcessorLanguage object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
* A config factory object for retrieving configuration settings.
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The configurable language manager.
* @param \Drupal\language\LanguageNegotiatorInterface $negotiator
* The language negotiator.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current active user.
* @param \Drupal\language\EventSubscriber\ConfigSubscriber $config_subscriber
* The language configuration event subscriber.
*/
public function __construct(ConfigFactoryInterface $config, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, AccountInterface $current_user, ConfigSubscriber $config_subscriber) {
$this->config = $config;
$this->languageManager = $language_manager;
$this->negotiator = $negotiator;
$this->negotiator->setCurrentUser($current_user);
$this->configSubscriber = $config_subscriber;
}
/**
* {@inheritdoc}
*/
public function processInbound($path, Request $request) {
if (!empty($path)) {
$scope = 'inbound';
if (!isset($this->processors[$scope])) {
$this->initProcessors($scope);
}
foreach ($this->processors[$scope] as $instance) {
$path = $instance->processInbound($path, $request);
}
}
return $path;
}
/**
* {@inheritdoc}
*/
public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
if (!isset($this->multilingual)) {
$this->multilingual = $this->languageManager->isMultilingual();
}
if ($this->multilingual) {
$this->negotiator->reset();
$scope = 'outbound';
if (!isset($this->processors[$scope])) {
$this->initProcessors($scope);
}
foreach ($this->processors[$scope] as $instance) {
$path = $instance->processOutbound($path, $options, $request, $bubbleable_metadata);
}
// No language dependent path allowed in this mode.
if (empty($this->processors[$scope])) {
unset($options['language']);
}
}
return $path;
}
/**
* Initializes the local cache for language path processors.
*
* @param string $scope
* The scope of the processors: "inbound" or "outbound".
*/
protected function initProcessors($scope) {
$interface = '\Drupal\Core\PathProcessor\\' . Unicode::ucfirst($scope) . 'PathProcessorInterface';
$this->processors[$scope] = array();
$weights = [];
foreach ($this->languageManager->getLanguageTypes() as $type) {
foreach ($this->negotiator->getNegotiationMethods($type) as $method_id => $method) {
if (!isset($this->processors[$scope][$method_id])) {
$reflector = new \ReflectionClass($method['class']);
if ($reflector->implementsInterface($interface)) {
$this->processors[$scope][$method_id] = $this->negotiator->getNegotiationMethodInstance($method_id);
$weights[$method_id] = $method['weight'];
}
}
}
}
// Sort the processors list, so that their functions are called in the
// order specified by the weight of the methods.
uksort($this->processors[$scope], function ($method_id_a, $method_id_b) use($weights) {
$a_weight = $weights[$method_id_a];
$b_weight = $weights[$method_id_b];
if ($a_weight == $b_weight) {
return 0;
}
return ($a_weight < $b_weight) ? -1 : 1;
});
}
/**
* Initializes the injected event subscriber with the language path processor.
*
* The language path processor service is registered only on multilingual
* site configuration, thus we inject it in the event subscriber only when
* it is initialized.
*/
public function initConfigSubscriber() {
$this->configSubscriber->setPathProcessorLanguage($this);
}
/**
* Resets the collected processors instances.
*/
public function reset() {
$this->processors = array();
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\language;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the access control handler for the language entity type.
*
* @see \Drupal\language\Entity\Language
*/
class LanguageAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
switch ($operation) {
case 'update':
/* @var \Drupal\Core\Language\LanguageInterface $entity */
return AccessResult::allowedIf(!$entity->isLocked())->addCacheableDependency($entity)
->andIf(parent::checkAccess($entity, $operation, $account));
case 'delete':
/* @var \Drupal\Core\Language\LanguageInterface $entity */
return AccessResult::allowedIf(!$entity->isLocked())->addCacheableDependency($entity)
->andIf(AccessResult::allowedIf(!$entity->isDefault())->addCacheableDependency($entity))
->andIf(parent::checkAccess($entity, $operation, $account));
default:
// No opinion.
return AccessResult::neutral();
}
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace Drupal\language;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\ParamConverter\ParamConverterInterface;
use Symfony\Component\Routing\Route;
/**
* Converts parameters for upcasting entity IDs to full objects.
*/
class LanguageConverter implements ParamConverterInterface {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new LanguageConverter.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(LanguageManagerInterface $language_manager) {
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults) {
if (!empty($value)) {
return $this->languageManager->getLanguage($value);
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function applies($definition, $name, Route $route) {
return (!empty($definition['type']) && $definition['type'] == 'language');
}
}

View file

@ -0,0 +1,163 @@
<?php
namespace Drupal\language;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\Entity\DraggableListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a class to build a listing of language entities.
*
* @see \Drupal\language\Entity\ConfigurableLanguage
*/
class LanguageListBuilder extends DraggableListBuilder {
/**
* {@inheritdoc}
*/
protected $entitiesKey = 'languages';
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$container->get('language_manager'),
$container->get('config.factory')
);
}
/**
* Constructs a new LanguageListBuilder object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage handler class.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory) {
parent::__construct($entity_type, $storage);
$this->languageManager = $language_manager;
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public function load() {
$entities = $this->storage->loadByProperties(array('locked' => FALSE));
// Sort the entities using the entity class's sort() method.
// See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
uasort($entities, array($this->entityType->getClass(), 'sort'));
return $entities;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_admin_overview_form';
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header = array(
'label' => t('Name'),
'default' => t('Default'),
) + parent::buildHeader();
return $header;
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['label'] = $entity->label();
$row['default'] = array(
'#type' => 'radio',
'#parents' => array('site_default_language'),
'#title' => t('Set @title as default', array('@title' => $entity->label())),
'#title_display' => 'invisible',
'#return_value' => $entity->id(),
'#id' => 'edit-site-default-language-' . $entity->id(),
);
// Mark the right language as default in the form.
if ($entity->id() == $this->languageManager->getDefaultLanguage()->getId()) {
$row['default']['#default_value'] = $entity->id();
}
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$form[$this->entitiesKey]['#languages'] = $this->entities;
$form['actions']['submit']['#value'] = t('Save configuration');
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if (!isset($this->entities[$form_state->getValue('site_default_language')])) {
$form_state->setErrorByName('site_default_language', $this->t('Selected default language no longer exists.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
// Save the default language if changed.
$new_id = $form_state->getValue('site_default_language');
if ($new_id != $this->languageManager->getDefaultLanguage()->getId()) {
$this->configFactory->getEditable('system.site')->set('default_langcode', $new_id)->save();
$this->languageManager->reset();
}
if ($this->languageManager instanceof ConfigurableLanguageManagerInterface) {
$this->languageManager->updateLockedLanguageWeights();
}
drupal_set_message(t('Configuration saved.'));
// Force the redirection to the page with the language we have just
// selected as default.
$form_state->setRedirectUrl($this->entities[$new_id]->urlInfo('collection', array('language' => $this->entities[$new_id])));
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Drupal\language;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Base class for language negotiation methods.
*/
abstract class LanguageNegotiationMethodBase implements LanguageNegotiationMethodInterface {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $config;
/**
* The current active user.
*
* @return \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* {@inheritdoc}
*/
public function setLanguageManager(ConfigurableLanguageManagerInterface $language_manager) {
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public function setConfig(ConfigFactoryInterface $config) {
$this->config = $config;
}
/**
* {@inheritdoc}
*/
public function setCurrentUser(AccountInterface $current_user) {
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public function persist(LanguageInterface $language) {
// Default implementation persists nothing.
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\language;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Interface for language negotiation classes.
*/
interface LanguageNegotiationMethodInterface {
/**
* Injects the language manager.
*
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language manager to be used to retrieve the language list and the
* already negotiated languages.
*/
public function setLanguageManager(ConfigurableLanguageManagerInterface $language_manager);
/**
* Injects the configuration factory.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
*/
public function setConfig(ConfigFactoryInterface $config);
/**
* Injects the current user.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current active user.
*/
public function setCurrentUser(AccountInterface $current_user);
/**
* Performs language negotiation.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* (optional) The current request. Defaults to NULL if it has not been
* initialized yet.
*
* @return string
* A valid language code or FALSE if the negotiation was unsuccessful.
*/
public function getLangcode(Request $request = NULL);
/**
* Notifies the plugin that the language code it returned has been accepted.
*
* @param \Drupal\Core\Language\LanguageInterface $language
* The accepted language.
*/
public function persist(LanguageInterface $language);
}

View file

@ -0,0 +1,33 @@
<?php
namespace Drupal\language;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
/**
* Manages language negotiation methods.
*/
class LanguageNegotiationMethodManager extends DefaultPluginManager {
/**
* Constructs a new LanguageNegotiationMethodManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* An object that implements CacheBackendInterface
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* An object that implements ModuleHandlerInterface
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/LanguageNegotiation', $namespaces, $module_handler, 'Drupal\language\LanguageNegotiationMethodInterface', 'Drupal\language\Annotation\LanguageNegotiation');
$this->cacheBackend = $cache_backend;
$this->cacheKeyPrefix = 'language_negotiation_plugins';
$this->cacheKey = 'language_negotiation_plugins';
$this->alterInfo('language_negotiation_info');
}
}

View file

@ -0,0 +1,361 @@
<?php
namespace Drupal\language;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Site\Settings;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Class responsible for performing language negotiation.
*/
class LanguageNegotiator implements LanguageNegotiatorInterface {
/**
* The language negotiation method plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $negotiatorManager;
/**
* The language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The settings instance.
*
* @return \Drupal\Core\Site\Settings
*/
protected $settings;
/**
* The request stack object.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The current active user.
*
* @return \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Local cache for language negotiation method instances.
*
* @var array
*/
protected $methods;
/**
* An array of language objects keyed by method id.
*
* @var \Drupal\Core\Language\LanguageInterface[]
*/
protected $negotiatedLanguages = array();
/**
* Constructs a new LanguageNegotiator object.
*
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Component\Plugin\PluginManagerInterface $negotiator_manager
* The language negotiation methods plugin manager
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Site\Settings $settings
* The settings instance.
*/
public function __construct(ConfigurableLanguageManagerInterface $language_manager, PluginManagerInterface $negotiator_manager, ConfigFactoryInterface $config_factory, Settings $settings, RequestStack $requestStack) {
$this->languageManager = $language_manager;
$this->negotiatorManager = $negotiator_manager;
$this->configFactory = $config_factory;
$this->settings = $settings;
$this->requestStack = $requestStack;
}
/**
* Initializes the injected language manager with the negotiator.
*
* This should be called right after instantiating the negotiator to make it
* available to the language manager without introducing a circular
* dependency.
*/
public function initLanguageManager() {
$this->languageManager->setNegotiator($this);
}
/**
* {@inheritdoc}
*/
public function reset() {
$this->negotiatedLanguages = array();
$this->methods = array();
}
/**
* {@inheritdoc}
*/
public function setCurrentUser(AccountInterface $current_user) {
$this->currentUser = $current_user;
$this->reset();
}
/**
* {@inheritdoc}
*/
public function initializeType($type) {
$language = NULL;
if ($this->currentUser) {
// Execute the language negotiation methods in the order they were set up
// and return the first valid language found.
foreach ($this->getEnabledNegotiators($type) as $method_id => $info) {
if (!isset($this->negotiatedLanguages[$method_id])) {
$this->negotiatedLanguages[$method_id] = $this->negotiateLanguage($type, $method_id);
}
// Since objects are references, we need to return a clone to prevent
// the language negotiation method cache from being unintentionally
// altered. The same methods might be used with different language types
// based on configuration.
$language = !empty($this->negotiatedLanguages[$method_id]) ? clone($this->negotiatedLanguages[$method_id]) : NULL;
if ($language) {
$this->getNegotiationMethodInstance($method_id)->persist($language);
break;
}
}
}
if (!$language) {
// If no other language was found use the default one.
$language = $this->languageManager->getDefaultLanguage();
$method_id = static::METHOD_ID;
}
return array($method_id => $language);
}
/**
* Gets enabled detection methods for the provided language type.
*
* @param string $type
* The language type.
*
* @return array
* An array of enabled detection methods for the provided language type.
*/
protected function getEnabledNegotiators($type) {
return $this->configFactory->get('language.types')->get('negotiation.' . $type . '.enabled') ?: array();
}
/**
* Performs language negotiation using the specified negotiation method.
*
* @param string $type
* The language type to be initialized.
* @param string $method_id
* The string identifier of the language negotiation method to use to detect
* language.
*
* @return \Drupal\Core\Language\LanguageInterface|null
* Negotiated language object for given type and method, FALSE otherwise.
*/
protected function negotiateLanguage($type, $method_id) {
$langcode = NULL;
$method = $this->negotiatorManager->getDefinition($method_id);
if (!isset($method['types']) || in_array($type, $method['types'])) {
$langcode = $this->getNegotiationMethodInstance($method_id)->getLangcode($this->requestStack->getCurrentRequest());
}
$languages = $this->languageManager->getLanguages();
return isset($languages[$langcode]) ? $languages[$langcode] : NULL;
}
/**
* {@inheritdoc}
*/
public function getNegotiationMethods($type = NULL) {
$definitions = $this->negotiatorManager->getDefinitions();
if (isset($type)) {
$enabled_methods = $this->getEnabledNegotiators($type);
$definitions = array_intersect_key($definitions, $enabled_methods);
}
return $definitions;
}
/**
* {@inheritdoc}
*/
public function getNegotiationMethodInstance($method_id) {
if (!isset($this->methods[$method_id])) {
$instance = $this->negotiatorManager->createInstance($method_id, array());
$instance->setLanguageManager($this->languageManager);
$instance->setConfig($this->configFactory);
$instance->setCurrentUser($this->currentUser);
$this->methods[$method_id] = $instance;
}
return $this->methods[$method_id];
}
/**
* {@inheritdoc}
*/
public function getPrimaryNegotiationMethod($type) {
$enabled_methods = $this->getEnabledNegotiators($type);
return empty($enabled_methods) ? LanguageNegotiatorInterface::METHOD_ID : key($enabled_methods);
}
/**
* {@inheritdoc}
*/
public function isNegotiationMethodEnabled($method_id, $type = NULL) {
$enabled = FALSE;
$language_types = !empty($type) ? array($type) : $this->languageManager->getLanguageTypes();
foreach ($language_types as $type) {
$enabled_methods = $this->getEnabledNegotiators($type);
if (isset($enabled_methods[$method_id])) {
$enabled = TRUE;
break;
}
}
return $enabled;
}
/**
* {@inheritdoc}
*/
function saveConfiguration($type, $enabled_methods) {
// As configurable language types might have changed, we reset the cache.
$this->languageManager->reset();
$definitions = $this->getNegotiationMethods();
$default_types = $this->languageManager->getLanguageTypes();
// Order the language negotiation method list by weight.
asort($enabled_methods);
foreach ($enabled_methods as $method_id => $weight) {
if (isset($definitions[$method_id])) {
$method = $definitions[$method_id];
// If the language negotiation method does not express any preference
// about types, make it available for any configurable type.
$types = array_flip(!empty($method['types']) ? $method['types'] : $default_types);
// Check whether the method is defined and has the right type.
if (!isset($types[$type])) {
unset($enabled_methods[$method_id]);
}
}
else {
unset($enabled_methods[$method_id]);
}
}
$this->configFactory->getEditable('language.types')->set('negotiation.' . $type . '.enabled', $enabled_methods)->save();
}
/**
* {@inheritdoc}
*/
function purgeConfiguration() {
// Ensure that we are getting the defined language negotiation information.
// An invocation of \Drupal\Core\Extension\ModuleInstaller::install() or
// \Drupal\Core\Extension\ModuleInstaller::uninstall() could invalidate the
// cached information.
$this->negotiatorManager->clearCachedDefinitions();
$this->languageManager->reset();
foreach ($this->languageManager->getDefinedLanguageTypesInfo() as $type => $info) {
$this->saveConfiguration($type, $this->getEnabledNegotiators($type));
}
}
/**
* {@inheritdoc}
*/
function updateConfiguration(array $types) {
// Ensure that we are getting the defined language negotiation information.
// An invocation of \Drupal\Core\Extension\ModuleInstaller::install() or
// \Drupal\Core\Extension\ModuleInstaller::uninstall() could invalidate the
// cached information.
$this->negotiatorManager->clearCachedDefinitions();
$this->languageManager->reset();
$language_types = array();
$language_types_info = $this->languageManager->getDefinedLanguageTypesInfo();
$method_definitions = $this->getNegotiationMethods();
foreach ($language_types_info as $type => $info) {
$configurable = in_array($type, $types);
// The default language negotiation settings, if available, are stored in
// $info['fixed'].
$has_default_settings = !empty($info['fixed']);
// Check whether the language type is unlocked. Only the status of
// unlocked language types can be toggled between configurable and
// non-configurable.
if (empty($info['locked'])) {
if (!$configurable && !$has_default_settings) {
// If we have an unlocked non-configurable language type without
// default language negotiation settings, we use the values
// negotiated for the interface language which, should always be
// available.
$method_weights = array(LanguageNegotiationUI::METHOD_ID);
$method_weights = array_flip($method_weights);
$this->saveConfiguration($type, $method_weights);
}
}
else {
// The language type is locked. Locked language types with default
// settings are always considered non-configurable. In turn if default
// settings are missing, the language type is always considered
// configurable.
// If the language type is locked we can just store its default language
// negotiation settings if it has some, since it is not configurable.
if ($has_default_settings) {
$method_weights = array();
// Default settings are in $info['fixed'].
foreach ($info['fixed'] as $weight => $method_id) {
if (isset($method_definitions[$method_id])) {
$method_weights[$method_id] = $weight;
}
}
$this->saveConfiguration($type, $method_weights);
}
else {
// It was missing default settings, so force it to be configurable.
$configurable = TRUE;
}
}
// Accumulate information for each language type so it can be saved later.
$language_types[$type] = $configurable;
}
// Store the language type configuration.
$config = array(
'configurable' => array_keys(array_filter($language_types)),
'all' => array_keys($language_types),
);
$this->languageManager->saveLanguageTypesConfiguration($config);
}
}

View file

@ -0,0 +1,209 @@
<?php
namespace Drupal\language;
use Drupal\Core\Session\AccountInterface;
/**
* Common interface for language negotiation services.
*
* The language negotiation API is based on two major concepts:
* - Language types: types of translatable data (the types of data that a user
* can view or request).
* - Language negotiation methods: responsible for determining which language to
* use to present a particular piece of data to the user.
* Both language types and language negotiation methods are customizable.
*
* Drupal defines three built-in language types:
* - Interface language: The page's main language, used to present translated
* user interface elements such as titles, labels, help text, and messages.
* - Content language: The language used to present content that is available
* in more than one language.
* - URL language: The language associated with URLs. When generating a URL,
* this value will be used for URL's as a default if no explicit preference is
* provided.
* Modules can define additional language types through
* hook_language_types_info(), and alter existing language type definitions
* through hook_language_types_info_alter().
*
* Language types may be configurable or fixed. The language negotiation
* methods associated with a configurable language type can be explicitly
* set through the user interface. A fixed language type has predetermined
* (module-defined) language negotiation settings and, thus, does not appear in
* the configuration page. Here is a code snippet that makes the content
* language (which by default inherits the interface language's values)
* configurable:
* @code
* function mymodule_language_types_info_alter(&$language_types) {
* unset($language_types[LanguageInterface::TYPE_CONTENT]['fixed']);
* }
* @endcode
*
* The locked configuration property prevents one language type from being
* switched from customized to not customized, and vice versa.
* @see \Drupal\language\LanguageNegotiator::updateConfiguration()
*
* Every language type can have a different set of language negotiation methods
* assigned to it. Different language types often share the same language
* negotiation settings, but they can have independent settings if needed. If
* two language types are configured the same way, their language switcher
* configuration will be functionally identical and the same settings will act
* on both language types.
*
* Drupal defines the following built-in language negotiation methods:
* - URL: Determine the language from the URL (path prefix or domain).
* - Session: Determine the language from a request/session parameter.
* - User: Follow the user's language preference.
* - User admin language: Identify admin language from the user preferences.
* - Browser: Determine the language from the browser's language settings.
* - Selected language: Use the default site language.
* Language negotiation methods are simple plugin classes that implement a
* particular logic to return a language code. For instance, the URL method
* searches for a valid path prefix or domain name in the current request URL.
* If a language negotiation method does not return a valid language code, the
* next method associated with the language type (based on method weight) is
* invoked.
*
* Modules can define additional language negotiation methods by simply provide
* the related plugins, and alter existing methods through
* hook_language_negotiation_info_alter(). Here is an example snippet that lets
* path prefixes be ignored for administrative paths:
* @code
* function mymodule_language_negotiation_info_alter(&$negotiation_info) {
* // Replace the original plugin with our own implementation.
* $method_id = \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::METHOD_ID;
* $negotiation_info[$method_id]['class'] = 'Drupal\my_module\Plugin\LanguageNegotiation\MyLanguageNegotiationUrl';
* }
*
* class MyLanguageNegotiationUrl extends LanguageNegotiationUrl {
* public function getCurrentLanguage(Request $request = NULL) {
* if ($request) {
* // Use the original URL language negotiation method to get a valid
* // language code.
* $langcode = parent::getCurrentLanguage($request);
*
* // If we are on an administrative path, override with the default
* language.
* if ($request->query->has('q') && strtok($request->query->get('q'), '/') == 'admin') {
* return $this->languageManager->getDefaultLanguage()->getId();
* }
* return $langcode;
* }
* }
* }
* ?>
* @endcode
*
* For more information, see
* @link https://www.drupal.org/node/1497272 Language Negotiation API @endlink
*/
interface LanguageNegotiatorInterface {
/**
* The language negotiation method id for the language negotiator itself.
*/
const METHOD_ID = 'language-default';
/**
* Resets the negotiated languages and the method instances.
*/
public function reset();
/**
* Sets the current active user and resets all language types.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current active user.
*/
public function setCurrentUser(AccountInterface $current_user);
/**
* Initializes the specified language type.
*
* @param string $type
* The language type to be initialized.
*
* @return \Drupal\Core\Language\LanguageInterface[]
* Returns an array containing a single language keyed by the language
* negotiation method ID used to determine the language of the specified
* type. If negotiation is not possible the default language is returned.
*/
public function initializeType($type);
/**
* Returns the language negotiation methods enabled for a language type.
*
* @param string $type
* (optional) The language type. If no type is specified all the method
* definitions are returned.
*
* @return array[]
* An array of language negotiation method definitions keyed by method id.
*/
public function getNegotiationMethods($type = NULL);
/**
* Returns an instance of the specified language negotiation method.
*
* @param string $method_id
* The method identifier.
*
* @return \Drupal\language\LanguageNegotiationMethodInterface
*/
public function getNegotiationMethodInstance($method_id);
/**
* Returns the ID of the language type's primary language negotiation method.
*
* @param string $type
* The language type.
*
* @return string
* The identifier of the primary language negotiation method for the given
* language type, or the default method if none exists.
*/
public function getPrimaryNegotiationMethod($type);
/**
* Checks whether a language negotiation method is enabled for a language type.
*
* @param string $method_id
* The language negotiation method ID.
* @param string $type
* (optional) The language type. If none is passed, all the configurable
* language types will be inspected.
*
* @return bool
* TRUE if the method is enabled for at least one of the given language
* types, or FALSE otherwise.
*/
public function isNegotiationMethodEnabled($method_id, $type = NULL);
/**
* Saves a list of language negotiation methods for a language type.
*
* @param string $type
* The language type.
* @param int[] $enabled_methods
* An array of language negotiation method weights keyed by method ID.
*/
function saveConfiguration($type, $enabled_methods);
/**
* Resave the configuration to purge missing negotiation methods.
*/
function purgeConfiguration();
/**
* Updates the configuration based on the given language types.
*
* Stores the list of the language types along with information about their
* configurable state. Stores the default settings if the language type is
* not configurable.
*
* @param string[] $types
* An array of configurable language types.
*/
function updateConfiguration(array $types);
}

View file

@ -0,0 +1,104 @@
<?php
namespace Drupal\language;
use Drupal\Core\Config\BootstrapConfigStorageFactory;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\Core\Language\LanguageInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* Overrides the language_manager service to point to language's module one.
*/
class LanguageServiceProvider extends ServiceProviderBase {
const CONFIG_PREFIX = 'language.entity.';
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
// The following services are needed only on multilingual sites.
if ($this->isMultilingual()) {
$container->register('language_request_subscriber', 'Drupal\language\EventSubscriber\LanguageRequestSubscriber')
->addTag('event_subscriber')
->addArgument(new Reference('language_manager'))
->addArgument(new Reference('language_negotiator'))
->addArgument(new Reference('string_translation'))
->addArgument(new Reference('current_user'));
$container->register('path_processor_language', 'Drupal\language\HttpKernel\PathProcessorLanguage')
->addTag('path_processor_inbound', array('priority' => 300))
->addTag('path_processor_outbound', array('priority' => 100))
->addArgument(new Reference('config.factory'))
->addArgument(new Reference('language_manager'))
->addArgument(new Reference('language_negotiator'))
->addArgument(new Reference('current_user'))
->addArgument(new Reference('language.config_subscriber'))
->addMethodCall('initConfigSubscriber');
}
}
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
$definition = $container->getDefinition('language_manager');
$definition->setClass('Drupal\language\ConfigurableLanguageManager')
->addArgument(new Reference('config.factory'))
->addArgument(new Reference('module_handler'))
->addArgument(new Reference('language.config_factory_override'))
->addArgument(new Reference('request_stack'));
if ($default_language_values = $this->getDefaultLanguageValues()) {
$container->setParameter('language.default_values', $default_language_values);
}
// For monolingual sites, we explicitly set the default language for the
// language config override service as there is no language negotiation.
if (!$this->isMultilingual()) {
$container->getDefinition('language.config_factory_override')
->addMethodCall('setLanguageFromDefault', array(new Reference('language.default')));
}
}
/**
* Checks whether the site is multilingual.
*
* @return bool
* TRUE if the site is multilingual, FALSE otherwise.
*/
protected function isMultilingual() {
// Assign the prefix to a local variable so it can be used in an anonymous
// function.
$prefix = static::CONFIG_PREFIX;
// @todo Try to swap out for config.storage to take advantage of database
// and caching. This might prove difficult as this is called before the
// container has finished building.
$config_storage = BootstrapConfigStorageFactory::get();
$config_ids = array_filter($config_storage->listAll($prefix), function($config_id) use ($prefix) {
return $config_id != $prefix . LanguageInterface::LANGCODE_NOT_SPECIFIED && $config_id != $prefix . LanguageInterface::LANGCODE_NOT_APPLICABLE;
});
return count($config_ids) > 1;
}
/**
* Gets the default language values.
*
* @return array|bool
* Returns the default language values for the language configured in
* system.site:default_langcode if the corresponding configuration entity
* exists, otherwise FALSE.
*/
protected function getDefaultLanguageValues() {
$config_storage = BootstrapConfigStorageFactory::get();
$system = $config_storage->read('system.site');
$default_language = $config_storage->read(static::CONFIG_PREFIX . $system['default_langcode']);
if (is_array($default_language)) {
return $default_language;
}
return FALSE;
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Drupal\language;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\Request;
/**
* Interface for language switcher classes.
*/
interface LanguageSwitcherInterface {
/**
* Returns language switch links.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* @param string $type
* The language type.
* @param \Drupal\Core\Url $url
* The URL the switch links will be relative to.
*
* @return array
* An array of link arrays keyed by language code.
*/
public function getLanguageSwitchLinks(Request $request, $type, Url $url);
}

View file

@ -0,0 +1,116 @@
<?php
namespace Drupal\language\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'Language switcher' block.
*
* @Block(
* id = "language_block",
* admin_label = @Translation("Language switcher"),
* category = @Translation("System"),
* deriver = "Drupal\language\Plugin\Derivative\LanguageBlock"
* )
*/
class LanguageBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The path matcher.
*
* @var \Drupal\Core\Path\PathMatcherInterface
*/
protected $pathMatcher;
/**
* Constructs an LanguageBlock 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 mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
* The path matcher.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManagerInterface $language_manager, PathMatcherInterface $path_matcher) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->languageManager = $language_manager;
$this->pathMatcher = $path_matcher;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('language_manager'),
$container->get('path.matcher')
);
}
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
$access = $this->languageManager->isMultilingual() ? AccessResult::allowed() : AccessResult::forbidden();
return $access->addCacheTags(['config:configurable_language_list']);
}
/**
* {@inheritdoc}
*/
public function build() {
$build = array();
$route_name = $this->pathMatcher->isFrontPage() ? '<front>' : '<current>';
$type = $this->getDerivativeId();
$links = $this->languageManager->getLanguageSwitchLinks($type, Url::fromRoute($route_name));
if (isset($links->links)) {
$build = array(
'#theme' => 'links__language_block',
'#links' => $links->links,
'#attributes' => array(
'class' => array(
"language-switcher-{$links->method_id}",
),
),
'#set_active_class' => TRUE,
);
}
return $build;
}
/**
* {@inheritdoc}
*
* @todo Make cacheable in https://www.drupal.org/node/2232375.
*/
public function getCacheMaxAge() {
return 0;
}
}

View file

@ -0,0 +1,150 @@
<?php
namespace Drupal\language\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'Language' condition.
*
* @Condition(
* id = "language",
* label = @Translation("Language"),
* context = {
* "language" = @ContextDefinition("language", label = @Translation("Language"))
* }
* )
*/
class Language extends ConditionPluginBase implements ContainerFactoryPluginInterface {
/**
* The Language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Creates a new Language instance.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param array $configuration
* The plugin configuration, i.e. an array with configuration values keyed
* by configuration option name. The special key 'context' may be used to
* initialize the defined contexts by setting it to an array of context
* values keyed by context names.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
*/
public function __construct(LanguageManagerInterface $language_manager, array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('language_manager'),
$configuration,
$plugin_id,
$plugin_definition
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
if ($this->languageManager->isMultilingual()) {
// Fetch languages.
$languages = $this->languageManager->getLanguages();
$langcodes_options = array();
foreach ($languages as $language) {
$langcodes_options[$language->getId()] = $language->getName();
}
$form['langcodes'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Language selection'),
'#default_value' => $this->configuration['langcodes'],
'#options' => $langcodes_options,
'#description' => $this->t('Select languages to enforce. If none are selected, all languages will be allowed.'),
);
}
else {
$form['langcodes'] = array(
'#type' => 'value',
'#default_value' => $this->configuration['langcodes'],
);
}
return parent::buildConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['langcodes'] = array_filter($form_state->getValue('langcodes'));
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function summary() {
$language_list = $this->languageManager->getLanguages(LanguageInterface::STATE_ALL);
$selected = $this->configuration['langcodes'];
// Reduce the language list to an array of language names.
$language_names = array_reduce($language_list, function(&$result, $item) use ($selected) {
// If the current item of the $language_list array is one of the selected
// languages, add it to the $results array.
if (!empty($selected[$item->getId()])) {
$result[$item->getId()] = $item->getName();
}
return $result;
}, array());
// If we have more than one language selected, separate them by commas.
if (count($this->configuration['langcodes']) > 1) {
$languages = implode(', ', $language_names);
}
else {
// If we have just one language just grab the only present value.
$languages = array_pop($language_names);
}
if (!empty($this->configuration['negate'])) {
return t('The language is not @languages.', array('@languages' => $languages));
}
return t('The language is @languages.', array('@languages' => $languages));
}
/**
* {@inheritdoc}
*/
public function evaluate() {
if (empty($this->configuration['langcodes']) && !$this->isNegated()) {
return TRUE;
}
$language = $this->getContextValue('language');
// Language visibility settings.
return !empty($this->configuration['langcodes'][$language->getId()]);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array('langcodes' => array()) + parent::defaultConfiguration();
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\language\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\language\ConfigurableLanguageManagerInterface;
/**
* Provides language switcher block plugin definitions for all languages.
*/
class LanguageBlock extends DeriverBase {
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$language_manager = \Drupal::languageManager();
if ($language_manager instanceof ConfigurableLanguageManagerInterface) {
$info = $language_manager->getDefinedLanguageTypesInfo();
$configurable_types = $language_manager->getLanguageTypes();
foreach ($configurable_types as $type) {
$this->derivatives[$type] = $base_plugin_definition;
$this->derivatives[$type]['admin_label'] = t('Language switcher (@type)', array('@type' => $info[$type]['name']));
}
// If there is just one configurable type then change the title of the
// block.
if (count($configurable_types) == 1) {
$this->derivatives[reset($configurable_types)]['admin_label'] = t('Language switcher');
}
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\Component\Utility\UserAgent;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Class for identifying language from the browser Accept-language HTTP header.
*
* @LanguageNegotiation(
* id = \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationBrowser::METHOD_ID,
* weight = -2,
* name = @Translation("Browser"),
* description = @Translation("Language from the browser's language settings."),
* config_route_name = "language.negotiation_browser"
* )
*/
class LanguageNegotiationBrowser extends LanguageNegotiationMethodBase {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-browser';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = NULL;
if ($this->languageManager && $request && $request->server->get('HTTP_ACCEPT_LANGUAGE')) {
$http_accept_language = $request->server->get('HTTP_ACCEPT_LANGUAGE');
$langcodes = array_keys($this->languageManager->getLanguages());
$mappings = $this->config->get('language.mappings')->get('map');
$langcode = UserAgent::getBestMatchingLangcode($http_accept_language, $langcodes, $mappings);
// Internal page cache with multiple languages and browser negotiation
// could lead to wrong cached sites. Therefore disabling the internal
// page cache.
// @todo Solve more elegantly in https://www.drupal.org/node/2430335.
\Drupal::service('page_cache_kill_switch')->trigger();
}
return $langcode;
}
}

View file

@ -0,0 +1,289 @@
<?php
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Url;
use Drupal\language\LanguageNegotiationMethodBase;
use Drupal\language\LanguageSwitcherInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Class for identifying the content translation language.
*
* @LanguageNegotiation(
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::METHOD_ID,
* types = {Drupal\Core\Language\LanguageInterface::TYPE_CONTENT},
* weight = -9,
* name = @Translation("Content language"),
* description = @Translation("Determines the content language from a request parameter."),
* )
*/
class LanguageNegotiationContentEntity extends LanguageNegotiationMethodBase implements OutboundPathProcessorInterface, LanguageSwitcherInterface, ContainerFactoryPluginInterface {
/**
* The language negotiation method ID.
*/
const METHOD_ID = 'language-content-entity';
/**
* The query string parameter.
*/
const QUERY_PARAMETER = 'language_content_entity';
/**
* A list of all the link paths of enabled content entities.
*
* @var array
*/
protected $contentEntityPaths;
/**
* Static cache for the language negotiation order check.
*
* @see \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::hasLowerLanguageNegotiationWeight()
*
* @var bool
*/
protected $hasLowerLanguageNegotiationWeightResult;
/**
* Static cache of outbound route paths per request.
*
* @var \SplObjectStorage
*/
protected $paths;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new LanguageNegotiationContentEntity instance.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
$this->paths = new \SplObjectStorage();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($container->get('entity.manager'));
}
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = $request->query->get(static::QUERY_PARAMETER);
$language_enabled = array_key_exists($langcode, $this->languageManager->getLanguages());
return $language_enabled ? $langcode : NULL;
}
/**
* {@inheritdoc}
*/
public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
// If appropriate, process outbound to add a query parameter to the url and
// remove the language option, so that url negotiator does not rewrite the
// url.
// First, check if processing conditions are met.
if (!($request && !empty($options['route']) && $this->hasLowerLanguageNegotiationWeight() && $this->meetsContentEntityRoutesCondition($options['route'], $request))) {
return $path;
}
if (isset($options['language']) || $langcode = $this->getLangcode($request)) {
// If the language option is set, unset it, so that the url language
// negotiator does not rewrite the url.
if (isset($options['language'])) {
$langcode = $options['language']->getId();
unset($options['language']);
}
if (isset($options['query']) && is_string($options['query'])) {
$query = [];
parse_str($options['query'], $query);
$options['query'] = $query;
}
else {
$options['query'] = [];
}
if (!isset($options['query'][static::QUERY_PARAMETER])) {
$query_addon = [static::QUERY_PARAMETER => $langcode];
$options['query'] += $query_addon;
// @todo Remove this once https://www.drupal.org/node/2507005 lands.
$path .= (strpos($path, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($query_addon);
}
if ($bubbleable_metadata) {
// Cached URLs that have been processed by this outbound path
// processor must be:
$bubbleable_metadata
// - varied by the content language query parameter.
->addCacheContexts(['url.query_args:' . static::QUERY_PARAMETER]);
}
}
return $path;
}
/**
* {@inheritdoc}
*/
public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
$links = [];
$query = [];
parse_str($request->getQueryString(), $query);
foreach ($this->languageManager->getNativeLanguages() as $language) {
$langcode = $language->getId();
$query[static::QUERY_PARAMETER] = $langcode;
$links[$langcode] = [
'url' => $url,
'title' => $language->getName(),
'attributes' => ['class' => ['language-link']],
'query' => $query,
];
}
return $links;
}
/**
* Determines if content entity language negotiator has higher priority.
*
* The content entity language negotiator having higher priority than the url
* language negotiator, is a criteria in
* \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::processOutbound().
*
* @return bool
* TRUE if the the content entity language negotiator has higher priority
* than the url language negotiator, FALSE otherwise.
*/
protected function hasLowerLanguageNegotiationWeight() {
if (!isset($this->hasLowerLanguageNegotiationWeightResult)) {
// Only run if the LanguageNegotiationContentEntity outbound function is
// being executed before the outbound function of LanguageNegotiationUrl.
$content_method_weights = $this->config->get('language.types')->get('negotiation.language_content.enabled') ?: [];
// Check if the content language is configured to be dependent on the
// url negotiator directly or indirectly over the interface negotiator.
if (isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) && ($content_method_weights[static::METHOD_ID] > $content_method_weights[LanguageNegotiationUrl::METHOD_ID])) {
$this->hasLowerLanguageNegotiationWeightResult = FALSE;
}
else {
$check_interface_method = FALSE;
if (isset($content_method_weights[LanguageNegotiationUI::METHOD_ID])) {
$interface_method_weights = $this->config->get('language.types')->get('negotiation.language_interface.enabled') ?: [];
$check_interface_method = isset($interface_method_weights[LanguageNegotiationUrl::METHOD_ID]);
}
if ($check_interface_method) {
$max_weight = $content_method_weights[LanguageNegotiationUI::METHOD_ID];
$max_weight = isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) ? max($max_weight, $content_method_weights[LanguageNegotiationUrl::METHOD_ID]) : $max_weight;
}
else {
$max_weight = isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) ? $content_method_weights[LanguageNegotiationUrl::METHOD_ID] : PHP_INT_MAX;
}
$this->hasLowerLanguageNegotiationWeightResult = $content_method_weights[static::METHOD_ID] < $max_weight;
}
}
return $this->hasLowerLanguageNegotiationWeightResult;
}
/**
* Determines if content entity route condition is met.
*
* Requirements: currently being on an content entity route and processing
* outbound url pointing to the same content entity.
*
* @param \Symfony\Component\Routing\Route $outbound_route
* The route object for the current outbound url being processed.
* @param \Symfony\Component\HttpFoundation\Request $request
* The HttpRequest object representing the current request.
*
* @return bool
* TRUE if the content entity route condition is met, FALSE otherwise.
*/
protected function meetsContentEntityRoutesCondition(Route $outbound_route, Request $request) {
$outbound_path_pattern = $outbound_route->getPath();
$storage = isset($this->paths[$request]) ? $this->paths[$request] : [];
if (!isset($storage[$outbound_path_pattern])) {
$storage[$outbound_path_pattern] = FALSE;
// Check if the outbound route points to the current entity.
if ($content_entity_type_id_for_current_route = $this->getContentEntityTypeIdForCurrentRequest($request)) {
if (!empty($this->getContentEntityPaths()[$outbound_path_pattern]) && $content_entity_type_id_for_current_route == $this->getContentEntityPaths()[$outbound_path_pattern]) {
$storage[$outbound_path_pattern] = TRUE;
}
}
$this->paths[$request] = $storage;
}
return $storage[$outbound_path_pattern];
}
/**
* Returns the content entity type ID from the current request for the route.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The HttpRequest object representing the current request.
*
* @return string
* The entity type ID for the route from the request.
*/
protected function getContentEntityTypeIdForCurrentRequest(Request $request) {
$content_entity_type_id_for_current_route = '';
if ($current_route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) {
$current_route_path = $current_route->getPath();
$content_entity_type_id_for_current_route = isset($this->getContentEntityPaths()[$current_route_path]) ? $this->getContentEntityPaths()[$current_route_path] : '';
}
return $content_entity_type_id_for_current_route;
}
/**
* Returns the paths for the link templates of all content entities.
*
* @return array
* An array of all content entity type IDs, keyed by the corresponding link
* template paths.
*/
protected function getContentEntityPaths() {
if (!isset($this->contentEntityPaths)) {
$this->contentEntityPaths = [];
$entity_types = $this->entityManager->getDefinitions();
foreach ($entity_types as $entity_type_id => $entity_type) {
if ($entity_type->isSubclassOf(ContentEntityInterface::class)) {
$entity_paths = array_fill_keys($entity_type->getLinkTemplates(), $entity_type_id);
$this->contentEntityPaths = array_merge($this->contentEntityPaths, $entity_paths);
}
}
}
return $this->contentEntityPaths;
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Class for identifying language from a selected language.
*
* @LanguageNegotiation(
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected::METHOD_ID,
* weight = 12,
* name = @Translation("Selected language"),
* description = @Translation("Language based on a selected language."),
* config_route_name = "language.negotiation_selected"
* )
*/
class LanguageNegotiationSelected extends LanguageNegotiationMethodBase {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-selected';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = NULL;
if ($this->languageManager) {
$langcode = $this->config->get('language.negotiation')->get('selected_langcode');
}
return $langcode;
}
}

View file

@ -0,0 +1,161 @@
<?php
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Url;
use Drupal\language\LanguageNegotiationMethodBase;
use Drupal\language\LanguageSwitcherInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Identify language from a request/session parameter.
*
* @LanguageNegotiation(
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSession::METHOD_ID,
* weight = -6,
* name = @Translation("Session"),
* description = @Translation("Language from a request/session parameter."),
* config_route_name = "language.negotiation_session"
* )
*/
class LanguageNegotiationSession extends LanguageNegotiationMethodBase implements OutboundPathProcessorInterface, LanguageSwitcherInterface {
/**
* Flag used to determine whether query rewriting is active.
*
* @var bool
*/
protected $queryRewrite;
/**
* The query parameter name to rewrite.
*
* @var string
*/
protected $queryParam;
/**
* The query parameter value to be set.
*
* @var string
*/
protected $queryValue;
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-session';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$config = $this->config->get('language.negotiation')->get('session');
$param = $config['parameter'];
$langcode = $request && $request->query->get($param) ? $request->query->get($param) : NULL;
if (!$langcode && isset($_SESSION[$param])) {
$langcode = $_SESSION[$param];
}
return $langcode;
}
/**
* {@inheritdoc}
*/
public function persist(LanguageInterface $language) {
// We need to update the session parameter with the request value only if we
// have an authenticated user.
$langcode = $language->getId();
if ($langcode && $this->languageManager) {
$languages = $this->languageManager->getLanguages();
if ($this->currentUser->isAuthenticated() && isset($languages[$langcode])) {
$config = $this->config->get('language.negotiation')->get('session');
$_SESSION[$config['parameter']] = $langcode;
}
}
}
/**
* {@inheritdoc}
*/
public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
if ($request) {
// The following values are not supposed to change during a single page
// request processing.
if (!isset($this->queryRewrite)) {
if ($this->currentUser->isAnonymous()) {
$languages = $this->languageManager->getLanguages();
$config = $this->config->get('language.negotiation')->get('session');
$this->queryParam = $config['parameter'];
$this->queryValue = $request->query->has($this->queryParam) ? $request->query->get($this->queryParam) : NULL;
$this->queryRewrite = isset($languages[$this->queryValue]);
}
else {
$this->queryRewrite = FALSE;
}
}
// If the user is anonymous, the user language negotiation method is
// enabled, and the corresponding option has been set, we must preserve
// any explicit user language preference even with cookies disabled.
if ($this->queryRewrite) {
if (isset($options['query']) && is_string($options['query'])) {
$query = array();
parse_str($options['query'], $query);
$options['query'] = $query;
}
if (!isset($options['query'][$this->queryParam])) {
$options['query'][$this->queryParam] = $this->queryValue;
}
if ($bubbleable_metadata) {
// Cached URLs that have been processed by this outbound path
// processor must be:
$bubbleable_metadata
// - invalidated when the language negotiation config changes, since
// another query parameter may be used to determine the language.
->addCacheTags($this->config->get('language.negotiation')->getCacheTags())
// - varied by the configured query parameter.
->addCacheContexts(['url.query_args:' . $this->queryParam]);
}
}
}
return $path;
}
/**
* {@inheritdoc}
*/
public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
$links = array();
$config = $this->config->get('language.negotiation')->get('session');
$param = $config['parameter'];
$language_query = isset($_SESSION[$param]) ? $_SESSION[$param] : $this->languageManager->getCurrentLanguage($type)->getId();
$query = array();
parse_str($request->getQueryString(), $query);
foreach ($this->languageManager->getNativeLanguages() as $language) {
$langcode = $language->getId();
$links[$langcode] = array(
// We need to clone the $url object to avoid using the same one for all
// links. When the links are rendered, options are set on the $url
// object, so if we use the same one, they would be set for all links.
'url' => clone $url,
'title' => $language->getName(),
'attributes' => array('class' => array('language-link')),
'query' => $query,
);
if ($language_query != $langcode) {
$links[$langcode]['query'][$param] = $langcode;
}
else {
$links[$langcode]['attributes']['class'][] = 'session-active';
}
}
return $links;
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Identifies the language from the interface text language selected for page.
*
* @LanguageNegotiation(
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI::METHOD_ID,
* types = {Drupal\Core\Language\LanguageInterface::TYPE_CONTENT},
* weight = 9,
* name = @Translation("Interface"),
* description = @Translation("Use the detected interface language.")
* )
*/
class LanguageNegotiationUI extends LanguageNegotiationMethodBase {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-interface';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
return $this->languageManager ? $this->languageManager->getCurrentLanguage()->getId() : NULL;
}
}

View file

@ -0,0 +1,213 @@
<?php
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Url;
use Drupal\language\LanguageNegotiationMethodBase;
use Drupal\language\LanguageSwitcherInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Class for identifying language via URL prefix or domain.
*
* @LanguageNegotiation(
* id = \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::METHOD_ID,
* types = {\Drupal\Core\Language\LanguageInterface::TYPE_INTERFACE,
* \Drupal\Core\Language\LanguageInterface::TYPE_CONTENT,
* \Drupal\Core\Language\LanguageInterface::TYPE_URL},
* weight = -8,
* name = @Translation("URL"),
* description = @Translation("Language from the URL (Path prefix or domain)."),
* config_route_name = "language.negotiation_url"
* )
*/
class LanguageNegotiationUrl extends LanguageNegotiationMethodBase implements InboundPathProcessorInterface, OutboundPathProcessorInterface, LanguageSwitcherInterface {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-url';
/**
* URL language negotiation: use the path prefix as URL language indicator.
*/
const CONFIG_PATH_PREFIX = 'path_prefix';
/**
* URL language negotiation: use the domain as URL language indicator.
*/
const CONFIG_DOMAIN = 'domain';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = NULL;
if ($request && $this->languageManager) {
$languages = $this->languageManager->getLanguages();
$config = $this->config->get('language.negotiation')->get('url');
switch ($config['source']) {
case LanguageNegotiationUrl::CONFIG_PATH_PREFIX:
$request_path = urldecode(trim($request->getPathInfo(), '/'));
$path_args = explode('/', $request_path);
$prefix = array_shift($path_args);
// Search prefix within added languages.
$negotiated_language = FALSE;
foreach ($languages as $language) {
if (isset($config['prefixes'][$language->getId()]) && $config['prefixes'][$language->getId()] == $prefix) {
$negotiated_language = $language;
break;
}
}
if ($negotiated_language) {
$langcode = $negotiated_language->getId();
}
break;
case LanguageNegotiationUrl::CONFIG_DOMAIN:
// Get only the host, not the port.
$http_host = $request->getHost();
foreach ($languages as $language) {
// Skip the check if the language doesn't have a domain.
if (!empty($config['domains'][$language->getId()])) {
// Ensure that there is exactly one protocol in the URL when
// checking the hostname.
$host = 'http://' . str_replace(array('http://', 'https://'), '', $config['domains'][$language->getId()]);
$host = parse_url($host, PHP_URL_HOST);
if ($http_host == $host) {
$langcode = $language->getId();
break;
}
}
}
break;
}
}
return $langcode;
}
/**
* {@inheritdoc}
*/
public function processInbound($path, Request $request) {
$config = $this->config->get('language.negotiation')->get('url');
$parts = explode('/', trim($path, '/'));
$prefix = array_shift($parts);
// Search prefix within added languages.
foreach ($this->languageManager->getLanguages() as $language) {
if (isset($config['prefixes'][$language->getId()]) && $config['prefixes'][$language->getId()] == $prefix) {
// Rebuild $path with the language removed.
$path = '/' . implode('/', $parts);
break;
}
}
return $path;
}
/**
* {@inheritdoc}
*/
public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
$url_scheme = 'http';
$port = 80;
if ($request) {
$url_scheme = $request->getScheme();
$port = $request->getPort();
}
$languages = array_flip(array_keys($this->languageManager->getLanguages()));
// Language can be passed as an option, or we go for current URL language.
if (!isset($options['language'])) {
$language_url = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL);
$options['language'] = $language_url;
}
// We allow only added languages here.
elseif (!is_object($options['language']) || !isset($languages[$options['language']->getId()])) {
return $path;
}
$config = $this->config->get('language.negotiation')->get('url');
if ($config['source'] == LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
if (is_object($options['language']) && !empty($config['prefixes'][$options['language']->getId()])) {
$options['prefix'] = $config['prefixes'][$options['language']->getId()] . '/';
if ($bubbleable_metadata) {
$bubbleable_metadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_URL]);
}
}
}
elseif ($config['source'] == LanguageNegotiationUrl::CONFIG_DOMAIN) {
if (is_object($options['language']) && !empty($config['domains'][$options['language']->getId()])) {
// Save the original base URL. If it contains a port, we need to
// retain it below.
if (!empty($options['base_url'])) {
// The colon in the URL scheme messes up the port checking below.
$normalized_base_url = str_replace(array('https://', 'http://'), '', $options['base_url']);
}
// Ask for an absolute URL with our modified base URL.
$options['absolute'] = TRUE;
$options['base_url'] = $url_scheme . '://' . $config['domains'][$options['language']->getId()];
// In case either the original base URL or the HTTP host contains a
// port, retain it.
if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) {
list(, $port) = explode(':', $normalized_base_url);
$options['base_url'] .= ':' . $port;
}
elseif (($url_scheme == 'http' && $port != 80) || ($url_scheme == 'https' && $port != 443)) {
$options['base_url'] .= ':' . $port;
}
if (isset($options['https'])) {
if ($options['https'] === TRUE) {
$options['base_url'] = str_replace('http://', 'https://', $options['base_url']);
}
elseif ($options['https'] === FALSE) {
$options['base_url'] = str_replace('https://', 'http://', $options['base_url']);
}
}
// Add Drupal's subfolder from the base_path if there is one.
$options['base_url'] .= rtrim(base_path(), '/');
if ($bubbleable_metadata) {
$bubbleable_metadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_URL, 'url.site']);
}
}
}
return $path;
}
/**
* {@inheritdoc}
*/
public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
$links = array();
$query = $request->query->all();
foreach ($this->languageManager->getNativeLanguages() as $language) {
$links[$language->getId()] = array(
// We need to clone the $url object to avoid using the same one for all
// links. When the links are rendered, options are set on the $url
// object, so if we use the same one, they would be set for all links.
'url' => clone $url,
'title' => $language->getName(),
'language' => $language,
'attributes' => array('class' => array('language-link')),
'query' => $query,
);
}
return $links;
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Class that determines the language to be assigned to URLs when none is
* detected.
*
* The language negotiation process has a fallback chain that ends with the
* default language negotiation method. Each built-in language type has a
* separate initialization:
* - Interface language, which is the only configurable one, always gets a valid
* value. If no request-specific language is detected, the default language
* will be used.
* - Content language merely inherits the interface language by default.
* - URL language is detected from the requested URL and will be used to rewrite
* URLs appearing in the page being rendered. If no language can be detected,
* there are two possibilities:
* - If the default language has no configured path prefix or domain, then the
* default language is used. This guarantees that (missing) URL prefixes are
* preserved when navigating through the site.
* - If the default language has a configured path prefix or domain, a
* requested URL having an empty prefix or domain is an anomaly that must be
* fixed. This is done by introducing a prefix or domain in the rendered
* page matching the detected interface language.
*
* @LanguageNegotiation(
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrlFallback::METHOD_ID,
* types = {Drupal\Core\Language\LanguageInterface::TYPE_URL},
* weight = 8,
* name = @Translation("URL fallback"),
* description = @Translation("Use an already detected language for URLs if none is found.")
* )
*/
class LanguageNegotiationUrlFallback extends LanguageNegotiationMethodBase {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-url-fallback';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = NULL;
if ($this->languageManager) {
$default = $this->languageManager->getDefaultLanguage();
$config = $this->config->get('language.negotiation')->get('url');
$prefix = ($config['source'] == LanguageNegotiationUrl::CONFIG_PATH_PREFIX);
// If the default language is not configured to convey language
// information, a missing URL language information indicates that URL
// language should be the default one, otherwise we fall back to an
// already detected language.
if (($prefix && empty($config['prefixes'][$default->getId()])) || (!$prefix && empty($config['domains'][$default->getId()]))) {
$langcode = $default->getId();
}
else {
$langcode = $this->languageManager->getCurrentLanguage()->getId();
}
}
return $langcode;
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\language\Plugin\migrate\destination;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Plugin\migrate\destination\Config;
use Drupal\migrate\Row;
/**
* Provides a destination plugin for the default langcode config.
*
* @MigrateDestination(
* id = "default_langcode"
* )
*/
class DefaultLangcode extends Config {
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
$destination = $row->getDestination();
$langcode = $destination['default_langcode'];
// Check if the language exists.
if (ConfigurableLanguage::load($langcode) === NULL) {
throw new MigrateException("The language '$langcode' does not exist on this site.");
}
$this->config->set('default_langcode', $destination['default_langcode']);
$this->config->save();
return [$this->config->getName()];
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Drupal\language\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\migrate\process\ArrayBuild;
use Drupal\migrate\Row;
/**
* This plugin makes sure that no domain is empty if domain negotiation is used.
*
* @MigrateProcessPlugin(
* id = "language_domains",
* handle_multiples = TRUE
* )
*/
class LanguageDomains extends ArrayBuild {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if ($row->getSourceProperty('domain_negotiation')) {
global $base_url;
foreach ($value as $old_key => $old_value) {
if (empty($old_value['domain'])) {
// The default language domain might be empty.
// If it is, use the current domain.
$value[$old_key]['domain'] = parse_url($base_url, PHP_URL_HOST);
}
else {
// Ensure we have a protocol when checking for the hostname.
$domain = 'http://' . str_replace(['http://', 'https://'], '', $old_value['domain']);
// Only keep the host part of the domain.
$value[$old_key]['domain'] = parse_url($domain, PHP_URL_HOST);
}
}
}
return parent::transform($value, $migrate_executable, $row, $destination_property);
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Drupal\language\Plugin\migrate\process;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Processes the arrays for the language types' negotiation methods and weights.
*
* @MigrateProcessPlugin(
* id = "language_negotiation",
* handle_multiples = TRUE
* )
*/
class LanguageNegotiation extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$new_value = [
'enabled' => [],
'method_weights' => [],
];
if (!is_array($value)) {
throw new MigrateException('The input should be an array');
}
// If no weights are provided, use the keys by flipping the array.
if (empty($value[1])) {
$new_value['enabled'] = array_flip(array_map([$this, 'mapNewMethods'], array_keys($value[0])));
unset($new_value['method_weights']);
}
else {
foreach ($value[1] as $method => $weight) {
$new_method = $this->mapNewMethods($method);
$new_value['method_weights'][$new_method] = $weight;
if (in_array($method, array_keys($value[0]))) {
$new_value['enabled'][$new_method] = $weight;
}
}
}
return $new_value;
}
/**
* Maps old negotiation method names to the new ones.
*
* @param string $value
* The old negotiation method name.
*
* @return string
* The new negotiation method name.
*/
protected function mapNewMethods($value) {
switch ($value) {
case 'language-default':
return 'language-selected';
case 'locale-browser':
return 'language-browser';
case 'locale-interface':
return 'language-interface';
case 'locale-session':
return 'language-session';
case 'locale-url':
return 'language-url';
case 'locale-url-fallback':
return 'language-url-fallback';
case 'locale-user':
return 'language-user';
default:
return $value;
}
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\language\Plugin\migrate\process;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Processes the array for the language types.
*
* @MigrateProcessPlugin(
* id = "language_types",
* handle_multiples = TRUE
* )
*/
class LanguageTypes extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (!is_array($value)) {
throw new MigrateException('The input should be an array');
}
if (array_key_exists('language', $value)) {
$value['language_interface'] = $value['language'];
unset($value['language']);
}
if (!empty($this->configuration['filter_configurable'])) {
$value = array_filter($value);
}
return array_keys($value);
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Drupal\language\Plugin\migrate\source;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* @MigrateSource(
* id = "language",
* source_provider = "locale"
* )
*/
class Language extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'language' => $this->t('The language code.'),
'name' => $this->t('The English name of the language.'),
'native' => $this->t('The native name of the language.'),
'direction' => $this->t('The language direction. (0 = LTR, 1 = RTL)'),
'enabled' => $this->t('Whether the language is enabled.'),
'plurals' => $this->t('Number of plural indexes in this language.'),
'formula' => $this->t('PHP formula to get plural indexes.'),
'domain' => $this->t('Domain to use for this language.'),
'prefix' => $this->t('Path prefix used for this language.'),
'weight' => $this->t('The language weight when listed.'),
'javascript' => $this->t('Location of the JavaScript translation file.'),
);
}
/**
* {@inheritdoc}
*/
public function getIds() {
return array(
'language' => array(
'type' => 'string',
),
);
}
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('languages')->fields('languages');
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
if (!empty($this->configuration['fetch_all'])) {
// Get an array of all languages.
$languages = $this->query()->execute()->fetchAll();
$row->setSourceProperty('languages', $languages);
}
if (!empty($this->configuration['domain_negotiation'])) {
// Check if domain negotiation is used to be able to fill in the default
// language domain, which may be empty. In D6, domain negotiation is used
// when the 'language_negotiation' variable is set to '3', and in D7, when
// the 'locale_language_negotiation_url_part' variable is set to '1'.
if ($this->variableGet('language_negotiation', 0) == 3 || $this->variableGet('locale_language_negotiation_url_part', 0) == 1) {
$row->setSourceProperty('domain_negotiation', TRUE);
}
}
return parent::prepareRow($row);
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Drupal\language\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal multilingual node settings from database.
*
* @MigrateSource(
* id = "d6_language_content_settings",
* )
*/
class LanguageContentSettings extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('node_type', 't')
->fields('t', array(
'type',
));
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = array(
'type' => $this->t('Type'),
'language_content_type' => $this->t('Multilingual support.'),
'i18n_lock_node' => $this->t('Lock language.'),
);
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$type = $row->getSourceProperty('type');
$row->setSourceProperty('language_content_type', $this->variableGet('language_content_type_' . $type, NULL));
$row->setSourceProperty('i18n_lock_node', $this->variableGet('i18n_lock_node_' . $type, 0));
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['type']['type'] = 'string';
return $ids;
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Drupal\language\Plugin\migrate\source\d7;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal multilingual node settings from database.
*
* @MigrateSource(
* id = "d7_language_content_settings",
* )
*/
class LanguageContentSettings extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('node_type', 't')
->fields('t', array(
'type',
));
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = array(
'type' => $this->t('Type'),
'language_content_type' => $this->t('Multilingual support.'),
'i18n_lock_node' => $this->t('Lock language.'),
);
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$type = $row->getSourceProperty('type');
$row->setSourceProperty('language_content_type', $this->variableGet('language_content_type_' . $type, NULL));
$i18n_node_options = $this->variableGet('i18n_node_options_' . $type, NULL);
if ($i18n_node_options && in_array('lock', $i18n_node_options)) {
$row->setSourceProperty('i18n_lock_node', 1);
}
else {
$row->setSourceProperty('i18n_lock_node', 0);
}
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['type']['type'] = 'string';
return $ids;
}
}

View file

@ -0,0 +1,88 @@
<?php
// @codingStandardsIgnoreFile
/**
* This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\language\LanguageConverter' "core/modules/language/src".
*/
namespace Drupal\language\ProxyClass {
/**
* Provides a proxy class for \Drupal\language\LanguageConverter.
*
* @see \Drupal\Component\ProxyBuilder
*/
class LanguageConverter implements \Drupal\Core\ParamConverter\ParamConverterInterface
{
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
* The id of the original proxied service.
*
* @var string
*/
protected $drupalProxyOriginalServiceId;
/**
* The real proxied service, after it was lazy loaded.
*
* @var \Drupal\language\LanguageConverter
*/
protected $service;
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Constructs a ProxyClass Drupal proxy object.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The container.
* @param string $drupal_proxy_original_service_id
* The service ID of the original service.
*/
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
{
$this->container = $container;
$this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
}
/**
* Lazy loads the real service from the container.
*
* @return object
* Returns the constructed real service.
*/
protected function lazyLoadItself()
{
if (!isset($this->service)) {
$this->service = $this->container->get($this->drupalProxyOriginalServiceId);
}
return $this->service;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults)
{
return $this->lazyLoadItself()->convert($value, $definition, $name, $defaults);
}
/**
* {@inheritdoc}
*/
public function applies($definition, $name, \Symfony\Component\Routing\Route $route)
{
return $this->lazyLoadItself()->applies($definition, $name, $route);
}
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Drupal\language\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Test administration path based conversion of entities.
*
* @group language
*/
class AdminPathEntityConverterLanguageTest extends WebTestBase {
public static $modules = array('language', 'language_test');
protected function setUp() {
parent::setUp();
$permissions = array(
'access administration pages',
'administer site configuration',
);
$this->drupalLogin($this->drupalCreateUser($permissions));
ConfigurableLanguage::createFromLangcode('es')->save();
}
/**
* Tests the translated and untranslated config entities are loaded properly.
*/
public function testConfigUsingCurrentLanguage() {
\Drupal::languageManager()
->getLanguageConfigOverride('es', 'language.entity.es')
->set('label', 'Español')
->save();
$this->drupalGet('es/admin/language_test/entity_using_current_language/es');
$this->assertNoRaw(t('Loaded %label.', array('%label' => 'Spanish')));
$this->assertRaw(t('Loaded %label.', array('%label' => 'Español')));
$this->drupalGet('es/admin/language_test/entity_using_original_language/es');
$this->assertRaw(t('Loaded %label.', array('%label' => 'Spanish')));
$this->assertNoRaw(t('Loaded %label.', array('%label' => 'Español')));
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\language\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests entity type without language support.
*
* This is to ensure that an entity type without language support can not
* enable the language select from the content language settings page.
*
* @group language
*/
class EntityTypeWithoutLanguageFormTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array(
'language',
'language_test',
);
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create and log in administrative user.
$admin_user = $this->drupalCreateUser(array(
'administer languages',
));
$this->drupalLogin($admin_user);
}
/**
* Tests configuration options with an entity without language definition.
*/
public function testEmptyLangcode() {
// Assert that we can not enable language select from
// content language settings page.
$this->drupalGet('admin/config/regional/content-language');
$this->assertNoField('entity_types[no_language_entity_test]');
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Drupal\language\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests that the language settings on block config appears correctly.
*
* @group language
*/
class LanguageBlockSettingsVisibilityTest extends WebTestBase {
public static $modules = array('block', 'language');
public function testUnnecessaryLanguageSettingsVisibility() {
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'administer blocks'));
$this->drupalLogin($admin_user);
$this->drupalPostForm('admin/config/regional/language/add', array('predefined_langcode' => 'hu'), t('Add language'));
$this->drupalGet('admin/structure/block/add/system_menu_block:admin/stark');
$this->assertNoFieldByXPath('//input[@id="edit-visibility-language-langcodes-und"]', NULL, '\'Not specified\' option does not appear at block config, language settings section.');
$this->assertNoFieldByXpath('//input[@id="edit-visibility-language-langcodes-zxx"]', NULL, '\'Not applicable\' option does not appear at block config, language settings section.');
$this->assertFieldByXPath('//input[@id="edit-visibility-language-langcodes-en"]', NULL, '\'English\' option appears at block config, language settings section.');
$this->assertFieldByXpath('//input[@id="edit-visibility-language-langcodes-hu"]', NULL, '\'Hungarian\' option appears at block config, language settings section.');
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Drupal\language\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests browser language detection.
*
* @group language
*/
class LanguageBrowserDetectionTest extends WebTestBase {
public static $modules = array('language');
/**
* Tests for adding, editing and deleting mappings between browser language
* codes and Drupal language codes.
*/
function testUIBrowserLanguageMappings() {
// User to manage languages.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
// Check that the configure link exists.
$this->drupalGet('admin/config/regional/language/detection');
$this->assertLinkByHref('admin/config/regional/language/detection/browser');
// Check that defaults are loaded from language.mappings.yml.
$this->drupalGet('admin/config/regional/language/detection/browser');
$this->assertField('edit-mappings-zh-cn-browser-langcode', 'zh-cn', 'Chinese browser language code found.');
$this->assertField('edit-mappings-zh-cn-drupal-langcode', 'zh-hans-cn', 'Chinese Drupal language code found.');
// Delete zh-cn language code.
$browser_langcode = 'zh-cn';
$this->drupalGet('admin/config/regional/language/detection/browser/delete/' . $browser_langcode);
$message = t('Are you sure you want to delete @browser_langcode?', array(
'@browser_langcode' => $browser_langcode,
));
$this->assertRaw($message);
// Confirm the delete.
$edit = array();
$this->drupalPostForm('admin/config/regional/language/detection/browser/delete/' . $browser_langcode, $edit, t('Confirm'));
// We need raw here because %browser will add HTML.
$t_args = array(
'%browser' => $browser_langcode,
);
$this->assertRaw(t('The mapping for the %browser browser language code has been deleted.', $t_args), 'The test browser language code has been deleted.');
// Check we went back to the browser negotiation mapping overview.
$this->assertUrl(\Drupal::url('language.negotiation_browser', [], ['absolute' => TRUE]));
// Check that ch-zn no longer exists.
$this->assertNoField('edit-mappings-zh-cn-browser-langcode', 'Chinese browser language code no longer exists.');
// Add a new custom mapping.
$edit = array(
'new_mapping[browser_langcode]' => 'xx',
'new_mapping[drupal_langcode]' => 'en',
);
$this->drupalPostForm('admin/config/regional/language/detection/browser', $edit, t('Save configuration'));
$this->assertUrl(\Drupal::url('language.negotiation_browser', [], ['absolute' => TRUE]));
$this->assertField('edit-mappings-xx-browser-langcode', 'xx', 'Browser language code found.');
$this->assertField('edit-mappings-xx-drupal-langcode', 'en', 'Drupal language code found.');
// Add the same custom mapping again.
$this->drupalPostForm('admin/config/regional/language/detection/browser', $edit, t('Save configuration'));
$this->assertText('Browser language codes must be unique.');
// Change browser language code of our custom mapping to zh-sg.
$edit = array(
'mappings[xx][browser_langcode]' => 'zh-sg',
'mappings[xx][drupal_langcode]' => 'en',
);
$this->drupalPostForm('admin/config/regional/language/detection/browser', $edit, t('Save configuration'));
$this->assertText(t('Browser language codes must be unique.'));
// Change Drupal language code of our custom mapping to zh-hans.
$edit = array(
'mappings[xx][browser_langcode]' => 'xx',
'mappings[xx][drupal_langcode]' => 'zh-hans',
);
$this->drupalPostForm('admin/config/regional/language/detection/browser', $edit, t('Save configuration'));
$this->assertUrl(\Drupal::url('language.negotiation_browser', [], ['absolute' => TRUE]));
$this->assertField('edit-mappings-xx-browser-langcode', 'xx', 'Browser language code found.');
$this->assertField('edit-mappings-xx-drupal-langcode', 'zh-hans', 'Drupal language code found.');
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Drupal\language\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Ensures the language config overrides can be synchronized.
*
* @group language
*/
class LanguageConfigOverrideImportTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'config', 'locale', 'config_translation');
/**
* Tests that language can be enabled and overrides are created during a sync.
*/
public function testConfigOverrideImport() {
ConfigurableLanguage::createFromLangcode('fr')->save();
/* @var \Drupal\Core\Config\StorageInterface $sync */
$sync = \Drupal::service('config.storage.sync');
$this->copyConfig(\Drupal::service('config.storage'), $sync);
// Uninstall the language module and its dependencies so we can test
// enabling the language module and creating overrides at the same time
// during a configuration synchronization.
\Drupal::service('module_installer')->uninstall(array('language'));
// Ensure that the current site has no overrides registered to the
// ConfigFactory.
$this->rebuildContainer();
/* @var \Drupal\Core\Config\StorageInterface $override_sync */
$override_sync = $sync->createCollection('language.fr');
// Create some overrides in sync.
$override_sync->write('system.site', array('name' => 'FR default site name'));
$override_sync->write('system.maintenance', array('message' => 'FR message: @site is currently under maintenance. We should be back shortly. Thank you for your patience'));
$this->configImporter()->import();
$this->rebuildContainer();
\Drupal::service('router.builder')->rebuild();
$override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'system.site');
$this->assertEqual('FR default site name', $override->get('name'));
$this->drupalGet('fr');
$this->assertText('FR default site name');
$this->drupalLogin($this->rootUser);
$this->drupalGet('admin/config/development/maintenance/translate/fr/edit');
$this->assertText('FR message: @site is currently under maintenance. We should be back shortly. Thank you for your patience');
}
/**
* Tests that configuration events are not fired during a sync of overrides.
*/
public function testConfigOverrideImportEvents() {
// Enable the config_events_test module so we can record events occurring.
\Drupal::service('module_installer')->install(array('config_events_test'));
$this->rebuildContainer();
ConfigurableLanguage::createFromLangcode('fr')->save();
/* @var \Drupal\Core\Config\StorageInterface $sync */
$sync = \Drupal::service('config.storage.sync');
$this->copyConfig(\Drupal::service('config.storage'), $sync);
/* @var \Drupal\Core\Config\StorageInterface $override_sync */
$override_sync = $sync->createCollection('language.fr');
// Create some overrides in sync.
$override_sync->write('system.site', array('name' => 'FR default site name'));
\Drupal::state()->set('config_events_test.event', FALSE);
$this->configImporter()->import();
$this->rebuildContainer();
\Drupal::service('router.builder')->rebuild();
// Test that no config save event has been fired during the import because
// language configuration overrides do not fire events.
$event_recorder = \Drupal::state()->get('config_events_test.event', FALSE);
$this->assertFalse($event_recorder);
$this->drupalGet('fr');
$this->assertText('FR default site name');
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Drupal\language\Tests;
use Drupal\config\Tests\SchemaCheckTestTrait;
use Drupal\simpletest\WebTestBase;
/**
* Ensures the language config schema is correct.
*
* @group language
*/
class LanguageConfigSchemaTest extends WebTestBase {
use SchemaCheckTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'menu_link_content');
/**
* A user with administrative permissions.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create user.
$this->adminUser = $this->drupalCreateUser(array('administer languages'));
$this->drupalLogin($this->adminUser);
}
/**
* Tests whether the language config schema is valid.
*/
function testValidLanguageConfigSchema() {
// Make sure no language configuration available by default.
$config_data = $this->config('language.settings')->get();
$this->assertTrue(empty($config_data));
$settings_path = 'admin/config/regional/content-language';
// Enable translation for menu link.
$edit['entity_types[menu_link_content]'] = TRUE;
$edit['settings[menu_link_content][menu_link_content][settings][language][language_alterable]'] = TRUE;
// Enable translation for user.
$edit['entity_types[user]'] = TRUE;
$edit['settings[user][user][settings][language][language_alterable]'] = TRUE;
$edit['settings[user][user][settings][language][langcode]'] = 'en';
$this->drupalPostForm($settings_path, $edit, t('Save configuration'));
$config_data = $this->config('language.content_settings.menu_link_content.menu_link_content');
// Make sure configuration saved correctly.
$this->assertTrue($config_data->get('language_alterable'));
$this->assertConfigSchema(\Drupal::service('config.typed'), $config_data->getName(), $config_data->get());
}
}

View file

@ -0,0 +1,253 @@
<?php
namespace Drupal\language\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\simpletest\WebTestBase;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests the features of the language configuration element field.
*
* @group language
*/
class LanguageConfigurationElementTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('taxonomy', 'node', 'language', 'language_elements_test', 'field_ui');
protected function setUp() {
parent::setUp();
$user = $this->drupalCreateUser(array('access administration pages', 'administer languages', 'administer content types'));
$this->drupalLogin($user);
}
/**
* Tests the language settings have been saved.
*/
public function testLanguageConfigurationElement() {
$this->drupalGet('language-tests/language_configuration_element');
$edit['lang_configuration[langcode]'] = 'current_interface';
$edit['lang_configuration[language_alterable]'] = FALSE;
$this->drupalPostForm(NULL, $edit, 'Save');
$lang_conf = ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'some_bundle');
// Check that the settings have been saved.
$this->assertEqual($lang_conf->getDefaultLangcode(), 'current_interface');
$this->assertFalse($lang_conf->isLanguageAlterable());
$this->drupalGet('language-tests/language_configuration_element');
$this->assertOptionSelected('edit-lang-configuration-langcode', 'current_interface');
$this->assertNoFieldChecked('edit-lang-configuration-language-alterable');
// Reload the page and save again.
$this->drupalGet('language-tests/language_configuration_element');
$edit['lang_configuration[langcode]'] = 'authors_default';
$edit['lang_configuration[language_alterable]'] = TRUE;
$this->drupalPostForm(NULL, $edit, 'Save');
$lang_conf = ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'some_bundle');
// Check that the settings have been saved.
$this->assertEqual($lang_conf->getDefaultLangcode(), 'authors_default');
$this->assertTrue($lang_conf->isLanguageAlterable());
$this->drupalGet('language-tests/language_configuration_element');
$this->assertOptionSelected('edit-lang-configuration-langcode', 'authors_default');
$this->assertFieldChecked('edit-lang-configuration-language-alterable');
// Test if content type settings have been saved.
$edit = array(
'name' => 'Page',
'type' => 'page',
'language_configuration[langcode]' => 'authors_default',
'language_configuration[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/types/add', $edit, 'Save and manage fields');
// Make sure the settings are saved when creating the content type.
$this->drupalGet('admin/structure/types/manage/page');
$this->assertOptionSelected('edit-language-configuration-langcode', 'authors_default');
$this->assertFieldChecked('edit-language-configuration-language-alterable');
}
/**
* Tests that the language_get_default_langcode() returns the correct values.
*/
public function testDefaultLangcode() {
// Add some custom languages.
foreach (array('aa', 'bb', 'cc') as $language_code) {
ConfigurableLanguage::create(array(
'id' => $language_code,
'label' => $this->randomMachineName(),
))->save();
}
// Fixed language.
ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'custom_bundle')
->setLanguageAlterable(TRUE)
->setDefaultLangcode('bb')
->save();
$langcode = language_get_default_langcode('entity_test', 'custom_bundle');
$this->assertEqual($langcode, 'bb');
// Current interface.
ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'custom_bundle')
->setLanguageAlterable(TRUE)
->setDefaultLangcode('current_interface')
->save();
$langcode = language_get_default_langcode('entity_test', 'custom_bundle');
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
$this->assertEqual($langcode, $language_interface->getId());
// Site's default.
$old_default = \Drupal::languageManager()->getDefaultLanguage();
// Ensure the language entity default value is correct.
$configurable_language = ConfigurableLanguage::load($old_default->getId());
$this->assertTrue($configurable_language->isDefault(), 'The en language entity is flagged as the default language.');
$this->config('system.site')->set('default_langcode', 'cc')->save();
ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'custom_bundle')
->setLanguageAlterable(TRUE)
->setDefaultLangcode(LanguageInterface::LANGCODE_SITE_DEFAULT)
->save();
$langcode = language_get_default_langcode('entity_test', 'custom_bundle');
$this->assertEqual($langcode, 'cc');
// Ensure the language entity default value is correct.
$configurable_language = ConfigurableLanguage::load($old_default->getId());
$this->assertFalse($configurable_language->isDefault(), 'The en language entity is not flagged as the default language.');
$configurable_language = ConfigurableLanguage::load('cc');
// Check calling the
// \Drupal\language\ConfigurableLanguageInterface::isDefault() method
// directly.
$this->assertTrue($configurable_language->isDefault(), 'The cc language entity is flagged as the default language.');
// Check the default value of a language field when authors preferred option
// is selected.
// Create first an user and assign a preferred langcode to him.
$some_user = $this->drupalCreateUser();
$some_user->preferred_langcode = 'bb';
$some_user->save();
$this->drupalLogin($some_user);
ContentLanguageSettings::create([
'target_entity_type_id' => 'entity_test',
'target_bundle' => 'some_bundle',
])->setLanguageAlterable(TRUE)
->setDefaultLangcode('authors_default')
->save();
$this->drupalGet('language-tests/language_configuration_element_test');
$this->assertOptionSelected('edit-langcode', 'bb');
}
/**
* Tests that the configuration is retained when the node type is updated.
*/
public function testNodeTypeUpdate() {
// Create the article content type first if the profile used is not the
// standard one.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
}
$admin_user = $this->drupalCreateUser(array('administer content types'));
$this->drupalLogin($admin_user);
$edit = array(
'language_configuration[langcode]' => 'current_interface',
'language_configuration[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
// Check the language default configuration for the articles.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('node', 'article');
$uuid = $configuration->uuid();
$this->assertEqual($configuration->getDefaultLangcode(), 'current_interface', 'The default language configuration has been saved on the Article content type.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been saved on the Article content type.');
// Update the article content type by changing the title label.
$edit = array(
'title_label' => 'Name'
);
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
// Check that we still have the settings for the updated node type.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('node', 'article');
$this->assertEqual($configuration->getDefaultLangcode(), 'current_interface', 'The default language configuration has been kept on the updated Article content type.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been kept on the updated Article content type.');
$this->assertEqual($configuration->uuid(), $uuid, 'The language configuration uuid has been kept on the updated Article content type.');
}
/**
* Tests the language settings are deleted on bundle delete.
*/
public function testNodeTypeDelete() {
// Create the article content type first if the profile used is not the
// standard one.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(array(
'type' => 'article',
'name' => 'Article'
));
}
$admin_user = $this->drupalCreateUser(array('administer content types'));
$this->drupalLogin($admin_user);
// Create language configuration for the articles.
$edit = array(
'language_configuration[langcode]' => 'authors_default',
'language_configuration[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
// Check the language default configuration for articles is present.
$configuration = \Drupal::entityManager()->getStorage('language_content_settings')->load('node.article');
$this->assertTrue($configuration, 'The language configuration is present.');
// Delete 'article' bundle.
$this->drupalPostForm('admin/structure/types/manage/article/delete', array(), t('Delete'));
// Check that the language configuration has been deleted.
\Drupal::entityManager()->getStorage('language_content_settings')->resetCache();
$configuration = \Drupal::entityManager()->getStorage('language_content_settings')->load('node.article');
$this->assertFalse($configuration, 'The language configuration was deleted after bundle was deleted.');
}
/**
* Tests that the configuration is retained when a vocabulary is updated.
*/
public function testTaxonomyVocabularyUpdate() {
$vocabulary = Vocabulary::create([
'name' => 'Country',
'vid' => 'country',
]);
$vocabulary->save();
$admin_user = $this->drupalCreateUser(array('administer taxonomy'));
$this->drupalLogin($admin_user);
$edit = array(
'default_language[langcode]' => 'current_interface',
'default_language[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/taxonomy/manage/country', $edit, t('Save'));
// Check the language default configuration.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', 'country');
$uuid = $configuration->uuid();
$this->assertEqual($configuration->getDefaultLangcode(), 'current_interface', 'The default language configuration has been saved on the Country vocabulary.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been saved on the Country vocabulary.');
// Update the vocabulary.
$edit = array(
'name' => 'Nation'
);
$this->drupalPostForm('admin/structure/taxonomy/manage/country', $edit, t('Save'));
// Check that we still have the settings for the updated vocabulary.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', 'country');
$this->assertEqual($configuration->getDefaultLangcode(), 'current_interface', 'The default language configuration has been kept on the updated Country vocabulary.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been kept on the updated Country vocabulary.');
$this->assertEqual($configuration->uuid(), $uuid, 'The language configuration uuid has been kept on the updated Country vocabulary.');
}
}

Some files were not shown because too many files have changed in this diff Show more