r54944 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r54943‎ | r54944 | r54945 >
Date:16:23, 13 August 2009
Author:nikerabbit
Status:ok
Tags:
Comment:
* Experimental buzzword-compatible-ajax editing for translations with opt-in in preferences
* New and missing js files (one+plugin for above, one for imports)
* Some missing right definitions
* Minor bugfix for loading translatable page message groups
* TranslationHelpers for all the nice boxes with some enhancements, TranslateEditAddons (normal edit) not ported to use it yet.
Modified paths:
  • /trunk/extensions/Translate/MessageGroups.php (modified) (history)
  • /trunk/extensions/Translate/Translate.css (modified) (history)
  • /trunk/extensions/Translate/Translate.i18n.php (modified) (history)
  • /trunk/extensions/Translate/Translate.php (modified) (history)
  • /trunk/extensions/Translate/TranslatePage.php (modified) (history)
  • /trunk/extensions/Translate/_autoload.php (modified) (history)
  • /trunk/extensions/Translate/js (added) (history)
  • /trunk/extensions/Translate/js/import.js (added) (history)
  • /trunk/extensions/Translate/js/jquery.form.js (added) (history)
  • /trunk/extensions/Translate/js/quickedit.js (added) (history)
  • /trunk/extensions/Translate/utils/MessageTable.php (modified) (history)
  • /trunk/extensions/Translate/utils/TranslationEditPage.php (added) (history)
  • /trunk/extensions/Translate/utils/TranslationHelpers.php (added) (history)
  • /trunk/extensions/Translate/utils/UserToggles.php (modified) (history)

Diff [purge]

Index: trunk/extensions/Translate/TranslatePage.php
@@ -25,12 +25,10 @@
2626
2727 /**
2828 * Access point for this special page.
29 - * GLOBALS: $wgHooks, $wgOut.
3029 */
3130 public function execute( $parameters ) {
32 - wfLoadExtensionMessages( 'Translate' );
 31+ global $wgOut, $wgTranslateBlacklist, $wgUser, $wgRequest;
3332 TranslateUtils::injectCSS();
34 - global $wgOut, $wgTranslateBlacklist, $wgUser;
3533
3634 $this->setHeaders();
3735 if ( $parameters === 'manage' ) {
@@ -43,6 +41,12 @@
4442 $manage = new SpecialManageGroups();
4543 $manage->execute();
4644 return;
 45+ } elseif ( $parameters === 'editpage' ) {
 46+ $editpage = TranslationEditPage::newFromRequest( $wgRequest );
 47+ if ( $editpage ) {
 48+ $editpage->execute();
 49+ return;
 50+ }
4751 }
4852
4953 $this->setup( $parameters );
Index: trunk/extensions/Translate/Translate.css
@@ -44,15 +44,49 @@
4545 font-weight: bold;
4646 }
4747
 48+/* Edit page */
 49+
4850 .mw-sp-translate-edit-fields fieldset {
4951 line-height: normal;
50 - margin: 0px !important;
 52+ margin: 0px;
5153 }
5254
53 -.mw-sp-translate-edit-extra {
 55+.mw-translate-edit-extra {
5456 border-bottom: 1px solid black;
5557 }
5658
 59+.ltr .mw-translate-legend {
 60+ float: right;
 61+ border-left: 1px solid black;
 62+ font-weight: bold;
 63+ margin-left: 5pt;
 64+ padding-left: 5pt;
 65+}
 66+
 67+.rtl .mw-translate-legend {
 68+ float: left;
 69+ border-left: 1px solid black;
 70+ font-weight: bold;
 71+ margin-right: 5pt;
 72+ padding-right: 5pt;
 73+}
 74+
 75+.mw-translate-sep {
 76+ margin-bottom:1ex;
 77+ margin-top:0.5ex;
 78+}
 79+
 80+.mw-translate-edit-deftext {
 81+ font-family: monospace;
 82+}
 83+
 84+.mw-translate-edit-area {
 85+ padding: 0;
 86+}
 87+
 88+/* End edit page */
 89+
 90+
5791 .mw-sp-translate-group {
5892 border: 0;
5993 margin: 0 3em 3em 3em;
@@ -124,4 +158,4 @@
125159 padding-left: 2em;
126160 padding-right: 2em;
127161 padding-bottom: 2ex;
128 -}
 162+}
