output translations without document.write - javascript

I understand that document.write is evil and should not be used to output text on a website.
However, I did not find a better solution yet for my usage, where I output translated text via javascript on a page. Consider this:
My template code looks like this:
<h1><script>_e("title");</script></h1>
In a javascript file I have similar code like this to translate the strigs:
// Initialize the dictionaries and set current language.
window.lang = {};
window.lang.en = { "title": "Sample Page" };
window.lang.de = { "title": "Beispiel Seite" };
window.curLang = "en";
// Translation function that should output translated text.
function _e(text) {
var dictionary = window.lang[window.curLang];
var translation = dictionary[text];
// BELOW LINE IS EVIL.
// But what's the alternative without having to change the template code above?
document.write( translation );
}
Question:
Can I change the javascript function to not use document.write to work without changing the template code?
If not: What would be the best solution for translation here?

This is my first time using document.currentScript but i've tried this code and it should work correctly:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript" charset="utf-8">
// Initialize the dictionaries and set current language.
window.lang = {};
window.lang.en = { "title": "Sample Page" };
window.lang.de = { "title": "Beispiel Seite" };
window.curLang = "en";
// Translation function that should output translated text.
function _e(text) {
var dictionary = window.lang[window.curLang];
var translation = dictionary[text];
var container = document.currentScript;
container.parentNode.innerHTML = translation;
}
</script>
</head>
<body>
<h1><script>_e("title");</script></h1>
</body>
</html>
Another why is to use special tags or attribute to replace with something like jQuery, but this change your template. Something like
<span class='i18n-text'>title</span>
var dictionary = window.lang[window.curLang];
jQuery('.i18n-text').each(function(){
var text = $(this).text();
var translation = dictionary[text];
$(this).html(translation);
});
(i haven't tried the second solution but it should work :D)

One way of doing the translations without using document.write - and with the added consequence (benefit?) of having text when you turn off Javascript or don't have a translation - would be to annotate elements that require translation and perform the translation after the document is loaded by changing the innerHTML or textContent (if you want to escape entities).
Your translate functions could be something like this:
function translateAll() {
var dictionary = window.lang[window.curLang];
var elements = document.querySelectorAll("[data-i18n-id]");
for (var i = 0; i < elements.length; i++) {
var el = elements[i];
var translation = dictionary[el.dataset.i18nId]
if (translation) {
el.innerHTML = translation;
}
}
}
And you could execute it, for example, onload:
<body onload="translateAll();">
<h1 data-i18n-id="title">My Title</h1>
</body>

Using knockout.js, the way to go would be to store your translation keys in computed variables, then write a translation service function that looks the translations up in a hash table. The thing that would make this trigger would then be an observable which you would do an attribute binding on the HTML tag with, setting the lang attribute. Changing this observable would trigger the depending computed variables to automatically re-evaluate and update your document. If using knockout is an option here, let me know and I will setup an example.

Related

How can I create a syntax like vue js in vanilla JavaScript?

<div id="">
<span>{{msg}}</span>
</div>
Let's think msg is variable of JavaScript and now I want to get the parent tag of {{msg}} and push a new value by innerHTML, here {{msg}} working as an identity.
demo JavaScript example:
<script>
var msg = "This is update data";
{{msg}}.parentElement.innerHTML=msg;
</scritp>
This is not actual JavaScript code, only for better understanding.
You can use jquery easily to find that element and then replace the text
var msg = "This is update data";
$(`span:contains(${msg})`).html("Its New");
In javascript:
var spanTags = document.getElementsByTagName("span");
var msg = "This is update data";
var found;
for (var i = 0; i < spanTags.length; i++) {
if (spanTags[i].textContent == msg) {
found = spanTags[i];
break;
}
}
Now, you have found that element in found and you can now change its text
if (found) {
found.innerHTML = "New text";
}
The simplest approach is to treat the entire document as a string and then re-parse it when you're done.
The .innerHTML property is both an HTML decompiler and compiler depending on weather you're reading or writing to it. So for example if you have a list of variables that you want to replace in your document you can do:
let vars = {
msg: msg, // pass value as variable
test_number: 10, // pass value as number
test_str: 'hello' // pass value as string
};
let htmlText = document.body.innerHTML;
// find each var (assuming the syntax is {{var_name}})
// and replace with its value:
for (let var in vars) {
let pattern = '\\{\\{\\s*' + var + '\\s*\\}\\}';
let regexp = new RegExp(pattern, 'g'); // 'g' to replace all
htmlText = htmlText.replace(regexp, vars[var]);
}
// Now re-parse the html text and redraw the entire page
document.body.innerHTML = htmlText;
This is a quick, simple but brutal way to implement the {{var}} syntax. As long as you've correctly specified/designed the syntax to make it impossible to appear in the middle of html tags (for example <span {{ msg > hello </ }} span>) then this should be OK.
There may be performance penalties redrawing the entire page but if you're not doing this all the time (animation) then you would generally not notice it. In any case, if you are worried about performance always benchmark your code.
A more subtle way to do this is to only operate on text nodes so we don't accidentally mess up real html tags. The key to doing this is to write your own recursive descent parser. All nodes have a .childNodes attribute and the DOM is strictly a tree (non-cyclic) so we can scan the entire DOM and search for the syntax.
I'm not going to write complete code for this because it can get quite involved but the basic idea is as follows:
const TEXT_NODE = 3;
let vars = {
msg: msg, // pass value as variable
test_number: 10, // pass value as number
test_str: 'hello' // pass value as string
};
function walkAndReplace (node) {
if (node.nodeType === TEXT_NODE) {
let text = node.nodeValue;
// Do what you need to do with text here.
// You can copy the RegExp logic from the example above
// for simple text replacement. If you need to generate
// new DOM elements such as a <span> or <a> then remove
// this node from its .parentNode, generate the necessary
// objects then add them back to the .parentNode
}
else {
if (node.childNodes.length) {
for (let i=0; i<node.childNodes.length; i++) {
walkAndReplace(node.childNodes[i]); // recurse
}
}
}
}
walkAndReplace(document.body);

Updating variables inside js template literals

I have 1 string named a which stores large html element with js template literals by calling objects property . I am keeping those element in my div id hello. Now I want to update data inside js template literal when I change objects property without keeping elements of a in div again.
my code:-
var object = {name:'alisha', age:18 , count:1000};
var a = `<div class="nav">
<span>name:</span><span>${object.name}<span><br>
<span>age:</span><span>${object.age}<span><br>
<span>count:</span><span>${object.count}<span><br>
<input type="text">
</div>`;
var el = document.getElementById('hello');
el.innerHTML = a;
var replace_count = 0;
setInterval(function(){
replace_count = replace_count + 1;
var object.count = replace_count;
},2000)
Yes I have alternative idea of doing this but I can't follow those idea
My Ideas:-
I can give id or class to each span containing js template literals.
I can keep all the elements of variable a by updating objects property in div of id hello.
Please give me idea how can I do this.
You need to place the object and the string within a function to get this to work.
function doReplace(count) {
var object = {name:'alisha', age:18 , count};
var a = `<div class="nav">
<span>name:</span><span>${object.name}<span><br>
<span>age:</span><span>${object.age}<span><br>
<span>count:</span><span>${object.count}<span><br>
<input type="text">
</div>`;
var el = document.getElementById('hello');
el.innerHTML = a;
}
var replace_count = 0;
doReplace(replace_count);
setInterval(function(){
replace_count = replace_count + 1;
doReplace(replace_count);
},200)
<div id="hello"></div>
I change the time to 200ms instead of 2s so it is easier to see.
But the string literals must be recreated each time. It is a literal, not a template.
If you don't want to replace the entire innerHTML every time, then you need to set the innerHTML once and then change the textContent of the correct span during each step of the interval.
But, as I said above, these are not really templates and the ES6 name of template literal is misleading. There is no binding in vanilla JavaScript and Template Literals are a one time generation. So you need to either regenerate the literal after the data changes or just change the innerHTML or textContent of a specific element.
UPDATE
You must understand that vanilla JavaScript does not have built in data binding. Template Literals are really just a simplification of string concatenation. But they are not bindable templates.
You can always look into any of the multiple of template tools out there. If you don't want something as heavy as React, Vue or Angular then you can look into things like Rivets, Simulacra, knockout or any other of the many small data binding libraries out there.
var object = {name:'alisha', age:18 , count:0};
function render(obj) {
var binders = document.querySelectorAll('[bind]');
binders.forEach(
(el) => {
var field = el.getAttribute('bind');
if (field && (obj[field] !== undefined)) {
el.innerHTML = obj[field];
}
}
);
}
var replace_count = 0;
render(object);
setInterval(function(){
object.count++;
render(object);
},200)
<div class="nav">
<div>name: <span bind="name"></div>
<div>age: <span bind="age"></div>
<div>count: <span bind="count"></div>
<input type="text">
</div>
This is a simple binding library I just wrote. It is under 300 bytes. It probably needs some enhancements, but it shows that you can create a very simple binding library.
If these answers don't help then please describe why they don't help. I can help if you don't describe why.
Here this simple function do everything.
const getTemplateByInterpolatingParams = (templateHTML, placeholders) => {
try {
const names = Object.keys(placeholders);
const values = Object.values(placeholders);
return new Function(...names, `return \`${templateHTML}\`;`)(...values);
} catch (error) {
console.log('Error in template interpolation', error);
return error;
}
};
Pass your template with template literals and object with same keys
If you will use jQuery, it is easy:
HTML
<span>count:</span><span class="count"></span>
JS
var replace_count = 0;
setInterval(function(){
$('.nav span.count','#hello').text(replace_count++);
}, 2000);
This will change the data in all span.count in div#hello every 2 sec.
If you render ${object.count}, than it is a normal Text, or you use view.js or angular with special template-engine.
test it on fiddle:
https://jsfiddle.net/chrobaks/r2a1anw6/

How to create an array or object of variables by looking them in the DOM?

I am generating some JS variables on a Twig template and I am prefixing them with a dynamic value. For example:
<script type="text/javascript">
quoteGridId = 'grid_quote';
</script>
<script type="text/javascript">
quoteContactGridId = 'grid_quote_contact';
</script>
<script type="text/javascript">
archiveGridId = 'grid_archive';
</script>
I need to be able to use them in a Javascript file included after the page loads. How can I create an array of values containing all the *GridId vars?
I would like to be able to use the following on the script:
[
'quoteGridId' => 'grid_quote',
'quoteContactGridId' => 'grid_quote_contact',
'archiveGridId' => 'grid_archive',
]
UPDATE:
Let's try to get my problem clear for those ones opened to help. Currently I am working on a legacy system. Such system had a grid generating a gridId value and at the end a JS file was included and such file was using the var gridId to perform several things.
Now I need to replicate more than one grid on the same page and that becomes a problem since two grids are generating the same var name:
gridId = 'something';
gridId = 'something1';
When the script try to reach the gridId var is getting always the latest one (something1) and therefore no actions are being taken on the first grid.
My solution was to prefix the name to each gridId resulting on what I've as OP. Ex:
somethingGridId = 'something';
something1GridId = 'something1';
What I am trying to find is a way to re-use the main JS file by passing those dynamic gridIds but I can't find a way to get this to work.
The only solution I've in mind is to create the same file per grid and then change the value of gridId to the name of the ID to be used ....
I am open to ideas, any?
You can search the window variables with regex expressions (regular expression expressions?) i.e./.+GridId/ matches any word or variable that ends in GridId you can then iterate over them as you wish.
Example:
var pattern = /.+GridId/;
GridIds = []
for (var varName in window) {
if (pattern.test(varName)) {
GridIds.push({varName:window[varName]})
}
}
console.log(GridIds);
<script type="text/javascript">
quoteGridId = 'grid_quote';
</script>
<script type="text/javascript">
quoteContactGridId = 'grid_quote_contact';
</script>
<script type="text/javascript">
archiveGridId = 'grid_archive';
</script>
Hope this helps!
Instead of assigning quoteGridId = 'grid_quote', why don't you create a top level object and then assigning each var as a key-val pair, like:
var gridData = {}
gridData.quoteGridId = 'grid_quote'
gridData.quoteContactGridId = 'grid_quote_contact';
/* etc assignments */
//Access your data points like so in a loop, if you choose
Object.keys(gridData).forEach(key => {
const val = gridData[key]
//User `key`, and `val` however you'd like
})
i think you have you use List.
each time you push you value to the list like that :
var myList = [];
myList.push('something');
myList.push('something1');
now you cann access to all of them like that :
console.log(myList[0]);
console.log(myList[1]);
or just last :
console.log(myList[myList.length - 1])

Parsing Javascript withing Html using HTML DOM PARSER

currently trying to parse the download link for zippyshare files in php the issue is I need to get their javascript and I am not being able to do it. This is the part of the page I need to parse:
<script type="text/javascript">
var somdfunction = function() {
var a = 327030;
document.getElementById('dlbutton').omg = 327033%78956;
var b = parseInt(document.getElementById('dlbutton').omg) * (327033%3);
var e = function() {if (false) {return a+b+c} else {return (a+3)%b + 3}};
document.getElementById('dlbutton').href = "/d/91667079/"+(b+18)+"/Animals%20%28Radio%20Edit%29-www.manomuzika.net.mp3";
if (document.getElementById('fimage')) {
document.getElementById('fimage').href = "/i/91667079/"+(b+18)+"/Animals%20%28Radio%20Edit%29-www.manomuzika.net.mp3";
}
var result = 0;
}
</script>
Which being fetched from its website using:
$html = file_get_html($url);
Basically they create the download links dynamically using javascript, I am able to get the source using my parser but I need to cut it down to getting the values of:
var a = 327030;
document.getElementById('dlbutton').omg = 327033%78956;
and finally
document.getElementById('dlbutton').href = "/d/91667079/"+(b+18)+"/Animals%20%28Radio%20Edit%29-www.manomuzika.net.mp3";
Once I am able to get these three variables from within the source I will be able to create the download link my issue at the moment is cutting it down to that.
I am using this parser:
http://simplehtmldom.sourceforge.net/
If you would like to see the source code I am able to parse at the moment here it is:
http://www.somf.us/music/test.php?url=http://www66.zippyshare.com/v/91667079/file.html
You need to use regex because simple is not a javascript parser.
Here's a hint to get you started:
preg_match('/var a = (\d+);/', file_get_contents($url), $m);
echo $m[1];

wikimedia api getting relavant data from json string

This is the question I asked yesterday. I was able to get the required data. The final data is like this. Please follow this link.
I tried with the following code to get all the infobox data
content = content.split("}}\n");
for(k in content)
{
if(content[k].search("Infobox")==2)
{
var infobox = content[k];
alert(infobox);
infobox = infobox.replace("{{","");
alert(infobox);
infobox = infobox.split("\n|");
//alert(infobox[0]);
var infohtml="";
for(l in infobox)
{
if(infobox[l].search("=")>0)
{
var line = infobox[l].split("=");
infohtml = infohtml+"<tr><td>"+line[0]+"</td><td>"+line[1]+"</td></tr>";
}
}
infohtml="<table>"+infohtml+"</table>";
$('#con').html(infohtml);
break;
}
}
I initially thought each element is enclosed in {{ }}. So I wrote this code. But what I see is, I was not able to get the entire infobox data with this. There is this element
{{Sfn|National Informatics Centre|2005}}
occuring which ends my infobox data.
It seems to be far simpler without using json. Please help me
Have you tried DBpedia? Afaik they provide template usage information. There is also a toolserver tool named Templatetiger, which does template extraction from the static dumps (not live).
However, I once wrote a tiny snippet to extract templates from wikitext in javascript:
var title; // of the template
var wikitext; // of the page
var templateRegexp = new RegExp("{{\\s*"+(title.indexOf(":")>-1?"(?:Vorlage:|Template:)?"+title:title)+"([^[\\]{}]*(?:{{[^{}]*}}|\\[?\\[[^[\\]]*\\]?\\])?[^[\\]{}]*)+}}", "g");
var paramRegexp = /\s*\|[^{}|]*?((?:{{[^{}]*}}|\[?\[[^[\]]*\]?\])?[^[\]{}|]*)*/g;
wikitext.replace(templateRegexp, function(template){
// logabout(template, "input ");
var parameters = template.match(paramRegexp);
if (!parameters) {
console.log(page.title + " ohne Parameter:\n" + template);
parameters = [];
}
var unnamed = 1;
var p = parameters.reduce(function(map, line) {
line = line.replace(/^\s*\|/,"");
var i = line.indexOf("=");
map[line.substr(0,i).trim() || unnamed++] = line.substr(i+1).trim();
return map;
}, {});
// you have an object "p" in here containing the template parameters
});
It features one-level nested templates, but still is very error-prone. Parsing wikitext with regexp is as evil as trying to do it on html :-)
It may be easier to query the parse-tree from the api: api.php?action=query&prop=revisions&rvprop=content&rvgeneratexml=1&titles=....
From that parsetree you will be able to extract the templates easily.

Categories