r89647 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r89646‎ | r89647 | r89648 >
Date:14:44, 7 June 2011
Author:jeroendedauw
Status:deferred
Tags:
Comment:
work on lt 1.1 with ms translation service supporrt
Modified paths:
  • /trunk/extensions/LiveTranslate/LiveTranslate.hooks.php (modified) (history)
  • /trunk/extensions/LiveTranslate/LiveTranslate.php (modified) (history)
  • /trunk/extensions/LiveTranslate/LiveTranslate_Settings.php (modified) (history)
  • /trunk/extensions/LiveTranslate/includes/LiveTranslate_Functions.php (modified) (history)
  • /trunk/extensions/LiveTranslate/includes/ext.livetranslate.js (modified) (history)
  • /trunk/extensions/LiveTranslate/includes/ext.lt.google.js (added) (history)
  • /trunk/extensions/LiveTranslate/includes/ext.lt.ms.js (added) (history)

Diff [purge]

Index: trunk/extensions/LiveTranslate/LiveTranslate.php
@@ -24,7 +24,7 @@
2525 die( 'Not an entry point.' );
2626 }
2727
28 -define( 'LiveTranslate_VERSION', '1.0.1' );
 28+define( 'LiveTranslate_VERSION', '1.1 alpha' );
2929
3030 $wgExtensionCredits['other'][] = array(
3131 'path' => __FILE__,
@@ -95,6 +95,18 @@
9696 'dependencies' => array(),
9797 'messages' => $egLTJSMessages
9898 );
 99+
 100+ $wgResourceModules['ext.lt.google'] = $moduleTemplate + array(
 101+ 'scripts' => array( 'includes/ext.lt.google.js' ),
 102+ 'dependencies' => array( 'ext.livetranslate' ),
 103+ 'messages' => array()
 104+ );
 105+
 106+ $wgResourceModules['ext.lt.ms'] = $moduleTemplate + array(
 107+ 'scripts' => array( 'includes/ext.lt.ms.js' ),
 108+ 'dependencies' => array( 'ext.livetranslate' ),
 109+ 'messages' => array()
 110+ );
99111 }
100112
101113 /**
@@ -106,6 +118,14 @@
107119 define( 'TMT_TMX', 1 );
108120 define( 'TMT_GCSV', 2 );
109121
 122+/**
 123+ * Enum for translation services.
 124+ *
 125+ * @since 1.1
 126+ */
 127+define( 'LTS_GOOGLE', 0 );
 128+define( 'LTS_MS', 1 );
 129+
110130 $egLiveTranslateMagicWords = array();
111131
112132 require_once 'LiveTranslate_Settings.php';
Index: trunk/extensions/LiveTranslate/LiveTranslate_Settings.php
@@ -33,6 +33,10 @@
3434 # TMT_LTF, TMT_TMX, TMT_GCSV
3535 $egLiveTranslateTMT = TMT_LTF;
3636
 37+# Translation service to use.
 38+# LTS_GOOGLE, LTS_MS
 39+$egLiveTranslateService = LTS_GOOGLE;
 40+
