TwigNodeTrans.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. <?php
  2. namespace Drupal\Core\Template;
  3. use Twig\Node\CheckToStringNode;
  4. /**
  5. * A class that defines the Twig 'trans' tag for Drupal.
  6. *
  7. * This Twig extension was originally based on Twig i18n extension. It has been
  8. * severely modified to work properly with the complexities of the Drupal
  9. * translation system.
  10. *
  11. * @see https://twig-extensions.readthedocs.io/en/latest/i18n.html
  12. * @see https://github.com/fabpot/Twig-extensions
  13. */
  14. class TwigNodeTrans extends \Twig_Node {
  15. /**
  16. * {@inheritdoc}
  17. */
  18. public function __construct(\Twig_Node $body, \Twig_Node $plural = NULL, \Twig_Node_Expression $count = NULL, \Twig_Node_Expression $options = NULL, $lineno, $tag = NULL) {
  19. $nodes['body'] = $body;
  20. if ($count !== NULL) {
  21. $nodes['count'] = $count;
  22. }
  23. if ($plural !== NULL) {
  24. $nodes['plural'] = $plural;
  25. }
  26. if ($options !== NULL) {
  27. $nodes['options'] = $options;
  28. }
  29. parent::__construct($nodes, [], $lineno, $tag);
  30. }
  31. /**
  32. * {@inheritdoc}
  33. */
  34. public function compile(\Twig_Compiler $compiler) {
  35. $compiler->addDebugInfo($this);
  36. list($singular, $tokens) = $this->compileString($this->getNode('body'));
  37. $plural = NULL;
  38. if ($this->hasNode('plural')) {
  39. list($plural, $pluralTokens) = $this->compileString($this->getNode('plural'));
  40. $tokens = array_merge($tokens, $pluralTokens);
  41. }
  42. // Start writing with the function to be called.
  43. $compiler->write('echo ' . (empty($plural) ? 't' : '\Drupal::translation()->formatPlural') . '(');
  44. // Move the count to the beginning of the parameters list.
  45. if (!empty($plural)) {
  46. $compiler->raw('abs(')->subcompile($this->getNode('count'))->raw('), ');
  47. }
  48. // Write the singular text parameter.
  49. $compiler->subcompile($singular);
  50. // Write the plural text parameter, if necessary.
  51. if (!empty($plural)) {
  52. $compiler->raw(', ')->subcompile($plural);
  53. }
  54. // Write any tokens found as an associative array parameter, otherwise just
  55. // leave as an empty array.
  56. $compiler->raw(', array(');
  57. foreach ($tokens as $token) {
  58. $compiler->string($token->getAttribute('placeholder'))->raw(' => ')->subcompile($token)->raw(', ');
  59. }
  60. $compiler->raw(')');
  61. // Write any options passed.
  62. if ($this->hasNode('options')) {
  63. $compiler->raw(', ')->subcompile($this->getNode('options'));
  64. }
  65. // Write function closure.
  66. $compiler->raw(')');
  67. // @todo Add debug output, see https://www.drupal.org/node/2512672
  68. // End writing.
  69. $compiler->raw(";\n");
  70. }
  71. /**
  72. * Extracts the text and tokens for the "trans" tag.
  73. *
  74. * @param \Twig_Node $body
  75. * The node to compile.
  76. *
  77. * @return array
  78. * Returns an array containing the two following parameters:
  79. * - string $text
  80. * The extracted text.
  81. * - array $tokens
  82. * The extracted tokens as new \Twig_Node_Expression_Name instances.
  83. */
  84. protected function compileString(\Twig_Node $body) {
  85. if ($body instanceof \Twig_Node_Expression_Name || $body instanceof \Twig_Node_Expression_Constant || $body instanceof \Twig_Node_Expression_TempName) {
  86. return [$body, []];
  87. }
  88. $tokens = [];
  89. if (count($body)) {
  90. $text = '';
  91. foreach ($body as $node) {
  92. if (get_class($node) === 'Twig_Node' && $node->getNode(0) instanceof \Twig_Node_SetTemp) {
  93. $node = $node->getNode(1);
  94. }
  95. if ($node instanceof \Twig_Node_Print) {
  96. $n = $node->getNode('expr');
  97. while ($n instanceof \Twig_Node_Expression_Filter) {
  98. $n = $n->getNode('node');
  99. }
  100. if ($n instanceof CheckToStringNode) {
  101. $n = $n->getNode('expr');
  102. }
  103. $args = $n;
  104. // Support TwigExtension->renderVar() function in chain.
  105. if ($args instanceof \Twig_Node_Expression_Function) {
  106. $args = $n->getNode('arguments')->getNode(0);
  107. }
  108. // Detect if a token implements one of the filters reserved for
  109. // modifying the prefix of a token. The default prefix used for
  110. // translations is "@". This escapes the printed token and makes them
  111. // safe for templates.
  112. // @see TwigExtension::getFilters()
  113. $argPrefix = '@';
  114. while ($args instanceof \Twig_Node_Expression_Filter) {
  115. switch ($args->getNode('filter')->getAttribute('value')) {
  116. case 'placeholder':
  117. $argPrefix = '%';
  118. break;
  119. }
  120. $args = $args->getNode('node');
  121. }
  122. if ($args instanceof CheckToStringNode) {
  123. $args = $args->getNode('expr');
  124. }
  125. if ($args instanceof \Twig_Node_Expression_GetAttr) {
  126. $argName = [];
  127. // Reuse the incoming expression.
  128. $expr = $args;
  129. // Assemble a valid argument name by walking through the expression.
  130. $argName[] = $args->getNode('attribute')->getAttribute('value');
  131. while ($args->hasNode('node')) {
  132. $args = $args->getNode('node');
  133. if ($args instanceof \Twig_Node_Expression_Name) {
  134. $argName[] = $args->getAttribute('name');
  135. }
  136. else {
  137. $argName[] = $args->getNode('attribute')->getAttribute('value');
  138. }
  139. }
  140. $argName = array_reverse($argName);
  141. $argName = implode('.', $argName);
  142. }
  143. else {
  144. $argName = $n->getAttribute('name');
  145. if (!is_null($args)) {
  146. $argName = $args->getAttribute('name');
  147. }
  148. $expr = new \Twig_Node_Expression_Name($argName, $n->getTemplateLine());
  149. }
  150. $placeholder = sprintf('%s%s', $argPrefix, $argName);
  151. $text .= $placeholder;
  152. $expr->setAttribute('placeholder', $placeholder);
  153. $tokens[] = $expr;
  154. }
  155. else {
  156. $text .= $node->getAttribute('data');
  157. }
  158. }
  159. }
  160. elseif (!$body->hasAttribute('data')) {
  161. throw new \Twig_Error_Syntax('{% trans %} tag cannot be empty');
  162. }
  163. else {
  164. $text = $body->getAttribute('data');
  165. }
  166. return [
  167. new \Twig_Node([new \Twig_Node_Expression_Constant(trim($text), $body->getTemplateLine())]),
  168. $tokens,
  169. ];
  170. }
  171. }