summaryrefslogtreecommitdiffstatshomepage
path: root/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php
blob: 49a2e0bfaad288686d14a55cb50cdbe910b81f60 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?php

namespace Drupal\Core\StringTranslation;

use Drupal\Component\Gettext\PoItem;

/**
 * A class to hold plural translatable markup.
 */
class PluralTranslatableMarkup extends TranslatableMarkup {

  /**
   * The item count to display.
   *
   * @var int
   */
  protected $count;

  /**
   * The already translated string.
   *
   * @var string
   */
  protected $translatedString;

  /**
   * Constructs a new PluralTranslatableMarkup object.
   *
   * Parses values passed into this class through the format_plural() function
   * in Drupal and handles an optional context for the string.
   *
   * @param int $count
   *   The item count to display.
   * @param string $singular
   *   The string for the singular case. Make sure it is clear this is singular,
   *   to ease translation (e.g. use "1 new comment" instead of "1 new"). Do not
   *   use @count in the singular string.
   * @param string $plural
   *   The string for the plural case. Make sure it is clear this is plural, to
   *   ease translation. Use @count in place of the item count, as in
   *   "@count new comments".
   * @param array $args
   *   (optional) An array with placeholder replacements, keyed by placeholder.
   *   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.
   * @param array $options
   *   (optional) An associative array of additional options. See t() for
   *   allowed keys.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
   *   (optional) The string translation service.
   *
   * @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
   */
  public function __construct($count, $singular, $plural, array $args = [], array $options = [], ?TranslationInterface $string_translation = NULL) {
    $this->count = $count;
    $translatable_string = implode(PoItem::DELIMITER, [$singular, $plural]);
    parent::__construct($translatable_string, $args, $options, $string_translation);
  }

  /**
   * Constructs a new class instance from already translated markup.
   *
   * This method ensures that the string is pluralized correctly. As opposed
   * to the __construct() 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 $translated_string
   *   The already translated string.
   * @param array $args
   *   An associative array of replacements to make after 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 \Drupal\Component\Render\FormattableMarkup. 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. See t() for allowed keys.
   *
   * @return static
   *   A PluralTranslatableMarkup object.
   */
  public static function createFromTranslatedString($count, $translated_string, array $args = [], array $options = []) {
    $plural = new static($count, '', '', $args, $options);
    $plural->translatedString = $translated_string;
    return $plural;
  }

  /**
   * Renders the object as a string.
   *
   * @return string
   *   The translated string.
   */
  public function render() {
    if (!$this->translatedString) {
      $this->translatedString = $this->getStringTranslation()->translateString($this);
    }
    if ($this->translatedString === '') {
      return '';
    }

    $arguments = $this->getArguments();
    $arguments['@count'] = $this->count;
    $translated_array = explode(PoItem::DELIMITER, $this->translatedString);

    $index = $this->getPluralIndex();
    if ($this->count == 1 || $index == 0 || count($translated_array) == 1) {
      // Singular form.
      $return = $translated_array[0];
    }
    else {
      // Nth plural form, fallback to second plural form.
      $return = $translated_array[$index] ?? $translated_array[1];
    }
    return $this->placeholderFormat($return, $arguments);
  }

  /**
   * Gets the plural index through the gettext formula.
   *
   * @return int
   *   The numeric index of the plural variant to use for this language and
   *   count combination. Defaults to -1 when the language was not found or does
   *   not have a plural formula.
   */
  protected function getPluralIndex() {
    // We have to test both if the function and the service exist since in
    // certain situations it is possible that locale code might be loaded but
    // the service does not exist. For example, where the parent test site has
    // locale installed but the child site does not.
    // @todo Refactor in https://www.drupal.org/node/2660338 so this code does
    // not depend on knowing that the Locale module exists.
    if (function_exists('locale_get_plural') && \Drupal::hasService('locale.plural.formula')) {
      return locale_get_plural($this->count, $this->getOption('langcode'));
    }
    return -1;
  }

  /**
   * {@inheritdoc}
   */
  public function __sleep(): array {
    return array_merge(parent::__sleep(), ['count']);
  }

}