diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..a30621a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_size = 4
+indent_style = space
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..77aae3d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/.idea
+/composer.lock
+/vendor
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..75778d5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) Mario
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bf32af8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,89 @@
+# Twig Trans
+
+[![Latest Version](https://img.shields.io/github/release/JBlond/twig-trans.svg?style=flat-square&label=Release)](https://github.com/JBlond/twig-trans/releases)
+
+## Introduction
+
+This is the replacement for the old **Twig** Extensions **I18n** / **Intl** for the translation with po / mo
+**gettext** files.
+
+I didn't wanted to Symfony, but Twig only. Symfony seemed to be too much overhead.
+
+This extension enable Twig templates to use `|trans` and `{% trans %}` + `{% endtrans %}` again
+
+## Install
+
+```shell
+composer require jblond/twig-trans
+```
+
+## Example Use
+
+```PHP
+ false,
+ 'debug' => true,
+ 'auto_reload' => true
+);
+
+$twigLoader = new FilesystemLoader('./tpl/');
+$twig = new Environment($twigLoader, $twigConfig);
+
+// this is for the filter |trans
+$filter = new TwigFilter('trans', function (Environment $env, $context, $string) {
+ return Translation::TransGetText($string, $context);
+}, ['needs_context' => true, 'needs_environment' => true]);
+
+// load the i18n extension for using the translation tag for twig
+// {% trans %}my string{% endtrans %}
+$twig->addFilter($filter);
+$twig->addExtension(new Translation());
+
+try {
+ $tpl = $twig->load('default.twig');
+} catch (Exception $exception) {
+ echo $exception->getMessage();
+ return;
+}
+
+$tpl->render();
+```
+
+
+## Requirements
+
+* PHP 7.2 or greater
+* PHP Multibyte String
+' gettext'
+
+
+### License (MIT License)
+
+see [License](LICENSE)
+
+## Tests
+
+```bash
+composer run-script php_src
+```
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..f417757
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,39 @@
+{
+ "name" : "jblond/twig-trans",
+ "type" : "library",
+ "description" : "",
+ "license" : "MIT",
+ "keywords" : [
+ "php",
+ "twig",
+ "trans",
+ "translation",
+ "endtrans",
+ "po file"
+ ],
+ "authors" : [{
+ "name" : "Mario",
+ "email" : "leet31337@web.de"
+ }
+ ],
+ "require" : {
+ "php" : ">=7.2",
+ "ext-gettext": "*",
+ "ext-mbstring": "*",
+ "twig/twig": ">=3.0.0"
+ },
+ "require-dev" : {
+ "squizlabs/php_codesniffer" : "*"
+ },
+ "autoload" : {
+ "psr-4" : {
+ "jblond\\" : "src/jblond"
+ }
+ },
+ "config" : {
+ "classmap-authoritative" : true
+ },
+ "scripts" : {
+ "php_src" : "phpcs --standard=phpcs.xml -s -p --colors ./src/"
+ }
+}
diff --git a/example/example.php b/example/example.php
new file mode 100644
index 0000000..4235e40
--- /dev/null
+++ b/example/example.php
@@ -0,0 +1,51 @@
+ false,
+ 'debug' => true,
+ 'auto_reload' => true
+);
+
+$twigLoader = new FilesystemLoader('./tpl/');
+$twig = new Environment($twigLoader, $twigConfig);
+
+// this is for the filter |trans
+$filter = new TwigFilter('trans', function (Environment $env, $context, $string) {
+ return Translation::transGetText($string, $context);
+}, ['needs_context' => true, 'needs_environment' => true]);
+
+// load the i18n extension for using the translation tag for twig
+// {% trans %}my string{% endtrans %}
+$twig->addFilter($filter);
+$twig->addExtension(new Translation());
+
+try {
+ $tpl = $twig->load('default.twig');
+} catch (Exception $exception) {
+ echo $exception->getMessage();
+ die();
+}
+
+echo $tpl->render();
diff --git a/example/locale/de_DE/Web_Content.mo b/example/locale/de_DE/Web_Content.mo
new file mode 100644
index 0000000..3ff2ad8
Binary files /dev/null and b/example/locale/de_DE/Web_Content.mo differ
diff --git a/example/locale/de_DE/Web_Content.po b/example/locale/de_DE/Web_Content.po
new file mode 100644
index 0000000..b98ca89
--- /dev/null
+++ b/example/locale/de_DE/Web_Content.po
@@ -0,0 +1,14 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: product-information-rekovelle\n"
+"Language-Team: German\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Language: de_DE\n"
+
+#. Test
+#: test id
+msgid "my string"
+msgstr "Meine Zeichenkette"
diff --git a/example/locale/en_EN/Web_Content.mo b/example/locale/en_EN/Web_Content.mo
new file mode 100644
index 0000000..3a0deaa
Binary files /dev/null and b/example/locale/en_EN/Web_Content.mo differ
diff --git a/example/locale/en_EN/Web_Content.po b/example/locale/en_EN/Web_Content.po
new file mode 100644
index 0000000..2978421
--- /dev/null
+++ b/example/locale/en_EN/Web_Content.po
@@ -0,0 +1,14 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: product-information-rekovelle\n"
+"Language-Team: German\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Language: en_EN\n"
+
+#. Test
+#: test id
+msgid "my string"
+msgstr "my string"
diff --git a/example/tpl/default.twig b/example/tpl/default.twig
new file mode 100644
index 0000000..09d5011
--- /dev/null
+++ b/example/tpl/default.twig
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+ PHP JBlond Twig Trans
+
+
+
+
+{% trans %}my string{% endtrans %}
+
+{{ 'my string'|trans }}
+THE END
+
+
+
+
diff --git a/phpcs.xml b/phpcs.xml
new file mode 100644
index 0000000..17a3f42
--- /dev/null
+++ b/phpcs.xml
@@ -0,0 +1,6 @@
+
+
+ twig-trans
+
+ src
+
diff --git a/src/jblond/TwigTrans/TransNode.php b/src/jblond/TwigTrans/TransNode.php
new file mode 100644
index 0000000..5b4b927
--- /dev/null
+++ b/src/jblond/TwigTrans/TransNode.php
@@ -0,0 +1,186 @@
+ $body);
+ if (null !== $count) {
+ $nodes['count'] = $count;
+ }
+ if (null !== $plural) {
+ $nodes['plural'] = $plural;
+ }
+ if (null !== $notes) {
+ $nodes['notes'] = $notes;
+ }
+
+ parent::__construct($nodes, array(), $lineno, $tag);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function compile(Compiler $compiler)
+ {
+ $compiler->addDebugInfo($this);
+ $msg1 = '';
+
+ list($msg, $vars) = $this->compileString($this->getNode('body'));
+
+ if ($this->hasNode('plural')) {
+ list($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(array("\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 array($body, array());
+ }
+
+ $vars = array();
+ 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 array(new Node(array(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';
+ }
+}
diff --git a/src/jblond/TwigTrans/TransTag.php b/src/jblond/TwigTrans/TransTag.php
new file mode 100644
index 0000000..7b66580
--- /dev/null
+++ b/src/jblond/TwigTrans/TransTag.php
@@ -0,0 +1,100 @@
+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(array($this, 'decideForFork'));
+ $next = $stream->next()->getValue();
+
+ if ('plural' === $next) {
+ $count = $this->parser->getExpressionParser()->parseExpression();
+ $stream->expect(Token::BLOCK_END_TYPE);
+ $plural = $this->parser->subparse(array($this, 'decideForFork'));
+
+ if ('notes' === $stream->next()->getValue()) {
+ $stream->expect(Token::BLOCK_END_TYPE);
+ $notes = $this->parser->subparse(array($this, 'decideForEnd'), true);
+ }
+ } elseif ('notes' === $next) {
+ $stream->expect(Token::BLOCK_END_TYPE);
+ $notes = $this->parser->subparse(array($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(array('plural', 'notes', 'endtrans'));
+ }
+
+ /**
+ * @param Token $token
+ * @return bool
+ */
+ public function decideForEnd(Token $token)
+ {
+ return $token->test('endtrans');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTag()
+ {
+ return 'trans';
+ }
+
+ /**
+ * @param Node $body
+ * @param $lineno
+ * @throws SyntaxError
+ */
+ protected 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
+ );
+ }
+ }
+}
diff --git a/src/jblond/TwigTrans/Translation.php b/src/jblond/TwigTrans/Translation.php
new file mode 100644
index 0000000..1bcad14
--- /dev/null
+++ b/src/jblond/TwigTrans/Translation.php
@@ -0,0 +1,129 @@
+ $value) {
+ if (is_array($value)) {
+ return self::replaceContext($string, $value);
+ }
+ $string = str_replace('{{ ' . $key . ' }}', $value, $string);
+ }
+ return $string;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFilters()
+ {
+ return [
+ new TwigFilter('Translation', 'transGetText'),
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFunctions()
+ {
+ return [
+ new TwigFunction('transGetText', [$this, 'transGetText']),
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getTests()
+ {
+ return array(
+ new TwigTest('Translation', 'test')
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getTokenParsers()
+ {
+ return array(
+ new TransTag()
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getNodeVisitors()
+ {
+ return [new MacroAutoImportNodeVisitor()];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getOperators()
+ {
+ return [
+ [
+ '!' => ['precedence' => 50, 'class' => NotUnary::class],
+ ],
+ [
+ '||' => [
+ 'precedence' => 10,
+ 'class' => OrBinary::class,
+ 'associativity' => ExpressionParser::OPERATOR_LEFT
+ ],
+ '&&' => [
+ 'precedence' => 15,
+ 'class' => AndBinary::class,
+ 'associativity' => ExpressionParser::OPERATOR_LEFT
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @return bool
+ */
+ public function test()
+ {
+ return true;
+ }
+}