1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // This code is used in conjunction with the Google Translate Element script. 6 // It is executed in an isolated world of a page to translate it from one 7 // language to another. 8 // It should be included in the page before the Translate Element script. 9 10 var cr = cr || {}; 11 12 /** 13 * An object to provide functions to interact with the Translate library. 14 * @type {object} 15 */ 16 cr.googleTranslate = (function() { 17 /** 18 * The Translate Element library's instance. 19 * @type {object} 20 */ 21 var lib; 22 23 /** 24 * A flag representing if the Translate Element library is initialized. 25 * @type {boolean} 26 */ 27 var libReady = false; 28 29 /** 30 * Error definitions for |errorCode|. See chrome/common/translate_errors.h 31 * to modify the definition. 32 * @const 33 */ 34 var ERROR = { 35 'NONE': 0, 36 'INITIALIZATION_ERROR': 2, 37 'UNSUPPORTED_LANGUAGE': 4, 38 'TRANSLATION_ERROR': 6, 39 'TRANSLATION_TIMEOUT': 7, 40 'UNEXPECTED_SCRIPT_ERROR': 8, 41 'BAD_ORIGIN': 9, 42 'SCRIPT_LOAD_ERROR': 10 43 }; 44 45 /** 46 * Error code map from te.dom.DomTranslator.Error to |errorCode|. 47 * See also go/dom_translator.js in google3. 48 * @const 49 */ 50 var TRANSLATE_ERROR_TO_ERROR_CODE_MAP = { 51 0: ERROR['NONE'], 52 1: ERROR['TRANSLATION_ERROR'], 53 2: ERROR['UNSUPPORTED_LANGUAGE'] 54 }; 55 56 /** 57 * An error code happened in translate.js and the Translate Element library. 58 */ 59 var errorCode = ERROR['NONE']; 60 61 /** 62 * A flag representing if the Translate Element has finished a translation. 63 * @type {boolean} 64 */ 65 var finished = false; 66 67 /** 68 * Counts how many times the checkLibReady function is called. The function 69 * is called in every 100 msec and counted up to 6. 70 * @type {number} 71 */ 72 var checkReadyCount = 0; 73 74 /** 75 * Time in msec when this script is injected. 76 * @type {number} 77 */ 78 var injectedTime = performance.now(); 79 80 /** 81 * Time in msec when the Translate Element library is loaded completely. 82 * @type {number} 83 */ 84 var loadedTime = 0.0; 85 86 /** 87 * Time in msec when the Translate Element library is initialized and ready 88 * for performing translation. 89 * @type {number} 90 */ 91 var readyTime = 0.0; 92 93 /** 94 * Time in msec when the Translate Element library starts a translation. 95 * @type {number} 96 */ 97 var startTime = 0.0; 98 99 /** 100 * Time in msec when the Translate Element library ends a translation. 101 * @type {number} 102 */ 103 var endTime = 0.0; 104 105 function checkLibReady() { 106 if (lib.isAvailable()) { 107 readyTime = performance.now(); 108 libReady = true; 109 return; 110 } 111 if (checkReadyCount++ > 5) { 112 errorCode = ERROR['TRANSLATION_TIMEOUT']; 113 return; 114 } 115 setTimeout(checkLibReady, 100); 116 } 117 118 function onTranslateProgress(progress, opt_finished, opt_error) { 119 finished = opt_finished; 120 // opt_error can be 'undefined'. 121 if (typeof opt_error == 'boolean' && opt_error) { 122 // TODO(toyoshim): Remove boolean case once a server is updated. 123 errorCode = ERROR['TRANSLATION_ERROR']; 124 // We failed to translate, restore so the page is in a consistent state. 125 lib.restore(); 126 } else if (typeof opt_error == 'number' && opt_error != 0) { 127 errorCode = TRANSLATE_ERROR_TO_ERROR_CODE_MAP[opt_error]; 128 lib.restore(); 129 } 130 if (finished) 131 endTime = performance.now(); 132 } 133 134 // Public API. 135 return { 136 /** 137 * Whether the library is ready. 138 * The translate function should only be called when |libReady| is true. 139 * @type {boolean} 140 */ 141 get libReady() { 142 return libReady; 143 }, 144 145 /** 146 * Whether the current translate has finished successfully. 147 * @type {boolean} 148 */ 149 get finished() { 150 return finished; 151 }, 152 153 /** 154 * Whether an error occured initializing the library of translating the 155 * page. 156 * @type {boolean} 157 */ 158 get error() { 159 return errorCode != ERROR['NONE']; 160 }, 161 162 /** 163 * Returns a number to represent error type. 164 * @type {number} 165 */ 166 get errorCode() { 167 return errorCode; 168 }, 169 170 /** 171 * The language the page translated was in. Is valid only after the page 172 * has been successfully translated and the original language specified to 173 * the translate function was 'auto'. Is empty otherwise. 174 * Some versions of Element library don't provide |getDetectedLanguage| 175 * function. In that case, this function returns 'und'. 176 * @type {boolean} 177 */ 178 get sourceLang() { 179 if (!libReady || !finished || errorCode != ERROR['NONE']) 180 return ''; 181 if (!lib.getDetectedLanguage) 182 return 'und'; // Defined as translate::kUnknownLanguageCode in C++. 183 return lib.getDetectedLanguage(); 184 }, 185 186 /** 187 * Time in msec from this script being injected to all server side scripts 188 * being loaded. 189 * @type {number} 190 */ 191 get loadTime() { 192 if (loadedTime == 0) 193 return 0; 194 return loadedTime - injectedTime; 195 }, 196 197 /** 198 * Time in msec from this script being injected to the Translate Element 199 * library being ready. 200 * @type {number} 201 */ 202 get readyTime() { 203 if (!libReady) 204 return 0; 205 return readyTime - injectedTime; 206 }, 207 208 /** 209 * Time in msec to perform translation. 210 * @type {number} 211 */ 212 get translationTime() { 213 if (!finished) 214 return 0; 215 return endTime - startTime; 216 }, 217 218 /** 219 * Translate the page contents. Note that the translation is asynchronous. 220 * You need to regularly check the state of |finished| and |errorCode| to 221 * know if the translation finished or if there was an error. 222 * @param {string} originalLang The language the page is in. 223 * @param {string} targetLang The language the page should be translated to. 224 * @return {boolean} False if the translate library was not ready, in which 225 * case the translation is not started. True otherwise. 226 */ 227 translate: function(originalLang, targetLang) { 228 finished = false; 229 errorCode = ERROR['NONE']; 230 if (!libReady) 231 return false; 232 startTime = performance.now(); 233 try { 234 lib.translatePage(originalLang, targetLang, onTranslateProgress); 235 } catch (err) { 236 console.error('Translate: ' + err); 237 errorCode = ERROR['UNEXPECTED_SCRIPT_ERROR']; 238 return false; 239 } 240 return true; 241 }, 242 243 /** 244 * Reverts the page contents to its original value, effectively reverting 245 * any performed translation. Does nothing if the page was not translated. 246 */ 247 revert: function() { 248 lib.restore(); 249 }, 250 251 /** 252 * Entry point called by the Translate Element once it has been injected in 253 * the page. 254 */ 255 onTranslateElementLoad: function() { 256 loadedTime = performance.now(); 257 try { 258 lib = google.translate.TranslateService({ 259 // translateApiKey is predefined by translate_script.cc. 260 'key': translateApiKey, 261 'useSecureConnection': true 262 }); 263 translateApiKey = undefined; 264 } catch (err) { 265 errorCode = ERROR['INITIALIZATION_ERROR']; 266 translateApiKey = undefined; 267 return; 268 } 269 // The TranslateService is not available immediately as it needs to start 270 // Flash. Let's wait until it is ready. 271 checkLibReady(); 272 }, 273 274 /** 275 * Entry point called by the Translate Element when it want to load an 276 * external CSS resource into the page. 277 * @param {string} url URL of an external CSS resource to load. 278 */ 279 onLoadCSS: function(url) { 280 var element = document.createElement('link'); 281 element.type = 'text/css'; 282 element.rel = 'stylesheet'; 283 element.charset = 'UTF-8'; 284 element.href = url; 285 document.head.appendChild(element); 286 }, 287 288 /** 289 * Entry point called by the Translate Element when it want to load and run 290 * an external JavaScript on the page. 291 * @param {string} url URL of an external JavaScript to load. 292 */ 293 onLoadJavascript: function(url) { 294 // securityOrigin is predefined by translate_script.cc. 295 if (url.indexOf(securityOrigin) != 0) { 296 console.error('Translate: ' + url + ' is not allowed to load.'); 297 errorCode = ERROR['BAD_ORIGIN']; 298 return; 299 } 300 var xhr = new XMLHttpRequest(); 301 xhr.open('GET', url, true); 302 xhr.onreadystatechange = function() { 303 if (this.readyState != this.DONE) 304 return; 305 if (this.status != 200) { 306 errorCode = ERROR['SCRIPT_LOAD_ERROR']; 307 return; 308 } 309 eval(this.responseText); 310 } 311 xhr.send(); 312 } 313 }; 314 })(); 315