3741 # The namespaces that should show the translation control.
3842 $egLTNSWithTranslationControl = array(
3943 NS_MAIN => true,
@@ -58,3 +62,5 @@
5963
6064 # When true, this prevents storange of translations that are the same in the source and target language.
6165 $egLTRequireSignificance = false;
 66+
 67+$egLiveTranslateDebugJS = false;
Index: trunk/extensions/LiveTranslate/LiveTranslate.hooks.php
@@ -154,42 +154,39 @@
155155 protected static function displayTranslationControl( $currentLang ) {
156156 global $wgOut, $egGoogleApiKey;
157157
 158+ $divContents = htmlspecialchars( wfMsg( 'livetranslate-translate-to' ) ) .
 159+ ' ' .
 160+ LiveTranslateFunctions::getLanguageSelector( $currentLang ) .
 161+ ' ' .
 162+ Html::element(
 163+ 'button',
 164+ array( 'id' => 'livetranslatebutton' ),
 165+ wfMsg( 'livetranslate-button-translate' )
 166+ ) .
 167+ ' ' .
 168+ Html::element(
 169+ 'button',
 170+ array( 'id' => 'ltrevertbutton', 'style' => 'display:none' ),
 171+ wfMsg( 'livetranslate-button-revert' )
 172+ );
 173+
 174+ if ( $GLOBALS['egLiveTranslateService'] == LTS_GOOGLE ) {
 175+ $divContents .= '<br /><br /><div id="googlebranding" style="display:inline; float:right"></div>';
 176+ }
 177+
158178 $wgOut->addHTML(
159179 Html::rawElement(
160180 'div',
161181 array(
162182 'id' => 'livetranslatediv',
163183 'style' => 'display:inline; float:right',
164 - 'class' => 'notranslate'
 184+ 'class' => 'notranslate',
 185+ 'sourcelang' => $currentLang
165186 ),
166 - htmlspecialchars( wfMsg( 'livetranslate-translate-to' ) ) .
167 - '&#160;' .
168 - LiveTranslateFunctions::getLanguageSelector( $currentLang ) .
169 - '&#160;' .
170 - Html::element(
171 - 'button',
172 - array( 'id' => 'livetranslatebutton' ),
173 - wfMsg( 'livetranslate-button-translate' )
174 - ) .
175 - '&#160;' .
176 - Html::element(
177 - 'button',
178 - array( 'id' => 'ltrevertbutton', 'style' => 'display:none' ),
179 - wfMsg( 'livetranslate-button-revert' )
180 - )
181 - ) .
182 - '<br /><br /><div id="googlebranding" style="display:inline; float:right"></div>'
 187+ $divContents
 188+ )
183189 );
184190
185 - $wgOut->addScript(
186 - Html::linkedScript( 'https://www.google.com/jsapi?key=' . htmlspecialchars( $egGoogleApiKey ) ) .
187 - Html::inlineScript(
188 - 'google.load("language", "1");
189 - google.setOnLoadCallback(function(){google.language.getBranding("googlebranding");});' .
190 - 'var sourceLang = ' . FormatJson::encode( $currentLang ) . ';'
191 - )
192 - );
193 -
194191 LiveTranslateFunctions::loadJs();
195192 }
196193
@@ -350,9 +347,8 @@
351348 }
352349
353350 public static function onOutputPageParserOutput( $outputpage, $parseroutput ) {
354 - $magicWords = isset( $parseroutput->mLTMagicWords ) ? $parseroutput->mLTMagicWords : array();
355 -
356 - return true;
 351+ $magicWords = isset( $parseroutput->mLTMagicWords ) ? $parseroutput->mLTMagicWords : array();
 352+ return true;
357353 }
358354
359355 }
Index: trunk/extensions/LiveTranslate/includes/ext.lt.google.js
@@ -0,0 +1,175 @@
 2+google.load("language", "1");
 3+google.setOnLoadCallback(function(){google.language.getBranding("googlebranding");});
 4+
 5+( window.translationService = function( $ ) {
 6+
 7+ var self = this;
 8+
 9+ this.done = function( targetLang ){};
 10+
 11+ this.runningJobs = 0;
 12+
 13+ // This is to enable a hack to decode quotes.
 14+ this.textAreaElement = document.createElement( 'textarea' );
 15+
 16+ /**
 17+ * Determines a chunk to translate of an DOM elements contents and calls the Google Translate API.
 18+ * Then calls itself if there is any remaining work to be done.
 19+ *
 20+ * @param {array} untranslatedsentences
 21+ * @param {array} chunks
 22+ * @param {integer} currentMaxSize
 23+ * @param {string} sourceLang
 24+ * @param {string} targetLang
 25+ * @param {jQuery} element
 26+ */
 27+ this.translateChunk = function( untranslatedsentences, chunks, currentMaxSize, sourceLang, targetLang, element ) {
 28+ ltdebug( 'Google: Translating chunk' );
 29+ var remainingPart = false;
 30+ var partToUse = false;
 31+ var sentenceCount = 0;
 32+ var currentLength = 0;
 33+
 34+ // Find the scentances that can be put in the current chunk.
 35+ for ( i in untranslatedsentences ) {
 36+ sentenceCount++;
 37+
 38+ if ( currentLength + untranslatedsentences[i].length < currentMaxSize ) {
 39+ currentLength += untranslatedsentences[i].length;
 40+ }
 41+ else if ( untranslatedsentences[i].length > 0 ) {
 42+ if ( currentLength == 0 ) {
 43+ // If the first scentance is longer then the max chunk legth, split it.
 44+ partToUse = untranslatedsentences[i].substr( 0, currentMaxSize - currentLength );
 45+ remainingPart = untranslatedsentences[i].substr( currentMaxSize - currentLength );
 46+ }
 47+
 48+ break;
 49+ }
 50+ }
 51+
 52+ var chunk = '';
 53+
 54+ // Build the chunck.
 55+ for ( i = 0; i < sentenceCount; i++ ) {
 56+ var part = untranslatedsentences.shift();
 57+
 58+ if ( i != sentenceCount - 1 || partToUse === false ) {
 59+ chunk += part;
 60+ }
 61+ }
 62+
 63+ // If there is a remaining part, re-add it to the scentances to translate list.
 64+ if ( remainingPart !== false ) {
 65+ untranslatedsentences.unshift( remainingPart );
 66+ }
 67+
 68+ // If there is a partial scentance, add it to the chunk.
 69+ if ( partToUse !== false ) {
 70+ chunk += partToUse;
 71+ }
 72+
 73+ // If the lenght is 0, the element has been translated.
 74+ if ( chunk.length == 0 ) {
 75+ this.handleTranslationCompletion( targetLang );
 76+ return;
 77+ }
 78+
 79+ // Keep track of leading and tailing spaces, as they often get modified by the GT API.
 80+ var leadingSpace = chunk.substr( 0, 1 ) == ' ' ? ' ' : '';
 81+ var tailingSpace = ( chunk.length > 1 && chunk.substr( chunk.length - 1, 1 ) == ' ' ) ? ' ' : '';
 82+
 83+ google.language.translate(
 84+ jQuery.trim( chunk ), // Trim, so the result does not contain preceding or tailing spaces.
 85+ sourceLang,
 86+ targetLang,
 87+ function(result) {
 88+ ltdebug( 'Google: Translated chunk' );
 89+
 90+ if ( result.translation ) {
 91+ chunks.push( leadingSpace + result.translation + tailingSpace );
 92+ }
 93+ else {
 94+ // If the translation failed, keep the original text.
 95+ chunks.push( chunk );
 96+ }
 97+
 98+ if ( untranslatedsentences.length == 0 ) {
 99+ // If the current chunk was smaller then the max size, node translation is complete, so update text.
 100+ self.textAreaElement.innerHTML = chunks.join( '' ); // This is a hack to decode quotes.
 101+ element.replaceData( 0, element.length, self.textAreaElement.value );
 102+ self.handleTranslationCompletion( targetLang );
 103+ }
 104+ else {
 105+ // If there is more work to do, move on to the next chunk.
 106+ self.translateChunk( untranslatedsentences, chunks, currentMaxSize, sourceLang, targetLang, element );
 107+ }
 108+ }
 109+ );
 110+ }
 111+
 112+ /**
 113+ * Translates a single DOM element using Google Translate.
 114+ * Loops through child elements and recursivly calls itself to translate these.
 115+ *
 116+ * @param {jQuery} element
 117+ * @param {string} sourceLang
 118+ * @param {string} targetLang
 119+ */
 120+ this.translateElement = function( element, sourceLang, targetLang ) {
 121+ ltdebug( 'Google: Translating element' );
 122+ runningJobs++;
 123+
 124+ var maxChunkLength = 500;
 125+
 126+ element.contents().each( function() {
 127+ if ( this.nodeType == 3 && ( typeof this.data != undefined ) ) {
 128+ console.log( $.trim( this.data ) );
 129+ console.log( typeof $.trim( this.data ) );
 130+ }
 131+ ltdebug( 'Google: Element conent item' );
 132+
 133+ // If it's a text node, then translate it.
 134+ if ( this.nodeType == 3 && this.data != undefined && $.trim( this.data ).length > 0 ) {
 135+ ltdebug( 'Google: Found content node' );
 136+
 137+ runningJobs++;
 138+ self.translateChunk(
 139+ this.data.split( new RegExp( "(\\S.+?[.!?])(?=\\s+|$)", "gi" ) ),
 140+ [],
 141+ maxChunkLength,
 142+ sourceLang,
 143+ targetLang,
 144+ this
 145+ );
 146+ }
 147+ // If it's an html element, check to see if it should be ignored, and if not, apply function again.
 148+ else if ( $.inArray( $( this ).attr( 'id' ), [ 'siteSub', 'jump-to-nav' ] ) == -1
 149+ && !$( this ).hasClass( 'notranslate' ) && !$( this ).hasClass( 'printfooter' )
 150+ && $( this ).text().length > 0 ) {
 151+
 152+ ltdebug( 'Google: Found child node' );
 153+ self.translateElement( $( this ), sourceLang, targetLang );
 154+ }
 155+ else {
 156+ ltdebug( 'Google: Found ignore node' );
 157+ }
 158+ } );
 159+
 160+ this.handleTranslationCompletion( targetLang );
 161+ }
 162+
 163+ /**
 164+ * Should be called every time a DOM element has been translated.
 165+ * By use of the runningJobs var, completion of the translation process is detected,
 166+ * and further handled by this function.
 167+ *
 168+ * @param {string} targetLang
 169+ */
 170+ this.handleTranslationCompletion = function( targetLang ) {
 171+ if ( !--this.runningJobs ) {
 172+ this.done( targetLang );
 173+ }
 174+ }
 175+
 176+} )( jQuery );
