lliure / twig-translate
Twig extension for translation using 'yaml'
Requires
- php: >= 8.0
- ext-yaml: *
- twig/twig: ^3.0
README
This extension allows translation of messages in Twig templates using yaml files. It's useful for projects that require multi-language support.
⚠️ Security Notice: This library processes translations as Twig templates. Never use
|translatewith user-provided data (form inputs, URL parameters, database content from users) as this can lead to code injection. See Security Warnings for details.
Example Usage
Installation:
Before you begin, make sure you have installed the package in your project using Composer:
composer require lliure/twig-translateConfiguration:
require_once 'vendor/autoload.php'; use Twig\TwigFilter; $loader = new \Twig\Loader\FilesystemLoader('views'); $twig = new \Twig\Environment($loader); // Create an instance of the translation class, passing the desired language $myTwigFilters = new \TwigTranslate\Translate('pt_BR'); // Add the extension to the Twig environment $twig->addExtension($myTwigFilters); echo $twig->render('home.html', ['name' => 'Jeison']);You can also specify the domain you want to use.
$myTwigFilters = new \TwigTranslate\Translate('pt_BR', 'location/of/translate/files', ['home']);By default, a domain with the same name as the specified language is always loaded unless you specify a different one.
For example, the default translation file path for 'pt_BR' would be:
location/of/translate/files/pt_BR/pt_BR.ymlYou can specify the 'home' domain as shown in the code to load a different translation domain.
Usage in Twig Template:
In your Twig templates, you can use the
translatefilter or_to translate messages:{{ 'Hello'|translate }} {{ name }}, {{ 'Goodbye'|_ }}Using Variables
In YAML, you can define variables within "%" to make your text dynamic:
"Displaying page %currentPage%": "Displaying page %currentPage%"In Twig, you can incorporate these variables into the translated text as follows:
{{ 'Displaying page %currentPage%'|translate({'currentPage': data.pagination.currentPage}) }}This allows you to insert the value of the
currentPagevariable into the translated text, making it personalized and dynamic.
⚠️ Security Warnings
This library processes translated text as Twig templates to enable variable substitution. This is powerful but comes with important security considerations:
1. Infinite Loop Risk
Do not use |translate inside translated text. Since translations are processed as Twig templates, using the translate filter within a translation can cause an infinite loop.
# ❌ DANGER - Can cause infinite loop
"Welcome": "{{ 'Hello'|translate }} World"
# ✅ SAFE - Plain text or simple variables only
"Welcome": "Hello World"
"Welcome %name%": "Hello %name%"
2. Text Injection / XSS Risk
Never use |translate with untrusted data. Since translations are rendered as Twig templates, malicious input can execute arbitrary Twig code.
{# ❌ DANGER - User input processed as Twig template #}
{{ userProvidedText|translate }}
{{ request.query.get('msg')|translate }}
{{ post.title|translate }}
{# ✅ SAFE - Only static strings from your codebase #}
{{ "Welcome to our site"|translate }}
{{ "Hello %name%"|translate({'name': userName}) }}
Untrusted sources include:
- User form submissions (POST data)
- URL query parameters
- Database content created by users
- External API responses
- Any data not hardcoded in your templates
Safe usage: Only use |translate with string literals defined in your templates. Pass dynamic data through the variables parameter, not as the text to translate.
Disclaimer
This library is provided "as is", without warranty of any kind. The authors and contributors are not responsible for any damages, security breaches, or issues arising from improper implementation or misuse of this library. It is the implementer's responsibility to ensure secure usage according to the guidelines above.
Namespace Support
You can specify which translation domain (namespace) to use directly in the template.
Filter with Namespace
Pass the namespace as the second argument to the filter:
{# Single namespace #}
{{ "Save"|translate({}, 'module1') }}
{# Multiple namespaces (searches in order) #}
{{ "Save"|translate({}, ['module1', 'common']) }}
Block Syntax
Use the {% translate %} block to set a namespace context for all translations inside:
{% translate 'module1' %}
{{ "Save"|translate }}
{{ "Cancel"|_ }}
{% endtranslate %}
With multiple namespaces:
{% translate ['module1', 'common'] %}
{{ "Save"|translate }}
{{ "Yes"|translate }}
{% endtranslate %}
Nested Blocks
Blocks can be nested. Inner blocks have priority, with fallback to outer blocks:
{% translate 'module1' %}
{{ "Title"|translate }} {# searches in: module1 -> route domains #}
{% translate 'module2' %}
{{ "Title"|translate }} {# searches in: module2 -> module1 -> route domains #}
{% endtranslate %}
{{ "Title"|translate }} {# back to: module1 -> route domains #}
{% endtranslate %}
Priority Order
Translation lookup follows this priority:
- Namespace passed directly in the filter
- Namespace from the current
{% translate %}block - Namespaces from the dynamic buffer (persisted from previous blocks)
- Domains configured in the constructor (route domains)
Persistent Buffer
Domains loaded via {% translate %} blocks are added to a persistent buffer. They remain available for the rest of the request, even after the block closes:
{{ "Yes"|translate }} {# not translated if 'common' not in route #}
{% translate 'common' %}
{{ "Yes"|translate }} {# translated: "Sim" #}
{% endtranslate %}
{{ "Yes"|translate }} {# still translated: "Sim" (buffer persists) #}
Translation Files
Translations are saved in YAML files.
Example .yaml File
hello: Olá
goodbye: Até mais
Location of translate Files
.yaml files should be stored in a specific directory structure:
pt_BR/home.yaml
By default, the configured folder is:
etc/locale
However, you can change the folder path in the extension call. The final path will be a combination of the configured path and the language:
etc/locale/pt_BR/home.yaml
Make sure to adjust the path as needed to match your project's directory structure.
Examples
The example/ folder contains usage examples for each feature:
cd /path/to/lliure-twigtranslate
php example/basic.php pt_BR # Basic usage
php example/variables.php pt_BR # Variables with %var%
php example/namespace.php pt_BR # Single namespace
php example/multi-namespace.php pt_BR # Multiple namespaces (array)
php example/block.php pt_BR # Block syntax
php example/nested-blocks.php pt_BR # Nested blocks
php example/buffer.php pt_BR # Persistent buffer
Running Tests
The tests/ folder contains the test suite:
cd /path/to/lliure-twigtranslate
# Visual output
php tests/run.php pt_BR
# Automated validation (returns exit code 0 on success, 1 on failure)
php tests/run.php pt_BR --validate
Contribution
If you'd like to contribute to this package, please feel free to open an issue or submit a pull request. We appreciate your collaboration!