How can one get a twig global variable to maintain modification after changing it with includes? My desired output is "set # deeper" though I get "original setting".
app/config/config.yml
twig:
globals:
testvar: "original setting"
root.html.twig
{% include "MyBundle::levelone.html.twig" %}
{{ testvar }}
levelone.html.twig
{% set testvar = "set # levelone" %}
{% include "MyBundle::deeper.html.twig" %}
deeper.html.twig
{% set testvar = "set # deeper" %}
From a container aware object :
$this->get('twig')->addGlobal('ThomasDecauxIsAwsome', 4);
Interesting question and I don't know an answer but no amount fooling around with blocks and stuff will work. I looked in the generated php cache template files. When you echo a variable it looks like:
// {{ testvar }}
echo twig_escape_filter($this->env, $this->getContext($context, "testvar"), "html", null, true);
So basically it first looks for testvar in your local context. If not found then it looks in the global context.
When you set the value of test var you get:
// {% set testvar = 'level one' }}
$context["testvar"] = "level one";
So only the local context gets updated. The changed value goes away when the included template returns.
So by default at least it looks like global variables are really read only.
It might be possible to do this through an extension. I don't know enough about the internals.
Have you tried to define a global variable through Twig? You can do it in your config.yml file as follows:
twig:
globals:
my_global_var : myvalue
So {{my_global_var}} in your template will print myvalue
For more info read the official docs.
It's possible by defining a Twig Extension via Services, check here
Related
I am a little bit confused. First of all, look at my code, I guess.
public function renderTemplate($templateType, $data)
{
$layoutName = "$templateType.layout.html.twig";
$policy = new \Twig_Sandbox_SecurityPolicy(
['if', 'for', 'block', 'set', 'extends'],
['escape', 'format', 'dateformat', 'trans', 'raw', 'striptags'],
self::$allowedMethods,
self::$allowedProperties,
['gettext']
);
$sandboxExt = new \Twig_Extension_Sandbox($policy);
$intlExt = new \Twig_Extensions_Extension_Intl();
$i18nExt = new \Twig_Extensions_Extension_I18n();
$twig = new \Twig_Environment(new \Twig_Loader_Filesystem(__DIR__ . "/../Resources/views/Something/", "__main__"));
$sandboxExt->enableSandbox();
$twig->addExtension($sandboxExt);
$twig->addExtension($intlExt);
$twig->addExtension($i18nExt);
try {
$result = $twig->render($layoutName, $data);
} catch (\Exception $e) {
\Doctrine\Common\Util\Debug::dump($e);die();
}
return $result;
}
Here it is the template I want to render
{% extends 'layout.html.twig' %}
{% block title %}{{ entity.id }}{% endblock %}
{% block bodyTitle %}
{{ entity.id }} {{ 'translation_key.created_at'|trans({}, 'entities', locale) }} {{ entity.createdAt|dateformat(null, locale) }}
{% endblock %}
Here, as you can see, I want to render a template according to its type. The problem is: half of the template renders just fine, and then, when it tries to render the translated string, it throws an error.
Fatal error: Call to undefined function gettext() in /home/dev/vhosts/my-project/vendor/twig/twig/lib/Twig/Environment.php(403) : eval()'d code on line 69
I checked if this function existed right before trying to call render method, and it was really undefined. Basically, I have 2 questions here:
Question 1
How does it work in other parts of my project, but not in this specific handler? See "Important update" below.
Question 2
Can I solve my problem in another way? For example, without using Sandbox or using Sandbox with some kind of a flag "everythingAllowed=true"?
ATTENTION! IMPORTANT UPDATE
Previously, I misunderstood my own question. I thought the error was thrown while rendering the variable, but I re-checked the situation (When Alain Tiemblo asked me for twig template code here in comments) and now 100% sure it's thrown while trying to translate smth. Also, I have translations all over my project, and it works just fine, but in this specific situation it's not. I think it worth to mention, that I also tried to render the template without using Sandbox. I tried to render it directly from Twig Engine like this
return $this->templating->render($layoutName, $data);
//$this->templating is injected in the constructor via services.yml like this
//arguments:
// - "#templating"
The result - not properly translated text. When I dumped "locale" - I got one specific language, but the text was translated in another. But at least using this method - I didn't get any errors.. Can anybody clarify this for me? Because I really don't understand how the Intl/i18n extension works and why it doesn't want to work in Sandbox or not in Sanbox?
P.S. My guess, why it doesn't work directly from Twig Engine - probably I should inject not like "#templating" or it injects just right, but the Intl or I18n extensions are not enabled? How to enable. And I have no clues why it doesn't work with Sandbox
Is it possible to translate the value of twig variables in a template with the 'trans' tag?
Say for instance I am passing a product to my template. This product has a definition with a trans tag e.g {{ product.definition|trans }}. This definition could either be in EN or DE or some other language. How could I translate the definition.
What are you trying to do is not a good way, It would look like this:
messages.en.yml
product:
definition:
some_value1: Some value 1
some_value2: Some value 2
and in template, you would do something like this:
{% set definition_value = product.definition %}
{% set trans_definition = 'product.definition.' ~ definition_value %}
{{ trans_definition|trans }}
it'll work, if it finds the key. What if it cant find it?
That's why you should use DoctrineBehaviors from KnpLabs, which handles all the dynamic translations for you..
If {{ product.definition }} equals 'cellphone' the following should work.
message.language.yml:
'cellphone': This will work!
However if you want to map it with the 'product' key in your message file like this:
product:
'cellphone': This also works
add the key to the twig template like so:
{{('product.'~product.definition)|trans }}
I'm currently developing an applications that will need to use translations from my JavaScripts.
The bundle BazingaJsTranslationBundle seems to be good, but after I tried it I don't think it fits my needs. It generates all the translations for all my application's bundles. It can be heavy to load.
Do you know other bundles or tricks for that ?
Thank you for your help.
I had similar problem (large translation files) with BazingaJsTranslationBundle and I simplify this by:
#config.yml
bazinga_expose_translation:
default_domains: [ jsonly ]
locale_fallback: "%locale%"
create simple html twig to store your js variables and bazing expose them from this files
{# jsOnleVariables.html.twig #}
{% set var1 = 'Welcome'|trans({},'jsonly') %}
{% set var2 = 'Bye'|trans({},'jsonly') %}
dump variables
php app/console bazinga:expose-translation:dump web/js
and in your layout include only wanted variables
{# layout.html.twig #}
<script src="{{ asset('bundles/bazingaexposetranslation/js/translator.js') }}" type="text/javascript"></script>
{% if app.request.locale == 'pl' %}
<script src="{{ asset('js/i18n/jsonly/pl.js') }}" type="text/javascript"></script>
{% else %}
<script src="{{ asset('js/i18n/jsonly/en.js') }}" type="text/javascript"></script>
{% endif %}
Depending on your exact needs and circumstances, a simple object in json format can be a good enough dictionary. For example like this:
var dict = {
TextIdWelcome : "Welcome",
TextIdGoodBy : "Good by"
}
A usage example:
var elem = document.getElementById("WelcomeTag");
elem.innerHtml = dict["TextIdWelcome"];
You can generate such json object on your server side for the actual language selected on client side and you can retrieve it by your favourite method (jquery, XMLHttpRequest, etc), and just assign it to this dict variable.
If you need sentences with runtime dependent values in it, a simple basic trick might fit your needs. Let use some markup like %0, %1, etc in the translated text.
var dict = {
TextIdWelcome : "Welcome %0",
TextIdGoodBy : "Good by %0"
}
function tr(textId, runTimeValue1, runTimeValue2){
var text = dict[textId];
if(runTimeValue1 !== undefined)
text = text.replace("%0", runTimeValue1);
if(runTimeValue2 !== undefined)
text = text.replace("%1", runTimeValue2);
return text;
}
So a usage example:
var userName = "Jhon";
var elem = document.getElementById("WelcomeTag");
elem.innerHtml = tr("TextIdWelcome", userName);
Please note that this solution lacks several refined tricks (escaping markups, variable number of runtime values, efficient replace algorithm, etc), but in simple everyday cases this could be good enough. This trick is also very simple (so you can easily enhance it to your needs) and you can control 100% how exactly it should handle the dictionary. You can of course control when and what dictionary to load into the dict variable.
I tried dump variables in block template (block_core_children_pages.html.twig) by {{ dump() }} but it turn into blank page. Anybody have same problem? and I have some questions also:
How to pass a variable from function: {{ sonata_page_render_container('footer', 'global' ) }} to template ?
What are variables is passed by default to block template?
Thanksss alot.
The sonata_page_render_container function takes the following arguments:
public function renderContainer($name, $page = null, array $options = array())
So you have the third argument to specify some options/settings to add to your block, like this:
{{ sonata_page_render_container('footer', 'global', {mysetting: myvalue}) }}
After that, you can modify your execute() BlockService's method to use the settings you passed.
I'm wondering how to get the current page name, basically 'just' the last parameter in the route (i.e. /news or /about). I'm doing this because I want to be able to have the current page in the navigation highlighted.
Ideally, I'd like to store the current page name in a global variable so that in Twig I can just compare the current page name against the link and add a class accordingly.
I can't figure out how to add the current page name to a global variable though. I've tried using something like this:
$app['twig']->addGlobal('current_page_name', $app['request']->getRequestUri());
at the top of my app.php file, but an 'outside of request scope' error. But I wouldn't like to have to include this in every route.
What's the best way to do this?
If you put it into an app-level before middleware like this, that'll work:
$app->before(function (Request $request) use ($app) {
$app['twig']->addGlobal('current_page_name', $request->getRequestUri());
});
The "page name" part of your question is unclear, are you looking for the current route's name? You can access that via $request->get("_route") even in the before middleware, as it gets called when routing is already done.
You could also generate navigation list directly in stand alone nav twig template. And then import it in to the main template. Then you would only have to get silex to pass to the view the current page identifier. Simplest way... for example from Silex you would have to pass in the "path" variable to your view. Probably it would more convenient to to fetch nav_list from database and pass it in to twig template as global array variable instead. However this example is the simplest you could get to do what you intend.
nav.twig
{% set nav_list = [
["./", "home"],
["./contact", "contact"],
["./about", "about us"]
{# ... #}
] %}
{% set link_active = path|default("") %}
{% for link in nav_list %}
<li><a href="{{ link[0] }}" class="{% if link[0] == link_active %} activeClass {% endif %}" >{{ link[1] }}</a></li>
{% endfor %}
app.php
//...
$app->match('/about', function (Request $request) use ($app) {
return $app['twig']->render('about.twig', array(
'path' => './'.end(explode('/', $request->getRequestUri()))
));
});
//...