Best practice javascript and multilanguage - javascript

what is the best practice for multilanguage website using DOM Manipulating with javascript? I build some dynamic parts of the website using javascript. My first thought was using an array with the text strings and the language code as index. Is this a good idea?

When I've built multi-lingual sites before (not very large ones, so this might not scale too well), I keep a series of "language" files:
lang.en.js
lang.it.js
lang.fr.js
Each of the files declares an object which is basically just a map from key word to language phrase:
// lang.en.js
lang = {
greeting : "Hello"
};
// lang.fr.js
lang = {
greeting : "Bonjour"
};
Dynamically load one of those files and then all you need to do is reference the key from your map:
document.onload = function() {
alert(lang.greeting);
};
There are, of course, many other ways to do this, and many ways to do this style but better: encapsulating it all into a function so that a missing phrase from your "dictionary" can be handled gracefully, or even do the whole thing using OOP, and let it manage the dynamic including of the files, it could perhaps even draw language selectors for you, etc.
var l = new Language('en');
l.get('greeting');

There are a few things you need to keep in mind when designing multilanguage support:
1 - Separate code from data (i.e. don't hard-code strings right into your functions)
2 - create a formatting hook function to deal with localization differences. Allowing formattable strings ("{0}") is better than concatenating ("Welcome to" + value), for a lot of reasons:
in some languages, a number is formatted like 1.234.678,00 instead of 1,234,567.00
pluralization is often not as simple as appending an "s" at the end of the singular
grammar rules are different and can affect the order of things so you should allow dynamic data to be appended after the translation hook: for example, "Welcome to {0}" turns into "{0} he youkoso" in japanese (this happens in pretty much every language, mind you).
3 - Make sure that you can actually format strings after the translation hook runs, so you can reuse keys.
4 - Do not, under any circunstance, hook database outputs to the translator utility. If you have multilingual data, create separate tables / rows in your database. I've seen people get this no-brainer wrong fairly often (usually for countries and states/provinces in forms).
5 - Create explicit coding practices rules for creating keys. The formatter utility function (which will look something like translate("hello world") will take a key as a parameter, and keys with slight variations make maintainance very annoying. For instance, you might end up with three keys in the following example: "enter you name", "enter your name:", "enter your name: ". Choose one format (e.g. no colon, trimmed) and catch discrepancies in code reviews. Don't do this filtering programmatically, as it can trigger false positives.
6 - Be mindful that HTML markup could potentially be needed in the translation table (e.g. if you need to bold a word in a sentence, or have footnote medical references). Test for this extensively.
7 - There are several ways of importing language strings. Ideally, you should have multiple versions of a language.lang.js file, switch between them with server side code, and reference the file from the bottom of the HTML file. Pulling the file via AJAX is also an alternative, but could introduce delays. Merging language.js into your main code file is not advisable, since you lose the benefits of file caching.
8 - Test with your target languages. This sounds silly, but I've seen a serious bug once because the programmer didn't bother to check for the existence of "é" in the key.

function Language(lang)
{
var __construct = function() {
if (eval('typeof ' + lang) == 'undefined')
{
lang = "en";
}
return;
}()
this.getStr = function(str, defaultStr) {
var retStr = eval('eval(lang).' + str);
if (typeof retStr != 'undefined')
{
return retStr;
} else {
if (typeof defaultStr != 'undefined')
{
return defaultStr;
} else {
return eval('en.' + str);
}
}
}
}
After adding this to your page, you can work with it like this:
var en = {
SelPlace:"Select this place?",
Save:"Saved."
};
var tr = {
SelPlace:"Burayı seçmek istiyor musunuz?"
};
var translator = new Language("en");
alert(translator.getStr("SelPlace")); // result: Select this place?
alert(translator.getStr("Save")); // result: Saved.
alert(translator.getStr("DFKASFASDFJK", "Default string for non-existent string")); // result: Default string for non-existent string
var translator = new Language("tr");
alert(translator.getStr("SelPlace")); // result: Burayı seçmek istiyor musunuz?
alert(translator.getStr("Save")); // result: Saved. (because it doesn't exist in this language, borrowed from english as default)
alert(translator.getStr("DFKASFASDFJK", "Default string for non-existent string")); // result: Default string for non-existent string
If you call the class with a language that you haven't defined, English(en) will be selected.

Just found a nice article about i18n in javascript:
http://24ways.org/2007/javascript-internationalisation
Although a simple google search with i18n + javascript reveals plenty of alternatives.
In the end, it depends on how deep you want it to be. For a couple of languages, a single file is enough.
You could use a framework like Jquery, use a span to identify the text (with a class) and then use the id of each span to find the corresponding text in the chosen language.
1 Line of Jquery, done.

After reading the great answers by nickf and Leo, I created the following CommonJS style language.js to manage all my strings (and optionally, Mustache to format them):
var Mustache = require('mustache');
var LANGUAGE = {
general: {
welcome: "Welcome {{name}}!"
}
};
function _get_string(key) {
var parts = key.split('.');
var result = LANGUAGE, i;
for (i = 0; i < parts.length; ++i) {
result = result[parts[i]];
}
return result;
}
module.exports = function(key, params) {
var str = _get_string(key);
if (!params || _.isEmpty(params)) {
return str;
}
return Mustache.render(str, params);
};
And this is how I get a string:
var L = require('language');
var the_string = L('general.welcome', {name='Joe'});

This way you can use one js code for multi language by multi word :
var strings = new Object();
if(navigator.browserLanguage){
lang = navigator.browserLanguage;
}else{
lang = navigator.language;
}
lang = lang.substr(0,2).toLowerCase();
if(lang=='fa'){/////////////////////////////Persian////////////////////////////////////////////////////
strings["Contents"] = "فهرست";
strings["Index"] = "شاخص";
strings["Search"] = "جستجو";
strings["Bookmark"] = "ذخیره";
strings["Loading the data for search..."] = "در حال جسنجوی متن...";
strings["Type in the word(s) to search for:"] = "لغت مد نظر خود را اینجا تایپ کنید:";
strings["Search title only"] = "جستجو بر اساس عنوان";
strings["Search previous results"] = "جستجو در نتایج قبلی";
strings["Display"] = "نمایش";
strings["No topics found!"] = "موردی یافت نشد!";
strings["Type in the keyword to find:"] = "کلیدواژه برای یافتن تایپ کنید";
strings["Show all"] = "نمایش همه";
strings["Hide all"] = "پنهان کردن";
strings["Previous"] = "قبلی";
strings["Next"] = "بعدی";
strings["Loading table of contents..."] = "در حال بارگزاری جدول فهرست...";
strings["Topics:"] = "عنوان ها";
strings["Current topic:"] = "عنوان جاری:";
strings["Remove"] = "پاک کردن";
strings["Add"] = "افزودن";
}else{//////////////////////////////////////English///////////////////////////////////////////////////
strings["Contents"] = "Contents";
strings["Index"] = "Index";
strings["Search"] = "Search";
strings["Bookmark"] = "Bookmark";
strings["Loading the data for search..."] = "Loading the data for search...";
strings["Type in the word(s) to search for:"] = "Type in the word(s) to search for:";
strings["Search title only"] = "Search title only";
strings["Search previous results"] = "Search previous results";
strings["Display"] = "Display";
strings["No topics found!"] = "No topics found!";
strings["Type in the keyword to find:"] = "Type in the keyword to find:";
strings["Show all"] = "Show all";
strings["Hide all"] = "Hide all";
strings["Previous"] = "Previous";
strings["Next"] = "Next";
strings["Loading table of contents..."] = "Loading table of contents...";
strings["Topics:"] = "Topics:";
strings["Current topic:"] = "Current topic:";
strings["Remove"] = "Remove";
strings["Add"] = "Add";
}
you can add another lang in this code and set objects on your html code.
I used Persian For Farsi language and English, you can use any type language just create copy of this part of code by If-Else statement.

You should look into what has been done in classic JS components - take things like Dojo, Ext, FCKEditor, TinyMCE, etc. You'll find lots of good ideas.
Usually it ends up being some kind of attributes you set on tags, and then you replace the content of the tag with the translation found in your translation file, based on the value of the attribute.
One thing to keep in mind, is the evolution of the language set (when your code evolves, will you need to retranslate the whole thing or not). We keep the translations in PO Files (Gnu Gettext), and we have a script that transforms the PO File into ready to use JS Files.
In addition:
Always use UTF-8 - this sounds silly, but if you are not in utf-8 from start (HTML head + JS encoding), you'll be bust quickly.
Use the english string as a key to your translations - this way you won't end up with things like: lang.Greeting = 'Hello world' - but lang['Hello world'] = 'Hello world';

You can use a google translator:
<div id="google_translate_element" style = "float: left; margin-left: 10px;"></div>
<script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({pageLanguage: 'en', layout: google.translate.TranslateElement.InlineLayout.HORIZONTAL}, 'google_translate_element');
}
</script>
<script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
</div><input type = "text" style = "display: inline; margin-left: 8%;" class = "sear" placeholder = "Search people..."><button class = "bar">🔎</button>

class Language {
constructor(lang) {
var __construct = function (){
if (eval('typeof ' + lang) == 'undefined'){
lang = "en";
}
return;
};
this.getStr = function (str){
var retStr = eval('eval(lang).' + str);
if (typeof retStr != 'undefined'){
return retStr;
} else {
return str;
}
};
}
}
var en = {
Save:"Saved."
};
var fa = {
Save:"ذخیره"
};
var translator = new Language("fa");
console.log(translator.getStr("Save"));

For Spring bundles and JavaScript there are simple solution: generate i18n array in template (e.g. JSP) and use it in JavaScript:
JSP:
<html>
<script type="text/javascript">
var i18n = [];
<c:forEach var='key' items='<%=new String[]{"common.deleted","common.saved","common.enabled","common.disabled","...}%>'>
i18n['${key}'] = '<spring:message code="${key}"/>';
</c:forEach>
</script>
</html>
And in JS:
alert(i18n["common.deleted"]);
See also Resolving spring:messages in javascript for i18n internationalization

Related

I need to allocate a url to very student name in Javascript

The name list is supposedly as below:
Rose : 35621548
Jack : 32658495
Lita : 63259547
Seth : 27956431
Cathy: 75821456
Given you have a variable as StudentCode that contains the list above (I think const will do! Like:
const StudentCode = {
[Jack]: [32658495],
[Rose]: [35621548],
[Lita]: [63259547],
[Seth]: [27956431],
[Cathy]:[75821456],
};
)
So here are the questions:
1st: Ho can I define them in URL below:
https://www.mylist.com/student=?StudentCode
So the link for example for Jack will be:
https://www.mylist.com/student=?32658495
The URL is imaginary. Don't click on it please.
2nd: By the way the overall list is above 800 people and I'm planning to save an external .js file to be called within the current code. So tell me about that too. Thanks a million
Given
const StudentCode = {
"Jack": "32658495",
"Rose": "35621548",
"Lita": "63259547",
"Seth": "27956431",
"Cathy": "75821456",
};
You can construct urls like:
const urls = Object.values(StudentCode).map((c) => `https://www.mylist.com?student=${c}`)
// urls: ['https://www.mylist.com?student=32658495', 'https://www.mylist.com?student=35621548', 'https://www.mylist.com?student=63259547', 'https://www.mylist.com?student=27956431', 'https://www.mylist.com?student=75821456']
To get the url for a specific student simply do:
const url = `https://www.mylist.com?student=${StudentCode["Jack"]}`
// url: 'https://www.mylist.com?student=32658495'
Not sure I understand your second question - 800 is a rather low number so will not be any performance issues with it if that is what you are asking?
The properties of the object (after the trailing comma is removed) can be looped through using a for-in loop, (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in)
This gives references to each key of the array and the value held in that key can be referenced using objectName[key], Thus you will loop through your object using something like:
for (key in StudentCode) {
keyString = key; // e.g = "Jack"
keyValue = StudentCode[key]; // e.g. = 32658495
// build the urls and links
}
to build the urls, string template literals will simplify the process (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) allowing you to substitute values in your string. e.g.:
url = `https://www.mylist.com/student=?${StudentCode[key]`}
Note the use of back ticks and ${} for the substitutions.
Lastly, to build active links, create an element and sets its innerHTML property to markup built using further string template literals:
let link = `<a href=${url}>${keyValue}</a>`
These steps are combined in the working snippet here:
const StudentCode = {
Jack: 32658495,
Rose: 35621548,
Lita: 63259547,
Seth: 27956431,
Cathy: 75821456,
};
const studentLinks = [];
for (key in StudentCode) {
let url = `https://www.mylist.com/student=?${StudentCode[key]}`;
console.log(url);
studentLinks.push(`<a href href="url">${key}</a>`)
}
let output= document.createElement('div');
output.innerHTML = studentLinks.join("<br>");
document.body.appendChild(output);

