3

I have an application mainly written in PHP. The translations are done using gettext().

There is a small JavaScript part which also contains strings to be translated. I wrote this simple but working method using XMLHttpRequest:

function gettext(string_to_translate) {
    var filename = get_php_script_folder() + 'gettext.php?string_to_translate=' + string_to_translate;
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET", filename, false);
    xmlhttp.send();
    if (xmlhttp.status === 200) {
        var translated_string = xmlhttp.responseText;
        return translated_string;
    } else {
        console.log("Error while translating " + string_to_translate + " Status " + xmlhttp.status);
        return string_to_translate; //Just give the original string.
    }

}

The php file is also quite simple:

require_once '../../default.php'; //configures gettext, session management, etc.
//TODO: support for ngettext might be added.
$string_to_translate = filter_input(INPUT_GET, 'string_to_translate', FILTER_SANITIZE_STRING);
$translated_string = gettext($string_to_translate);
echo $translated_string;

In the JavaScript I just call:

var input_box_form_default_reason = gettext("Vacation");
document.getElementById('input_box_form_reason').value = input_box_form_default_reason;

If I call this function synchronously [xmlhttp.open("GET", filename, false);] Firefox/Chrome warns me that:

[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.

So while this method works, it may cease to do so at any time.

But if I run the code async [xmlhttp.open("GET", filename, true);], then the next line will be executed before the result is there. The value will be undefined.

Is it feasable to make async XMLHttpRequest work in this context? Should I stick to synchronously fetching the values until some clever API is written? Should I write my JS files with PHP? (I hope not.)

PS:

  1. I do not use any framework like jQuery. That is a "religious" thing. I want to fully understand and maintain the whole codebase myself.

  2. I read the following questions, which did not answer mine:

Bruno
  • 57
  • 8
  • 2
    Personally I use PHP to populate a JS array of any strings that need translating directly in the page's source code, which doesn't rely on any AJAX requests. If you have a lot of strings, you're also looking at a lot of requests, which could slow your app down. – Mike Sep 28 '17 at 20:25
  • I agree. I load an xml file with translations and use xpath to read them out. – Mouser Sep 28 '17 at 21:04

3 Answers3

3

Yes, you need a callback!

function gettext(string_to_translate, obj, callback)
... //your original xmlhttprequest
xmlhttp.callback = callback;, add a callback to the xmlhttp

//set the readystate event that listens to changes in the ajax call
xmlhttp.onreadystatechange = function()
{
    //target found and request complete
    if (this.status === 200 && this.readyState == 4) {
        //send result to the callback function
        this.callback(obj, this.responseText);
    } else {
        console.log("Error while translating " + string_to_translate + " Status " + xmlhttp.status);
        this.callback(obj, string_to_translate);
    }
}

//callback function specific for this case.
function setValue(obj, value)
{
   obj.value = value;
}

To call this:

gettext("Vacation", document.getElementById('input_box_form_reason'), setValue);

An extended ajax function with callback

function ajax(url, method, json, callBack)
{
 //it supports get and post
 //returns parsed JSON, when json is set to true. | json accessible via this.JSON in the callBack
 //a callback can be attached where the this refers to the xmlHTTP
 //supply an url like you would in a get request: http://www.example.com/page.php?query=1
 //start the request with xmlDoc.fire.
 
 var xmlDoc = new XMLHttpRequest
 xmlDoc.JSON = json ? true : false;
 xmlDoc.error = true;
 xmlDoc.errorMessage = ""; 
 xmlDoc.errorObj = {"error" : xmlDoc.error, "object" : "XMLHttpRequest", "message" : xmlDoc.errorMessage, "url" : url, "sync" : true, "method" : (method ? "POST" : "GET")};
 xmlDoc.url = url
 xmlDoc.method = method ? "post" : "get";
 
 xmlDoc.preserveWhiteSpace = true;

 if (method == "post")
 {
  xmlDoc.pUrl = url; 
  xmlDoc.pArg = "";
  if (url.match(/\?/)) //we need to filter out the arguments since the are send seperately. 
  {
   var splitted = url.split(/\?/);
   xmlDoc.pUrl = splitted[0];
   xmlDoc.pArg = "";
   for (var i = 1; i < splitted.length; i++)
   {
    xmlDoc.pArg += splitted[i]; //prevent additional questionmarks from being splitted.    
   }     
   
  }
  
  xmlDoc.open.apply(xmlDoc, ["post", xmlDoc.pUrl , true]); //set up the connection
  
  xmlDoc.setRequestHeader("Content-type", "application/x-www-form-urlencoded ; charset=UTF-8");
 }
 else
 {
  //get request, no special action need, just pass the url
  this.xmlDoc.open("get", url, true); //true for async
 }

 xmlDoc.onreadystatechange = readyStateXML.bind(xmlDoc, callBack);
 xmlDoc.setRequestHeader("Pragma", "no-cache");
 xmlDoc.setRequestHeader("Cache-Control", "no-cache, must-revalidate");
 
 xmlDoc.fire = fireXmlRequest; //set up fire function.
 
 return xmlDoc;
}

function fireXmlRequest()
{
 if (this.method == "post")
 {
  this.send(this.pArg); //post
 }
 else
 {
  this.send(null); //get
 }
}

function readyStateXML(callBack)
{
 if (this.readyState == 4)
 {
  //request completed, now check the returned data
  //We always assume that a request fails.
  if (this.errorMessage == "XML Not loaded." || this.errorMessage == "")
  {
   this.error = false; //set error to false, request succeeded.
   this.errorObj.error = false;   

   if (!this.responseXML && !this.JSON)
   {
    this.error = true;
    this.errorMessage = "invalid XML.";
    this.errorObj.error = this.error;
    this.errorObj.message = this.errorMessage;    
   }
   
   if (this.error == false)
   {
    this.xmlData = this.responseXML;
    
    if (this.JSON)
    {
     try
     {
      this.JSON = JSON.parse(this.responseText);
     }
     catch(err)
     {
      //JSON couldn't be parsed
      this.error = true;
      this.errorMessage = err.message + "<br />" + this.responseText;
      this.errorObj.error = this.error;
      this.errorObj.message = this.errorMessage;  
     }
    }
    
   }
   
   //404 or 400, not found error
   if (this.status == "400" || this.status == "404" || this.status == 400 || this.status == 404)
   {
    this.error = true;
    this.errorMessage = "404: The requested page isn't found.";
    this.errorObj.error = this.error;
    this.errorObj.message = this.errorMessage;    
   }
   else if(this.status == "500")
   {
    this.error = true;
    this.errorMessage = "500: Internal server error.";
    this.errorObj.error = this.error;
    this.errorObj.message = this.errorMessage;    
   }

   if (typeof(callBack) != "undefined")
   {
    callBack.call(this); //pass the xmlDoc object to the callBack
   }
  }
  else
  {
   alert("Error \n" + this.errorMessage);
   if (typeof(callBack) != "undefined")
   {
    callBack.call(this);
   }
  }
   
 }
 else
 {
  this.error = true;
  this.errorMessage = "XML Not loaded.";
  this.errorObj.error = this.error;
  this.errorObj.message = this.errorMessage;  
 }
}

//to use
ajx = ajax("index.php?query=1", "post", true, false, function(){/*callback*/});
   console.log(ajx);
   ajx.fire();
Mouser
  • 13,132
  • 3
  • 28
  • 54
  • 1
    Thank you! I just made some minor changes: [link](https://stackoverflow.com/a/46497244/2323627) – Bruno Sep 29 '17 at 21:41
2

JS is an event driven programming language. What you are missing is an event that gets triggered when your request is completed. You can bind an event “onreadystatechange” to your xmlhttp object that’ll get triggered every time readyState changes.

function gettext(string_to_translate) {
var filename = get_php_script_folder() + 'gettext.php?string_to_translate=' + string_to_translate;
var xmlhttp = new XMLHttpRequest();

xmlhttp.onreadystatechange = function() {
    if (xmlhttp.status === 200) {
        var translated_string = xmlhttp.responseText;
        document.getElementById('input_box_form_reason').value = translated_string;
    } else {
        console.log("Error while translating " + string_to_translate + " Status " + xmlhttp.status);
    }
};
xmlhttp.open("GET", filename);
xmlhttp.send();
}

I would suggest reading about JS events and Callbacks.

Himan
  • 379
  • 1
  • 7
0

As Mouser pointed out, there needs to be a callback. I edited my function as follows:

function gettext(string_to_translate, object, callback_function) {
    var filename = get_php_script_folder() + 'gettext.php?string_to_translate=' + string_to_translate;
    var xml_http_request = new XMLHttpRequest();

    /*
     * Default values:
     */
    if (undefined === callback_function) {
        callback_function = set_value;
    }
    /*
     * Input error handling:
     */
    if (typeof object !== "object") {
        console.log("Error:" + object + " is not an object.");
        return false;
    }
    if (typeof callback_function === "function") {
        xml_http_request.callback = callback_function; // add a callback to the xml_http_request
    } else {
        console.log("Error:" + callback_function + " is not a function.");
        return false;
    }

    xml_http_request.onreadystatechange = function ()
    {
        //target found and request complete
        if (this.status === 200 && this.readyState === 4) {
            //send result to the callback function
            this.callback(object, this.responseText);
        } else if (this.readyState === 4) {
            console.log("Error while translating " + string_to_translate + " Status " + xml_http_request.status);
            this.callback(object, string_to_translate);
        }
    };
    xml_http_request.open("GET", filename, true);
    xml_http_request.send();
}
//callback function specific for gettext
function set_value(object, value)
{
    object.value = value;
}

It can be called as follows:

gettext("Vacation", document.getElementById('input_box_form_reason'));

With "Vacation" being the string to be translated and input_box_form_reason being the object which value will be changed. For more flexibility in the assignment the set_value function might be modified. For complex translations and concatenations of text, gettext.php has to be optimized.

Bruno
  • 57
  • 8