r94052 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r94051‎ | r94052 | r94053 >
Date:01:59, 8 August 2011
Author:jeroendedauw
Status:deferred
Tags:
Comment:
rewrote translation control to jQuery plugin and implemented local tm asbtraction layer using LS and memory
Modified paths:
  • /trunk/extensions/LiveTranslate (modified) (history)
  • /trunk/extensions/LiveTranslate/LiveTranslate.hooks.php (modified) (history)
  • /trunk/extensions/LiveTranslate/LiveTranslate.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 (modified) (history)
  • /trunk/extensions/LiveTranslate/includes/ext.lt.ms.js (modified) (history)
  • /trunk/extensions/LiveTranslate/includes/ext.lt.tm.js (modified) (history)
  • /trunk/extensions/LiveTranslate/includes/jquery.liveTranslate.js (added) (history)
  • /trunk/extensions/LiveTranslate/includes/jquery.replaceText.js (added) (history)

Diff [purge]

Index: trunk/extensions/LiveTranslate/LiveTranslate.php
@@ -83,6 +83,9 @@
8484 'livetranslate-button-translate',
8585 'livetranslate-button-translating',
8686 'livetranslate-dictionary-error',
 87+ 'livetranslate-button-translate',
 88+ 'livetranslate-button-revert',
 89+ 'livetranslate-translate-to',