Is there a way to improved method for separating a substring from search position text via indexOf?

The method I use I need to put +13 and -1 inside the calculation when searching the position of each part of the text (const Before and const After), is there a more reliable and correct way?
const PositionBefore = TextScript.indexOf(Before)+13;
const PositionAfter = TextScript.indexOf(After)-1;
My fear is that for some reason the search text changes and I forget to change the numbers for the calculation and this causes an error in the retrieved text.
The part of text i'm return is date and hour:
2021-08-31 19:12:08
function Clock() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Clock');
var url = 'https://int.soccerway.com/';
const contentText = UrlFetchApp.fetch(url).getContentText();
const $ = Cheerio.load(contentText);
const Before = '"timestamp":"';
const After = '});\n block.registerForCallbacks();';
var ElementSelect = $('script:contains(' + Before + ')');
var TextScript = ElementSelect.html().replace("\n","");
const PositionBefore = TextScript.indexOf(Before)+13;
const PositionAfter = TextScript.indexOf(After)-1;
sheet.getRange(1, 1).setValue(TextScript.substring(PositionBefore, PositionAfter));
}
Example full text colected in var TextScript:
(function() {
var block = new HomeMatchesBlock('block_home_matches_31', 'block_home_matches', {"block_service_id":"home_index_block_homematches","date":"2021-08-31","display":"all","timestamp":"2021-08-31 19:12:08"});
block.registerForCallbacks();
$('block_home_matches_31_1_1').observe('click', function() { block.filterContent({"display":"all"}); }.bind(block));
$('block_home_matches_31_1_2').observe('click', function() { block.filterContent({"display":"now_playing"}); }.bind(block));
block.setAttribute('colspan_left', 2);
block.setAttribute('colspan_right', 2);
TimestampFormatter.format('block_home_matches_31');
})();
There is no way to eliminate the risk of structural changes to the source content.
You can take some steps to minimize the likelihood that you forget to change your code - for example, by removing the need for hard-coded +13 and -1. But there can be other reasons for your code to fail, beyond that.
It's probably more important to make it extremely obvious when your code does fail.
Consider the following sample (which does not use Cheerio, for simplicity):
function demoHandler() {
var url = 'https://int.soccerway.com/';
const contentText = UrlFetchApp.fetch(url).getContentText();
var matchedJsonString = contentText.match(/{.*?"timestamp".*?}/)[0];
if ( matchedJsonString ) {
try {
var json = JSON.parse(matchedJsonString);
} catch(err) {
console.log( err ); // "SyntaxError..."
}
console.log(json.timestamp)
} else {
consle.log( 'Something went terribly wrong...' )
}
}
When you run the above function it prints the following to the console:
2021-08-31 23:18:46
It does this by assuming the key value of "timestamp" is part of a JSON string, starting with { and ending with }.
You can therefore extract this JSON string and convert it to a JavaScript object and then access the timestamp value directly, without needing to handle substrings.
If the JSON is not valid you will get an explicit error similar to this:
[SyntaxError: Unexpected token c in JSON at position 0]
Scraping web page data almost always has these types of risk: Your code can be brittle and break easily if the source structure changes without warning. Just try to make suc changes as noticeable as possible. In your case, write the errors to your spreadsheet and make it really obvious (red, bold, etc.).
And make good use of try...catch statements. See: try...catch

