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