\ No newline at end of file
Index: trunk/extensions/Translate/MessageGroups.php
@@ -694,6 +694,7 @@
695695 public static function init() {
696696 static $loaded = false;
697697 if ( $loaded ) return;
 698+ wfDebug( __METHOD__ . "\n" );
698699
699700 global $wgTranslateAddMWExtensionGroups;
700701 if ( $wgTranslateAddMWExtensionGroups ) {
@@ -708,10 +709,10 @@
709710 $dbr = wfGetDB( DB_SLAVE );
710711
711712 $tables = array( 'page', 'revtag', 'revtag_type' );
712 - $vars = array( 'page_id', 'page_namespace', 'page_title', 'rt_revision' );
 713+ $vars = array( 'page_id', 'page_namespace', 'page_title', );
713714 $conds = array( 'page_id=rt_page', 'rtt_id=rt_type', 'rtt_name' => 'tp:mark' );
714715 $options = array( 'GROUP BY' => 'page_id' );
715 - $res = $dbr->select( $tables, $vars, $conds, __METHOD__ );
 716+ $res = $dbr->select( $tables, $vars, $conds, __METHOD__, $options );
716717 foreach ( $res as $r ) {
717718 $title = Title::makeTitle( $r->page_namespace, $r->page_title )->getPrefixedText();
718719 $id = "page|$title";
@@ -726,6 +727,7 @@
727728
728729 global $wgTranslateGroupFiles;
729730 foreach ( $wgTranslateGroupFiles as $file ) {
 731+ wfDebug( $file."\n" );
730732 $conf = TranslateSpyc::load($file);
731733 $group = MessageGroupBase::factory( $conf );
732734 $wgTranslateCC[$group->getId()] = $group;
@@ -765,7 +767,6 @@
766768 private function __construct() {
767769 self::init();
768770 global $wgTranslateEC, $wgTranslateCC;
769 -
770771 $all = array_merge( $wgTranslateEC, array_keys( $wgTranslateCC ) );
771772 sort( $all );
772773 foreach ( $all as $id ) {
Index: trunk/extensions/Translate/js/import.js
@@ -0,0 +1,15 @@
 2+hookEvent("load", translateImportInit);
 3+
 4+/** Call this to enable suggestions on input (id=inputId), on a form (name=formName) */
 5+function translateImportInit(){
 6+ os_initHandlers( 'mw-translate-up-wiki-input', 'mw-translate-import', document.getElementById('mw-translate-up-wiki-input') );
 7+
 8+ jQuery(".mw-translate-import-inputs").each(function(i) {
 9+ os_hookEvent(this, "focus", function(event) {
 10+ var srcid = os_getTarget(event).id;
 11+ var inputid = srcid.replace("-input", "");
 12+
 13+ jQuery("#" + inputid).attr("checked", "checked");
 14+ });
 15+ });
 16+}
Property changes on: trunk/extensions/Translate/js/import.js
___________________________________________________________________
Name: svn:eol-style
117 + native
Index: trunk/extensions/Translate/js/jquery.form.js
@@ -0,0 +1,643 @@
 2+/*
 3+ * jQuery Form Plugin
 4+ * version: 2.28 (10-MAY-2009)
 5+ * @requires jQuery v1.2.2 or later
 6+ *
 7+ * Examples and documentation at: http://malsup.com/jquery/form/
 8+ * Dual licensed under the MIT and GPL licenses:
 9+ * http://www.opensource.org/licenses/mit-license.php
 10+ * http://www.gnu.org/licenses/gpl.html
 11+ */
 12+;(function($) {
 13+
 14+/*
 15+ Usage Note:
 16+ -----------
 17+ Do not use both ajaxSubmit and ajaxForm on the same form. These
 18+ functions are intended to be exclusive. Use ajaxSubmit if you want
 19+ to bind your own submit handler to the form. For example,
 20+
 21+ $(document).ready(function() {
 22+ $('#myForm').bind('submit', function() {
 23+ $(this).ajaxSubmit({
 24+ target: '#output'
 25+ });
 26+ return false; // <-- important!
 27+ });
 28+ });
 29+
 30+ Use ajaxForm when you want the plugin to manage all the event binding
 31+ for you. For example,
 32+
 33+ $(document).ready(function() {
 34+ $('#myForm').ajaxForm({
 35+ target: '#output'
 36+ });
 37+ });
 38+
 39+ When using ajaxForm, the ajaxSubmit function will be invoked for you
 40+ at the appropriate time.
 41+*/
 42+
 43+/**
 44+ * ajaxSubmit() provides a mechanism for immediately submitting
 45+ * an HTML form using AJAX.
 46+ */
 47+$.fn.ajaxSubmit = function(options) {
 48+ // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
 49+ if (!this.length) {
 50+ log('ajaxSubmit: skipping submit process - no element selected');
 51+ return this;
 52+ }
 53+
 54+ if (typeof options == 'function')
 55+ options = { success: options };
 56+
 57+ var url = $.trim(this.attr('action'));
 58+ if (url) {
 59+ // clean url (don't include hash vaue)
 60+ url = (url.match(/^([^#]+)/)||[])[1];
 61+ }
 62+ url = url || window.location.href || ''
 63+
 64+ options = $.extend({
 65+ url: url,
 66+ type: this.attr('method') || 'GET'
 67+ }, options || {});
 68+
 69+ // hook for manipulating the form data before it is extracted;
 70+ // convenient for use with rich editors like tinyMCE or FCKEditor
 71+ var veto = {};
 72+ this.trigger('form-pre-serialize', [this, options, veto]);
 73+ if (veto.veto) {
 74+ log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
 75+ return this;
 76+ }
 77+
 78+ // provide opportunity to alter form data before it is serialized
 79+ if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
 80+ log('ajaxSubmit: submit aborted via beforeSerialize callback');
 81+ return this;
 82+ }
 83+
 84+ var a = this.formToArray(options.semantic);
 85+ if (options.data) {
 86+ options.extraData = options.data;
 87+ for (var n in options.data) {
 88+ if(options.data[n] instanceof Array) {
 89+ for (var k in options.data[n])
 90+ a.push( { name: n, value: options.data[n][k] } );
 91+ }
 92+ else
 93+ a.push( { name: n, value: options.data[n] } );
 94+ }
 95+ }
 96+
 97+ // give pre-submit callback an opportunity to abort the submit
 98+ if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
 99+ log('ajaxSubmit: submit aborted via beforeSubmit callback');
 100+ return this;
 101+ }
 102+
 103+ // fire vetoable 'validate' event
 104+ this.trigger('form-submit-validate', [a, this, options, veto]);
 105+ if (veto.veto) {
 106+ log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
 107+ return this;
 108+ }
 109+
 110+ var q = $.param(a);
 111+
 112+ if (options.type.toUpperCase() == 'GET') {
 113+ options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
 114+ options.data = null; // data is null for 'get'
 115+ }
 116+ else
 117+ options.data = q; // data is the query string for 'post'
 118+
 119+ var $form = this, callbacks = [];
 120+ if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
 121+ if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
 122+
 123+ // perform a load on the target only if dataType is not provided
 124+ if (!options.dataType && options.target) {
 125+ var oldSuccess = options.success || function(){};
 126+ callbacks.push(function(data) {
 127+ $(options.target).html(data).each(oldSuccess, arguments);
 128+ });
 129+ }
 130+ else if (options.success)
 131+ callbacks.push(options.success);
 132+
 133+ options.success = function(data, status) {
 134+ for (var i=0, max=callbacks.length; i < max; i++)
 135+ callbacks[i].apply(options, [data, status, $form]);
 136+ };
 137+
 138+ // are there files to upload?
 139+ var files = $('input:file', this).fieldValue();
 140+ var found = false;
 141+ for (var j=0; j < files.length; j++)
 142+ if (files[j])
 143+ found = true;
 144+
 145+ var multipart = false;
 146+// var mp = 'multipart/form-data';
 147+// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
 148+
 149+ // options.iframe allows user to force iframe mode
 150+ if (options.iframe || found || multipart) {
 151+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
 152+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
 153+ if (options.closeKeepAlive)
 154+ $.get(options.closeKeepAlive, fileUpload);
 155+ else
 156+ fileUpload();
 157+ }
 158+ else
 159+ $.ajax(options);
 160+
 161+ // fire 'notify' event
 162+ this.trigger('form-submit-notify', [this, options]);
 163+ return this;
 164+
 165+
 166+ // private function for handling file uploads (hat tip to YAHOO!)
 167+ function fileUpload() {
 168+ var form = $form[0];
 169+
 170+ if ($(':input[name=submit]', form).length) {
 171+ alert('Error: Form elements must not be named "submit".');
 172+ return;
 173+ }
 174+
 175+ var opts = $.extend({}, $.ajaxSettings, options);
 176+ var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
 177+
 178+ var id = 'jqFormIO' + (new Date().getTime());
 179+ var $io = $('<iframe id="' + id + '" name="' + id + '" src="about:blank" />');
 180+ var io = $io[0];
 181+
 182+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
 183+
 184+ var xhr = { // mock object
 185+ aborted: 0,
 186+ responseText: null,
 187+ responseXML: null,
 188+ status: 0,
 189+ statusText: 'n/a',
 190+ getAllResponseHeaders: function() {},
 191+ getResponseHeader: function() {},
 192+ setRequestHeader: function() {},
 193+ abort: function() {
 194+ this.aborted = 1;
 195+ $io.attr('src','about:blank'); // abort op in progress
 196+ }
 197+ };
 198+
 199+ var g = opts.global;
 200+ // trigger ajax global events so that activity/block indicators work like normal
 201+ if (g && ! $.active++) $.event.trigger("ajaxStart");
 202+ if (g) $.event.trigger("ajaxSend", [xhr, opts]);
 203+
 204+ if (s.beforeSend && s.beforeSend(xhr, s) === false) {
 205+ s.global && $.active--;
 206+ return;
 207+ }
 208+ if (xhr.aborted)
 209+ return;
 210+
 211+ var cbInvoked = 0;
 212+ var timedOut = 0;
 213+
 214+ // add submitting element to data if we know it
 215+ var sub = form.clk;
 216+ if (sub) {
 217+ var n = sub.name;
 218+ if (n && !sub.disabled) {
 219+ options.extraData = options.extraData || {};
 220+ options.extraData[n] = sub.value;
 221+ if (sub.type == "image") {
 222+ options.extraData[name+'.x'] = form.clk_x;
 223+ options.extraData[name+'.y'] = form.clk_y;
 224+ }
 225+ }
 226+ }
 227+
 228+ // take a breath so that pending repaints get some cpu time before the upload starts
 229+ setTimeout(function() {
 230+ // make sure form attrs are set
 231+ var t = $form.attr('target'), a = $form.attr('action');
 232+
 233+ // update form attrs in IE friendly way
 234+ form.setAttribute('target',id);
 235+ if (form.getAttribute('method') != 'POST')
 236+ form.setAttribute('method', 'POST');
 237+ if (form.getAttribute('action') != opts.url)
 238+ form.setAttribute('action', opts.url);
 239+
 240+ // ie borks in some cases when setting encoding
 241+ if (! options.skipEncodingOverride) {
 242+ $form.attr({
 243+ encoding: 'multipart/form-data',
 244+ enctype: 'multipart/form-data'
 245+ });
 246+ }
 247+
 248+ // support timout
 249+ if (opts.timeout)
 250+ setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
 251+
 252+ // add "extra" data to form if provided in options
 253+ var extraInputs = [];
 254+ try {
 255+ if (options.extraData)
 256+ for (var n in options.extraData)
 257+ extraInputs.push(
 258+ $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
 259+ .appendTo(form)[0]);
 260+
 261+ // add iframe to doc and submit the form
 262+ $io.appendTo('body');
 263+ io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
 264+ form.submit();
 265+ }
 266+ finally {
 267+ // reset attrs and remove "extra" input elements
 268+ form.setAttribute('action',a);
 269+ t ? form.setAttribute('target', t) : $form.removeAttr('target');
 270+ $(extraInputs).remove();
 271+ }
 272+ }, 10);
 273+
 274+ var nullCheckFlag = 0;
 275+
 276+ function cb() {
 277+ if (cbInvoked++) return;
 278+
 279+ io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
 280+
 281+ var ok = true;
 282+ try {
 283+ if (timedOut) throw 'timeout';
 284+ // extract the server response from the iframe
 285+ var data, doc;
 286+
 287+ doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
 288+
 289+ if ((doc.body == null || doc.body.innerHTML == '') && !nullCheckFlag) {
 290+ // in some browsers (cough, Opera 9.2.x) the iframe DOM is not always traversable when
 291+ // the onload callback fires, so we give them a 2nd chance
 292+ nullCheckFlag = 1;
 293+ cbInvoked--;
 294+ setTimeout(cb, 100);
 295+ return;
 296+ }
 297+
 298+ xhr.responseText = doc.body ? doc.body.innerHTML : null;
 299+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
 300+ xhr.getResponseHeader = function(header){
 301+ var headers = {'content-type': opts.dataType};
 302+ return headers[header];
 303+ };
 304+
 305+ if (opts.dataType == 'json' || opts.dataType == 'script') {
 306+ var ta = doc.getElementsByTagName('textarea')[0];
 307+ xhr.responseText = ta ? ta.value : xhr.responseText;
 308+ }
 309+ else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
 310+ xhr.responseXML = toXml(xhr.responseText);
 311+ }
 312+ data = $.httpData(xhr, opts.dataType);
 313+ }
 314+ catch(e){
 315+ ok = false;
 316+ $.handleError(opts, xhr, 'error', e);
 317+ }
 318+
 319+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
 320+ if (ok) {
 321+ opts.success(data, 'success');
 322+ if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
 323+ }
 324+ if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
 325+ if (g && ! --$.active) $.event.trigger("ajaxStop");
 326+ if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
 327+
 328+ // clean up
 329+ setTimeout(function() {
 330+ $io.remove();
 331+ xhr.responseXML = null;
 332+ }, 100);
 333+ };
 334+
 335+ function toXml(s, doc) {
 336+ if (window.ActiveXObject) {
 337+ doc = new ActiveXObject('Microsoft.XMLDOM');
 338+ doc.async = 'false';
 339+ doc.loadXML(s);
 340+ }
 341+ else
 342+ doc = (new DOMParser()).parseFromString(s, 'text/xml');
 343+ return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
 344+ };
 345+ };
 346+};
 347+
 348+/**
 349+ * ajaxForm() provides a mechanism for fully automating form submission.
 350+ *
 351+ * The advantages of using this method instead of ajaxSubmit() are:
 352+ *
 353+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
 354+ * is used to submit the form).
 355+ * 2. This method will include the submit element's name/value data (for the element that was
 356+ * used to submit the form).
 357+ * 3. This method binds the submit() method to the form for you.
 358+ *
 359+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
 360+ * passes the options argument along after properly binding events for submit elements and
 361+ * the form itself.
 362+ */
 363+$.fn.ajaxForm = function(options) {
 364+ return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
 365+ $(this).ajaxSubmit(options);
 366+ return false;
 367+ }).each(function() {
 368+ // store options in hash
 369+ $(":submit,input:image", this).bind('click.form-plugin',function(e) {
 370+ var form = this.form;
 371+ form.clk = this;
 372+ if (this.type == 'image') {
 373+ if (e.offsetX != undefined) {
 374+ form.clk_x = e.offsetX;
 375+ form.clk_y = e.offsetY;
 376+ } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
 377+ var offset = $(this).offset();
 378+ form.clk_x = e.pageX - offset.left;
 379+ form.clk_y = e.pageY - offset.top;
 380+ } else {
 381+ form.clk_x = e.pageX - this.offsetLeft;
 382+ form.clk_y = e.pageY - this.offsetTop;
 383+ }
 384+ }
 385+ // clear form vars
 386+ setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10);
 387+ });
 388+ });
 389+};
 390+
 391+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
 392+$.fn.ajaxFormUnbind = function() {
 393+ this.unbind('submit.form-plugin');
 394+ return this.each(function() {
 395+ $(":submit,input:image", this).unbind('click.form-plugin');
 396+ });
 397+
 398+};
 399+
 400+/**
 401+ * formToArray() gathers form element data into an array of objects that can
 402+ * be passed to any of the following ajax functions: $.get, $.post, or load.
 403+ * Each object in the array has both a 'name' and 'value' property. An example of
 404+ * an array for a simple login form might be:
 405+ *
 406+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 407+ *
 408+ * It is this array that is passed to pre-submit callback functions provided to the
 409+ * ajaxSubmit() and ajaxForm() methods.
 410+ */
 411+$.fn.formToArray = function(semantic) {
 412+ var a = [];
 413+ if (this.length == 0) return a;
 414+
 415+ var form = this[0];
 416+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
 417+ if (!els) return a;
 418+ for(var i=0, max=els.length; i < max; i++) {
 419+ var el = els[i];
 420+ var n = el.name;
 421+ if (!n) continue;
 422+
 423+ if (semantic && form.clk && el.type == "image") {
 424+ // handle image inputs on the fly when semantic == true
 425+ if(!el.disabled && form.clk == el) {
 426+ a.push({name: n, value: $(el).val()});
 427+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
 428+ }
 429+ continue;
 430+ }
 431+
 432+ var v = $.fieldValue(el, true);
 433+ if (v && v.constructor == Array) {
 434+ for(var j=0, jmax=v.length; j < jmax; j++)
 435+ a.push({name: n, value: v[j]});
 436+ }
 437+ else if (v !== null && typeof v != 'undefined')
 438+ a.push({name: n, value: v});
 439+ }
 440+
 441+ if (!semantic && form.clk) {
 442+ // input type=='image' are not found in elements array! handle it here
 443+ var $input = $(form.clk), input = $input[0], n = input.name;
 444+ if (n && !input.disabled && input.type == 'image') {
 445+ a.push({name: n, value: $input.val()});
 446+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
 447+ }
 448+ }
 449+ return a;
 450+};
 451+
 452+/**
 453+ * Serializes form data into a 'submittable' string. This method will return a string
 454+ * in the format: name1=value1&amp;name2=value2
 455+ */
 456+$.fn.formSerialize = function(semantic) {
 457+ //hand off to jQuery.param for proper encoding
 458+ return $.param(this.formToArray(semantic));
 459+};
 460+
 461+/**
 462+ * Serializes all field elements in the jQuery object into a query string.
 463+ * This method will return a string in the format: name1=value1&amp;name2=value2
 464+ */
 465+$.fn.fieldSerialize = function(successful) {
 466+ var a = [];
 467+ this.each(function() {
 468+ var n = this.name;
 469+ if (!n) return;
 470+ var v = $.fieldValue(this, successful);
 471+ if (v && v.constructor == Array) {
 472+ for (var i=0,max=v.length; i < max; i++)
 473+ a.push({name: n, value: v[i]});
 474+ }
 475+ else if (v !== null && typeof v != 'undefined')
 476+ a.push({name: this.name, value: v});
 477+ });
 478+ //hand off to jQuery.param for proper encoding
 479+ return $.param(a);
 480+};
 481+
 482+/**
 483+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
 484+ *
 485+ * <form><fieldset>
 486+ * <input name="A" type="text" />
 487+ * <input name="A" type="text" />
 488+ * <input name="B" type="checkbox" value="B1" />
 489+ * <input name="B" type="checkbox" value="B2"/>
 490+ * <input name="C" type="radio" value="C1" />
 491+ * <input name="C" type="radio" value="C2" />
 492+ * </fieldset></form>
 493+ *
 494+ * var v = $(':text').fieldValue();
 495+ * // if no values are entered into the text inputs
 496+ * v == ['','']
 497+ * // if values entered into the text inputs are 'foo' and 'bar'
 498+ * v == ['foo','bar']
 499+ *
 500+ * var v = $(':checkbox').fieldValue();
 501+ * // if neither checkbox is checked
 502+ * v === undefined
 503+ * // if both checkboxes are checked
 504+ * v == ['B1', 'B2']
 505+ *
 506+ * var v = $(':radio').fieldValue();
 507+ * // if neither radio is checked
 508+ * v === undefined
 509+ * // if first radio is checked
 510+ * v == ['C1']
 511+ *
 512+ * The successful argument controls whether or not the field element must be 'successful'
 513+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 514+ * The default value of the successful argument is true. If this value is false the value(s)
 515+ * for each element is returned.
 516+ *
 517+ * Note: This method *always* returns an array. If no valid value can be determined the
 518+ * array will be empty, otherwise it will contain one or more values.
 519+ */
 520+$.fn.fieldValue = function(successful) {
 521+ for (var val=[], i=0, max=this.length; i < max; i++) {
 522+ var el = this[i];
 523+ var v = $.fieldValue(el, successful);
 524+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
 525+ continue;
 526+ v.constructor == Array ? $.merge(val, v) : val.push(v);
 527+ }
 528+ return val;
 529+};
 530+
 531+/**
 532+ * Returns the value of the field element.
 533+ */
 534+$.fieldValue = function(el, successful) {
 535+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
 536+ if (typeof successful == 'undefined') successful = true;
 537+
 538+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
 539+ (t == 'checkbox' || t == 'radio') && !el.checked ||
 540+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
 541+ tag == 'select' && el.selectedIndex == -1))
 542+ return null;
 543+
 544+ if (tag == 'select') {
 545+ var index = el.selectedIndex;
 546+ if (index < 0) return null;
 547+ var a = [], ops = el.options;
 548+ var one = (t == 'select-one');
 549+ var max = (one ? index+1 : ops.length);
 550+ for(var i=(one ? index : 0); i < max; i++) {
 551+ var op = ops[i];
 552+ if (op.selected) {
 553+ var v = op.value;
 554+ if (!v) // extra pain for IE...
 555+ v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
 556+ if (one) return v;
 557+ a.push(v);
 558+ }
 559+ }
 560+ return a;
 561+ }
 562+ return el.value;
 563+};
 564+
 565+/**
 566+ * Clears the form data. Takes the following actions on the form's input fields:
 567+ * - input text fields will have their 'value' property set to the empty string
 568+ * - select elements will have their 'selectedIndex' property set to -1
 569+ * - checkbox and radio inputs will have their 'checked' property set to false
 570+ * - inputs of type submit, button, reset, and hidden will *not* be effected
 571+ * - button elements will *not* be effected
 572+ */
 573+$.fn.clearForm = function() {
 574+ return this.each(function() {
 575+ $('input,select,textarea', this).clearFields();
 576+ });
 577+};
 578+
 579+/**
 580+ * Clears the selected form elements.
 581+ */
 582+$.fn.clearFields = $.fn.clearInputs = function() {
 583+ return this.each(function() {
 584+ var t = this.type, tag = this.tagName.toLowerCase();
 585+ if (t == 'text' || t == 'password' || tag == 'textarea')
 586+ this.value = '';
 587+ else if (t == 'checkbox' || t == 'radio')
 588+ this.checked = false;
 589+ else if (tag == 'select')
 590+ this.selectedIndex = -1;
 591+ });
 592+};
 593+
 594+/**
 595+ * Resets the form data. Causes all form elements to be reset to their original value.
 596+ */
 597+$.fn.resetForm = function() {
 598+ return this.each(function() {
 599+ // guard against an input with the name of 'reset'
 600+ // note that IE reports the reset function as an 'object'
 601+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
 602+ this.reset();
 603+ });
 604+};
 605+
 606+/**
 607+ * Enables or disables any matching elements.
 608+ */
 609+$.fn.enable = function(b) {
 610+ if (b == undefined) b = true;
 611+ return this.each(function() {
 612+ this.disabled = !b;
 613+ });
 614+};
 615+
 616+/**
 617+ * Checks/unchecks any matching checkboxes or radio buttons and
 618+ * selects/deselects and matching option elements.
 619+ */
 620+$.fn.selected = function(select) {
 621+ if (select == undefined) select = true;
 622+ return this.each(function() {
 623+ var t = this.type;
 624+ if (t == 'checkbox' || t == 'radio')
 625+ this.checked = select;
 626+ else if (this.tagName.toLowerCase() == 'option') {
 627+ var $sel = $(this).parent('select');
 628+ if (select && $sel[0] && $sel[0].type == 'select-one') {
 629+ // deselect all other options
 630+ $sel.find('option').selected(false);
 631+ }
 632+ this.selected = select;
 633+ }
 634+ });
 635+};
 636+
 637+// helper fn for console logging
 638+// set $.fn.ajaxSubmit.debug to true to enable debug logging
 639+function log() {
 640+ if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
 641+ window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
 642+};
 643+
 644+})(jQuery);
Property changes on: trunk/extensions/Translate/js/jquery.form.js
___________________________________________________________________
Name: svn:eol-style
1645 + native
Index: trunk/extensions/Translate/js/quickedit.js
@@ -0,0 +1,52 @@
 2+function trlOpenJsEdit( page ) {
 3+ var url = wgScript + "?title=Special:Translate/editpage&page=" + page + "&uselang=" + wgUserLanguage;
 4+ var id = "jsedit" + page.replace( /[^a-zA-Z0-9_]/g, '_' );
 5+
 6+ var dialog = jQuery("#"+id);
 7+ if ( dialog.size() > 0 ) {
 8+ dialog.dialog("option", "position", "top" );
 9+ dialog.dialog("open");
 10+ return false;
 11+ }
 12+
 13+ var div = jQuery('<div id=' + id + '></div>');
 14+ div.appendTo(document.body);
 15+
 16+ var dialog = jQuery("#"+id);
 17+
 18+ dialog = dialog.load(url, false, function() {
 19+ var form = jQuery("#"+ id + " form");
 20+ var textarea = form.find( ".mw-translate-edit-area" );
 21+ textarea.width(textarea.width()-4);
 22+ form.ajaxForm({
 23+ datatype: "json",
 24+ success: function(json) {
 25+ json = JSON.parse(json);
 26+ if ( json.error ) {
 27+ alert( json.error.info + " (" + json.error.code +")" );
 28+ } else if ( json.edit.result == "Failure" ) {
 29+ alert( "Extension error. Copy your text and try normal edit." );
 30+ } else if ( json.edit.result == "Success" ) {
 31+ alert( "Saved!" );
 32+ dialog.dialog("close");
 33+ dialog.dialog("destroy");
 34+ } else {
 35+ alert( "Unknown error." );
 36+ }
 37+ },
 38+ });
 39+ });
 40+
 41+ dialog.dialog({
 42+ bgiframe: true,
 43+ width: parseInt(trlVpWidth()*0.8),
 44+ title: page,
 45+ position: "top"
 46+ });
 47+
 48+ return false;
 49+}
 50+
 51+function trlVpWidth() {
 52+ return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
 53+}
\ No newline at end of file
Property changes on: trunk/extensions/Translate/js/quickedit.js
___________________________________________________________________
Name: svn:eol-style
154 + native
Index: trunk/extensions/Translate/Translate.php
@@ -58,8 +58,10 @@
5959 # Custom preferences
6060 $wgDefaultUserOptions['translate'] = 0;
6161 $wgDefaultUserOptions['translate-editlangs'] = 'default';
 62+$wgDefaultUserOptions['translate-jsedit'] = 0;
6263 $wgHooks['GetPreferences'][] = 'TranslatePreferences::onGetPreferences';
6364 $wgHooks['GetPreferences'][] = 'TranslatePreferences::translationAssistLanguages';
 65+$wgHooks['GetPreferences'][] = 'TranslatePreferences::translationJsedit';
6466
6567 # Recent changes filters
6668 $wgHooks['SpecialRecentChangesQuery'][] = 'TranslateRcFilter::translationFilter';
@@ -67,7 +69,9 @@
6870 $wgHooks['SkinTemplateToolboxEnd'][] = 'TranslateToolbox::toolboxAllTranslations';
6971
7072 $wgJSAutoloadClasses['TranslateImport'] = "extensions/Translate/js/import.js";
71 -$wgJSAutoloadClasses['JsSelectToInput'] = "extensions/Translate/utils/JsSelectToInput.js";
 73+$wgJSAutoloadClasses['JsSelectToInput'] = "extensions/Translate/utils/JsSelectToInput.js";
 74+$wgJSAutoloadClasses['JsEdit'] = "extensions/Translate/js/quickedit.js";
 75+$wgJSAutoloadClasses['j.form'] = "extensions/Translate/js/jquery.form.js";
7276
7377
7478 $wgEnablePageTranslation = false;
@@ -75,6 +79,8 @@
7680
7781 $wgJobClasses['RenderJob'] = 'RenderJob';
7882 $wgAvailableRights[] = 'translate';
 83+$wgAvailableRights[] = 'translate-import';
 84+$wgAvailableRights[] = 'translate-manage';
7985
8086 define( 'TRANSLATE_FUZZY', '!!FUZZY!!' );
8187
Index: trunk/extensions/Translate/_autoload.php
@@ -82,6 +82,8 @@
8383 $wgAutoloadClasses['HTMLJsSelectToInputField'] = $dir . 'utils/HTMLJsSelectToInputField.php';
8484 $wgAutoloadClasses['MessageGroupCache'] = $dir . 'utils/MessageGroupCache.php';
8585 $wgAutoloadClasses['MessageWebImporter'] = $dir . 'utils/MessageWebImporter.php';
 86+$wgAutoloadClasses['TranslationEditPage'] = $dir . 'utils/TranslationEditPage.php';
 87+$wgAutoloadClasses['TranslationHelpers'] = $dir . 'utils/TranslationHelpers.php';
8688
8789
8890 # predefined groups
Index: trunk/extensions/Translate/utils/MessageTable.php
@@ -48,7 +48,22 @@
4949 $this->headers[$type] = array( 'raw', htmlspecialchars($value) );
5050 }
5151
 52+ public function setCSSJS() {
 53+ global $wgOut, $wgScriptPath;
 54+ // Our class
 55+ $wgOut->addScriptClass( 'JsEdit' );
 56+ // Core jQuery
 57+ $wgOut->addScriptClass( 'j.ui' );
 58+ $wgOut->addScriptClass( 'j.ui.dialog' );
 59+ $wgOut->addScriptClass( 'j.ui.draggable' );
 60+ $wgOut->addScriptClass( 'j.ui.resizable' );
 61+ // Additional jQuery
 62+ $wgOut->addScriptClass( 'j.form' );
 63+ // TODO: this can't be a good way...
 64+ $wgOut->addExtensionStyle( "$wgScriptPath/js2/mwEmbed/jquery/jquery.ui-1.7.1/themes/base/ui.all.css" );
 65+ }
