PageRenderTime 65ms CodeModel.GetById 19ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 1ms

/translate.js

http://multi-language-translation.googlecode.com/
JavaScript | 899 lines | 534 code | 132 blank | 233 comment | 166 complexity | 49128e1f4f4a5cdc572e3a1a28f5ec75 MD5 | raw file
  1/* Multi-Language Translation Script:
  2*
  3*  Automatically translates a webpage (containing text in more than one language) into any single language
  4*  requested by the user. Particularly useful for sites involving a lot of user-generated content (which
  5*  might have originally been written in different languages) - e.g. blogs, forums, or other community sites. 
  6*  Harnesses the Google AJAX Language API to perform translation and language detection.
  7*
  8*  Version: 0.2.5 [2010-01-22]
  9*  Author: Patrick Hathway, OdinLab, University of Reading.
 10*  For Documentation and Updates: See http://code.google.com/p/multi-language-translation/
 11*  Licence: MIT License - see http://opensource.org/licenses/mit-license.php for full terms. Also check 
 12*  the script Documentation for more information, and for details of one important consideration.
 13*
 14*  Some minor configuration is required for this script to work on your website. Follow the instructions
 15*  below, or see the Documentation for full details and examples...   */
 16
 17/* 1. Specify the 'Base Language' for your website: */
 18
 19var GL_baseLang = "en";
 20
 21/* 2. Specify the IDs for all major regions (i.e. HTML elements) in your site that should have their 
 22*  contents translated. Note that an ID can only be used to identify a single element on a page (for 
 23*  elements which appear multiple times, use Class Names instead - see below). The script will translate
 24*  each element separately; any elements that cannot be found on a page will be ignored:   */
 25
 26var GL_classIds = [
 27	["navigation"],
 28	["left_sidebar", ""],
 29	["right_sidebar", "fr", true],
 30	["footer"]
 31];
 32
 33/* 3. Specify the Class Names of all major regions (i.e. HTML elements) in your site that should have their 
 34*  their contents translated. Multiple elements may appear on a page with the same class name; the script
 35*  will locate each occurrance (if present) and translate them separately:   */
 36
 37var GL_classNames = [
 38	["entry_baselang"],
 39	["entry_french", "fr"],
 40	["entry", "", true],
 41	["quotes", "de", true]
 42];
 43
 44
 45/* 4. Specify the default language for new visitors (leave blank to auto-detect using browser/system settings) */
 46
 47var GL_curLang = "";
 48
 49/* The rest of the Script should not be modified; unless you want to alter the functionality!
 50*  ------------------------------------------------------------------------------------------ */
 51
 52// Global variables:
 53var GL_srcContent;             // array containing all content on the page to be translated, split up into chunks
 54var GL_transContent = [];      // array containing all chunks which have been translated
 55var GL_chunksTotal = 0;        // total number of chunks to translate
 56var GL_curChunk;               // ID of current chunk being translated
 57var GL_errors;                 // array containing details of all errors that occur (if applicable)
 58var GL_errSrcTxt;              // array containing all source text that causes errors (if applicable)
 59var GL_miniTransItems = [];    // array containing element IDs/class names which contain minor items to translate
 60
 61google.load("language", "1");  // load v1 of google ajax language api
 62
 63// run when page loads
 64function initialise() {
 65	addTransMsgs();        // append elements (which will display translation status messages) to page
 66	buildChunkedContent(); // create a local 'cache' of chunked content to be translated
 67	getLangCookie();       // find out the current site language (if known)
 68	getLangs();            // display listbox containing all languages the site can be translated into
 69
 70	// if the original languages should be shown, end function.
 71	if(GL_curLang == "orig") {
 72		return;
 73	}
 74	
 75	// otherwise, start the translation
 76	startTranslation();
 77}
 78
 79// display listbox containing all languages the site can be translated into (so that one can be selected by user)
 80function getLangs() {
 81	// create listbox within script so that it won't appear if JavaScript is disabled
 82	document.getElementById("mlt_languagelist").innerHTML = '<select id="mlt_language" onchange="changeLang()"></select>' +
 83	'<div id="mlt_langattr"></div>';
 84	var langItems = document.getElementById("mlt_language").options;
 85
 86	// add option to top of listbox allowing user to view the site in its original (i.e. un-translated) languages
 87	langItems.add(new Option("Original Languages", "orig", true));
 88
 89	// loop through list of currently available languages and add each to listbox
 90	for(var lang in google.language.Languages) {
 91		// find out the 2 character Google language code
 92		var langCode = google.language.Languages[lang];
 93		// convert language string to title case
 94		var langString = lang.substr(0,1) + lang.substr(1).toLowerCase();
 95		// improve formatting of 'chinese_simplified' and 'chinese_traditional' language strings
 96		langString = langString.split("_").join(" - ");
 97
 98		// check that Google can translate content into the current language
 99		if ((google.language.isTranslatable(langCode)) && (langCode != "")) {
100			// if so, add to listbox and translate, so native speakers of that language can find it
101			langItems.add(new Option(langString, langCode));
102			miniTranslate(langItems[(langItems.length-1)],langCode);
103		}
104	}
105
106	document.getElementById("mlt_language").value = GL_curLang; // change listbox to display current site language
107	google.language.getBranding("mlt_langattr");  // display Google attribution below listbox (as required by TOS)
108}
109
110// start the translation
111function startTranslation() {
112	// reset global variables
113	GL_curChunk = 0; GL_errors = []; GL_errSrcTxt = [];
114	
115	// check if all content has already been translated into current language since page loaded.
116	if(chkLangTrans()) {
117		return; // if so, don't need to translate content again
118	} 
119	
120	// sanity check - can google actually translate into the current site language?
121	if (!(google.language.isTranslatable(GL_curLang)) || (GL_curLang == "")) {
122		return; // if not, end translation
123	} // Otherwise, send content to google to be translated:
124	
125	// show the 'Translating...' block
126	document.getElementById("mlt_translatemsg").innerHTML = 'Translating... <span id="mlt_perc">0%</span>';
127	miniTranslate(document.getElementById("mlt_translatemsg")); // translate block into site language
128	document.getElementById("mlt_translatemsg").style.display='block';
129
130	// find out the ID of the destination language
131	var curLangNo = document.getElementById("mlt_language").selectedIndex;
132	var k = 0;
133	
134	// create sub-array to hold all translations for current language
135	GL_transContent[GL_transContent.length]        = new Array(3);
136	GL_transContent[(GL_transContent.length-1)][0] = []; // this array will contain all translated chunks
137	GL_transContent[(GL_transContent.length-1)][1] = GL_curLang; // store current language
138	GL_transContent[(GL_transContent.length-1)][2] = false; // mark translation of language as incomplete
139
140	// loop through all the chunks to be translated, send to google...
141	for(var i = 0; i < GL_srcContent.length; i++) {
142		// find out the source language for the current element to be translated
143		var srcLang = GL_srcContent[i][GL_srcContent[i].length - 1];
144
145		for(var j = 0; j < (GL_srcContent[i].length - 1); j++) {
146			/* AJAX is used to return translated chunks, so they MAY be returned in any order.
147		   	To solve, include current chunk id, destination language + separators at start of each
148		   	string. This is a bit of a 'hack', so try to find a better solution! */
149			var srcText = curLangNo + "<br />" + k + "<br />" + GL_srcContent[i][j];
150
151			// send current chunk to google translate; when translated, call function translateChunk
152			google.language.translate(srcText, srcLang, GL_curLang, translateChunk);
153			k++;
154		}
155	}
156
157	setTimeout("checkTransStatus()", 1000); // run function to check translation status after 1 second
158}
159
160// check if page content has already been translated into current language since page loaded.
161function chkLangTrans() {
162	// loop through the list of languages the page has been translated into
163	for(var i = 0; i < GL_transContent.length; i++) {
164		// check if a translation for current language exists, and if so, whether it is complete.
165		if((GL_transContent[i][1] == GL_curLang) && (GL_transContent[i][2] == true)){
166			/* if so, don't need to send content to google again, just assemble the pre-translated content.
167			This will *dramatically* reduce the translation time for this language! */
168			endTranslation(i);
169			return true;
170		} 
171	}
172	return false; // otherwise, all relevant page content must be submitted to google for translation.
173}
174
175// This runs after each chunk has been translated...
176function translateChunk(result) {
177	// handle translation errors gracefully by storing error details but allowing translation to continue
178	if (result.error) {
179		GL_errors[GL_errors.length] = result.error.message;
180	}
181	else {
182
183	/* Translated chunks may be returned in any order, so we need a method to assemble the chunks
184	   in the correct order:
185	   * Start of each chunk has a destination language id and a chunk id followed by separators.
186	   * Separator is a <br /> HTML tag, as these always appear to retain position after translation...
187	   * The destination language id is used to ensure the translated text is now in the correct language.
188	   * Each chunk is stored in the position of the 'GL_transContent' array corresponding to the current chunk id.
189	   * The ids + separators should not be shown, and are discarded from the chunk when it's stored in array.
190	*/
191	
192		// split into array based on separator, so we know the id & destination language of translated chunk
193		var transChunk = result.translation.split("<br />");
194		// [find out length of substring to chop off from start of chunk]
195		var chunkSubStr = 12 + transChunk[0].length + transChunk[1].length;
196		// remove whitespace from chunk id & destination language (a problem with some translations)
197		transChunk[0] = transChunk[0].replace(/^\s+|\s+$/g,"");
198		transChunk[1] = transChunk[1].replace(/^\s+|\s+$/g,"");
199
200		/* If the translated chunk has a different destination language to the current site language, discard
201		translation & end function. This may happen if user changes language whilst translation is in progress */
202		if(transChunk[0] != document.getElementById("mlt_language").selectedIndex) {
203			return;
204		}
205
206		// store translated chunk in current language array, along with detected source language (if known)
207		GL_transContent[(GL_transContent.length-1)][0][transChunk[1]] = new Array(3);
208		GL_transContent[(GL_transContent.length-1)][0][transChunk[1]][0] = result.translation.substr(chunkSubStr);
209		GL_transContent[(GL_transContent.length-1)][0][transChunk[1]][1] = result.detectedSourceLanguage;
210	}
211
212	GL_curChunk++; // increment current chunk ID every time function is called
213
214	// calculate and display percentage of translation completed, based on number of chunks remaining
215	var perc = (GL_curChunk / GL_chunksTotal)*100;
216	document.getElementById("mlt_perc").innerHTML = Math.round(perc) + "%";
217
218	// when all chunks have been translated, run the 'endTranslation' function...
219	if(GL_curChunk >= GL_chunksTotal) {
220		endTranslation((GL_transContent.length-1)); // [final array element holds current language content!]
221	}
222}
223
224/* runs once a second whilst translation is in progress, updating contents of all elements that have already
225   been completely translated. Also shows 'timeout' messages if translation not completed within 30 seconds... */
226function checkTransStatus() {
227	// 'static' member variables to keep track of how many times function has run, as well as current language:
228	if((typeof checkTransStatus.chkStatusCount == 'undefined') || (checkTransStatus.curTransLang != GL_curLang)) {
229		// reset variables if not defined yet, or if site language has changed since last function call
230        	checkTransStatus.chkStatusCount = 0;
231		checkTransStatus.curTransLang = GL_curLang;
232	}
233
234	// if translation is now complete, end function.
235	if(GL_curChunk >= GL_chunksTotal) {
236		return;
237	}
238	
239	checkTransStatus.chkStatusCount++; // increment Translation Status counter
240
241	// Store the list of languages in case they are replaced during translation
242	var langList = document.getElementById("mlt_languagelist").innerHTML;
243
244	var curElement; var curTransChunk = 0; var curClass = 0; var curTransLang = GL_transContent.length-1;
245
246	// loop through all element IDs (as specified at script top), searching for ones with completed translations
247	for(var i = 0; i < GL_classIds.length; i++) {
248		curElement = document.getElementById(GL_classIds[i][0]); // find out current element
249
250		// if current element ID exists on the page, replace contents with translated chunks
251		if(curElement != null) {
252			// assemble and display translated chunks for current element
253			curTransChunk = unpackTransChunks(curTransLang,curTransChunk,curClass,curElement,false);
254			curClass++; // move to next element ID
255		}
256	}
257
258	// Get all elements on the page, so that ones with the specified class names can be located
259	var allElements = document.getElementsByTagName("*");
260	// loop through all element class names (as specified at script top), searching for completed translations
261	for(var i = 0; i < GL_classNames.length; i++) {
262		// loop through all elements, so that any with the current class name can be identified
263		for(var j = 0; j < allElements.length; j++) {
264			if(allElements[j].className == GL_classNames[i][0]) {
265				// assemble and display translated chunks for current element
266				curTransChunk = unpackTransChunks(curTransLang,curTransChunk,curClass,allElements[j],false);
267				curClass++; // move to next element Class Name
268			}
269		}
270	}
271
272	// if listbox containing all languages has been removed as a result of translation, display it again
273	if(document.getElementById("mlt_languagelist").innerHTML == "") {
274		document.getElementById("mlt_languagelist").innerHTML = langList;
275		document.getElementById("mlt_language").value = GL_curLang; // change listbox to show current language
276	}
277
278	/* if translation still isn't complete after 30 seconds, update translation message. Note: there is no
279	   need to halt the current translation completely, but we will allow user to restart it if desired! */
280	if(checkTransStatus.chkStatusCount == 30) {
281		// calculate and display percentage of translation completed, based on number of chunks remaining
282		var perc = Math.round((GL_curChunk / GL_chunksTotal)*100) + "%";
283		// update translation status message
284		document.getElementById("mlt_translatemsg").innerHTML = 'Translating (Slowly!)... ' +
285		'<span id="mlt_perc">' + perc + '</span> - <a href="javascript:refresh()">Retry</a>';
286		miniTranslate(document.getElementById("mlt_translatemsg")); // translate block into site language
287	}
288
289	setTimeout("checkTransStatus()", 1000); // check translation status again after another second
290}
291
292// runs after all translations have been completed; displaying the results on screen in place of previous content
293function endTranslation(curTransLang) {
294	var curElement;
295	var curTransChunk = 0; var curClass = 0;
296
297	// loop through all element IDs (as specified at script top), replacing current contents with translations
298	for(var i = 0; i < GL_classIds.length; i++) {
299		curElement = document.getElementById(GL_classIds[i][0]); // find out current element
300
301		// if current element ID exists on the page, replace contents with translated chunks
302		if(curElement != null) {
303			// assemble and display translated chunks for current element
304			curTransChunk = unpackTransChunks(curTransLang,curTransChunk,curClass,curElement,true,GL_classIds[i]);
305			curClass++; // move to next element ID
306		}
307	}
308
309	// Get all elements on the page, so that ones with the specified class names can be located
310	var allElements = document.getElementsByTagName("*");
311	// loop through all element class names (as specified at script top), so contents can be replaced by translations
312	for(var i = 0; i < GL_classNames.length; i++) {
313		// loop through all elements, so that any with the current class name can be identified
314		for(var j = 0; j < allElements.length; j++) {
315			if(allElements[j].className == GL_classNames[i][0]) {
316				// assemble and display translated chunks for current element
317				curTransChunk = unpackTransChunks(curTransLang,curTransChunk,curClass,allElements[j],true,GL_classNames[i]);
318				curClass++; // move to next element Class Name
319			}
320		}
321	}
322
323	// Perform translations of all content added by the script (e.g. language strings and error messages)
324	transAddedText();
325
326	// Re-create listbox containing all languages, in case it has been removed as a result of translation
327	getLangs();
328
329	// translate the 'Original Languages' listbox item to equivalent in current site language
330	miniTranslate(document.getElementById("mlt_language").options[0],GL_curLang,"Original Languages");
331	
332	// if any errors occured during translation, display warning message at top of screen and end function 
333	if(GL_errors.length != 0) {
334		document.getElementById("mlt_translatemsg").innerHTML = GL_errors.length + ' Problems(s) Occurred During Translation: ' +
335		'<a href="javascript:refresh()">Retry</a> - <a href="javascript:showErrDetails()">Details</a> - ' +
336		'<a href="javascript:hideTransMsg()">Hide</a>';
337		miniTranslate(document.getElementById("mlt_translatemsg")); // translate warning message into current site language
338		return;
339	}
340	
341	// mark translation of page into current language as complete, so that translation can be quicker next time.
342	GL_transContent[curTransLang][2] = true;
343
344	hideTransMsg(); // otherwise, hide 'Translating...' message
345}
346
347// assemble and display translated chunks for current element on page
348function unpackTransChunks(curTransLang,curTransChunk,curClassNum,curElement,transComplete,curClass) {
349	// 'static' member variable to keep track of how many times function has run
350	if(typeof unpackTransChunks.curLinkId == 'undefined') {
351		// reset curLinkId variable if not defined yet - it's used to identify the correct 'show source text' link
352        	unpackTransChunks.curLinkId = 0;
353	} unpackTransChunks.curLinkId++; var j = unpackTransChunks.curLinkId; // increment current link id, j is an alias
354
355	var curContent = ""; // reset translated contents of current element
356	var initTransChunk = curTransChunk; // store initial chunk id of current element
357
358	// loop through all translated chunks, assembling all translated content for current element
359	for(var i = 0; i < (GL_srcContent[curClassNum].length - 1); i++) {
360		// if current chunk does not exist, translation is not complete or errors occurred
361		if((GL_transContent[curTransLang][0][curTransChunk] == null) && (transComplete == false)) {
362			break; // stop looping through chunks if full translation has not yet been completed
363		} else if((GL_transContent[curTransLang][0][curTransChunk] == null) && (transComplete == true)) {
364			// if full translation IS complete, show warning message and a link to show source text (in red)
365			curContent += '<div style="color: red;" id="mlt_srctxt' + j + '">[<span class="mlt_transerrtxt">' +
366			'<em style="text-transform: uppercase;">THIS SECTION COULD NOT BE TRANSLATED</em> - '+
367			'<a id="mlt_srctxtlnk' + j + '" href="javascript:showSrcTxt(' + j + ',' + curClassNum + ',' + (i+1) + 
368			')">View Source Text [+]</a></span><span id="mlt_srctxtbracket' + j + '">]</span></div>';
369			GL_errSrcTxt[GL_errSrcTxt.length] = GL_srcContent[curClassNum][i]; // store affected source text
370			unpackTransChunks.curLinkId++; j = unpackTransChunks.curLinkId; // increment current link id		
371		} else {
372			// otherwise append current translated chunk to translated content string
373			curContent += GL_transContent[curTransLang][0][curTransChunk][0];
374		}
375		curTransChunk++; // move to next chunk
376	}
377
378	// if translation is complete, replace contents of current element with translated text
379	if(transComplete == true) {
380		curElement.innerHTML = curContent;
381		// append a language string to bottom of current element if desired
382		getLangString(curTransLang,curElement,curClass,initTransChunk,(GL_srcContent[curClassNum].length - 1),j,curClassNum);
383	// otherwise, check if all translated chunks for current element were assembled
384	} else if((i == (GL_srcContent[curClassNum].length - 1)) && (GL_transContent[curTransLang][0][initTransChunk][2] != true)) {			
385		// if so (and current element translation is not yet marked as complete), replace contents with translation
386		curElement.innerHTML = curContent;
387		GL_transContent[curTransLang][0][initTransChunk][2] = true; // mark current element translation as complete
388	}
389
390	return curTransChunk; // return current chunk id to calling function
391}
392
393// If required, find and append a string containing the Source Language to the bottom of the specified element
394function getLangString(curTransLang,curElement,curClass,curTransChunk,transChunksLen,j,curClassNum) {
395	
396	var detectedLang = "";
397
398	// if the current element does not need a language string appended, end function
399	if(curClass[2] != true) {
400		return;
401	}
402
403	// if source language for current element has already been manually specified, set this as detected language
404	if(curClass[1] != "") {
405		detectedLang = curClass[1];
406	}
407	// otherwise find out language based on the detected source languages for translated chunks in current element 
408	else {
409		detectedLang = findDetectedLang(curTransLang,curTransChunk,transChunksLen);
410	}
411
412	// if detected language is same as site language, there is no need to display language string, so end function
413	if(detectedLang == GL_curLang){
414			return;
415	}
416
417	var srcLangString = getFmtLangStr(detectedLang); // retrieve name of detected language as a formatted string 
418
419	// append language string to current element...
420	curElement.innerHTML += '<div style="color: green;" id="mlt_srctxt' + j + '">[<span class="mlt_langstring" ' +
421	'style="font-family: arial, sans-serif; font-size: 11px;"><em>Automatic translation from ' + srcLangString + 
422	': ' +  google.language.getBranding().innerHTML + '</em> - <a id="mlt_srctxtlnk' + j + '" href="javascript:showSrcTxt(' + 
423	j + ',' + curClassNum + ',0)">View Source Text [+]</a></span><span id="mlt_srctxtbracket' + j + '">]</span></div>';
424}
425
426// convert a google language code into a formatted string containing the language name
427function getFmtLangStr(lang) {
428	// loop through list of supported languages and retrieve names
429	for(var l in google.language.Languages) {
430		if(google.language.Languages[l] == lang) {
431			// convert language string into title case
432			var lang = l.substr(0,1) + l.substr(1).toLowerCase();
433			// improve formatting of 'chinese_simplified' and 'chinese_traditional' language strings
434			lang = lang.split("_").join(" - ");
435			break; // stop loop (don't need to continue searching for languages)
436		}
437	}
438	// if language cannot be found in list of supported languages, set language to 'unknown'
439	if(typeof lang == "undefined") {
440		lang = "An Unknown Language";
441	}
442	return lang; // return formatted language string
443}
444
445// find out the most common detected language for all translated chunks in current element, to appear in language string
446function findDetectedLang(curTransLang,curTransChunk,transChunksLen) {
447	var chunkLangs = []; langCounter = 0;
448
449	// loop through all translated chunks in current element
450	for(var i = 0; i < transChunksLen; i++) {
451		// bugfix: set detected language to 'undefined' if an error occurred during translation... 
452		if(GL_transContent[curTransLang][0][curTransChunk] == null) {
453			GL_transContent[curTransLang][0][curTransChunk] = new Array(3);
454			GL_transContent[curTransLang][0][curTransChunk][1] = "undefined";	
455		}
456		// loop through array of detected languages to see if current chunk has an existing detected language
457		for(var j = 0; j < chunkLangs.length; j++) {
458			if(chunkLangs[j][0] == GL_transContent[curTransLang][0][curTransChunk][1]) {
459				// if so, increment counter containing the total number of chunks with that language
460				chunkLangs[j][1]++;
461				break; // halt loop
462			}
463		}
464		// if current chunk has a language not yet detected, add it to end of detected languages array
465		if(j == chunkLangs.length) {
466			chunkLangs[j] = new Array(2);
467			chunkLangs[j][0] = GL_transContent[curTransLang][0][curTransChunk][1]; // name of detected language
468			chunkLangs[j][1] = 1; // total number of chunks with that language
469		}
470		curTransChunk++; // move to next chunk
471	}
472
473	/* once array has been created containing all detected languages in current element, loop through it
474	to find the most common detected language - language string should contain this */
475	for(i = 0; i < chunkLangs.length; i++) {
476		if(chunkLangs[i][1] > langCounter) {
477			/* if current language was detected more times than all previous languages, store language
478			and number of times detected - so that next language can be compared against this total */
479			var detectedLang = chunkLangs[i][0];
480			langCounter = chunkLangs[i][1];
481		}		
482	}
483	
484	return detectedLang; // return the most common detected language
485}
486
487// Auto-translate all text added previously by the script (including language strings and error messages)
488function transAddedText() {
489	var allElements = document.getElementsByTagName("*"); // get all elements on page, so relevant ones can be translated
490	var foundElements = [];
491
492	// loop through all elements to find any of class 'mlt_langstring' or 'mlt_transerrtxt' 
493	for(var i=0; i<allElements.length; i++) {
494		if((allElements[i].className == "mlt_langstring") || (allElements[i].className == "mlt_transerrtxt")) {
495			foundElements[foundElements.length] = allElements[i];
496		}
497	}
498
499	// translate all located elements (runs separately from above code to prevent infinite loop as items are translated)
500	for(i = 0; i < foundElements.length; i++) {
501		miniTranslate(foundElements[i]); // translate current element
502	}
503}
504
505/* this function runs before any translation takes place, and creates a local 'cache' of all content to be translated (in chunks)
506   - meaning the content can be submitted to Google more rapidly, and the resulting translations can then be sorted reliably */
507function buildChunkedContent() {
508	var curElement; GL_srcContent = []; var j = 0;
509
510	/* loop through all applicable element IDs (as specified at script top), converting the contents of each into chunks,
511	   and storing each chunk in an array */
512	for(var i = 0; i < GL_classIds.length; i++) {
513		curElement = document.getElementById(GL_classIds[i][0]); // find out current element
514		// check that element actually exists on the page - if not, ignore it
515		if(curElement != null) {
516			GL_srcContent[j] = createChunks(curElement); // separate the content of current element into chunks
517			GL_chunksTotal += GL_srcContent[j].length;   // store a running total of the number of chunks created
518
519			// if a Source Language is not specified for current element, store Base Language in array instead
520			if(GL_classIds[i].length == 1) {
521				GL_srcContent[j][GL_srcContent[j].length] = GL_baseLang;
522			} else {
523				// otherwise store the specified language (or a blank string if this is to be auto-detected)
524				GL_srcContent[j][GL_srcContent[j].length] = GL_classIds[i][1];
525			}
526			j++;
527		}
528	}
529
530	// get all elements on page, so that the ones specified can be located
531	var allElements = document.getElementsByTagName("*");
532	// loop through all applicable element IDs (as specified at script top), converting into chunks and storing in array
533	for(var i = 0; i < GL_classNames.length; i++) {
534		// find all elements which have the current class name
535		for(var k = 0; k < allElements.length; k++) { 
536			if(allElements[k].className == GL_classNames[i][0]) {
537				// separate the content of current element into chunks
538				GL_srcContent[j] = createChunks(allElements[k]);
539				// store a running total of the number of chunks created
540				GL_chunksTotal += GL_srcContent[j].length;
541
542				// if a Source Language is not specified for current element, store Base Language in array
543				if(GL_classNames[i].length == 1) {
544					GL_srcContent[j][GL_srcContent[j].length] = GL_baseLang;
545				} else {
546					// otherwise store specified language (or a blank string if auto-detected)
547					GL_srcContent[j][GL_srcContent[j].length] = GL_classNames[i][1];
548				}
549				j++;
550			}
551		}
552	}
553}
554
555/* Web browsers (and Google) limit the amount of text translated per request, so split contents of current element into chunks.
556   The aim is to achieve this whilst maintaining the translation integrity */
557function createChunks(srcContent) {	
558	var chars = 1000; // number of characters to allow per chunk.
559
560	/* We do NOT want the content inside any HTML tag to be split between chunks (as this could cause 
561	rendering problems), so create 2 arrays storing tags and textual content of current div separately */
562	var tags = srcContent.innerHTML.match(/<.*?>/g);
563	var text = srcContent.innerHTML.split(/<.*?>/g);
564	if(tags == null) {
565		tags = new Array();} // handle content containing no tags
566
567	var curchar = 0; var tagnum = 0; var textnum = 0; var contTotal = tags.length + text.length;
568	// create an array containing content to be chunked, with tags + text separately re-assembled (in order)
569	var content = new Array(contTotal);
570	// loop through content of current div
571	for(var i=0; i < contTotal; i++) {
572		// if current position in content is the start of a tag char, add appropriate tag to content array
573		if(srcContent.innerHTML.substr(curchar,1) == "<") {
574			content[i] = tags[tagnum];
575			curchar += tags[tagnum].length;
576			tagnum++;
577		// if it is a text string, add the corresponding text to the curent element in content array
578		} else {
579			content[i] = text[textnum];
580			curchar += text[textnum].length;
581			textnum++;
582		}
583	}
584
585	// Create the chunks - don't split either tags or words (to ensure as high translation quality as possible)
586	var chunks = []; var curChunk = 0;
587	chunks[0]="";
588	for (i = 0; i < contTotal; i++) {
589		// if next content item (tag or textual) won't exceed length of current chunk, add it to chunk
590		if((chunks[curChunk].length + content[i].length) < chars) {
591			chunks[curChunk] += content[i];
592		} else {
593			// otherwise, proceed to next chunk.
594			curChunk++;
595			// add the entire content item to this chunk if possible (to ensure better translation)
596			if(content[i].length < chars) {
597				chunks[curChunk] = content[i];
598			}
599			else {
600				// only split further in rare cases where text length exceeds maximum chunk size
601				var curPos = 0;
602				do {
603					// retrieve the maximum amount of text that can fit in this chunk
604					var subContent = content[i].substr(curPos,chars);
605					// find out how many words are in this substring
606					var words = subContent.split(" ");
607					// chop off final word, so words aren't split up between chunks
608					var lastWordPos = subContent.length - words[(words.length-1)].length;
609					if(subContent.length < chars) { // [bugfix: prevent word cut-off on final iteration]
610						lastWordPos = subContent.length}
611					chunks[curChunk] = subContent.substr(0,lastWordPos); // store in chunk
612					curChunk++; // remainder of text will go in next chunk
613					curPos+=lastWordPos; // store current position in text, for further processing
614				} while(subContent.length >= chars); // repeat until all this text has been chunked
615				curChunk--; // because there may still be space in the current chunk!
616			}
617		}
618	}		
619	
620	return chunks; // return chunked content to calling function...
621}
622
623// Append elements to page (will be used to display translation messages), and specify their css styles.
624function addTransMsgs() {
625	// add element to page which will contain translation messages
626	var transCont = document.createElement("div");
627	document.body.appendChild(transCont);
628	
629	// add element to container created above - this will display the translation messages
630	var translateMsg = document.createElement("div");
631	translateMsg.id = "mlt_translatemsg";
632	transCont.appendChild(translateMsg);
633
634	// define visual styles for translation messages displayed on screen
635	translateMsg.style.backgroundColor = "silver";
636	translateMsg.style.border = "3px green solid";
637	translateMsg.style.padding = "5px";
638	translateMsg.style.fontSize = "large";
639	translateMsg.style.fontWeight = "bold";
640	translateMsg.style.textAlign = "left";
641	translateMsg.style.color = "black";
642
643	// more translation message styles (these should not need editing!)
644	translateMsg.style.left = "-50%";
645	translateMsg.style.position = "relative";
646	translateMsg.style.display = "none";
647	
648	// define the styles for the container element
649	transCont.style.left = "50%";
650	transCont.style.zIndex = "1000";
651	if(!(/MSIE 6/i.test(navigator.userAgent))) {
652		// styles for all browsers except IE6
653		transCont.style.position = "fixed";
654		transCont.style.top = "0px";
655	}
656	else {
657		// styles for IE6
658		transCont.style.position = "absolute";
659		transCont.style.setExpression("top", "(i = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + 'px'");
660	}
661}
662
663// Hide 'Translating...' message (or translation error message) - runs at end of translation, or if user dismisses error message
664function hideTransMsg() {
665	document.getElementById("mlt_translatemsg").innerHTML = "";
666	document.getElementById("mlt_translatemsg").style.display='none';
667}
668
669// if the user clicks on the 'Details' link [after error(s) occurred], show details of all errors on screen
670function showErrDetails() {
671	// continue to show original error message (but remove 'Details' link)
672	document.getElementById("mlt_translatemsg").innerHTML = '<span id="mlt_transerrmsg1">' + GL_errors.length + 
673	' Problems(s) Occurred During Translation: ' + '<a href="javascript:refresh()">Retry</a> - ' +
674	'<a href="javascript:hideTransMsg()">Hide</a>... <br /><em>Error(s) Reported:</em></span>' +
675	'<ul style="margin: 0px; padding: 0px;" id="mlt_transerrlist"></ul>';
676
677	// Loop through array containing details of all errors, and show them as a list on screen
678	for(var i = 0; i < GL_errors.length; i++) {
679		document.getElementById("mlt_transerrlist").innerHTML += '<li style="margin-left: 15px;">' + GL_errors[i] + '.</li> ';	
680	}
681
682	/* create a textarea - this will show the original source text of all affected sections (as HTML) to aid debugging
683	   Note: because translations are returned using AJAX, the affected sections are not NECESSARILY in the same order as
684	   the errors reported (though in many cases, they will be!) */
685	document.getElementById("mlt_translatemsg").innerHTML += '<em id="mlt_transerrmsg2">Section(s) Affected:</em><br />' +
686	'<textarea id="mlt_errsrctxt" rows="8" readonly="readonly" style="width: 99%; background-color: silver; border: 1px gray solid;">' +
687	'</textarea>';
688
689	// Loop through Source Text array, appending to textarea.
690	for(var i = 0; i < GL_errSrcTxt.length; i++) {
691		document.getElementById("mlt_errsrctxt").value += "Affected Section " + (i+1) + ":\n" + GL_errSrcTxt[i] + "\n\n";
692	}
693
694	// Translate the entire contents of the Translation Errors box (with the exception of the text inside the textarea)
695	miniTranslate(document.getElementById("mlt_transerrmsg1")); miniTranslate(document.getElementById("mlt_transerrlist"));
696	miniTranslate(document.getElementById("mlt_transerrmsg2"));
697}
698
699// function runs when the user clicks on a link to view the original source text for a translated element / chunk
700function showSrcTxt(curLinkId,curClassNum,curChunkId) {
701	// if the current chunk should be shown only, display chunk on screen
702	if((curChunkId-1) != -1) {
703		document.getElementById("mlt_srctxt"+curLinkId).innerHTML += 
704		'<div id="mlt_srctxtcontent' + curLinkId + '">'+GL_srcContent[curClassNum][(curChunkId-1)]+']</div>';
705	} else {
706	// otherwise assemble all chunks for current element, and display on screen
707		var srcTxt = "";
708		for(var i = 0; i < (GL_srcContent[curClassNum].length - 1); i++) {
709			srcTxt +=  GL_srcContent[curClassNum][i];
710		}
711		document.getElementById("mlt_srctxt"+curLinkId).innerHTML += 
712		'<div id="mlt_srctxtcontent' + curLinkId + '">'+srcTxt+']</div>';
713	}	
714
715	// update Source Text link so that when clicked in future, the source text will be hidden instead...
716	srcTxtLnk = document.getElementById("mlt_srctxtlnk"+curLinkId);
717	srcTxtLnk.innerHTML = "Hide Source Text [-]";
718	srcTxtLnk.href = 'javascript:hideSrcTxt(' + curLinkId + ',' + curClassNum + ',' + curChunkId + ')';
719	miniTranslate(srcTxtLnk); // translate link text
720	document.getElementById("mlt_srctxtbracket"+curLinkId).innerHTML = ""; // hide bracket after link
721}
722
723// function runs when the user clicks on a link to hide the original source text for a translated element / chunk
724function hideSrcTxt(curLinkId,curClassNum,curChunkId) {
725	// remove the element containing the original source text
726	srcTxtContent = document.getElementById("mlt_srctxtcontent"+curLinkId);
727	document.getElementById("mlt_srctxt"+curLinkId).removeChild(srcTxtContent);
728
729	// update Source Text link so that when clicked in future, the source text will be shown instead...
730	srcTxtLnk = document.getElementById("mlt_srctxtlnk"+curLinkId);
731	srcTxtLnk.innerHTML = "View Source Text [+]";
732	srcTxtLnk.href = 'javascript:showSrcTxt(' + curLinkId + ',' + curClassNum + ',' + curChunkId + ')';
733	miniTranslate(srcTxtLnk); // translate link text
734	document.getElementById("mlt_srctxtbracket"+curLinkId).innerHTML = "]"; // show bracket after link
735}
736
737// translate small non crucial items of text into current site language
738function miniTranslate(item,destLang,srcTxt) {
739	if(typeof destLang == "undefined") {
740		var destLang = GL_curLang; // if the destLang argument was not specified, use the current site language
741	}
742	if(typeof srcTxt == "undefined") {
743		var srcTxt = item.innerHTML; // if the srcText argument was not specified, use the current item contents
744	}
745
746	// loop through translated items array to see if a translation for current text already exists since page loaded
747	for(var i = 0; i < GL_miniTransItems.length; i++) {
748		// check if source text and destination language match a translated item (and a completed translation exists for it)
749		if((GL_miniTransItems[i][1] == srcTxt) && (GL_miniTransItems[i][2] == destLang) && (GL_miniTransItems[i][3] != "")) {
750			item.innerHTML = GL_miniTransItems[i][3]; // if so, display existing translation in item
751			item.title = srcTxt.split(/<.*?>/g).join(""); // display English source text as a tooltip (strip HTML first)
752			return; // Don't need to send a translation request to Google, so end function.
753		}
754	}
755
756	// store item to be translated in global array for access by callback function
757	GL_miniTransItems[GL_miniTransItems.length]	   = new Array(4);
758	GL_miniTransItems[(GL_miniTransItems.length-1)][0] = item;
759	GL_miniTransItems[(GL_miniTransItems.length-1)][1] = srcTxt;
760	GL_miniTransItems[(GL_miniTransItems.length-1)][2] = destLang;
761	GL_miniTransItems[(GL_miniTransItems.length-1)][3] = "";
762
763	// create source text by adding current item ID to start of item text (ID will ensure correct item is translated)
764	srcTxt = (GL_miniTransItems.length-1) + "<br />" + srcTxt;
765
766	google.language.translate(srcTxt, "en", destLang, function(result) {
767        	if (!result.error) {
768			// split into array based on separator, so we know the id of translated item
769			var transChunk = result.translation.split("<br />");
770			// [find out length of substring to chop off from start of item]
771			var chunkSubStr = 6 + transChunk[0].length;
772			// remove whitespace from item id (a problem with some translations)
773			transChunk[0] = transChunk[0].replace(/^\s+|\s+$/g,"");
774
775			// display english source text of translated item (with HTML stripped) if user hovers mouse over it
776			var strippedSrcTxt = GL_miniTransItems[transChunk[0]][1].split(/<.*?>/g).join("");
777			GL_miniTransItems[transChunk[0]][0].title = "[" + strippedSrcTxt  + "]";
778
779			// store and display translation of specified item (stripping item ID from start of text)
780			GL_miniTransItems[transChunk[0]][3] = result.translation.substr(chunkSubStr);
781			GL_miniTransItems[transChunk[0]][0].innerHTML = GL_miniTransItems[transChunk[0]][3];
782        	}
783	});
784}
785
786// runs when the user changes the site language
787function changeLang(destLang) {
788	if(typeof destLang != "undefined") {
789		GL_curLang = destLang; // if the destLang argument was specified, set new site language to this
790		document.getElementById("mlt_language").value = GL_curLang; // change listbox to display new site language
791	} else {
792		// otherwise get the new language requested by user
793		GL_curLang = document.getElementById("mlt_language").value;
794	}
795
796	// store the requested language in a cookie
797	setLangCookie();
798	
799	// if the original site languages should be shown, refresh the page.
800	if(GL_curLang == "orig") {
801		refresh();
802		return;
803	}
804	
805	// otherwise, start the translation
806	startTranslation();
807}
808
809// find out the value of the language cookie (if it exists)
810function getLangCookie() {
811	var curStoredLang;
812	// retrieve a list of cookies for this page, split into array elements (separated by ; if multiple cookies)
813	var getCurLang=document.cookie.split(";");
814	// loop through all array elements (i.e. cookies) until one with name 'lang' is found.
815	for(var i=0; i < getCurLang.length; i++) {
816
817		// remove whitespace from cookies (a problem if there are multiple cookies)
818		var lngCookie = getCurLang[i].replace(/^\s+|\s+$/g,"");
819
820		if(lngCookie.indexOf("lang=") == 0) {
821			// find out value of lang
822			curStoredLang = lngCookie.substring(5);
823		}
824	}
825	
826	// if language cookie exists, set current site language to stored value
827	if(typeof curStoredLang != "undefined") {
828		GL_curLang = curStoredLang;
829	// otherwise, check if an initial language has been specified
830	} else if((GL_curLang == "") || (typeof GL_curLang == "undefined")) {
831		detectUserLang(); // if not, find out user's language based on their settings, and offer to translate into that language
832		GL_curLang = "orig"; // show site using original languages for now
833		return; // don't want to store language in cookie until one has been specified, so end function
834	}
835
836	setLangCookie(); // if valid cookie exists or initial language was specified, set/renew cookie (to store new expiry date)
837}
838
839// If a new user has not specified a language yet, offer to translate page into their current browser / system language
840function detectUserLang(){
841	/* detect user's current language (if they are using IE this will be based on the regional settings on their system; 
842	   otherwise it will be based on the default language of their current browser) */
843	var detectedLang = (navigator.language) ? navigator.language : navigator.userLanguage;
844	
845	// if language cannot be detected for some reason, end function.
846	if((typeof detectedLang == "undefined") || (detectedLang == ""))  {
847		return;
848	}
849	
850	// if language is regional dialect (e.g. "en-gb"), check last 2 letters are upper case - for google compatibility.
851	if(detectedLang.length == 5) {
852		detectedLang = detectedLang.substr(0,3) + detectedLang.substr(3).toUpperCase();
853	}
854	
855	// check that Google can translate content into detected language
856	if (!google.language.isTranslatable(detectedLang)) {
857		// if not, check if language is a regional dialect (e.g. "en-GB")
858		if(detectedLang.length == 5) {
859			// if so, Google may still be able to translate into a non-regional equivalent (e.g. "en")
860			detectedLang = detectedLang.substr(0,2);
861			if (!google.language.isTranslatable(detectedLang)) {
862				return; // google cannot translate into non-regional equivalent, so end function
863			}
864		// language is not a regional dialect, and google cannot translate into it, so end function
865		} else {
866			return;
867		}
868	}
869	
870	var detectedLangStr = getFmtLangStr(detectedLang); // retrieve name of detected language as a formatted string
871	
872	// display message asking user if they want to translate current page into detected language
873	document.getElementById("mlt_translatemsg").innerHTML =  'Translate this page into ' + detectedLangStr + '? ' +
874	'<a href="javascript:changeLang(\'' + detectedLang + '\')">Yes</a> | <a href="javascript:changeLang(\'orig\')">No</a>';
875	miniTranslate(document.getElementById("mlt_translatemsg"),detectedLang); // translate question into detected language
876	document.getElementById("mlt_translatemsg").style.display='block'; // show the question on screen
877}
878
879// create a cookie to store the current site language
880function setLangCookie() {
881	// expire previous cookie (if it exists)
882	document.cookie = "lang=; expires=-1; path=/";
883
884	// cookie will expire after 90 days.
885	var expdate = new Date();
886	expdate.setTime(expdate.getTime()+7776000000);
887
888	// create the cookie
889	document.cookie = "lang=" + GL_curLang + "; expires=" + expdate.toGMTString() + "; path=/";
890}
891
892// refresh the current page [runs if the Original Languages option was selected, or if requested by user after error(s)]
893function refresh() {
894	hideTransMsg();
895	window.location.reload(true);
896}
897
898// run the 'initialise' function once the page has loaded.
899google.setOnLoadCallback(initialise);