Home | History | Annotate | Download | only in resources
      1 // Copyright 2014 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