5266
 67+
5368 public function header() {
5469 $tableheader = Xml::openElement( 'table', array(
5570 'class' => 'mw-sp-translate-table',
@@ -79,10 +94,9 @@
8095 }
8196
8297 public function contents() {
83 -
8498 global $wgUser;
8599 $sk = $wgUser->getSkin();
86 - wfLoadExtensionMessages( 'Translate' );
 100+
87101 $optional = wfMsgHtml( 'translate-optional' );
88102
89103 $batch = new LinkBatch();
@@ -107,7 +121,7 @@
108122 $tools['edit'] = $sk->link(
109123 $title,
110124 $niceTitle,
111 - array(),
 125+ TranslationEditPage::jsEdit( $title ),
112126 array( 'action' => 'edit' ) + $this->editLinkParams,
113127 'known'
114128 );
@@ -143,6 +157,7 @@
144158 }
145159
146160 public function fullTable() {
 161+ $this->setCSSJS();
147162 return $this->header() . $this->contents() . '</table>';
148163 }
149164
Index: trunk/extensions/Translate/utils/TranslationEditPage.php
@@ -0,0 +1,96 @@
 2+<?php
 3+
 4+/**
 5+ *
 6+ */
 7+class TranslationEditPage {
 8+ /// Instance of an Title object
 9+ protected $title;
 10+
 11+ /**
 12+ * Constructor.
 13+ * @param $title Title A title object
 14+ */
 15+ public function __construct( Title $title ) {
 16+ $this->setTitle( $title );
 17+ }
 18+
 19+ public static function newFromRequest( WebRequest $request ) {
 20+ $title = Title::newFromText( $request->getText( 'page' ) );
 21+ if ( !$title ) return null;
 22+ return new self( $title );
 23+ }
 24+
 25+
 26+ public function setTitle( Title $title ) { $this->title = $title; }
 27+ public function getTitle() { return $this->title; }
 28+
 29+ public function execute() {
 30+ $data = $this->getEditInfo();
 31+ $helpers = new TranslationHelpers( $this->getTitle() );
 32+
 33+ global $wgServer, $wgScriptPath, $wgOut;
 34+ $wgOut->disable();
 35+
 36+ $translation = $helpers->getTranslation();
 37+ $short = strpos( $translation, "\n" ) === false && strlen($translation) < 200;
 38+ $textareaParams = array(
 39+ 'name' => 'text',
 40+ 'class' => 'mw-translate-edit-area',
 41+ 'rows' => $short ? 2: 10,
 42+ );
 43+ $textarea = Html::element( 'textarea', $textareaParams, $translation );
 44+
 45+ $hidden = array();
 46+ $hidden[] = Xml::hidden( 'title', $this->getTitle()->getPrefixedDbKey() );
 47+ if ( isset($data['revisions'][0]['timestamp']) )
 48+ $hidden[] = Xml::hidden( 'basetimestamp', $data['revisions'][0]['timestamp'] );
 49+ $hidden[] = Xml::hidden( 'starttimestamp', $data['starttimestamp'] );
 50+ $hidden[] = Xml::hidden( 'token', $data['edittoken'] );
 51+ $hidden[] = Xml::hidden( 'format', 'json' );
 52+ $hidden[] = Xml::hidden( 'action', 'edit' );
 53+
 54+ $summary = Xml::inputLabel( wfMsg( 'summary' ), 'summary', 'summary', 40 );
 55+ $save = Html::input( 'submit', wfMsg( 'savearticle' ), 'submit' );
 56+
 57+ $formParams = array(
 58+ 'action' => "{$wgServer}{$wgScriptPath}/api.php",
 59+ 'method' => 'post',
 60+ );
 61+ $form = Html::element( 'form', $formParams,
 62+ implode( "\n", $hidden ) . "\n" .
 63+ $helpers->getBoxes() . "\n" .
 64+ "$textarea\n$summary$save"
 65+ );
 66+
 67+ echo $form;
 68+ }
 69+
 70+ protected function getEditInfo() {
 71+ $params = new FauxRequest( array(
 72+ 'action' => 'query',
 73+ 'prop' => 'info|revisions',
 74+ 'intoken' => 'edit',
 75+ 'titles' => $this->getTitle(),
 76+ 'rvprop' => 'timestamp',
 77+ ));
 78+
 79+ $api = new ApiMain($params);
 80+ $api->execute();
 81+ $data = $api->getResultData();
 82+ $data = $data['query']['pages'];
 83+ $data = array_shift($data);
 84+ return $data;
 85+ }
 86+
 87+ public static function jsEdit( Title $title ) {
 88+ global $wgUser;
 89+
 90+ if ( !$wgUser->isAllowed( 'translate' ) ) return array();
 91+ if ( !$wgUser->getOption( 'translate-jsedit' ) ) return array();
 92+
 93+ $jsTitle = Xml::escapeJsString( $title->getPrefixedDbKey() );
 94+ return array( 'onclick' => "return trlOpenJsEdit( \"$jsTitle\" );" );
 95+ }
 96+
 97+}
\ No newline at end of file
Property changes on: trunk/extensions/Translate/utils/TranslationEditPage.php
___________________________________________________________________
Name: svn:eol-style
198 + native
Index: trunk/extensions/Translate/utils/UserToggles.php
@@ -45,6 +45,16 @@
4646 return true;
4747 }
4848
 49+ public static function translationJsedit( $user, &$preferences ) {
 50+ $preferences['translate-jsedit'] = array(
 51+ 'class' => 'HTMLCheckField',
 52+ 'section' => 'editing/translate',
 53+ 'label-message' => 'translate-pref-jsedit',
 54+ );
 55+
 56+ return true;
 57+ }
 58+
4959 protected static function languageSelector() {
5060 global $wgLang;
5161 if ( is_callable( array( 'LanguageNames', 'getNames' ) ) ) {
Index: trunk/extensions/Translate/utils/TranslationHelpers.php
@@ -0,0 +1,396 @@
 2+<?php
 3+
 4+class TranslationHelpers {
 5+ protected $title;
 6+ protected $page;
 7+ protected $targetLanguage;
 8+ protected $group;
 9+ protected $translation;
 10+ protected $definition;
 11+
 12+ public function __construct( Title $title ) {
 13+ $this->title = $title;
 14+ $this->init();
 15+ }
 16+
 17+ protected function init() {
 18+ $title = $this->title;
 19+ list( $page, $code ) = self::figureMessage( $title );
 20+
 21+ $this->page = $page;
 22+ $this->targetLanguage = $code;
 23+ $this->group = self::getMessageGroup( $title->getNamespace(), $page );
 24+ }
 25+
 26+ public static function figureMessage( Title $title ) {
 27+ $text = $title->getDBkey();
 28+ $pos = strrpos( $text, '/' );
 29+ if ( $pos === false ) {
 30+ $code = '';
 31+ $key = $text;
 32+ } else {
 33+ $code = substr( $text, $pos + 1 );
 34+ $key = substr( $text, 0, $pos );
 35+ }
 36+ return array( $key, $code );
 37+ }
 38+
 39+ /**
 40+ * Tries to determine to which group this message belongs. It tries to get
 41+ * group id from loadgroup GET-paramater, but fallbacks to messageIndex file
 42+ * if no valid group was provided.
 43+ * @param $namespace int The namespace where the page is.
 44+ * @param $key string The message key we are interested in.
 45+ * @return MessageGroup which the key belongs to, or null.
 46+ */
 47+ protected static function getMessageGroup( $namespace, $key ) {
 48+ global $wgRequest;
 49+ $group = $wgRequest->getText( 'loadgroup', '' );
 50+ $mg = MessageGroups::getGroup( $group );
 51+
 52+ # If we were not given group
 53+ if ( $mg === null ) {
 54+ $group = TranslateUtils::messageKeyToGroup( $namespace, $key );
 55+ if ( $group ) {
 56+ $mg = MessageGroups::getGroup( $group );
 57+ }
 58+ }
 59+
 60+ return $mg;
 61+ }
 62+
 63+
 64+ public function getDefinition() {
 65+ if ( $this->definition !== null ) return $this->definition;
 66+ $this->definition = $this->group->getMessage( $this->page, 'en' );
 67+ return $this->definition;
 68+ }
 69+
 70+ public function getTranslation() {
 71+ if ( $this->translation !== null ) return $this->translation;
 72+
 73+ // Shoter names
 74+ $page = $this->page;
 75+ $code = $this->targetLanguage;
 76+
 77+ // Try database first
 78+ $translation = TranslateUtils::getMessageContent(
 79+ $page, $code, $this->group->getNamespace()
 80+ );
 81+
 82+ if ( $translation !== null ) {
 83+ if ( !TranslateEditAddons::hasFuzzyString( $translation ) && TranslateEditAddons::isFuzzy( $this->title ) ) {
 84+ $translation = TRANSLATE_FUZZY . $translation;
 85+ }
 86+ } elseif ( !$this->group instanceof FileBasedMessageGroup ) {
 87+ // Then try to load from files (old groups)
 88+ $translation = $this->group->getMessage( $page, $code );
 89+ } else {
 90+ // Nothing to prefil
 91+ $translation = '';
 92+ }
 93+ $this->translation = $translation;
 94+ return $translation;
 95+ }
 96+
 97+ public function getBoxes( $types = null ) {
 98+ if ( !$this->group ) return '';
 99+
 100+ // Box filter
 101+ $all = array(
 102+ 'other-languages' => array( $this, 'getOtherLanguagesBox' ),
 103+ 'translation-memory' => array( $this, 'getTmBox' ),
 104+ 'separator' => array( $this, 'getSeparatorBox' ),
 105+ 'documenation' => array( $this, 'getDocumentationBox' ),
 106+ 'definition' => array( $this, 'getDefinitionBox' ),
 107+ 'check' => array( $this, 'getCheckBox' ),
 108+ );
 109+ if ( $types !== null ) foreach( $types as $type ) unset($all[$type]);
 110+
 111+ $boxes = array();
 112+ foreach ( $all as $type => $cb ) {
 113+ $box = call_user_func( $cb );
 114+ if ( $box ) $boxes[$type] = $box;
 115+ }
 116+
 117+ if ( count($boxes) ) {
 118+ return Html::element( 'div', array( 'class' => 'mw-sp-translate-edit-fields' ), implode( "\n\n", $boxes ) );
 119+ } else {
 120+ throw new MWException( "no boxes" );
 121+ }
 122+ }
 123+
 124+ /**
 125+ * Returns suggestions from a translation memory.
 126+ * @return Html fieldset snippet which contains the suggestions.
 127+ */
 128+ protected function getTmBox() {
 129+ global $wgTranslateTM;
 130+ if ( $wgTranslateTM === false ) return null;
 131+
 132+ // Needed data
 133+ $code = $this->targetLanguage;
 134+ $definition = $this->getDefinition();
 135+
 136+ $boxes = array();
 137+
 138+ // Fetch suggestions
 139+ $server = $wgTranslateTM['server'];
 140+ $port = $wgTranslateTM['port'];
 141+ $timeout= $wgTranslateTM['timeout'];
 142+ $def = rawurlencode( $definition );
 143+ $url = "$server:$port/tmserver/en/$code/unit/$def";
 144+ $suggestions = Http::get( $url, $timeout );
 145+
 146+ // Parse suggestions, but limit to three (in case there would be more)
 147+ if ( $suggestions !== false ) {
 148+ $suggestions = json_decode( $suggestions, true );
 149+ $suggestions = array_slice( $suggestions, 0, 3 );
 150+ foreach ( $suggestions as $s ) {
 151+ $label = wfMsgHtml( 'translate-edit-tmmatch' , sprintf( '%.2f', $s['quality'] ) );
 152+ $text = TranslateUtils::convertWhiteSpaceToHTML( $s['target'] );
 153+
 154+ $text = TranslateUtils::convertWhiteSpaceToHTML( $text );
 155+ $params = array( 'class' => 'mw-sp-translate-edit-tmsug', 'title' => $s['source'] );
 156+ $boxes[] = Html::element( 'div', $params, self::legend( $label ) . $text . self::clear() );
 157+ }
 158+ }
 159+
 160+ // Enclose if there is more than one box
 161+ if ( count($boxes) ) {
 162+ $sep = Html::element( 'hr', array( 'class' => 'mw-translate-sep' ) );
 163+ return TranslateUtils::fieldset( wfMsgHtml( 'translate-edit-tmsugs' ),
 164+ implode( "$sep\n", $boxes ), array( 'class' => 'mw-translate-edit-tmsugs' ) );
 165+ } else {
 166+ return null;
 167+ }
 168+ }
 169+
 170+ protected function getDefinitionBox() {
 171+ $en = $this->getDefinition();
 172+ if ( $en === null ) return null;
 173+
 174+ global $wgUser;
 175+ $label = " ()";
 176+ $title = $wgUser->getSkin()->link(
 177+ SpecialPage::getTitleFor( 'Translate' ),
 178+ htmlspecialchars( $this->group->getLabel() ),
 179+ array(),
 180+ array(
 181+ 'group' => $this->group->getId(),
 182+ 'language' => $this->targetLanguage
 183+ )
 184+ );
 185+
 186+ $label =
 187+ wfMsg( 'translate-edit-definition' ) .
 188+ wfMsg( 'word-separator') .
 189+ wfMsg( 'parentheses', $title );
 190+
 191+ $msg = Html::element( 'span',
 192+ array( 'class' => 'mw-translate-edit-deftext' ),
 193+ TranslateUtils::convertWhiteSpaceToHTML( $en )
 194+ );
 195+
 196+ $class = array( 'class' => 'mw-sp-translate-edit-definition mw-translate-edit-definition' );
 197+ return TranslateUtils::fieldset( $label, $msg, $class );
 198+ }
 199+
 200+ protected function getCheckBox() {
 201+ global $wgTranslateDocumentationLanguageCode;
 202+
 203+ $page = $this->page;
 204+ $translation = $this->getTranslation();
 205+ $code = $this->targetLanguage;
 206+ $en = $this->getDefinition();
 207+
 208+ if ( strval($translation) === '' ) return null;
 209+ if( $code === $wgTranslateDocumentationLanguageCode) return null;
 210+
 211+ $checker = $this->group->getChecker();
 212+ if ( !$checker ) return null;
 213+
 214+ $message = new FatMessage( $page, $en );
 215+ // Take the contents from edit field as a translation
 216+ $message->setTranslation( $translation );
 217+
 218+ $checks = $checker->checkMessage( $message, $code );
 219+ if ( !count($checks) ) return null;
 220+
 221+ $checkMessages = array();
 222+ foreach ( $checks as $checkParams ) {
 223+ array_splice( $checkParams, 1, 0, 'parseinline' );
 224+ $checkMessages[] = call_user_func_array( 'wfMsgExt', $checkParams );
 225+ }
 226+
 227+ return TranslateUtils::fieldset(
 228+ wfMsgHtml( 'translate-edit-warnings' ), implode( '<hr />', $checkMessages ),
 229+ array( 'class' => 'mw-sp-translate-edit-warnings' )
 230+ );
 231+ }
 232+
 233+ protected function getOtherLanguagesBox() {
 234+ global $wgLang, $wgUser;
 235+
 236+ $code = $this->targetLanguage;
 237+ $page = $this->page;
 238+ $ns = $this->title->getNamespace();
 239+
 240+ $boxes = array();
 241+ foreach ( self::getFallbacks( $code ) as $fbcode ) {
 242+ $text = TranslateUtils::getMessageContent( $page, $fbcode, $ns );
 243+ if ( $text === null ) continue;
 244+
 245+ $label =
 246+ TranslateUtils::getLanguageName( $fbcode, false, $wgLang->getCode() ) .
 247+ wfMsg( 'word-separator' ) .
 248+ wfMsg( 'parentheses', wfBCP47( $fbcode ) );
 249+
 250+ $target = Title::makeTitleSafe( $ns, "$page/$fbcode" );
 251+ if ( $target ) {
 252+ $label = self::editLink( $target,
 253+ htmlspecialchars($label), array( 'action' => 'edit' )
 254+ );
 255+ }
 256+
 257+ $text = TranslateUtils::convertWhiteSpaceToHTML( $text );
 258+ $params = array( 'class' => 'mw-translate-edit-item' );
 259+ $boxes[] = Html::element( 'div', $params, self::legend( $label ) . $text . self::clear() );
 260+ }
 261+
 262+ if ( count($boxes) ) {
 263+ $sep = Html::element( 'hr', array( 'class' => 'mw-translate-sep' ) );
 264+ return TranslateUtils::fieldset( wfMsgHtml( 'translate-edit-in-other-languages' , $page ),
 265+ implode( "$sep\n", $boxes ), array( 'class' => 'mw-sp-translate-edit-inother' ) );
 266+ }
 267+
 268+ return null;
 269+ }
 270+
 271+ public function getSeparatorBox() {
 272+ return Html::element( 'div', array( 'class' => 'mw-translate-edit-extra' ) );
 273+ }
 274+
 275+ public function getDocumentationBox() {
 276+ global $wgTranslateDocumentationLanguageCode, $wgUser, $wgOut;
 277+
 278+ if ( !$wgTranslateDocumentationLanguageCode ) return null;
 279+ $page = $this->page;
 280+ $ns = $this->title->getNamespace();
 281+
 282+ $title = Title::makeTitle( $ns, $page . '/' . $wgTranslateDocumentationLanguageCode );
 283+ $edit = $wgUser->getSkin()->link(
 284+ $title,
 285+ wfMsgHtml( 'translate-edit-contribute' ),
 286+ array(),
 287+ array( 'action' => 'edit' )
 288+ );
 289+ $info = TranslateUtils::getMessageContent( $page, $wgTranslateDocumentationLanguageCode, $ns );
 290+
 291+ $class = 'mw-sp-translate-edit-info';
 292+ if ( $info === null ) {
 293+ $info = wfMsg( 'translate-edit-no-information' );
 294+ $class = 'mw-sp-translate-edit-noinfo';
 295+ }
 296+
 297+ if ( $this->group instanceof GettextMessageGroup ) {
 298+ $reader = $this->group->getReader( 'en' );
 299+ if ( $reader ) {
 300+ $data = $reader->parseFile();
 301+ $help = GettextFormatWriter::formatcomments( @$data[$key]['comments'], false, @$data[$key]['flags'] );
 302+ $info .= "<hr /><pre>$help</pre>";
 303+ }
 304+ }
 305+
 306+ $class .= ' mw-sp-translate-message-documentation';
 307+
 308+ $contents = $wgOut->parse( $info );
 309+ // Remove whatever block element wrapup the parser likes to add
 310+ $contents = preg_replace( '~^<([a-z]+)>(.*)</\1>$~us', '\2', $contents );
 311+ return TranslateUtils::fieldset(
 312+ wfMsgHtml( 'translate-edit-information', $edit , $page ), $contents, array( 'class' => $class )
 313+ );
 314+
 315+ }
 316+
 317+ protected static function legend( $label ) {
 318+ return Html::element( 'div', array( 'class' => 'mw-translate-legend' ), $label );
 319+ }
 320+
 321+ protected static function clear() {
 322+ return Html::element( 'div', array( 'style' => 'clear:both;' ) );
 323+ }
 324+
 325+ protected static function getFallbacks( $code ) {
 326+ global $wgUser, $wgTranslateLanguageFallbacks;
 327+
 328+ // User preference has the final say
 329+ $preference = $wgUser->getOption( 'translate-editlangs' );
 330+ if ( $preference !== 'default' ) {
 331+ $fallbacks = array_map( 'trim', explode( ',', $preference ) );
 332+ foreach( $fallbacks as $k => $v ) if ( $v === $code ) unset($fallbacks[$k]);
 333+ return $fallbacks;
 334+ }
 335+
 336+ // Global configuration settings
 337+ $fallbacks = array();
 338+ if ( isset( $wgTranslateLanguageFallbacks[$code] ) ) {
 339+ $fallbacks = (array) $wgTranslateLanguageFallbacks[$code];
 340+ }
 341+
 342+ // And the real fallback
 343+ // TODO: why only one?
 344+ $realFallback = $code ? Language::getFallbackFor( $code ) : false;
 345+ if ( $realFallback && $realFallback !== 'en' ) {
 346+ $fallbacks = array_merge( array( $realFallback ), $fallbacks );
 347+ }
 348+
 349+ return $fallbacks;
 350+ }
 351+
 352+ protected function doBox( $msg, $code, $title = false, $makelink = false ) {
 353+ global $wgUser, $wgLang;
 354+
 355+ $name = TranslateUtils::getLanguageName( $code, false, $wgLang->getCode() );
 356+ $code = wfBCP47( $code );
 357+
 358+ $skin = $wgUser->getSkin();
 359+
 360+ $attributes = array();
 361+ if ( !$title ) {
 362+ $attributes['class'] = 'mw-sp-translate-in-other-big';
 363+ } elseif ( $code === 'en' ) {
 364+ $attributes['class'] = 'mw-sp-translate-edit-definition';
 365+ } else {
 366+ $attributes['class'] = 'mw-sp-translate-edit-committed';
 367+ }
 368+ if ( mb_strlen( $msg ) < 100 && !$title ) {
 369+ $attributes['class'] = 'mw-sp-translate-in-other-small';
 370+ }
 371+
 372+ $msg = TranslateUtils::convertWhiteSpaceToHTML( $msg );
 373+
 374+ if ( !$title ) $title = "$name ($code)";
 375+
 376+ if( $makelink ) {
 377+ $linkTitle = Title::newFromText( $makelink );
 378+ $title = $skin->link(
 379+ $linkTitle,
 380+ htmlspecialchars( $title ),
 381+ array(),
 382+ array( 'action' => 'edit' )
 383+ );
 384+ }
 385+
 386+ return TranslateUtils::fieldset( $title, Html::element( 'span', null, $msg ), $attributes );
 387+ }
 388+
 389+
 390+ public static function editLink( $target, $text, $params = array() ) {
 391+ global $wgUser;
 392+
 393+ $jsEdit = TranslationEditPage::jsEdit( $target );
 394+
 395+ return $wgUser->getSkin()->link( $target, $text, $jsEdit, $params );
 396+ }
 397+}
\ No newline at end of file
Property changes on: trunk/extensions/Translate/utils/TranslationHelpers.php
___________________________________________________________________
Name: svn:eol-style
1398 + native
Index: trunk/extensions/Translate/Translate.i18n.php
@@ -71,7 +71,7 @@
7272 'translate-edit-committed' => 'Current translation in software',
7373 'translate-edit-warnings' => 'Warnings about incomplete translations',
7474 'translate-edit-tmsugs' => 'Suggestions from translation memory',
75 - 'translate-edit-tmsug' => '$1% match in translation memory',
 75+ 'translate-edit-tmmatch' => '$1% match',
7676
7777 'translate-edit-goto-no-prev' => 'No previous message',
7878 'translate-edit-goto-no-next' => 'No next message',
@@ -144,8 +144,11 @@
145145 The default list of languages depends on your language.',
146146 'translate-pref-editassistlang-bad' => 'Invalid language code in the list:
147147 <nowiki>$1</nowiki>.',
 148+ 'translate-pref-jsedit' => 'Enhanced translation editor (JavaScript)',
148149
149 - 'right-translate' => 'Edit using the translate interface',
 150+ 'right-translate' => 'Edit using the translate interface',
 151+ 'right-translate-manage' => 'Manage translation groups',
 152+ 'right-translate-import' => 'Import offline translations',
150153
151154 'translate-rc-translation-filter' => 'Filter translations:',
152155 'translate-rc-translation-filter-no' => 'Do nothing',

Status & tagging log