How does templating engine work? - template-engine

Could you please explain how templating engine works, for example in django with
context = {'q' : 1, 'w' : 2, items : [3,4,5,6]}
{% for item in items %}
<p>{{ item }}</p>
{% endfor %}
to
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>

We can create a level of abstraction by introducing a new term "template engine language", which is different for each template engine.
During the runtime, behind the scenes, before content being send to browser, the interpreter of template engine walkthrough the code following different semantic and syntax rules (defined by template engine language) and acording to these rules, different actions are performed. Here we can make analogy with any programming language.
Let me show you how the above example can be written in PHP, and this time code will be interpreted by PHP interpreter rather than interpreter of template engine:
<?php
// Default data
$context = array( 'q' => 1, 'w' => 2, 'items' => array(1, 2, 3, 4) );
// Now array keys can be accessed as variables
extract($context);
foreach( $items as $item ) {
echo '<p>' . $item . '</p>' . PHP_EOL;
}
?>
Live: http://codepad.org/mxzJcC9N

Template Engine in Django
The template engine has two phases:
parsing
rendering
The parsing stage takes the template string and produces something
that could be rendered. Consider the template string as source code,
the parsing tool could be either a programming language interpreter or
a programming language compiler. If the tool is an interpreter,
parsing produces a data structure, the rendering tool will walk
through the structure and produces the result text. The Django
template engine parsing tool is an interpreter. Otherwise, parsing
produces some executable code, the rendering tool does nothing but
executes the code and produces the result. The Jinja2, Mako and
Tornado template module are all using a compiler as parsing tool.

Related

Laravel 8 multilanguage routes and how get the translated link of the same page

I'm trying to make my test app in multilanguage way.
This question has two correlated questions:
First question:
I followed the second answer in How to create multilingual translated routes in Laravel and this help me having a multilanguage site and the route cached, but I've a question and some misunderstanding.
It's a good practice overwrite an app config as they do int the AppServiceProver.php, making:
Config::set('app.locale_prefix', Request::segment(1));
Isn't better to work with the Session::locale in any case?
Second question:
In my case I've two languages, and in the navbar I want to print just ENG when locale is original language, and ITA when session locale is English.
If I'm in the Italian page, the ENG link in the navbar should point to the same English translated page.
Working with the method used in the other question, I hade many problems caused by the:
Config::set('app.locale_prefix', Request::segment(1));
We overwrite the variable in the config file local_prefix, and every time I switch to English language the locale_prefix will change to 'eng' and this sounds me strange, another thing I did is this:
if ( $lang && in_array($lang, config('app.alt_langs')) ){
return app('url')->route($lang . '_' . $name, $parameters, $absolute);
}
We use the alt_langs where are defined only the alternative languages, and this is a problem cause if I pass the local lang, in my case 'it', like lang parameter, this will not be found cause, from the description, the alt_lang should not contain the locale language and you will be able to get only the translated string.
If I change the:
if ( $lang && in_array($lang, config('app.alt_langs')) ){
return app('url')->route($lang . '_' . $name, $parameters, $absolute);
}
in:
if ( $lang && in_array($lang, config('app.all_langs')) ){
return app('url')->route($lang . '_' . $name, $parameters, $absolute);
}
Now using app.all_langs I'm able to choose which URL you want and in which language I want.
How do I get the translated URL?
In the blade file I need to get the translated URL of the page, and if read the other question, we used the $prefix for caching the routes and giving to the route a new name ->name($prefix.'_home'); in this way I can cache all the route and I can call the routes using blade without prefix {{ route('name') }} but, needing the translated url of the actual page a made this on the top of the view:
#php
$ThisRoute = Route::currentRouteName();
$result = substr($ThisRoute, 0, 2);
if ($result =='it' ){
$routeName = str_replace('it_', '', $ThisRoute);
$url = route($routeName,[],true,'en');
} else {
$routeName = str_replace('en_', '', $ThisRoute);
$url = route($routeName,[],true,'it');
}
#endphp
Doing this I get the actual route name that should be it_home I check if start with it_ or en_, I remove the it_ or en_ prefix and I get the translated URL, now you can use the $url as <a href="{{ $url" }}>text</a> cause if I call the {{ route('page') }} I get the link, with the locale language.
This code is not very good, I know, but I written in 5 minutes, need more implementation, and check, but for the moment is just to play with Laravel.
It's a good way?? How can I do it better (except the blade link retrieving)?? Many solution I found used middleware, but I would like to avoid a link in the navbar like mysite.com/changelang?lang=en
Is a good approach overriding the app.locale_prefix?
First
according to your question, it's a bad practice to save the preferences into .env or session because as soon as the session is finished the saved language will be removed also it's common when you need to store any preferences related to your website such as (Color, Font, Language, ...etc) you must store any of them into the cache.
Second
honestly, your code is a very strange and NOT common way and there are two ways to handle what do you need
First
There is a very helpful and awesome package called mcamara it'll help you too much (I recommend this solution).
Second
you can do it from scratch using the lang folder located in the resource folder and you must create files with the same count of the needed languages then use the keys that you'll define into these files into views and you can prefix your routes with the selected language you can use group method like so
Route::group(['prefix' => 'selected_lang'], function() {
Route::get('first_route', [Controller::class, 'your_method']);
});
or you can add the selected language as a query string like so localhost:8000/your_route?lang=en you can follow this tutorial for more info.