How can I use underscore in name of dynamic object variable

Website that I'm making is in two different languages each data is saved in mongodb with prefix _nl or _en
With a url I need to be able to set up language like that:
http://localhost/en/This-Is-English-Head/This-Is-English-Sub
My code look like that:
var headPage = req.params.headPage;
var subPage = req.params.subPage;
var slug = 'name';
var slugSub = 'subPages.slug_en';
var myObject = {};
myObject[slugSub] = subPage;
myObject[slug] = headPage;
console.log(myObject);
Site.find(myObject,
function (err, pages) {
var Pages = {};
pages.forEach(function (page) {
Pages[page._id] = page;
});
console.log(Pages);
});
After console.log it I get following:
{ 'subPages.slug_en': 'This-Is-English-Sub',
name: 'This-Is-English-Head' }
Is you can see objectname subPages.slug_en is seen as a String insteed of object name..
I know that javascript does not support underscores(I guess?) but I'm still looking for a fix, otherwise i'll be forced to change all underscores in my db to different character...
Edit:
The final result of console.log need to be:
{ subPages.slug_en: 'This-Is-English-Sub',
name: 'This-Is-English-Head' }
Insteed of :
{ 'subPages.slug_en': 'This-Is-English-Sub',
name: 'This-Is-English-Head' }
Otherwise it does not work
The reason you are seeing 'subPages.slug_en' (with string quotes) is because of the . in the object key, not the underscore.
Underscores are definitely supported in object keys without quoting.
Using subPages.slug_en (without string quotes) would require you to have an object as follows:
{ subPages: {slug_en: 'This-Is-English-Sub'},
name: 'This-Is-English-Head' }
Which you could set with the following:
myObject['subPages']['slug_en'] = subPage;
Or simply:
myObject.subPages.slug_en = subPage;

