diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 130d4cc..93743e4 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -56,6 +56,11 @@ label: label: 'Label' translatable: true +# String containing plural variants, separated by EXT. +plural_label: + type: label + label: 'Plural variants' + # Internal Drupal path path: type: string diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module index b4f882b..3c81f78 100644 --- a/core/modules/config_translation/config_translation.module +++ b/core/modules/config_translation/config_translation.module @@ -188,6 +188,7 @@ function config_translation_config_schema_info_alter(&$definitions) { 'text_format' => '\Drupal\config_translation\FormElement\TextFormat', 'mapping' => '\Drupal\config_translation\FormElement\ListElement', 'sequence' => '\Drupal\config_translation\FormElement\ListElement', + 'plural_label' => '\Drupal\config_translation\FormElement\PluralVariants', ); // Enhance the text and date type definitions with classes to generate proper diff --git a/core/modules/config_translation/src/FormElement/FormElementBase.php b/core/modules/config_translation/src/FormElement/FormElementBase.php index e13cc31..7db01e1 100644 --- a/core/modules/config_translation/src/FormElement/FormElementBase.php +++ b/core/modules/config_translation/src/FormElement/FormElementBase.php @@ -91,7 +91,6 @@ public function getTranslationBuild(LanguageInterface $source_language, Language * A render array for the source value. */ protected function getSourceElement(LanguageInterface $source_language, $source_config) { - // @todo Should support singular+plurals https://www.drupal.org/node/2454829 if ($source_config) { $value = '' . nl2br($source_config) . ''; } @@ -162,7 +161,6 @@ 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 Should support singular+plurals https://www.drupal.org/node/2454829 return array( '#title' => $this->t('!label (!source_language)', array( '!label' => $this->t($this->definition['label']), diff --git a/core/modules/config_translation/src/FormElement/PluralVariants.php b/core/modules/config_translation/src/FormElement/PluralVariants.php new file mode 100644 index 0000000..ec6b977 --- /dev/null +++ b/core/modules/config_translation/src/FormElement/PluralVariants.php @@ -0,0 +1,82 @@ +getNumberOfPlurals($source_language->getId()); + $values = explode(LOCALE_PLURAL_DELIMITER, $source_config); + $element = array( + '#type' => 'fieldset', + '#title' => SafeMarkup::format('@label (@source_language)', array( + '@label' => $this->t($this->definition->getLabel()), + '@source_language' => $source_language->getName(), + )), + '#tree' => TRUE, + ); + for ($i = 0; $i < $plurals; $i++) { + $element[$i] = array( + '#type' => 'item', + // @todo Should use better labels https://www.drupal.org/node/2499639 + '#title' => $i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form'), + '#markup' => SafeMarkup::format('@value', array( + '@langcode' => $source_language->getId(), + '@value' => isset($values[$i]) ? $values[$i] : $this->t('(Empty)'), + )), + ); + } + return $element; + } + + /** + * {@inheritdoc} + */ + protected function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) { + $plurals = $this->getNumberOfPlurals($translation_language->getId()); + $values = explode(LOCALE_PLURAL_DELIMITER, $translation_config); + $element = array( + '#type' => 'fieldset', + '#title' => SafeMarkup::format('@label (@translation_language)', array( + '@label' => $this->t($this->definition->getLabel()), + '@translation_language' => $translation_language->getName(), + )), + '#tree' => TRUE, + ); + for ($i = 0; $i < $plurals; $i++) { + $element[$i] = array( + '#type' => 'textfield', + // @todo Should use better labels https://www.drupal.org/node/2499639 + '#title' => $i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form'), + '#default_value' => isset($values[$i]) ? $values[$i] : '', + '#attributes' => array('lang' => $translation_language->getId()), + ); + } + return $element; + } + + /** + * {@inheritdoc} + */ + public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, $config_values, $base_key = NULL) { + $config_values = implode(LOCALE_PLURAL_DELIMITER, $config_values); + parent::setConfig($base_config, $config_translation, $config_values, $base_key); + } + +} diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php index c7218df..4bbb408 100644 --- a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php +++ b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php @@ -89,6 +89,7 @@ protected function setUp() { 'administer themes', 'bypass node access', 'administer content types', + 'translate interface', ] ); // Create and login user. @@ -610,6 +611,96 @@ public function testViewsTranslationUI() { } /** + * Test the number of source elements for plural strings in config translation forms. + */ + public function testPluralConfigStringsSourceElements() { + $this->drupalLogin($this->adminUser); + + // Languages to test, with various number of plural forms. + $languages = array( + 'vi' => array('plurals' => 1, 'expected' => array(TRUE, FALSE, FALSE, FALSE)), + 'fr' => array('plurals' => 2, 'expected' => array(TRUE, TRUE, FALSE, FALSE)), + 'sl' => array('plurals' => 4, 'expected' => array(TRUE, TRUE, TRUE, TRUE)), + ); + + foreach ($languages as $langcode => $data) { + // Import a .po file to add a new language with a given number of plural forms + $name = tempnam('temporary://', $langcode . '_') . '.po'; + file_put_contents($name, $this->getPoFile($data['plurals'])); + $this->drupalPostForm('admin/config/regional/translate/import', array( + 'langcode' => $langcode, + 'files[file]' => $name, + ), t('Import')); + + // Change the config langcode of the 'files' view. + $config = \Drupal::service('config.factory')->getEditable('views.view.files'); + $config->set('langcode', $langcode); + $config->save(); + + // Go to the translation page of the 'files' view. + $translation_url = 'admin/structure/views/view/files/translate/' . $langcode . '/add'; + $this->drupalGet($translation_url); + + // Check if the expected number of source elements are present. + foreach ($data['expected'] as $index => $expected) { + if ($expected) { + $this->assertRaw('edit-source-config-names-viewsviewfiles-display-default-display-options-fields-count-format-plural-string-' . $index); + } + else { + $this->assertNoRaw('edit-source-config-names-viewsviewfiles-display-default-display-options-fields-count-format-plural-string-' . $index); + } + } + } + } + + /** + * Test translation of plural strings with multiple plural forms in config. + */ + public function testPluralConfigStrings() { + $this->drupalLogin($this->adminUser); + + // First import a .po file with multiple plural forms. + // This will also automatically add the 'sl' language. + $name = tempnam('temporary://', "sl_") . '.po'; + file_put_contents($name, $this->getPoFile(4)); + $this->drupalPostForm('admin/config/regional/translate/import', array( + 'langcode' => 'sl', + 'files[file]' => $name, + ), t('Import')); + + // Translate the files view, as this one uses numeric formatters. + $description = 'Singular form'; + $field_value = '1 place'; + $field_value_plural = '@count places'; + $translation_url = 'admin/structure/views/view/files/translate/sl/add'; + $this->drupalGet($translation_url); + + // Make sure original text is present on this page, in addition to 2 new + // empty fields. + $this->assertRaw($description); + $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][0]', $field_value); + $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][1]', $field_value_plural); + $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][2]', ''); + $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][3]', ''); + + // Then make sure it also works. + $edit = [ + 'translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][0]' => $field_value . ' SL', + 'translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][1]' => $field_value_plural . ' 1 SL', + 'translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][2]' => $field_value_plural . ' 2 SL', + 'translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][3]' => $field_value_plural . ' 3 SL', + ]; + $this->drupalPostForm($translation_url, $edit, t('Save translation')); + + // Make sure the values have changed. + $this->drupalGet($translation_url); + $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][0]', "$field_value SL"); + $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][1]', "$field_value_plural 1 SL"); + $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][2]', "$field_value_plural 2 SL"); + $this->assertFieldByName('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][3]', "$field_value_plural 3 SL"); + } + + /** * Test translation storage in locale storage. */ public function testLocaleDBStorage() { @@ -936,4 +1027,43 @@ protected function assertDisabledTextarea($id) { ))); } + /** + * Helper function that returns a .po file with a given number of plural forms. + */ + public function getPoFile($plurals) { + $po_file = array(); + + $po_file[1] = <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=1; plural=0;\\n" +EOF; + + $po_file[2] = <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n>1);\\n" +EOF; + + $po_file[4] = <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=4; plural=(((n%100)==1)?(0):(((n%100)==2)?(1):((((n%100)==3)||((n%100)==4))?(2):3)));\\n" +EOF; + + return $po_file[$plurals]; + } + } diff --git a/core/modules/locale/src/Form/TranslateEditForm.php b/core/modules/locale/src/Form/TranslateEditForm.php index 44e86e7..bac65c7 100644 --- a/core/modules/locale/src/Form/TranslateEditForm.php +++ b/core/modules/locale/src/Form/TranslateEditForm.php @@ -123,6 +123,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { for ($i = 0; $i < $plurals; $i++) { $form['strings'][$string->lid]['translations'][$i] = array( '#type' => 'textarea', + // @todo Should use better labels https://www.drupal.org/node/2499639 '#title' => ($i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form')), '#rows' => $rows, '#default_value' => isset($translation_array[$i]) ? $translation_array[$i] : '', diff --git a/core/modules/views/config/schema/views.field.schema.yml b/core/modules/views/config/schema/views.field.schema.yml index 1c884fb..58d4c0d 100644 --- a/core/modules/views/config/schema/views.field.schema.yml +++ b/core/modules/views/config/schema/views.field.schema.yml @@ -117,8 +117,8 @@ views.field.numeric: type: boolean label: 'Format plural' format_plural_string: - type: label - label: 'Singular and one or more plurals' + type: plural_label + label: 'Plural variants' prefix: type: label label: 'Prefix' diff --git a/core/modules/views/src/Plugin/views/field/NumericField.php b/core/modules/views/src/Plugin/views/field/NumericField.php index 70622d1..999a648 100644 --- a/core/modules/views/src/Plugin/views/field/NumericField.php +++ b/core/modules/views/src/Plugin/views/field/NumericField.php @@ -102,6 +102,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { for ($i = 0; $i < $plurals; $i++) { $form['format_plural_values'][$i] = array( '#type' => 'textfield', + // @todo Should use better labels https://www.drupal.org/node/2499639 '#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.'),