Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions src/Extension/DateExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

namespace Dot\Twig\Extension;

use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;

/**
* Class DateExtension
* @package Dot\Twig\Extension
*/
class DateExtension extends AbstractExtension
{
/**
* @var array|string[]
*/
public static array $units = [
'y' => 'year',
'm' => 'month',
'd' => 'day',
'h' => 'hour',
'i' => 'minute',
's' => 'second',
];

/**
* @return array|TwigFilter[]
*/
public function getFilters()
{
return [
new TwigFilter('time_diff', [$this, 'diff'], ['needs_environment' => true]),
];
}

/**
* Filters for converting dates to a time ago string like Facebook and Twitter has.
*
* @param Environment $env
* @param string|\DateTime $date a string or DateTime object to convert
* @param string|\DateTime $now A string or DateTime object to compare with.
* If none given, the current time will be used.
*
* @return string the converted time
*/
public function diff(Environment $env, $date, $now = null)
{
// Convert both dates to DateTime instances.
$date = twig_date_converter($env, $date);
$now = twig_date_converter($env, $now);

// Get the difference between the two DateTime objects.
$diff = $date->diff($now);

// Check for each interval if it appears in the $diff object.
foreach (self::$units as $attribute => $unit) {
$count = $diff->$attribute;

if (0 !== $count) {
return $this->getPluralizedInterval($count, $diff->invert, $unit);
}
}

return '';
}

/**
* @param $count
* @param $invert
* @param $unit
* @return string
*/
private function getPluralizedInterval($count, $invert, $unit)
{

if (1 !== $count) {
$unit .= 's';
}

return $invert ? "in $count $unit" : "$count $unit ago";
}
}
186 changes: 186 additions & 0 deletions src/Extension/Translation/TransNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php

namespace Dot\Twig\Extension\Translation;

use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\TempNameExpression;
use Twig\Node\Node;
use Twig\Node\PrintNode;

/**
* Class TransNode
* @package Dot\Twig\Extension\Translation
*/
class TransNode extends Node
{
/**
* TransNode constructor.
* @param Node $body
* @param Node|null $plural
* @param AbstractExpression|null $count
* @param Node|null $notes
* @param int $lineno
* @param null $tag
*/
public function __construct(
Node $body,
Node $plural = null,
AbstractExpression $count = null,
Node $notes = null,
$lineno = 0,
$tag = null
) {
$nodes = ['body' => $body];
if (null !== $count) {
$nodes['count'] = $count;
}
if (null !== $plural) {
$nodes['plural'] = $plural;
}
if (null !== $notes) {
$nodes['notes'] = $notes;
}

parent::__construct($nodes, [], $lineno, $tag);
}

/**
* {@inheritdoc}
*/
public function compile(Compiler $compiler)
{
$compiler->addDebugInfo($this);
$msg1 = '';

[$msg, $vars] = $this->compileString($this->getNode('body'));

if ($this->hasNode('plural')) {
[$msg1, $vars1] = $this->compileString($this->getNode('plural'));

$vars = array_merge($vars, $vars1);
}

$function = $this->getTransFunction($this->hasNode('plural'));

if ($this->hasNode('notes')) {
$message = trim($this->getNode('notes')->getAttribute('data'));

// line breaks are not allowed cause we want a single line comment
$message = str_replace(["\n", "\r"], ' ', $message);
$compiler->write("// notes: {$message}\n");
}

if ($vars) {
$compiler
->write('echo strtr(' . $function . '(')
->subcompile($msg)
;

if ($this->hasNode('plural')) {
$compiler
->raw(', ')
->subcompile($msg1)
->raw(', abs(')
->subcompile($this->hasNode('count') ? $this->getNode('count') : null)
->raw(')')
;
}

$compiler->raw('), array(');

foreach ($vars as $var) {
if ('count' === $var->getAttribute('name')) {
$compiler
->string('%count%')
->raw(' => abs(')
->subcompile($this->hasNode('count') ? $this->getNode('count') : null)
->raw('), ')
;
} else {
$compiler
->string('%' . $var->getAttribute('name') . '%')
->raw(' => ')
->subcompile($var)
->raw(', ')
;
}
}

$compiler->raw("));\n");
} else {
$compiler
->write('echo ' . $function . '(')
->subcompile($msg)
;

if ($this->hasNode('plural')) {
$compiler
->raw(', ')
->subcompile($msg1)
->raw(', abs(')
->subcompile($this->hasNode('count') ? $this->getNode('count') : null)
->raw(')')
;
}

$compiler->raw(");\n");
}
}

/**
* @param Node $body A Twig_Node instance
*
* @return array
*/
protected function compileString(Node $body)
{
if (
$body instanceof NameExpression ||
$body instanceof ConstantExpression ||
$body instanceof TempNameExpression
) {
return [$body, []];
}

$vars = [];
if (count($body)) {
$msg = '';

/** @var Node $node */
foreach ($body as $node) {
if (get_class($node) === 'Node' && $node->getNode(0) instanceof TempNameExpression) {
$node = $node->getNode(1);
}

if ($node instanceof PrintNode) {
$n = $node->getNode('expr');
while ($n instanceof FilterExpression) {
$n = $n->getNode('node');
}
$msg .= sprintf('%%%s%%', $n->getAttribute('name'));
$vars[] = new NameExpression($n->getAttribute('name'), $n->getTemplateLine());
} else {
$msg .= $node->getAttribute('data');
}
}
} else {
$msg = $body->getAttribute('data');
}

return [new Node([new ConstantExpression(trim($msg), $body->getTemplateLine())]), $vars];
}

/**
* @param bool $plural Return plural or singular function to use
*
* @return string
*/
protected function getTransFunction($plural)
{
return $plural ? 'ngettext' : 'gettext';
}
}
109 changes: 109 additions & 0 deletions src/Extension/Translation/TransTokenParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Dot\Twig\Extension\Translation;

