jenkins-bot has submitted this change and it was merged.

Change subject: Source Selector: Refactor source selector
......................................................................


Source Selector: Refactor source selector

* Updates error message logic for when:
   * Source page does not exist
   * An equivalent target page exists
   * An equivalent target page exists and the chosen target title is in use
   * The chosen target title is in use

* Start translation button only enabled after entering a source title
  and choosing languages; disabled if source page does not exist

* New labels and message strings: From, To, placeholders

* Styling improvements to match design mocks

* Start translation on enter key if necessary fields are filled in

Change-Id: I6dbf7bfe79d5885e5507f3b3a22a488f8fbab6c0
---
M Resources.php
M i18n/en.json
M i18n/qqq.json
M modules/source/ext.cx.source.selector.js
M modules/source/styles/ext.cx.source.selector.less
5 files changed, 409 insertions(+), 32 deletions(-)

Approvals:
  Santhosh: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/Resources.php b/Resources.php
index 328a56d..61cad91 100644
--- a/Resources.php
+++ b/Resources.php
@@ -148,6 +148,12 @@
                'cx-sourceselector-dialog-button-start-translation',
                'cx-sourceselector-dialog-source-language-label',
                'cx-sourceselector-dialog-target-language-label',
+               'cx-sourceselector-dialog-source-title-placeholder',
+               'cx-sourceselector-dialog-target-title-placeholder',
+               'cx-sourceselector-dialog-error-page-and-title-exist',
+               'cx-sourceselector-dialog-error-page-exists',
+               'cx-sourceselector-dialog-error-title-in-use',
+               'cx-sourceselector-dialog-error-no-source-article',
        ),
 ) + $resourcePaths;
 
diff --git a/i18n/en.json b/i18n/en.json
index c34d025..7d0e982 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -79,8 +79,14 @@
        "cx-error-page-not-found": "The \"$1\" page could not be found in $2 
Wikipedia",
        "cx-sourceselector-dialog-new-translation": "New translation",
        "cx-sourceselector-dialog-button-start-translation": "Start 
translation",
-       "cx-sourceselector-dialog-source-language-label": "Source:",
-       "cx-sourceselector-dialog-target-language-label": "Target:",
+       "cx-sourceselector-dialog-source-language-label": "From:",
+       "cx-sourceselector-dialog-target-language-label": "To:",
+       "cx-sourceselector-dialog-source-title-placeholder": "Search for source 
article",
+       "cx-sourceselector-dialog-target-title-placeholder": "Translation title 
(if different from source)",
+       "cx-sourceselector-dialog-error-page-and-title-exist": "The page 
already exists in [$1 $2] and the title is used by [$3 a different article]",
+       "cx-sourceselector-dialog-error-page-exists": "The page [$1 already 
exists] in $2",
+       "cx-sourceselector-dialog-error-title-in-use": "The title for the new 
page is [$1 already in use]",
+       "cx-sourceselector-dialog-error-no-source-article": "The page to 
translate does not exist in $1",
        "cx-mt-abuse-warning-title": "Your translation contains $1% of 
unmodified machine-translated text",
        "cx-mt-abuse-warning-text": "Machine translations are provided only as 
a template. You need to make sure that the content is accurate and reads 
naturally in your language.",
        "cx-publish-captcha-title": "Security question",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index a3d413f..455086d 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -85,6 +85,12 @@
        "cx-sourceselector-dialog-button-start-translation": "Button label for 
translation selector. Clicking on it starts a new translation in 
Special:ContentTranslation.",
        "cx-sourceselector-dialog-source-language-label": "Label text for 
source language and title selector.\n{{Identical|Source}}",
        "cx-sourceselector-dialog-target-language-label": "Label text for 
target language and title selector.\n{{Identical|Target}}",
+       "cx-sourceselector-dialog-source-title-placeholder": "Placeholder for 
the source title input. Provides prompt to search for source title.",
+       "cx-sourceselector-dialog-target-title-placeholder": "Placeholder for 
the target title input. Provides prompt to enter translation title (optional).",
+       "cx-sourceselector-dialog-error-page-and-title-exist": "Error that 
indicates there is page in the target wiki with the same title as the proposed 
source title and the target title is used elsewhere.\n\nParameters:\n* $1 - 
link to existing target article.\n* $2 - target language name.\n* $3 - link to 
article using proposed target title.",
+       "cx-sourceselector-dialog-error-page-exists": "Error that indicates 
there is a page in the target wiki with the same title as the proposed source 
title.\n\nParameters:\n* $1 - link to existing target article.\n* $2 - target 
language name.",
+       "cx-sourceselector-dialog-error-title-in-use": "Error that indicates 
there is already an article in the target wiki with the same title as the 
proposed target title.\n\nParameters:\n* $1 - link to target article with same 
title",
+       "cx-sourceselector-dialog-error-no-source-article": "Error that 
indicates there is no page with the specified title in the source language to 
translate.\n\nParameters:\n* $1 - the source language",
        "cx-mt-abuse-warning-title": "Title text shown in machine translation 
