Manually switch _locale in symfony 4 - php

I'm absolutely stuck in getting a solution to manually switch the _locale variable in Symfony 4.
I followed these steps, but now I have absolutely no idea how to make a simple switch button in the nav section. I also took a look a this question, but this seems to be an older Symfony version..
Can anyone please help me climb out of this dark hole and explain to me how I can integrate a simple _locale switch button, or at least point me in the right direction?

The answer is slightly different from this answer which is not applicable in Symfony 4. Start with editing the services.yaml file in the config directory.
{# project/config/services.yaml}
# ...
parameters:
# ...
app_locales: [nl_NL, en_EN]
twig:
# ...
globals:
locales: %app_locales%
# ...
Then add a template to integrate the switch button somewhere in your base template.
{# project/templates/_locale_switcher.html.twig #}
{% set route = app.request.attributes.get('_route') %}
{% set route_params = app.request.attributes.get('_route_params') %}
{% set params = route_params|merge(app.request.query.all) %}
{# You may want to not print a flag/link for current view, the "if" here let
you handle it #}
{% for locale in locales if locale != app.request.locale %}
<li>
<a href="{{ path(route, params|merge({ _locale: locale })) }}">
<img src="{{ asset('img/flags/' ~ locale ~ '.jpg') }}" alt="{{
locale }}">
</a>
</li>
{% endfor %}
And finally integrate this brandnew template in your base template.
{# project/templates/base.html.twig #}
{% include '_locale_switcher.html.twig' %}
EDIT for Symfony 4.3.4+
As per the answer of Charles beneath, the locales value in services.yaml file should be inserted with quotes to avoid an unvalid YAML error:
{# project/config/services.yaml}
# ...
parameters:
# ...
app_locales: [nl_NL, en_EN]
twig:
# ...
globals:
locales: "%app_locales%"
# ...

As of latest Symfony (5.3.9), I strongly suggest one follows the documentation:
https://symfony.com/doc/current/the-fast-track/en/28-intl.html
Towards the end, do a bit of tweaking to generate a nice dropdown menu correctly based on your available languages:
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false" id="locales">
<i class="fa fa-globe" aria-hidden="true"></i>
{{ app.request.locale|locale_name(app.request.locale)|u.title }}
</a>
<ul class="dropdown-menu" aria-labelledby="locales">
{% for locale in locales|split('|') %}
<li>
<a {% if app.request.locale == locale %}
class="dropdown-item active"
{% else %}
class="dropdown-item"
{% endif %}
href="{{ path(app.request.get('_route', 'app_index'), app.request.get('_route_params', [])|merge({_locale: locale})) }}">
{{ locale|locale_name(locale)|u.title }}
</a>
</li>
{% endfor %}
</ul>
</li>
</ul>
PS: The above is using Bootstrap 5.

Here is what works for me on latest symfony 4 version
services.yaml:
parameters:
locale: 'en'
app_locales: en|fr
twig:
globals:
locales: '%app_locales%'
locale template:
{# project/templates/_locale_switcher.html.twig #}
{% set route = app.request.attributes.get('_route') %}
{% set route_params = app.request.attributes.get('_route_params') %}
{% set params = route_params|merge(app.request.query.all) %}
{# You may want to not print a flag/link for current view, the "if" here let
you handle it #}
{% for locale in locales|split('|') %}
{% if locale != app.request.locale %}
<li>
<a href="{{ path(route, params|merge({ _locale: locale })) }}">
<img src="{{ asset('img/flags/' ~ locale ~ '.jpg') }}" alt="{{ locale }}">
</a>
</li>
{% endif %}
{% endfor %}

Related

How to create language select in twig template

I'm currently using symfony 3.2. and now i have a link like this :
http://link.com?lang=en and in config/parameters I have allowed_locales -en, -ru
How could i create a language switcher in twig template like this :
EN<img src="{{ asset('assets/images/arrow-down.svg') }}" alt="arrow" class="arrow-down" />
<div class="locales-content" style="left:0;">
Russian
English
</div>
Here is what I did in my application, feel free to adapt it to your needs. It creates a dropdown with two links that redirect to the same page, but change the _locale parameter. In case there is no route in the request, it creates two links that redirect to the homepage.
<li class="dropdown">
{% if app.request.locale == 'ru' %}Russian{% else %}English{% endif %} <span class="caret"></span>
<ul class="dropdown-menu">
{# Check if there is a route and some parameters in the request #}
{% if app.request.get('_route') is not empty and app.request.get('_route_params') is not null %}
{# English #}
English
{# Russian #}
Russian
{# If there is no route in the request, redirect to the homepage #}
{% else %}
{# English #}
English
{# Russian #}
Russian
{% endif %}
</ul>
</li>

Looking for a can_access_route method

The security of my Symfony 4 application is using #security annotations in controllers :
/**
* #Route("/cat/list", name="cat_list")
*
* #Security("is_granted('ROLE_XYZ'")
*/
public function listAction()
{
// [...]
}
I am building a menu with twig from a list of route names :
{% for route_name in ["cat_list","cat_map", ,"cat_trips"] %}
<a href="{{ path(route_name) }}"/> {{ route_name|trans }} </a>
{% endfor %}
I would like to add a security check to only display the routes my user have access, something like that :
{% for route_name in ["cat_list","cat_map", ,"cat_trips"] %}
{% if can_access_route(route_name) %}
<a href="{{ path(route_name) }}"/> {{ route_name|trans }} </a>
{% endif %}
{% endfor %}
Is there something built in Symfony for that ? Or how would you build is_route_granted() ?

Parsing current uri to get the current route params inside a partial. How to avoid?

To render TOC (categories tree) inside the base.twig view I call the render() function passing it the corresponding action url:
{% block sidebar %}
{{ render(url('toc_documents_categories')) }}
{% endblock %}
The matching partial view for the '/toc/documents' action (_toc.documents_categories.twig) is defined as follows:
{% set category_id = (current_uri|split('/'))[4] %}
{% macro recursiveCategory(category, active_category_id) %}
<li>
{% if category.children|length %}
<a><span class="icon icon-plus"></span>{{ category.name_displayed }}</a>
{% else %}
{% set active_class = active_category_id == category.id ? 'active' %}
{% set url = app.url_generator.generate('documents_by_category', {category_id: category.id}) %}
<a href="{{ url }}" class="{{ active_class }}">
{{ category.name_displayed }}
</a>
{% endif %}
{% if category.children|length %}
<ul>
{% for child in category.children %}
{{ _self.recursiveCategory(child, active_category_id) }}
{% endfor %}
</ul>
{% endif %}
</li>
{% endmacro %}
{% if categories %}
<div id="categories">
<ul>
{% for category in categories %}
{{ _self.recursiveCategory(category, category_id) }}
{% endfor %}
</ul>
</div>
{% endif %}
As you can see I'm extracting current category's id by parsing current url. This is preceded by setting the current_uri global:
$app->before(function(Request $request) use ($app) {
$app['twig']->addGlobal('current_uri', $request->getRequestUri());
});
Accessing the route information (global.request.attributes.get('_route')) inside the partial view shows the corresponding subrequest route name and not the actual request route name (master request).
Is there a way to avoid manually parsing the current uri and to get the current request route params inside the partial view?
Here's the solution.
render() issues a subrequest, so you have to use the master request context:
use Symfony\Component\HttpFoundation\Request
$app->get('/documents_categories', function(Request $request) use($app) {
$master_request = $app['request_stack']->getMasterRequest();
$current_route = $master_request->get('_route');
$active_category_id = null;
if($current_route === 'documents_by_category') {
$active_category_id = $master_request->get('_route_params')['category_id'];
}
// ... do things: parse toc tree ...
return $app['twig']->render('_toc.documents.categories.twig', array(
"categories" => $toc_tree,
"active_category_id" => $active_category_id
));
})->bind('toc_documents_categories');
Then inside the partial view you have to only reference the passed active_category_id parameter:
{% if categories %}
<div id="categories">
<ul>
{% for category in categories %}
{{ _self.recursiveCategory(category, active_category_id) }}
{% endfor %}
</ul>
</div>
{% endif %}
Thanks to #keyboardSmasher for 'Why not pass the category_id in the render function' comment. Yet I'm not sure if I did it the way he assumed.

Multiple Header Styles In Timber/Twig

I'm coding up a theme using Timber for Wordpress, which uses the twig templating engine.
I currently have different data needed for header sections on three pages.
Home Page > BG Slider & Title
Blog Page > BG & Pinned Post
Other Pages > Regular BG Image
The navigation is also supposed to be the same over each of these page headers.
I'm unsure of the best way to use twig to solve this problem.
In my base.twig I currently have:
{% block header %}
<div class="wrapper">
<h1 class="hdr-logo" role="banner">
<a class="hdr-logo-link" href="/" rel="home">{{site.name}}</a>
</h1>
<nav id="nav-main" class="nav-main" role="navigation">
<ul class="nav">
{% for item in menu.get_items %}
<li class="nav-item {{item.classes | join(' ')}}">
<a class="nav-link" href="{{item.get_link}}">{{item.title}}</a>
{% if item.get_children %}
<ul class="nav nav-drop">
{% for child in item.get_children %}
<li class="nav-drop-item">
<a class="nav-link" href="{{child.get_link}}">{{child.title}}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</nav><!-- #nav -->
</div>
{% endblock %}
I could copy and paste this block onto each page, ie add it to
home.twig, blog.twig and other.twig
But i am repeating myself constantly, and it feels wrong. I need to feed through this on each page, without repeating my navigation each time i do it.
Does anyone have a suggestion as to how to fix?
What you want are Twig macros and/or Includes
Includes
can be used to include another .twig file in your template.
You can add variables to an Include also, but by default code inside the include have acccess to global context. eg. all template variables.
{# template.html will have access to the variables from the current context and the additional ones provided #}
{% include 'template.html' with {'foo': 'bar'} %}
{% set vars = {'foo': 'bar'} %}
{% include 'template.html' with vars %}
Macros
Are isolated from the global context, and can only work with variables/parameters that you supply to it.
{# defineit #}
{% macro input(name, value, type, size) %}
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
{% endmacro %}
{# then calling it #}
<p>{{ _self.input('username') }}</p>
With both Includes and Macros you can easily do infinitely nested trees, when you include itself in the loop.
Example of nested tree with include:
{# content of tree.twig file for include#}
{% for item in menu.get_items %}
<li class="nav-item {{item.classes | join(' ')}}">
<a class="nav-link" href="{{item.get_link}}">{{item.title}}</a>
{% if item.get_children %}
<ul class="nav nav-drop">
{# recursion #}
{% include 'tree.twig' with { menu : item.get_children } %}
</ul>
{% endif %}
</li>
{% endfor %}

Twig template cannot include php template

I encountered a problem, which for me is quite not clear and hard to understand. I have tried to make calendar widget, which is supposed to be display on every page on my site. So, I think it should be average template (no arguments, no every site). I tried to do it as twig template. I managed to render calendar, but I had a problem with acquiring date object (which is necessary to get proper arguments for rendering calendar). After a short while, I tried to make a php template, which will be included by main twig template (layout.html.twig). It does not success. I have enabled php engine in config.yml, but does not help - php template is in fact included, but as a normal file, not php file (not parsed as php script), (but php engine is working, I tried to render php template from a controller it works). I have read also it is possible to include a result from another controller in template, but for me it is not proper solution, I have not tried that (php template should fit to this problem).
What should I do to solve this problem?
Main config.yml
imports:
- { resource: parameters.ini }
- { resource: security.yml }
framework:
#esi: ~
translator: { fallback: %locale% }
secret: %secret%
charset: UTF8
router: { resource: "%kernel.root_dir%/config/routing.yml" }
form: true
csrf_protection: true
validation: { enable_annotations: true }
templating: { engines: ['twig', 'php'] }
session:
default_locale: %locale%
auto_start: true
# Twig Configuration
twig:
debug: %kernel.debug%
strict_variables: %kernel.debug%
# Assetic Configuration
assetic:
debug: %kernel.debug%
use_controller: false
# java: /usr/bin/java
filters:
cssrewrite: ~
# closure:
# jar: %kernel.root_dir%/java/compiler.jar
# yui_css:
# jar: %kernel.root_dir%/java/yuicompressor-2.4.2.jar
# Doctrine Configuration
doctrine:
dbal:
driver: %database_driver%
host: %database_host%
port: %database_port%
dbname: %database_name%
user: %database_user%
password: %database_password%
charset: UTF8
orm:
auto_generate_proxy_classes: %kernel.debug%
auto_mapping: true
# Swiftmailer Configuration
#swiftmailer:
# transport: %mailer_transport%
# host: %mailer_host%
# username: %mailer_user%
# password: %mailer_password%
jms_security_extra:
secure_controllers: true
secure_all_services: false
# services:
# TpsaMailer:
# class: Tpsa\TestBundle\Controller\MailerController
layout.html.twig
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
{% block stylesheets %}
<link rel="stylesheet" type="text/css"
href="{{ asset('bundles/tpsablog/css/main.css') }}">
{% endblock %}
{% block javascripts %}
<!-- empty javascripts -->
{% endblock %}
<title>
{% block title %}
{% trans %}blog.programisty.duga{% endtrans %}
{% endblock %}
</title>
</head>
<body>
<div id="all">
<div id="top">
{% block top %}
<div style="float: left" class="right">
<img style="float: left;
vertical-align: middle; margin: 8px
8px 8px 0px" src="{{ asset('bundles/tpsablog/images/glider.png') }}">
<h4>{% trans %}blog.programisty.duga{% endtrans %}</h4>
<div style="font-size: 8px">
{% trans %}ciekawosc.wiedza.niewygodne{% endtrans %}
<!-- Ciekawość i wiedza... To, co jest niewygodne dla
władzy -->
</div>
</div>
<div style="float: right">
{% trans %}strona.glowna{% endtrans %}
{% trans %}o.mnie{% endtrans %}
{% trans %}napisz.do.mnie{% endtrans %}
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
{{ app.user.username }}
{% trans %}tagi{% endtrans %}
{% trans %}wiadomosci{% endtrans %}
{% trans %}wyloguj{% endtrans %}
{% else %}
{% trans %}zaloguj{% endtrans %}
{% endif %}
<div style="text-align: center; margin: 10px 0px">
<a href="{{ path('TBB_rss') }}">
<img src="{{ asset('bundles/tpsablog/images/rss.png')
}}" alt="rss channel">
</a>
<a href="http://www.facebook.com/duga.chernobyl"
target="_blank">
<img src="{{ asset('bundles/tpsablog/images/facebook.png') }}"
alt="facebook">
</a>
<a href="http://www.youtube.com/user/DugaEye"
target="_blank">
<img src="{{ asset('bundles/tpsablog/images/youtube.png')
}}" alt="youtube">
</a>
</div>
</div>
<div style="clear:both"></div>
{% endblock %}
</div>
<div id="frame">
<div id="left">
{% block content %}
{% trans %}TODO{% endtrans %}
{% endblock %}
</div>
<div id="right">
{% block panel %}
<div style="text-align: left">
<div style="text-align: center">
<h4>{% trans %}profil.duga.eye{% endtrans %}</h4>
<img style="width: 100px" src="{{
asset('bundles/tpsablog/images/photo.jpg')
}}">
</div>
<div style="font-weight:900; margin-top: 10px">
<ul>
<li>{% trans %}wiek{% endtrans %}: 21</li>
<li>{% trans %}miejsce{% endtrans %}: /dev/null</li>
<li>{% trans %}zainteresowania{% endtrans %}: {% trans %}programowanie.hacking.filozofia{% endtrans %}</li>
<li>{% trans %}email{% endtrans %}: Mail
</ul>
</div>
</div>
<h3>{% trans %}reklamy{% endtrans %}</h3>
{% include '::calendar.html.php' %}
{% endblock %}
</div>
</div>
<div id="footer">
{% block footer %}
{% trans %}footer{% endtrans %}
{% endblock %}
</div>
</div>
</body>
</html>
How to get proper parameters offset, number, koniec, aktualny from php Date object? (now it is hard coded)
calendar.html.twig
{% include '::calendar.html.php' %}
{% set offset = 1 %}
{% set number = 28 %}
{% set koniec = 7 - ((offset + number) % 7) %}
{% set aktualny = 13 %}
<table border="0" style="text-align: center">
<thead>
<tr>
<td>{% trans %}pn{% endtrans %}</td>
<td>{% trans %}wt{% endtrans %}</td>
<td>{% trans %}sr{% endtrans %}</td>
<td>{% trans %}czw{% endtrans %}</td>
<td>{% trans %}pt{% endtrans %}</td>
<td>{% trans %}sob{% endtrans %}</td>
<td>{% trans %}nie{% endtrans %}</td>
</tr>
<tbody>
{% if offset % 7 != 0 %}
<tr>
{% for i in range(0,offset-1,1) %}<td><br></td>{% endfor %}
{% endif %}
{% for i in 1..number %}
{% if (i+offset)%7 == 1 %}<tr>{% endif %}
<td>
{% if i == aktualny %}
<span style="color: red">{{ i }}</span>
{% else %}
{{ i }}
{% endif %}
</td>
{% if (i+offset)%7 == 0 %}</tr>{% endif %}
{% endfor %}
{% if koniec < 7 %}
{% for i in 1..koniec %}
<td><br></td>
{% endfor %}
</tr>
{% endif %}
</tbody>
</table>
php template which should be executed as php template by including it in twig template , but it is not parsed and executed as the one template.
calendar.html.php
ppp<?php echo ('ala') ?>ooo
Simply includes 'pppooo' in source, not visible being handled as html tag.
deps file if it is needed
[symfony]
git=http://github.com/symfony/symfony.git
version=v2.0.9
[twig]
git=http://github.com/fabpot/Twig.git
version=v1.5.1
[monolog]
git=http://github.com/Seldaek/monolog.git
version=1.0.2
[doctrine-common]
git=http://github.com/doctrine/common.git
version=2.1.4
[doctrine-dbal]
git=http://github.com/doctrine/dbal.git
version=2.1.5
[doctrine]
git=http://github.com/doctrine/doctrine2.git
version=2.1.5
[swiftmailer]
git=http://github.com/swiftmailer/swiftmailer.git
version=v4.1.5
[assetic]
git=http://github.com/kriswallsmith/assetic.git
version=v1.0.2
[twig-extensions]
git=http://github.com/fabpot/Twig-extensions.git
[metadata]
git=http://github.com/schmittjoh/metadata.git
version=1.0.0
[SensioFrameworkExtraBundle]
git=http://github.com/sensio/SensioFrameworkExtraBundle.git
target=/bundles/Sensio/Bundle/FrameworkExtraBundle
version=origin/2.0
[JMSSecurityExtraBundle]
git=http://github.com/schmittjoh/JMSSecurityExtraBundle.git
target=/bundles/JMS/SecurityExtraBundle
version=origin/1.0.x
[SensioDistributionBundle]
git=http://github.com/sensio/SensioDistributionBundle.git
target=/bundles/Sensio/Bundle/DistributionBundle
version=origin/2.0
[SensioGeneratorBundle]
git=http://github.com/sensio/SensioGeneratorBundle.git
target=/bundles/Sensio/Bundle/GeneratorBundle
version=origin/2.0
[AsseticBundle]
git=http://github.com/symfony/AsseticBundle.git
target=/bundles/Symfony/Bundle/AsseticBundle
version=v1.0.1
You cannot mix-and-match twig and php in a single Response (to do so would be circumventing part of the point of twig, which is to prevent designers from creating too much logic in the view).
I think the Symfony documentation should/could be clearer about this (at the moment it basically says "enable them both and do what you like").
If you embed another controller then you should be able to serve up a different Response and that Response can be php based.
NOTE: snippets below are totally non-tested.
http://twig.sensiolabs.org/doc/functions/date.html
The function date seems to create \DateTime object.
{% set now = date() %}
{% set offset = date(now.format('Y/m/01')).format(w) %} {# weekday of 1st day #}
{% set number = now.format('t') %} {# days in month #}
{% set koniec = 7 - ((offset + number) % 7) %}
{% set aktualny = now.format('n') %} {# today #}
However, if you wants to include original php file (say 'calendar.php') in twig,
you have to write extension to get it work.
class CalendarExtension extends \Twig_Extension
{
private $pathToPhp; //store that where the php file is
public function setPhpFile($pathToPhp)
{
$this->pathToPhp = $pathToPhp;
}
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('calendar', array($this, 'showCalendar'))
);
}
public function showCalendar([put arguments here if you need])
{
ob_start();
include ($this->pathToPhp);
return ob_get_clean();
}
}
To make above work, you should create "tagged" service in container.
in app/config/config.yml
services:
calendar_twig_extension:
class: __Namespace__\CalendarExtension
calls:
- [setPhpFile, [__path to your calendar.php__]]
tags:
- [name: twig.extension]
words that double-underscored should be replaced:
_ _ Namespace _ _ : Namespace of CalendarExtension
_ _ path to your calendar.php _ _ : full path to your calendar.php. You can use parameters like %kernel.root_dir% and so on to manage your path project-relative.
With these, you finally can simply write
{{ calendar([arguments for CalendarExtension::showCalendar]) }}
Have you considered rendering your calendar php template by using {% render 'ApplicationBundle:Controller:action' %} and in the action rendering the php template?
You might also render your calendar php in the action that render calendar twig and pass the output of the php template as a simple twig variable.
Note: to display such a var, don't forget to do {{ var|raw }} if there is any html tag inside.
Note2: as of symfony2.2, the render parameter as changed to {% render url('route_name') %}

Categories