diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 3edb8057..7b658e0 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -650,12 +650,9 @@ field.field_settings.integer: max: type: integer label: 'Maximum' - prefix: + format_plural_string: type: label - label: 'Prefix' - suffix: - type: label - label: 'Suffix' + label: 'Plural formatted prefix/suffix' field.value.integer: type: mapping @@ -688,12 +685,9 @@ field.field_settings.decimal: max: type: float label: 'Maximum' - prefix: - type: label - label: 'Prefix' - suffix: + format_plural_string: type: label - label: 'Suffix' + label: 'Plural formatted prefix/suffix' field.value.decimal: type: mapping @@ -719,12 +713,9 @@ field.field_settings.float: max: type: float label: 'Maximum' - prefix: - type: label - label: 'Prefix' - suffix: + format_plural_string: type: label - label: 'Suffix' + label: 'Plural formatted prefix/suffix' field.value.float: type: mapping diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml index 71b7ccd..984056f 100644 --- a/core/config/schema/core.entity.schema.yml +++ b/core/config/schema/core.entity.schema.yml @@ -202,6 +202,9 @@ field.widget.settings.number: placeholder: type: label label: 'Placeholder' + format_plural: + type: boolean + label: 'Whether or not to display prefix/suffix' field.widget.settings.checkbox: type: mapping @@ -292,9 +295,12 @@ field.formatter.settings.number_decimal: scale: type: integer label: 'Scale' - prefix_suffix: - type: boolean - label: 'Display prefix and suffix.' + format_plural: + type: string + label: 'Which prefix/suffix to display, if any' + format_plural_string: + type: label + label: 'Override for prefix/suffix' field.formatter.settings.number_integer: type: mapping @@ -303,9 +309,12 @@ field.formatter.settings.number_integer: thousand_separator: type: string label: 'Thousand marker' - prefix_suffix: - type: boolean - label: 'Display prefix and suffix.' + format_plural: + type: string + label: 'Which prefix/suffix to display, if any' + format_plural_string: + type: label + label: 'Override for prefix/suffix' field.formatter.settings.number_unformatted: type: mapping diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php index bee0144..9760f29 100644 --- a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php +++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php @@ -141,6 +141,11 @@ public function getRenderer($field_name) { // Instantiate the widget object from the stored display properties. if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) { + // Pass the language of the display configuration down to the widget, + // which otherwise doesn't have access to it because it is not a + // stand-alone config entity. It may be relevant for some operations, + // such as using plural formatting as a field prefix. + $configuration['settings']['_settings_langcode'] = $this->langcode; $widget = $this->pluginManager->getInstance(array( 'field_definition' => $definition, 'form_mode' => $this->originalMode, diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php index 27884cd..f7d1f3c 100644 --- a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php +++ b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php @@ -197,6 +197,11 @@ public function getRenderer($field_name) { // Instantiate the formatter object from the stored display properties. if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) { + // Pass the language of the display configuration down to the formatter, + // which otherwise doesn't have access to it because it is not a + // stand-alone config entity. It may be relevant for some operations, + // such as plural formatting. + $configuration['settings']['_settings_langcode'] = $this->langcode; $formatter = $this->pluginManager->getInstance(array( 'field_definition' => $definition, 'view_mode' => $this->originalMode, diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php index b835327..5b374b8 100644 --- a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php +++ b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php @@ -292,6 +292,11 @@ public function toArray() { if (!$definition->isDisplayConfigurable($this->displayContext)) { unset($properties['content'][$field_name]); unset($properties['hidden'][$field_name]); + // If _settings_langcode is in the settings, remove it. It should never + // be saved in the configuration. + if (isset($properties['content'][$field_name]['settings']['_settings_langcode'])) { + unset($properties['content'][$field_name]['settings']['_settings_langcode']); + } } } @@ -339,6 +344,12 @@ public function setComponent($name, array $options = array()) { // Ensure we always have an empty settings and array. $options += ['settings' => [], 'third_party_settings' => []]; + // If _settings_langcode is in the settings, remove it. It should never + // be saved in the configuration. + if (isset($options['settings']['_settings_langcode'])) { + unset($options['settings']['_settings_langcode']); + } + $this->content[$name] = $options; unset($this->hidden[$name]); unset($this->plugins[$name]); diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index d212474..12091df 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -13,6 +13,7 @@ use Drupal\Core\Field\FieldItemInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Render\Element; use Drupal\Core\TypedData\TranslatableInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -453,10 +454,21 @@ protected function getSingleFieldDisplay($entity, $field_name, $display_options) $bundle = $entity->bundle(); $key = $entity_type_id . ':' . $bundle . ':' . $field_name . ':' . hash('crc32b', serialize($display_options)); if (!isset($this->singleFieldDisplays[$key])) { + // Assign the incoming '_settings_langcode' as the langcode of the + // EntityViewDisplay, or default to the current interface language. + if (isset($display_options['settings']['_settings_langcode'])) { + $langcode = $display_options['settings']['_settings_langcode']; + unset($display_options['settings']['_settings_langcode']); + } + else { + $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE)->getId(); + } + $this->singleFieldDisplays[$key] = EntityViewDisplay::create(array( 'targetEntityType' => $entity_type_id, 'bundle' => $bundle, 'status' => TRUE, + 'langcode' => $langcode, ))->setComponent($field_name, $display_options); } $display = $this->singleFieldDisplays[$key]; diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php index eff6516..42f8248 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php @@ -118,7 +118,11 @@ public function resetCache(array $entities = NULL); * 'default_formatter' for the field type. The default formatter will * also be used if the requested formatter is not available. * - settings: (array) Settings specific to the formatter. Defaults to the - * formatter's default settings. + * formatter's default settings. A '_settings_langcode' entry should be + * present to indicate the language of the settings that are being + * provided (typically, the language of the configuration entity that + * contains the formatter, such as an entity view mode). If absent, the + * current interface language will be assumed. * - weight: (float) The weight to assign to the renderable element. * Defaults to 0. * diff --git a/core/lib/Drupal/Core/Field/FormatterPluginManager.php b/core/lib/Drupal/Core/Field/FormatterPluginManager.php index e6054a9..9136c13 100644 --- a/core/lib/Drupal/Core/Field/FormatterPluginManager.php +++ b/core/lib/Drupal/Core/Field/FormatterPluginManager.php @@ -73,7 +73,7 @@ public function createInstance($plugin_id, array $configuration = array()) { } /** - * Overrides PluginManagerBase::getInstance(). + * Creates a formatter instance with the provided options. * * @param array $options * An array with the following key/value pairs: @@ -92,6 +92,11 @@ public function createInstance($plugin_id, array $configuration = array()) { * also be used if the requested formatter is not available. * - settings: (array) Settings specific to the formatter. Each setting * defaults to the default value specified in the formatter definition. + * A '_settings_langcode' entry should be present to indicate the + * language of the settings that are being provided (typically, the + * language of the configuration entity that contains the formatter, + * such as an entity view mode). If absent, the current interface + * language will be assumed. * - third_party_settings: (array) Settings provided by other extensions * through hook_field_formatter_third_party_settings_form(). * @@ -116,7 +121,7 @@ public function getInstance(array $options) { // - the formatter is not applicable to the field definition. $definition = $this->getDefinition($configuration['type'], FALSE); if (!isset($definition['class']) || !in_array($field_type, $definition['field_types']) || !$definition['class']::isApplicable($field_definition)) { - // Grab the default widget for the field type. + // Grab the default formatter for the field type. $field_type_definition = $this->fieldTypeManager->getDefinition($field_type); if (empty($field_type_definition['default_formatter'])) { return NULL; @@ -128,6 +133,7 @@ public function getInstance(array $options) { 'field_definition' => $field_definition, 'view_mode' => $options['view_mode'], ); + return $this->createInstance($plugin_id, $configuration); } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/DecimalFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/DecimalFormatter.php index 0e43ac1..7c9a17b 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/DecimalFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/DecimalFormatter.php @@ -32,10 +32,8 @@ class DecimalFormatter extends NumericFormatterBase { */ public static function defaultSettings() { return array( - 'thousand_separator' => '', 'decimal_separator' => '.', 'scale' => 2, - 'prefix_suffix' => TRUE, ) + parent::defaultSettings(); } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/IntegerFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/IntegerFormatter.php index 8768474..20e5d4b 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/IntegerFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/IntegerFormatter.php @@ -27,16 +27,6 @@ class IntegerFormatter extends NumericFormatterBase { /** * {@inheritdoc} */ - public static function defaultSettings() { - return array( - 'thousand_separator' => '', - 'prefix_suffix' => TRUE, - ) + parent::defaultSettings(); - } - - /** - * {@inheritdoc} - */ protected function numberFormat($number) { return number_format($number, 0, '', $this->getSetting('thousand_separator')); } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php index 28b0159..6b3d81e 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php @@ -11,6 +11,7 @@ use Drupal\Core\Field\FormatterBase; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\PluralTranslatableMarkup; /** * Parent plugin for decimal and integer formatters. @@ -20,6 +21,32 @@ use AllowedTagsXssTrait; /** + * Indicates plural formatting using the setting from the field. + */ + const FORMAT_PLURAL_USE_FIELD_SETTING = 'field'; + + /** + * Indicates plural formatting using the setting from the formatter. + */ + const FORMAT_PLURAL_USE_FORMATTER_SETTING = 'formatter'; + + /** + * Indicates no plural formatting. + */ + const FORMAT_PLURAL_NONE = 'none'; + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + return array( + 'thousand_separator' => '', + 'format_plural' => NumericFormatterBase::FORMAT_PLURAL_NONE, + 'format_plural_string' => '', + ) + parent::defaultSettings(); + } + + /** * {@inheritdoc} */ public function settingsForm(array $form, FormStateInterface $form_state) { @@ -39,16 +66,80 @@ public function settingsForm(array $form, FormStateInterface $form_state) { '#weight' => 0, ); - $elements['prefix_suffix'] = array( - '#type' => 'checkbox', - '#title' => t('Display prefix and suffix'), - '#default_value' => $this->getSetting('prefix_suffix'), - '#weight' => 10, + $elements['format_plural'] = array( + '#type' => 'radios', + '#title' => $this->t('Display prefix/suffix'), + '#default_value' => $this->getSetting('format_plural'), + '#options' => array( + NumericFormatterBase::FORMAT_PLURAL_NONE => $this->t('No prefix/suffix'), + NumericFormatterBase::FORMAT_PLURAL_USE_FIELD_SETTING => $this->t('Use setting from the field settings'), + NumericFormatterBase::FORMAT_PLURAL_USE_FORMATTER_SETTING => $this->t('Override the setting from the field settings'), + ), + ); + + // @todo This field needs to have some kind of #states magic so that it + // only shows if the option on format_plural is + // NumericFormatterBase::FORMAT_PLURAL_USE_FORMATTER_SETTING. + // Need to do this before this patch is finalized! + $elements['format_plural_values'] = array( + '#type' => 'fieldset', + '#title' => $this->t('Prefix/suffix override'), + '#after_build' => [[get_class($this), 'formatPluralValuesAfterBuild']], ); + $langcode = $this->getSetting('_settings_langcode'); + $plurals = $this->getNumberOfPlurals($langcode); + $labels = $this->getPluralLabels($langcode); + $plural_string = $this->getSetting('format_plural_string'); + if (!$plural_string) { + $settings = $this->getFieldSettings(); + if (isset($settings['format_plural_string'])) { + $plural_string = $settings['format_plural_string']; + } + } + $plural_array = explode(LOCALE_PLURAL_DELIMITER, $plural_string); + for ($i = 0; $i < $plurals; $i++) { + $elements['format_plural_values'][$i] = array( + '#type' => 'textfield', + '#title' => $labels[$i]['label'], + '#description' => $labels[$i]['description'], + '#default_value' => isset($plural_array[$i]) ? $plural_array[$i] : '', + ); + } + return $elements; } + /** + * After-build callback: Sets the value for the format_plural_string property. + * + * This method is assigned as an #after_build function in the settings form. + * + * The user interface for setting up singular/plural forms is an array of text + * fields, for usability, but for translation purposes, the values have to be + * stored as a single string in the field settings. This function implodes the + * values. + * + * @param array $element + * Textfield element where the plural format should be set. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * Textfield element with plural string set. + */ + public static function formatPluralValuesAfterBuild($element, FormStateInterface $form_state) { + if ($form_state->isProcessingInput()) { + $keys = $element['#parents']; + $array_value = $form_state->getValue($keys); + $string_value = implode(LOCALE_PLURAL_DELIMITER, $array_value); + array_pop($keys); + $keys[] = 'format_plural_string'; + $form_state->setValue($keys, $string_value); + } + return $element; + } + /** * {@inheritdoc} */ @@ -56,8 +147,11 @@ public function settingsSummary() { $summary = array(); $summary[] = $this->numberFormat(1234.1234567890); - if ($this->getSetting('prefix_suffix')) { - $summary[] = t('Display with prefix and suffix.'); + if ($this->getSetting('format_plural') == NumericFormatterBase::FORMAT_PLURAL_USE_FIELD_SETTING) { + $summary[] = $this->t('Display prefix/suffix from the field settings.'); + } + elseif ($this->getSetting('format_plural') == NumericFormatterBase::FORMAT_PLURAL_USE_FORMATTER_SETTING) { + $summary[] = $this->t('Display overridden prefix/suffix.'); } return $summary; @@ -68,19 +162,24 @@ public function settingsSummary() { */ public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); - $settings = $this->getFieldSettings(); + $field_settings = $this->getFieldSettings(); foreach ($items as $delta => $item) { $output = $this->numberFormat($item->value); // Account for prefix and suffix. - if ($this->getSetting('prefix_suffix')) { - $prefixes = isset($settings['prefix']) ? array_map(array('Drupal\Core\Field\FieldFilteredMarkup', 'create'), explode('|', $settings['prefix'])) : array(''); - $suffixes = isset($settings['suffix']) ? array_map(array('Drupal\Core\Field\FieldFilteredMarkup', 'create'), explode('|', $settings['suffix'])) : array(''); - $prefix = (count($prefixes) > 1) ? $this->formatPlural($item->value, $prefixes[0], $prefixes[1]) : $prefixes[0]; - $suffix = (count($suffixes) > 1) ? $this->formatPlural($item->value, $suffixes[0], $suffixes[1]) : $suffixes[0]; - $output = $prefix . $output . $suffix; + $format_string = ''; + if ($this->getSetting('format_plural') == NumericFormatterBase::FORMAT_PLURAL_USE_FIELD_SETTING) { + $format_string = $field_settings['format_plural_string']; + } + elseif ($this->getSetting('format_plural') == NumericFormatterBase::FORMAT_PLURAL_USE_FORMATTER_SETTING) { + $format_string = $this->getSetting('format_plural_string'); } + + if ($format_string) { + $output = PluralTranslatableMarkup::createFromTranslatedString($item->value, $format_string, ['@count' => $output], ['langcode' => $this->getSetting('_settings_langcode')]); + } + // Output the raw value in a content attribute if the text of the HTML // element differs from the raw value (for example when a prefix is used). if (isset($item->_attributes) && $item->value != $output) { diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php index 78d5d9e..2c3377a 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php @@ -40,18 +40,6 @@ public static function defaultStorageSettings() { /** * {@inheritdoc} */ - public static function defaultFieldSettings() { - return array( - 'min' => '', - 'max' => '', - 'prefix' => '', - 'suffix' => '', - ) + parent::defaultFieldSettings(); - } - - /** - * {@inheritdoc} - */ public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { $properties['value'] = DataDefinition::create('integer') ->setLabel(t('Integer value')) diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php index 9f9bd0b..96d2617 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php @@ -22,8 +22,7 @@ public static function defaultFieldSettings() { return array( 'min' => '', 'max' => '', - 'prefix' => '', - 'suffix' => '', + 'format_plural_string' => '', ) + parent::defaultFieldSettings(); } @@ -36,31 +35,65 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { $element['min'] = array( '#type' => 'number', - '#title' => t('Minimum'), + '#title' => $this->t('Minimum'), '#default_value' => $settings['min'], - '#description' => t('The minimum value that should be allowed in this field. Leave blank for no minimum.'), + '#description' => $this->t('The minimum value that should be allowed in this field. Leave blank for no minimum.'), ); $element['max'] = array( '#type' => 'number', - '#title' => t('Maximum'), + '#title' => $this->t('Maximum'), '#default_value' => $settings['max'], - '#description' => t('The maximum value that should be allowed in this field. Leave blank for no maximum.'), + '#description' => $this->t('The maximum value that should be allowed in this field. Leave blank for no maximum.'), ); - $element['prefix'] = array( - '#type' => 'textfield', - '#title' => t('Prefix'), - '#default_value' => $settings['prefix'], - '#size' => 60, - '#description' => t("Define a string that should be prefixed to the value, like '$ ' or '€ '. Leave blank for none. Separate singular and plural values with a pipe ('pound|pounds')."), - ); - $element['suffix'] = array( - '#type' => 'textfield', - '#title' => t('Suffix'), - '#default_value' => $settings['suffix'], - '#size' => 60, - '#description' => t("Define a string that should be suffixed to the value, like ' m', ' kb/s'. Leave blank for none. Separate singular and plural values with a pipe ('pound|pounds')."), + + $element['format_plural_values'] = array( + '#type' => 'fieldset', + '#title' => $this->t('Plural formatted prefix/suffix'), + '#after_build' => [[get_class($this), 'formatPluralValuesAfterBuild']], ); + $langcode = $this->getFieldDefinition()->get('langcode'); + $plurals = $this->getNumberOfPlurals($langcode); + $labels = $this->getPluralLabels($langcode); + $plural_array = explode(LOCALE_PLURAL_DELIMITER, $settings['format_plural_string']); + for ($i = 0; $i < $plurals; $i++) { + $element['format_plural_values'][$i] = array( + '#type' => 'textfield', + '#title' => $labels[$i]['label'], + '#description' => $labels[$i]['description'], + '#default_value' => isset($plural_array[$i]) ? $plural_array[$i] : '', + ); + } + + return $element; + } + /** + * After-build callback: Sets the value for the format_plural_string property. + * + * This method is assigned as an #after_build function in the settings form. + * + * The user interface for setting up singular/plural forms is an array of text + * fields, for usability, but for translation purposes, the values have to be + * stored as a single string in the field settings. This function implodes the + * values. + * + * @param array $element + * Textfield element where the plural format should be set. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * Textfield element with plural string set. + */ + public static function formatPluralValuesAfterBuild($element, FormStateInterface $form_state) { + if ($form_state->isProcessingInput()) { + $keys = $element['#parents']; + $array_value = $form_state->getValue($keys); + $string_value = implode(LOCALE_PLURAL_DELIMITER, $array_value); + array_pop($keys); + $keys[] = 'format_plural_string'; + $form_state->setValue($keys, $string_value); + } return $element; } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php index 57abc60..f7f6420 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php @@ -34,6 +34,7 @@ class NumberWidget extends WidgetBase { public static function defaultSettings() { return array( 'placeholder' => '', + 'format_plural' => TRUE, ) + parent::defaultSettings(); } @@ -47,6 +48,14 @@ public function settingsForm(array $form, FormStateInterface $form_state) { '#default_value' => $this->getSetting('placeholder'), '#description' => t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'), ); + + $element['format_plural'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Display prefix/suffix'), + '#description' => $this->t('If checked, prefix/suffix from the field will be displayed.'), + '#default_value' => $this->getSetting('format_plural'), + ); + return $element; } @@ -58,10 +67,14 @@ public function settingsSummary() { $placeholder = $this->getSetting('placeholder'); if (!empty($placeholder)) { - $summary[] = t('Placeholder: @placeholder', array('@placeholder' => $placeholder)); + $summary[] = $this->t('Placeholder: @placeholder', array('@placeholder' => $placeholder)); } else { - $summary[] = t('No placeholder'); + $summary[] = $this->t('No placeholder'); + } + + if ($this->getSetting('format_plural')) { + $summary[] = $this->t('Prefix/suffix from the field are displayed'); } return $summary; @@ -100,13 +113,19 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen } // Add prefix and suffix. - if ($field_settings['prefix']) { - $prefixes = explode('|', $field_settings['prefix']); - $element['#field_prefix'] = FieldFilteredMarkup::create(array_pop($prefixes)); - } - if ($field_settings['suffix']) { - $suffixes = explode('|', $field_settings['suffix']); - $element['#field_suffix'] = FieldFilteredMarkup::create(array_pop($suffixes)); + if ($field_settings['format_plural_string'] && $this->getSetting('format_plural')) { + // The field setting is a string containing all plural variants, suitable + // for use in formatPlural() methods, but already translated into the + // UI language. Pop off the last plural variant, and use the parts + // before/after @count in that string as the prefix/suffix for this field. + $values = explode(LOCALE_PLURAL_DELIMITER, $field_settings['format_plural_string']); + $labels = explode('@count', array_pop($values)); + if (isset($labels[0])) { + $element['#field_prefix'] = FieldFilteredMarkup::create(trim($labels[0])); + } + if (isset($labels[1])) { + $element['#field_suffix'] = FieldFilteredMarkup::create(trim($labels[1])); + } } return array('value' => $element); diff --git a/core/lib/Drupal/Core/Field/WidgetPluginManager.php b/core/lib/Drupal/Core/Field/WidgetPluginManager.php index 2bc41f1..510c685 100644 --- a/core/lib/Drupal/Core/Field/WidgetPluginManager.php +++ b/core/lib/Drupal/Core/Field/WidgetPluginManager.php @@ -55,7 +55,7 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac } /** - * Overrides PluginManagerBase::getInstance(). + * Creates a widget instance with the provided options. * * @param array $options * An array with the following key/value pairs: @@ -71,6 +71,11 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac * used if the requested widget is not available. * - settings: (array) Settings specific to the widget. Each setting * defaults to the default value specified in the widget definition. + * A '_settings_langcode' entry should be present to indicate the + * language of the settings that are being provided (typically, the + * language of the configuration entity that contains the widget, + * such as an entity form mode). If absent, the current interface + * language will be assumed. * - third_party_settings: (array) Settings provided by other extensions * through hook_field_formatter_third_party_settings_form(). * diff --git a/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php b/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php index 583ab18..4bcccd2 100644 --- a/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php +++ b/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php @@ -63,7 +63,9 @@ class PluralTranslatableMarkup extends TranslatableMarkup { * See \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for * additional information about placeholders. Note that you do not need to * include @count in this array; this replacement is done automatically - * for the plural cases. + * for the plural cases. But you can include @count to override the + * replacement value (for instance, to use a formatted number instead of + * the raw number). * @param array $options * (optional) An associative array of additional options. See t() for * allowed keys. @@ -95,7 +97,9 @@ public function __construct($count, $singular, $plural, array $args = [], array * Based on the first character of the key, the value is escaped and/or * themed. See \Drupal\Component\Utility\SafeMarkup::format(). Note that you * do not need to include @count in this array; this replacement is done - * automatically for the plural cases. + * automatically for the plural cases. But you can include @count to + * override the replacement value (for instance, to use a formatted number + * instead of the raw number). * @param array $options * An associative array of additional options. See t() for allowed keys. * @@ -123,7 +127,10 @@ public function render() { } $arguments = $this->getArguments(); - $arguments['@count'] = $this->count; + // Allow overrides of what gets substituted in for @count. + if (!isset($arguments['@count'])) { + $arguments['@count'] = $this->count; + } $translated_array = explode(static::DELIMITER, $this->translatedString); if ($this->count == 1) { diff --git a/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php b/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php index f4b4bd3..cfe8167 100644 --- a/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php +++ b/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php @@ -84,6 +84,59 @@ protected function getNumberOfPlurals($langcode = NULL) { } /** + * Returns the labels and descriptions for plural variants in a language. + * + * @param string $langcode + * The language code. + * + * @return array[] + * An array of labels and descriptions to use in forms for the plural + * variants of $langcode, translated into the current interface language. + * Each item in the returned array is an array with elements 'label' and + * 'description' for the corresponding plural variant. + */ + protected function getPluralLabels($langcode) { + $plurals = $this->getNumberOfPlurals($langcode); + + // @todo The return values for both cases should be more relevant to the + // actual plural rules of the language; see + // https://www.drupal.org/node/2499639. + + if ($plurals == 1) { + return [ + [ + 'label' => $this->t('Form for all numbers'), + 'description' => $this->t('Text to use for all numbers; @count will be replaced with the value.'), + ], + ]; + } + + if ($plurals == 2) { + return [ + [ + 'label' => $this->t('Singular form'), + 'description' => $this->t('Text to use for the singular form; @count will be replaced with the value.'), + ], + [ + 'label' => $this->t('Plural form'), + 'description' => $this->t('Text to use for the plural form; @count will be replaced with the value.'), + ], + ]; + } + + // For the != 2 case, generate labels. + $labels = []; + for ($i = 0; $i < $plurals; $i++) { + $labels[] = [ + 'label' => ($i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form')), + 'description' => $this->t('Text to use for this variant; @count will be replaced with the value.'), + ]; + } + + return $labels; + } + + /** * Gets the string translation service. * * @return \Drupal\Core\StringTranslation\TranslationInterface diff --git a/core/modules/field/src/Tests/Migrate/d6/MigrateFieldFormatterSettingsTest.php b/core/modules/field/src/Tests/Migrate/d6/MigrateFieldFormatterSettingsTest.php index 435c83c..191629c 100644 --- a/core/modules/field/src/Tests/Migrate/d6/MigrateFieldFormatterSettingsTest.php +++ b/core/modules/field/src/Tests/Migrate/d6/MigrateFieldFormatterSettingsTest.php @@ -74,7 +74,8 @@ public function testEntityDisplaySettings() { $expected['type'] = 'number_integer'; $expected['settings'] = array( 'thousand_separator' => ',', - 'prefix_suffix' => TRUE, + 'format_plural' => 'none', + 'format_plural_string' => '', ); $component = $display->getComponent('field_test_two'); $this->assertIdentical($expected, $component); @@ -84,7 +85,8 @@ public function testEntityDisplaySettings() { 'scale' => 2, 'decimal_separator' => '.', 'thousand_separator' => ',', - 'prefix_suffix' => TRUE, + 'format_plural' => 'none', + 'format_plural_string' => '', ); $component = $display->getComponent('field_test_three'); $this->assertIdentical($expected, $component); diff --git a/core/modules/field/src/Tests/Migrate/d6/MigrateFieldInstanceTest.php b/core/modules/field/src/Tests/Migrate/d6/MigrateFieldInstanceTest.php index afc6703..f98dc8a 100644 --- a/core/modules/field/src/Tests/Migrate/d6/MigrateFieldInstanceTest.php +++ b/core/modules/field/src/Tests/Migrate/d6/MigrateFieldInstanceTest.php @@ -45,8 +45,7 @@ public function testFieldInstanceSettings() { $expected = array( 'min' => 10, 'max' => 100, - 'prefix' => 'pref', - 'suffix' => 'suf', + 'format_plural_string' => 'pref@count suf', 'unsigned' => FALSE, 'size' => 'normal', ); @@ -57,8 +56,7 @@ public function testFieldInstanceSettings() { $expected = array( 'min' => 100.0, 'max' => 200.0, - 'prefix' => 'id-', - 'suffix' => '', + 'format_plural_string' => 'id-@count'; ); $this->assertIdentical($expected, $field->getSettings()); diff --git a/core/modules/field/src/Tests/Migrate/d6/MigrateFieldWidgetSettingsTest.php b/core/modules/field/src/Tests/Migrate/d6/MigrateFieldWidgetSettingsTest.php index 891eb69..4cfe61c 100644 --- a/core/modules/field/src/Tests/Migrate/d6/MigrateFieldWidgetSettingsTest.php +++ b/core/modules/field/src/Tests/Migrate/d6/MigrateFieldWidgetSettingsTest.php @@ -36,7 +36,7 @@ public function testWidgetSettings() { // Text field. $component = $form_display->getComponent('field_test'); $expected = array('weight' => 1, 'type' => 'text_textfield'); - $expected['settings'] = array('size' => 60, 'placeholder' => ''); + $expected['settings'] = array('size' => 60, 'placeholder' => '', 'format_plural' => TRUE); $expected['third_party_settings'] = array(); $this->assertIdentical($expected, $component, 'Text field settings are correct.'); @@ -44,7 +44,7 @@ public function testWidgetSettings() { $component = $form_display->getComponent('field_test_two'); $expected['type'] = 'number'; $expected['weight'] = 1; - $expected['settings'] = array('placeholder' => ''); + $expected['settings'] = array('placeholder' => '', 'format_plural' => TRUE); $this->assertIdentical($expected, $component); // Float field. @@ -82,7 +82,7 @@ public function testWidgetSettings() { $component = $form_display->getComponent('field_test_phone'); $expected['type'] = 'telephone_default'; $expected['weight'] = 13; - $expected['settings'] = array('placeholder' => ''); + $expected['settings'] = array('placeholder' => '', 'format_plural' => TRUE); $this->assertIdentical($expected, $component); // Date fields. diff --git a/core/modules/field_ui/src/Tests/EntityDisplayTest.php b/core/modules/field_ui/src/Tests/EntityDisplayTest.php index 7c7a48b..91a526f 100644 --- a/core/modules/field_ui/src/Tests/EntityDisplayTest.php +++ b/core/modules/field_ui/src/Tests/EntityDisplayTest.php @@ -214,6 +214,7 @@ public function testFieldComponent() { 'type' => $default_formatter, 'settings' => $formatter_settings, 'third_party_settings' => array(), + '_settings_langcode' => 'en', ); $this->assertEqual($display->getComponent($field_name), $expected); diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml index b61f1ad..c6b5b1c 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml @@ -106,7 +106,7 @@ display: type: number_integer settings: thousand_separator: '' - prefix_suffix: true + format_plural: 'field' group_column: value group_columns: { } group_rows: true diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml index 96d0559..a99eb9a 100644 --- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml +++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml @@ -140,7 +140,7 @@ display: type: number_integer settings: thousand_separator: '' - prefix_suffix: true + format_plural: 'field' group_column: value group_columns: { } group_rows: true diff --git a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php index 9e94a63..0b63fd3 100644 --- a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php +++ b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php @@ -196,6 +196,7 @@ protected function buildFields(array $values) { if ($values && ($field_ids = $this->getRenderableFieldIds())) { $entity_type_id = $this->getEntityTypeId(); + $settings_langcode = $this->view->storage->language()->getId(); // Collect the entities for the relationship, fetch the right translation, // and group by bundle. For each result row, the corresponding entity can @@ -232,6 +233,7 @@ protected function buildFields(array $values) { 'targetEntityType' => $entity_type_id, 'bundle' => $bundle, 'status' => TRUE, + 'langcode' => $settings_langcode, ]); foreach ($display_fields['field_ids'] as $field) { $display->setComponent($field->definition['field_name'], [ diff --git a/core/modules/views/src/Plugin/views/field/Field.php b/core/modules/views/src/Plugin/views/field/Field.php index 33253d1..9d0b6ec 100644 --- a/core/modules/views/src/Plugin/views/field/Field.php +++ b/core/modules/views/src/Plugin/views/field/Field.php @@ -471,6 +471,8 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { $format = $this->options['type']; $settings = $this->options['settings'] + $this->formatterPluginManager->getDefaultSettings($format); + $settings_langcode = $this->view->storage->language()->getId(); + $settings['_settings_langcode'] = $settings_langcode; $options = array( 'field_definition' => $field, diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml index ec19b13..c391c25 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml @@ -180,7 +180,7 @@ display: type: number_integer settings: thousand_separator: '' - prefix_suffix: true + format_plural: 'field' group_column: value group_columns: { } group_rows: true