abuse card.\n* $1: Percentage of machine translation",
        "cx-mt-abuse-warning-text": "Detailed explanation of machine 
translation abuse.",
        "cx-publish-captcha-title": "Title of captcha form while publishing the 
translation",
diff --git a/modules/source/ext.cx.source.selector.js 
b/modules/source/ext.cx.source.selector.js
index 956ac59..a561f21 100644
--- a/modules/source/ext.cx.source.selector.js
+++ b/modules/source/ext.cx.source.selector.js
@@ -29,6 +29,9 @@
                this.$targetLanguage = null;
                this.sourceLanguage = null;
                this.targetLanguage = null;
+               this.$messageBar = null;
+               this.$sourceTitleInput = null;
+               this.$targetTitleInput = null;
                this.init();
        }
 
@@ -126,26 +129,334 @@
         * Listen for events.
         */
        CXSourceSelector.prototype.listen = function () {
-               var selector = this;
-
                // Open or close the dialog when clicking the link.
                // The dialog will be unitialized until the first click.
                this.$trigger.click( $.proxy( this.show, this ) );
 
-               this.$sourceTitleInput.on( 'input', $.debounce( 100, false, 
function () {
-                       selector.sourceLanguage = 
selector.$sourceLanguage.val();
-                       selector.searchTitles( selector.sourceLanguage, $( this 
).val() ).done( function ( response ) {
-                               var i, len, suggestions = response[ 1 ];
-                               selector.$titleList.empty();
-                               if ( suggestions.length ) {
-                                       for ( i = 0, len = suggestions.length; 
i < len; i++ ) {
-                                               selector.$titleList.append( $( 
'<option>' ).attr( 'value', suggestions[ i ] ) );
+               // Source title input input (search titles)
+               this.$sourceTitleInput.on(
+                       'input',
+                       $.debounce( 100, false, $.proxy( this.searchHandler, 
this ) )
+               );
+
+               // Source language selector change (fill target languages, 
localStorage, check)
+               this.$sourceLanguage.on(
+                       'change',
+                       $.proxy( this.sourceLanguageChangeHandler, this )
+               );
+
+               // Target language selector change (localStorage, check)
+               this.$targetLanguage.on(
+                       'change',
+                       $.proxy( this.targetLanguageChangeHandler, this )
+               );
+
+               // Source title input or target title input, blur or search 
(check)
+               this.$dialog.on(
+                       'blur search',
+                       '.cx-sourceselector-dialog__title',
+                       $.proxy( this.check, this )
+               );
+
+               // Keypress (start translation on enter key)
+               this.$dialog.on(
+                       'keypress',
+                       '.cx-sourceselector-dialog__title',
+                       $.proxy( this.enterKeyHandler, this )
+               );
+       };
+
+       /**
+        * Handles searching for titles based on source title input
+        */
+       CXSourceSelector.prototype.searchHandler = function () {
+               var selector = this;
+
+               this.sourceLanguage = this.$sourceLanguage.val();
+               this.searchTitles( this.sourceLanguage, 
this.$sourceTitleInput.val() ).done( function ( response ) {
+                       var i, len, suggestions = response[ 1 ];
+                       selector.$titleList.empty();
+                       if ( suggestions.length ) {
+                               for ( i = 0, len = suggestions.length; i < len; 
i++ ) {
+                                       selector.$titleList.append( $( 
'<option>' ).attr( 'value', suggestions[ i ] ) );
+                               }
+                       }
+               } );
+       };
+
+       /**
+        * Handles source language change
+        */
+       CXSourceSelector.prototype.sourceLanguageChangeHandler = function () {
+               this.fillTargetLanguages();
+               if ( localStorage ) {
+                       localStorage.cxSourceLanguage = 
this.$sourceLanguage.val();
+                       localStorage.cxTargetLanguage = 
this.$targetLanguage.val();
+               }
+               this.check();
+       };
+
+       /**
+        * Handles target language change
+        */
+       CXSourceSelector.prototype.targetLanguageChangeHandler = function () {
+               if ( localStorage ) {
+                       localStorage.cxTargetLanguage = 
this.$targetLanguage.val();
+               }
+               this.check();
+       };
+
+       /**
+        * Handles enter keypress
+        */
+       CXSourceSelector.prototype.enterKeyHandler = function ( e ) {
+               var sourceLanguage = this.$sourceLanguage.val(),
+                       sourceTitle = this.$sourceTitleInput.val().trim(),
+                       selector = this;
+
+               if ( e.which === 13 && sourceTitle !== '' ) {
+                       this.checkForTitle( sourceLanguage, sourceTitle )
+                               .done( function ( response ) {
+                                       if ( response !== false ) {
+                                               selector.startPageInCX();
                                        }
+                               } );
+               }
+       };
+
+       /**
+        * Checks source and target inputs for errors.
+        */
+       CXSourceSelector.prototype.check = function () {
+               var sourceLanguage = this.$sourceLanguage.val(),
+                       targetLanguage = this.$targetLanguage.val(),
+                       sourceTitle = this.$sourceTitleInput.val().trim(),
+                       targetTitle = this.$targetTitleInput.val().trim(),
+                       selector = this;
+
+               this.$messageBar.hide();
+
+               // if source title is blank, disable button and skip validation
+               if ( sourceTitle === '' ) {
+                       selector.$translateFromButton.prop( 'disabled', true );
+                       return;
+               }
+
+               // check to see if the specified source article exists
+               this.checkForTitle( sourceLanguage, sourceTitle )
+                       .done( function ( sourcePage ) {
+                               // if no source page to translate disable 
button and show error
+                               // skip rest of validation checks
+                               if ( !sourcePage && sourceTitle !== '' ) {
+                                       selector.$translateFromButton.prop( 
'disabled', true );
+                                       selector.showSourceTitleError( 
sourceLanguage );
+                               } else {
+                                       selector.$translateFromButton.prop( 
'disabled', false );
+                                       // check to see if there is a matching 
article in the target wiki
+                                       // the matching article may or may not 
have the same title
+                                       selector.checkForEquivalentTargetPage(
+                                               sourceLanguage,
+                                               targetLanguage,
+                                               sourceTitle
+                                       )
+                                               .done( function ( 
equivalentTargetPage ) {
+                                                       // check to see if the 
specified target title is in use
+                                                       // must be nested 
inside check for matching target article
+                                                       // because first 
possible error requires results of both api calls
+                                                       selector.checkForTitle( 
targetLanguage, targetTitle )
+                                                               .done( function 
( existingTargetTitle ) {
+                                                                       // if 
there is a matching target page and
+                                                                       // the 
specified target title is in use
+                                                                       if ( 
equivalentTargetPage && existingTargetTitle ) {
+                                                                               
selector.showPageExistsAndTitleInUseError(
+                                                                               
        equivalentTargetPage,
+                                                                               
        existingTargetTitle
+                                                                               
);
+                                                                               
// if there is just an matching target page
+                                                                       } else 
if ( equivalentTargetPage ) {
+                                                                               
selector.showPageExistsError( equivalentTargetPage );
+                                                                               
// if the specified target title is in use
+                                                                       } else 
if ( existingTargetTitle ) {
+                                                                               
selector.showTitleInUseError( existingTargetTitle );
+                                                                       }
+                                                               } );
+                                               } );
                                }
                        } );
-               } ) );
+       };
 
-               this.$sourceLanguage.on( 'change', $.proxy( 
this.fillTargetLanguages, this ) );
+       /**
+        * Checks to see if a title exists in the specified language wiki
+        * @param {string} language the language of the wiki to check
+        * @param {string} title the title to look for
+        * return {jQuery.promise}
+        */
+       CXSourceSelector.prototype.checkForTitle = function ( language, title ) 
{
+               var api = this.siteMapper.getApi( language ),
+                       $deferred = $.Deferred();
+
+               api.get( {
+                       action: 'opensearch',
+                       search: title,
+                       namespace: 0,
+                       format: 'json',
+                       limit: 1
+               }, {
+                       dataType: 'jsonp',
+                       // This prevents warnings about the unrecognized 
parameter "_"
+                       cache: true
+               } )
+                       .done( function ( response ) {
+                               if ( response[ 1 ][ 0 ] === title ) {
+                                       $deferred.resolve( title );
+                               } else {
+                                       $deferred.resolve( false );
+                               }
+                       } )
+                       .fail( function () {
+                               $deferred.resolve( false );
+                       } );
+               return $deferred.promise();
+       };
+
+       /**
+        * Checks for an equivalent page in the target wiki based on sourceTitle
+        * @param {string} sourceLanguage the source language
+        * @param {string} targetLanguage the target language
+        * @param {string} sourceTitle the title to check
+        * @return {jQuery.promise}
+        */
+       CXSourceSelector.prototype.checkForEquivalentTargetPage = function (
+               sourceLanguage,
+               targetLanguage,
+               sourceTitle
+       ) {
+               var api = this.siteMapper.getApi( sourceLanguage ),
+                       $deferred = $.Deferred();
+
+               api.get( {
+                       action: 'query',
+                       prop: 'langlinks',
+                       titles: sourceTitle,
+                       lllang: targetLanguage,
+                       lllimit: 1,
+                       redirects: true,
+                       format: 'json'
+               }, {
+                       dataType: 'jsonp',
+                       cache: true
+               } ).done( function ( response ) {
+                       var equivalentTargetPage = false;
+                       if ( response.query && response.query.pages ) {
+                               $.each( response.query.pages, function ( 
pageId, page ) {
+                                       if ( page.langlinks ) {
+                                               equivalentTargetPage = 
page.langlinks[ 0 ][ '*' ];
+                                       }
+                               } );
+                       }
+                       $deferred.resolve( equivalentTargetPage );
+               } ).fail( function () {
+                       $deferred.resolve( false );
+               } );
+
+               return $deferred.promise();
+       };
+
+       /**
+        * Shows error for source page not existing
+        * @param {string} sourceLanguage the source language language code
+        */
+       CXSourceSelector.prototype.showSourceTitleError = function ( 
sourceLanguage ) {
+               var sourceLanguageDisplay, message;
+
+               sourceLanguageDisplay = $.uls.data.getAutonym( sourceLanguage );
+               message = mw.message(
+                       'cx-sourceselector-dialog-error-no-source-article',
+                       sourceLanguageDisplay
+               );
+               this.showMessage( message );
+       };
+
+       /**
+        * Shows error for target page existing and target title in use
+        * @param {string} equivalentTargetPage the title of the existing page
+        * @param {string} existingTargetTitle the title already in use
+        */
+       CXSourceSelector.prototype.showPageExistsAndTitleInUseError = function (
+               equivalentTargetPage,
+               existingTargetTitle
+       ) {
+               var targetLanguage, equivalentTargetPageLink, 
targetLanguageDisplay,
+                       existingTargetTitleLink, message;
+
+               targetLanguage = this.$targetLanguage.val();
+               equivalentTargetPageLink = this.siteMapper
+                       .getPageUrl( targetLanguage, equivalentTargetPage );
+               targetLanguageDisplay = $.uls.data.getAutonym( targetLanguage );
+               existingTargetTitleLink = this.siteMapper
+                       .getPageUrl( targetLanguage, existingTargetTitle );
+               message = mw.message(
+                       'cx-sourceselector-dialog-error-page-and-title-exist',
+                       equivalentTargetPageLink,
+                       targetLanguageDisplay,
+                       existingTargetTitleLink
+               );
+               this.showMessage( message );
+       };
+
+       /**
+        * Shows error for page already existing in target
+        * @param {string} equivalentTargetPage the title of the existing page
+        */
+       CXSourceSelector.prototype.showPageExistsError = function ( 
equivalentTargetPage ) {
+               var targetLanguage, equivalentTargetPageLink,
+                       targetLanguageDisplay, message;
+
+               targetLanguage = this.$targetLanguage.val();
+               equivalentTargetPageLink = this.siteMapper
+                       .getPageUrl( targetLanguage, equivalentTargetPage );
+               targetLanguageDisplay = $.uls.data.getAutonym( targetLanguage );
+               message = mw.message(
+                       'cx-sourceselector-dialog-error-page-exists',
+                       equivalentTargetPageLink, targetLanguageDisplay
+               );
+               this.showMessage( message );
+       };
+
+       /**
+        * Shows error for title already in use in target wiki
+        * @param {string} existingTargetTitle the title already in use
+        */
+       CXSourceSelector.prototype.showTitleInUseError = function ( 
existingTargetTitle ) {
+               var targetLanguage, existingTargetTitleLink, message;
+
+               targetLanguage = this.$targetLanguage.val();
+               existingTargetTitleLink = this.siteMapper
+                       .getPageUrl( targetLanguage, existingTargetTitle );
+               message = mw.message(
+                       'cx-sourceselector-dialog-error-title-in-use',
+                       existingTargetTitleLink
+               );
+               this.showMessage( message );
+       };
+
+       /**
+        * Shows error message for dialog
+        * @param {mw.Message|text} message the message to show
+        */
+       CXSourceSelector.prototype.showMessage = function ( message ) {
+               var $messageText = $( 
'.cx-sourceselector-dialog__messagebar-text' );
+
+               if ( message instanceof mw.Message ) {
+                       $messageText.html( message.parse() );
+               } else {
+                       $messageText.text( message );
+               }
+
+               this.$messageBar.find( 'a' )
+                       .attr( 'target', '_blank' );
+
+               this.$messageBar.show();
        };
 
        /**
@@ -210,9 +521,16 @@
         * Start a new page translation in Special:CX
         */
        CXSourceSelector.prototype.startPageInCX = function () {
+               var targetTitle;
+
+               if ( this.$targetTitleInput.val() === '' ) {
+                       targetTitle = this.$sourceTitleInput.val();
+               } else {
+                       targetTitle = this.$targetTitleInput.val();
+               }
                location.href = this.siteMapper.getCXUrl(
                        this.$sourceTitleInput.val(),
-                       this.$targetTitleInput.val(),
+                       targetTitle,
                        this.$sourceLanguage.val(),
                        this.$targetLanguage.val()
                );
@@ -226,6 +544,7 @@
                        $sourceLanguageLabel,
                        $heading, $targetLanguageLabel,
                        $sourceInputs, $targetInputs,
+                       $messageText,
                        index;
 
                this.$dialog = $( '<div>' )
@@ -260,12 +579,15 @@
                        .attr( {
                                name: 'sourceTitle',
                                type: 'search',
-                               list: 'searchresults'
+                               list: 'searchresults',
+                               placeholder: mw.msg( 
'cx-sourceselector-dialog-source-title-placeholder' )
                        } );
+
                this.$targetTitleInput = $( '<input>' )
                        .addClass( 'cx-sourceselector-dialog__title' )
                        .attr( {
-                               name: 'targetTitle'
+                               name: 'targetTitle',
+                               placeholder: mw.msg( 
'cx-sourceselector-dialog-target-title-placeholder' )
                        } );
 
                this.$titleList = $( '<datalist>' ).prop( 'id', 'searchresults' 
);
@@ -282,10 +604,18 @@
                                this.$targetLanguage,
                                this.$targetTitleInput
                );
+               this.$messageBar = $( '<div>' )
+                       .addClass( 'cx-sourceselector-dialog__messagebar' );
+               $messageText = $( '<span>' )
+                       .addClass( 'cx-sourceselector-dialog__messagebar-text' 
);
+               this.$messageBar
+                       .append( $messageText )
+                       .hide();
 
                this.$translateFromButton = $( '<button>' )
                        .addClass( 'mw-ui-button mw-ui-progressive 
cx-sourceselector-dialog__button-translate' )
                        .text( mw.msg( 
'cx-sourceselector-dialog-button-start-translation' ) )
+                       .prop( 'disabled', true )
                        .click( $.proxy( this.startPageInCX, this ) );
 
                $actions = $( '<div>' ).addClass( 
'cx-sourceselector-dialog__actions' )
@@ -294,9 +624,25 @@
                this.$dialog.append( $heading,
                        $sourceInputs,
                        $targetInputs,
+                       this.$messageBar,
                        $actions,
                        this.$titleList
                );
+
+               if ( localStorage && localStorage.cxSourceLanguage ) {
+                       this.$sourceLanguage.children().filter( function () {
+                               return this.getAttribute( 'value' ) === 
localStorage.cxSourceLanguage;
+                       } )
+                               .prop( 'selected', true );
+                       this.fillTargetLanguages();
+               }
+
+               if ( localStorage && localStorage.cxTargetLanguage ) {
+                       this.$targetLanguage.children().filter( function () {
+                               return this.getAttribute( 'value' ) === 
localStorage.cxTargetLanguage;
+                       } )
+                               .prop( 'selected', true );
+               }
 
                $( 'body' ).append( this.$dialog );
        };
@@ -321,7 +667,7 @@
 
                        $container.empty().cxSourceSelector( {
                                top: '150px',
-                               left: '33%'
+                               left: '30%'
                        } ).click();
                } );
        } );
