Symfony -- Twig's trans filter not picking up locale - symfony

I have verified that if I add the following line to my twig template, it outputs the desired locale:
{{ app.request.locale }}
However, the following is outputting in English:
{{ 'String'|trans }}
If I force the locale of the trans filter:
{{ 'String'|trans({}, 'messages', 'ja') }}
It outputs in the proper translation. Note that I'm setting the locale using an eventListener:
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$locale = $request->cookies->get('_locale') ? $request->cookies->get('_locale') : $request->getPreferredLanguage($this->availableLanguages);
$request->setLocale($locale);
}

I figured out the answer through Symfony Documentation:
Setting the locale using $request->setLocale() in the controller is too late to affect the translator.
Either set the locale
Via a Listener (like above)
Via the URL (see next)
Or call setLocale() directly on the Translator Service.
I ended up fixing it by changing the priority of the service, like the accepted answer in this thread: Symfony 2.1 set locale

Mabey a late reply but I was facing the same problem, after a bit of reading i found a better solution. You can use the trans function instead of the trans filter which seems to be a cleaner solution.
{% trans from "your-trans-domain" into app.user.locale %} trans.key {% endtrans %}
See symfony docs:
Docs v2.7 for translations

Related

call twig function from custom twig function

I created a custom twig function in AppExtension Class. I need to call form_label() from this new function. ¿Is it posible? I tried but does not work:
from template I call:
{{ myFunc(form.someField) }}
public function myFunc( $field )
{
$html = form_label($field);
}
The idea is to render each form field in a different order/way than the form_widget(form) twig function. The "form_label()" function it's not reconized.
Thx for any suggestion.
I feel like this is the wrong approach to handle this. Extensions are for transforming data not really to manipulate the form definition itself.
First of all the order is defined as in the form type, so you can swap those around. To render the fields differently you can use form themes, or even rendering a custom form type.
Alternatively if its a one time thing (you could also create a macro for this) you can also instead of form_widget(form) order them in the way you like.
{{ form_start(form) }}
{{ form_row(form.field3) }}
{{ form_row(form.field1, { attr: { class: 'im-different' } }) }}
{{ form_row(form.field2) }}
{{ form_end(form) }}
Or even go deeper.
{{ form_start(form) }}
{{ form_row(form.field3) }}
<div>
{{ form_label(form.field1) }}
{{ form_widget(form.field1) }}
{{ form_errors(form.field1) }}
</div>
{{ form_row(form.field2) }}
{{ form_end(form) }}
To see these functions and how they all rendered by default you can look at form_div_layout.html.twig.
I agree with Jenne van der Meer and Nico Haase that your approach isn't particularly optimal. If I had the choice, I would go a different route: Instead of rendering in your function, render in twig, then pass the result to the function (like {{ myFunc(form_label(form), form) }}). Since you omit what your function actually needs and/or does, it's hard to provide further advice. However, I'm absolutely sure, that rendering can be done in twig before or after entering your function, via a macro/block, maybe even a form theme).
However, if you really really require your function to render the form field ... the following will possibly help you. I strongly advise against doing this, there's probably a better suited solution.
The form_label function is slightly more complex than a simple function. Instead, it uses twig's compile mechanisms to generate specific php code. It will eventually call:
FormRenderer::searchAndRenderBlock(FormView $view, string $blockNameSuffix, array $variables = [])
Diving deep into the compiler, the template call form_label(form, options) would be turned into:
$this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(
$form, 'label', $options
);
where the $this->env seems to be the twig environment. That means, to call this in your twig extension you need to have access to the proper Twig environment, and then it should already work with the recipe I just provided. Especially if you can omit the options argument, I didn't take a deeper look into how that one's assembled (but it's probably just straight forward).
So your twig function must be defined via:
public function getFunctions(): array
{
return [
new TwigFunction('myFunc', [&$this, 'myFunc'], [
'needs_environment' => true, // <--- this!
'is_safe' => ['html'],
]),
];
}
public function myFunc(\Twig\Environment $env, $field) {
// other stuff
$html = $env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(
$field, 'label', $options
);
return $html;
}

Get current route in Symfony 3.3 and Twig

I would like to have the current route in Symfony 3 in a Twig view.
I already did some research, but I didn't find any solution. I try the following in my Twig view:
{{ app.request.uri }}
It returns something like: http://localhost:8000/_fragment?_path=_format%3Dhtml%26_locale%3Dfr%26_controller%3DFOSUserBundle%253ASecurity%253Alogin
{{ app.request.get('_route') }}
Always returns NULL.
{{ app.request.getpathinfo }}
Always have: /_fragment
What I need is very simple. For an URL like localhost:8000/article/5, I would like to retrieve /article/5.How to do this?
The following code snippet will do the trick:
{{ path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) }}
app.request.attributes.get('_route') - returns current route name.
app.request.attributes.get('_route_params') - returns current route params.
path() - generates route path by route name and params.
This approach will not work for forwarded requests. In case of forward request, there is a workaround: you should pass "_route" and "_route_params" to a forwarded params list.
return $this->forward('Yourbundle:Controller:action', array(
//... your parameters...,
'_route' => $this->getRequest()->attributes->get('_route'),
'_route_params' => $this->getRequest()->attributes->get('_route_params');
));
Also you can use app.request to access any Request object functions, such as getPathInfo(), which should return current route path.
{{ app.request.pathInfo }}

