addDebugInfo($this); [$singular, $tokens] = $this->compileString($this->getNode('body')); $plural = NULL; if ($this->hasNode('plural')) { [$plural, $pluralTokens] = $this->compileString($this->getNode('plural')); $tokens = array_merge($tokens, $pluralTokens); } // Start writing with the function to be called. $compiler->write('yield ' . (empty($plural) ? 't' : '\Drupal::translation()->formatPlural') . '('); // Move the count to the beginning of the parameters list. if (!empty($plural)) { $compiler->raw('abs(')->subcompile($this->getNode('count'))->raw('), '); } // Write the singular text parameter. $compiler->subcompile($singular); // Write the plural text parameter, if necessary. if (!empty($plural)) { $compiler->raw(', ')->subcompile($plural); } // Write any tokens found as an associative array parameter, otherwise just // leave as an empty array. $compiler->raw(', array('); foreach ($tokens as $token) { $compiler->string($token->getAttribute('placeholder'))->raw(' => ')->subcompile($token)->raw(', '); } $compiler->raw(')'); // Write any options passed. if ($this->hasNode('options')) { $compiler->raw(', ')->subcompile($this->getNode('options')); } // Write function closure. $compiler->raw(')'); // @todo Add debug output, see https://www.drupal.org/node/2512672 // End writing. $compiler->raw(";\n"); } /** * Extracts the text and tokens for the "trans" tag. * * @param \Twig\Node\Node $body * The node to compile. * * @return array * Returns an array containing the two following parameters: * - string $text * The extracted text. * - array $tokens * The extracted tokens as new \Twig\Node\Expression\TempNameExpression * instances. */ protected function compileString(Node $body) { if ($body instanceof NameExpression || $body instanceof ConstantExpression || $body instanceof TempNameExpression) { return [$body, []]; } $tokens = []; if (count($body)) { $text = ''; foreach ($body as $node) { if ($node instanceof PrintNode) { $n = $node->getNode('expr'); while ($n instanceof FilterExpression) { $n = $n->getNode('node'); } if ($n instanceof CheckToStringNode) { $n = $n->getNode('expr'); } $args = $n; // Support TwigExtension->renderVar() function in chain. if ($args instanceof FunctionExpression) { $args = $n->getNode('arguments')->getNode(0); } // Detect if a token implements one of the filters reserved for // modifying the prefix of a token. The default prefix used for // translations is "@". This escapes the printed token and makes them // safe for templates. // @see TwigExtension::getFilters() $argPrefix = '@'; while ($args instanceof FilterExpression) { switch ($args->getNode('filter')->getAttribute('value')) { case 'placeholder': $argPrefix = '%'; break; } $args = $args->getNode('node'); } if ($args instanceof CheckToStringNode) { $args = $args->getNode('expr'); } if ($args instanceof GetAttrExpression) { $argName = []; // Reuse the incoming expression. $expr = $args; // Assemble a valid argument name by walking through the expression. $argName[] = $args->getNode('attribute')->getAttribute('value'); while ($args->hasNode('node')) { $args = $args->getNode('node'); if ($args instanceof NameExpression) { $argName[] = $args->getAttribute('name'); } else { $argName[] = $args->getNode('attribute')->getAttribute('value'); } } $argName = array_reverse($argName); $argName = implode('.', $argName); } else { $argName = $n->getAttribute('name'); if (!is_null($args)) { $argName = $args->getAttribute('name'); } $expr = new NameExpression($argName, $n->getTemplateLine()); } $placeholder = sprintf('%s%s', $argPrefix, $argName); $text .= $placeholder; $expr->setAttribute('placeholder', $placeholder); $tokens[] = $expr; } else { $text .= $node->getAttribute('data'); } } } elseif (!$body->hasAttribute('data')) { throw new SyntaxError('{% trans %} tag cannot be empty'); } else { $text = $body->getAttribute('data'); } return [ new Node([new ConstantExpression(trim($text), $body->getTemplateLine())]), $tokens, ]; } }