Lex Parser & CodeIgniter, conditionnal weird behavior

I'm using lexpyrocms parser as a package installed with composer along with a codeigniter framework with HMVC, wich allows me to use {{pseudo-variable}} in my templates/views.
I have a very weird behavior whith the parser syntax in my view :
I have this simple $modules array as data that I can print_r() in the view/template
$modules =
Array (
[users] => stdClass Object ( [id_mdl] => 8 [name_mdl] => users ),
[actions] => stdClass Object ( [id_mdl] => 9 [name_mdl] => actions )
);
If I use basic Lex syntax, I can display the name_mdl without problem with
{{modules}} {{name_mdl}} {{/modules}} => output 'users' and 'actions'
but when I use the conditional 'if' inside the loop, I get a wrong matching when I test if a variable exists :
{{modules}}
{{name_mdl}}
{{/modules}}
this outputs correctly users actions
{{modules}}
{{if exists name_mdl}}
name_mdl OK {{name_mdl}}
{{endif}}
{{/modules}}
but this does not output anything : /
{{modules}}
{{if not exists name_mdl}}
name_mdl NOT OK {{name_mdl}}
{{endif}}
{{/modules}}
This outputs 'name_mdl NOT OK users
name_mdl NOT OK actions'
the parser displays correctly a variable it just recused as existing in the condition..
I've searched a bit everywhere but it looks like an orphan problem, can't find a clue anywhere ..
Well.. I managed to get rid of this strange behavior, but I'm not sure which part of the changement I've made solved the problem ...So for what it's worth :
The error I faced was that in a loop {{list_of_things}}{{/list_of_things}} the checking of an existing variable with conditional if exists wasn't possible, though the display of this variable worked fine :
{{list_of_things}}
{{ list_name }} <-- display the list_name of each entry
{{ if exists list_name }}
The condition was never met, tough the data 'list_name' can be displayed above
{{ endif }}
{{/list_of_things}}
I did two things after that, and got rid-off the problem...
1- I moved the declaration of the Lex Parser $this->parser = new Lex\Parser();
from my front controller application/modules/my_module/module_controller.php
into the constructor of my extending core controller class application/core/MY_Controller.php
2- I cleaned up my views folder cause there where some double file from earlier development :
to be noted : the view called was and is application/modules/my_module/views/theme/my_template.php
before :
modules/my_module/views/
my_template.php
modules/my_module/views/theme/
my_template.php
after :
modules/my_module/views/
modules/my_module/views/theme/
my_template.php
So my best guess is that HMVC messed a bit the Lex Parser with the 'loaded_paths', and that two views with the same name in the same module (even though not in the same directory) can lead to unexpected behavior .. but I don't see why the hell that would change the conditionals of the parser ...

Inserting template name as class

When creating a Go template, you can give it a name, like in this example, "my_home_template":
var tmplHome = template.Must(template.New("my_home_template").Funcs(funcMap).ParseFiles("templates/base.tmpl", "templates/content_home.tmpl"))
How can I get that template name and use it inside the actual template file?
Ultimately I just want to define a convenient css class, like so:
<body class="my_home_template">
Here's a working solution, taking mkopriva's advice:
When executing a template, pass some custom parameter with dummy data. Here, I just create a "PageHome" parameter to pass to the template, and value is a simple "1", but it could be any value:
tmplHome.ExecuteTemplate(w, "base", map[string]interface{}{"PageHome": "1", "Data": events, "UserFirstName": &u.FirstName, "UserProfilePic": &u.ProfilePic})
Then, inside the template itself, a simple if statement to check if the parameter exists, and do something accordingly:
{{ if .PageHome }}
<body class="PageHome">
{{ else }}
<body>
{{ end }}
All my other template executions don't pass a "PageHome" parameter at all, so the if statement never passes as true for them.
There's probably a more advanced solution using a functions via a template function map, and having a consistent "PageType":"something" parameter in all template executions, but in the end you still have to define a parameter per template execution and still have to build up if statements in your templates anyways.

SugarCRM 6.5 CE: how to customize label in editview using Smarty

