Google Translate Meets PrototypeGreat frameworks come together to make fantastic implementations.
Google TranslateGoogle has released a fantastic service to translate language, offering over 30 various languages to translate to. As well as detecting what language a given piece of text is written in. With these features combined the possibilities of implementation are quite vast. Walk the DOM - PrototypeI have been working Prototype.js for a few years and it is certainly my preferred Javascript framework. It assists in particular with this project for walking the DOM. A limitation of the translation API is that the service will only accept a string of a given length. When including HTML markup, white space etc this limit isn't hard to reach. My approach was to walk the DOM, collect text nodes and send them off indivually. TranslatorMy implementation as of now named "Translator" accepts 3 parameters in the constructor, one being an ID or element reference, the second being a CSS selector which will be invoked on the primary element, such that you can collect all paragraphs, headers etc to be translated. A benefactor of this approach is that any elements with event handlers or unique properties are not disturbed as only the text nodes of these elements are collected and swapped during translation. Translator also recognizes a node that has a node value that is beyond Google's maximum length limit, and will automatically splice these large nodes into more bite size payloads for translation. It is very careful not to disturb the order of the DOM and reconstructs the series of nodes just as the original large node was. Translator saves a reference to the original node such that when a user translates from French to German to Dutch it always translating based off of the original content, this is to avoid multiple translation which could lead to corrupted translations. EventDispatcher - SuperclassTranslator extends from EventDispatcher, a proto class that I had written a while back. Translator dispatches two event types "begin" and "complete". Begin dispatches an object that has the source element's innerHTML, source language and destination language as well as the collection of text nodes that Translator has collected for translation preperation. This is handy for caching as well as providing the user a notification that it is currently processing the translation. The Code
/*
Copyright (c) 2008 Matthew Foster
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/var
Translator = Class.create(EventDispatcher, {
initialize : function(element, selector, config){
this.element = $(element);
this.preservedHTML = this.element.innerHTML;
this.config = Object.extend(this.getDefaultConfig(), config);
this.textNodeCollection = this.collectChildren(selector);
this.entityWasher = new Element("div");
this.textNodeCollection = this.textNodeCollection.findAll(this.purifyTextNode);
},
purifyTextNode : function(node){
try{
if(!node)
return false;
return node.nodeType == 3;
}
catch(e){
return false;
}
},
getDefaultConfig : function(){
return { maxLength : 1000, srcLang : "en", recursive : true, type : "text" };
},
collectChildren : function(selector){
return this.element.select(selector).collect(this.collectTextNodes.bind(this)).flatten();
},
collectTextNodes : function(element){
var self = this;
var stack = $A(element.childNodes).collect(function(child){
if(child.nodeType == 3 && child.nodeValue.length < self.config.maxLength && child.nodeValue.search(/^\s+$/g) == -1)
return child;
else if(child.nodeType == 3 && child.nodeValue.length > self.config.maxLength)
return self.splitTextNode(child);
else if(child.nodeType == 1 && self.config.recursive)
return self.collectTextNodes(child);
});
return stack;
},
splitStringByMax : function(text){
var offset = 0;
var textArr = [];
while(text.length > this.config.maxLength){
var tmp = text.substr(0, this.config.maxLength);
offset = tmp.lastIndexOf(" ");
var subText = text.substr(0, offset);
text = text.substr(offset);
textArr.push(subText);
}
textArr.push(text);
return textArr;
},
splitTextNode : function(node){
var nodeStack = [];
var textArr = this.splitStringByMax(node.nodeValue);
var prevNode = false;
textArr.each(function(text, itr){
var newNode = document.createTextNode(text);
nodeStack.push(newNode);
if(node.nextSibling != null && !prevNode)
node.parentNode.insertBefore(newNode, node.nextSibling);
else if(prevNode && prevNode.nextSibling != null)
node.parentNode.insertBefore(newNode, prevNode.nextSibling);
else
node.parentNode.appendChild(newNode);
prevNode = newNode;
});
node.parentNode.removeChild(node);
return nodeStack;
},
getEventBeginObject : function(destLang){
return {
destLang : destLang,
srcLang : this.config.srcLang,
srcLangNodes : this.textNodeCollection,
srcHTML : this.preservedHTML
}
},
getEventCompleteObject : function(result){
return {
srcLangNodes : this.textNodeCollection,
destLangNodes : this.translationStack,
destLangHTML : this.element.innerHTML,
srcLangHTML : this.preservedHTML,
result : result,
resultStack : this.resultStack
}
},
finishTranslation : function(result){
this.translating = false;
this.dispatchEvent("complete", this.getEventCompleteObject(result));
},
translate : function(destLang){
if(this.translating)
return false;
var self = this;
this.dispatchEvent("begin", this.getEventBeginObject(destLang));
this.textNodeCount = this.textNodeCollection.length;
this.translationStack = [];
this.resultStack = [];
this.textNodeCollection.each(function(node){
self.translating = true;
google.language.translate(node.nodeValue, self.config.srcLang, destLang, self.handleTranslation.bind(self, node));
});
return true;
},
handleTranslation : function(node, obj){
try{
var parent = node.preservedParent || node.parentNode;
this.entityWasher.innerHTML = obj.translation;
var translatedText = this.entityWasher.innerHTML;
if(node.nodeValue.search(/^\s/) > -1)
translatedText = " " + translatedText;
if(node.nodeValue.search(/\s$/) > -1)
translatedText = translatedText + " ";
var newText = document.createTextNode(translatedText);
if(node.placeHolder)
parent.replaceChild(newText, node.placeHolder);
else
parent.replaceChild(newText, node);
node.placeHolder = newText;
node.preservedParent = parent;
this.translationStack.push(newText);
this.resultStack.push(obj);
this.textNodeCount--;
if(this.textNodeCount <= 0)
this.finishTranslation(obj);
}
catch(e){
console.log("Error has occured with handling translation error = %o arguments = %o", e, arguments);
}
}
});
References
|
||
CommentsNo comments have been posted for this page.
|
||