TwigNodeTrans.php 5.9 KB

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