\ No newline at end of file
Property changes on: trunk/extensions/LiveTranslate/includes/ext.lt.google.js
___________________________________________________________________
Added: svn:eol-style
1177 + native
Index: trunk/extensions/LiveTranslate/includes/ext.livetranslate.js
@@ -17,13 +17,14 @@
1818 */
1919 $.fn.replaceText=function(b,a,c){return this.each(function(){var f=this.firstChild,g,e,d=[];if(f){do{if(f.nodeType===3){g=f.nodeValue;e=g.replace(b,a);if(e!==g){if(!c&&/</.test(e)){$(f).before(e);d.push(f)}else{f.nodeValue=e}}}}while(f=f.nextSibling)}d.length&&$(d).remove()})}
2020
21 - var currentLang = window.sourceLang;
 21+ window.ltdebug = function( message ) {
 22+ if ( window.ltDebugMessages ) {
 23+ console.log( 'Live Translate: ' + message );
 24+ }
 25+ };
2226
23 - var runningJobs = 0;
 27+ var currentLang = $( '#livetranslatediv' ).attr( 'sourcelang' );
2428
25 - // This is to enable a hack to decode quotes.
26 - var textAreaElement = document.createElement( 'textarea' );
27 -
2829 // For the "show original" feature.
2930 var originalHtml = false;
3031
@@ -116,7 +117,7 @@
117118 var newLang = $( '#livetranslatelang' ).val();
118119
119120 if ( words.length == 0 ) {
120 - requestGoogleTranslate( currentLang, newLang );
 121+ initiateRemoteTranslating( currentLang, newLang );
121122 }
122123 else {
123124 $.getJSON(
@@ -132,7 +133,7 @@
133134 if ( data.translations ) {
134135 replaceSpecialWords( data.translations );
135136 }
136 - requestGoogleTranslate( currentLang, newLang );
 137+ initiateRemoteTranslating( currentLang, newLang );
137138 }
138139 );
139140 }
@@ -214,161 +215,23 @@
215216 }
216217
217218 /**
218 - * Initiates the Google Translate translation.
 219+ * Initiates the remote translation process.
219220 *
220221 * @param {string} sourceLang
221222 * @param {string} targetLang
222223 */
223 - function requestGoogleTranslate( sourceLang, targetLang ) {
224 - translateElement( $( '#bodyContent' ), sourceLang, targetLang );
 224+ function initiateRemoteTranslating( sourceLang, targetLang ) {
 225+ var translator = new translationService();
 226+ translator.done = handleTranslationCompletion;
 227+ ltdebug( 'Initiating remote translation' );
 228+ translator.translateElement( $( '#bodyContent' ), sourceLang, targetLang );
 229+ ltdebug( 'Remote translation completed' );
225230 }
226231
227 - /**
228 - * Translates a single DOM element using Google Translate.
229 - * Loops through child elements and recursivly calls itself to translate these.
230 - *
231 - * TODO: be smarter with the requests, and make sure they don't get broken up unecesarrily.
232 - *
233 - * @param {jQuery} element
234 - * @param {string} sourceLang
235 - * @param {string} targetLang
236 - */
237 - function translateElement( element, sourceLang, targetLang ) {
238 - runningJobs++;
239 -
240 - var maxChunkLength = 500;
241 -
242 - element.contents().each( function() {
243 - // If it's a text node, then translate it.
244 - if ( this.nodeType == 3 && this.data != undefined && jQuery.trim( this.data ).length > 0 ) {
245 - runningJobs++;
246 - translateChunk(
247 - this.data.split( new RegExp( "(\\S.+?[.!?])(?=\\s+|$)", "gi" ) ),
248 - [],
249 - maxChunkLength,
250 - sourceLang,
251 - targetLang,
252 - this
253 - );
254 - }
255 - // If it's an html element, check to see if it should be ignored, and if not, apply function again.
256 - else if ( $.inArray( $( this ).attr( 'id' ), [ 'siteSub', 'jump-to-nav' ] ) == -1
257 - && !$( this ).hasClass( 'notranslate' ) && !$( this ).hasClass( 'printfooter' )
258 - && $( this ).text().length > 0 ) {
259 -
260 - translateElement( $( this ), sourceLang, targetLang );
261 - }
262 - } );
263 -
264 - handleTranslationCompletion( targetLang );
265 - }
266 -
267 - /**
268 - * Determines a chunk to translate of an DOM elements contents and calls the Google Translate API.
269 - * Then calls itself if there is any remaining work to be done.
270 - *
271 - * @param {array} untranslatedsentences
272 - * @param {array} chunks
273 - * @param {integer} currentMaxSize
274 - * @param {string} sourceLang
275 - * @param {string} targetLang
276 - * @param {jQuery} element
277 - */
278 - function translateChunk( untranslatedsentences, chunks, currentMaxSize, sourceLang, targetLang, element ) {
279 - var remainingPart = false;
280 - var partToUse = false;
281 - var sentenceCount = 0;
282 - var currentLength = 0;
283 -
284 - // Find the scentances that can be put in the current chunk.
285 - for ( i in untranslatedsentences ) {
286 - sentenceCount++;
287 -
288 - if ( currentLength + untranslatedsentences[i].length < currentMaxSize ) {
289 - currentLength += untranslatedsentences[i].length;
290 - }
291 - else if ( untranslatedsentences[i].length > 0 ) {
292 - if ( currentLength == 0 ) {
293 - // If the first scentance is longer then the max chunk legth, split it.
294 - partToUse = untranslatedsentences[i].substr( 0, currentMaxSize - currentLength );
295 - remainingPart = untranslatedsentences[i].substr( currentMaxSize - currentLength );
296 - }
297 -
298 - break;
299 - }
300 - }
301 -
302 - var chunk = '';
303 -
304 - // Build the chunck.
305 - for ( i = 0; i < sentenceCount; i++ ) {
306 - var part = untranslatedsentences.shift();
307 -
308 - if ( i != sentenceCount - 1 || partToUse === false ) {
309 - chunk += part;
310 - }
311 - }
312 -
313 - // If there is a remaining part, re-add it to the scentances to translate list.
314 - if ( remainingPart !== false ) {
315 - untranslatedsentences.unshift( remainingPart );
316 - }
317 -
318 - // If there is a partial scentance, add it to the chunk.
319 - if ( partToUse !== false ) {
320 - chunk += partToUse;
321 - }
322 -
323 - // If the lenght is 0, the element has been translated.
324 - if ( chunk.length == 0 ) {
325 - handleTranslationCompletion( targetLang );
326 - return;
327 - }
328 -
329 - // Keep track of leading and tailing spaces, as they often get modified by the GT API.
330 - var leadingSpace = chunk.substr( 0, 1 ) == ' ' ? ' ' : '';
331 - var tailingSpace = ( chunk.length > 1 && chunk.substr( chunk.length - 1, 1 ) == ' ' ) ? ' ' : '';
332 -
333 - google.language.translate(
334 - jQuery.trim( chunk ), // Trim, so the result does not contain preceding or tailing spaces.
335 - sourceLang,
336 - targetLang,
337 - function(result) {
338 - if ( result.translation ) {
339 - chunks.push( leadingSpace + result.translation + tailingSpace );
340 - }
341 - else {
342 - // If the translation failed, keep the original text.
343 - chunks.push( chunk );
344 - }
345 -
346 - if ( untranslatedsentences.length == 0 ) {
347 - // If the current chunk was smaller then the max size, node translation is complete, so update text.
348 - textAreaElement.innerHTML = chunks.join( '' ); // This is a hack to decode quotes.
349 - element.replaceData( 0, element.length, textAreaElement.value );
350 - handleTranslationCompletion( targetLang );
351 - }
352 - else {
353 - // If there is more work to do, move on to the next chunk.
354 - translateChunk( untranslatedsentences, chunks, currentMaxSize, sourceLang, targetLang, element );
355 - }
356 - }
357 - );
358 - }
359 -
360 - /**
361 - * Should be called every time a DOM element has been translated.
362 - * By use of the runningJobs var, completion of the translation process is detected,
363 - * and further handled by this function.
364 - *
365 - * @param {string} targetLang
366 - */
367232 function handleTranslationCompletion( targetLang ) {
368 - if ( !--runningJobs ) {
369 - currentLang = targetLang;
370 - $( '#livetranslatebutton' ).attr( "disabled", false ).text( mediaWiki.msg( 'livetranslate-button-translate' ) );
371 - $( '#ltrevertbutton' ).css( 'display', 'inline' );
372 - }
 233+ currentLang = targetLang;
 234+ $( '#livetranslatebutton' ).attr( "disabled", false ).text( mediaWiki.msg( 'livetranslate-button-translate' ) );
 235+ $( '#ltrevertbutton' ).css( 'display', 'inline' );
373236 }
374237
375238 } ); })(jQuery);
\ No newline at end of file
Index: trunk/extensions/LiveTranslate/includes/ext.lt.ms.js
@@ -0,0 +1,3 @@
 2+/**
 3+ *
 4+ */
