lliure/twig-translate

Twig extension for translation using 'yaml'

Maintainers

Package info

bitbucket.org/vinteenove/twigtranslate

pkg:composer/lliure/twig-translate

Statistics

Installs: 135

Dependents: 0

Suggesters: 0

v0.7.0 2026-03-23 17:26 UTC

This package is auto-updated.

Last update: 2026-03-23 17:28:00 UTC


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 |translate with 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

  1. Installation:

    Before you begin, make sure you have installed the package in your project using Composer:

    composer require lliure/twig-translate
    
  2. Configuration:

    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.yml
    

    You can specify the 'home' domain as shown in the code to load a different translation domain.

  3. Usage in Twig Template:

    In your Twig templates, you can use the translate filter or _ to translate messages:

    {{ 'Hello'|translate }} {{ name }}, {{ 'Goodbye'|_ }}
    
  4. 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 currentPage variable 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:

  1. Namespace passed directly in the filter
  2. Namespace from the current {% translate %} block
  3. Namespaces from the dynamic buffer (persisted from previous blocks)
  4. 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!