A few weeks ago James Edmunds (fellow manager of our local user group and local ColdFusion developer) demonstrated a very slick example of the Google Ajax Language API. This is a set of APIs that allow for translation services. I thought I'd take some time to play with it myself and share a simple example or two.
First off, I recommend reading the documentation. It isn't 100% clear all the time (big surprise here) but it will give you a basic idea of what to do. Definitely check the Hello World example at minimum.
To use Google's language API, you begin by first loading their JavaScript loader library.
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
This provides a hook to load multiple Google services, including their Language API. To load it, you would simply do:
google.load("language", "1");
This fires off a request to load the relevant library (that second argument simply tells Google to load the latest version one build). Due to the asynchronous nature of this request, we need to ask Google to run another method when done:
google.setOnLoadCallback(initialize);
If you are building a jQuery application you will want to ensure these lines are not within the typical $(document).ready block. In fact, I removed that completely from my demo. Instead, I simply used the initialize function (and that name isn't required of course) as my main block for setting up listeners and other jQuery related tasks.
With that - let's talk about my first demo. The idea for this comes from what James had showed me - a basic product catalog that allowed you to translate product descriptions. I began by writing code to fetch records from the cfcodeexplorer sample database.
<cfquery name="getArt" datasource="cfcodeexplorer">
select *
from books
where bookdescription != ''
</cfquery>
<style>
.bookentry {
background-color: #f0f0f0;
width: 500;
padding: 10px;
margin-bottom: 10px;
}
</style>
<cfoutput query="getArt">
<div class="bookentry">
<h2>#title#</h2>
<p>
<span class="description">#bookdescription#</span>
</p>
</div>
</cfoutput>
Nothing terribly fancy there. I get the records and loop over them. All the fun stuff happens in the JavaScript. That means if a user comes here with no JavaScript support, they won't see a thing extra, which is good. Now for the JavaScript:
google.load("language", "1");
google.setOnLoadCallback(initialize);
function initialize() {
$(".bookentry").each(function() {
var s = "<div class="translationarea">"
s += "<p><a href="" class="tospanish">Spanish</a> | <a href="" class="tofrench">French</a></p>"
s += "<div class="translation"></div>"
s += "</div>"
$(this).append(s)
})
The first two lines are what I described above - they load up the Language library and run a function when done. I begin by finding each of my book entry divs that were output from the query. For each of them I create a string that includes links for Spanish and French translations. In this string I also create an empty div that will be used for my mouseover. (On reflection, I should create that variable one time, outside the each function, and then use it. I'd get much better performance, but to be honest, I don't see the necd to rewrite the demo just yet.)
The net result of this function is to add links to each of the records from the database. I want to support translations when you mouseover the links. So next I'll simply add the event listeners.
$(".tospanish").mouseover(function() {
var translationarea = $(this).parent().parent().find(".translation")
var text = $(this).parent().parent().parent().find(".description").text()
google.language.translate(text, "en", "es", function(result) {
if (!result.error) {
translationarea.html(result.translation)
}
})
})
$(".tofrench").mouseover(function() {
var translationarea = $(this).parent().parent().find(".translation")
var text = $(this).parent().parent().parent().find(".description").text()
google.language.translate(text, "en", "fr", function(result) {
if (!result.error) {
translationarea.html(result.translation)
}
})
})
I've got one listener each for both the Spanish and French links. Both functions do the exact same thing, except that one will translate to Spanish and the other to French. (My next demo will make this a bit more simpler and remove the duplication.) In each function, we do a bit of traversal to find the translationarea (where our translated text will be dumped) and the product text itself. This is probably the most complex aspect to be honest. The translation then is brutally simply: google.language.translate(text, "en", "fr", callback). The callback simply checks the result and sets it to the blank div we had created before. Here is the entire template:
<cfquery name="getArt" datasource="cfcodeexplorer">
select *
from books
where bookdescription != ''
</cfquery>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://www.google.com/jsapi" type="text/javascript" ></script>
<script>
google.load("language", "1");
google.setOnLoadCallback(initialize);
function initialize() {
$(".bookentry").each(function() {
var s = "<div class="translationarea">"
s += "<p><a href="" class="tospanish">Spanish</a> | <a href="" class="tofrench">French</a></p>"
s += "<div class="translation"></div>"
s += "</div>"
$(this).append(s)
})
$(".tospanish").mouseover(function() {
var translationarea = $(this).parent().parent().find(".translation")
var text = $(this).parent().parent().parent().find(".description").text()
google.language.translate(text, "en", "es", function(result) {
if (!result.error) {
translationarea.html(result.translation)
}
})
})
$(".tofrench").mouseover(function() {
var translationarea = $(this).parent().parent().find(".translation")
var text = $(this).parent().parent().parent().find(".description").text()
google.language.translate(text, "en", "fr", function(result) {
if (!result.error) {
translationarea.html(result.translation)
}
})
})
}
</script>
<style>
.bookentry {
background-color: #f0f0f0;
width: 500;
padding: 10px;
margin-bottom: 10px;
}
</style>
<cfoutput query="getArt">
<div class="bookentry">
<h2>#title#</h2>
<p>
<span class="description">#bookdescription#</span>
</p>
</div>
</cfoutput>
You can run this demo here. What's surprising is how darn fast this runs. Sure I'm translating a very small amount of text, but still, it works really nice!
Now let's kick it up a notch. The first problem I want to solve is the duplication of code to handle French and Spanish translations. There are probably multiple ways to do this, but for my solution, I simply provided multiple classes to my links:
$(".bookentry").each(function() {
var s = "<div class=\"translationarea\">"
s += "<p><a href=\"\" class=\"translationlink es\">Spanish</a> | <a href=\"\" class=\"translationlink fr\">French</a></p>"
s += "<div class=\"translation\"></div>"
s += "</div>"
$(this).append(s)
})
As you can see, I've used one core style "translationlink" as well as a style name based on the language code. This means that I will need to fetch the type when working with the mouseover. Here is the modified version:
$(".translationlink").mouseover(function() {
var toLang = $(this).attr("class").split(" ")[1]
var translationarea = $(this).parent().parent().find(".translation")
translationarea.html("Loading translation...")
var text = $(this).parent().parent().parent().find(".description").text()
google.language.translate(text, "en", toLang, function(result) {
if (!result.error) {
translationarea.html(result.translation)
} else {
translationarea.html("Translation failed.")
}
})
})
The very first modification is to bind to just translationlink. This will then work for all my translations links. I grab the language by simply getting the class attribute and splitting it into an array. This version also adds a basic loading text value. Again, the API works so fast I'm not really sure this is necessary, but it doesn't hurt. The API call now uses the dynamic variable and really, that's it. I added support to let folks know if the translation fails as well. The CFML code is the exact same, so to see the JavaScript in it's entirety, simply view source on the demo here: http://www.coldfusionjedi.com/demos/translation/index3.cfm
Notice something different about the demo? It's got Chinese support as well. That took me all of two seconds. I just added:
| <a href=\"\" class=\"translationlink zh-CN\">Chinese</a>
to my script. You can find a complete list of supported languages here. Unfortunately, Klingon is not supported.
Oh - I also added one small change - I put a new div on top:
<div id="branding" style="float:right">Translation service: </div>
and added this to my initialize function:
google.language.getBranding('branding');
As you can guess, this handles adding branding to the page to give credit to Google. I don't mind when APIs ask for this, but I've got to say - building a function to make it brain-dead simply is a nice touch.
That's basically it. The API supports more than what I demonstrated here. For example, it can read text and make guesses to the language. This allows you to say, "I've got some text here, no idea of the language, but please try to translate it to English." The API can also tell you if the client supports the proper Unicode fonts for extended characters (like Chinese for example). Again, check the docs and the reference in particular for more information.
Archived Comments
Thanks Ray..Very useful for me
Great information. Great demo. Unfortunately I would never use a service to translate, and neither should any of you. I don't know how many of you out there speak other languages, but the translations won't make sense to your readers. It's so bad that even the most simple Spanish translation is incorrect.
Amateur softballs greatest - Softball amateur más grande
Amateur is not even Spanish, and you don't say "the biggest\largest" to mean greatest. Can you imagine translating 4 paragraphs about what your technology company does? The service can't even get 4 words right.
I tried it with Italian, even worse. My point is if you do multi-language sites, get a real translator. You might think "It's better than nothing" - trust me it's not.
API is cool though.
I think it comes down to setting expectations. I think if you set the expectation that the translation is automatic, then a reader would be warned ahead of time to expect some oddness, and therefore, imho, should be able to infer at least some meaning from the translation.
I couldn't get this to run in Firefox 3.5 (I turned NoScript and Firebug off as well). Any ideas?
Wierd, it works now! I cleared the erro console and BANG! Never mind...
@papichulo: I agree that working with a technical site or a professional one, you should use human translation. That's what we, at our office, had to do on our European real estate websites (translated into 17 languages), sending the texts, variables, menus to human translators and spending a lot of money and time with that, but the final result is trustable.
I also agree with Ray when setting the expectations. If your website is informative and officially in only one language, I don't see any harm allowing the customers to have a quick pick or a ball park idea of the content in his/her own language. Most of the cases, you can have a good idea of the content.
@Ray: Good job and thanks for your continued help to our community.
no ray, you can't set expectations. people naturally expect translated text not to be gibberish. machine translations are ok for single words but are completely retarded beyond that.
just lipstick on a pig, ray.
You definitely have more experience in this then I - but we will have to agree to disagree here.
sorry, no. machine translations are bad. period.
deathstar on its way to LA, examples will be made ;-)
No doubt that a human translator is the solution for professional multilanguage site, but in a situation where the site owner has to load a lot of content (immagine an e-commerce with thousands of articles varying very often), in many languages, surely he cannot always ask for a human translator, in term of costs and time.
i think the solution is to write content in the original language in a simple and linear way so 'helping' the Goggle, or similar other, transalator.
salvatore
Excel! Now, I can modify my engine of spam to include several languages! I am convinced that will be to help my customers to produce contents which are easier to include/understand.
@salvatore then don't post them. if the content is important & it needs to be translated then for heaven's sake get a real translator. machine translators are retarded. posting machine translated gibberish makes your site look retarded.
That's a really good demo Ray, it's tempting to use it. But I agree with those who express caution at using machine translation. Some translations can be disasterous. Spend some time translating native Spanish, Italian or Polish websites into English and you'll see what I mean.
Anyone who doesn't speak English will already have a way of translating to their native tongue, either a toolbar or a bookmark.
"Anyone who doesn't speak English will already have a way of translating to their native tongue, either a toolbar or a bookmark."
Which I presume would _also_ be machine based as well? ;)
Yes Ray, that's right. :-) Or a bookmark to a human translation bureau that charge $100 to have a page's worth of text translated within the hour. ;-) One of my sites provides important public notices so I've looked into translations and paying a bureau to do a rapid human translation. It's the only viable option to avoid embarrassing, misleading, or even offensive machine translations. Text that uses colloquialisms (figures of speech) are the most amusing (dangerous) when translated verbatim.
Your code has given me an idea of using mini country flag icons where you'd mouseover and the text would instantly Googlefy into the selected language. Temptingly cool, but dangerous.
geez nobody reads anything i write :-0
no, never use flags to indicate locale or language choices. they don't scale well, nor can they represent every language spoken in a given country (what flag would you use for es_US which has more of a market than en_CA or fr_CA combined?).
Good point, Paul. How about those split flag icons I've seen on some sites. e.g. half Canadian, half French. That illustrates Canadian French (or French Canadian!).
Otherwise wouldn't you just click on the native country that represents the language? e.g. France=French, Spain=Spanish. So if you are from Mexico you'd click on the Spanish flag. You're probably thinking these people will be offended to have to click on a flag that is not of their own country? But I think everyone accepts the origin of their language and the impracticality of displaying 100 flags?
Or is a drop down list of languages in their native spelling(Espanol, Francais, etc) the only way to do it to keep all visitors happy?
and which flag would you use for english? GB? AU? NZ? CA? somebody's coming away mad. no, flags aren't good for this sort of thing. ever use the postgres d/l page? all flags, can't figure out the next most likely mirror to d/l from because i don't know which flags are which. fail.
stealthily figure out the user's locale (geoLocation + browser settings) & use that but let them manually over ride that via select or whatever in their language (french in french, thai in thai, etc.).
You seriously think an Englishman will get upset if he see bathroom instead of watercloset? Or some other small spelling change? I've not heard of American's getting mad for cases like that. I don't think you are being very practical Paul.
Watercloset, Ray? Let me get my pocket watch out of my waistcoat to check the time. Ah, it's tea time! Crumpets and Earl Gray anyone? Splendid! I'll get nanny to bring it in. [rings bell] :-D
I'm afraid these days we use the less elegant term "toilet" or if we're trying to be polite we say "gents". Sometimes the slang "bog" is used. Us Brits aren't as charming as we used to be I'm afraid. Tourists be warned. ;-)
Hey, I'm being ask to enter "OJ" for the captcha. As I'm English shouldn't I instead be asked to key in the name of an equivalent English celebrity who's fallen from grace? ;-)
England has celebrities?
Yes, Madonna. :-)
we were talking about flags ray.
ozzies+poms+beers=knuckle sandwiches (well sometimes, seen once up close, heard both sides bragging about many times).
and if you're trying to say that small "spelling" or phrase changes aren't important, you're sometimes right (you're also sometimes wrong). but machine translators don't produce that level of "understanding", they produce gibberish. worst case, somebody dies ("this side towards enemy"), in other "better" cases, the website simply looks retarded.
me not practical? maybe, but i'm not the guy slapping lipstick on a pig ;-)
One thing that I'm struggling with is using
google.load("jquery", "1");
instead of
<script src="http://ajax.googleapis.com/..."></script>
To me, it seems that the Google documentation prefers google.load("jquery", "1"), but I don't suppose it matters either way.
Why are you struggling with it? I mean is it not working or do you not like the syntax?
I guess because Google's documentation seems to prefer that people use setOnLoadCallback() instead of $(document).ready(function(), which is different than every example I ever see in any book or website.
Ah yea. I wonder - if you run N google.load() commands, will they chain to one setOnLoadCallback? I'm not sure. But if it did - then their way would probably be preferable.
The other struggle is that Google has jQuery cached, but not jQuery UI (that I can tell).
funny i bet you thought you were making a little code example about how to work some jQuery magic into your app. How it became a UN issue boggles the mind.
Oh I forgot I clicked the machine translated "Ask Translator Abbey" first.... my bad.
Good demo (worked so simple and easy for me I used it in an app that did NO translation at all) Now THERE is some magic for ya's.
thnx
Oh one small comment.... your captchya must be broiken I CAN READ AND UNDERSTAND IT :)