Changing text in several different places of the HTML page

Asked

Viewed 269 times

5

I’m thinking of developing a i18n for an application.

My idea is: there is a text #i18n_label_name at a certain place on the page and #i18n_label_contact elsewhere.

Question: What is the most performative way to scan the entire page by searching for these identifiers and changing them to the translation text.

  • have you tried using a js library to do this ? http://i18next.com/

  • @Gabrielrodrigues had no knowledge of this lib, very cool, but I want to develop my own, and I thought of something very simple, no need for attributes or something except the text.

  • you use some template engine or intend to write from scratch in pure js ?

  • @Gabrielrodrigues intend to do in pure js.

  • 3

    For anyone who needs something like this server side, I have a PHP solution here: http://answall.com/a/97733/70

5 answers

5

I believe that the best way to do this, is to make use of properties data-custom and a json with the values.

i18n = {};
i18n["pt"] = {
  "name": "Nome",
  "contact": "Contato"
};

i18n["es"] = {
  "name": "Nombre",
  "contact": "Contacto"
};

i18n["fn"] = {
  "name": "Nom",
  "contact": "Contact"
};

i18n["en"] = {
  "name": "Name",
  "contact": "Contact"
};

var language = document.getElementById("language");
var elementos = document.querySelectorAll("[data-i18n]");
var onLanguageChange = function (event) {  
  [].forEach.call(elementos, function (elem, indice) {
    elem.textContent = i18n[language.value][elem.dataset.i18n];
  }); 
};

language.addEventListener("change", onLanguageChange);
onLanguageChange();
<div>
  <select id="language">
    <option value="pt">Português</option>
    <option value="es">Español</option>
    <option value="fn">Français</option>
    <option value="en">English</option>  
  </select>
</div>
<div>
  Name: <label data-i18n="name"></label>
</div>
<div>
  Contact: <label data-i18n="contact"></label>
</div>

Follows an implementation using class instead of data-custom

i18n = {};
i18n["pt"] = {
  "name": "Nome",
  "contact": "Contato"
};

i18n["es"] = {
  "name": "Nombre",
  "contact": "Contacto"
};

i18n["fn"] = {
  "name": "Nom",
  "contact": "Contact"
};

i18n["en"] = {
  "name": "Name",
  "contact": "Contact"
};

var language = document.getElementById("language");
var elementos = document.querySelectorAll("[class*='i18n']");
var onLanguageChange = function (event) {
  [].forEach.call(elementos, function (elem, indice) {
    var propName = [].filter.call(elem.classList, function (classe, indice) {
      return classe.indexOf("i18n") >= 0;
    })[0].split("-")[1];
    elem.textContent = i18n[language.value][propName];
  }); 
};

language.addEventListener("change", onLanguageChange);
onLanguageChange();
<div>
  <select id="language">
    <option value="pt">Português</option>
    <option value="es">Español</option>
    <option value="fn">Français</option>
    <option value="en">English</option>  
  </select>
</div>
<div>
  Name: <label class="i18n-name"></label>
</div>
<div>
  Contact: <label class="i18n-contact"></label>
</div>

And finally the solution using string substitution, in this case all the HTML is recreated, so all queries to the DOM objects need to be redone and the events re-associated.

i18n = {};
i18n["pt"] = {
  "name": "Nome",
  "contact": "Contato"
};

i18n["es"] = {
  "name": "Nombre",
  "contact": "Contacto"
};

i18n["fn"] = {
  "name": "Nom",
  "contact": "Contact"
};

i18n["en"] = {
  "name": "Name",
  "contact": "Contact"
};

var template = document.body.innerHTML;
var language = document.getElementById("language");
var onLanguageChange = function (event) {  
  document.body.innerHTML = template.replace(/{{i18n-(\w*)}}/g,function(m,key){
    return i18n[language.value].hasOwnProperty(key)? i18n[language.value][key]: "";
  });
  
  language = document.getElementById("language");
  language.addEventListener("change", onLanguageChange);
};

onLanguageChange();
<div>
  <select id="language">
    <option value="pt">Português</option>
    <option value="es">Español</option>
    <option value="fn">Français</option>
    <option value="en">English</option>  
  </select>
</div>
<div>
  Name: <label>{{i18n-name}}</label>
</div>
<div>
  Contact: <label>{{i18n-contact}}</label>
</div>

  • I didn’t want to use custom data, it gets too complicated without ?

  • you can try to simulate the same effect using the class, but you will have an additional job, another option is to replace in innerHTML, but you would have to keep a "template" in memory. would have some problem with the data-custom?

  • Because you want to develop this for Apex custom data is not feasible.

  • @devgaspa, updated the answer, adapted the script to use a class in place of data-custom

  • @devgaspa, I added a third example using string manipulation.

  • In this third example the string manipulation was excellent, except for the way the strings were handled. Thank you so much for the layout and time, I will use this form of string replacement as you did, it was really cool.

Show 1 more comment

3


Using places, in relation to jquery.html vs innerHTML, the second option proved considerably faster by about 29% over the first. This is a somewhat considerable difference, without taking into account Jquery’s loading time, unlike Innerhtml which is native to Javascript.

However, in that second test, the innerHTML was slower than the jquery.append and jquery.html.

But how so???

What happens here is the following, note that in the first example the code is written as follows:

var list = document.getElementById('list');
var html = '';

for (var i = 0; i < 1000; i++) {
    html += '<div>Test ' + i + '</div>';
}

