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.
Related
I am running Symfony 4 app with Vue.js enabled. Is there any good practice to send my data from Twig templates to Vue.js components? Currently I have a number of data items for example on my header component, and HTML element data section looks like this:
<header-nav class="headnav headnav-fixed z-depth-1"
logo="{{ asset('build/images/site_logo.png') }}"
username="{{ app.user.name }}"
logout-url="{{ path('logout') }}"
logout-title="{% trans %} Logout {% endtrans %}"
instruction-url="{{ path('system_instruction_download') }}"
instruction-title="{% trans %} Download instruction {% endtrans %}"
current-locale="{{ app.request.getLocale()|upper }}"
v-bind:locales="{{ locales|json_encode }}"
>
Let's say I have a number of different URL's and other stuff. What is the best way to send the data? Should I first prepare an array of URL's on my controller? Which controller should it be if I want to prepare some global variables which will be used on my header, so they shouldn't be locked only on one controller.
Assuming that you render multiple "vue applications", you can define global variables with
1) twig configuration
Documentation says:
"Twig allows to inject automatically one or more variables into all templates.
These global variables are defined in the twig.globals option inside the main Twig configuration file"
2) You could create abstract controller with function merging variables
// MyAbstractController.php
protected function getTwigVariables(array $vars) {
$globals = [];
// ... fill $globals with your stuff
return array_merge(['globalVar1' => ], $vars);
}
// TestController extends MyAbstractController
public function indexAction() {
//...
return $this->render('viewPath.html.twig', $this->getTwigVariables([
'specificVariable' => 'variableContent'
]));
}
You could also embed controllers inside your main twig.
You can create headerAction, footerAction etc. and create subrequest for this actions.
For storing variables you can also use script tags
// twig
<script id="serverJson" type="application/json">{{ jsonContent|json_encode()|raw }}</script>
// serverJson.js
const configElement = document.getElementById("serverJson");
export default JSON.parse(configElement.innerHTML);
// ViewTemplate.vue
import serverJson from "path-to-serverJson.js"
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
I've added custom tags to my collection pages with help from this support forum: https://ecommerce.shopify.com/c/ecommerce-design/t/change-order-in-which-tags-are-displayed-on-collection-page-179468
The tags that appear on the collection pages are defined by linklists in the navigation. There is more about this in the discussion forum I posted above.
The snippet of code I inserted into collection-template.liquid just below the header is:
{% capture collection_taglist %}{{ collection.handle | append: "-tags" }}{% endcapture %}
{% if linklists[collection_taglist] %}
<ul class="tags tags--collection inline-list">
{% for link in linklists[collection_taglist].links %}
<li>{{ link.title | link_to: link.url }}</li>
{% endfor %}
</ul>
{% endif %}
Everything is working as intended, however I cannot get the active tag to be highlighted using CSS styling. I also tried some Javascript (see below) but that does not seem to be working either.
jQuery(".tags").find("a[href='"+window .location .href+"']").each(function() {
jQuery(this).addClass("active");
});
See here: http://shopsexologyinstitute.com/collections/women (you can enter using the password 'eaclim')
Any help would be greatly appreciated!
Many thanks :)
I run into this in all sorts of circumstances. Running the following in the console outlines the issue:
jQuery(".tags").find('a').each(function(){ console.log(this.href);});
The host in the urls is not the host of the site.
You'll need to extract the pathname for comparison. There are various ways to do that but run the following in Chrome or Firefox to get the idea. URL is not widely supported so for production you'd need a different way to parse the link urls:
(function(){
var path = location.pathname;
return jQuery(".tags").find('a').filter(function(){
console.log('checking: '+ this.getAttribute('href'));
var href = new URL(this.getAttribute('href'), location.href);
console.log(href.pathname +' vs '+ path);
return href.pathname == path;
});
})();
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()))
));
});
//...
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