I am trying to have twig convert an array from the translation file
// messages.en.yml
termsAndConditions:
title: Terms and Conditions
paragraph:
- Paragraph text...blah blah...1
- Paragraph text...blah blah...2
- Paragraph text...blah blah...3
- Paragraph text...blah blah...n
// termsandconditions.html.twig
// tried...
{% for i in range(1,termsAndConditions.paragraph|length) -%}
<p>{% trans %}termsAndConditions.paragraph. {{ i }}{% endtrans %}</p>
{%- endfor %}
// and tried...
{% for i in range(1,termsAndConditions.paragraph|length) -%}
<p>{{ 'termsAndConditions.paragraph.' ~ i ~ |trans }}</p>
{%- endfor %}
You need to use pairs of keys: values to get things to work:
// messages.en.yml
termsAndConditions:
title: Terms and Conditions
paragraph:
1: Paragraph text...blah blah...1
2: Paragraph text...blah blah...2
3: Paragraph text...blah blah...3
4: Paragraph text...blah blah...n
Also and due to the fact that you want to use a variable for your translation, access to the translation this way {{('termsAndConditions.paragraph.'~i)|trans }}.
I've hardcoded 4 instead of termsAndConditions.paragraph|length. Not really sure if you can access that from a twig template...
// termsandconditions.html.twig
{% for i in range(1,4) -%}
<p>{{('termsAndConditions.paragraph.'~i)|trans}}</p>
{%- endfor %}
It should work. Hope it helps.
UPDATE
termsAndConditions.paragraph|length makes no sense unless you've defined in the template the variable or you've injected the variable through the controller.
Solutions
Solution 1. In your controller, access the yaml and get the number of translations, then pass it to the template and that's it.
Solution 2. Create a service and inject the translator service to it. In the controller create a method that calculates the number of elements of a particular key. Injecting the translator service is better than reading the yaml directly as it caches the catalogue when it reads it.
Solution 3. Same as 2 but creating a twig filter. I'm going to go for this one as it seems kind of fun.
Solution 3
Let's start by creating the Extension:
namespace Acme\DemoBundle\Twig\Extension;
class TransLengthExtension extends \Twig_Extension
{
private $translator;
public function __construct($translator) {
$this->translator = $translator;
}
public function getFilters()
{
return array(
new \Twig_SimpleFilter('translength', array($this, 'translengthFilter')),
);
}
public function translengthFilter($id, $domain = null)
{
if (null === $domain) {
$domain = 'messages';
}
$i = 0;
$g = $this->translator->getMessages();
foreach($g["messages"] as $key => $message) {
if(substr( $key, 0, strlen($id) ) === $id ) {
$i++;
}
}
return $i;
}
public function getName()
{
return 'acme_extension';
}
}
As you can see above, it calculates the number of $id occurrences in the translation catalogue. Translator takes care of locale, loading the appropiate catalogue and so on. It also caches results which is really good in terms of performance.
Register the service injecting the translator and registering as a twig extension:
services:
acme.twig.acme_extension:
class: Acme\DemoBundle\Twig\Extension\TransLengthExtension
arguments:
- "#translator"
tags:
- { name: twig.extension }
Now, in your template:
{% set end = 'termsAndConditions.paragraph'|translength %}
{% for i in range(1,end) -%}
<p>{{('termsAndConditions.paragraph.'~i)|trans}}</p>
{%- endfor %}
Hope it helps.
There's a twig filter for this - keys
{% for keyname in yml_tree_nodes|keys %}
Related
After trying to create a delete method, I am experiencing the following error:
An exception has been thrown during the rendering of a template ("Some mandatory parameters are missing ("id") to generate a URL for route "movie_delete".") in movies/index.html.twig at line 10.
The code I am using:
Twig template:
{% extends 'base.html.twig' %}
{% block body %}
{% if movies|length == 0 %}
There are no movie items available. Add a movie here to get started.
{% elseif movies|length != 0 %}
These are the results: <br />
<ul>
{% for x in movies %}
<li>Title: {{ x.title }} - Price: {{ x.price }} - Edit - Delete</li>
{% endfor %}
</ul>
Add more movie entries
{% endif %}
{% endblock %}
Delete class:
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use AppBundle\Entity\Movie;
class MovieDeleteController extends Controller
{
public function deleteAction($id)
{
$em = $this->getDoctrine()->getManager();
if(!$id)
{
throw $this->createNotFoundException('No ID found');
}
$movie = $this->getDoctrine()->getEntityManager()->getRepository('AppBundle:Movie')->Find($id);
if($movie != null)
{
$em->remove($movie);
$em->flush();
}
return $this->redirectToRoute('movies');
}
}
And my routing.yml:
movie_delete:
path: /movies/delete/{id}
defaults: { _controller: AppBundle:MovieDelete:delete }
Can anyone explain me how I should add a Delete method in Symfony so that I can apply the change in the above written code?
You've forgot to pass the 'ID' variable to the twig route.
Delete
Should be
Delete
In your 'movie_delete' route, you've defined an 'id' parameter. This parameter is required for creating this route. Pass the missing parameter in your twig file and you're done!
In case you're going to work on your 'edit' route as well, keep in mind you will need an 'id' parameter as well. Make sure to pass it in your twig file as you did with the 'delete' route.
See: This Symfony documentation, Here the additional parameters for the twig routing are explained. In your case 'slug' is replaced by 'id'.
Good luck!
You didn't pass the id to the path() function. What you should've done is:
Delete
The path() function uses Symfony routing and will throw an exception if you forget to specify required attributes.
In your twig code, change the following line :
Delete
To :
Delete
Like this, the route will be generated using x.id as the id parameter.
I have a function in my Symfony controller which returns the following:
test+testString1+test2
TestController.php
public function getResultAction($fileName) {
$string1="test";
$string2="testString1";
$string3="test2";
$response = $string1."+".$string2."+".$string3;
return new Response($response);
}
In my twig I've rendered the function in my controller:
test.html.twig
{% set test %}
{{ render(controller('TestBundle:Test:getResult')) }}|split('+', 4)
{% endset %}
{{ test[0] }}
I'm using twig split filter so that I can display test, testString1 and test2 individually. But then whenever I attempt to display test[0], I receive the following error:
Impossible to access a key "0" on an object of class "Twig_Markup" that does not implement ArrayAccess interface in TestBundle:Test:test.html.twig
What's wrong with what I'm doing? I hope you could help me with this. Thanks
You miss the split filter inside the double brace as follow:
{% set test %}
{{ render(controller('TestBundle:Test:getResult')) |split('+', 4) }}
{% endset %}
Hovenever seems same problem with the set tag. From the doc:
The set tag can also be used to 'capture' chunks of text
Try with:
{%
set test=render(controller('TestBundle:Test:getResult'))|split('+', 4)
%}
Hope this help
I tried the answers of this question, but it's not working. It's weird and I cannot reproduce the behavior:
I cleared the cache
Concat works (it output's the right string)
The concated string as a variable is not translateable
The concated string pasted as a string is translateable
Edit: obj2arr casts the object to an array, to make it iterable. prepareForTwig is just using trim(), etc. - the string is outputted correctly.
Edit 2: {% set transVar = (key|prepareForTwig) %} (without prefix) doesn't work as well.
yml:
# Resources/translations/messages.en.yml
my:
keywords:
keyword1: K1
keyword2: K2
# ...
twig:
{# my.twig.html #}
{% for key, value in data|obj2arr %}
{% set transVar = 'my.keywords.' ~ (key|prepareForTwig)) %}
{{ transVar }}<br/> {# output, e.g.: my.keywords.keyword1 #}
{{ transVar|trans}}<br/> {# output, e.g.: my.keywords.keyword1 #}
{{ 'my.keywords.keyword1'|trans }} {# output: K1 #}
{% endfor %}
EDIT:
CustomTwigExtension.php
class CustomTwigExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('obj2arr', array($this, 'obj2arrFilter')),
new \Twig_SimpleFilter('prepareForTwig', array($this, 'prepareForTwigFilter')),
);
}
public function obj2arrFilter($obj)
{
return (array)$obj;
}
public function prepareForTwigFilter($str) {
$str = trim($str);
$str = strtolower($str);
$str = substr($str, 2, strlen($str)); // obj2arr() prefixes "*"
return $str;
}
public function getName()
{
return 'custom_twig_extension';
}
}
Thanks in advance!
Others examples didn't seem to work in my case, this is what I ended up using:
{{ "my.prefix.#{ myVariable }.my.postfix" | trans }}
The translation string has to be between double quotes.
Every answer of outputting concated translationstrings was right, example:
{% 'my.prefix.' ~ extendByKeyword | trans }}
The problem was a weird generated space:
(array) $obj added * (there are two spaces after the star) as a prefix to the key-variable.
I used substr() to get rid of the * (star + 1 space), but didn't notice/expect the second space.
After comparing the strings with strlen(), I found the cause.
Thanks to #Artamiel and #CarlosGranados for your help.
I am trying to make a messaging system and I'm facing a small problem. I have a bigger template that displays my menu and my content. The menu includes the number of new messages, and the content can be any page(compose a new message, inbox, sent).
The problem is that I have to render each of the small templates by passing the number of new received messages to each of them, calling the doctrine each time and repeating code. Is there any way to send the number only to the parent template?
Here are my templates:
This is the parent containing the newmsg variable that gives me problems.
{% extends "::base.html.twig" %}
{% block body %}
inbox : {{ newmsg }}
sent
compose
{% endblock body %}
Here is an example of child template:
{% block body %}
{{ parent() }}
{% if messageList %}
{% for message in messageList %}
<li>title = {{ message.title|e }}</li>
<li>cont= {{ message.content|e }}</li>
<li>data= {{ message.date|date('d-m-Y H:m:s') }}</li>
<li>sender= {{ message.sender|e }}</li>
<hr>
{% endfor %}
{% else %}
<div>no messages</div>
{% endif %}
{% endblock body %}
The problem is that each child template is asking me for the newmsg variable
$messages = $this->getDoctrine()->getRepository('MedAppCrudBundle:Message');
$newMessagesNo = count($messages->findBy(array('seen' => '0', 'receiver' => $this->getUser())));
return $this->render(
'MedAppCrudBundle:UserBackend\Message:new.html.twig',
array(
'form' => $form->createView(),
'newmsg' => $newMessagesNo,
)
);
And I have to write this in every single controller. Any way I can shorten this problem?
You could implement a service that gives back the newmsg value and call it on your parent template. Then it would not be necessary to pass the variable.
You can add a service in your bundle's services.yml with something like:
services:
newmessages:
class: Full\Class\Name\NewMessagesService
arguments: ["#doctrine.orm.entity_manager"]
Then, implement the Full\Class\Name\NewMessagesService class. Keep in mind that this class will need a constructor that receives an EntityManager argument. Something like:
<?php
namespace Full\Class\Name;
class NewMessagesService{
private $entityManager;
public function __construct($entityManager){
$this->entityManager = $entityManager;
}
public function methodToCalculate(){
//Perform calculation and return result
}
}
Then, in your parent template, replace {{newmsg} with:
{{ newmessages.methodToCalculate() }}
What's the best way to create breadcrumbs using knpmenu bundle in symfony 2.1.x ? Aside from using 3-rd party bundles.
UPDATE:
Hi, theunraveler, sorry for late answer. Now I've been following your example and I'm stuck at one moment. Here, code below throws an exception, that
Missing argument 2 for Acme\DemoBundle\Menu\MenuBuilder::getBreadCrumbs()
{% set item = knp_menu_get('main') %}
{{ knp_menu_render(item) }}
{% block breadcrumbs %}
{% set breadcrumbs = knp_menu_get('breadcrumbs', [], {'request': app.request, 'menu': item }) %}
{{ dump(breadcrumbs) }}
{% endblock %}
Why it doesn't accepts "item" variable?
Since version 2.0, getBreadcrumbsArray has been moved to Knp\Menu\Util\MenuManipulator.
Possible workout to this solution is to create a twig extension:
<?php
namespace Kimwild\CommonBundle\Twig;
use Knp\Menu\Util\MenuManipulator;
use Knp\Menu\ItemInterface;
class MenuManipulatorExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('menu_manipulator', array($this, 'menuManipulator')),
);
}
public function menuManipulator(ItemInterface $item)
{
$manipulator = new MenuManipulator();
return $manipulator->getBreadcrumbsArray($item);
}
public function getName()
{
return 'menu_manipulator';
}
}
Register twig extension:
kimwild_common.menu_manipulator_extension:
class: Kimwild\CommonBundle\Twig\MenuManipulatorExtension
public: false
tags:
- { name: twig.extension }
In breadcrumb.html.twig:
{% block root %}
{%- for link in menu_manipulator(item) %}
/* whatever you want to do ... */
{%- endfor %}
{% endblock %}
The Knp\Menu\MenuItem class has a getBreadcrumbsArray() method. It should return an array of items in the current active menu trail. If you are on an earlier version of KnpMenu (<= 1.1.2, I think), the returned array will be in the form of label => uri. Otherwise, it will be an array with each item having keys label, uri, and item.
To find the current menu item, you'll probably want to create a method in your controller (or somewhere else, if it makes more sense for your project) that looks something like this:
public function getCurrentMenuItem($menu)
{
foreach ($menu as $item) {
if ($item->isCurrent()) {
return $item;
}
if ($item->getChildren() && $current_child = $this->getCurrentMenuItem($item)) {
return $current_child;
}
}
return null;
}
From there, you can call getBreadcrumbsArray() on the returned value:
$this->getCurrentMenuItem($your_menu)->getBreadcrumbsArray();
I guess what I would do ultimately is create a Twig extension that registers a breadcrumbs global, and put the getCurrentMenuItem() method in there. That way, you can have the breadcrumb variable in all of your templates without having to manually render it in each controller.
Source: https://github.com/KnpLabs/KnpMenu/blob/master/src/Knp/Menu/MenuItem.php#L544.
Since KnpMenu 2.1, there is a new twig function: knp_menu_get_breadcrumbs_array
You can take a look at my gist: https://gist.github.com/fsevestre/b378606c4fd23814278a
I added a new twig function knp_menu_get_current_item, which retrieve the current menu item and work fine with the knp_menu_get_breadcrumbs_array function.
--
Edit:
With KnpMenu 2.2, you can now do:
<ol class="breadcrumb">
{% for breadcrumb_item in knp_menu_get_breadcrumbs_array(knp_menu_get_current_item('main')) %}
{% if not loop.last %}
<li>{{ breadcrumb_item.label }}</li>
{% else %}
<li class="active">{{ breadcrumb_item.label }}</li>
{% endif %}
{% endfor %}
</ol>
https://github.com/KnpLabs/KnpMenu/blob/master/doc/02-Twig-Integration.markdown#functions
The knp_menu_get_current_item('main') Twig function will retrieve the current menu item, for the main menu.