diff --git a/modules/source/styles/ext.cx.source.selector.less 
b/modules/source/styles/ext.cx.source.selector.less
index eb71247..a788989 100644
--- a/modules/source/styles/ext.cx.source.selector.less
+++ b/modules/source/styles/ext.cx.source.selector.less
@@ -7,7 +7,8 @@
 
 .cx-sourceselector-dialog {
        .mw-ui-grid;
-       .mw-ui-one-third;
+       .mw-ui-item;
+       .mw-ui-four-tenths;
        color: #333;
        position: absolute;
        min-width: 500px;
@@ -17,6 +18,7 @@
        border-bottom-width: 3px;
        border-radius: 3px;
        z-index: 100;
+       padding: 0;
 }
 
 .cx-sourceselector-dialog__heading {
@@ -25,37 +27,44 @@
        .mw-ui-item;
        .mw-ui-one-whole;
        padding: @vertical-margin @horizontal-margin;
-       font-size: larger;
+       font-size: 2em;
        line-height: 1.5em;
-       background-color: #fbfbfb;
+       border-bottom: 1px solid #ccc;
+       font-weight: normal;
 }
 
 .cx-sourceselector-dialog__target-inputs,
 .cx-sourceselector-dialog__source-inputs {
        .mw-ui-item;
        .mw-ui-one-whole;
+       height: 50px;
+       padding: 10px;
+       border-bottom: 1px solid #ccc;
 }
 
 .cx-sourceselector-dialog__language-label {
        .mw-ui-item;
-       .mw-ui-one-sixth;
+       .mw-ui-one-eighth;
        padding: 5px;
        font-size: large;
 }
 
 .cx-sourceselector-dialog__language {
        .mw-ui-item;
-       .mw-ui-one-third;
-       margin: 5px 0;
-       font-size: large;
+       .mw-ui-two-tenths;
+       font-size: medium;
+       height: 30px;
+       margin-top: 5px;
 }
 
 .cx-sourceselector-dialog__title {
        .mw-ui-item;
-       .mw-ui-one-half;
-       margin: 5px 0;
-       font-size: large;
+       .mw-ui-six-tenths;
+       margin: 5px 10px 0;
+       font-size: medium;
        -webkit-appearance: none;
+       border: none;
+       padding: 2px;
 
        &[type=search] {
                .background-image-svg('../../tools/images/search.svg', 
'../../tools/images/search.png');
@@ -65,16 +74,20 @@
                padding-left: 30px;
        }
 
-       border: 1px solid #ccc;
+ }
+
+ .cx-sourceselector-dialog__messagebar {
+       .mw-ui-item;
+       .mw-ui-one-whole;
+       padding: 10px;
+       background-color: #F7D358;
  }
 
 .cx-sourceselector-dialog__actions {
-       .mw-ui-item;
        .mw-ui-one-whole;
        padding: 10px 15px 15px 15px;
        text-align: right;
-       border-top: 1px solid #aaa;
-       margin-top: 20px;
+       margin-top: 10px;
 
        button {
                margin-left: 10px;

-- 
To view, visit https://gerrit.wikimedia.org/r/170657
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I6dbf7bfe79d5885e5507f3b3a22a488f8fbab6c0
Gerrit-PatchSet: 12
Gerrit-Project: mediawiki/extensions/ContentTranslation
Gerrit-Branch: master
Gerrit-Owner: Jsahleen <jsahl...@wikimedia.org>
Gerrit-Reviewer: Amire80 <amir.ahar...@mail.huji.ac.il>
Gerrit-Reviewer: Jsahleen <jsahl...@wikimedia.org>
Gerrit-Reviewer: Nikerabbit <niklas.laxst...@gmail.com>
Gerrit-Reviewer: Pginer <pgi...@wikimedia.org>
Gerrit-Reviewer: Santhosh <santhosh.thottin...@gmail.com>
Gerrit-Reviewer: Siebrand <siebr...@kitano.nl>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to