PluralTranslatableMarkup.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php
  2. namespace Drupal\Core\StringTranslation;
  3. /**
  4. * A class to hold plural translatable markup.
  5. */
  6. class PluralTranslatableMarkup extends TranslatableMarkup {
  7. /**
  8. * The delimiter used to split plural strings.
  9. *
  10. * This is the ETX (End of text) character and is used as a minimal means to
  11. * separate singular and plural variants in source and translation text. It
  12. * was found to be the most compatible delimiter for the supported databases.
  13. */
  14. const DELIMITER = "\03";
  15. /**
  16. * The item count to display.
  17. *
  18. * @var int
  19. */
  20. protected $count;
  21. /**
  22. * The already translated string.
  23. *
  24. * @var string
  25. */
  26. protected $translatedString;
  27. /**
  28. * Constructs a new PluralTranslatableMarkup object.
  29. *
  30. * Parses values passed into this class through the format_plural() function
  31. * in Drupal and handles an optional context for the string.
  32. *
  33. * @param int $count
  34. * The item count to display.
  35. * @param string $singular
  36. * The string for the singular case. Make sure it is clear this is singular,
  37. * to ease translation (e.g. use "1 new comment" instead of "1 new"). Do not
  38. * use @count in the singular string.
  39. * @param string $plural
  40. * The string for the plural case. Make sure it is clear this is plural, to
  41. * ease translation. Use @count in place of the item count, as in
  42. * "@count new comments".
  43. * @param array $args
  44. * (optional) An array with placeholder replacements, keyed by placeholder.
  45. * See \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for
  46. * additional information about placeholders. Note that you do not need to
  47. * include @count in this array; this replacement is done automatically
  48. * for the plural cases.
  49. * @param array $options
  50. * (optional) An associative array of additional options. See t() for
  51. * allowed keys.
  52. * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
  53. * (optional) The string translation service.
  54. *
  55. * @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
  56. */
  57. public function __construct($count, $singular, $plural, array $args = [], array $options = [], TranslationInterface $string_translation = NULL) {
  58. $this->count = $count;
  59. $translatable_string = implode(static::DELIMITER, [$singular, $plural]);
  60. parent::__construct($translatable_string, $args, $options, $string_translation);
  61. }
  62. /**
  63. * Constructs a new class instance from already translated markup.
  64. *
  65. * This method ensures that the string is pluralized correctly. As opposed
  66. * to the __construct() method, this method is designed to be invoked with
  67. * a string already translated (such as with configuration translation).
  68. *
  69. * @param int $count
  70. * The item count to display.
  71. * @param string $translated_string
  72. * The already translated string.
  73. * @param array $args
  74. * An associative array of replacements to make after translation. Instances
  75. * of any key in this array are replaced with the corresponding value.
  76. * Based on the first character of the key, the value is escaped and/or
  77. * themed. See \Drupal\Component\Utility\SafeMarkup::format(). Note that you
  78. * do not need to include @count in this array; this replacement is done
  79. * automatically for the plural cases.
  80. * @param array $options
  81. * An associative array of additional options. See t() for allowed keys.
  82. *
  83. * @return \Drupal\Core\StringTranslation\PluralTranslatableMarkup
  84. * A PluralTranslatableMarkup object.
  85. */
  86. public static function createFromTranslatedString($count, $translated_string, array $args = [], array $options = []) {
  87. $plural = new static($count, '', '', $args, $options);
  88. $plural->translatedString = $translated_string;
  89. return $plural;
  90. }
  91. /**
  92. * Renders the object as a string.
  93. *
  94. * @return string
  95. * The translated string.
  96. */
  97. public function render() {
  98. if (!$this->translatedString) {
  99. $this->translatedString = $this->getStringTranslation()->translateString($this);
  100. }
  101. if ($this->translatedString === '') {
  102. return '';
  103. }
  104. $arguments = $this->getArguments();
  105. $arguments['@count'] = $this->count;
  106. $translated_array = explode(static::DELIMITER, $this->translatedString);
  107. if ($this->count == 1) {
  108. return $this->placeholderFormat($translated_array[0], $arguments);
  109. }
  110. $index = $this->getPluralIndex();
  111. if ($index == 0) {
  112. // Singular form.
  113. $return = $translated_array[0];
  114. }
  115. else {
  116. if (isset($translated_array[$index])) {
  117. // N-th plural form.
  118. $return = $translated_array[$index];
  119. }
  120. else {
  121. // If the index cannot be computed or there's no translation, use the
  122. // second plural form as a fallback (which allows for most flexibility
  123. // with the replaceable @count value).
  124. $return = $translated_array[1];
  125. }
  126. }
  127. return $this->placeholderFormat($return, $arguments);
  128. }
  129. /**
  130. * Gets the plural index through the gettext formula.
  131. *
  132. * @return int
  133. */
  134. protected function getPluralIndex() {
  135. // We have to test both if the function and the service exist since in
  136. // certain situations it is possible that locale code might be loaded but
  137. // the service does not exist. For example, where the parent test site has
  138. // locale installed but the child site does not.
  139. // @todo Refactor in https://www.drupal.org/node/2660338 so this code does
  140. // not depend on knowing that the Locale module exists.
  141. if (function_exists('locale_get_plural') && \Drupal::hasService('locale.plural.formula')) {
  142. return locale_get_plural($this->count, $this->getOption('langcode'));
  143. }
  144. return -1;
  145. }
  146. /**
  147. * {@inheritdoc}
  148. */
  149. public function __sleep() {
  150. return array_merge(parent::__sleep(), ['count']);
  151. }
  152. }