diff --git a/core/core.services.yml b/core/core.services.yml
index 2b81197..cffc627 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -510,7 +510,7 @@ services:
- { name: string_translator, priority: 30 }
string_translation:
class: Drupal\Core\StringTranslation\TranslationManager
- arguments: ['@language_manager']
+ arguments: ['@language_manager', '@state']
calls:
- [initLanguageManager]
tags:
diff --git a/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php b/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php
index 85a6e3f..0946709 100644
--- a/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php
+++ b/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php
@@ -53,6 +53,28 @@ protected function formatPlural($count, $singular, $plural, array $args = array(
}
/**
+ * Formats a translated string containing a count of items.
+ *
+ * See the
+ * \Drupal\Core\StringTranslation\TranslationInterface::formatPluralTranslated()
+ * documentation for details.
+ */
+ protected function formatPluralTranslated($count, $translated, array $args = array(), array $options = array()) {
+ return $this->getStringTranslation()->formatPluralTranslated($count, $translated, $args, $options);
+ }
+
+ /**
+ * Returns number of plurals supported by a given language.
+ *
+ * See the
+ * \Drupal\Core\StringTranslation\TranslationInterface::getNumberOfPlurals()
+ * documentation for details.
+ */
+ protected function getNumberOfPlurals($langcode = NULL) {
+ return $this->getStringTranslation()->getNumberOfPlurals($langcode);
+ }
+
+ /**
* Gets the string translation service.
*
* @return \Drupal\Core\StringTranslation\TranslationInterface
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php b/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
index bc560cf..d3626a6 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
@@ -41,7 +41,7 @@ public function translate($string, array $args = array(), array $options = array
*
* This function ensures that the string is pluralized correctly. Since t() is
* called by this function, make sure not to pass already-localized strings to
- * it.
+ * it. See formatPluralTranslated() for that.
*
* For example:
* @code
@@ -71,7 +71,8 @@ public function translate($string, array $args = array(), array $options = array
* of any key in this array are replaced with the corresponding value.
* Based on the first character of the key, the value is escaped and/or
* themed. See format_string(). Note that you do not need to include @count
- * in this array; this replacement is done automatically for the plural case.
+ * in this array; this replacement is done automatically for the plural
+ * cases.
* @param array $options
* An associative array of additional options. See t() for allowed keys.
*
@@ -82,7 +83,53 @@ public function translate($string, array $args = array(), array $options = array
* @see \Drupal\Component\Utility\String
* @see t()
* @see format_string()
+ * @see self::formatPluralTranslated
*/
public function formatPlural($count, $singular, $plural, array $args = array(), array $options = array());
+ /**
+ * Formats an already translated string containing a count of items.
+ *
+ * This function ensures that the string is pluralized correctly. As opposed
+ * to the formatPlural() method, this method is designed to be invoked with
+ * a string already translated (such as with configuration translation).
+ *
+ * @param int $count
+ * The item count to display.
+ * @param string $translation
+ * The string containing the translation of a singular/plural pair. It may
+ * contain any number of possible variants (depending on the language
+ * translated to) separated by the value of the LOCALE_PLURAL_DELIMITER
+ * constant.
+ * @param array $args
+ * Associative array of replacements to make in the translation. Instances
+ * of any key in this array are replaced with the corresponding value.
+ * Based on the first character of the key, the value is escaped and/or
+ * themed. See format_string(). Note that you do not need to include @count
+ * in this array; this replacement is done automatically for the plural
+ * cases.
+ * @param array $options
+ * An associative array of additional options. t() defines all allowed keys.
+ * The 'context' key is not supported because the passed string is already
+ * translated.
+ *
+ * @return string
+ * The correct substring for the given $count with $args replaced.
+ *
+ * @see self::formatPlural
+ * @see format_string()
+ */
+ public function formatPluralTranslated($count, $translation, array $args = array(), array $options = array());
+
+ /**
+ * Returns number of plurals supported by a given language.
+ *
+ * @param null $langcode
+ * (optional) The language code. If not provided, the current language
+ * will be used.
+ * @return int
+ * Number of plural variants supported by the given language.
+ */
+ public function getNumberOfPlurals($langcode = NULL);
+
}
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationManager.php b/core/lib/Drupal/Core/StringTranslation/TranslationManager.php
index cdfc2df..9820d4c 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationManager.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationManager.php
@@ -10,6 +10,7 @@
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\String;
use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\State\StateInterface;
use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
/**
@@ -54,14 +55,24 @@ class TranslationManager implements TranslationInterface, TranslatorInterface {
protected $defaultLangcode;
/**
+ * The state service.
+ *
+ * @var \Drupal\Core\State\StateInterface
+ */
+ protected $state;
+
+ /**
* Constructs a TranslationManager object.
*
- * @param \Drupal\Core\Language\LanguageManagerInterface
+ * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
+ * @param \Drupal\Core\State\StateInterface $state
+ * (optional) The state service.
*/
- public function __construct(LanguageManagerInterface $language_manager) {
+ public function __construct(LanguageManagerInterface $language_manager, StateInterface $state = NULL) {
$this->languageManager = $language_manager;
$this->defaultLangcode = $language_manager->getDefaultLanguage()->getId();
+ $this->state = $state;
}
/**
@@ -152,13 +163,20 @@ public function translate($string, array $args = array(), array $options = array
* {@inheritdoc}
*/
public function formatPlural($count, $singular, $plural, array $args = array(), array $options = array()) {
- $args['@count'] = $count;
// Join both forms to search a translation.
$translatable_string = implode(LOCALE_PLURAL_DELIMITER, array($singular, $plural));
// Translate as usual.
$translated_strings = $this->translate($translatable_string, $args, $options);
+ return $this->formatPluralTranslated($count, $translated_strings, $args, $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formatPluralTranslated($count, $translation, array $args = array(), array $options = array()) {
+ $args['@count'] = $count;
// Split joined translation strings into array.
- $translated_array = explode(LOCALE_PLURAL_DELIMITER, $translated_strings);
+ $translated_array = explode(LOCALE_PLURAL_DELIMITER, $translation);
if ($count == 1) {
return SafeMarkup::set($translated_array[0]);
@@ -183,7 +201,29 @@ public function formatPlural($count, $singular, $plural, array $args = array(),
$return = $translated_array[1];
}
}
- return SafeMarkup::set($return);
+
+ if (empty($args)) {
+ return SafeMarkup::set($return);
+ }
+ else {
+ return String::format($return, $args);
+ }
+ }
+
+ /**
+ * @inheritdoc.
+ */
+ public function getNumberOfPlurals($langcode = NULL) {
+ // If the state service is not injected, we assume 2 plural variants are
+ // allowed. This may happen in the installer for simplicity.
+ if (isset($this->state)) {
+ $langcode = $langcode ?: $this->languageManager->getCurrentLanguage()->getId();
+ $plural_formulas = $this->state->get('locale.translation.plurals') ?: array();
+ if (isset($plural_formulas[$langcode]['plurals'])) {
+ return $plural_formulas[$langcode]['plurals'];
+ }
+ }
+ return 2;
}
/**
diff --git a/core/modules/config_translation/src/FormElement/FormElementBase.php b/core/modules/config_translation/src/FormElement/FormElementBase.php
index 7db01e1..19114ee 100644
--- a/core/modules/config_translation/src/FormElement/FormElementBase.php
+++ b/core/modules/config_translation/src/FormElement/FormElementBase.php
@@ -91,6 +91,9 @@ public function getTranslationBuild(LanguageInterface $source_language, Language
* A render array for the source value.
*/
protected function getSourceElement(LanguageInterface $source_language, $source_config) {
+ // @todo Should be able to render source as singular+plurals. Similar
+ // to TranslateEditForm::buildForm(), but here the source may also be
+ // multi-plural.
if ($source_config) {
$value = '' . nl2br($source_config) . '';
}
@@ -161,6 +164,8 @@ protected function getSourceElement(LanguageInterface $source_language, $source_
*/
protected function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
// Add basic properties that apply to all form elements.
+ // @todo Needs support for possibly singular+plurals input if the source
+ // was singular/plural. As in TranslateEditForm::buildForm().
return array(
'#title' => $this->t('!label (!source_language)', array(
'!label' => $this->t($this->definition['label']),
diff --git a/core/modules/file/config/install/views.view.files.yml b/core/modules/file/config/install/views.view.files.yml
index 6783c1b..526d9fd 100644
--- a/core/modules/file/config/install/views.view.files.yml
+++ b/core/modules/file/config/install/views.view.files.yml
@@ -529,8 +529,7 @@ display:
decimal: .
separator: ','
format_plural: true
- format_plural_singular: '1 place'
- format_plural_plural: '@count places'
+ format_plural_string: "1 place\x03@count places"
prefix: ''
suffix: ''
plugin_id: numeric
@@ -952,8 +951,7 @@ display:
decimal: .
separator: ','
format_plural: false
- format_plural_singular: '1'
- format_plural_plural: '@count'
+ format_plural_string: "1\x03@count"
prefix: ''
suffix: ''
plugin_id: numeric
diff --git a/core/modules/forum/tests/modules/forum_test_views/test_views/views.view.test_forum_index.yml b/core/modules/forum/tests/modules/forum_test_views/test_views/views.view.test_forum_index.yml
index 1f917f0..32a9306 100644
--- a/core/modules/forum/tests/modules/forum_test_views/test_views/views.view.test_forum_index.yml
+++ b/core/modules/forum/tests/modules/forum_test_views/test_views/views.view.test_forum_index.yml
@@ -141,8 +141,7 @@ display:
decimal: .
separator: ','
format_plural: false
- format_plural_singular: '1'
- format_plural_plural: '@count'
+ format_plural_string: "1\x03@count"
prefix: ''
suffix: ''
plugin_id: numeric
diff --git a/core/modules/statistics/tests/modules/statistics_test_views/test_views/views.view.test_statistics_integration.yml b/core/modules/statistics/tests/modules/statistics_test_views/test_views/views.view.test_statistics_integration.yml
index e8099fa..680648b 100644
--- a/core/modules/statistics/tests/modules/statistics_test_views/test_views/views.view.test_statistics_integration.yml
+++ b/core/modules/statistics/tests/modules/statistics_test_views/test_views/views.view.test_statistics_integration.yml
@@ -160,8 +160,7 @@ display:
decimal: .
separator: ''
format_plural: false
- format_plural_singular: '1'
- format_plural_plural: '@count'
+ format_plural_string: "1\x03@count"
prefix: ''
suffix: ''
plugin_id: numeric
@@ -218,8 +217,7 @@ display:
decimal: .
separator: ''
format_plural: false
- format_plural_singular: '1'
- format_plural_plural: '@count'
+ format_plural_string: "1\x03@count"
prefix: ''
suffix: ''
plugin_id: numeric
diff --git a/core/modules/views/config/schema/views.field.schema.yml b/core/modules/views/config/schema/views.field.schema.yml
index 0d636ac..69365e0 100644
--- a/core/modules/views/config/schema/views.field.schema.yml
+++ b/core/modules/views/config/schema/views.field.schema.yml
@@ -116,12 +116,9 @@ views.field.numeric:
format_plural:
type: boolean
label: 'Format plural'
- format_plural_singular:
+ format_plural_string:
type: label
- label: 'Singular form'
- format_plural_plural:
- type: label
- label: 'Plural form'
+ label: 'Singular and one or more plurals'
prefix:
type: label
label: 'Prefix'
diff --git a/core/modules/views/src/Plugin/views/field/Numeric.php b/core/modules/views/src/Plugin/views/field/Numeric.php
index da99d15..6b57d19 100644
--- a/core/modules/views/src/Plugin/views/field/Numeric.php
+++ b/core/modules/views/src/Plugin/views/field/Numeric.php
@@ -34,8 +34,7 @@ protected function defineOptions() {
$options['decimal'] = array('default' => '.');
$options['separator'] = array('default' => ',');
$options['format_plural'] = array('default' => FALSE);
- $options['format_plural_singular'] = array('default' => '1');
- $options['format_plural_plural'] = array('default' => '@count');
+ $options['format_plural_string'] = array('default' => '1' . LOCALE_PLURAL_DELIMITER . '@count');
$options['prefix'] = array('default' => '');
$options['suffix'] = array('default' => '');
@@ -93,28 +92,55 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
'#description' => $this->t('If checked, special handling will be used for plurality.'),
'#default_value' => $this->options['format_plural'],
);
- $form['format_plural_singular'] = array(
- '#type' => 'textfield',
- '#title' => $this->t('Singular form'),
- '#default_value' => $this->options['format_plural_singular'],
- '#description' => $this->t('Text to use for the singular form.'),
- '#states' => array(
- 'visible' => array(
- ':input[name="options[format_plural]"]' => array('checked' => TRUE),
- ),
- ),
+ $form['format_plural_string'] = array(
+ '#type' => 'value',
+ '#default_value' => $this->options['format_plural_string'],
);
- $form['format_plural_plural'] = array(
- '#type' => 'textfield',
- '#title' => $this->t('Plural form'),
- '#default_value' => $this->options['format_plural_plural'],
- '#description' => $this->t('Text to use for the plural form, @count will be replaced with the value.'),
- '#states' => array(
- 'visible' => array(
- ':input[name="options[format_plural]"]' => array('checked' => TRUE),
+
+ // @todo Figure out how to pass in the language of the view.
+ $plural_array = explode(LOCALE_PLURAL_DELIMITER, $this->options['format_plural_string']);
+ $plurals = $this->getNumberOfPlurals();
+ if ($plurals > 2) {
+ for ($i = 0; $i < $plurals; $i++) {
+ $form['format_plural_values'][$i] = array(
+ '#type' => 'textfield',
+ '#title' => ($i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form')),
+ '#default_value' => isset($plural_array[$i]) ? $plural_array[$i] : '',
+ '#description' => $this->t('Text to use for this variant, @count will be replaced with the value.'),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="options[format_plural]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ }
+ }
+ else {
+ // Fallback for unknown number of plurals.
+ $form['format_plural_values'][0] = array(
+ '#type' => 'textfield',
+ '#title' => $this->t('Singular form'),
+ '#default_value' => $plural_array[0],
+ '#description' => $this->t('Text to use for the singular form.'),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="options[format_plural]"]' => array('checked' => TRUE),
+ ),
),
- ),
- );
+ );
+ $form['format_plural_values'][1] = array(
+ '#type' => 'textfield',
+ '#title' => $this->t('Plural form'),
+ '#default_value' => isset($plural_array[1]) ? $plural_array[1] : '',
+ '#description' => $this->t('Text to use for the plural form, @count will be replaced with the value.'),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="options[format_plural]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ }
+
$form['prefix'] = array(
'#type' => 'textfield',
'#title' => $this->t('Prefix'),
@@ -132,6 +158,18 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
}
/**
+ * @inheritdoc
+ */
+ public function submitOptionsForm(&$form, FormStateInterface $form_state) {
+ // Merge plural format options into one string and drop the individual
+ // option values.
+ $options = &$form_state->getValue('options');
+ $options['format_plural_string'] = implode(LOCALE_PLURAL_DELIMITER, $options['format_plural_values']);
+ unset($options['format_plural_values']);
+ parent::submitOptionsForm($form, $form_state);
+ }
+
+ /**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
@@ -156,7 +194,7 @@ public function render(ResultRow $values) {
// Should we format as a plural.
if (!empty($this->options['format_plural'])) {
- $value = $this->formatPlural($value, $this->options['format_plural_singular'], $this->options['format_plural_plural']);
+ $value = $this->formatPluralTranslated($value, $this->options['format_plural_string']);
}
return $this->sanitizeValue($this->options['prefix'], 'xss')