passing formatting JavaScript code to HighCharts with JSON

I have a website that uses AJAX to deliver a JSON formatted string to a HighCharts chart.
You can see this as the middle JSON code part at:
http://jsfiddle.net/1Loag7pv/
$('#container').highcharts(
//JSON Start
{
"plotOptions": {
"series": {"animation": {"duration": 500}}
,"pie": {
"allowPointSelect": true,
"cursor": "pointer",
"dataLabels": {"formatter":function(){return this.point.name+': '+this.percentage.toFixed(1) + '%';}}
}
},
"chart":{"renderTo":"divReportChart"}
,"title":{"text":"Sales Totals"}
,"xAxis":{"title":{"text":"Item"}, "categories":["Taxes","Discounts","NetSalesTotal"], "gridLineWidth":1}
,"yAxis":[{"title":{"text":"Amount"}, "gridLineWidth":1}]
,"series":[{"name":"Amount","type":"pie", "startAngle": -60,"yAxis": 0,"data":[["Taxes",17.8700],["Discounts",36.0000],["NetSalesTotal",377.9500]]}]
}
//JSON end
);
The problem is that the function part...
"dataLabels": {"formatter":function(){return this.point.name+': '+this.percentage.toFixed(1) + '%';}}
is not being transferred via the JSON
All research tells me that there is NO WAY to do this.
IE... Is it valid to define functions in JSON results?
Anybody got an idea on how to get around this limitation?
It is true that you cannot pass functions in JSON. Javascript is a superset of JSON.
A common approach is for the chart to be defined in javascript (e.g. during the page load), and the page then requests just the data via Ajax. When the data is returned it can be added to the chart object, either before it is rendered or afterwards using the highcharts API.
If you really want to pass the formatter function from the server with the chart, send it as a string, and then turn it into a function like this:
var fn = Function(mystring);
and use it in highcharts like:
chart.plotOptions.pie.dataLabels = {"formatter":fn};
I've re-factored your example to show the approach: http://jsfiddle.net/wo7zn0bw/
I had a similar conundrum. I wanted to create the JSON server side (ruby on rails) so I could create images of charts for a web API and also present it on the client web browser with the same code. This is similar to SteveP's answer.
To conform with JSON standards, I changed all formatter functions to strings
{"formatter": "function(){ return this.point.name+':'+this.percentage.toFixed(1) + '%';}"}
On the web side, I navigate the hash looking for formatter keys and replace them with the function using this code (may be a better way!?). javascript:
function HashNavigator(){
this.navigateAndReplace = function(hash, key){
if (!this.isObject(hash)){
//Nice if only navigated hashes and arrays
return;
}
var keys = Object.keys(hash);
for(var i = 0; i< keys.length; i++){
if (keys[i] == key){
//convert string to js function
hash[keys[i]] = this.parseFunction(hash[keys[i]]);
} else if (this.isObject(hash[keys[i]])){
//navigate hash tree
this.navigateAndReplace(hash[keys[i]], key);
} else {
//continue
}
}
};
this.isObject = function(testVar) {
return testVar !== null && typeof testVar === 'object'
}
//http://stackoverflow.com/questions/7650071/is-there-a-way-to-create-a-function-from-a-string-with-javascript
this.parseFunction = function(fstring){
var funcReg = /function *\(([^()]*)\)[ \n\t]*{(.*)}/gmi;
var match = funcReg.exec(fstring.replace(/\n/g, ' '));
if(match) {
return new Function(match[1].split(','), match[2]);
}
return null;
};
}
To use this, would be something similar to this javascript:
hashNavigator = new HashNavigator();
hashNavigator.navigateAndReplace(myHighchartsHash, "formatter")
At that point the hash/js-object is Highcharts ready
Similar idea was used for the web image API.
I was really hoping that hacking at the JSON was not the only solution, but it works!
I used a different approach. I created a JSON like below
{"formatter": "function(){ return this.point.name+':'+this.percentage.toFixed(1) + '%';}"}
When I came to evaluating the expression, I used (assuming that the value of the 'formatter' is formatterValueString)
formatterValueString = formatterValueString.replace('function()', '');
let opts = (new Function(formatterValueString)).call(this);
formatterValue = opts;
The reason to use this approach was it became hard to bind 'this' with the function. The eval() function did not go well with accessing variable this. I am sure there are ways to do it. Just thought this was quick.