I'm trying to customize a label in an edit view using Smarty.
A field named decription should have label LBL_LABEL_ONE with a currency symbol if some_fieds has value 1, LBL_LABEL_TWO otherwise.
With the following code in detailviewdefs.php, I have no problems to accomplish this in the view detail:
array (
'name' => 'description',
'label' => '
{if $bean->some_field==1}
{$MOD.LBL_LABEL_ONE} {$CURRENCY}
{else}
{$MOD.LBL_LABEL_TWO}
{/if}',
),
Note that some_field is a field of the current module and $CURRENCY is assigned in view.detail.php with the following code:
function preDisplay() {
parent::preDisplay();
$currency = new Currency();
$this->ss->assign('CURRENCY', $currency->getDefaultCurrencySymbol());
}
Using same code in editviewdefs.php I have several problems:
First: the following fatal error getting the field of the bean:
Fatal error: Smarty error: [in
cache/modules/omn_fornitura_gas/EditView.tpl line 506]: syntax error:
invalid attribute name: '$bean->some_field'
(Smarty_Compiler.class.php, line 1536) in
C:\xampp\htdocs\sugar\include\Smarty\Smarty.class.php on line 1095
Second: the variable $CURRENCY seems not to be correct:
{€' module='omn_fornitura_gas'}
instead of
€
Quite a puzzle this one.
The reason you're seeing such a thing is that Sugar seems to render Smarty Detail and Edit view definitions quite differently.
The Problem
In detail, it very neatly compiles the field as such:
cache/modules/omn_fornitura_gas/EditView.tpl
{capture name="label" assign="label"}
{if $bean->some_field==1}
{$MOD.LBL_LABEL_ONE} {$CURRENCY}
{else}
{$MOD.LBL_LABEL_TWO}
{/if}{/capture}
However, in edit, it tries to insert it as an attribute of a translated label:
cache/modules/omn_fornitura_gas/EditView.tpl
{capture name="label" assign="label"}{sugar_translate label='
{if $bean->some_field==1}
{$MOD.LBL_LABEL_ONE} {$CURRENCY}
{else}
{$MOD.LBL_LABEL_TWO}
{/if}' module='omn_fornitura_gas'}{/capture}
This explains both the issues you're seeing, as it complains about the attribute, and displays the module name in the bottom line.
So what can we do about it?
Unfortunately, as the edit/detail templates are compiled and cached by Sugar, we can't feasibly get to them directly without digging quite deeply into the Sugar code.
However, what we can do is work around it by specifying a different label, and then setting that label dynamically, just like you were doing in the metadata, but this time via PHP in view.edit.php.
Try this:
editviewdefs.php
array (
'name' => 'description',
'label' => 'LBL_CUSTOM_LABEL',
),
view.edit.php
function preDisplay() {
parent::preDisplay();
$currency = new Currency();
global $mod_strings;
if ($this->bean->some_field == "1"){
$mod_strings['LBL_CUSTOM_LABEL'] = $mod_strings['LBL_LABEL_ONE'] . " " . $currency->getDefaultCurrencySymbol();
}
else {
$mod_strings['LBL_CUSTOM_LABEL'] = $mod_strings['LBL_LABEL_TWO'];
}
}
What happened there?
$mod_strings is a global variable in sugar that holds the current module's labels. What we've done here is use that to our advantage, and conditionally set what the label should be by setting the property of this custom label when the view is generated. Appreciated that this isn't technically a solution using Smarty, but I hope my above details can help shine a light on the inability to solve this problem exactly the same as the detail view definitions within Sugar.
Let me know if that helps, or if I can provide any further information!

How to make client side I18n with mustache.js

i have some static html files and want to change the static text inside with client side modification through mustache.js.
it seems that this was possible Twitter's mustache extension on github: https://github.com/bcherry/mustache.js
But lately the specific I18n extension has been removed or changed.
I imagine a solution where http:/server/static.html?lang=en loads mustache.js and a language JSON file based on the lang param data_en.json.
Then mustache replaces the {{tags}} with the data sent.
Can someone give me an example how to do this?
You can use lambdas along with some library like i18next or something else.
{{#i18n}}greeting{{/i18n}} {{name}}
And the data passed:
{
name: 'Mike',
i18n: function() {
return function(text, render) {
return render(i18n.t(text));
};
}
}
This solved the problem for me
I don't think Silent's answer really solves/explains the problem.
The real issue is you need to run Mustache twice (or use something else and then Mustache).
That is most i18n works as two step process like the following:
Render the i18n text with the given variables.
Render the HTML with the post rendered i18n text.
Option 1: Use Mustache partials
<p>{{> i18n.title}}</p>
{{#somelist}}{{> i18n.item}}{{/somelist}}
The data given to this mustache template might be:
{
"amount" : 10,
"somelist" : [ "description" : "poop" ]
}
Then you would store all your i18n templates/messages as a massive JSON object of mustache templates on the server:
Below is the "en" translations:
{
"title" : "You have {{amount}} fart(s) left",
"item" : "Smells like {{description}}"
}
Now there is a rather big problem with this approach in that Mustache has no logic so handling things like pluralization gets messy.
The other issue is that performance might be bad doing so many partial loads (maybe not).
Option 2: Let the Server's i18n do the work.
Another option is to let the server do the first pass of expansion (step 1).
Java does have lots of options for i18n expansion I assume other languages do as well.
Whats rather annoying about this solution is that you will have to load your model twice. Once with the regular model and second time with the expanded i18n templates. This is rather annoying as you will have to know exactly which i18n expansions/templates to expand and put in the model (otherwise you would have to expand all the i18n templates). In other words your going to get some nice violations of DRY.
One way around the previous problem is pre-processing the mustache templates.
My answer is based on developingo's. He's answer is very great I'll just add the possibility to use mustache tags in the message keycode. It is really needed if you want to be able the get messages according to the current mustache state or in loops
It's base on a simple double rendering
info.i18n = function(){
return function(text, render){
var code = render(text); //Render first to get all variable name codes set
var value = i18n.t(code)
return render(value); //then render the messages
}
}
Thus performances aren't hit because of mustache operating on a very small string.
Here a little example :
Json data :
array :
[
{ name : "banana"},
{ name : "cucomber" }
]
Mustache template :
{{#array}}
{{#i18n}}description_{{name}}{{/i18n}}
{{/array}}
Messages
description_banana = "{{name}} is yellow"
description_cucomber = "{{name}} is green"
The result is :
banana is yellow
cucomber is green
Plurals
[Edit] : As asked in the comment follows an example with pseudo-code of plural handling for english and french language. Its a very simple and not tested example but it gives you a hint.
description_banana = "{{#plurable}}a {{name}} is{{/plurable}} green" (Adjectives not getting "s" in plurals)
description_banana = "{{#plurable}}Une {{name}} est verte{{/plurable}}" (Adjectives getting an "s" in plural, so englobing the adjective as well)
info.plurable = function()
{
//Check if needs plural
//Parse each word with a space separation
//Add an s at the end of each word except ones from a map of common exceptions such as "a"=>"/*nothing*/", "is"=>"are" and for french "est"=>"sont", "une" => "des"
//This map/function is specific to each language and should be expanded at need.
}
This is quite simple and pretty straightforward.
First, you will need to add code to determine the Query String lang. For this, I use snippet taken from answer here.
function getParameterByName(name) {
var match = RegExp('[?&]' + name + '=([^&]*)')
.exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
And then, I use jQuery to handle ajax and onReady state processing:
$(document).ready(function(){
var possibleLang = ['en', 'id'];
var currentLang = getParameterByName("lang");
console.log("parameter lang: " + currentLang);
console.log("possible lang: " + (jQuery.inArray(currentLang, possibleLang)));
if(jQuery.inArray(currentLang, possibleLang) > -1){
console.log("fetching AJAX");
var request = jQuery.ajax({
processData: false,
cache: false,
url: "data_" + currentLang + ".json"
});
console.log("done AJAX");
request.done(function(data){
console.log("got data: " + data);
var output = Mustache.render("<h1>{{title}}</h1><div id='content'>{{content}}</div>", data);
console.log("output: " + output);
$("#output").append(output);
});
request.fail(function(xhr, textStatus){
console.log("error: " + textStatus);
});
}
});
For this answer, I try to use simple JSON data:
{"title": "this is title", "content": "this is english content"}
Get this GIST for complete HTML answer.
Make sure to remember that other languages are significantly different from EN.
In FR and ES, adjectives come after the noun. "green beans" becomes "haricots verts" (beans green) in FR, so if you're plugging in variables, your translated templates must have the variables in reverse order. So for instance, printf won't work cuz the arguments can't change order. This is why you use named variables as in Option 1 above, and translated templates in whole sentences and paragraphs, rather than concatenating phrases.
Your data needs to also be translated, so the word 'poop', which came from data - somehow that has to be translated. Different languages do plurals differently, as does english, as in tooth/teeth, foot/feet, etc. EN also has glasses and pants that are always plural. Other languages similarly have exceptions and strange idoms. In the UK, IBM 'are' at the trade show whereas in in the US, IBM 'is' at the trade show. Russian has several different rules for plurals depending on if they are people, animals, long narrow objects, etc. In other countries, thousands separators are spaces, dots, or apostrophes, and in some cases don't work by 3 digits: 4 in Japan, inconsistently in India.
Be content with mediocre language support; it's just too much work.
And don't confuse changing language with changing country - Switzerland, Belgium and Canada also have FR speakers, not to mention Tahiti, Haiti and Chad. Austria speaks DE, Aruba speaks NL, and Macao speaks PT.

Resources