use Twig\Error\SyntaxError;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\Node\TextNode;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;

/**
* Class TransTokenParser
* @package Dot\Twig\Extension\Translation
*/
class TransTokenParser extends AbstractTokenParser
{
/**
* @param Token $token
* @return TransNode|Node
* @throws SyntaxError
*/
public function parse(Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$count = null;
$plural = null;
$notes = null;

if (!$stream->test(Token::BLOCK_END_TYPE)) {
$body = $this->parser->getExpressionParser()->parseExpression();
} else {
$stream->expect(Token::BLOCK_END_TYPE);
$body = $this->parser->subparse([$this, 'decideForFork']);
$next = $stream->next()->getValue();

if ('plural' === $next) {
$count = $this->parser->getExpressionParser()->parseExpression();
$stream->expect(Token::BLOCK_END_TYPE);
$plural = $this->parser->subparse([$this, 'decideForFork']);

if ('notes' === $stream->next()->getValue()) {
$stream->expect(Token::BLOCK_END_TYPE);
$notes = $this->parser->subparse([$this, 'decideForEnd'], true);
}
} elseif ('notes' === $next) {
$stream->expect(Token::BLOCK_END_TYPE);
$notes = $this->parser->subparse([$this, 'decideForEnd'], true);
}
}

$stream->expect(Token::BLOCK_END_TYPE);

$this->checkTransString($body, $lineno);

return new TransNode($body, $plural, $count, $notes, $lineno, $this->getTag());
}

/**
* @param Token $token
* @return bool
*/
public function decideForFork(Token $token)
{
return $token->test(['plural', 'notes', 'endtrans']);
}

/**
* @param Token $token
* @return bool
*/
public function decideForEnd(Token $token)
{
return $token->test('endtrans');
}

/**
* @return string
*/
public function getTag()
{
return 'trans';
}

/**
* @param Node $body
* @param $lineno
* @throws SyntaxError
*/
private function checkTransString(Node $body, $lineno)
{
foreach ($body as $i => $node) {
if (
$node instanceof TextNode
||
($node instanceof PrintNode && $node->getNode('expr') instanceof NameExpression)
) {
continue;
}

throw new SyntaxError(
sprintf('The text to be translated with "trans" can only contain references to simple variables'),
$lineno
);
}
}
}
Loading