8790 );
8891
8992 // For backward compatibility with MW < 1.17.
@@ -95,10 +98,12 @@
9699
97100 $wgResourceModules['ext.livetranslate'] = $moduleTemplate + array(
98101 'scripts' => array(
 102+ 'includes/ext.livetranslate.js',
99103 'includes/ext.lt.tm.js',
100 - 'includes/ext.livetranslate.js'
 104+ 'includes/jquery.replaceText.js',
 105+ 'includes/jquery.liveTranslate.js'
101106 ),
102 - 'dependencies' => array( 'jquery' ),
 107+ 'dependencies' => array( 'jquery', 'jquery.ui.button' ),
103108 'messages' => $egLTJSMessages
104109 );
105110
Index: trunk/extensions/LiveTranslate/LiveTranslate.hooks.php
@@ -30,7 +30,6 @@
3131
3232 $currentLang = LiveTranslateFunctions::getCurrentLang( $title );
3333
34 - // FIXME: Hitting the db on every page load should be avoided
3534 if ( in_array( $title->getFullText(), LiveTranslateFunctions::getLocalMemoryNames() ) ) {
3635 self::displayDictionaryPage( $article, $title );
3736 $outputDone = true; // The translations themselves should not be shown.
@@ -154,24 +153,10 @@
155154 protected static function displayTranslationControl( $currentLang ) {
156155 global $wgOut;
157156
158 - $divContents = htmlspecialchars( wfMsg( 'livetranslate-translate-to' ) ) .
159 - '&#160;' .
160 - LiveTranslateFunctions::getLanguageSelector( $currentLang ) .
161 - '&#160;' .
162 - Html::element(
163 - 'button',
164 - array( 'id' => 'livetranslatebutton' ),
165 - wfMsg( 'livetranslate-button-translate' )
166 - ) .
167 - '&#160;' .
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; position:absolute; right: 0px"></div>';
 157+ $langs = array();
 158+
 159+ foreach ( LiveTranslateFunctions::getLanguages( $currentLang ) as $label => $code ) {
 160+ $langs[] = "$code|$label";
176161 }
177162
178163 $wgOut->addHTML(
@@ -179,11 +164,9 @@
180165 'div',
181166 array(
182167 'id' => 'livetranslatediv',
183 - 'style' => 'display:inline; float:right',
184 - 'class' => 'notranslate',
185 - 'sourcelang' => $currentLang
186 - ),
187 - $divContents
 168+ 'sourcelang' => $currentLang,
 169+ 'languages' => implode( '||', $langs )
 170+ )
188171 )
189172 );
190173
Index: trunk/extensions/LiveTranslate/includes/ext.lt.google.js
@@ -9,11 +9,11 @@
1010 google.load("language", "1");
1111 google.setOnLoadCallback(function(){google.language.getBranding("googlebranding");});
1212
13 -(function( $ ){ window.translationService = function() {
 13+(function( $, lt ){ window.translationService = function() {
1414
1515 var self = this;
1616
17 - this.done = function( targetLang ){};
 17+ this.done = function(){};
1818
1919 this.runningJobs = 0;
2020
@@ -32,7 +32,7 @@
3333 * @param {DOM element} element
3434 */
3535 this.translateChunk = function( untranslatedsentences, chunks, currentMaxSize, sourceLang, targetLang, element ) {
36 - ltdebug( 'Google: Translating chunk' );
 36+ lt.debug( 'Google: Translating chunk' );
3737 var remainingPart = false;
3838 var partToUse = false;
3939 var sentenceCount = 0;
@@ -79,7 +79,7 @@
8080
8181 // If the lenght is 0, the element has been translated.
8282 if ( chunk.length == 0 ) {
83 - this.handleTranslationCompletion( targetLang );
 83+ this.handleTranslationCompletion();
8484 return;
8585 }
8686
@@ -92,7 +92,7 @@
9393 sourceLang,
9494 targetLang,
9595 function(result) {
96 - ltdebug( 'Google: Translated chunk' );
 96+ lt.debug( 'Google: Translated chunk' );
9797
9898 if ( result.translation ) {
9999 chunks.push( leadingSpace + result.translation + tailingSpace );
@@ -106,7 +106,7 @@
107107 // If the current chunk was smaller then the max size, node translation is complete, so update text.
108108 self.textAreaElement.innerHTML = chunks.join( '' ); // This is a hack to decode quotes.
109109 element.replaceData( 0, element.length, self.textAreaElement.value );
110 - self.handleTranslationCompletion( targetLang );
 110+ self.handleTranslationCompletion();
111111 }
112112 else {
113113 // If there is more work to do, move on to the next chunk.
@@ -125,17 +125,17 @@
126126 * @param {string} targetLang
127127 */
128128 this.translateElement = function( element, sourceLang, targetLang ) {
129 - ltdebug( 'Google: Translating element' );
 129+ lt.debug( 'Google: Translating element' );
130130 this.runningJobs++;
131131
132132 var maxChunkLength = 500;
133133
134134 element.contents().each( function() {
135 - ltdebug( 'Google: Element conent item' );
 135+ lt.debug( 'Google: Element conent item' );
136136
137137 // If it's a text node, then translate it.
138138 if ( this.nodeType == 3 && typeof this.data === 'string' && $.trim( this.data ).length > 0 ) {
139 - ltdebug( 'Google: Found content node' );
 139+ lt.debug( 'Google: Found content node' );
140140
141141 self.runningJobs++;
142142 self.translateChunk(
@@ -153,29 +153,27 @@
154154 && !$( this ).hasClass( 'notranslate' ) && !$( this ).hasClass( 'printfooter' )
155155 && $( this ).text().length > 0 ) {
156156
157 - ltdebug( 'Google: Found child node' );
 157+ lt.debug( 'Google: Found child node' );
158158 self.translateElement( $( this ), sourceLang, targetLang );
159159 }
160160 else {
161 - ltdebug( 'Google: Found ignore node' );
 161+ lt.debug( 'Google: Found ignore node' );
162162 }
163163 } );
164164
165 - this.handleTranslationCompletion( targetLang );
 165+ this.handleTranslationCompletion();
166166 }
167167
168168 /**
169169 * Should be called every time a DOM element has been translated.
170170 * By use of the runningJobs var, completion of the translation process is detected,
171171 * and further handled by this function.
172 - *
173 - * @param {string} targetLang
174172 */
175 - this.handleTranslationCompletion = function( targetLang ) {
 173+ this.handleTranslationCompletion = function() {
176174 if ( !--this.runningJobs ) {
177 - ltdebug( 'Google: translation process done' );
178 - this.done( targetLang );
 175+ lt.debug( 'Google: translation process done' );
 176+ this.done();
179177 }
180178 }
181179
182 -}; })( jQuery );
\ No newline at end of file
 180+}; })( jQuery, window.liveTranslate );
\ No newline at end of file
Index: trunk/extensions/LiveTranslate/includes/jquery.liveTranslate.js
@@ -0,0 +1,182 @@
 2+/**
 3+ * JavasSript for the Live Translate extension.
 4+ * @see http://www.mediawiki.org/wiki/Extension:Live_Translate
 5+ *
 6+ * @licence GNU GPL v3 or later
 7+ * @author Jeroen De Dauw <jeroendedauw at gmail dot com>
 8+ */
 9+
 10+( function ( $, lt ) { $.fn.liveTranslate = function( options ) {
 11+
 12+ var _this = this;
 13+
 14+ this.doTranslations = function() {
 15+ _this.runningJobs = 2;
 16+
 17+ _this.doLocalTranslation( _this.completeTranslationProcess );
 18+ _this.doRemoteTranslation( _this.completeTranslationProcess );
 19+ };
 20+
 21+ this.findSpecialWords = function() {
 22+ var words = [];
 23+
 24+ $.each( $( "span.notranslate" ), function( i, v ) {
 25+ words.push( $.trim( $( v ).text() ) );
 26+ } );
 27+
 28+ return words;
 29+ };
 30+
 31+ this.doLocalTranslation = function( callback ) {
 32+ _this.memory.getTranslations(
 33+ {
 34+ source: _this.currentLang,
 35+ target: _this.select.val(),
 36+ words: _this.findSpecialWords()
 37+ },
 38+ function( translations ) {
 39+ $.each( $( "span.notranslate" ), function( i, v ) {
 40+ var currentText = $(v).text();
 41+ var trimmedText = $.trim( currentText );
 42+
 43+ if ( translations[trimmedText] ) {
 44+ $( v ).text( currentText.replace( trimmedText, translations[trimmedText] ) );
 45+ }
 46+ });
 47+
 48+ callback();
 49+ }
 50+ );
 51+ };
 52+
 53+ this.doRemoteTranslation = function( callback ) {
 54+ var translator = new window.translationService();
 55+ translator.done = callback;
 56+ lt.debug( 'Initiating remote translation' );
 57+ translator.translateElement( $( '#bodyContent' ), _this.currentLang, _this.select.val() );
 58+ };
 59+
 60+ this.completeTranslationProcess = function() {
 61+ if ( !--_this.runningJobs ) {
 62+ _this.translateButton.attr( "disabled", false ).text( lt.msg( 'livetranslate-button-translate' ) );
 63+ _this.select.attr( "disabled", false );
 64+ _this.revertButton.css( 'display', 'inline' );
 65+ }
 66+ };
 67+
 68+ /**
 69+ * Inserts notranslate spans around the words specified in the passed array in the page content.
 70+ *
 71+ * @param {Array} words
 72+ */
 73+ this.insertSpecialWords = function( words ) {
 74+ lt.debug( 'inserting special words' );
 75+
 76+ for ( i in words ) {
 77+ $( '#bodyContent *' ).replaceText(
 78+ new RegExp( "(\\W)*" + RegExp.escape( words[i] ) + "(\\W)*", "g" ),
 79+ function( str ) {
 80+ return '<span class="notranslate">' + str + '</span>';
 81+ }
 82+ );
 83+ }
 84+ };
 85+
 86+ this.obatinAndInsetSpecialWords = function( callback ) {
 87+ // TODO: only run at first translation
 88+ _this.memory.getSpecialWords( _this.currentLang, function( specialWords ) {
 89+ _this.specialWords = specialWords;
 90+ _this.insertSpecialWords( specialWords );
 91+
 92+ callback();
 93+ } );
 94+ };
 95+
 96+ this.setup = function() {
 97+ var defaults = {
 98+ languages: {},
 99+ sourcelang: 'en'
 100+ };
 101+
 102+ $.extend( options, defaults );
 103+
 104+ $.each( this.attr( 'languages' ).split( '||' ), function( i, lang ) {
 105+ var parts = lang.split( '|' );
 106+ options.languages[parts[0]] = parts[1];
 107+ } );
 108+
 109+ _this.currentLang = this.attr( 'sourcelang' );
 110+
 111+ // For the "show original" feature.
 112+ _this.originalHtml = false;
 113+
 114+ _this.textAreaElement = document.createElement( 'textarea' );
 115+
 116+ _this.uniqueId = Math.random().toString().substring( 2 );
 117+
 118+ _this.memory = lt.memory.singleton();
 119+
 120+ _this.runningJobs = 0;
 121+
 122+ _this.buildHtml();
 123+
 124+ _this.bindEvents();
 125+ };
 126+
 127+ this.buildHtml = function() {
 128+ _this.attr( {
 129+ style: 'display:inline; float:right',
 130+ } ).attr( 'class', 'notranslate' );
 131+
 132+ _this.html( lt.msg( 'livetranslate-translate-to' ) );
 133+
 134+ _this.select = $( '<select />' ).attr( {
 135+ id: 'ltselect' + _this.uniqueId,
 136+ } );
 137+
 138+ for ( langCode in options.languages ) {
 139+ _this.select.append( $( '<option />' ).attr( 'value', langCode ).text( options.languages[langCode] ) );
 140+ }
 141+
 142+ _this.translateButton = $( '<button />' ).attr( {
 143+ id: 'livetranslatebutton' + _this.uniqueId,
 144+ } ).text( lt.msg( 'livetranslate-button-translate' ) ); // .button()
 145+
 146+ _this.revertButton = $( '<button />' ).attr( {
 147+ id: 'ltrevertbutton' + _this.uniqueId,
 148+ style: 'display:none'
 149+ } ).text( lt.msg( 'livetranslate-button-revert' ) ); // .button()
 150+
 151+ _this.append( _this.select, _this.translateButton, _this.revertButton );
 152+ };
 153+
 154+ this.bindEvents = function() {
 155+ _this.translateButton.click( function() {
 156+ _this.originalHtml = $( '#bodyContent' ).html();
 157+
 158+ $( this ).attr( "disabled", true ).text( lt.msg( 'livetranslate-button-translating' ) );
 159+ _this.select.attr( "disabled", true );
 160+
 161+ _this.obatinAndInsetSpecialWords( _this.doTranslations );
 162+ } );
 163+
 164+ _this.revertButton.click( function() {
 165+ // Replace the body content wth it's original value.
 166+ // This un-binds the jQuery selectors and event handlers.
 167+ $( '#bodyContent' ).html( _this.originalHtml );
 168+
 169+ // Re-assing the controls.
 170+ _this.select = $( '#ltselect' + _this.uniqueId );
 171+ _this.translateButton = $( '#livetranslatebutton' + _this.uniqueId );
 172+ _this.revertButton = $( '#ltrevertbutton' + _this.uniqueId );
 173+
 174+ // Re-bind the events to the controls.
 175+ _this.bindEvents();
 176+ } )
 177+ };
 178+
 179+ this.setup();
 180+
 181+ return this;
 182+
 183+}; } )( jQuery, window.liveTranslate );
\ No newline at end of file
Property changes on: trunk/extensions/LiveTranslate/includes/jquery.liveTranslate.js
___________________________________________________________________
Added: svn:eol-style
1184 + native
Index: trunk/extensions/LiveTranslate/includes/ext.livetranslate.js
@@ -6,19 +6,9 @@
77 * @author Jeroen De Dauw <jeroendedauw at gmail dot com>
88 */
99
10 -(function($) { $( document ).ready( function() {
11 -
12 - /*
13 - * jQuery replaceText - v1.1 - 11/21/2009
14 - * http://benalman.com/projects/jquery-replacetext-plugin/
15 - *
16 - * Copyright (c) 2009 "Cowboy" Ben Alman
17 - * Dual licensed under the MIT and GPL licenses.
18 - * http://benalman.com/about/license/
19 - */
20 - $.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()})}
 10+window.liveTranslate = new( function() {
2111
22 - window.ltdebug = function( message ) {
 12+ this.debug = function( message ) {
2313 if ( window.ltDebugMessages ) {
2414 if ( typeof console === 'undefined' ) {
2515 document.title = 'Live Translate: ' + message;
@@ -27,21 +17,10 @@
2818 console.log( 'Live Translate: ' + message );
2919 }
3020 }
31 - };
 21+ }
3222
33 - var currentLang = $( '#livetranslatediv' ).attr( 'sourcelang' );
34 -
35 - // For the "show original" feature.
36 - var originalHtml = false;
37 -
38 - window.textAreaElement = document.createElement( 'textarea' );
39 -
40 - // Compatibility with pre-RL code.
41 - // Messages will have been loaded into wgPushMessages.
42 - if ( typeof mediaWiki === 'undefined' ) {
43 - mediaWiki = new Object();
44 -
45 - mediaWiki.msg = function() {
 23+ this.msg = function() {
 24+ if ( typeof mediaWiki === 'undefined' ) {
4625 message = window.wgLTEMessages[arguments[0]];
4726
4827 for ( var i = arguments.length - 1; i > 0; i-- ) {
@@ -50,196 +29,15 @@
5130
5231 return message;
5332 }
54 - }
55 -
56 - /**
57 - * Disables the translation button and then either kicks of insertion of
58 - * notranslate spans around special words, or when this already happened,
59 - * the actual translation process.
60 - */
61 - setupTranslationFeatures = function() {
62 - $( this ).attr( "disabled", true ).text( mediaWiki.msg( 'livetranslate-button-translating' ) );
63 -
64 - if ( originalHtml === false ) {
65 - obtainAndInsertTranslations( -1 );
66 - }
6733 else {
68 - initiateTranslating();
 34+ return mediaWiki.msg.apply( this, arguments );
6935 }
7036 }
7137
72 - /**
73 - * Queries a batch of special words in the source language, finds them in the page,
74 - * and wraps the into notranslate spans. If there are no more words, the translation
75 - * process is initaiated, otherwise the function calls itself again.
76 - */
77 - function obtainAndInsertTranslations( offset ) {
78 - var requestArgs = {
79 - 'action': 'query',
80 - 'format': 'json',
81 - 'list': 'livetranslate',
82 - 'ltlanguage': currentLang
83 - };
84 -
85 - if ( offset > 0 ) {
86 - requestArgs['ltcontinue'] = offset;
87 - }
88 -
89 - $.getJSON(
90 - wgScriptPath + '/api.php',
91 - requestArgs,
92 - function( data ) {
93 - if ( data.words ) {
94 - insertNoTranslateTags( data.words );
95 - }
96 - else if ( data.error && data.error.info ) {
97 - alert( data.error.info );
98 - }
99 - else {
100 - for ( i in data ) {
101 - alert( mediaWiki.msg( 'livetranslate-dictionary-error' ) );
102 - break;
103 - }
104 - }
105 -
106 - originalHtml = $( '#bodyContent' ).html();
107 -
108 - if ( data['query-continue'] ) {
109 - obtainAndInsertTranslations( data['query-continue'].livetranslate.ltcontinue );
110 - }
111 - else {
112 - initiateTranslating();
113 - }
114 - }
115 - );
116 - }
 38+} )();
 39+
 40+(function( $ ) { $( document ).ready( function() {
 41+
 42+ $( '#livetranslatediv' ).liveTranslate( {} );
11743
118 - /**
119 - * Initiates the translation process.
120 - * First all special words are found and send to the local API,
121 - * and then replaced by their translation in the response. Then
122 - * the Google Translate translation is initiated.
123 - */
124 - function initiateTranslating() {
125 - var words = getSpecialWords();
126 - var newLang = $( '#livetranslatelang' ).val();
127 -
128 - if ( words.length == 0 ) {
129 - initiateRemoteTranslating( currentLang, newLang );
130 - }
131 - else {
132 - $.getJSON(
133 - wgScriptPath + '/api.php',
134 - {
135 - 'action': 'livetranslate',
136 - 'format': 'json',
137 - 'from': currentLang,
138 - 'to': newLang,
139 - 'words': words.join( '|' )
140 - },
141 - function( data ) {
142 - if ( data.translations ) {
143 - replaceSpecialWords( data.translations );
144 - }
145 - initiateRemoteTranslating( currentLang, newLang );
146 - }
147 - );
148 - }
149 - }
150 -
151 - /**
152 - * Shows the original page content, simply by setting the html to a stored copy of the original.
153 - * Also re-binds the jQuery events, as they get lost when doing the html replace.
154 - */
155 - showOriginal = function() {
156 - currentLang = window.sourceLang;
157 - $( '#bodyContent' ).html( originalHtml );
158 - $( '#livetranslatebutton' ).attr( "disabled", false ).text( mediaWiki.msg( 'livetranslate-button-translate' ) );
159 - $( '#livetranslatebutton' ).click( setupTranslationFeatures );
160 - $( '#ltrevertbutton' ).click( showOriginal );
161 - }
162 -
163 - // Initial binding of the button click events.
164 - $( '#livetranslatebutton' ).click( setupTranslationFeatures );
165 - $( '#ltrevertbutton' ).click( showOriginal );
166 -
167 - /**
168 - * Regex text escaping function.
169 - * Borrowed from http://simonwillison.net/2006/Jan/20/escape/
170 - */
171 - RegExp.escape = function(text) {
172 - if (!arguments.callee.sRE) {
173 - var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\' ];
174 - arguments.callee.sRE = new RegExp( '(\\' + specials.join('|\\') + ')', 'g' );
175 - }
176 - return text.replace(arguments.callee.sRE, '\\$1');
177 - }
178 -
179 - /**
180 - * Inserts notranslate spans around the words specified in the passed array in the page content.
181 - *
182 - * @param {Array} words
183 - */
184 - function insertNoTranslateTags( words ) {
185 - for ( i in words ) {
186 - $( '#bodyContent *' ).replaceText(
187 - new RegExp( "(\\W)*" + RegExp.escape( words[i] ) + "(\\W)*", "g" ),
188 - function( str ) {
189 - return '<span class="notranslate">' + str + '</span>';
190 - }
191 - );
192 - }
193 - }
194 -
195 - /**
196 - * Finds the special words in the page contents by getting the contents of all
197 - * notranslate spans and pushing them onto an array.
198 - *
199 - * @returns {Array}
200 - */
201 - function getSpecialWords() {
202 - var words = [];
203 -
204 - $.each($( 'span.notranslate' ), function( i, v ) {
205 - words.push( $(v).text() );
206 - });
207 -
208 - return words;
209 - }
210 -
211 - /**
212 - * Replaced the special words in the page content by looping over them,
213 - * and checking if there is a matching translation in the provided object.
214 - *
215 - * @param {object} translations
216 - */
217 - function replaceSpecialWords( translations ) {
218 - $.each($("span.notranslate"), function(i,v) {
219 - var currentText = $(v).text();
220 - if ( translations[currentText] ) {
221 - $(v).text( translations[currentText] );
222 - }
223 - });
224 - }
225 -
226 - /**
227 - * Initiates the remote translation process.
228 - *
229 - * @param {string} sourceLang
230 - * @param {string} targetLang
231 - */
232 - function initiateRemoteTranslating( sourceLang, targetLang ) {
233 - var translator = new translationService();
234 - translator.done = handleTranslationCompletion;
235 - ltdebug( 'Initiating remote translation' );
236 - translator.translateElement( $( '#bodyContent' ), sourceLang, targetLang );
237 - }
238 -
239 - function handleTranslationCompletion( targetLang ) {
240 - ltdebug( 'Remote translation completed' );
241 - currentLang = targetLang;
242 - $( '#livetranslatebutton' ).attr( "disabled", false ).text( mediaWiki.msg( 'livetranslate-button-translate' ) );
243 - $( '#ltrevertbutton' ).css( 'display', 'inline' );
244 - }
245 -
246 -} ); })(jQuery);
\ No newline at end of file
 44+} ); })( jQuery );
Index: trunk/extensions/LiveTranslate/includes/ext.lt.ms.js
@@ -6,16 +6,15 @@
77 * @author Jeroen De Dauw <jeroendedauw at gmail dot com>
88 */
99
10 -(function( $ ){ window.translationService = function() {
 10+(function( $, lt ){ window.translationService = function() {
1111
1212 var self = this;
1313
14 - this.done = function( targetLang ){};
 14+ this.done = function(){};
1515
1616 this.runningJobs = 0;
1717 this.checkingForIdle = false;
1818 this.lastCompletion;
19 -// window.fooz = 0;
2019
2120 /**
2221 * Determines a chunk to translate of an DOM elements contents and calls the Microsoft Translate API.
@@ -29,7 +28,7 @@
3029 * @param {DOM element} element
3130 */
3231 this.translateChunk = function( untranslatedsentences, chunks, currentMaxSize, sourceLang, targetLang, element ) {
33 - ltdebug( 'MS: Translating chunk' );
 32+ lt.debug( 'MS: Translating chunk' );
3433 var remainingPart = false;
3534 var partToUse = false;
3635 var sentenceCount = 0;
@@ -76,7 +75,7 @@
7776
7877 // If the lenght is 0, the element has been translated.
7978 if ( chunk.length == 0 ) {
80 - this.handleTranslationCompletion( targetLang );
 79+ this.handleTranslationCompletion();
8180 return;
8281 }
8382
@@ -85,7 +84,7 @@
8685 var tailingSpace = ( chunk.length > 1 && chunk.substr( chunk.length - 1, 1 ) == ' ' ) ? ' ' : '';
8786
8887 var chunckTranslationDone = function( translation ) {
89 - ltdebug( 'MS: Translated chunk' );
 88+ lt.debug( 'MS: Translated chunk' );
9089
9190 if ( translation ) {
9291 chunks.push( leadingSpace + translation + tailingSpace );
@@ -100,8 +99,8 @@
101100 window.textAreaElement.innerHTML = chunks.join( '' ); // This is a hack to decode quotes.
102101 element.replaceData( 0, element.length, window.textAreaElement.value );
103102
104 - ltdebug( 'MS: Translated element' );
105 - self.handleTranslationCompletion( targetLang );
 103+ lt.debug( 'MS: Translated element' );
 104+ self.handleTranslationCompletion();
106105 }
107106 else {
108107 // If there is more work to do, move on to the next chunk.
@@ -130,17 +129,17 @@
131130 * @param {string} targetLang
132131 */
133132 this.translateElement = function( element, sourceLang, targetLang ) {
134 - ltdebug( 'MS: Translating element' );
 133+ lt.debug( 'MS: Translating element' );
135134 this.runningJobs++;
136135
137136 var maxChunkLength = 500;
138137
139138 element.contents().each( function() {
140 - ltdebug( 'MS: Element conent item' );
 139+ lt.debug( 'MS: Element conent item' );
141140
142141 // If it's a text node, then translate it.
143142 if ( this.nodeType == 3 && typeof this.data === 'string' && $.trim( this.data ).length > 0 ) {
144 - ltdebug( 'MS: Found content node' );
 143+ lt.debug( 'MS: Found content node' );
145144
146145 self.runningJobs++;
147146
@@ -170,28 +169,28 @@
171170 && !$( this ).hasClass( 'notranslate' ) && !$( this ).hasClass( 'printfooter' )
172171 && $( this ).text().length > 0 ) {
173172
174 - ltdebug( 'MS: Found child node' );
 173+ lt.debug( 'MS: Found child node' );
175174 self.translateElement( $( this ), sourceLang, targetLang );
176175 }
177176 else {
178 - ltdebug( 'MS: Found ignore node' );
 177+ lt.debug( 'MS: Found ignore node' );
179178 }
180179 } );
181180
182 - this.handleTranslationCompletion( targetLang );
 181+ this.handleTranslationCompletion();
183182 }
184183
185 - this.invokeDone = function( targetLang ) {
186 - ltdebug( 'MS: translation process done' );
187 - ltdebug( this.runningJobs );
 184+ this.invokeDone = function() {
 185+ lt.debug( 'MS: translation process done' );
 186+ lt.debug( this.runningJobs );
188187 this.runningJobs = 0;
189 - this.done( targetLang );
 188+ this.done();
190189 }
191190
192191 this.checkForIdleness = function( targetLang, hits ) {
193 - ltdebug( 'MS: checkForIdleness' );
194 - ltdebug( 'MS: last + 250: ' + ( this.lastCompletion + 250 ) );
195 - ltdebug( 'MS: now: ' + (new Date()).getTime() );
 192+ lt.debug( 'MS: checkForIdleness' );
 193+ lt.debug( 'MS: last + 250: ' + ( this.lastCompletion + 250 ) );
 194+ lt.debug( 'MS: now: ' + (new Date()).getTime() );
196195
197196 if ( this.lastCompletion + 250 < (new Date()).getTime() ) {
198197 hits++;
@@ -212,10 +211,8 @@
213212 * Should be called every time a DOM element has been translated.
214213 * By use of the runningJobs var, completion of the translation process is detected,
215214 * and further handled by this function.
216 - *
217 - * @param {string} targetLang
218215 */
219 - this.handleTranslationCompletion = function( targetLang ) {
 216+ this.handleTranslationCompletion = function() {
220217 if ( !this.checkingForIdle && this.runningJobs > 1 && this.runningJobs < 20 ) {
221218 this.checkingForIdle = true;
222219 setTimeout( function() { self.checkForIdleness( targetLang, 0 ); }, 250 );
@@ -226,9 +223,9 @@
227224 }
228225
229226 if ( !--this.runningJobs ) {
230 - this.invokeDone( targetLang );
 227+ this.invokeDone();
231228 }
232229 }
233230
234231
235 -}; })( jQuery );
\ No newline at end of file
 232+}; })( jQuery, window.liveTranslate );
\ No newline at end of file
Index: trunk/extensions/LiveTranslate/includes/ext.lt.tm.js
@@ -4,131 +4,377 @@
55 *
66 * @licence GNU GPL v3 or later
77 * @author Jeroen De Dauw <jeroendedauw at gmail dot com>
 8+ *
 9+ * This file holds the lt.memory class.
810 */
911
10 -window.liveTranslate = new Object();
11 -
12 -( function( mw, lt ) {
 12+( function( $, lt ) {
1313
 14+ /**
 15+ * An lt.memory acts as abstraction layer via which special words
 16+ * (words for which a translation is specified in the wiki) and
 17+ * translations of those special words can be accessed. It takes
 18+ * care of obtaining these via the API and utilizing caching in
 19+ * memory and LocalStorage where appropriate and possible.
 20+ *
 21+ * @constructor
 22+ * @since 1.2
 23+ */
1424 lt.memory = function( options ) {
15 - var _this = this;
 25+ // Words to not translate using the translation services.
 26+ // { en: [foo, bar, baz], nl: [ ... ] }
 27+ this.words = {};
1628
17 - this.translations = false;
 29+ // List of translations.
 30+ // { en: { nl: { foo_en: foo_nl }, de: { bar_en: bar_de } }, nl: { ... } }
 31+ this.translations = {};
 32+
 33+ this.options = {
 34+ lsprefix: 'mw_lt_'
 35+ };
 36+
 37+ this.cleanedLS = false;
 38+
 39+ $.extend( this.options, options );
1840 };
1941
 42+ lt.memory.singleton = function() {
 43+ if ( typeof lt.memoryinstance === 'undefined' ) {
 44+ lt.memoryinstance = new lt.memory();
 45+ }
 46+
 47+ return lt.memoryinstance;
 48+ }
 49+
2050 lt.memory.prototype = {
21 -
 51+
 52+ /**
 53+ * Returns if LocalStorage can be used or not.
 54+ *
 55+ * @protected
 56+ * @since 1.2
 57+ *
 58+ * @return {boolean}
 59+ */
2260 canUseLocalStorage: function() {
2361 try {
2462 return 'localStorage' in window && window['localStorage'] !== null;
2563 } catch ( e ) {
2664 return false;
2765 }
28 - }
29 -
30 - hasLocalStorage: function() {
31 - if ( !_this.canUseLocalStorage() ) {
32 - return false;
33 - }
 66+ },
 67+
 68+ hasLocalStorage: function( itemName ) {
 69+ return localStorage.getItem( this.options.lsprefix + itemName ) !== null;
 70+ },
 71+
 72+ obtainFromLS: function( itemName ) {
 73+ return JSON.parse( localStorage.getItem( this.options.lsprefix + itemName ) );
 74+ },
 75+
 76+ writeToLS: function( itemName, object ) {
 77+ localStorage.setItem( this.options.lsprefix + itemName, JSON.stringify( object ) )
 78+ },
 79+
 80+ removeFromLS: function( itemName ) {
 81+ lt.debug( 'tm: removing item from LS: ' + this.options.lsprefix + itemName );
 82+ localStorage.removeItem( this.options.lsprefix + itemName );
 83+ },
 84+
 85+ getMemoryHashes: function( args, callback ) {
 86+ var defaults = {
 87+ apiPath: window.wgScriptPath
 88+ };
3489
35 - var memory = localStorage.getItem( 'lt_memory' );
36 - debugger;
37 - return memory;
38 - }
39 -
40 - getMemoryHashes: function( callback ) {
 90+ args = $.extend( {}, defaults, args );
 91+
4192 $.getJSON(
42 - wgScriptPath + '/api.php',
 93+ args.apiPath + '/api.php',
4394 {
44 - 'action': 'translationmemories',
 95+ 'action': 'query',
 96+ 'list': 'translationmemories',
4597 'format': 'json',
46 - 'props': 'version_hash'
 98+ 'qtmprops': 'version_hash'
4799 },
48100 function( data ) {
49101 if ( data.memories ) {
50102 callback( data.memories );
51103 }
52104 else {
 105+ lt.debug( 'tm: failed to fetch memory hash' );
53106 // TODO
54107 }
55108 }
56 - );
57 - }
 109+ );
 110+ },
58111
59 - localStorageIsValid: function() {
60 - if ( !_this.hasLocalStorage() ) {
 112+ hashesMatch: function( a, b ) {
 113+ if ( a === null || b === null ) {
61114 return false;
62115 }
63116
64 - _this.getMemoryHashes();
65 - }
 117+ for ( i in a ) {
 118+ if ( b[i] ) {
 119+ if ( a[i].memory_version_hash !== b[i].memory_version_hash ) {
 120+ return false;
 121+ }
 122+ }
 123+ else {
 124+ return false;
 125+ }
 126+ }
 127+
 128+ for ( i in b ) {
 129+ if ( !a[i] ) {
 130+ return false;
 131+ }
 132+ }
 133+
 134+ return true;
 135+ },
66136
67 - obtainTranslationsFromServer: function() {
 137+ cleanLocalStorage: function( options, callback ) {
 138+ options = $.extend( {}, { forceCheck: false }, options );
 139+
 140+ if ( this.cleanedLS && !options.forceCheck ) {
 141+ callback();
 142+ }
 143+ else {
 144+ var _this = this;
 145+ lt.debug( 'tm: getting memory hashes' );
 146+
 147+ this.getMemoryHashes(
 148+ {},
 149+ function( memories ) {
 150+ if ( _this.hashesMatch( _this.obtainFromLS( 'hash' ), memories ) ) {
 151+ lt.debug( 'tm: memory hashes obtained: match' );
 152+ }
 153+ else {
 154+ _this.removeFromLS( 'words' );
 155+ _this.removeFromLS( 'translations' );
 156+ _this.writeToLS( 'hash', memories );
 157+ lt.debug( 'tm: memory hashes obtained: no match; LS cleared' );
 158+ }
 159+
 160+ _this.cleanedLS = true;
 161+ callback();
 162+ }
 163+ );
 164+ }
 165+ },
 166+
 167+ obtainTranslationsFromServer: function( args, callback ) {
 168+ var defaults = {
 169+ offset: -1,
 170+ words: [],
 171+ language: 'en',
 172+ apiPath: window.wgScriptPath
 173+ };
 174+
 175+ args = $.extend( {}, defaults, args );
 176+
 177+ lt.debug( 'tm: obtaining translations from server' );
 178+
 179+ $.getJSON(
 180+ args.apiPath + '/api.php',
 181+ {
 182+ 'action': 'livetranslate',
 183+ 'format': 'json',
 184+ 'from': args.source,
 185+ 'to': args.target,
 186+ 'words': args.words.join( '|' )
 187+ },
 188+ function( data ) {
 189+ if ( data.translations ) {
 190+ lt.debug( 'tm: obtained translations from server' );
 191+ callback( data.translations );
 192+ }
 193+ else {
 194+ // TODO
 195+ }
 196+ }
 197+ );
 198+ },
 199+
 200+ obtainWordsFromServer: function( args, callback ) {
 201+ var _this = this;
 202+
 203+ var defaults = {
 204+ offset: -1,
 205+ allWords: [],
 206+ language: 'en',
 207+ apiPath: window.wgScriptPath
 208+ };
 209+
 210+ args = $.extend( {}, defaults, args );
 211+
 212+ lt.debug( 'tm: obtaining special words from server, offset ' + args.offset );
 213+
68214 var requestArgs = {
69215 'action': 'query',
70216 'format': 'json',
71217 'list': 'livetranslate',
72 - 'ltlanguage': currentLang
 218+ 'ltlanguage': args.language
73219 };
74220
75 - if ( offset > 0 ) {
76 - requestArgs['ltcontinue'] = offset;
 221+ if ( args.offset > 0 ) {
 222+ requestArgs['ltcontinue'] = args.offset;
77223 }
78224
79225 $.getJSON(
80 - wgScriptPath + '/api.php',
 226+ args.apiPath + '/api.php',
81227 requestArgs,
82228 function( data ) {
83229 if ( data.words ) {
84 - insertNoTranslateTags( data.words );
 230+ args.allWords.push.apply( args.allWords, data.words );
85231 }
86 - else if ( data.error && data.error.info ) {
87 - alert( data.error.info );
88 - }
89232 else {
90 - for ( i in data ) {
91 - alert( mediaWiki.msg( 'livetranslate-dictionary-error' ) );
92 - break;
93 - }
 233+ // TODO
94234 }
95235
96 - originalHtml = $( '#bodyContent' ).html();
97 -
98236 if ( data['query-continue'] ) {
99 - obtainAndInsertTranslations( data['query-continue'].livetranslate.ltcontinue );
 237+ _this.obtainWordsFromServer(
 238+ {
 239+ offset: data['query-continue'].livetranslate.ltcontinue,
 240+ language: args.language,
 241+ allWords: args.allWords
 242+ },
 243+ callback
 244+ );
100245 }
101246 else {
102 - initiateTranslating();
 247+ lt.debug( 'tm: obtained special words from server' );
 248+ callback( args.allWords );
103249 }
104250 }
105251 );
106 - }
 252+ },
107253
108 - obtainTranslationsFromLS: function() {
109 - return JSON.parse( localStorage.getItem( 'lt_memory' ) );
110 - }
 254+ /**
 255+ *
 256+ */
 257+ getTranslations: function( args, callback ) {
 258+ var _this = this;
 259+
 260+ var defaults = {
 261+ source: 'en',
 262+ target: 'en',
 263+ words: []
 264+ };
 265+
 266+ var translations = {};
 267+
 268+ args = $.extend( {}, defaults, args );
 269+
 270+ if ( !this.translations[args.source] ) {
 271+ this.translations[args.source] = {};
 272+ }
 273+
 274+ var mergeInTranslations = function( words ) {
 275+ lt.debug( 'tm: merging in translations' );
 276+ var wordsAdded = [];
 277+
 278+ $.each( args.words, function( index, word ) {
 279+ if ( !!words[word] ) {
 280+ translations[word] = words[word];
 281+ _this.translations[args.source][args.target][word] = words[word];
 282+ wordsAdded.push( index );
 283+ }
 284+ } );
 285+
 286+ args.words = $.grep( args.words, function( e, index ) {
 287+ return $.inArray( index, wordsAdded ) === -1;
 288+ } );
 289+ };
 290+
 291+ var getFromServer = function() {
 292+ _this.obtainTranslationsFromServer( args, function( obtainedTranslations ) {
 293+ mergeInTranslations( obtainedTranslations );
 294+
 295+ if ( _this.canUseLocalStorage() ) {
 296+ _this.writeToLS( 'translations', _this.translations );
 297+ lt.debug( 'tm: wrote translations to LS' );
 298+ }
 299+
 300+ callback( translations );
 301+ } );
 302+ };
 303+
 304+ if ( this.translations[args.source][args.target] ) {
 305+ mergeInTranslations( this.translations[args.source][args.target] );
 306+ }
 307+ else {
 308+ this.translations[args.source][args.target] = {};
 309+ }
 310+
 311+ if ( args.words.length == 0 ) {
 312+ callback( translations );
 313+ }
 314+ else {
 315+ if ( this.canUseLocalStorage() ) {
 316+ this.cleanLocalStorage( {}, function() {
 317+ var lsTranslations = _this.obtainFromLS( 'translations' );
 318+
 319+ if ( lsTranslations !== null && lsTranslations[args.source] && lsTranslations[args.source][args.target] ) {
 320+ mergeInTranslations( lsTranslations[args.source][args.target] );
 321+ }
 322+
 323+ if ( args.words.length == 0 ) {
 324+ callback( translations );
 325+ }
 326+ else {
 327+ getFromServer();
 328+ }
 329+ } );
 330+ }
 331+ else {
 332+ getFromServer();
 333+ }
 334+ }
 335+ },
111336
112 - writeTranslationsToLS: function() {
113 - localStorage.setItem( 'lt_memory', JSON.stringify( _this.translations ) )
114 - }
115 -
116 - getTranslations: function() {
117 - if ( _this.translations === false ) {
118 - if ( _this.canUseLocalStorage() && _this.localStorageIsValid() ) {
119 - _this.translations = _this.obtainTranslationsFromLS();
 337+ getSpecialWords: function( language, callback ) {
 338+ var _this = this;
 339+
 340+ var getFromServer = function() {
 341+ _this.obtainWordsFromServer(
 342+ {
 343+ language: language
 344+ },
 345+ function( words ) {
 346+ _this.words[language] = words;
 347+
 348+ if ( _this.canUseLocalStorage() ) {
 349+ _this.writeToLS( 'words', _this.words );
 350+ lt.debug( 'tm: wrote special words to LS' );
 351+ }
 352+
 353+ callback( words );
 354+ }
 355+ );
 356+ };
 357+
 358+ if ( this.words[language] ) {
 359+ callback( this.words[language] );
 360+ }
 361+ else {
 362+ if ( this.canUseLocalStorage() ) {
 363+ this.cleanLocalStorage( {}, function() {
 364+ var words = _this.obtainFromLS( 'words' );
 365+
 366+ if ( words !== null && words[language] ) {
 367+ callback( words[language] );
 368+ }
 369+ else {
 370+ getFromServer();
 371+ }
 372+ } );
120373 }
121374 else {
122 - _this.translations = _this.obtainTranslationsFromServer();
123 -
124 - if ( _this.canUseLocalStorage() ) {
125 - _this.writeTranslationsToLS();
126 - }
 375+ getFromServer();
127376 }
128377 }
129 -
130 - return _this.translations;
131378 }
132 -
133379 };
134380
135 -}) ( window.mediaWiki, window.liveTranslate );
 381+}) ( jQuery, window.liveTranslate );
Index: trunk/extensions/LiveTranslate/includes/jquery.replaceText.js
@@ -0,0 +1,23 @@
 2+/*
 3+ * jQuery replaceText - v1.1 - 11/21/2009
 4+ * http://benalman.com/projects/jquery-replacetext-plugin/
 5+ *
 6+ * Copyright (c) 2009 "Cowboy" Ben Alman
 7+ * Dual licensed under the MIT and GPL licenses.
 8+ * http://benalman.com/about/license/
 9+ */
 10+( function ( $ ) {
 11+ $.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()})};
 12+} )( jQuery );
 13+
 14+/**
 15+ * Regex text escaping function.
 16+ * Borrowed from http://simonwillison.net/2006/Jan/20/escape/
 17+ */
 18+RegExp.escape = function( text ) {
 19+ if ( !arguments.callee.sRE ) {
 20+ var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\' ];
 21+ arguments.callee.sRE = new RegExp( '(\\' + specials.join('|\\') + ')', 'g' );
 22+ }
 23+ return text.replace(arguments.callee.sRE, '\\$1');
 24+}
\ No newline at end of file
Property changes on: trunk/extensions/LiveTranslate/includes/jquery.replaceText.js
___________________________________________________________________
Added: svn:eol-style
125 + native
Index: trunk/extensions/LiveTranslate/includes/LiveTranslate_Functions.php
@@ -133,15 +133,15 @@
134134 }
135135
136136 /**
137 - * Returns the HTML for a language selector.
 137+ * Returns a list of languages that can be translated to.
138138 *
139 - * @since 0.1
 139+ * @since 1.2
140140 *
141141 * @param string $currentLang
142142 *
143 - * @return string
 143+ * @return array
144144 */
145 - public static function getLanguageSelector( $currentLang ) {
 145+ public static function getLanguages( $currentLang ) {
146146 global $wgUser, $wgLanguageCode, $egLiveTranslateLanguages;
147147
148148 $allowedLanguages = array_merge( $egLiveTranslateLanguages, array( $currentLang ) );
@@ -155,7 +155,7 @@
156156
157157 if ( array_key_exists( $userLang, $languages ) && in_array( $userLang, $allowedLanguages ) ) {
158158 $targetLang = $userLang;
159 - }
 159+ }
160160 }
161161
162162 $options = array();
@@ -167,39 +167,11 @@
168168 $options[$display] = $code;
169169 }
170170 }
171 -
172 - $languageSelector = new HTMLSelectField( array(
173 - 'id' => 'livetranslatelang',
174 - 'fieldname' => 'language',
175 - 'options' => $options
176 - ) );
177171
178 - return $languageSelector->getInputHTML( $targetLang );
 172+ return $options;
179173 }
180174
181175 /**
182 - * Gets a list of all available languages.
183 - *
184 - * @since 0.1
185 - *
186 - * @return array
187 - */
188 - public static function getAvailableLanguages() {
189 - $dbr = wfGetDB( DB_SLAVE );
190 -
191 - $destinationLangs = array();
192 -
193 - // TODO: fix index
194 - $res = $dbr->query( 'SELECT DISTINCT word_language FROM ' . $dbr->tableName( 'live_translate' ) );
195 -
196 - while ( $lang = $dbr->fetchObject( $res ) ) {
197 - $destinationLangs[] = $lang->word_language;
198 - }
199 -
200 - return $destinationLangs;
201 - }
202 -
203 - /**
204176 * Returns a PHP version of the JavaScript google.language.Languages enum of the Google Translate v1 API.
205177 * @see https://code.google.com/apis/language/translate/v1/getting_started.html#LangNameArray
206178 *
@@ -207,7 +179,7 @@
208180 *
209181 * @return array LANGUAGE_NAME => 'code'
210182 */
211 - public static function getGTSupportedLanguages() { //Language::getLanguageNames( false );
 183+ public static function getGTSupportedLanguages() {
212184 return array(
213185 'AFRIKAANS' => 'af',
214186 'ALBANIAN' => 'sq',
Property changes on: trunk/extensions/LiveTranslate
___________________________________________________________________
Added: svn:ignore
215187 + .git
.gitignore

Status & tagging log