\ No newline at end of file
Property changes on: trunk/extensions/LiveTranslate/includes/ext.lt.ms.js
___________________________________________________________________
Added: svn:eol-style
15 + native
Index: trunk/extensions/LiveTranslate/includes/LiveTranslate_Functions.php
@@ -21,9 +21,30 @@
2222 public static function loadJs() {
2323 global $wgOut;
2424
 25+ $wgOut->addScript(
 26+ Html::inlineScript(
 27+ 'var ltDebugMessages = ' . FormatJson::encode( $GLOBALS['egLiveTranslateDebugJS'] ) . ';'
 28+ )
 29+ );
 30+
2531 // For backward compatibility with MW < 1.17.
2632 if ( is_callable( array( $wgOut, 'addModules' ) ) ) {
27 - $wgOut->addModules( 'ext.livetranslate' );
 33+ $modules = array( 'ext.livetranslate' );
 34+
 35+ switch( $GLOBALS['egLiveTranslateService'] ) {
 36+ case LTS_GOOGLE:
 37+ $modules[] = 'ext.lt.google';
 38+ $wgOut->addHeadItem(
 39+ 'ext.lt.google.jsapi',
 40+ Html::linkedScript( 'https://www.google.com/jsapi?key=' . htmlspecialchars( $GLOBALS['egGoogleApiKey'] ) )
 41+ );
 42+ break;
 43+ case LTS_MS:
 44+ $modules[] = 'ext.lt.ms';
 45+ break;
 46+ }
 47+
 48+ $wgOut->addModules( $modules );
2849 }
2950 else {
3051 global $egLiveTranslateScriptPath;
@@ -36,6 +57,26 @@
3758 'ext.livetranslate',
3859 Html::linkedScript( $egLiveTranslateScriptPath . '/includes/ext.livetranslate.js' )
3960 );
 61+
 62+ switch( $GLOBALS['egLiveTranslateService'] ) {
 63+ case LTS_GOOGLE:
 64+ $wgOut->addHeadItem(
 65+ 'ext.lt.google.jsapi',
 66+ Html::linkedScript( 'https://www.google.com/jsapi?key=' . htmlspecialchars( $GLOBALS['egGoogleApiKey'] ) )
 67+ );
 68+
 69+ $wgOut->addHeadItem(
 70+ 'ext.lt.google',
 71+ Html::linkedScript( $egLiveTranslateScriptPath . '/includes/ext.lt.google.js' )
 72+ );
 73+ break;
 74+ case LTS_MS:
 75+ $wgOut->addHeadItem(
 76+ 'ext.lt.ms',
 77+ Html::linkedScript( $egLiveTranslateScriptPath . '/includes/ext.lt.ms.js' )
 78+ );
 79+ break;
 80+ }
4081 }
4182 }
4283

Status & tagging log