TranslatableMarkup.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. <?php
  2. namespace Drupal\Core\StringTranslation;
  3. use Drupal\Component\Render\FormattableMarkup;
  4. use Drupal\Component\Utility\ToStringTrait;
  5. use Drupal\Component\Utility\Unicode;
  6. /**
  7. * Provides translatable markup class.
  8. *
  9. * This object, when cast to a string, will return the formatted, translated
  10. * string. Avoid casting it to a string yourself, because it is preferable to
  11. * let the rendering system do the cast as late as possible in the rendering
  12. * process, so that this object itself can be put, untranslated, into render
  13. * caches and thus the cache can be shared between different language contexts.
  14. *
  15. * @see \Drupal\Component\Render\FormattableMarkup
  16. * @see \Drupal\Core\StringTranslation\TranslationManager::translateString()
  17. * @see \Drupal\Core\Annotation\Translation
  18. */
  19. class TranslatableMarkup extends FormattableMarkup {
  20. use ToStringTrait;
  21. /**
  22. * The translated markup without placeholder replacements.
  23. *
  24. * @var string
  25. */
  26. protected $translatedMarkup;
  27. /**
  28. * The translation options.
  29. *
  30. * @var array
  31. */
  32. protected $options;
  33. /**
  34. * The string translation service.
  35. *
  36. * @var \Drupal\Core\StringTranslation\TranslationInterface
  37. */
  38. protected $stringTranslation;
  39. /**
  40. * Constructs a new class instance.
  41. *
  42. * When possible, use the
  43. * \Drupal\Core\StringTranslation\StringTranslationTrait $this->t(). Otherwise
  44. * create a new \Drupal\Core\StringTranslation\TranslatableMarkup object
  45. * directly.
  46. *
  47. * Calling the trait's t() method or instantiating a new TranslatableMarkup
  48. * object serves two purposes:
  49. * - At run-time it translates user-visible text into the appropriate
  50. * language.
  51. * - Static analyzers detect calls to t() and new TranslatableMarkup, and add
  52. * the first argument (the string to be translated) to the database of
  53. * strings that need translation. These strings are expected to be in
  54. * English, so the first argument should always be in English.
  55. * To allow the site to be localized, it is important that all human-readable
  56. * text that will be displayed on the site or sent to a user is made available
  57. * in one of the ways supported by the
  58. * @link https://www.drupal.org/node/322729 Localization API @endlink.
  59. * See the @link https://www.drupal.org/node/322729 Localization API @endlink
  60. * pages for more information, including recommendations on how to break up or
  61. * not break up strings for translation.
  62. *
  63. * @section sec_translating_vars Translating Variables
  64. * $string should always be an English literal string.
  65. *
  66. * $string should never contain a variable, such as:
  67. * @code
  68. * new TranslatableMarkup($text)
  69. * @endcode
  70. * There are several reasons for this:
  71. * - Using a variable for $string that is user input is a security risk.
  72. * - Using a variable for $string that has even guaranteed safe text (for
  73. * example, user interface text provided literally in code), will not be
  74. * picked up by the localization static text processor. (The parameter could
  75. * be a variable if the entire string in $text has been passed into t() or
  76. * new TranslatableMarkup() elsewhere as the first argument, but that
  77. * strategy is not recommended.)
  78. *
  79. * It is especially important never to call new TranslatableMarkup($user_text)
  80. * or t($user_text) where $user_text is some text that a user entered -- doing
  81. * that can lead to cross-site scripting and other security problems. However,
  82. * you can use variable substitution in your string, to put variable text such
  83. * as user names or link URLs into translated text. Variable substitution
  84. * looks like this:
  85. * @code
  86. * new TranslatableMarkup("@name's blog", array('@name' => $account->getDisplayName()));
  87. * @endcode
  88. * Basically, you can put placeholders like @name into your string, and the
  89. * method will substitute the sanitized values at translation time. (See the
  90. * Localization API pages referenced above and the documentation of
  91. * \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
  92. * for details about how to safely and correctly define variables in your
  93. * string.) Translators can then rearrange the string as necessary for the
  94. * language (e.g., in Spanish, it might be "blog de @name").
  95. *
  96. * @param string $string
  97. * A string containing the English text to translate.
  98. * @param array $arguments
  99. * (optional) An associative array of replacements to make after
  100. * translation. Based on the first character of the key, the value is
  101. * escaped and/or themed. See
  102. * \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for
  103. * details.
  104. * @param array $options
  105. * (optional) An associative array of additional options, with the following
  106. * elements:
  107. * - 'langcode' (defaults to the current language): A language code, to
  108. * translate to a language other than what is used to display the page.
  109. * - 'context' (defaults to the empty context): The context the source
  110. * string belongs to.
  111. * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
  112. * (optional) The string translation service.
  113. *
  114. * @throws \InvalidArgumentException
  115. * Exception thrown when $string is not a string.
  116. *
  117. * @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
  118. * @see \Drupal\Core\StringTranslation\StringTranslationTrait::t()
  119. *
  120. * @ingroup sanitization
  121. */
  122. public function __construct($string, array $arguments = [], array $options = [], TranslationInterface $string_translation = NULL) {
  123. if (!is_string($string)) {
  124. $message = $string instanceof TranslatableMarkup ? '$string ("' . $string->getUntranslatedString() . '") must be a string.' : '$string ("' . (string) $string . '") must be a string.';
  125. throw new \InvalidArgumentException($message);
  126. }
  127. parent::__construct($string, $arguments);
  128. $this->options = $options;
  129. $this->stringTranslation = $string_translation;
  130. }
  131. /**
  132. * Gets the untranslated string value stored in this translated string.
  133. *
  134. * @return string
  135. * The string stored in this wrapper.
  136. */
  137. public function getUntranslatedString() {
  138. return $this->string;
  139. }
  140. /**
  141. * Gets a specific option from this translated string.
  142. *
  143. * @param string $name
  144. * Option name.
  145. *
  146. * @return mixed
  147. * The value of this option or empty string of option is not set.
  148. */
  149. public function getOption($name) {
  150. return isset($this->options[$name]) ? $this->options[$name] : '';
  151. }
  152. /**
  153. * Gets all options from this translated string.
  154. *
  155. * @return mixed[]
  156. * The array of options.
  157. */
  158. public function getOptions() {
  159. return $this->options;
  160. }
  161. /**
  162. * Gets all arguments from this translated string.
  163. *
  164. * @return mixed[]
  165. * The array of arguments.
  166. */
  167. public function getArguments() {
  168. return $this->arguments;
  169. }
  170. /**
  171. * Renders the object as a string.
  172. *
  173. * @return string
  174. * The translated string.
  175. */
  176. public function render() {
  177. if (!isset($this->translatedMarkup)) {
  178. $this->translatedMarkup = $this->getStringTranslation()->translateString($this);
  179. }
  180. // Handle any replacements.
  181. if ($args = $this->getArguments()) {
  182. return $this->placeholderFormat($this->translatedMarkup, $args);
  183. }
  184. return $this->translatedMarkup;
  185. }
  186. /**
  187. * Magic __sleep() method to avoid serializing the string translator.
  188. */
  189. public function __sleep() {
  190. return ['string', 'arguments', 'options'];
  191. }
  192. /**
  193. * Gets the string translation service.
  194. *
  195. * @return \Drupal\Core\StringTranslation\TranslationInterface
  196. * The string translation service.
  197. */
  198. protected function getStringTranslation() {
  199. if (!$this->stringTranslation) {
  200. $this->stringTranslation = \Drupal::service('string_translation');
  201. }
  202. return $this->stringTranslation;
  203. }
  204. /**
  205. * Returns the string length.
  206. *
  207. * @return int
  208. * The length of the string.
  209. */
  210. public function count() {
  211. return Unicode::strlen($this->render());
  212. }
  213. }