what are the different approaches to multilingual javascript applications

I wonder what are the different and which is the best method to set up some multilingual javascript application. i want to have all used strings in one file to easily change strings or add more languages later.
thnx!
You can simply make a big object tree:
var languages = {
english:{
Save:"Save"
},
german:{
Save:"Speichern"
}
};
In your app:
var l = languages.german;
alert(l.Save); //Alerts "Speicher"
The benefit of this system is that you can make sub objects to group some values together.
Whatever you do, the most important thing is to separate between your code and the texts.
If the code and the texts are mixed, maintenance will be impossible and you'll soon abandon it.
The translatable texts must be easily scanned, so that translators can translate just texts. Then, you should be able to insert the translations conveniently.
We use a JS file that includes a map of strings. We have a simple Python script that extracts the translatable strings from it. The same script also builds the output JS file that includes the same labels with the translated strings.
The result is:
When the application evolves, it's
easy to add new strings.
The script automatically finds the new strings
and we can translate them.
Completed translations go back to the JS file
without manual work.
I like using a "language dictionary array", which you can do using JSON or a simple array.
This is easy to implement:
var lang = 0 //0 = english, 1=french
var phrases=[]
phrases['cancel'] = "cancel,annuler".split(",")
alert(phrases['cancel'][lang])
Here's a function I put together to handle language translations based on the accepted answer in this question:
/**
Core script to handle language translations
**/
var Language = function() {
var activeLanguage = 'en';
var languagePack = {
'en': {
'hello-world': 'Hello World',
'show-variants': 'Show Variants',
'hide-variants': 'Hide Variants'
},
'fr': {
'hello-world': 'Bonjour World',
'show-variants': 'représentent des variantes',
'hide-variants': 'masquer variantes'
}
}
var translate = function(key, language)
{
if (typeof languagePack[language] == 'undefined')
{
return;
}else {
return languagePack[language][key];
}
};
return {
init: function(language)
{
activeLanguage = language;
},
getString: function(key, defaultText)
{
var text = translate(key, activeLanguage);
if (typeof(text) === 'undefined' || text.length == 0 || text == null)
{
text = defaultText;
}
return text;
}
}
}();
Then to initialise it in common page code, where ${language.language} is jsp code to set the language from a server side configuration.
<script type="text/javascript">
jQuery(document).ready(function() {
Language.init('${language.language}');
});
</script>
Then whenever you need a message use
Language.getString('hello-world', 'Hi World');

Categories

Resources