list.innerHTML = html;

As early as Monday:

var list = document.getElementById('list');
for (var i = 0; i < len; i++) {
  list.innerHTML = list.innerHTML + '<div>Test ' + i + '</div>';
}

That is, the two tests have similar purposes but in terms of performance we can notice a very distinct number, this is due to the access of the innerHTML in the second test be done within the for, may seem silly, but each time looping the same is accessed and this small slip can generate a catastrophic slowness.

Now one more thing about security...

According to the MDN: "[...]it is recommended that you do not use the innerHTML when inserting plain text as an alternative, use Node.textContent. This does not interpret the past content as HTML, but instead inserts it as a pure text."

This part concerns the implementation of <script> and security failures in the use of innerHTML (for more details I suggest you see the link) and as an alternative it is suggested the use of Node.textContent.

The Node.textContent represents the node content of the element and its descendants and the main difference between it and the innerHTML is that the first does not analyze HTML as a whole, only the textNode making it more performatic in relation to the second.

There are several other methods as: innerText and nodeValue, I will not talk in detail of all so that the content is not too extensive more with a brief search you can see what each one does. Well, let’s get down to business, this test, includes all cited native methods tested on different elements.

But, which one to use? The textContent In this last test it was more performative in relation to the others totaling an average of approximately 15% in comparison to the other forms. I believe that the best way based on what was described above, both in terms of safety and performance is to use the same. Another point to take into account is the fact of being native to Javascript.


Regarding native selectors the selector getElementByID is faster than selectors like getElementsByClassName and querySelector according to this test. Id also wins in selector testing on Jquery in this test.

What to use? no first test and in that third you can clearly see that again native features come out in front of performance.


The first two tests were taken from Stackoverflow.

Here a list of other interesting topics regarding javascript performance, recommend as additional reading.

Obs.: the values can vary in thousandths depending on the machine and browser.

2

You can use Jquery

$("#i18n_label_name").html("TEXTO");
$("#i18n_label_contact").html("TEXTO");

With this $("#i18n_label_name") selector, it goes straight into the element without having to scan all the HTML.

2

Using only JS with the function [querySelector()][1]

document.querySelector("#i18n_label_name").innerHTML = "Novo Texto";

document.querySelector("#i18n_label_contact").innerHTML = "Novo Texto";

1

For simplicity, I suggest making a method in jQuery, but if you want to write a little more and be independent of the library, you can do as the example suggested by @Tobymosque:

The content in HTML:

<select name="translate" id="tradutor">
    <option value="pt-BR">Portunguês</option>
    <option value="en-US">Inglês</option>
    <option value="es-GT">Espanhol</option>
    <option value="pe-PG">Língua do P</option>

</select>

<div data-translate="pt-BR">
    <p data-i18n="content_A">The Book's on the table</p>
    <p data-i18n="content_B">Hello World</p>
    <p data-i18n="content_C">My name is Ivan</p>
</div>

And the javascript:

 document.onreadystatechange = function () {
    if (document.readyState == "interactive") {
        var el_translate = document.querySelector('[data-translate]');
        var tradutor = document.getElementById('tradutor');

        tradutor.value = el_translate.dataset.translate;        
        tradutor.addEventListener("change", function (event) {
            el_translate.dataset.translate = event.target.value;
            setLang(el_translate);
        });
        setLang(el_translate);
    }
}

var setLang = (function() {
    var i18n = {
        "pt-BR":{
            "content_A":"O livro está sobre a mesa",
            "content_B":"Olá Mundo",
            "content_C":"Meu nome é Ivan",
            "content_PT":"Português",
            "content_EN":"Inglês",
            "content_ES":"Espanhol",
            "content_PE":"Língua do P",

        },
        "en-US":{
            "content_A":"The Book's on the table",
            "content_B":"Hello World",
            "content_C":"My name is Ivan",
            "content_PT":"Portuguese",
            "content_EN":"English",
            "content_ES":"Espanish",
            "content_PE":"Language of P",
        },
        "es-GT":{
            "content_A":"El libro está en la mesa",
            "content_B":"Hola mundo",
            "content_C":"Mi nombre es Iván",
            "content_PT":"Portugués",
            "content_EN":"Inglés",
            "content_ES":"Español",
            "content_PE":"Idioma hacer P",
        },
        "pe-PG":{
            "content_A":"PO PliPvro PesPtá PsoPbre Pa PmePsa",
            "content_B":"POPlá PMunPdo",
            "content_C":"PMeu PnoPme Pé PIPvan",
            "content_PT":"PPorPtuPguês",
            "content_EN":"PInPglês",
            "content_ES":"PEsPpanhol",
            "content_PE":"PLínPgua Pdo PP",
        }
    };

    return function (e) {
        var elementos = document.querySelectorAll("[data-i18n]");
        [].forEach.call(elementos, function (el, index) {
            el.textContent = i18n[e.dataset.translate][el.dataset.i18n];
        }); 
    };
})();

Here’s the working example and Pure Javascript

And here the example running with jQuery

Edited: At @Tobymosque’s suggestion, which I found very welcome, a no use jQuery adaptation:

  • 3

    AP made it clear that it intends to use only Javascript without jQuery, as to its method setLang, I suggest you make a closure and declare the i18n within it, thus avoiding instantiating the same whenever the method setLang run. follow your script with my improvement suggestions: Jsfiddle

Browser other questions tagged

You are not signed in. Login or sign up in order to post.