Service to get/set current entity

I have a Project entity, which has a controller defining many routes:
projects/1
projects/1/foo
projects/1/bar
I need a service to provide the current project. The use case is that I have dependencies in my base twig templates which need to know the current project. i.e. a dropdown project selector that is outside the context of the template the current controller is serving.
I've tried creating a service getting route info with $container->get('router.request_context');, however, that only provides a path. I don't want to have to parse the path string if I don't have to.
What is the most proper approach?
If I understood you correctly the solution for your problem is rendering/embedding controller. Of course it's simplest, yet somehow acceptable solution for rendering parts of html with some custom logic apart from current template.
You can read about rendering/embedding controllers.
Some snippets...
Define controller:action (obviously the logic in my example is pretty straight forward):
/**
* Generate select input field.
*
* #Route("/widget", name="project_widget")
* #Method("GET")
*/
public function widgetAction()
{
$repo = $this->getDoctrine()
->getManager()
->getRepository('AppBundle:Project');
// #NOTICE: Wee need master request info, because our widget
// is rendered and called as subrequest.
$masterRequest = $this->get('request_stack')->getMasterRequest();
// #NOTICE: You need second parameter as default value in case there is no id in masterRequest route.
$currentProjectId = $masterRequest->get('id', 0);
$currentProject = $repo->find($currentProjectId);
$projects = $repo->findAll();
return $this->render('project/widget.html.twig', [
'currentProject' => $currentProject,
'projects' => $projects,
]);
}
Then you need to create the template project/widget.html.twig for it:
<div class="widget_project_selection">
{% if projects is not empty %}
<select name="widget_project_selection" id="widget_project_selection">
<option value="">None</option>
{% for project in projects %}
<option value="{{ project.id }}"
{# #NOTICE: If this is current project, select it #}
{{- currentProject and project.id == currentProject.id
? 'selected="selected"'
: '' -}}
>
{{- project.title -}}
</option>
{% endfor %}
</select>
{% else %}
<span>{{ 'Sadly, no projects yet :('|trans }}</span>
{% endif %}
</div>
and at last (but not least) render it somewhere like in base.html.twig:
{{- render(controller('AppBundle:Project:widget')) -}}
I've created for you a Github repo as reference. It's a small Symfony app with similar setup. You can even run it if you like, don't forget about dependencies and database update thou. Entry point is /app_dev.php/project/
Take a look at widgetAction, widget template and example usage in base.html.twig.
EDIT: But that's not everything. You said you need a service. If for some reason rendering/embedding controller is not an option for you or you really whant to use a Service (as in Dependency Container) you can extend Twig and use the full power of services.
I've also implemented a Twig Filter as example to show you the real power of Twig Extensions in here and here (usage in templates).
Check out Twig Extension and Extending Twig for more info about Twig Extensions.
Also check out service.yml for service and extension definitions - if you are not using Symfony3.3+, there will be some additional work to do - defining service and extension directly.
In your controller, you can use type hinting to load the "current" entity via the route.
For example:
#myrouter.yml
current_project:
path: /projects/{project}
Separately, your controller...
//mycontroller.php
public function myControllerAction(Request $request, Project $project)
{
//$project is entity (assuming valid) loaded via the route above
return $this->render('mytemplate.twig', ['project' => $project]);
}

Cannot render text filtered with trans in twig template using Sandbox Security Policy

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

Twig: Set default translation language

there is situation like this:
example.twig.html
<P>{%trans%}Example Message 1{%endtrans%}</P>
<P>{%trans%}Example Message 2{%endtrans%}</P>
<P>{%trans%}Example Message 3{%endtrans%}</P>
This is not template to show by visitor via web browser, but to send on mail
randomTroll#randomDomain.randomCountry
by Swift_Message.
I want to force change default laguage to Trollish, but I do not want to use this:
{%trans into 'trollish' %}...{% endtrans %}
every time. It works perfectly, but it is ugly like a troll after washing in swamp.
In controller i tried something like this before render('example.twig.html'):
$request->setLocale('trollish');
$this->get('session')->set('_locale', 'trollish');
then
{{ app.request.locale }}
returns "trollish"
but used language in {% trans %}...{ %trans% } still is not trollish :(
How i can fix that?
In your controller, add:
$this->get('translator')->setLocale('trollish');

Resources