Merge "newRevisionsFromBatch: don't throw on duplicate row."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 26 Sep 2019 19:39:58 +0000 (19:39 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 26 Sep 2019 19:39:58 +0000 (19:39 +0000)
59 files changed:
RELEASE-NOTES-1.34
composer.json
docs/hooks.txt
includes/CommentStore.php
includes/DefaultSettings.php
includes/Html.php
includes/MediaWiki.php
includes/Rest/Router.php
includes/api/ApiQueryBacklinksprop.php
includes/api/ApiQueryBase.php
includes/exception/BadRequestError.php
includes/exception/ErrorPageError.php
includes/exception/PermissionsError.php
includes/exception/ThrottledError.php
includes/exception/UserNotLoggedIn.php
includes/filerepo/file/LocalFileLockError.php
includes/http/HttpRequestFactory.php
includes/installer/DatabaseInstaller.php
includes/installer/WebInstaller.php
includes/installer/WebInstallerOptions.php
includes/installer/WebInstallerRestart.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/position/MySQLMasterPos.php
includes/logging/LogPager.php
includes/parser/Parser.php
includes/specials/SpecialContributions.php
includes/specials/pagers/NewFilesPager.php
languages/i18n/ban.json
languages/i18n/bg.json
languages/i18n/exif/nap.json
languages/i18n/exif/nds-nl.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/it.json
languages/i18n/lad.json
languages/i18n/lv.json
languages/i18n/min.json
languages/i18n/nap.json
languages/i18n/nds-nl.json
languages/i18n/nl.json
languages/i18n/te.json
maintenance/Maintenance.php
maintenance/cleanupTable.inc
maintenance/userDupes.inc
resources/Resources.php
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.WatchlistTopSectionWidget.less
resources/src/mediawiki.special.recentchanges.js
resources/src/mediawiki.special/contributions.less [new file with mode: 0644]
resources/src/mediawiki.special/special.less
resources/src/mediawiki.widgets/mw.widgets.TitleOptionWidget.js
tests/phpunit/includes/CommentStoreTest.php
tests/phpunit/includes/HtmlTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php [deleted file]
tests/phpunit/unit/includes/resourceloader/ResourceLoaderImageTest.php [new file with mode: 0644]
tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js

index 508ba08..8d9b06e 100644 (file)
@@ -126,6 +126,9 @@ $wgPasswordPolicy['policies']['default']['PasswordNotInLargeBlacklist'] = false;
 * (T222388) API modules can now be specified as an ObjectFactory spec,
   allowing the construction of modules that require services to be injected
   in their constructor.
+* (T117736) The function signature of SpecialContributions::getForm::filters
+  has changed. It now expects definitions of additional filter fields as array
+  rather than string.
 
 === External library changes in 1.34 ===
 
@@ -147,6 +150,7 @@ $wgPasswordPolicy['policies']['default']['PasswordNotInLargeBlacklist'] = false;
 * Updated wikimedia/timestamp from 2.2.0 to 3.0.0.
 * Updated wikimedia/xmp-reader from 0.6.2 to 0.6.3.
 * Updated mediawiki/mediawiki-phan-config from 0.6.0 to 0.6.1 (dev-only).
+* Updated wikimedia/avro from 1.8.0 to 1.9.0 (dev-only).
 * …
 
 ==== Removed external libraries ====
index 1f2776c..bd34b92 100644 (file)
@@ -74,7 +74,7 @@
                "nmred/kafka-php": "0.1.5",
                "phpunit/phpunit": "4.8.36 || ^6.5",
                "psy/psysh": "0.9.9",
-               "wikimedia/avro": "1.8.0",
+               "wikimedia/avro": "1.9.0",
                "wikimedia/testing-access-wrapper": "~1.0",
                "wmde/hamcrest-html-matchers": "^0.1.0",
                "mediawiki/mediawiki-phan-config": "0.7.1",
index 55ba06e..4261b0b 100644 (file)
@@ -3177,7 +3177,7 @@ $row: Revision information from the database
 'SpecialContributions::getForm::filters': Called with a list of filters to render
 on Special:Contributions.
 $sp: SpecialContributions object, for context
-&$filters: List of filters rendered as HTML
+&$filters: List of filter object definitions (compatible with OOUI form)
 
 'SpecialListusersDefaultQuery': Called right before the end of
 UsersPager::getDefaultQuery().
index 994a064..9054e7a 100644 (file)
@@ -83,7 +83,8 @@ class CommentStore {
        protected $key = null;
 
        /**
-        * @var int One of the MIGRATION_* constants
+        * @var int One of the MIGRATION_* constants, or an appropriate combination
+        *  of SCHEMA_COMPAT_* constants.
         * @todo Deprecate and remove once extensions seem unlikely to need to use
         *  it for migration anymore.
         */
@@ -98,11 +99,19 @@ class CommentStore {
        /**
         * @param Language $lang Language to use for comment truncation. Defaults
         *  to content language.
-        * @param int $migrationStage One of the MIGRATION_* constants. Always
-        *  MIGRATION_NEW for MediaWiki core since 1.33.
+        * @param int $stage One of the MIGRATION_* constants, or an appropriate
+        *  combination of SCHEMA_COMPAT_* constants. Always MIGRATION_NEW for
+        *  MediaWiki core since 1.33.
         */
-       public function __construct( Language $lang, $migrationStage ) {
-               $this->stage = $migrationStage;
+       public function __construct( Language $lang, $stage ) {
+               if ( ( $stage & SCHEMA_COMPAT_WRITE_BOTH ) === 0 ) {
+                       throw new InvalidArgumentException( '$stage must include a write mode' );
+               }
+               if ( ( $stage & SCHEMA_COMPAT_READ_BOTH ) === 0 ) {
+                       throw new InvalidArgumentException( '$stage must include a read mode' );
+               }
+
+               $this->stage = $stage;
                $this->lang = $lang;
        }
 
@@ -166,21 +175,21 @@ class CommentStore {
        public function getFields( $key = null ) {
                $key = $this->getKey( $key );
                $fields = [];
-               if ( $this->stage === MIGRATION_OLD ) {
+               if ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) {
                        $fields["{$key}_text"] = $key;
                        $fields["{$key}_data"] = 'NULL';
                        $fields["{$key}_cid"] = 'NULL';
-               } else {
-                       if ( $this->stage < MIGRATION_NEW ) {
+               } else { // READ_BOTH or READ_NEW
+                       if ( $this->stage & SCHEMA_COMPAT_READ_OLD ) {
                                $fields["{$key}_old"] = $key;
                        }
 
                        $tempTableStage = isset( $this->tempTables[$key] )
                                ? $this->tempTables[$key]['stage'] : MIGRATION_NEW;
-                       if ( $tempTableStage < MIGRATION_NEW ) {
+                       if ( $tempTableStage & SCHEMA_COMPAT_READ_OLD ) {
                                $fields["{$key}_pk"] = $this->tempTables[$key]['joinPK'];
                        }
-                       if ( $tempTableStage > MIGRATION_OLD ) {
+                       if ( $tempTableStage & SCHEMA_COMPAT_READ_NEW ) {
                                $fields["{$key}_id"] = "{$key}_id";
                        }
                }
@@ -211,21 +220,21 @@ class CommentStore {
                        $fields = [];
                        $joins = [];
 
-                       if ( $this->stage === MIGRATION_OLD ) {
+                       if ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) {
                                $fields["{$key}_text"] = $key;
                                $fields["{$key}_data"] = 'NULL';
                                $fields["{$key}_cid"] = 'NULL';
-                       } else {
-                               $join = $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN';
+                       } else { // READ_BOTH or READ_NEW
+                               $join = ( $this->stage & SCHEMA_COMPAT_READ_OLD ) ? 'LEFT JOIN' : 'JOIN';
 
                                $tempTableStage = isset( $this->tempTables[$key] )
                                        ? $this->tempTables[$key]['stage'] : MIGRATION_NEW;
-                               if ( $tempTableStage < MIGRATION_NEW ) {
+                               if ( $tempTableStage & SCHEMA_COMPAT_READ_OLD ) {
                                        $t = $this->tempTables[$key];
                                        $alias = "temp_$key";
                                        $tables[$alias] = $t['table'];
                                        $joins[$alias] = [ $join, "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
-                                       if ( $tempTableStage === MIGRATION_OLD ) {
+                                       if ( ( $tempTableStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) {
                                                $joinField = "{$alias}.{$t['field']}";
                                        } else {
                                                // Nothing hits this code path for now, but will in the future when we set
@@ -245,7 +254,7 @@ class CommentStore {
                                $tables[$alias] = 'comment';
                                $joins[$alias] = [ $join, "{$alias}.comment_id = {$joinField}" ];
 
-                               if ( $this->stage === MIGRATION_NEW ) {
+                               if ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_NEW ) {
                                        $fields["{$key}_text"] = "{$alias}.comment_text";
                                } else {
                                        $fields["{$key}_text"] = "COALESCE( {$alias}.comment_text, $key )";
@@ -282,13 +291,15 @@ class CommentStore {
                        $cid = $row["{$key}_cid"] ?? null;
                        $text = $row["{$key}_text"];
                        $data = $row["{$key}_data"];
-               } elseif ( $this->stage === MIGRATION_OLD ) {
+               } elseif ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) {
                        $cid = null;
                        if ( $fallback && isset( $row[$key] ) ) {
                                wfLogWarning( "Using deprecated fallback handling for comment $key" );
                                $text = $row[$key];
                        } else {
-                               wfLogWarning( "Missing {$key}_text and {$key}_data fields in row with MIGRATION_OLD" );
+                               wfLogWarning(
+                                       "Missing {$key}_text and {$key}_data fields in row with MIGRATION_OLD / READ_OLD"
+                               );
                                $text = '';
                        }
                        $data = null;
@@ -296,7 +307,7 @@ class CommentStore {
                        $tempTableStage = isset( $this->tempTables[$key] )
                                ? $this->tempTables[$key]['stage'] : MIGRATION_NEW;
                        $row2 = null;
-                       if ( $tempTableStage > MIGRATION_OLD && array_key_exists( "{$key}_id", $row ) ) {
+                       if ( ( $tempTableStage & SCHEMA_COMPAT_READ_NEW ) && array_key_exists( "{$key}_id", $row ) ) {
                                if ( !$db ) {
                                        throw new InvalidArgumentException(
                                                "\$row does not contain fields needed for comment $key and getComment(), but "
@@ -311,7 +322,9 @@ class CommentStore {
                                        __METHOD__
                                );
                        }
-                       if ( !$row2 && $tempTableStage < MIGRATION_NEW && array_key_exists( "{$key}_pk", $row ) ) {
+                       if ( !$row2 && ( $tempTableStage & SCHEMA_COMPAT_READ_OLD ) &&
+                               array_key_exists( "{$key}_pk", $row )
+                       ) {
                                if ( !$db ) {
                                        throw new InvalidArgumentException(
                                                "\$row does not contain fields needed for comment $key and getComment(), but "
@@ -341,7 +354,9 @@ class CommentStore {
                                $cid = $row2->comment_id;
                                $text = $row2->comment_text;
                                $data = $row2->comment_data;
-                       } elseif ( $this->stage < MIGRATION_NEW && array_key_exists( "{$key}_old", $row ) ) {
+                       } elseif ( ( $this->stage & SCHEMA_COMPAT_READ_OLD ) &&
+                               array_key_exists( "{$key}_old", $row )
+                       ) {
                                $cid = null;
                                $text = $row["{$key}_old"];
                                $data = null;
@@ -474,7 +489,7 @@ class CommentStore {
                # Truncate comment in a Unicode-sensitive manner
                $comment->text = $this->lang->truncateForVisual( $comment->text, self::COMMENT_CHARACTER_LIMIT );
 
-               if ( $this->stage > MIGRATION_OLD && !$comment->id ) {
+               if ( ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) && !$comment->id ) {
                        $dbData = $comment->data;
                        if ( !$comment->message instanceof RawMessage ) {
                                if ( $dbData === null ) {
@@ -534,14 +549,14 @@ class CommentStore {
 
                $comment = $this->createComment( $dbw, $comment, $data );
 
-               if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
+               if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
                        $fields[$key] = $this->lang->truncateForDatabase( $comment->text, 255 );
                }
 
-               if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
+               if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
                        $tempTableStage = isset( $this->tempTables[$key] )
                                ? $this->tempTables[$key]['stage'] : MIGRATION_NEW;
-                       if ( $tempTableStage <= MIGRATION_WRITE_BOTH ) {
+                       if ( $tempTableStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                $t = $this->tempTables[$key];
                                $func = __METHOD__;
                                $commentId = $comment->id;
@@ -556,7 +571,7 @@ class CommentStore {
                                        );
                                };
                        }
-                       if ( $tempTableStage >= MIGRATION_WRITE_BOTH ) {
+                       if ( $tempTableStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                $fields["{$key}_id"] = $comment->id;
                        }
                }
@@ -594,7 +609,7 @@ class CommentStore {
 
                $tempTableStage = isset( $this->tempTables[$key] )
                        ? $this->tempTables[$key]['stage'] : MIGRATION_NEW;
-               if ( $tempTableStage < MIGRATION_WRITE_NEW ) {
+               if ( $tempTableStage & SCHEMA_COMPAT_WRITE_OLD ) {
                        throw new InvalidArgumentException( "Must use insertWithTempTable() for $key" );
                }
 
index 1068700..29b628c 100644 (file)
@@ -9096,6 +9096,9 @@ $wgNativeImageLazyLoading = false;
 
 /**
  * Option to whether serve the main page as the domain root
+ *
+ * @warning EXPERIMENTAL!
+ *
  * @since 1.34
  * @var bool
  */
index c4b57af..a8f3496 100644 (file)
@@ -704,7 +704,7 @@ class Html {
         * Return the HTML for a message box.
         * @since 1.31
         * @param string $html of contents of box
-        * @param string $className corresponding to box
+        * @param string|array $className corresponding to box
         * @param string $heading (optional)
         * @return string of HTML representing a box.
         */
@@ -718,32 +718,38 @@ class Html {
        /**
         * Return a warning box.
         * @since 1.31
+        * @since 1.34 $className optional parameter added
         * @param string $html of contents of box
+        * @param string $className (optional) corresponding to box
         * @return string of HTML representing a warning box.
         */
-       public static function warningBox( $html ) {
-               return self::messageBox( $html, 'warningbox' );
+       public static function warningBox( $html, $className = '' ) {
+               return self::messageBox( $html, [ 'warningbox', $className ] );
        }
 
        /**
         * Return an error box.
         * @since 1.31
+        * @since 1.34 $className optional parameter added
         * @param string $html of contents of error box
         * @param string $heading (optional)
+        * @param string $className (optional) corresponding to box
         * @return string of HTML representing an error box.
         */
-       public static function errorBox( $html, $heading = '' ) {
-               return self::messageBox( $html, 'errorbox', $heading );
+       public static function errorBox( $html, $heading = '', $className = '' ) {
+               return self::messageBox( $html, [ 'errorbox', $className ], $heading );
        }
 
        /**
         * Return a success box.
         * @since 1.31
+        * @since 1.34 $className optional parameter added
         * @param string $html of contents of box
+        * @param string $className (optional) corresponding to box
         * @return string of HTML representing a success box.
         */
-       public static function successBox( $html ) {
-               return self::messageBox( $html, 'successbox' );
+       public static function successBox( $html, $className = '' ) {
+               return self::messageBox( $html, [ 'successbox', $className ] );
        }
 
        /**
index 28c9e16..1a75714 100644 (file)
@@ -526,11 +526,15 @@ class MediaWiki {
                        try {
                                $this->main();
                        } catch ( ErrorPageError $e ) {
+                               $out = $this->context->getOutput();
+                               // TODO: Should ErrorPageError::report accept a OutputPage parameter?
+                               $e->report( ErrorPageError::STAGE_OUTPUT );
+
                                // T64091: while exceptions are convenient to bubble up GUI errors,
                                // they are not internal application faults. As with normal requests, this
                                // should commit, print the output, do deferred updates, jobs, and profiling.
                                $this->doPreOutputCommit();
-                               $e->report(); // display the GUI error
+                               $out->output(); // display the GUI error
                        }
                } catch ( Exception $e ) {
                        $context = $this->context;
index 6821d89..6dfcf3c 100644 (file)
@@ -276,8 +276,7 @@ class Router {
                $request->setPathParams( array_map( 'rawurldecode', $match['params'] ) );
                $spec = $match['userData'];
                $objectFactorySpec = array_intersect_key( $spec,
-                       // @todo ObjectFactory supports more keys than this.
-                       [ 'factory' => true, 'class' => true, 'args' => true ] );
+                       [ 'factory' => true, 'class' => true, 'args' => true, 'services' => true ] );
                /** @var $handler Handler (annotation for PHPStorm) */
                $handler = $this->objectFactory->createObject( $objectFactorySpec );
                $handler->init( $this, $request, $spec, $this->responseFactory );
index b8672ee..022fd9b 100644 (file)
@@ -286,6 +286,8 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
                $res = $this->select( __METHOD__ );
 
                if ( is_null( $resultPageSet ) ) {
+                       $this->executeGenderCacheFromResultWrapper( $res, __METHOD__ );
+
                        $count = 0;
                        foreach ( $res as $row ) {
                                if ( ++$count > $params['limit'] ) {
index 8d9cb48..059c438 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\IResultWrapper;
 
@@ -569,6 +570,42 @@ abstract class ApiQueryBase extends ApiBase {
                );
        }
 
+       /**
+        * Preprocess the result set to fill the GenderCache with the necessary information
+        * before using self::addTitleInfo
+        *
+        * @param IResultWrapper $res Result set to work on.
+        *  The result set must have _namespace and _title fields with the provided field prefix
+        * @param string $fname The caller function name, always use __METHOD__
+        * @param string $fieldPrefix Prefix for fields to check gender for
+        */
+       protected function executeGenderCacheFromResultWrapper(
+               IResultWrapper $res, $fname = __METHOD__, $fieldPrefix = 'page'
+       ) {
+               if ( !$res->numRows() ) {
+                       return;
+               }
+
+               $services = MediaWikiServices::getInstance();
+               $nsInfo = $services->getNamespaceInfo();
+               $namespaceField = $fieldPrefix . '_namespace';
+               $titleField = $fieldPrefix . '_title';
+
+               $usernames = [];
+               foreach ( $res as $row ) {
+                       if ( $nsInfo->hasGenderDistinction( $row->$namespaceField ) ) {
+                               $usernames[] = $row->$titleField;
+                       }
+               }
+
+               if ( $usernames === [] ) {
+                       return;
+               }
+
+               $genderCache = $services->getGenderCache();
+               $genderCache->doQuery( $usernames, $fname );
+       }
+
        /** @} */
 
        /************************************************************************//**
index 5fcf0e6..2448421 100644 (file)
@@ -26,9 +26,9 @@
  */
 class BadRequestError extends ErrorPageError {
 
-       public function report() {
+       public function report( $action = self::SEND_OUTPUT ) {
                global $wgOut;
                $wgOut->setStatusCode( 400 );
-               parent::report();
+               parent::report( $action );
        }
 }
index 4b18126..64216a4 100644 (file)
@@ -25,6 +25,8 @@
  * @ingroup Exception
  */
 class ErrorPageError extends MWException implements ILocalizedException {
+       const SEND_OUTPUT = 0;
+       const STAGE_OUTPUT = 1;
        public $title, $msg, $params;
 
        /**
@@ -60,13 +62,19 @@ class ErrorPageError extends MWException implements ILocalizedException {
                return wfMessage( $this->msg, $this->params );
        }
 
-       public function report() {
+       public function report( $action = self::SEND_OUTPUT ) {
                if ( self::isCommandLine() || defined( 'MW_API' ) ) {
                        parent::report();
                } else {
                        global $wgOut;
                        $wgOut->showErrorPage( $this->title, $this->msg, $this->params );
-                       $wgOut->output();
+                       // Allow skipping of the final output step, so that web-based page views
+                       // from MediaWiki.php, can inspect the staged OutputPage state, and perform
+                       // graceful shutdown via doPreOutputCommit first, just like for regular
+                       // output when there isn't an error page.
+                       if ( $action === self::SEND_OUTPUT ) {
+                               $wgOut->output();
+                       }
                }
        }
 }
index 87a3dc2..9fa1c7c 100644 (file)
@@ -67,10 +67,12 @@ class PermissionsError extends ErrorPageError {
                parent::__construct( 'permissionserrors', Message::newFromSpecifier( $errors[0] ) );
        }
 
-       public function report() {
+       public function report( $action = self::SEND_OUTPUT ) {
                global $wgOut;
 
                $wgOut->showPermissionsErrorPage( $this->errors, $this->permission );
-               $wgOut->output();
+               if ( $action === self::SEND_OUTPUT ) {
+                       $wgOut->output();
+               }
        }
 }
index bec0d90..cdeb402 100644 (file)
@@ -32,9 +32,9 @@ class ThrottledError extends ErrorPageError {
                );
        }
 
-       public function report() {
+       public function report( $action = ErrorPageError::SEND_OUTPUT ) {
                global $wgOut;
                $wgOut->setStatusCode( 429 );
-               parent::report();
+               parent::report( $action );
        }
 }
index 7a99765..ff992b0 100644 (file)
@@ -75,14 +75,15 @@ class UserNotLoggedIn extends ErrorPageError {
         * Redirect to Special:Userlogin if the specified message is compatible. Otherwise,
         * show an error page as usual.
         */
-       public function report() {
+       public function report( $action = self::SEND_OUTPUT ) {
                // If an unsupported message is used, don't try redirecting to Special:Userlogin,
                // since the message may not be compatible.
                if ( !in_array( $this->msg, LoginHelper::getValidErrorMessages() ) ) {
-                       parent::report();
+                       parent::report( $action );
+                       return;
                }
 
-               // Message is valid. Redirec to Special:Userlogin
+               // Message is valid. Redirect to Special:Userlogin
 
                $context = RequestContext::getMain();
 
@@ -98,6 +99,8 @@ class UserNotLoggedIn extends ErrorPageError {
                        'warning' => $this->msg,
                ] ) );
 
-               $output->output();
+               if ( $action === self::SEND_OUTPUT ) {
+                       $output->output();
+               }
        }
 }
index 7cfc8c2..b76f3da 100644 (file)
@@ -29,9 +29,9 @@ class LocalFileLockError extends ErrorPageError {
                );
        }
 
-       public function report() {
+       public function report( $action = self::SEND_OUTPUT ) {
                global $wgOut;
                $wgOut->setStatusCode( 429 );
-               parent::report();
+               parent::report( $action );
        }
 }
index 5315ced..84e7b73 100644 (file)
@@ -59,13 +59,12 @@ class HttpRequestFactory {
         *    - originalRequest     Information about the original request (as a WebRequest object or
         *                          an associative array with 'ip' and 'userAgent').
         * @codingStandardsIgnoreStart
-        * @phan-param array{timeout?:int|string,connectTimeout?:int|string,postData?:array,proxy?:string,noProxy?:bool,sslVerifyHost?:bool,sslVerifyCert?:bool,caInfo?:string,maxRedirects?:int,followRedirects?:bool,userAgent?:string,method?:string,logger?:\Psr\Log\LoggerInterface,username?:string,password?:string,originalRequest?:WebRequest|array{ip:string,userAgent:string}} $options
+        * @phan-param array{timeout?:int|string,connectTimeout?:int|string,postData?:array,proxy?:string,noProxy?:bool,sslVerifyHost?:bool,sslVerifyCert?:bool,caInfo?:string,maxRedirects?:int,followRedirects?:bool,userAgent?:string,method?:string,logger?:\Psr\Log\LoggerInterface,username?:string,password?:string,originalRequest?:\WebRequest|array{ip:string,userAgent:string}} $options
         * @codingStandardsIgnoreEnd
         * @param string $caller The method making this request, for profiling
         * @throws RuntimeException
         * @return MWHttpRequest
         * @see MWHttpRequest::__construct
-        * @suppress PhanUndeclaredTypeParameter
         */
        public function create( $url, array $options = [], $caller = __METHOD__ ) {
                if ( !Http::$httpEngine ) {
index ac8c9e6..ce7e29d 100644 (file)
@@ -690,7 +690,7 @@ abstract class DatabaseInstaller {
                        $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
                        $this->parent->getHelpBox( 'config-db-web-help' );
                if ( $noCreateMsg ) {
-                       $s .= $this->parent->getWarningBox( wfMessage( $noCreateMsg )->plain() );
+                       $s .= Html::warningBox( wfMessage( $noCreateMsg )->plain(), 'config-warning-box' );
                } else {
                        $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
                }
index b6e90a9..21ad210 100644 (file)
@@ -390,7 +390,8 @@ class WebInstaller extends Installer {
                        );
                }
                $text = $msg->useDatabase( false )->plain();
-               $this->output->addHTML( $this->getErrorBox( $text ) );
+               $box = Html::errorBox( $text, '', 'config-error-box' );
+               $this->output->addHTML( $box );
        }
 
        /**
@@ -1046,9 +1047,9 @@ class WebInstaller extends Installer {
                        $text = $status->getWikiText();
 
                        if ( $status->isOK() ) {
-                               $box = $this->getWarningBox( $text );
+                               $box = Html::warningBox( $text, 'config-warning-box' );
                        } else {
-                               $box = $this->getErrorBox( $text );
+                               $box = Html::errorBox( $text, '', 'config-error-box' );
                        }
 
                        $this->output->addHTML( $box );
index 7bec49a..3521fa1 100644 (file)
@@ -137,7 +137,7 @@ class WebInstallerOptions extends WebInstallerPage {
                        }
                } else {
                        $skinHtml .=
-                               $this->parent->getWarningBox( wfMessage( 'config-skins-missing' )->plain() ) .
+                               Html::warningBox( wfMessage( 'config-skins-missing' )->plain(), 'config-warning-box' ) .
                                Html::hidden( 'config_wgDefaultSkin', $chosenSkinName );
                }
 
index be55c32..07e2e75 100644 (file)
@@ -36,7 +36,7 @@ class WebInstallerRestart extends WebInstallerPage {
                }
 
                $this->startForm();
-               $s = $this->parent->getWarningBox( wfMessage( 'config-help-restart' )->plain() );
+               $s = Html::warningBox( wfMessage( 'config-help-restart' )->plain(), '', 'config-warning-box' );
                $this->addHTML( $s );
                $this->endForm( 'restart' );
 
index 7be3b7d..464e68c 100644 (file)
@@ -850,27 +850,44 @@ abstract class DatabaseMysqlBase extends Database {
                }
 
                if ( $this->getLBInfo( 'is static' ) === true ) {
+                       $this->queryLogger->debug(
+                               "Bypassed replication wait; database has a static dataset",
+                               $this->getLogContext( [ 'method' => __METHOD__ ] )
+                       );
+
                        return 0; // this is a copy of a read-only dataset with no master DB
                } elseif ( $this->lastKnownReplicaPos && $this->lastKnownReplicaPos->hasReached( $pos ) ) {
+                       $this->queryLogger->debug(
+                               "Bypassed replication wait; replication already known to have reached $pos",
+                               $this->getLogContext( [ 'method' => __METHOD__ ] )
+                       );
+
                        return 0; // already reached this point for sure
                }
 
                // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
                if ( $pos->getGTIDs() ) {
-                       // Ignore GTIDs from domains exclusive to the master DB (presumably inactive)
-                       $rpos = $this->getReplicaPos();
-                       $gtidsWait = $rpos ? MySQLMasterPos::getCommonDomainGTIDs( $pos, $rpos ) : [];
+                       // Get the GTIDs from this replica server too see the domains (channels)
+                       $refPos = $this->getReplicaPos();
+                       if ( !$refPos ) {
+                               $this->queryLogger->error(
+                                       "Could not get replication position",
+                                       $this->getLogContext( [ 'method' => __METHOD__ ] )
+                               );
+
+                               return -1; // this is the master itself?
+                       }
+                       // GTIDs with domains (channels) that are active and are present on the replica
+                       $gtidsWait = $pos::getRelevantActiveGTIDs( $pos, $refPos );
                        if ( !$gtidsWait ) {
                                $this->queryLogger->error(
-                                       "No GTIDs with the same domain between master ($pos) and replica ($rpos)",
-                                       $this->getLogContext( [
-                                               'method' => __METHOD__,
-                                       ] )
+                                       "No active GTIDs in $pos share a domain with those in $refPos",
+                                       $this->getLogContext( [ 'method' => __METHOD__, 'activeDomain' => $pos ] )
                                );
 
                                return -1; // $pos is from the wrong cluster?
                        }
-                       // Wait on the GTID set (MariaDB only)
+                       // Wait on the GTID set
                        $gtidArg = $this->addQuotes( implode( ',', $gtidsWait ) );
                        if ( strpos( $gtidArg, ':' ) !== false ) {
                                // MySQL GTIDs, e.g "source_id:transaction_id"
@@ -886,28 +903,28 @@ abstract class DatabaseMysqlBase extends Database {
                        $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
                }
 
-               list( $res, $err ) = $this->executeQuery( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX );
-               $row = $res ? $this->fetchRow( $res ) : false;
-               if ( !$row ) {
-                       throw new DBExpectedError( $this, "Replication wait failed: {$err}" );
-               }
+               $res = $this->query( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX );
+               $row = $this->fetchRow( $res );
 
                // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
                $status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
                if ( $status === null ) {
-                       if ( !$pos->getGTIDs() ) {
-                               // T126436: jobs programmed to wait on master positions might be referencing
-                               // binlogs with an old master hostname; this makes MASTER_POS_WAIT() return null.
-                               // Try to detect this case and treat the replica DB as having reached the given
-                               // position (any master switchover already requires that the new master be caught
-                               // up before the switch).
-                               $replicationPos = $this->getReplicaPos();
-                               if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) {
-                                       $this->lastKnownReplicaPos = $replicationPos;
-                                       $status = 0;
-                               }
-                       }
+                       $this->queryLogger->error(
+                               "An error occurred while waiting for replication to reach $pos",
+                               $this->getLogContext( [ 'method' => __METHOD__, 'sql' => $sql ] )
+                       );
+               } elseif ( $status < 0 ) {
+                       $this->queryLogger->error(
+                               "Timed out waiting for replication to reach $pos",
+                               $this->getLogContext( [
+                                       'method' => __METHOD__, 'sql' => $sql, 'timeout' => $timeout
+                               ] )
+                       );
                } elseif ( $status >= 0 ) {
+                       $this->queryLogger->debug(
+                               "Replication has reached $pos",
+                               $this->getLogContext( [ 'method' => __METHOD__ ] )
+                       );
                        // Remember that this position was reached to save queries next time
                        $this->lastKnownReplicaPos = $pos;
                }
index fa2c1db..e1b8f9a 100644 (file)
@@ -190,39 +190,69 @@ class MySQLMasterPos implements DBMasterPos {
        }
 
        /**
+        * Set the GTID domain known to be used in new commits on a replication stream of interest
+        *
+        * This makes getRelevantActiveGTIDs() filter out GTIDs from other domains
+        *
+        * @see MySQLMasterPos::getRelevantActiveGTIDs()
+        * @see https://mariadb.com/kb/en/library/gtid/#gtid_domain_id
+        *
         * @param int|null $id @@gtid_domain_id of the active replication stream
+        * @return MySQLMasterPos This instance (since 1.34)
         * @since 1.31
         */
        public function setActiveDomain( $id ) {
                $this->activeDomain = (int)$id;
+
+               return $this;
        }
 
        /**
+        * Set the server ID known to be used in new commits on a replication stream of interest
+        *
+        * This makes getRelevantActiveGTIDs() filter out GTIDs from other origin servers
+        *
+        * @see MySQLMasterPos::getRelevantActiveGTIDs()
+        *
         * @param int|null $id @@server_id of the server were writes originate
+        * @return MySQLMasterPos This instance (since 1.34)
         * @since 1.31
         */
        public function setActiveOriginServerId( $id ) {
                $this->activeServerId = (int)$id;
+
+               return $this;
        }
 
        /**
+        * Set the server UUID known to be used in new commits on a replication stream of interest
+        *
+        * This makes getRelevantActiveGTIDs() filter out GTIDs from other origin servers
+        *
+        * @see MySQLMasterPos::getRelevantActiveGTIDs()
+        *
         * @param string|null $id @@server_uuid of the server were writes originate
+        * @return MySQLMasterPos This instance (since 1.34)
         * @since 1.31
         */
        public function setActiveOriginServerUUID( $id ) {
                $this->activeServerUUID = $id;
+
+               return $this;
        }
 
        /**
         * @param MySQLMasterPos $pos
         * @param MySQLMasterPos $refPos
-        * @return string[] List of GTIDs from $pos that have domains in $refPos
-        * @since 1.31
+        * @return string[] List of active GTIDs from $pos that have domains in $refPos
+        * @since 1.34
         */
-       public static function getCommonDomainGTIDs( MySQLMasterPos $pos, MySQLMasterPos $refPos ) {
-               return array_values(
-                       array_intersect_key( $pos->gtids, $refPos->getActiveGtidCoordinates() )
-               );
+       public static function getRelevantActiveGTIDs( MySQLMasterPos $pos, MySQLMasterPos $refPos ) {
+               return array_values( array_intersect_key(
+                       $pos->gtids,
+                       $pos->getActiveGtidCoordinates(),
+                       $refPos->gtids
+               ) );
        }
 
        /**
index 781df06..0b78a36 100644 (file)
@@ -361,6 +361,11 @@ class LogPager extends ReverseChronologicalPager {
                if ( !$this->mTagFilter && !array_key_exists( 'ls_field', $this->mConds ) ) {
                        $options[] = 'STRAIGHT_JOIN';
                }
+               if ( $this->performer !== '' ) {
+                       // T223151: MariaDB's optimizer, at least 10.1, likes to choose a wildly bad plan for
+                       // some reason for this code path. Tell it not to use the wrong index it wants to pick.
+                       $options['IGNORE INDEX'] = [ 'logging' => [ 'times' ] ];
+               }
 
                $info = [
                        'tables' => $tables,
index ec1628f..9ec5834 100644 (file)
@@ -910,7 +910,7 @@ class Parser {
         */
        public function setTitle( $t ) {
                if ( !$t ) {
-                       $t = Title::newFromText( 'NO TITLE' );
+                       $t = Title::makeTitle( NS_SPECIAL, 'Badtitle/Parser' );
                }
 
                if ( $t->hasFragment() ) {
index e8b85fa..8f92cd5 100644 (file)
@@ -23,7 +23,6 @@
 
 use MediaWiki\Block\DatabaseBlock;
 use MediaWiki\MediaWikiServices;
-use MediaWiki\Widget\DateInputWidget;
 
 /**
  * Special:Contributions, show user contributions in a paged list
@@ -43,11 +42,16 @@ class SpecialContributions extends IncludableSpecialPage {
                $out = $this->getOutput();
                // Modules required for viewing the list of contributions (also when included on other pages)
                $out->addModuleStyles( [
+                       'jquery.makeCollapsible.styles',
                        'mediawiki.interface.helpers.styles',
                        'mediawiki.special',
                        'mediawiki.special.changeslist',
                ] );
-               $out->addModules( 'mediawiki.special.recentchanges' );
+               $out->addModules( [
+                       'mediawiki.special.recentchanges',
+                       // Certain skins e.g. Minerva might have disabled this module.
+                       'mediawiki.page.ready'
+               ] );
                $this->addHelpLink( 'Help:User contributions' );
 
                $this->opts = [];
@@ -59,7 +63,7 @@ class SpecialContributions extends IncludableSpecialPage {
 
                if ( !strlen( $target ) ) {
                        if ( !$this->including() ) {
-                               $out->addHTML( $this->getForm() );
+                               $out->addHTML( $this->getForm( $this->opts ) );
                        }
 
                        return;
@@ -77,7 +81,7 @@ class SpecialContributions extends IncludableSpecialPage {
                if ( ExternalUserNames::isExternal( $target ) ) {
                        $userObj = User::newFromName( $target, false );
                        if ( !$userObj ) {
-                               $out->addHTML( $this->getForm() );
+                               $out->addHTML( $this->getForm( $this->opts ) );
                                return;
                        }
 
@@ -89,12 +93,12 @@ class SpecialContributions extends IncludableSpecialPage {
                } else {
                        $nt = Title::makeTitleSafe( NS_USER, $target );
                        if ( !$nt ) {
-                               $out->addHTML( $this->getForm() );
+                               $out->addHTML( $this->getForm( $this->opts ) );
                                return;
                        }
                        $userObj = User::newFromName( $nt->getText(), false );
                        if ( !$userObj ) {
-                               $out->addHTML( $this->getForm() );
+                               $out->addHTML( $this->getForm( $this->opts ) );
                                return;
                        }
                        $id = $userObj->getId();
@@ -114,14 +118,23 @@ class SpecialContributions extends IncludableSpecialPage {
                }
 
                $ns = $request->getVal( 'namespace', null );
-               if ( $ns !== null && $ns !== '' ) {
+               if ( $ns !== null && $ns !== '' && $ns !== 'all' ) {
                        $this->opts['namespace'] = intval( $ns );
                } else {
                        $this->opts['namespace'] = '';
                }
 
+               // Backwards compatibility: Before using OOUI form the old HTML form had
+               // fields for nsInvert and associated. These have now been replaced with the
+               // wpFilters query string parameters. These are retained to keep old URIs working.
                $this->opts['associated'] = $request->getBool( 'associated' );
                $this->opts['nsInvert'] = (bool)$request->getVal( 'nsInvert' );
+               $nsFilters = $request->getArray( 'wpfilters', null );
+               if ( $nsFilters !== null ) {
+                       $this->opts['associated'] = in_array( 'associated', $nsFilters );
+                       $this->opts['nsInvert'] = in_array( 'nsInvert', $nsFilters );
+               }
+
                $this->opts['tagfilter'] = (string)$request->getVal( 'tagfilter' );
 
                // Allows reverts to have the bot flag in recent changes. It is just here to
@@ -149,7 +162,7 @@ class SpecialContributions extends IncludableSpecialPage {
                                "<div class=\"mw-negative-namespace-not-supported error\">\n\$1\n</div>",
                                [ 'negative-namespace-not-supported' ]
                        );
-                       $out->addHTML( $this->getForm() );
+                       $out->addHTML( $this->getForm( $this->opts ) );
                        return;
                }
 
@@ -201,9 +214,6 @@ class SpecialContributions extends IncludableSpecialPage {
                $this->addFeedLinks( $feedParams );
 
                if ( Hooks::run( 'SpecialContributionsBeforeMainOutput', [ $id, $userObj, $this ] ) ) {
-                       if ( !$this->including() ) {
-                               $out->addHTML( $this->getForm() );
-                       }
                        $pager = new ContribsPager( $this->getContext(), [
                                'target' => $target,
                                'namespace' => $this->opts['namespace'],
@@ -217,6 +227,9 @@ class SpecialContributions extends IncludableSpecialPage {
                                'nsInvert' => $this->opts['nsInvert'],
                                'associated' => $this->opts['associated'],
                        ], $this->getLinkRenderer() );
+                       if ( !$this->including() ) {
+                               $out->addHTML( $this->getForm( $this->opts ) );
+                       }
 
                        if ( IP::isValidRange( $target ) && !$pager->isQueryableRange( $target ) ) {
                                // Valid range, but outside CIDR limit.
@@ -458,52 +471,12 @@ class SpecialContributions extends IncludableSpecialPage {
 
        /**
         * Generates the namespace selector form with hidden attributes.
+        * @param array $pagerOptions with keys contribs, user, deletedOnly, limit, target, topOnly,
+        *  newOnly, hideMinor, namespace, associated, nsInvert, tagfilter, year, start, end
         * @return string HTML fragment
         */
-       protected function getForm() {
+       protected function getForm( array $pagerOptions ) {
                $this->opts['title'] = $this->getPageTitle()->getPrefixedText();
-               if ( !isset( $this->opts['target'] ) ) {
-                       $this->opts['target'] = '';
-               } else {
-                       $this->opts['target'] = str_replace( '_', ' ', $this->opts['target'] );
-               }
-
-               if ( !isset( $this->opts['namespace'] ) ) {
-                       $this->opts['namespace'] = '';
-               }
-
-               if ( !isset( $this->opts['nsInvert'] ) ) {
-                       $this->opts['nsInvert'] = '';
-               }
-
-               if ( !isset( $this->opts['associated'] ) ) {
-                       $this->opts['associated'] = false;
-               }
-
-               if ( !isset( $this->opts['start'] ) ) {
-                       $this->opts['start'] = '';
-               }
-
-               if ( !isset( $this->opts['end'] ) ) {
-                       $this->opts['end'] = '';
-               }
-
-               if ( !isset( $this->opts['tagfilter'] ) ) {
-                       $this->opts['tagfilter'] = '';
-               }
-
-               if ( !isset( $this->opts['topOnly'] ) ) {
-                       $this->opts['topOnly'] = false;
-               }
-
-               if ( !isset( $this->opts['newOnly'] ) ) {
-                       $this->opts['newOnly'] = false;
-               }
-
-               if ( !isset( $this->opts['hideMinor'] ) ) {
-                       $this->opts['hideMinor'] = false;
-               }
-
                // Modules required only for the form
                $this->getOutput()->addModules( [
                        'mediawiki.userSuggest',
@@ -511,15 +484,7 @@ class SpecialContributions extends IncludableSpecialPage {
                ] );
                $this->getOutput()->addModuleStyles( 'mediawiki.widgets.DateInputWidget.styles' );
                $this->getOutput()->enableOOUI();
-
-               $form = Html::openElement(
-                       'form',
-                       [
-                               'method' => 'get',
-                               'action' => wfScript(),
-                               'class' => 'mw-contributions-form'
-                       ]
-               );
+               $fields = [];
 
                # Add hidden params for tracking except for parameters in $skipParameters
                $skipParameters = [
@@ -527,7 +492,6 @@ class SpecialContributions extends IncludableSpecialPage {
                        'nsInvert',
                        'deletedOnly',
                        'target',
-                       'contribs',
                        'year',
                        'month',
                        'start',
@@ -543,207 +507,171 @@ class SpecialContributions extends IncludableSpecialPage {
                        if ( in_array( $name, $skipParameters ) ) {
                                continue;
                        }
-                       $form .= "\t" . Html::hidden( $name, $value ) . "\n";
-               }
-
-               $tagFilter = ChangeTags::buildTagFilterSelector(
-                       $this->opts['tagfilter'], false, $this->getContext() );
-
-               if ( $tagFilter ) {
-                       $filterSelection = Html::rawElement(
-                               'div',
-                               [],
-                               implode( "\u{00A0}", $tagFilter )
-                       );
-               } else {
-                       $filterSelection = Html::rawElement( 'div', [], '' );
-               }
-
-               $labelUsername = Xml::label(
-                       $this->msg( 'sp-contributions-username' )->text(),
-                       'mw-target-user-or-ip'
-               );
-               $input = Html::input(
-                       'target',
-                       $this->opts['target'],
-                       'text',
-                       [
-                               'id' => 'mw-target-user-or-ip',
-                               'size' => '40',
-                               'class' => [
-                                       'mw-input',
-                                       'mw-ui-input-inline',
-                                       'mw-autocomplete-user', // used by mediawiki.userSuggest
-                               ],
-                       ] + (
-                               // Only autofocus if target hasn't been specified
-                               $this->opts['target'] ? [] : [ 'autofocus' => true ]
-                       )
-               );
-
-               $targetSelection = Html::rawElement(
-                       'div',
-                       [],
-                       $labelUsername . ' ' . $input . ' '
-               );
 
-               $hidden = $this->opts['namespace'] === '' ? ' mw-input-hidden' : '';
-               $namespaceSelection = Xml::tags(
-                       'div',
-                       [],
-                       Xml::label(
-                               $this->msg( 'namespace' )->text(),
-                               'namespace'
-                       ) . "\u{00A0}" .
-                       Html::namespaceSelector(
-                               [ 'selected' => $this->opts['namespace'], 'all' => '', 'in-user-lang' => true ],
-                               [
-                                       'name' => 'namespace',
-                                       'id' => 'namespace',
-                                       'class' => 'namespaceselector',
-                               ]
-                       ) . "\u{00A0}" .
-                               Html::rawElement(
-                                       'span',
-                                       [ 'class' => 'mw-input-with-label' . $hidden ],
-                                       Xml::checkLabel(
-                                               $this->msg( 'invert' )->text(),
-                                               'nsInvert',
-                                               'nsinvert',
-                                               $this->opts['nsInvert'],
-                                               [
-                                                       'title' => $this->msg( 'tooltip-invert' )->text(),
-                                                       'class' => 'mw-input'
-                                               ]
-                                       ) . "\u{00A0}"
-                               ) .
-                               Html::rawElement( 'span', [ 'class' => 'mw-input-with-label' . $hidden ],
-                                       Xml::checkLabel(
-                                               $this->msg( 'namespace_association' )->text(),
-                                               'associated',
-                                               'nsassociated',
-                                               $this->opts['associated'],
-                                               [
-                                                       'title' => $this->msg( 'tooltip-namespace_association' )->text(),
-                                                       'class' => 'mw-input'
-                                               ]
-                                       ) . "\u{00A0}"
-                               )
-               );
+                       $fields[$name] = [
+                               'name' => $name,
+                               'type' => 'hidden',
+                               'default' => $value,
+                       ];
+               }
+
+               $target = $this->opts['target'] ?? null;
+               $fields['target'] = [
+                       'type' => 'text',
+                       'cssclass' => 'mw-autocomplete-user mw-ui-input-inline mw-input',
+                       'default' => $target ?
+                               str_replace( '_', ' ', $target ) : '' ,
+                       'label' => $this->msg( 'sp-contributions-username' )->text(),
+                       'name' => 'target',
+                       'id' => 'mw-target-user-or-ip',
+                       'size' => 40,
+                       'autofocus' => !$target,
+                       'section' => 'contribs-top',
+               ];
 
-               $filters = [];
+               $ns = $this->opts['namespace'] ?? null;
+               $fields['namespace'] = [
+                       'type' => 'namespaceselect',
+                       'label' => $this->msg( 'namespace' )->text(),
+                       'name' => 'namespace',
+                       'cssclass' => 'namespaceselector',
+                       'default' => $ns,
+                       'id' => 'namespace',
+                       'section' => 'contribs-top',
+               ];
+               $request = $this->getRequest();
+               $nsFilters = $request->getArray( 'wpfilters' );
+               $fields['nsFilters'] = [
+                       'class' => 'HTMLMultiSelectField',
+                       'label' => '',
+                       'name' => 'wpfilters',
+                       'flatlist' => true,
+                       // Only shown when namespaces are selected.
+                       'cssclass' => $ns === '' ?
+                               'contribs-ns-filters mw-input-with-label mw-input-hidden' :
+                               'contribs-ns-filters mw-input-with-label',
+                       // `contribs-ns-filters` class allows these fields to be toggled on/off by JavaScript.
+                       // See resources/src/mediawiki.special.recentchanges.js
+                       'infusable' => true,
+                       'options' => [
+                               $this->msg( 'invert' )->text() => 'nsInvert',
+                               $this->msg( 'namespace_association' )->text() => 'associated',
+                       ],
+                       'default' => $nsFilters,
+                       'section' => 'contribs-top',
+               ];
+               $fields['tagfilter'] = [
+                       'type' => 'tagfilter',
+                       'cssclass' => 'mw-tagfilter-input',
+                       'id' => 'tagfilter',
+                       'label-message' => [ 'tag-filter', 'parse' ],
+                       'name' => 'tagfilter',
+                       'size' => 20,
+                       'section' => 'contribs-top',
+               ];
 
                if ( MediaWikiServices::getInstance()
-                               ->getPermissionManager()
-                               ->userHasRight( $this->getUser(), 'deletedhistory' )
+                       ->getPermissionManager()
+                       ->userHasRight( $this->getUser(), 'deletedhistory' )
                ) {
-                       $filters[] = Html::rawElement(
-                               'span',
-                               [ 'class' => 'mw-input-with-label' ],
-                               Xml::checkLabel(
-                                       $this->msg( 'history-show-deleted' )->text(),
-                                       'deletedOnly',
-                                       'mw-show-deleted-only',
-                                       $this->opts['deletedOnly'],
-                                       [ 'class' => 'mw-input' ]
-                               )
-                       );
-               }
-
-               $filters[] = Html::rawElement(
-                       'span',
-                       [ 'class' => 'mw-input-with-label' ],
-                       Xml::checkLabel(
-                               $this->msg( 'sp-contributions-toponly' )->text(),
-                               'topOnly',
-                               'mw-show-top-only',
-                               $this->opts['topOnly'],
-                               [ 'class' => 'mw-input' ]
-                       )
-               );
-               $filters[] = Html::rawElement(
-                       'span',
-                       [ 'class' => 'mw-input-with-label' ],
-                       Xml::checkLabel(
-                               $this->msg( 'sp-contributions-newonly' )->text(),
-                               'newOnly',
-                               'mw-show-new-only',
-                               $this->opts['newOnly'],
-                               [ 'class' => 'mw-input' ]
-                       )
-               );
-               $filters[] = Html::rawElement(
-                       'span',
-                       [ 'class' => 'mw-input-with-label' ],
-                       Xml::checkLabel(
-                               $this->msg( 'sp-contributions-hideminor' )->text(),
-                               'hideMinor',
-                               'mw-hide-minor-edits',
-                               $this->opts['hideMinor'],
-                               [ 'class' => 'mw-input' ]
-                       )
-               );
+                       $fields['deletedOnly'] = [
+                               'type' => 'check',
+                               'id' => 'mw-show-deleted-only',
+                               'label' => $this->msg( 'history-show-deleted' )->text(),
+                               'name' => 'deletedOnly',
+                               'section' => 'contribs-top',
+                       ];
+               }
+
+               $fields['topOnly'] = [
+                       'type' => 'check',
+                       'id' => 'mw-show-top-only',
+                       'label' => $this->msg( 'sp-contributions-toponly' )->text(),
+                       'name' => 'topOnly',
+                       'section' => 'contribs-top',
+               ];
+               $fields['newOnly'] = [
+                       'type' => 'check',
+                       'id' => 'mw-show-new-only',
+                       'label' => $this->msg( 'sp-contributions-newonly' )->text(),
+                       'name' => 'newOnly',
+                       'section' => 'contribs-top',
+               ];
+               $fields['hideMinor'] = [
+                       'type' => 'check',
+                       'cssclass' => 'mw-hide-minor-edits',
+                       'id' => 'mw-show-new-only',
+                       'label' => $this->msg( 'sp-contributions-hideminor' )->text(),
+                       'name' => 'hideMinor',
+                       'section' => 'contribs-top',
+               ];
 
+               // Allow additions at this point to the filters.
+               $rawFilters = [];
                Hooks::run(
                        'SpecialContributions::getForm::filters',
-                       [ $this, &$filters ]
-               );
-
-               $extraOptions = Html::rawElement(
-                       'div',
-                       [],
-                       implode( '', $filters )
+                       [ $this, &$rawFilters ]
                );
+               foreach ( $rawFilters as $filter ) {
+                       // Backwards compatibility support for previous hook function signature.
+                       if ( is_string( $filter ) ) {
+                               $fields[] = [
+                                       'type' => 'info',
+                                       'default' => $filter,
+                                       'raw' => true,
+                                       'section' => 'contribs-top',
+                               ];
+                               wfDeprecated(
+                                       __METHOD__ .
+                                       ' returning string[]',
+                                       '1.33'
+                               );
+                       } else {
+                               // Preferred append method.
+                               $fields[] = $filter;
+                       }
+               }
 
-               $dateRangeSelection = Html::rawElement(
-                       'div',
-                       [],
-                       Xml::label( $this->msg( 'date-range-from' )->text(), 'mw-date-start' ) . ' ' .
-                       new DateInputWidget( [
-                               'infusable' => true,
-                               'id' => 'mw-date-start',
-                               'name' => 'start',
-                               'value' => $this->opts['start'],
-                               'longDisplayFormat' => true,
-                       ] ) . '<br>' .
-                       Xml::label( $this->msg( 'date-range-to' )->text(), 'mw-date-end' ) . ' ' .
-                       new DateInputWidget( [
-                               'infusable' => true,
-                               'id' => 'mw-date-end',
-                               'name' => 'end',
-                               'value' => $this->opts['end'],
-                               'longDisplayFormat' => true,
-                       ] )
-               );
+               $fields['start'] = [
+                       'type' => 'date',
+                       'default' => '',
+                       'id' => 'mw-date-start',
+                       'label' => $this->msg( 'date-range-from' )->text(),
+                       'name' => 'start',
+                       'section' => 'contribs-date',
+               ];
+               $fields['end'] = [
+                       'type' => 'date',
+                       'default' => '',
+                       'id' => 'mw-date-end',
+                       'label' => $this->msg( 'date-range-to' )->text(),
+                       'name' => 'end',
+                       'section' => 'contribs-date',
+               ];
 
-               $submit = Xml::tags( 'div', [],
-                       Html::submitButton(
-                               $this->msg( 'sp-contributions-submit' )->text(),
-                               [ 'class' => 'mw-submit' ], [ 'mw-ui-progressive' ]
+               $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
+               $htmlForm
+                       ->setMethod( 'get' )
+                       // When offset is defined, the user is paging through results
+                       // so we hide the form by default to allow users to focus on browsing
+                       // rather than defining search parameters
+                       ->setCollapsibleOptions(
+                               ( $pagerOptions['target'] ?? null ) ||
+                               ( $pagerOptions['start'] ?? null ) ||
+                               ( $pagerOptions['end'] ?? null )
                        )
-               );
-
-               $form .= Xml::fieldset(
-                       $this->msg( 'sp-contributions-search' )->text(),
-                       $targetSelection .
-                       $namespaceSelection .
-                       $filterSelection .
-                       $extraOptions .
-                       $dateRangeSelection .
-                       $submit,
-                       [ 'class' => 'mw-contributions-table' ]
-               );
+                       ->setAction( wfScript() )
+                       ->setSubmitText( $this->msg( 'sp-contributions-submit' )->text() )
+                       ->setWrapperLegend( $this->msg( 'sp-contributions-search' )->text() );
 
                $explain = $this->msg( 'sp-contributions-explain' );
                if ( !$explain->isBlank() ) {
-                       $form .= Html::rawElement(
-                               'p', [ 'id' => 'mw-sp-contributions-explain' ], $explain->parse()
-                       );
+                       $htmlForm->addFooterText( "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>" );
                }
 
-               $form .= Xml::closeElement( 'form' );
+               $htmlForm->loadData();
 
-               return $form;
+               return $htmlForm->getHTML( false );
        }
 
        /**
index be4a1bd..9a78c5d 100644 (file)
@@ -64,11 +64,11 @@ class NewFilesPager extends RangeChronologicalPager {
        function getQueryInfo() {
                $opts = $this->opts;
                $conds = [];
-               $imgQuery = LocalFile::getQueryInfo();
-               $tables = $imgQuery['tables'];
-               $fields = [ 'img_name', 'img_timestamp' ] + $imgQuery['fields'];
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'img_user' );
+               $tables = [ 'image' ] + $actorQuery['tables'];
+               $fields = [ 'img_name', 'img_timestamp' ] + $actorQuery['fields'];
                $options = [];
-               $jconds = $imgQuery['joins'];
+               $jconds = $actorQuery['joins'];
 
                $user = $opts->getValue( 'user' );
                if ( $user !== '' ) {
@@ -89,7 +89,7 @@ class NewFilesPager extends RangeChronologicalPager {
                                        'LEFT JOIN',
                                        [
                                                'ug_group' => $groupsWithBotPermission,
-                                               'ug_user = ' . $imgQuery['fields']['img_user'],
+                                               'ug_user = ' . $actorQuery['fields']['img_user'],
                                                'ug_expiry IS NULL OR ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() )
                                        ]
                                ];
@@ -107,7 +107,7 @@ class NewFilesPager extends RangeChronologicalPager {
                                'JOIN',
                                [
                                        'rc_title = img_name',
-                                       'rc_actor = ' . $imgQuery['fields']['img_actor'],
+                                       'rc_actor = ' . $actorQuery['fields']['img_actor'],
                                        'rc_timestamp = img_timestamp'
                                ]
                        ];
index e834be3..d4ab79e 100644 (file)
        "tog-numberheadings": "isinin nomor murda anggen cara otomatis",
        "tog-editondblclick": "sunting lembar nganggen klik kaping pindo",
        "tog-editsectiononrightclick": "sayagayang panyuntingan kepahan anggen ngeklik tengen ring kepahan judul",
-       "tog-watchcreations": "imbuhin lembar sane karyanin titiang ring kepahan pangiwasan",
+       "tog-watchcreations": "Tambeh kaca sané kardi titiang miwah berkas sané unggah titiang ring pangawasan titiang",
        "tog-watchdefault": "imbuhin lembar panyuntingansane sunting titiang ring kepahan pangiwasan",
-       "tog-watchmoves": "imbuhang lembar sane kakisidang titiang ring kepahan pangiwasan",
+       "tog-watchmoves": "Tambeh kaca miwan berkas sané gingsirang titiang ring pangawasan titiang",
        "tog-watchdeletion": "imbuhin lembar sane kaapus ring kepahan pangiwasan",
-       "tog-watchuploads": "Tambehin berkas anyar sané unggah tiang ka pupulan pantaun\nTambahkan berkas baru yang saya unggah ke daftar pantauan",
+       "tog-watchuploads": "Tambeh berkas anyar sané unggah titiang ring pangawasan titiang",
        "tog-watchrollback": "Tambehin kaca sané sampun uliang tiang ka tengah pupulan pantauan tiangé",
        "tog-minordefault": "pingetin samian suntingan dados suntingan alit sane ajeg",
        "tog-previewontop": "tampilang pratayang sadurung kotak sunting lan nenten sadurungnyane",
index a209419..8da3832 100644 (file)
        "listfiles-userdoesnotexist": "Няма регистрирана потребителска сметка за „$1“.",
        "imgfile": "файл",
        "listfiles": "Списък на файловете",
+       "listfiles_subpage": "Качвания от $1",
        "listfiles_thumb": "Миникартинка",
        "listfiles_date": "Дата",
        "listfiles_name": "Име на файла",
        "blocklist-userblocks": "Скриване блокирането на потребителски сметки",
        "blocklist-tempblocks": "Скриване на временни блокирания",
        "blocklist-addressblocks": "Скриване на отделни блокирания на IP адреси",
+       "blocklist-type": "Вид:",
        "blocklist-type-opt-all": "Всички",
        "blocklist-type-opt-sitewide": "За всички уикита",
        "blocklist-type-opt-partial": "Частично",
        "version-ext-colheader-description": "Описание",
        "version-ext-colheader-credits": "Автори",
        "version-license-title": "Лиценз за $1",
+       "version-credits-title": "Списък на авторите на $1",
        "version-poweredby-credits": "Това уики работи на базата на <strong>[https://www.mediawiki.org/ MediaWiki]</strong>, copyright © 2001-$1 $2.",
        "version-poweredby-others": "други",
        "version-poweredby-translators": "преводачи в translatewiki.net",
        "feedback-bugornote": "Ако сте готови подробно да опишете технически проблем, моля [$1 докладвайте го тук].\nВ противен случай, можете да използвате лесния формуляр по-долу. Коментарът ви ще бъде добавен към страницата „[$3 $2]“, наред с вашето потребителско име.",
        "feedback-cancel": "Отказ",
        "feedback-close": "Готово",
+       "feedback-external-bug-report-button": "Изпращане на техническа задача",
        "feedback-dialog-title": "Изпращане на обратна връзка",
        "feedback-error1": "Грешка: Неразпознат резултат от API",
        "feedback-error2": "Грешка: Неуспешна редакция",
        "duration-centuries": "$1 {{PLURAL:$1|век|века}}",
        "duration-millennia": "$1 {{PLURAL:$1|хилядолетие|хилядолетия}}",
        "rotate-comment": "Изображението е завъртяно на $1 {{PLURAL:$1|градус|градуса}} по часовниковата стрелка",
+       "limitreport-cputime": "Употреба на процесорното време",
        "limitreport-cputime-value": "$1 {{PLURAL:$1|секунда|секунди}}",
+       "limitreport-walltime": "Употреба в режим на реално време",
        "limitreport-walltime-value": "$1 {{PLURAL:$1|секунда|секунди}}",
+       "limitreport-ppvisitednodes": "Брой възли посетени от препроцесора",
        "limitreport-ppvisitednodes-value": "$1/$2",
+       "limitreport-ppgeneratednodes": "Брой възли генерирани от препроцесора",
        "limitreport-ppgeneratednodes-value": "$1/$2",
        "limitreport-postexpandincludesize-value": "$1/$2 {{PLURAL:$2|байт|байта}}",
        "limitreport-templateargumentsize-value": "$1/$2 {{PLURAL:$2|байт|байта}}",
        "special-characters-group-ipa": "IPA",
        "special-characters-group-symbols": "Символи",
        "special-characters-group-greek": "Гръцки",
+       "special-characters-group-greekextended": "Гръцки (разширен)",
        "special-characters-group-cyrillic": "Кирилица",
        "special-characters-group-arabic": "Арабски",
        "special-characters-group-arabicextended": "Разширен арабски",
index 76bd5e0..3326a75 100644 (file)
@@ -3,7 +3,8 @@
                "authors": [
                        "C.R.",
                        "E. abu Filumena",
-                       "SabineCretella"
+                       "SabineCretella",
+                       "Sannita"
                ]
        },
        "exif-imagewidth": "Larghezza",
        "exif-imagedescription": "Titulo 'e l'immaggene",
        "exif-make": "Frabbeca ca muntaje 'a camera",
        "exif-model": "Mudello d' 'a camera",
-       "exif-software": "Software ausàto",
+       "exif-software": "Software ausato",
        "exif-artist": "Autore",
        "exif-copyright": "Titolare d' 'o Copyright",
-       "exif-exifversion": "Verzione d'Exif",
+       "exif-exifversion": "Verzione 'e ll'exif",
        "exif-flashpixversion": "Verziona Flashpix suppurtata",
        "exif-colorspace": "Spazio d' 'e culore",
        "exif-componentsconfiguration": "Significato d'ogne componente",
index 484ad32..57fac84 100644 (file)
@@ -29,7 +29,7 @@
        "exif-imagedescription": "Afbealdingname",
        "exif-make": "Kameramark",
        "exif-model": "Kameramodel",
-       "exif-software": "Programmatuur dee bruked wördt",
+       "exif-software": "Programmatuur dee gebruked wördt",
        "exif-artist": "Autöör",
        "exif-copyright": "Autöörsrechtenholder",
        "exif-exifversion": "Exif-versy",
@@ -93,7 +93,7 @@
        "exif-gpsaltituderef": "Höygdereferensy",
        "exif-gpsaltitude": "Höygde",
        "exif-gpstimestamp": "GPS-tyd (atoomklokke)",
-       "exif-gpssatellites": "Satelliten dee bruked binnet vöär de meating",
+       "exif-gpssatellites": "Satelliten dee gebruked binnet vöär de meating",
        "exif-gpsstatus": "Untvangerståtus",
        "exif-gpsmeasuremode": "Meatmodus",
        "exif-gpsdop": "Meatprecisy",
        "exif-gpstrack": "Beweagingsrichting",
        "exif-gpsimgdirectionref": "Referensy vöär afbealdingsrichting",
        "exif-gpsimgdirection": "Afbealdingsrichting",
-       "exif-gpsmapdatum": "Geodetiske undersööksgegeavens dee bruked binnet",
+       "exif-gpsmapdatum": "Geodetiske undersööksgegeavens dee gebruked binnet",
        "exif-gpsdestlatituderef": "Referensy vöär breyddegråd tot bestemming",
        "exif-gpsdestlatitude": "Breyddegråd bestemming",
        "exif-gpsdestlongituderef": "Referensy vöär längdegråd bestemming",
        "exif-iimversion": "IIM-versy",
        "exif-iimcategory": "Kategory",
        "exif-iimsupplementalcategory": "Anvüllende kategoryen",
-       "exif-datetimeexpires": "Neet te bruken nå",
+       "exif-datetimeexpires": "Neet te gebruken nå",
        "exif-datetimereleased": "Uutbröcht up",
        "exif-originaltransmissionref": "Oorsprungelike språklokatykode",
        "exif-identifier": "ID",
index 7d8e596..20d2991 100644 (file)
        "version-specialpages": "Toimintosivut",
        "version-parserhooks": "Jäsenninkytkökset",
        "version-variables": "Muuttujat",
-       "version-editors": "Muokkaajat",
+       "version-editors": "Muokkaimet",
        "version-antispam": "Roskalinkkien estäminen",
        "version-other": "Muut",
        "version-mediahandlers": "Median käsittelijät",
index 07f248d..53d54a2 100644 (file)
        "thursday": "jeudi",
        "friday": "vendredi",
        "saturday": "samedi",
-       "sun": "Dim.",
-       "mon": "Lun.",
-       "tue": "Mar.",
-       "wed": "Mer.",
-       "thu": "Jeu.",
-       "fri": "Ven.",
-       "sat": "Sam.",
+       "sun": "dim.",
+       "mon": "lun.",
+       "tue": "mar.",
+       "wed": "mer.",
+       "thu": "jeu.",
+       "fri": "ven.",
+       "sat": "sam.",
        "january": "janvier",
        "february": "février",
        "march": "mars",
index 7d18ae6..3ad54a4 100644 (file)
        "tooltip-pt-watchlist": "A lista de páxinas cuxas modificacións está a seguir",
        "tooltip-pt-mycontris": "Lista das súas contribucións",
        "tooltip-pt-anoncontribs": "Unha lista das modificacións feitas desde este enderezo IP",
-       "tooltip-pt-login": "Recoméndaselle rexistrarse, se ben non é obrigatorio",
+       "tooltip-pt-login": "É recomendable que se rexistre, se ben non é obrigatorio",
        "tooltip-pt-login-private": "Precisa conectarse para usa esta wiki",
        "tooltip-pt-logout": "Saír ao anonimato",
-       "tooltip-pt-createaccount": "Recoméndaselle crear unha conta e acceder ao sistema, se ben non é obrigatorio",
+       "tooltip-pt-createaccount": "É recomendable que cree unha conta e acceda ao sistema, se ben non é obrigatorio",
        "tooltip-ca-talk": "Conversa acerca do contido desta páxina",
        "tooltip-ca-edit": "Edite esta páxina",
        "tooltip-ca-addsection": "Comezar unha nova sección",
        "tooltip-p-logo": "Visitar a páxina principal",
        "tooltip-n-mainpage": "Visitar a páxina principal",
        "tooltip-n-mainpage-description": "Visitar a páxina principal",
-       "tooltip-n-portal": "Información acerca do proxecto, o que pode facer e os lugares onde atopar as cousas",
+       "tooltip-n-portal": "Información acerca do proxecto, do que pode facer e dos lugares onde atopar as cousas",
        "tooltip-n-currentevents": "Información acerca de acontecementos de actualidade",
        "tooltip-n-recentchanges": "A lista de modificacións recentes no wiki",
        "tooltip-n-randompage": "Cargar unha páxina ao chou",
index 6a35a73..50ef092 100644 (file)
        "undo-norev": "La modifica non può essere annullata perché non esiste o è stata cancellata.",
        "undo-nochange": "Sembra che la modifica sia già stata annullata.",
        "undo-summary": "Annullata la modifica $1 di [[Special:Contributions/$2|$2]] ([[User talk:$2|discussione]])",
-       "undo-summary-anon": "Annulla la modifica $1 di [[Speciale:Contributi/$2|$2]]",
+       "undo-summary-anon": "Annullata la modifica $1 di [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "Annullata la modifica $1 di un utente nascosto",
        "cantcreateaccount-text": "La registrazione è stata bloccata da [[User:$3|$3]] per questo indirizzo IP ('''$1''').\n\nLa motivazione del blocco fornita da $3 è la seguente: ''$2''",
        "cantcreateaccount-range-text": "La registrazione da indirizzi IP nell'intervallo <strong>$1</strong>, che include il tuo (<strong>$4</strong>), è stata bloccata da [[User:$3|$3]].\n\nLa motivazione fornita da $3 è <em>$2</em>",
        "prefs-help-email": "L'inserimento del proprio indirizzo email è facoltativo, ma permette di ricevere la propria password qualora venisse dimenticata.",
        "prefs-help-email-others": "Puoi anche scegliere di lasciare che gli altri ti contattino via posta elettronica con un collegamento dalla tua pagina utente o di discussione.\nIl tuo indirizzo non viene rivelato quando gli altri utenti ti contattano.",
        "prefs-help-email-required": "L'indirizzo email è obbligatorio.",
+       "prefs-help-requireemail": "Se selezionato, invieremo solo email per la reimpostazione della password se la persona che sta resettando la password ha fornito sia il nome utente che l'email per questa utenza.",
        "prefs-info": "Informazioni di base",
        "prefs-i18n": "Internazionalizzazione",
        "prefs-signature": "Firma",
        "ipblocklist-legend": "Cerca un utente bloccato",
        "blocklist-userblocks": "Nascondi i blocchi degli utenti registrati",
        "blocklist-tempblocks": "Nascondi i blocchi temporanei",
+       "blocklist-indefblocks": "Nascondi blocchi infiniti",
        "blocklist-addressblocks": "Nascondi i blocchi di un solo IP",
        "blocklist-type": "Tipo:",
        "blocklist-type-opt-all": "Tutto",
        "move-page-legend": "Spostamento di pagina",
        "movepagetext": "Questo modulo consente di rinominare una pagina, spostando tutta la sua cronologia al nuovo nome. La pagina attuale diverrà automaticamente un redirect al nuovo titolo. Puoi aggiornare automaticamente i redirect che puntano al titolo originale. Puoi decidere di non farlo, ma ricordati di verificare che lo spostamento non abbia creato [[Special:DoubleRedirects|doppi redirect]] o [[Special:BrokenRedirects|redirect errati]]. L'onere di garantire che i collegamenti alla pagina restino corretti spetta a chi la sposta.\n\nSi noti che la pagina <strong>non</strong> sarà spostata se ne esiste già una con il nuovo nome, a meno che quest'ultima non sia costituita solo da un redirect e sia priva di versioni precedenti. In caso di spostamento errato si può quindi tornare subito al vecchio titolo, e non è possibile sovrascrivere per errore una pagina già esistente.\n\n<strong>Nota:</strong>\nUn cambiamento così drastico può creare contrattempi e problemi, soprattutto per le pagine più visitate. Accertarsi di aver valutato le conseguenze dello spostamento prima di procedere.",
        "movepagetext-noredirectfixer": "Questo modulo consente di rinominare una pagina, spostando tutta la sua cronologia al nuovo nome. La pagina attuale diverrà automaticamente un redirect al nuovo titolo. Controlla che lo spostamento non abbia creato [[Special:DoubleRedirects|doppi redirect]] o [[Special:BrokenRedirects|redirect errati]]. L'onere di garantire che i collegamenti alla pagina restino corretti spetta a chi la sposta.\n\nSi noti che la pagina <strong>non</strong> sarà spostata se ne esiste già una con il nuovo nome, a meno che non sia costituita solo da un redirect e sia priva di versioni precedenti. In caso di spostamento errato si può quindi tornare subito al vecchio titolo, e non è possibile sovrascrivere per errore una pagina già esistente.\n\n<strong>Nota:</strong>\nUn cambiamento così drastico può creare contrattempi e problemi, soprattutto per le pagine più visitate. Accertarsi di aver valutato le conseguenze dello spostamento prima di procedere.",
-       "movepagetext-noredirectsupport": "Usare il seguente modulo rinominerà una pagina, spostando tutta la sua cronologia al nuovo nome. È tua la responsabilità di assicurarti che i collegamenti continuino a puntare dove dovrebbero.\n\nNotare che la pagina <strong>non</strong> sarà spostata se è già presente una pagina presso il nuovo titolo.\nQuesto comporta che in caso di errori puoi spostare nuovamente una pagina al nome precedente, e che non puoi sovrascrivere una pagina esistente.\n\n<strong>Nota:</strong>\nQuesto cambiamento può essere drastico e inaspettato se la pagina è popolare; per favore assicurati di capire le conseguenze di ciò prima di procedere.",
+       "movepagetext-noredirectsupport": "Questo modulo consente di rinominare una pagina, spostando tutta la sua cronologia al nuovo nome. L'onere di garantire che i collegamenti alla pagina restino corretti spetta a chi la sposta.\n\nSi noti che la pagina <strong>non</strong> sarà spostata se ne esiste già una con il nuovo nome.\nIn caso di spostamento errato si può quindi tornare subito al vecchio titolo, e non è possibile sovrascrivere per errore una pagina già esistente.\n\n<strong>Nota:</strong>\nUn cambiamento così drastico può creare contrattempi e problemi, soprattutto per le pagine più visitate. Accertarsi di aver valutato le conseguenze dello spostamento prima di procedere.",
        "movepagetalktext": "Se selezioni questa casella, la corrispondente pagina di discussione sarà spostata automaticamente al nuovo titolo, a meno che esista già una pagina di discussione non vuota.\n\nIn questi casi, se lo ritieni opportuno, dovrai spostare o unire manualmente la pagina.",
        "moveuserpage-warning": "'''Attenzione:''' Si sta per spostare una pagina utente. Nota che verrà spostata solamente la pagina. L'utente ''non'' sarà rinominato.",
        "movecategorypage-warning": "<strong>Attenzione:</strong> si sta per spostare una categoria. Solo questa pagina verrà spostata: tutte le pagine nella vecchia categoria <em>non</em> saranno inserite nella nuova.",
index bdfc178..e04bda1 100644 (file)
@@ -11,7 +11,8 @@
                        "Universal Life",
                        "לערי ריינהארט",
                        "아라",
-                       "StevenJ81"
+                       "StevenJ81",
+                       "Chabi1"
                ]
        },
        "tog-underline": "Suliñar los atamientos:",
        "mypage": "Hoja",
        "mytalk": "Diskusyón",
        "anontalk": "Diskusyón para este adresso de IP",
-       "navigation": "Navigación",
-       "and": "&#32;y",
+       "navigation": "Navigasyon",
+       "and": "&#32;i",
        "faq": "DDS",
        "actions": "Aksiones",
        "namespaces": "Espacios de nombres",
        "tagline": "De {{SITENAME}}",
        "help": "Ayudo",
        "search": "Buxcar",
-       "searchbutton": "Buxcar",
+       "searchbutton": "Bushkar",
        "go": "Ir",
        "searcharticle": "Yir",
        "history": "La istoria de la hoja",
        "tooltip-pt-logout": "Sal de tu cuento",
        "tooltip-pt-createaccount": "Te consejamos de avrir un cuento y hazer entrada allá, portanto no sos obligado",
        "tooltip-ca-talk": "Diskusyón encima del contènido desta hoja",
-       "tooltip-ca-edit": "Troca esta hoja",
+       "tooltip-ca-edit": "Troka esta oja",
        "tooltip-ca-addsection": "Ajusta un kapítolo muevo",
        "tooltip-ca-viewsource": "Esta hoja está guadrada.\nPuedes ver su manadero",
        "tooltip-ca-history": "Enderechamientos passados desta hoja",
        "feedback-cancel": "Anular",
        "feedback-message": "Messaje",
        "feedback-subject": "Sujeto",
-       "searchsuggest-search": "Buxca en {{SITENAME}}",
+       "searchsuggest-search": "Bushka en {{SITENAME}}",
        "duration-seconds": "$1{{PLURAL:$1|segundo|segundos}}",
        "duration-minutes": "$1{{PLURAL:$1|minuto|minutos}}",
        "duration-hours": "$1{{PLURAL:$1|ora|oras}}",
index f355e60..8d79167 100644 (file)
        "password-login-forbidden": "Šī lietotājvārda un paroles izmantošana ir aizliegta.",
        "mailmypassword": "Atiestatīt paroli",
        "passwordremindertitle": "Jauna pagaidu parole no {{SITENAME}}s",
-       "passwordremindertext": "Kāds (iespējams, Tu pats, no IP adreses $1)\nlūdza, lai nosūtām Tev jaunu {{SITENAME}} ($4) paroli.\nLietotajam $2 pagaidu parole tagad ir $3.\nLudzu, nomaini paroli, kad esi veiksmīgi iekļuvis iekšš.\nTavas pagaidu paroles derīguma termiņš beigsies pēc {{PLURAL:$5|$5 dienām|$5 dienas|$5 dienām}}.\n\nJa paroles pieprasījumu bija nosūtījis kāds cits, vai arī tu atcerējies savu veco paroli, šo var ignorēt. Vecā parole joprojām darbojas.",
+       "passwordremindertext": "Kāds (no IP adreses $1)\nlūdza, lai nosūtām tev jaunu {{SITENAME}} ($4) paroli.\nLietotajam $2 izveidota pagaidu parole $3. Ja tāds bija tavs mērķis, tagad tev jāpieslēdzas un jānomaina parole. Tavas pagaidu paroles derīguma termiņš beigsies pēc {{PLURAL:$5|$5 dienām|$5 dienas|$5 dienām}}.\n\nJa paroles pieprasījumu bija nosūtījis kāds cits, vai arī tu atcerējies savu veco paroli, šo var ignorēt. Vecā parole joprojām darbojas.",
        "noemail": "Lietotājs \"$1\" nav reģistrējis e-pasta adresi.",
        "noemailcreate": "Tev jānorāda derīgu e-pasta adresi",
        "passwordsent": "Esam nosūtījuši jaunu paroli uz e-pasta adresi, kuru ir norādījis lietotājs $1. Lūdzu, nāc iekšā ar jauno paroli, kad būsi to saņēmis.",
index 1dd2e86..b78c96c 100644 (file)
@@ -18,7 +18,8 @@
                        "Zakiy",
                        "Vlad5250",
                        "S Kartika",
-                       "NoiX180"
+                       "NoiX180",
+                       "Pitnia Ayu Saputri"
                ]
        },
        "tog-underline": "Garih bawahi pautan:",
        "right-unblockself": "Malapehan sakek surang",
        "right-protect": "Ubah tingkek palinduangan jo suntiang halaman yang dilinduangi baruntun",
        "right-editprotected": "Manyuntiang halaman yang dilinduangi sabagai \"{{int:protect-level-sysop}}\"",
+       "right-editcontentmodel": "manyuntiang model konten sabuah laman",
        "right-editinterface": "Manyuntiang antarmuko pangguno",
        "right-editusercss": "Manyuntiang berkas CSS pangguno lain",
        "right-edituserjson": "Manyuntiang berkas JSON pangguno lain",
        "right-edituserjs": "Manyuntiang berkas JS pangguno lain",
+       "right-editsitecss": "manyuntiang CSS untuak sadoalah situs",
+       "right-editsitejson": "suntiang JSON untuak sadoalah situs",
+       "right-editsitejs": "suntiang JavaScript untuak sadoalah situs",
+       "right-editmyusercss": "suntiang berkas CSS pangguno Sanak",
+       "right-editmyuserjson": "suntiang berkas JSON pangguno Sanak",
+       "right-editmyuserjs": "suntiang berkas JavaScript pangguno Sanak",
+       "right-editmyuserjsredirect": "suntiang berkas JavaScript Sanak nan marupokan aliahan",
+       "right-viewmywatchlist": "Caliak daftar pantauan Sanak",
+       "right-editmywatchlist": "Manyuntiang daftar Sanak surang. Beberapo kegiatan akan tetap akan manambahan halaman tanpa hak iko.",
+       "right-viewmyprivateinfo": "Maliek data pribadi Sanak surang (misalnyo: alamaik email, namo asli Sanak)",
+       "right-editmyprivateinfo": "Manyuntiang data pribadi Sanak surang (misalnyo: alamaik surel, namo asli)",
+       "right-editmyoptions": "manyuntiang preferensi Sanak",
+       "right-rollback": "mambaliakan sacaro capek suntiangan-suntiangan pangguno tarakhia nan manyuntiang laman tatantu",
+       "right-markbotedits": "tandoi pambaliakan revisi sabagai suntiangan bot",
        "right-noratelimit": "Indak dipangaruahi jo pambatehan jumlah suntiangan",
        "right-import": "Mangimpor laman dari wiki lain",
        "right-importupload": "Mangimpor laman dari berkas nan dimuek",
+       "right-patrol": "manandoi suntiangan pangguno lain sabagai terpatroli",
        "right-autopatrol": "Suntiangan surang sacaro otomatih ditandoi tapantau",
+       "right-patrolmarks": "caliak panandoan patroli parubahan tabaru",
+       "right-unwatchedpages": "mancaliak daftar laman nan indak tapantau",
+       "right-mergehistory": "manggabuangan revisi-revisi tadahulu dari laman ko",
        "right-userrights": "Manyuntiang sadoalah hak pangguno",
        "right-userrights-interwiki": "Manyuntiang hak pangguno-pangguno di wiki lain",
        "right-siteadmin": "Mangunci jo mambukak kunci basis data",
        "action-editcontentmodel": "manyuntiang model konten sabuah laman",
        "action-managechangetags": "buek jo matikan tag",
        "action-applychangetags": "terapkan tag basamoan jo parubahan Sanak",
-       "action-changetags": "Tambah jo apuih [[Special:Tags|tag]] arbitrari pado tiok-tiok  revisi jo entri log",
+       "action-changetags": "Tambah jo apuih arbitrari pado tiok-tiok  revisi jo entri log",
        "action-deletechangetags": "apuih tag dari basisdata",
        "action-purge": "apuih singgahan laman ko",
        "action-apihighlimits": "manggunoan bateh labiah tinggi dalam kueri API",
        "recentchanges-legend": "Piliahan parubahan baru",
        "recentchanges-summary": "Caliak parubahan baru di wiki pado laman ko.<br />\n;Patunjuak:(<span style=\"color:blue;\">bedo</span>) parubahan, (<span style=\"color:blue;\">sijarah</span>) riwayaik parubahan, '''B''' laman baru, '''b''' suntiangan bot, '''k''' suntiangan ketek, <span class=\"unpatrolled\">!</span> parubahan alun dipatroli,<br /><span style=\"color:green;\">'''(+ ''bita'')'''</span> isi laman batambah, <span style=\"color:red;\">(- ''bita'')</span> isi laman bakurang, (← Ikhtisar otomatih), (→ <span style=\"color:grey;\">Suntiangan bagian</span>)",
        "recentchanges-noresult": "Indak ado parubahan dalam rantang wakatu ko nan sasuai jo kriteria.",
+       "recentchanges-timeout": "wakatu mancari alah habih. Sanak mungkin ka mancubo parameter pancarian nan lain.",
+       "recentchanges-network": "Salamo tajadi kasalahan teknis, indak ado hasil nan biso dimuek. Silahkan cubo untuak manyagarkan halaman liak.",
+       "recentchanges-notargetpage": "Masuakan namo halaman nan di ateh untuak maliek parubahan nan terkait jo halaman itu.",
        "recentchanges-feed-description": "Tamuan parubahan baru dalam umpan wiki ko",
        "recentchanges-label-newpage": "Suntiangan ko mambuek laman baru",
        "recentchanges-label-minor": "Iko suntiangan ketek",
        "recentchanges-label-plusminus": "Parubahan ukuran laman dalam bita",
        "recentchanges-legend-heading": "<strong>Katarangan:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (caliak pulo [[Special:NewPages|daftar laman nan baru]])",
+       "recentchanges-submit": "Tunjuakan",
+       "rcfilters-tag-remove": "Hapuih '$1'",
+       "rcfilters-legend-heading": "<strong> daftar singkekan:</strong>",
        "rcfilters-other-review-tools": "Pakakeh paninjauan lainnyo",
        "rcfilters-group-results-by-page": "Kalompokan hasil manuruik laman",
        "rcfilters-activefilters": "Panyariang aktip",
        "rcfilters-activefilters-hide": "Suruakan",
        "rcfilters-activefilters-show": "Tunjuakan",
+       "rcfilters-activefilters-hide-tooltip": "Sambunyian wilayah filter aktif",
+       "rcfilters-activefilters-show-tooltip": "Tunjukan wilayah filter aktif",
        "rcfilters-advancedfilters": "Panyariang lanjutan",
        "rcfilters-limit-title": "Hasil untuak ditampilkan",
        "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|parubahan}}, $2",
        "rcfilters-savedqueries-unsetdefault": "Hapuih dari baku",
        "rcfilters-savedqueries-remove": "Hapuih",
        "rcfilters-savedqueries-new-name-label": "Namo",
+       "rcfilters-savedqueries-new-name-placeholder": "Jalehan tujuan panyaringan iko",
        "rcfilters-savedqueries-apply-label": "Buek panyariang",
        "rcfilters-savedqueries-apply-and-setdefault-label": "Buek panyariang baku",
        "rcfilters-savedqueries-cancel-label": "Batalan",
        "rcfilters-savedqueries-add-new-title": "Simpan pangaturan panyariang ko",
        "rcfilters-savedqueries-already-saved": "Panyariang ko alah tasimpan. Ubah pangaturan Sanak untuak manyimpan panyariang baru.",
+       "rcfilters-restore-default-filters": "Kambalian filter awal",
+       "rcfilters-clear-all-filters": "Hapuih sado panyaringan",
        "rcfilters-show-new-changes": "Tunjuakan parubahan baru dari $1",
        "rcfilters-search-placeholder": "Panyariang parubahan (gunokan menu atau cari namo panyariang)",
        "rcfilters-search-placeholder-mobile": "Panyariang",
+       "rcfilters-invalid-filter": "Panyaringan indak sah",
+       "rcfilters-empty-filter": "Indak ado filter aktif. Sado alah kontribusi ditampilan.",
        "rcfilters-filterlist-title": "Panyariang",
+       "rcfilters-filterlist-whatsthis": "Baa caro karajonyo?",
+       "rcfilters-filterlist-feedbacklink": "Agiah tau kami apo nan Sanak pikian tantang alaik-alaik filter ko",
+       "rcfilters-highlightbutton-title": "Caliak hasilnyo",
+       "rcfilters-highlightmenu-title": "Piliah warno",
+       "rcfilters-highlightmenu-help": "Piliah warno untuak manyorot atribut iko",
        "rcfilters-filterlist-noresults": "Indak ado panyariang ditamukan",
+       "rcfilters-noresults-conflict": "Hasil indak ditamuan karano kriteria pancariannyo batantangan",
+       "rcfilters-state-message-subset": "Filter iko indak akan bapangaruh karano hasilnyo disaratoan jo {{PLURAL:$2|}} (cubo caliak untuak mambedaannyo):$1",
+       "rcfilters-state-message-fullcoverage": "Mamiliah sado panyaringan dalam kalompok iko samo jo indak mamiliah apopun, sahinggo panyaringan ko indak mambarikan hasil. Kalompok itu: $1",
+       "rcfilters-filtergroup-authorship": "Kontribusi panulih",
        "rcfilters-filter-editsbyself-label": "Suntiangan Sanak",
        "rcfilters-filter-editsbyself-description": "Kontribusi Sanak",
        "rcfilters-filter-editsbyother-label": "Suntiangan urang lain",
        "rcfilters-filter-logactions-label": "Tindakan tacataik",
        "rcfilters-filter-logactions-description": "Tindakan administratif, pambuatan akun, pangapusan halaman, pangunggahan...",
        "rcfilters-hideminor-conflicts-typeofchange": "Jinih parubahan tatantu indak dapek ditandoi sabagai \"ketek\", jadi panyariang ko bakonflik jo panyariang Jinih Parubahan iko: $1",
+       "rcfilters-typeofchange-conflicts-hideminor": "Jenih parubahann ko berkonflik jo \"Suntiangan ketek\". Jenih parubahan tatantu indak dapek ditandoi sabagai \"ketek\".",
        "rcfilters-filtergroup-lastrevision": "Revisi tabaru",
        "rcfilters-filter-lastrevision-label": "Revisi tabaru",
        "rcfilters-filter-lastrevision-description": "Anyo parubahan tabaru pado laman ko.",
        "rcfilters-exclude-button-on": "Salain nan tapiliah",
        "rcfilters-view-tags": "Suntiangan ditandoi",
        "rcfilters-view-namespaces-tooltip": "Sariang hasil manuruik ruangnamo",
+       "rcfilters-view-tags-tooltip": "Pisahan hasil manggonoan panyuntingan",
+       "rcfilters-view-return-to-default-tooltip": "Kembali ka manu panyaringan utamo",
+       "rcfilters-view-tags-help-icon-tooltip": "Palajari labiah lanjuik tantangan suntiangan jo TAG",
        "rcfilters-liveupdates-button": "Parubahan langsuang",
        "rcfilters-liveupdates-button-title-on": "Matian parubahan langsuang",
+       "rcfilters-liveupdates-button-title-off": "Tampilan parubahan baru katiko parubahan tu alah tajadi",
+       "rcfilters-watchlist-markseen-button": "Tandoi sadonyo parubahan sabagai taliek",
        "rcnotefrom": "Di bawah iko adolah {{PLURAL:$5|parubahan|babagai parubahan}} sajak <strong>$3, $4</strong> (ditampilkan sampai <strong>$1</strong> parubahan).",
        "rclistfrom": "Tunjuakan parubahan baru mulai dari tanggal $3 $2",
        "rcshowhideminor": "$1 suntiangan ketek",
index e421252..14235e1 100644 (file)
        "protect": "Prutegge",
        "protect_change": "càgna",
        "unprotect": "Càgna prutezzione",
-       "newpage": "Paggena nòva",
+       "newpage": "Paggena nova",
        "talkpagelinktext": "Chiàcchiera",
        "specialpage": "Paggena speciàle",
        "personaltools": "Strumiente perzonale",
        "shown-title": "Fa verè {{PLURAL:$1|nu risultato|$1 risultate}} pe paggena",
        "viewprevnext": "Vire ($1 {{int:pipe-separator}} $2) ($3).",
        "searchmenu-exists": "'''Ncopp' 'o sito esiste na paggena c' 'o nomme \"[[:$1]]\"'''\n{{PLURAL:$2|0=|Vedite pure dint'a l'ati risultate 'e cerca.}}",
-       "searchmenu-new": "<strong>'''Crèa 'a paggena \"[[:$1]]\" ncopp'a stu wiki!'''</strong> {{PLURAL:$2|0=|Vedite pure 'a paggena truvata c' 'a recerca vuosta|Vedite pure 'e risultate d\"a recerca}}",
+       "searchmenu-new": "<strong>'''Crèa 'a paggena \"[[:$1]]\" ncopp'a sta wiki!'''</strong> {{PLURAL:$2|0=|Vedite pure 'a paggena truvata c' 'a recerca vuosta|Vedite pure 'e risultate d' 'a recerca}}",
        "searchprofile-articles": "Paggene 'e contenute",
        "searchprofile-images": "Multimedia",
        "searchprofile-everything": "Tutto",
        "right-suppressionlog": "Vide 'e riggistre private",
        "right-block": "Nun fa fa' cagnamienti a ll'at'utente",
        "right-blockemail": "Nun fa mannà e-mail a n'utente",
-       "right-hideuser": "Blocca n'utente e fallo sparì 'a 'o pubbreco",
+       "right-hideuser": "Fremma n'utente e annascunnillo r'o pubbreco",
        "right-ipblock-exempt": "Ignora 'e blocche 'e l'IP, 'e blocche automatece e li blocche 'e range 'e l'IP",
        "right-unblockself": "Sblocca se stesso",
        "right-protect": "Cagna 'e livelle 'e prutezione 'e cagna paggene prutette ricurzivamente",
        "right-mergehistory": "Aunisce 'a cronologgia d' 'e paggene",
        "right-userrights": "Cagna 'e deritte 'e ll'utente",
        "right-userrights-interwiki": "Cagna 'e deritte 'e ll'utente int'a l'ati wiki",
-       "right-siteadmin": "Blocca e sblocca 'o database",
+       "right-siteadmin": "Chiure e arape 'o database",
        "right-override-export-depth": "Esporta 'e paggene azzeccanno 'e paggene cullegate nfin'a na profondità 'e 5",
        "right-sendemail": "Manna na mail a ll'at'utente",
        "right-managechangetags": "Crìa e appiccia/stuta 'e [[Special:Tags|tag]]",
        "uploadlogpage": "Riggistro 'e carreche",
        "uploadlogpagetext": "Ccà abbascio song'alencate l'urdeme file carrecate.\nCuntrullate 'a [[Special:NewFiles|gallaria d' 'e file nuove]] pe' ve ffà na guardata cchiù visuale 'e tutto.",
        "filename": "Nomme d' 'o file",
-       "filedesc": "Énnece",
+       "filedesc": "Riepilego",
        "fileuploadsummary": "Dettaglie:",
        "filereuploadsummary": "Cagnamiente a 'o file:",
        "filestatus": "Stato d' 'o copyright:",
index 893a2f2..91f6f1e 100644 (file)
@@ -44,7 +44,7 @@
        "tog-enotifusertalkpages": "Stüür my een bericht as myn oaverlegsyde wysigd is.",
        "tog-enotifminoredits": "Stüür my ouk een bericht by kleine bewarkingen van syden en bestanden",
        "tog-enotifrevealaddr": "Myn e-postadres låten seen in e-postberichten",
-       "tog-shownumberswatching": "Et antal brukers bekyken dee disse syde volgt",
+       "tog-shownumberswatching": "Et antal gebrukers bekyken dee disse syde volgt",
        "tog-oldsig": "Bestånde handteykening:",
        "tog-fancysig": "Underteykening seen as wikitekst (sunder automatiske verwysing)",
        "tog-uselivepreview": "Nåkyksyde låten seen sunder eyrst te herladen",
        "tog-watchlisthideown": "Verbarg myn eigen bewarkingen",
        "tog-watchlisthidebots": "Verbarg botbrukers",
        "tog-watchlisthideminor": "Verbarg kleine wysigingen in myn volglyste",
-       "tog-watchlisthideliu": "Bewarkingen van anmeldede brukers up myn volglyste verbargen",
-       "tog-watchlisthideanons": "Bewarkingen van anonyme brukers up myn volglyste verbargen",
+       "tog-watchlisthideliu": "Bewarkingen van anmeldede gebrukers up myn volglyste verbargen",
+       "tog-watchlisthideanons": "Bewarkingen van anonyme gebrukers up myn volglyste verbargen",
        "tog-watchlisthidepatrolled": "Wysigingen dee markeerd binnet up volglyste verbargen",
-       "tog-ccmeonemails": "Stüür my kopien van berichten an andere brukers",
+       "tog-ccmeonemails": "Stüür my kopyen van berichten an andere gebrukers",
        "tog-diffonly": "Under wysigingen neet de syde-inhold låten seen.",
        "tog-showhiddencats": "Låt verbörgen kategoryen seen",
        "tog-norollbackdiff": "Wysigingen vordlåten nå et weaderümmedraien",
        "tog-useeditwarning": "Wårschüw my as ik een bewarkede syde afsluten wil dee noch neet seakerd is",
-       "tog-prefershttps": "Altyd een beveiligde verbinding bruken as jy anmelded binnet",
+       "tog-prefershttps": "Altyd een beveiligde verbinding gebruken as jy anmelded binnet",
        "underline-always": "Altyd",
        "underline-never": "Nooit",
        "underline-default": "Standard in juw formgeaving of webkyker",
        "cancel": "Afbreaken",
        "moredotdotdot": "Meyr...",
        "morenotlisted": "Disse lyste is möägelik neet kompleet.",
-       "mypage": "Brukerssyde",
+       "mypage": "Gebrukerssyde",
        "mytalk": "Oaverleg",
        "anontalk": "Oaverleg",
        "navigation": "Navigaty",
        "views": "Weadergåven",
        "toolbox": "Warktügen",
        "tool-link-userrights": "{{GENDER:$1|Brukersgruppen}} wysigen",
-       "tool-link-emailuser": "Disse {{GENDER:$1|bruker}} een bericht stüren",
+       "tool-link-emailuser": "Disse {{GENDER:$1|gebruker}} een bericht stüren",
        "imagepage": "Bestandssyde bekyken",
        "mediawikipage": "Berichtsyde bekyken",
        "templatepage": "Mal bekyken",
        "privacypage": "Project:Gegeavensbeleid",
        "badaccess": "Geen tostemming",
        "badaccess-group0": "Jy hebbet geen tostemming üm disse akty uut te voren.",
-       "badaccess-groups": "Disse akty kan allinnig uutvoord wörden döär brukers uut {{PLURAL:$2|de grup|eyn van de gruppen}}: $1.",
+       "badaccess-groups": "Disse akty kan allinnig uutvoord wörden döär gebrukers uut {{PLURAL:$2|de grup|eyn van de gruppen}}: $1.",
        "versionrequired": "Versy $1 van MediaWiki is nöydig",
-       "versionrequiredtext": "Versy $1 van MediaWiki is nöydig üm disse syde te bruken. See [[Special:Version|Versy]].",
+       "versionrequiredtext": "Versy $1 van MediaWiki is nöydig üm disse syde te gebruken. See [[Special:Version|Versy]].",
        "ok": "Okee",
        "retrievedfrom": "Van \"$1\"",
        "youhavenewmessages": "{{PLURAL:$3|Jy hebbet}} $1 ($2).",
-       "youhavenewmessagesfromusers": "{{PLURAL:$4|Jy hebbet}} $1 van {{PLURAL:$3|een andere bruker|$3 brukers}} ($2).",
-       "youhavenewmessagesmanyusers": "Jy hebbet $1 van een bült brukers ($2).",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|Jy hebbet}} $1 van {{PLURAL:$3|een andere gebruker|$3 gebrukers}} ($2).",
+       "youhavenewmessagesmanyusers": "Jy hebbet $1 van een bült gebrukers ($2).",
        "newmessageslinkplural": "{{PLURAL:$1|een ny bericht|999=nye berichten}}",
        "newmessagesdifflinkplural": "läste {{PLURAL:$1|wysiging|999=wysigingen}}",
        "youhavenewmessagesmulti": "Jy hebbet nye berichten up $1",
        "sort-descending": "Afloupend sorteren",
        "sort-ascending": "Uploupend sorteren",
        "nstab-main": "Syde",
-       "nstab-user": "Brukerssyde",
+       "nstab-user": "Gebrukerssyde",
        "nstab-media": "Mediasyde",
        "nstab-special": "Speciale syde",
        "nstab-project": "Projektsyde",
        "delete-hook-aborted": "Et vordsmyten wördt in et wyre skopped döär een topassing van MediaWiki.\nDer is wyder geen informaty beskikbår.",
        "no-null-revision": "Kun geen leadige nye versy maken vöär de syde \"$1\"",
        "badtitle": "Ungeldige name",
-       "badtitletext": "De name van de upvrågde syde is neet geldig, leadig, of der stünd een verkeyrde interspråk- of interwikiverwysing in.\nMöägelik binnet der eyn of meyr teykens bruked dee neet in titels tostån binnet.",
+       "badtitletext": "De name van de upvrågde syde is neet geldig, leadig, of der stünd een verkeyrde interspråk- of interwikiverwysing in.\nMöägelik binnet der eyn of meyr teykens gebruked dee neet in titels tostån binnet.",
        "perfcached": "Disse gegeavens kummen uut et tüskengehöägen en binnet meskeen neet aktueel. Der {{PLURAL:$1|is houguut eyn resultaat|binnet houguut $1 resultaten}} beskikbår in et tüskengehöägen.",
        "perfcachedts": "Disse gegeavens kummen uut et tüskengehöägen dee vöär et lätst bywarked is up $2 üm $3. Der {{PLURAL:$4|is houguut eyn resultaat|binnet houguut $4 resultaten}} beskikbår in et tüskengehöägen.",
        "querypage-no-updates": "Disse syde wördt neet bywarked.\nGegeavens up disse syde wördet neet vervarsd.",
        "protectedpagetext": "Disse syde is beveiligd. Bewarken of andere handelingen binnet neet möägelik.",
        "viewsourcetext": "Jy künnet de brontekst van disse syde bewarken en bekyken.",
        "viewyourtext": "Jy künnet <strong>juw bewarkingen</strong> an de brontekst van disse syde bekyken en kopieren.",
-       "protectedinterface": "Up disse syde steyt tekst dee bruked wördt vöär systeemteksten van disse wiki, en is beveiligd üm misbruuk te vöärkommen. Bruuk [https://translatewiki.net/ translatewiki.net], et lokaliseringsprojekt vöär MediaWiki, üm oaversetingen vöär alle wikis to te vogen of te wysigen.",
-       "editinginterface": "<strong>Wårsküwing:</strong> jy bewarket een syde dee teksten bruukt vöär de brukersümgeaving van de programmatuur. \nWat jy up disse syde wysigen is van invlööd up de brukersümgeaving van andere brukers van disse wiki.",
-       "translateinterface": "Üm oaversettingen vöär alle wikis to te vogen of te wysigen, kün jy [https://translatewiki.net/ translatewiki.net] bruken, et lokaliseringsprojekt vöär MediaWiki.",
+       "protectedinterface": "Up disse syde steyt tekst dee gebruked wördt vöär systeemteksten van disse wiki, en is beveiligd üm misbruuk te vöärkommen. Bruuk [https://translatewiki.net/ translatewiki.net], et lokaliseringsprojekt vöär MediaWiki, üm oaversetingen vöär alle wikis to te vogen of te wysigen.",
+       "editinginterface": "<strong>Wårsküwing:</strong> jy bewarket een syde dee teksten gebruukt vöär de gebrukersümgeaving van de programmatuur. \nWat jy up disse syde wysigen is van invlööd up de gebrukersümgeaving van andere gebrukers van disse wiki.",
+       "translateinterface": "Üm oaversettingen vöär alle wikis to te vogen of te wysigen, kün jy [https://translatewiki.net/ translatewiki.net] gebruken, et lokaliseringsprojekt vöär MediaWiki.",
        "cascadeprotected": "Disse syde is beveiligd ümdat et vöärkümt in de volgende {{PLURAL:$1|syde|syden}}, dee beveiligd {{PLURAL:$1|is|binnet}} mid de \"kaskade\"-opty:\n$2",
        "namespaceprotected": "Jy möäget geen syden in de <strong>$1</strong>-naamruumde bewarken.",
-       "customcssprotected": "Jy möäget disse CSS-syde neet bewarken, ümdat der persoonlike instellingen van een andere bruker in stån.",
-       "customjsprotected": "Jy möäget disse JavaScript-syde neet bewarken, ümdat der persoonlike instellingen van een andere bruker in stån.",
+       "customcssprotected": "Jy möäget disse CSS-syde neet bewarken, ümdat der persoonlike instellingen van een andere gebruker in stån.",
+       "customjsprotected": "Jy möäget disse JavaScript-syde neet bewarken, ümdat der persoonlike instellingen van een andere gebruker in stån.",
        "mycustomcssprotected": "Jy hebbet geen rechten üm disse CSS-syde te bewarken.",
        "mycustomjsprotected": "Jy hebbet geen rechten üm disse JavaScript-syde te bewarken.",
        "myprivateinfoprotected": "Jy hebbet geen rechten üm juw priveegegeavens te bewarken.",
        "logouttext": "'''Je bin noen aofemeld.'''\n\nt Kan ween dat der wat ziejen bin die weeregeven wörden as of je an-emeld bin totda'j t tussengeheugen van joew webkieker leegmaken.",
        "welcomeuser": "Welkom, $1!",
        "welcomecreation-msg": "Juw gebrukerskonto is anmaked.\nVergeat neet juw [[Special:Preferences|vöärköären vöär {{SITENAME}}]] in te stellen.",
-       "yourname": "Brukersname",
-       "userlogin-yourname": "Brukersname",
-       "userlogin-yourname-ph": "Geav juw brukersname up",
-       "createacct-another-username-ph": "Geav de brukersname up",
+       "yourname": "Gebrukersname",
+       "userlogin-yourname": "Gebrukersname",
+       "userlogin-yourname-ph": "Geav juw gebrukersname up",
+       "createacct-another-username-ph": "Geav de gebrukersname up",
        "yourpassword": "Wachtwoord",
        "userlogin-yourpassword": "Wachtwoord",
        "userlogin-yourpassword-ph": "Geav juw wachtwoord up",
        "createacct-yourpasswordagain": "Bevästig wachtwoord",
        "createacct-yourpasswordagain-ph": "Geav et wachtwoord upny up",
        "userlogin-remembermypassword": "Vanselv anmelden",
-       "userlogin-signwithsecure": "Beveiligde verbinding bruken",
+       "userlogin-signwithsecure": "Beveiligde verbinding gebruken",
        "yourdomainname": "Juw domein",
        "password-change-forbidden": "Je kunnen joew wachtwoord niet wiezigen op disse wiki.",
        "externaldberror": "Der gung iets fout bie de externe authentisering, of je maggen je gebrukersprofiel niet bewarken.",
        "logout": "Afmelden",
        "userlogout": "Aofmelden",
        "notloggedin": "Neet an-emelded",
-       "userlogin-noaccount": "Heb jy noch geen brukersname?",
+       "userlogin-noaccount": "Heb jy noch geen gebrukersname?",
        "userlogin-joinproject": "Wörd lid van {{SITENAME}}",
        "createaccount": "Inskryven",
        "userlogin-resetpassword-link": "Wachtwoord vergeaten?",
        "userlogin-createanother": "Een andere gebrukerskonto anmaken",
        "createacct-emailrequired": "Netpostadres",
        "createacct-emailoptional": "Netpostadres (niet verplicht)",
-       "createacct-email-ph": "Geef joew netpostadres op",
+       "createacct-email-ph": "Geav juw netpostadresse up",
        "createacct-another-email-ph": "Vul joew netpostadres in",
        "createaccountmail": "Gebruuk n tiejelik wachtwoord dat joe netzelde is en stuur t naor t op-egeven netpostadres",
        "createacct-realname": "Echte naam (niet verplicht)",
        "createacct-reason-ph": "Waorumme je n aandere gebrukerskonto anmaken",
        "createacct-submit": "Gebrukerskonto anmaken",
        "createacct-another-submit": "Gebrukerskonto anmaken",
-       "createacct-benefit-heading": "{{SITENAME}} wörden emaakt deur meensen zo as jie.",
-       "createacct-benefit-body1": "bewarking{{PLURAL:$1||en}}",
-       "createacct-benefit-body2": "{{PLURAL:$1|zied|ziejen}}",
-       "createacct-benefit-body3": "aktieve {{PLURAL:$1|biedrager|biedragers}}",
+       "createacct-benefit-heading": "{{SITENAME}} is maked döär mensken so as ju.",
+       "createacct-benefit-body1": "{{PLURAL:$1|bewarking|bewarkingen}}",
+       "createacct-benefit-body2": "{{PLURAL:$1|syde|syden}}",
+       "createacct-benefit-body3": "aktive {{PLURAL:$1|bydrager|bydragers}}",
        "badretype": "De wachtwoorden die'j in-etikt hebben bin niet liek alleens.",
        "userexists": "Disse gebrukersnaam is al gebruuk.\nKies n aandere naam.",
        "loginerror": "Anmeldingsfout",
        "resetpass-wrong-oldpass": "t Veurlopige wachtwoord of t wachtwoord da'j noen hebben is ongeldig.\nMisschien he'j t wachtwoord al ewiezigd of n niej veurlopig wachtwoord an-evreugen.",
        "resetpass-temp-password": "Veurlopig wachtwoord:",
        "resetpass-abort-generic": "De wachtwoordwieziging is aofebreuken deur n uutbreiding.",
-       "passwordreset": "Wachtwoord opniej instellen",
+       "passwordreset": "Wachtwoord upny instellen",
        "passwordreset-text-one": "Vul dit formulier in um joew wachtwoord opniej in te stellen.",
        "passwordreset-text-many": "{{PLURAL:$1|Vul een van de gegevensvelden in um per netpost n tiejelik wachtwoord te ontvangen.}}",
        "passwordreset-disabled": "Je kunnen op disse wiki joew wachtwoord niet opniej instellen.",
        "showpreview": "Bewarking nåkyken",
        "showdiff": "Verskil bekyken",
        "blankarticle": "<strong>Waorschuwing:</strong> de zied die'j anmaken willen is leeg.\nA'j noen weer op \"$1\" klikken, dan wördt de zied an-emaakt zonder enige inhoud.",
-       "anoneditwarning": "<strong>Wårsküwing:</strong> jy binnet neet anmelded. Juw IP-adresse sal vöär ydereyne sichtbår weasen as jy wysigingen up disse syde anbrenget. As jy juw eigen <strong>[$1 anmeldet]</strong> of <strong>[$2 inskryvet]</strong> dan kommen juw bewarkingen under juw brukersname te stån, samen mid andere vöärdeylen.",
+       "anoneditwarning": "<strong>Wårsküwing:</strong> jy binnet neet anmelded. Juw IP-adresse sal vöär ydereyne sichtbår weasen as jy wysigingen up disse syde anbrenget. As jy juw eigen <strong>[$1 anmeldet]</strong> of <strong>[$2 inskryvet]</strong> dan kommen juw bewarkingen under juw gebrukersname te stån, samen mid andere vöärdeylen.",
        "anonpreviewwarning": "''Je bin niet an-emeld.''\n''Deur de bewarking op te slaon wörden joew IP-adres op-esleugen in de ziedgeschiedenisse.''",
        "missingsummary": "'''Herinnering:''' je hebben gien samenvatting op-egeven veur de bewarking. A'j noen weer op ''Opslaon'' klikken wörden de bewarking zonder samenvatting op-esleugen.",
        "missingcommenttext": "Skryv een upmarking.",
        "summary-preview": "Samenvatting nåkyken:",
        "subject-preview": "Underwarp nåkyken:",
        "blockedtitle": "Gebruker is eblokkeerd",
-       "blockedtext": "<strong>Juw brukersname of IP-adres is blokkeerd.</strong>\n\nJy binnet blokkeerd döär $1.\nDe upgeaven readen is <em>$2</em>.\n\n* Blokkeerd vanaf: $8\n* Blokkeerd tot: $6\n* Bedoold üm te blokkeren: $7\n\nJy künnet kontakt upneamen mid $1 of een andere [[{{MediaWiki:Grouppage-sysop}}|beheyrder]] üm de blokkering te bepråten.\nJy künnet de funkty \"{{int:emailuser}}\" neet bruken, behalven as jy een geldig e-postadres in juw [[Special:Preferences|instellingen]] upgeaven hebbet en et gebruuk van disse funkty neet blokkeerd is.\nEt IP-adres wat jy nu bruket is $3, en et blokkeringsnummer is #$5.\nVermeld de gegeavens dee hyrboaven stån as jy argens up disse blokkering reageren.",
+       "blockedtext": "<strong>Juw gebrukersname of IP-adres is blokkeerd.</strong>\n\nJy binnet blokkeerd döär $1.\nDe upgeaven readen is <em>$2</em>.\n\n* Blokkeerd vanaf: $8\n* Blokkeerd tot: $6\n* Bedoold üm te blokkeren: $7\n\nJy künnet kontakt upneamen mid $1 of een andere [[{{MediaWiki:Grouppage-sysop}}|beheyrder]] üm de blokkering te bepråten.\nJy künnet de funkty \"{{int:emailuser}}\" neet gebruken, behalven as jy een geldig e-postadres in juw [[Special:Preferences|instellingen]] upgeaven hebbet en et gebruuk van disse funkty neet blokkeerd is.\nEt IP-adres wat jy nu gebruket is $3, en et blokkeringsnummer is #$5.\nVermeld de gegeavens dee hyrboaven stån as jy argens up disse blokkering reageren.",
        "autoblockedtext": "Joew IP-adres is automaties eblokkeerd umdat t gebruukt wördt deur n aandere gebruker, die eblokkeerd wördt deur $1.\nDe reden hierveur was:\n\n:''$2''\n\n* Begint: $8\n* Löp of nao: $6\n* Wee eblokkeerd wördt: $7\n\nJe kunnen kontakt opnemen mit $1 of n van de aandere\n[[{{MediaWiki:Grouppage-sysop}}|beheerders]] um de blokkering te bepraoten.\n\nNB: je kunnen de opsie \"n bericht sturen\" niet gebruken, behalven a'j n geldig netpostadres op-egeven hebben in de [[Special:Preferences|gebrukersveurkeuren]] en je niet eblokkeerd bin.\n\nJoew IP-adres is $3 en joew blokkeernummer is $5.\nGeef disse nummers deur a'j kontakt mit ene opnemen over de blokkering.",
        "blockednoreason": "gien reden op-egeven",
        "whitelistedittext": "Um ziejen te kunnen wiezigen, mu'j $1 ween",
        "accmailtitle": "Wachtwoord is verstuurd.",
        "accmailtext": "Der is n willekeurig wachtwoord veur [[User talk:$1|$1]] verstuurd naor $2. t Kan ewiezigd wörden op de zied ''[[Special:ChangePassword|wachtwoord wiezigen]]'' naoda'j an-emeld bin.",
        "newarticle": "(Niej)",
-       "newarticletext": "Disse zied besteet nog niet.\nIn t veld hieronder ku'j wat schrieven um disse zied an te maken (meer informasie vie'j op de [$1 hulpzied]).\nA'j hier per ongelok terechtekeumen bin gebruuk dan de knoppe '''veurige''' um weerumme te gaon.",
-       "anontalkpagetext": "----\n<em>Disse oaverlegsyde höyrt by een anonyme bruker dee noch geen brukersname hevt, of et neet bruukt.</em>\nDårümme bruken wy et IP-adresse ter identifikaty. Een IP-adresse kan döär meyrere lüde bruked wörden. As jy een anonyme bruker binnet, en et gevööl hebbet dat jy berichten kryget dee neet vöär ju bedoold binnet [[Special:CreateAccount|skryv ju eigen dan in]] of [[Special:UserLogin|meld ju eigen an]] üm verwarring mid andere anonyme brukers in de tokumst te vöärkommen.''",
+       "newarticletext": "Disse syde besteyt noch neet.\nIn et veld hyrunder kün jy wat skryven üm disse syde an te maken (meyr informaty vind jy up de [$1 hülpsyde]).\nAs jy hyr per ungelük terechtekommen binnet bruuk dan de knoppe '''vöärige''' üm terügge te gån.",
+       "anontalkpagetext": "----\n<em>Disse oaverlegsyde höyrt by een anonyme gebruker dee noch geen gebrukersname hevt, of et neet bruukt.</em>\nDårümme gebruken wy et IP-adresse ter identifikaty. Een IP-adresse kan döär meyrere lüde gebruked wörden. As jy een anonyme gebruker binnet, en et gevööl hebbet dat jy berichten kryget dee neet vöär ju bedoold binnet [[Special:CreateAccount|skryv ju eigen dan in]] of [[Special:UserLogin|meld ju eigen an]] üm verwarring mid andere anonyme gebrukers in de tokumst te vöärkommen.''",
        "noarticletext": "Der steyt nun geen tekst up disse syde.\nJy künnet [[Special:Search/{{PAGENAME}}|de titel upsöken]] in andere syden,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} söken in de logboken],\nof [{{fullurl:{{FULLPAGENAME}}|action=edit}} disse syde anmaken]</span>.",
        "noarticletext-nopermission": "Up disse syde steyt geen tekst.\nJy künnet [[Special:Search/{{PAGENAME}}|söken nå disse term]] in andere syden of\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de logboken döärsöken]</span>, mär jy hebbet geen rechten üm disse syde an te maken.",
        "missing-revision": "De versie #$1 van de zied \"{{FULLPAGENAME}} besteet niet.\n\nDit kömp meestentieds deur t volgen van n verouwerde verwiezing naor n zied die vortedaon is.\nWaorschienlik ku'j der meer gegevens over vienen in t [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} vortdologboek].",
        "edit_form_incomplete": "'''Partie delen van t bewarkingsformulier hebben de server niet bereikt. Kiek eers nao of de bewarkingen kloppen en probeer t opniej.'''",
        "editing": "Bewarken: $1",
        "creating": "Anmaken: $1",
-       "editingsection": "Bewarken: $1 (deelzied)",
+       "editingsection": "Bewarken: $1 (deylsyde)",
        "editingcomment": "Bewarken: $1 (niej onderwarp)",
        "editconflict": "Tegelieke bewörken: $1",
        "explainconflict": "'''NB:''' n aander hef disse zied ewiezigd naoda'j an disse bewarking begunnen bin.\nt Bovenste bewarkingsveld löt de zied zien zo as t noen is.\nDaoronder (bie \"Wiezigingen\") staon de verschillen tussen joew versie en de op-esleugen zied.\nHelemaole onderan (bie \"Joew tekste\") steet nog n bewarkingsveld mit joew versie.\nJe zullen je eigen wiezigingen in de nieje tekste in mutten passen.\n'''Allinnig''' de tekste in t bovenste veld wörden beweerd a'j noen kiezen veur \"$1\".",
        "semiprotectedpagewarning": "'''Let op:''' disse zied is beveiligd en ku'j allinnig bewarken a'j n eregistreerden gebruker bin.\nDe leste logboekregel steet hieronder:",
        "cascadeprotectedwarning": "'''Waorschuwing:''' disse zied is beveiligd, zodat allinnig beheerders disse zied bewarken kunnen, dit wörden edaon umdat disse zied veurkömp in de volgende {{PLURAL:$1|kaskade-beveiligden zied|kaskade-beveiligden ziejen}}:",
        "titleprotectedwarning": "'''Waorschuwing: disse zied is beveiligd. Je hebben [[Special:ListGroupRights|bepaolde rechten]] neudig um t an te kunnen maken.'''\nDe leste logboekregel steet hieronder:",
-       "templatesused": "{{PLURAL:$1|Mal|Mallen}} die op disse zied gebruukt wörden:",
+       "templatesused": "{{PLURAL:$1|Mal|Mallen}} dee up disse syde gebruked wörden:",
        "templatesusedpreview": "{{PLURAL:$1|Mal|Mallen}} die in disse bewarking gebruukt wörden:",
        "templatesusedsection": "{{PLURAL:$1|Mal|Mallen}} die in dit subkopjen gebruukt wörden:",
        "template-protected": "(beveiligd)",
        "template-semiprotected": "(halv-beveiligd)",
-       "hiddencategories": "Disse zied völt in de volgende verbörgen {{PLURAL:$1|kategorie|kategorieën}}:",
+       "hiddencategories": "Disse syde valt in de volgende verbörgen {{PLURAL:$1|kategory|kategoryen}}:",
        "edittools": "<!-- Disse tekste steet onder de bewarkings- en bestaandinlaodformulieren. -->",
        "nocreatetext": "Disse webstee hef de meugelikheid um nieje ziejen an te maken beteund. Je kunnen ziejen die al bestaon wiezigen of je kunnen je [[Special:UserLogin|anmelden of n gebrukerszied anmaken]].",
        "nocreate-loggedin": "Je hebben gien toestemming um nieje ziejen an te maken.",
        "sectioneditnotsupported-text": "Je kunnen op disse zied gien seksies bewarken.",
        "permissionserrors": "Gien toestemming",
        "permissionserrorstext": "Je maggen of kunnen dit niet doon. De {{PLURAL:$1|reden|redens}} daorveur {{PLURAL:$1|is|bin}}:",
-       "permissionserrorstext-withaction": "Je hebben gien recht um $2, mit de volgende {{PLURAL:$1|reden|redens}}:",
+       "permissionserrorstext-withaction": "Jy hebbet geen rechten üm $2, mid de volgende {{PLURAL:$1|readen|readenen}}:",
        "recreate-moveddeleted-warn": "'''Waorschuwing: je maken n zied an die eerder al vortedaon is.'''\n\nBedenk eerst of t neudig is um disse zied veerder te bewarken.\nVeur de dudelikheid steet hieronder  t vortdologboek en t herneumlogboek veur disse zied:",
-       "moveddeleted-notice": "Disse zied is vortedaon.\nHieronder steet de informasie uut t vortdologboek, t beveiligingslogboek, en t herneumlogboek.",
+       "moveddeleted-notice": "Disse syde is vorddån.\nHyrunder steyt de informaty uut et vortsmytlogbook, et beveiligingslogbook, en et hernöömlogbook.",
        "log-fulllog": "t Hele logboek bekieken",
        "edit-hook-aborted": "De bewarking is aofebreuken deur n hook.\nDer is gien reden op-egeven.",
        "edit-gone-missing": "De zied kon niet bie-ewörken wörden.\nt Lik derop as of t vortedaon is.",
        "undo-summary": "Versie $1 van [[Special:Contributions/$2|$2]] ([[User talk:$2|overleg]]) weerummedreid",
        "undo-summary-username-hidden": "Versie $1 deur n verbörgen gebruker weerummedreid",
        "cantcreateaccount-text": "t Anmaken van gebrukers van dit IP-adres (<b>$1</b>) is eblokkeerd deur [[User:$3|$3]].\n\nDe deur $3 op-egeven reden is ''$2''",
-       "viewpagelogs": "Bekiek logboeken veur disse zied",
+       "viewpagelogs": "Bekyk logboken vöär disse syde",
        "nohistory": "Der bin gien eerdere versies van disse zied.",
        "currentrev": "Leste versie",
-       "currentrev-asof": "Leste versie van $1",
+       "currentrev-asof": "Lätste versy van $1",
        "revisionasof": "Versy up $1",
-       "revision-info": "Versie op $1 van {{GENDER:$6|$2}}$7",
+       "revision-info": "Versy up $1 van {{GENDER:$6|$2}}$7",
        "previousrevision": "&larr; eyrere versy",
-       "nextrevision": "niejere versie &rarr;",
-       "currentrevisionlink": "versie zo as t noen is",
+       "nextrevision": "nyere versy &rarr;",
+       "currentrevisionlink": "Aktuele versy",
        "cur": "aktueel",
        "next": "Volgende",
        "last": "lätste",
        "mergelog": "Samenvoegingslogboek",
        "revertmerge": "Samenvoeging weerummedreien",
        "mergelogpagetext": "Hieronder zie'j n lieste van de leste samenvoegingen van n ziedgeschiedenisse naor n aandere.",
-       "history-title": "Versiegeschiedenisse van \"$1\"",
-       "difference-title": "Verschil tussen versies van \"$1\"",
+       "history-title": "Versygeskydenisse van \"$1\"",
+       "difference-title": "Verskil tüsken versys van \"$1\"",
        "difference-title-multipage": "Verschil tussen ziejen \"$1\" en \"$2\"",
        "difference-multipage": "(Verschil tussen ziejen)",
        "lineno": "Regel $1:",
        "showhideselectedversions": "Ekeuzen versies bekieken/verbargen",
        "editundo": "weaderümmedraien",
        "diff-empty": "(Gien verschil)",
-       "diff-multi-sameuser": "({{PLURAL:$1|n Tussenliggende versie|$1 tussenliggende versies}} deur de zelfde gebruker is verbörgen)",
-       "diff-multi-otherusers": "({{PLURAL:$1|Eyn tüskenliggende versy|$1 tüskenliggende versys}} döär {{PLURAL:$2|eyn andere bruker|$2 brukers}} neet weadergeaven)",
+       "diff-multi-sameuser": "({{PLURAL:$1|Eyn tüskenliggende versy|$1 tüskenliggende versys}} döär deselvde gebruker is verbörgen)",
+       "diff-multi-otherusers": "({{PLURAL:$1|Eyn tüskenliggende versy|$1 tüskenliggende versys}} döär {{PLURAL:$2|eyn andere gebruker|$2 gebrukers}} neet weadergeaven)",
        "diff-multi-manyusers": "($1 tussenliggende {{PLURAL:$1|versie|versies}} deur meer as $2 {{PLURAL:$2|gebruker|gebrukers}} niet weeregeven)",
        "difference-missing-revision": "{{PLURAL:$2|Eén versie|$2 versies}} van disse verschillen ($1) {{PLURAL:$2|is|bin}} niet evunnen.\n\nDit kömp meestentieds deur t volgen van n verouwerde verwiezing naor n zied die vortedaon is.\nWaorschienlik ku'j der meer gegevens over vienen in t [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} vortdologboek].",
        "searchresults": "Söökresultaten",
        "search-redirect": "(döärverwysing vanaf $1)",
        "search-section": "(underwarp $1)",
        "search-file-match": "(kümt oavereyne mid de inhold van et bestand)",
-       "search-suggest": "Bedoelden je: $1",
+       "search-suggest": "Bedoolde jy: $1",
        "search-interwiki-caption": "Resultaten van süsterprojekten",
        "search-interwiki-default": "Resultaoten van $1:",
        "search-interwiki-more": "(meer)",
        "gender-unknown": "Geslachtsneutrale anspreaksform",
        "gender-male": "Hee bewarkt syden",
        "gender-female": "See bewarkt syden",
-       "prefs-help-gender": "Disse instelling is optioneel.\nDe programmatuur bruukt disse waerde um ju up de jüüste manere an te spreaken en vöär andere brukers üm juw geslacht an te geaven.\nDisse informaty is sichtbår vöär andere brukers.",
+       "prefs-help-gender": "Disse instelling is optioneel.\nDe programmatuur gebruukt disse waerde um ju up de jüüste manere an te spreaken en vöär andere gebrukers üm juw geslacht an te geaven.\nDisse informaty is sichtbår vöär andere gebrukers.",
        "email": "Privéberichten",
        "prefs-help-realname": "Echte naam is keuzevrie.\nA'j disse opsie invullen zu'w joew echte naam gebruken um erkenning te geven veur joew wark.",
        "prefs-help-email": "n Netpostadres is niet verplicht, mer zo ku'w wel joew wachtwoord toesturen veur a'j t vergeten bin.",
        "right-siteadmin": "De databanke blokkeren en weer vriegeven",
        "right-override-export-depth": "Ziejen exporteren, oek de ziejen waor naor verwezen wördt, tot n diepte van 5",
        "right-sendemail": "Bericht versturen naor aandere gebrukers",
-       "newuserlogpage": "Logbook van nye brukers",
+       "newuserlogpage": "Logbook van nye gebrukers",
        "newuserlogpagetext": "Hieronder staon de niej in-eschreven gebrukers",
        "rightslog": "Gebrukersrechtenlogboek",
        "rightslogtext": "Dit is n logboek mit veraanderingen van gebrukersrechten",
        "rcfilters-filter-user-experience-level-newcomer-label": "Anwas",
        "rcfilters-filter-user-experience-level-newcomer-description": "An-emelden bewarkers dee minder as 10 bewarkingen edån hebben of 4 dagen aktiv ewesd hebben.",
        "rcfilters-filter-user-experience-level-learner-label": "Leyrlingen",
-       "rcfilters-filter-user-experience-level-learner-description": "Anmeldede bewarkers mid meyr ervaring as \"anwas\", mär minder as \"ervaren brukers\".",
+       "rcfilters-filter-user-experience-level-learner-description": "Anmeldede bewarkers mid meyr ervaring as \"anwas\", mär minder as \"ervaren gebrukers\".",
        "rcfilters-filter-user-experience-level-experienced-label": "Ervåren gebrukers",
        "rcfilters-filter-user-experience-level-experienced-description": "An-emelde bewarkers mid meyr as 500 bewarkingen en 30 dagen van aktiviteit.",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-previousrevision-description": "Alle wysigingen dee neet de \"lätste versy\" binnen.",
        "rcfilters-view-tags": "Emarkeerde wysigingen",
        "rcfilters-view-namespaces-tooltip": "Filter resultaten up naamruumde",
-       "rcfilters-view-tags-tooltip": "Filter resultaten döär bewarkingsetiketten te bruken",
+       "rcfilters-view-tags-tooltip": "Filter resultaten döär bewarkingsetiketten te gebruken",
        "rcfilters-liveupdates-button": "Rechtstreakse aktualisering",
        "rcfilters-liveupdates-button-title-off": "Nye wysigingen voorddalik låten seen",
        "rcnotefrom": "Wysigingen sinds <strong>$3, $4</strong> (maximaal <strong>$1</strong> {{PLURAL:$1|wysiging|wysigingen}}).",
        "rclistfrom": "Bekyk wysigingen vanaf $3 $2",
        "rcshowhideminor": "$1 kleine wysigingen",
-       "rcshowhideminor-show": "Bekiek",
+       "rcshowhideminor-show": "bekyken",
        "rcshowhideminor-hide": "verbargen",
        "rcshowhidebots": "$1 botbrukers",
        "rcshowhidebots-show": "bekyken",
-       "rcshowhidebots-hide": "Verbarg",
-       "rcshowhideliu": "$1 registreerde brukers",
+       "rcshowhidebots-hide": "verbargen",
+       "rcshowhideliu": "$1 registreerde gebrukers",
        "rcshowhideliu-show": "Bekiek",
        "rcshowhideliu-hide": "verbargen",
-       "rcshowhideanons": "$1 anonyme brukers",
-       "rcshowhideanons-show": "Bekiek",
+       "rcshowhideanons": "$1 anonyme gebrukers",
+       "rcshowhideanons-show": "bekyken",
        "rcshowhideanons-hide": "verbargen",
        "rcshowhidepatr": "$1 nao-ekeken bewarkingen",
        "rcshowhidepatr-show": "Bekiek",
        "rcshowhidepatr-hide": "Verbarg",
        "rcshowhidemine": "$1 myn bewarkingen",
-       "rcshowhidemine-show": "Bekiek",
+       "rcshowhidemine-show": "bekyken",
        "rcshowhidemine-hide": "verbargen",
        "rcshowhidecategorization": "$1 kategorisering van ziejen",
        "rcshowhidecategorization-show": "Bekiek",
        "upload_source_file": " (n bestaand op de hardeschieve)",
        "listfiles-summary": "Op disse spesiale zied ku'j alle bestaanden bekieken die lestens op-estuurd bin.",
        "listfiles_search_for": "Zeuk naor bestaand:",
-       "imgfile": "bestaand",
+       "imgfile": "bestand",
        "listfiles": "Bestaandslieste",
        "listfiles_thumb": "Miniatuuraofbeelding",
        "listfiles_date": "Daotum",
        "filehist-thumb": "Miniatuurafbealding",
        "filehist-thumbtext": "Miniatuurafbealding vöär versy van $1",
        "filehist-nothumb": "Gien miniatuuraofbeelding",
-       "filehist-user": "Bruker",
+       "filehist-user": "Gebruker",
        "filehist-dimensions": "Groutde",
        "filehist-filesize": "Bestaandsgrootte",
        "filehist-comment": "Kommentaar",
        "imagelinks": "Bestandsbruuk",
-       "linkstoimage": "Dit bestand wördt up de volgende {{PLURAL:$1|syde|$1 syden}} bruked:",
-       "linkstoimage-more": "Meyr as $1 {{PLURAL:$1|syde bruukt|syden bruken}} dit bestand.\nDe volgende lyste givt allinnig de {{PLURAL:$1|eyrste syde|eyrste $1 syden}} weader dee dit bestand bruukt.\nDe [[Special:WhatLinksHere/$2|heyle lyste]] is ouk beskikbår.",
-       "nolinkstoimage": "Geen enkele syde bruukt dit bestand.",
+       "linkstoimage": "Dit bestand wördt up de volgende {{PLURAL:$1|syde|$1 syden}} gebruked:",
+       "linkstoimage-more": "Meyr as $1 {{PLURAL:$1|syde gebruukt|syden gebruken}} dit bestand.\nDe volgende lyste givt allinnig de {{PLURAL:$1|eyrste syde|eyrste $1 syden}} weader dee dit bestand gebruukt.\nDe [[Special:WhatLinksHere/$2|heyle lyste]] is ouk beskikbår.",
+       "nolinkstoimage": "Geen enkele syde gebruukt dit bestand.",
        "morelinkstoimage": "[[Special:WhatLinksHere/$1|Meer verwiezingen]] naor dit bestaand bekieken.",
        "linkstoimage-redirect": "$1 (bestaandsdeurverwiezing) $2",
        "duplicatesoffile": "{{PLURAL:$1|t Volgende bestaand is|De volgende $1 bestaanden bin}} gelieke an dit bestaand ([[Special:FileDuplicateSearch/$2|meer informasie]]):",
        "sharedupload": "Dit is n edeeld bestaand op $1 en ku'j oek gebruken veur aandere projekten.",
        "sharedupload-desc-there": "Dit is n edeeld bestaand op $1 en ku'j oek gebruken veur aandere projekten. Bekiek de [$2 beschrieving van t bestaand] veur meer informasie.",
-       "sharedupload-desc-here": "Dit bestand kümt van $1 en kan ouk in andere projekten bruked weasen. De [$2 syde mid de beskryving van et bestand] steyt hyrunder.",
+       "sharedupload-desc-here": "Dit bestand kümt van $1 en kan ouk in andere projekten gebruked weasen. De [$2 syde mid de beskryving van et bestand] steyt hyrunder.",
        "sharedupload-desc-edit": "Dit besatand kömp van $1 en kan oek in aandere projekten gebruukt wörden.\nJe kunnen de [$2 zied mit de bestaandsbeschrieving] daor bewarken.",
        "sharedupload-desc-create": "Dit besatand kömp van $1 en kan oek in aandere projekten gebruukt wörden.\nJe kunnen de [$2 zied mit de bestaandsbeschrieving] daor bewarken.",
        "filepage-nofile": "Der besteet gien bestaand mit disse naam.",
        "ncategories": "$1 {{PLURAL:$1|kategorie|kategorieën}}",
        "ninterwikis": "$1 {{PLURAL:$1|interwikiverwiezing|interwikiverwiezingen}}",
        "nlinks": "$1 {{PLURAL:$1|verwiezing|verwiezingen}}",
-       "nmembers": "$1 {{PLURAL:$1|onderwarp|onderwarpen}}",
+       "nmembers": "$1 {{PLURAL:$1|lid|leaden}}",
        "nrevisions": "$1 {{PLURAL:$1|versie|versies}}",
        "nimagelinks": "Wörden op {{PLURAL:$1|één zied|$1 ziejen}} gebruukt",
        "ntransclusions": "wörden op {{PLURAL:$1|één zied|$1 ziejen}} gebruukt",
        "nopagetitle": "Doelzied besteet niet",
        "nopagetext": "De zied die'j herneumen willen besteet niet.",
        "pager-newer-n": "{{PLURAL:$1|1 niejere|$1 niejere}}",
-       "pager-older-n": "{{PLURAL:$1|1 ouwere|$1 ouwere}}",
+       "pager-older-n": "{{PLURAL:$1|1 oldere|$1 oldere}}",
        "suppress": "Toezicht",
        "querypage-disabled": "Disse spesiale zied is uutezet um prestasieredens.",
        "apisandbox": "API-zaandkule",
        "booksources": "Bookinformaty",
-       "booksources-search-legend": "Zeuk informasie over n boek",
-       "booksources-search": "Zeuken",
+       "booksources-search-legend": "Söök bronnen van een book",
+       "booksources-search": "ken",
        "booksources-text": "Hieronder steet n lieste mit verwiezingen naor aandere websteeën die nieje of wat ouwere boeken verkopen, en daor hebben ze warschienlik meer informasie over t boek da'j zeuken:",
        "booksources-invalid-isbn": "De op-egeven ISBN klop niet; kiek effen nao o'j gien fout emaakt hebben bie de invoer.",
        "magiclink-tracking-isbn": "Ziejen die magiese ISBN-verwiezingen gebruken",
        "cachedspecial-viewing-cached-ttl": "Je bekieken noen n versie uut t tussengeheugen van disse zied, die hooguut $1 oud is.",
        "cachedspecial-viewing-cached-ts": "Je bekieken noen n versie uut t tussengeheugen van disse zied, t kan ween dat t niet helemaole bie de tied is.",
        "cachedspecial-refresh-now": "Leste bekieken.",
-       "categories": "Kategorieën",
+       "categories": "Kategoryen",
        "categoriespagetext": "De de volgende {{PLURAL:$1|kategorie steet|kategorieën staon}} ziejen of mediabestaanden.\n[[Special:UnusedCategories|ongebruukten kategorieën]] zie'j hier niet.\nZie oek [[Special:WantedCategories|gewunste kategorieën]].",
        "categoriesfrom": "Laot kategorieën zien vanaof:",
        "deletedcontributions": "Vortedaone gebrukersbiedragen",
        "actioncomplete": "Uutevoerd",
        "actionfailed": "De haandeling is mislokt.",
        "deletedtext": "t Artikel \"$1\" is vortedaon. Zie de \"$2\" veur n lieste van ziejen die as lest vortedaon bin.",
-       "dellogpage": "Vortdologboek",
+       "dellogpage": "Vordsmytlogbook",
        "dellogpagetext": "Hieronder steet n lieste van ziejen en bestaanden die as lest vortedaon bin.",
        "deletionlog": "Vortdologboek",
        "reverted": "Eerdere versie hersteld",
        "deleting-backlinks-warning": "<strong>Waorschuwing:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|aandere ziejen]] gebruken of verwiezen naor de zied die'j vortdoon willen.",
        "rollback": "Wiezigingen herstellen",
        "rollbacklink": "weaderümmedraien",
-       "rollbacklinkcount": "{{PLURAL:$1|één bewarking|$1 bewarkingen}} weerummedreien",
+       "rollbacklinkcount": "{{PLURAL:$1|eyn bewarking|$1 bewarkingen}} weaderümmedraien",
        "rollbacklinkcount-morethan": "Meer as {{PLURAL:$1|één bewarking|$1 bewarkingen}} weerummedreien",
        "rollbackfailed": "Wieziging herstellen is mislokt",
        "cantrollback": "De wiezigingen konnen niet hersteld wörden; der is mer 1 auteur.",
        "rollback-success": "Wiezigingen van $1; weerummedreid naor de leste versie van $2.",
        "sessionfailure-title": "Sessiefout",
        "sessionfailure": "Der is n probleem mit joew anmeldsessie. De aksie is stop-ezet uut veurzörg tegen n beveiligingsrisico (dat besteet uut t meugelike \"kraken\" van disse sessie). Gao weerumme naor de veurige zied, laoj disse zied opniej en probeer t nog es.",
-       "protectlogpage": "Beveiligingslogboek",
+       "protectlogpage": "Beveiligingslogbook",
        "protectlogtext": "Hieronder staon de leste wiezigingen veur t blokkeren en vriegeven van artikels en ziejen.\nZie de [[Special:ProtectedPages|lieste mit ziejen die beveiligd bin]] veur t hele overzicht.",
        "protectedarticle": "[[$1]] is beveiligd",
        "modifiedarticleprotection": "beveiligingsnivo van \"[[$1]]\"  ewiezigd",
        "contribsub2": "Veur {{GENDER:$3|$1}} ($2)",
        "nocontribs": "Gien wiezigingen evunnen die an de estelde criteria voldoon.",
        "uctop": "leste wieziging",
-       "month": "Maond:",
-       "year": "Jaor:",
+       "month": "Månd:",
+       "year": "Vanaf jår (en eyrer):",
        "sp-contributions-blocklog": "blokkeerlogboek",
        "sp-contributions-deleted": "vortedaone gebrukersbiedragen",
        "sp-contributions-uploads": "nieje bestaanden",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|versie|versies}} van $2",
        "javascripttest": "JavaScript testen",
        "javascripttest-qunit-intro": "Zie de [$1 testdokumentasie] op mediawiki.org.",
-       "tooltip-pt-userpage": "{{GENDER:|Juw}} brukerssyde",
+       "tooltip-pt-userpage": "{{GENDER:|Juw}} gebrukerssyde",
        "tooltip-pt-anonuserpage": "Gebroekersbladziede vuur t IP-adres da'j broekt",
        "tooltip-pt-mytalk": "{{GENDER:|Juw}} oaverlegsyde",
        "tooltip-pt-anontalk": "Oaverlegbladziede van n naamlozen gebroeker van dit IP-adres",
        "tooltip-pt-preferences": "{{GENDER:|Juw}} instellingen",
        "tooltip-pt-watchlist": "Lyste van syden dee up myn volglyste stån",
        "tooltip-pt-mycontris": "Oaversicht van {{GENDER:|juw}} bydragen",
-       "tooltip-pt-login": "Jy wördet van harte uutnöygd üm ju an te melden as bruker, mär et is neet verplicht",
+       "tooltip-pt-login": "Jy wördet van harte nöygd üm ju an te melden as gebruker, mär et is neet verplichted",
        "tooltip-pt-logout": "Afmelden",
        "tooltip-pt-createaccount": "Skryv juw eigen vöäral in en meld juw eigen an. Dit is lykewels neet verplicht.",
        "tooltip-ca-talk": "Låt een oaverlegtekst oaver disse syde seen",
        "tooltip-ca-unprotect": "De beveiliging vuur disse ziede wiezigen",
        "tooltip-ca-delete": "Smiet disse ziede vort",
        "tooltip-ca-undelete": "Haal n inhoald van disse ziede oet n emmer",
-       "tooltip-ca-move": "Gef disse ziede nen aanderen titel",
+       "tooltip-ca-move": "Disse syde hernömen",
        "tooltip-ca-watch": "Voog disse syde to an juw volglyste",
        "tooltip-ca-unwatch": "Smiet disse ziede van oewe voalglieste",
        "tooltip-search": "{{SITENAME}} döärsöken",
        "tooltip-t-recentchangeslinked": "Pas verrichte veranderingen dee nå disse syde verwyset",
        "tooltip-feed-rss": "RSS-voer vuur disse ziede",
        "tooltip-feed-atom": "Atom-voer vuur disse ziede",
-       "tooltip-t-contributions": "Een lyste mid bydragen van {{GENDER:$1|disse bruker}}",
+       "tooltip-t-contributions": "Een lyste mid bydragen van {{GENDER:$1|disse gebruker}}",
        "tooltip-t-emailuser": "Stüür disse {{GENDER:$1|gebruker}} een netpostbericht",
        "tooltip-t-info": "Meer informasie over disse zied",
        "tooltip-t-upload": "Laad afbealdingen en/of gelüüdsmateriaal",
        "tooltip-t-print": "De afdrükbåre versy van disse syde",
        "tooltip-t-permalink": "Permanente verwysing nå disse versy van de syde",
        "tooltip-ca-nstab-main": "Låt een tekst van et artikel seen",
-       "tooltip-ca-nstab-user": "Brukerssyde bekyken",
+       "tooltip-ca-nstab-user": "Gebrukerssyde bekyken",
        "tooltip-ca-nstab-media": "Loat n mediatekst zeen",
        "tooltip-ca-nstab-special": "Dit is een bysündere syde dee jy neet veranderen künt",
-       "tooltip-ca-nstab-project": "Loat de projektbladziede zeen",
+       "tooltip-ca-nstab-project": "Projektsyde bekyken",
        "tooltip-ca-nstab-image": "Låt de bestandssyde seen",
        "tooltip-ca-nstab-mediawiki": "Loat de systeemtekstbladziede zeen",
        "tooltip-ca-nstab-template": "Mal bekyken",
        "tooltip-watchlistedit-raw-submit": "Volglieste biewarken",
        "tooltip-recreate": "Disse ziede opniej anmaken, ondanks t feit dat t vortdoan is.",
        "tooltip-upload": "Bestaanden opsturen",
-       "tooltip-rollback": "\"Weaderümmedraien\" drait mid eyn klik de bewarking(en) van de lätste bruker up disse syde terügge.",
+       "tooltip-rollback": "\"Weaderümmedraien\" drait mid eyn klik de bewarking(en) van de lätste gebruker up disse syde terügge.",
        "tooltip-undo": "As jy up \"weaderümmedraien\" klikket geyt et bewarkingsveld lös en kün jy een vöärige versy weaderümmesetten. Jy künnet in de bewarkingssamenvatting een readen upgeaven.",
        "tooltip-preferences-save": "Vuurkeuren opsloan",
        "tooltip-summary": "Voor een korte samenvatting in",
        "filedelete-old-unregistered": "De an-egeven bestaandsversie \"$1\" steet niet in de databanke.",
        "filedelete-current-unregistered": "t An-egeven bestaand \"$1\" steet niet in de databanke.",
        "filedelete-archive-read-only": "De webserver kan niet in de archiefmap \"$1\" schrieven.",
-       "previousdiff": "← veurige wieziging",
-       "nextdiff": "volgende wieziging →",
+       "previousdiff": "← vöärige wysiging",
+       "nextdiff": "volgende wysiging →",
        "mediawarning": "'''Waorschuwing:''' in dit bestaand zit misschien kodering die slicht is veur t systeem.",
        "imagemaxsize": "Maximale aofmetingen van aofbeeldingen:<br />\n''(veur op de beschrievingszied)''",
        "thumbsize": "Grootte van de miniatuuraofbeelding:",
        "version-entrypoints-header-entrypoint": "Ingang",
        "version-entrypoints-header-url": "Webadres",
        "redirect": "Deurverwiezen op bestaandsnaam, gebrukers-, zied-, versie- of logboekregelnummer",
-       "redirect-summary": "Disse speciale syde verwist döär nå een bestand (as de bestandsname upgeaven wördt), een syde (as een syd- of versynummer upgeaven wördt), een brukerssyde (as et brukersnummer upgeaven wördt) of een logbookinskryving (as een logbooknummer upgeaven wördt). Gebruuk: [[{{#Special:Redirect}}/file/Vöärbeald.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] of [[{{#Special:Redirect}}/logid/186]].",
+       "redirect-summary": "Disse speciale syde verwist döär nå een bestand (as de bestandsname upgeaven wördt), een syde (as een syd- of versynummer upgeaven wördt), een gebrukerssyde (as et gebrukersnummer upgeaven wördt) of een logbookinskryving (as een logbooknummer upgeaven wördt). Gebruuk: [[{{#Special:Redirect}}/file/Vöärbeald.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] of [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Zeuk",
        "redirect-lookup": "Opzeuken:",
        "redirect-value": "Weerde:",
        "logentry-rights-rights": "$1 {{GENDER:$2|hef}} groepslidmaotschap veur $3 ewiezigd van $4 naor $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|hef}} t groepslidmaotschap ewiezigd veur $3",
        "logentry-rights-autopromote": "$1 {{GENDER:$2|is}} automaties bevorderd van $4 tot $5",
-       "logentry-upload-upload": "$1 hef $3 {{GENDER:$2|op-estuurd}}",
+       "logentry-upload-upload": "$1 hevt $3 {{GENDER:$2|upladen}}",
        "logentry-upload-overwrite": "$1 {{GENDER:$2|hef}} n nieje versie van $3 op-elaojen",
        "rightsnone": "(gien)",
        "feedback-adding": "Joew kommentaar wörden op de zied ezet...",
index 5fb8323..c67bab3 100644 (file)
        "mycustomjsredirectprotected": "U hebt geen rechten om deze JavaScriptpagina te bewerken omdat het een doorverwijzing is en deze niet verwijst naar een pagina in uw gebruikersruimte.",
        "easydeflate-invaliddeflate": "De opgegeven inhoud is onjuist gecomprimeerd",
        "unprotected-js": "Vanwege veiligheidsredenen kan er geen JavaScript geladen worden vanaf onbeveiligde pagina's. Gelieve alleen JavaScript pagina's aan te maken in de MediaWiki: naamruimte of als een subpagina van een gebruikerspagina.",
-       "userlogout-continue": "Wilt u zich afmelden?"
+       "userlogout-continue": "Wilt u zich afmelden?",
+       "rest-wrong-method": "De requestmethode ($1) is {{PLURAL:$3|niet de toegestane methode voor dit pad|een van de toegestane methoden voor dit pad}} ($2)"
 }
index d3e4af9..83a72df 100644 (file)
@@ -64,6 +64,7 @@
        "tog-watchlisthideminor": "చిన్న మార్పులను నా వీక్షణా జాబితాలో చూపించొద్దు",
        "tog-watchlisthideliu": "లాగిన్ ఐన వాడుకరులు చేసే మార్పులను వీక్షణా జాబితాలో చూపించకు",
        "tog-watchlistreloadautomatically": "ఫిల్టరు మారినప్పుడెల్లా వీక్షణ జాబితాను తిరిగి లోడు చెయ్యి (JavaScript అవసరం)",
+       "tog-watchlistunwatchlinks": "మార్పులు జరిగిన వీక్షణ పేజీలకు నేరుగా వీక్షించు/వద్దు సూచికలను ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) చేర్చు (టాగుల్ చెయాలంటే JavaScript ఆవశ్యకం)",
        "tog-watchlisthideanons": "అజ్ఞాత వాడుకరుల మార్పులను వీక్షణా జాబితాలో చూపించకు",
        "tog-watchlisthidepatrolled": "నిఘా ఉన్న మార్పులను వీక్షణజాబితా నుంచి దాచిపెట్టు",
        "tog-watchlisthidecategorization": "పేజీ వర్గీకరణను దాచు",
@@ -74,6 +75,7 @@
        "tog-useeditwarning": "ఏదైనా పేజీని నేను వదిలివెళ్తున్నప్పుడు దానిలో భద్రపరచని మార్పులు ఉంటే నన్ను హెచ్చరించు",
        "tog-prefershttps": "లాగిన్ అయి ఉన్నప్పుడెల్లా భద్ర కనెక్షనునే వాడు",
        "tog-showrollbackconfirmation": "రోల్‌బ్యాక్ లింకును నొక్కినపుడు నిర్ధారించుకునే సందేశాన్ని చూపించు",
+       "tog-requireemail": "సంకేతపదం మార్చుకోడానికి ఈమెయిలు ఆవశ్యకం",
        "underline-always": "ఎల్లప్పుడూ",
        "underline-never": "ఎప్పటికీ వద్దు",
        "underline-default": "అలంకారపు లేదా విహారిణి అప్రమేయం",
        "createaccountmail": "ఏదో ఒక తాత్కాలిక సంకేతపదాన్ని వాడి దాన్ని పేర్కొన్న ఈమెయిలు చిరునామాకు పంపించు",
        "createaccountmail-help": "సంకేతపదం తెలుసుకోనవసరం లేకుండా వేరొకరి కోసం ఖాతా సృష్టించేందుకు వాడవచ్చు.",
        "createacct-realname": "అసలు పేరు (ఐచ్చికం)",
-       "createacct-reason": "కారణం",
+       "createacct-reason": "కారణం (అందరికీ కనిపిస్తుంది)",
        "createacct-reason-ph": "మీరు మరో ఖాతాను ఎందుకు సృష్టించుకుంటున్నారు",
        "createacct-reason-help": "సృష్టించిన ఖాతాల చిట్టాలో చూపించే సందేశం",
        "createacct-submit": "మీ ఖాతాను సృష్టించుకోండి",
        "botpasswords": "బాట్ సంకేతపదాలు",
        "botpasswords-disabled": "బాట్ సంకేతపదాలను అచేతనం చేసాం.",
        "botpasswords-no-central-id": "బాఅత్ సంకేతపదాలను వాడాలంటే, మీరు ఒక కేంద్రీకృత ఖాతాలోకి లాగినవ్వాలి.",
+       "botpasswords-existing": "ప్రస్తుతం ఉన్న బాట్ సంకేతపదాలు",
        "botpasswords-createnew": "ఓ కొత్త బాట్ సంకేతపదాన్ని సృష్టించండి",
        "botpasswords-editexisting": "ఉనికిలో ఉన్న బాట్ సంకేతపదాన్ని మార్చండి",
+       "botpasswords-label-needsreset": "(సంకేతపదాన్ని మార్చాల్సిన అవసరం ఉంది)",
        "botpasswords-label-appid": "బాట్ పేరు:",
        "botpasswords-label-create": "సృష్టించు",
        "botpasswords-label-update": "తాజాకరించు",
index f89fa62..f741cd2 100644 (file)
@@ -587,7 +587,7 @@ abstract class Maintenance {
                        "server name detection may fail in command line scripts.", false, true );
                $this->addOption( 'profiler', 'Profiler output format (usually "text")', false, true );
                // This is named --mwdebug, because --debug would conflict in the phpunit.php CLI script.
-               $this->addOption( 'mwdebug', 'Enable built-in MediaWiki development settings', false, true );
+               $this->addOption( 'mwdebug', 'Enable built-in MediaWiki development settings', false, false );
 
                # Save generic options to display them separately in help
                $this->mGenericParameters = $this->mParams;
index b78e691..a7a6465 100644 (file)
@@ -87,7 +87,7 @@ class TableCleanup extends Maintenance {
 
                $this->output(
                        sprintf( "%s %s: %6.2f%% done on %s; ETA %s [%d/%d] %.2f/sec <%.2f%% updated>\n",
-                               wfWikiID(),
+                               WikiMap::getCurrentWikiDbDomain()->getId(),
                                wfTimestamp( TS_DB, intval( $now ) ),
                                $portion * 100.0,
                                $this->table,
index 5f7f9d5..cbe5d27 100644 (file)
@@ -110,8 +110,9 @@ class UserDupes {
         * @return bool
         */
        private function checkDupes( $doDelete = false ) {
+               $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
                if ( $this->hasUniqueIndex() ) {
-                       echo wfWikiID() . " already has a unique index on its user table.\n";
+                       echo "$dbDomain already has a unique index on its user table.\n";
 
                        return true;
                }
@@ -122,7 +123,7 @@ class UserDupes {
                $dupes = $this->getDupes();
                $count = count( $dupes );
 
-               $this->out( "Found $count accounts with duplicate records on " . wfWikiID() . ".\n" );
+               $this->out( "Found $count accounts with duplicate records on $dbDomain.\n" );
                $this->trimmed = 0;
                $this->reassigned = 0;
                $this->failed = 0;
@@ -145,11 +146,13 @@ class UserDupes {
 
                if ( $this->trimmed > 0 ) {
                        if ( $doDelete ) {
-                               $this->out( "$this->trimmed duplicate user records were deleted from "
-                                       . wfWikiID() . ".\n" );
+                               $this->out(
+                                       "$this->trimmed duplicate user records were deleted from $dbDomain.\n" );
                        } else {
-                               $this->out( "$this->trimmed duplicate user accounts were found on "
-                                       . wfWikiID() . " which can be removed safely.\n" );
+                               $this->out(
+                                       "$this->trimmed duplicate user accounts were found on $dbDomain " .
+                                       "which can be removed safely.\n"
+                               );
                        }
                }
 
index c8eae03..0b0e485 100644 (file)
@@ -1983,6 +1983,7 @@ return [
                        'resources/src/mediawiki.special/special.less',
                        'resources/src/mediawiki.special/apisandbox.css',
                        'resources/src/mediawiki.special/comparepages.less',
+                       'resources/src/mediawiki.special/contributions.less',
                        'resources/src/mediawiki.special/edittags.css',
                        'resources/src/mediawiki.special/movePage.css',
                        'resources/src/mediawiki.special/newpages.less',
@@ -2146,9 +2147,12 @@ return [
        'mediawiki.special.contributions' => [
                'scripts' => 'resources/src/mediawiki.special.contributions.js',
                'dependencies' => [
+                       'jquery.makeCollapsible',
+                       'oojs-ui',
                        'mediawiki.widgets.DateInputWidget',
                        'mediawiki.jqueryMsg',
-               ]
+               ],
+               'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.special.edittags' => [
                'scripts' => 'resources/src/mediawiki.special.edittags.js',
@@ -2195,6 +2199,9 @@ return [
                'styles' => 'resources/src/mediawiki.special.preferences.styles.ooui.less',
        ],
        'mediawiki.special.recentchanges' => [
+               'dependencies' => [
+                       'mediawiki.widgets'
+               ],
                'scripts' => 'resources/src/mediawiki.special.recentchanges.js',
                'targets' => [ 'desktop', 'mobile' ],
        ],
index dff7881..9559d3a 100644 (file)
        font-weight: bold;
 }
 
+// on smaller screen, set .watchlistDetail to full width
+// so that the spinner doesn't appear beside it. T225127#5518870
+@media screen and ( max-width: @width-breakpoint-tablet ) {
+       .client-js {
+               /* stylelint-disable-next-line selector-class-pattern */
+               .watchlistDetails {
+                       float: none;
+                       width: auto;
+               }
+       }
+}
+
 @-webkit-keyframes rcfiltersBouncedelay {
        // 50% equals 800ms
        0%,
index 52f7ff2..75da5dd 100644 (file)
                border-top: 2px solid @colorGray14;
        }
 }
+
+// On small screens, remove the table properties from the
+// top section. T225127#5518870
+@media screen and ( max-width: @width-breakpoint-tablet ) {
+       .mw-rcfilters-ui-watchlistTopSectionWidget {
+               .mw-rcfilters-ui-table,
+               .mw-rcfilters-ui-row,
+               .mw-rcfilters-ui-cell {
+                       display: block;
+               }
+
+               &-editWatchlistButton {
+                       margin-top: 1em;
+               }
+       }
+}
index 310832d..c62acd9 100644 (file)
@@ -2,7 +2,7 @@
  * JavaScript for Special:RecentChanges
  */
 ( function () {
-       var rc, $checkboxes, $select;
+       var rc, $checkboxes, $select, namespaceDropdown;
 
        /**
         * @class mw.special.recentchanges
                 */
                updateCheckboxes: function () {
                        // The option element for the 'all' namespace has an empty value
-                       var isAllNS = $select.val() === '';
+                       var value = $select.val(),
+                               isAllNS = value === 'all' || value === '';
 
                        // Iterates over checkboxes and propagate the selected option
                        $checkboxes.toggleClass( 'mw-input-hidden', isAllNS );
                },
 
                init: function () {
-                       $select = $( '#namespace' );
-                       $checkboxes = $( '#nsassociated, #nsinvert' ).closest( '.mw-input-with-label' );
+                       $select = $( 'select#namespace' );
+                       $checkboxes = $( '#nsassociated, #nsinvert, .contribs-ns-filters' )
+                               .closest( '.mw-input-with-label' );
 
-                       // Bind to change event of the checkboxes.
-                       // The initial state is already set in HTML.
-                       $select.on( 'change', rc.updateCheckboxes );
+                       if ( $select.length === 0 ) {
+                               $select = $( '#namespace select' );
+                               if ( $select.length > 0 ) {
+                                       namespaceDropdown = OO.ui.infuse( $( '#namespace' ).closest( '[data-ooui]' ) );
+                                       namespaceDropdown.on( 'change', rc.updateCheckboxes );
+                               }
+                       } else {
+                               // Bind to change event of the checkboxes.
+                               // The initial state is already set in HTML.
+                               $select.on( 'change', rc.updateCheckboxes );
+                       }
                }
        };
 
diff --git a/resources/src/mediawiki.special/contributions.less b/resources/src/mediawiki.special/contributions.less
new file mode 100644 (file)
index 0000000..cc0e538
--- /dev/null
@@ -0,0 +1,65 @@
+/*!
+ * Styling for Special:Contributions
+ */
+@import 'mediawiki.ui/variables.less';
+
+// OOUIHTMLForm styles.
+@ooui-font-size-browser: 16; // Assumed browser default of `16px`.
+@ooui-font-size-base: 0.875em; // Equals `14px` at browser default of `16px`.
+
+@ooui-spacing-small: 8 / @ooui-font-size-browser / @ooui-font-size-base; // Equals `0.57142857em`≈`8px`.
+@ooui-spacing-medium: 12 / @ooui-font-size-browser / @ooui-font-size-base; // Equals `0.8571429em`≈`12px`.
+@ooui-spacing-large: 16 / @ooui-font-size-browser / @ooui-font-size-base; // Equals `1.1428571em`≈`16px`.
+
+.oo-ui-fieldsetLayout-group {
+       max-width: 50em;
+
+       .oo-ui-panelLayout-padded.oo-ui-panelLayout-framed {
+               margin: 0;
+               border: 0;
+               padding: 0;
+       }
+
+       // Hide extra `legend`s when grouping form in sections.
+       .oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-fieldsetLayout-header {
+               display: none;
+       }
+}
+
+.mw-autocomplete-user.oo-ui-fieldLayout {
+       margin-top: @ooui-spacing-small;
+}
+
+// Higher specificity needed to override OOUIHTMLForm styles.
+.mw-htmlform-field-HTMLMultiSelectField.mw-htmlform-flatlist.oo-ui-fieldLayout {
+       margin-top: @ooui-spacing-small;
+}
+
+.mw-htmlform-field-HTMLTagFilter ~ .mw-htmlform-field-HTMLCheckField.oo-ui-fieldLayout {
+       display: inline-block;
+       padding-right: @ooui-spacing-large;
+}
+
+// Clearfix for floated `.mw-htmlform-field-HTMLDateTimeField` below.
+#mw-htmlform-contribs-date:after {
+       content: '';
+       clear: both;
+       display: block;
+}
+
+.mw-htmlform-field-HTMLDateTimeField {
+       margin-right: @ooui-spacing-large;
+       margin-bottom: @ooui-spacing-small;
+
+       .oo-ui-fieldLayout.oo-ui-labelElement&:first-child {
+               margin-top: @ooui-spacing-medium;
+       }
+}
+
+@media all and ( min-width: @width-breakpoint-tablet ) {
+       .mw-htmlform-field-HTMLDateTimeField {
+               float: left;
+               // Same `width` as DateInputWidget.
+               width: 21em;
+       }
+}
index 3f76cf0..40a2ec2 100644 (file)
        font-weight: bold;
 }
 
-.mw-contributions-form select {
-       vertical-align: middle;
-}
-
 /* Special:EditWatchlist */
 .watchlistredir {
        font-style: italic;
index 0eb1134..89de155 100644 (file)
@@ -73,7 +73,7 @@
 
                // Highlight matching parts of link suggestion
                if ( config.query ) {
-                       this.setHighlightedQuery( config.data, config.query, config.compare );
+                       this.setHighlightedQuery( config.data, config.query, config.compare, true );
                }
                this.$label.attr( 'title', config.data );
 
index 9c08b9f..57cc073 100644 (file)
@@ -73,6 +73,47 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                return $store;
        }
 
+       /**
+        * @dataProvider provideConstructor
+        * @param int $stage
+        * @param string|null $exceptionMsg
+        */
+       public function testConstructor( $stage, $exceptionMsg ) {
+               try {
+                       $m = new CommentStore( Language::factory( 'qqx' ), $stage );
+                       if ( $exceptionMsg !== null ) {
+                               $this->fail( 'Expected exception not thrown' );
+                       }
+                       $this->assertInstanceOf( CommentStore::class, $m );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame( $exceptionMsg, $ex->getMessage() );
+               }
+       }
+
+       public static function provideConstructor() {
+               return [
+                       [ 0, '$stage must include a write mode' ],
+                       [ SCHEMA_COMPAT_READ_OLD, '$stage must include a write mode' ],
+                       [ SCHEMA_COMPAT_READ_NEW, '$stage must include a write mode' ],
+                       [ SCHEMA_COMPAT_READ_BOTH, '$stage must include a write mode' ],
+
+                       [ SCHEMA_COMPAT_WRITE_OLD, '$stage must include a read mode' ],
+                       [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_OLD, null ],
+                       [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_NEW, null ],
+                       [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, null ],
+
+                       [ SCHEMA_COMPAT_WRITE_NEW, '$stage must include a read mode' ],
+                       [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_OLD, null ],
+                       [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_NEW, null ],
+                       [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, null ],
+
+                       [ SCHEMA_COMPAT_WRITE_BOTH, '$stage must include a read mode' ],
+                       [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, null ],
+                       [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, null ],
+                       [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, null ],
+               ];
+       }
+
        /**
         * @dataProvider provideGetFields
         * @param int $stage
@@ -115,6 +156,14 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                MIGRATION_NEW, 'ipb_reason',
                                [ 'ipb_reason_id' => 'ipb_reason_id' ],
                        ],
+                       'Simple table, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_reason',
+                               [ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ],
+                       ],
+                       'Simple table, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_reason',
+                               [ 'ipb_reason_id' => 'ipb_reason_id' ],
+                       ],
 
                        'Revision, old' => [
                                MIGRATION_OLD, 'rev_comment',
@@ -136,6 +185,18 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                MIGRATION_NEW, 'rev_comment',
                                [ 'rev_comment_pk' => 'rev_id' ],
                        ],
+                       'Revision, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_comment',
+                               [
+                                       'rev_comment_text' => 'rev_comment',
+                                       'rev_comment_data' => 'NULL',
+                                       'rev_comment_cid' => 'NULL',
+                               ],
+                       ],
+                       'Revision, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_comment',
+                               [ 'rev_comment_pk' => 'rev_id' ],
+                       ],
 
                        'Image, old' => [
                                MIGRATION_OLD, 'img_description',
@@ -165,6 +226,20 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        'img_description_id' => 'img_description_id'
                                ],
                        ],
+                       'Image, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'img_description',
+                               [
+                                       'img_description_text' => 'img_description',
+                                       'img_description_data' => 'NULL',
+                                       'img_description_cid' => 'NULL',
+                               ],
+                       ],
+                       'Image, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'img_description',
+                               [
+                                       'img_description_id' => 'img_description_id'
+                               ],
+                       ],
                ];
        }
 
@@ -244,6 +319,30 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        ],
                                ],
                        ],
+                       'Simple table, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_reason', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'ipb_reason_text' => 'ipb_reason',
+                                               'ipb_reason_data' => 'NULL',
+                                               'ipb_reason_cid' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Simple table, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_reason', [
+                                       'tables' => [ 'comment_ipb_reason' => 'comment' ],
+                                       'fields' => [
+                                               'ipb_reason_text' => 'comment_ipb_reason.comment_text',
+                                               'ipb_reason_data' => 'comment_ipb_reason.comment_data',
+                                               'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
+                                       ],
+                               ],
+                       ],
 
                        'Revision, old' => [
                                MIGRATION_OLD, 'rev_comment', [
@@ -310,6 +409,35 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        ],
                                ],
                        ],
+                       'Revision, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_comment', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'rev_comment_text' => 'rev_comment',
+                                               'rev_comment_data' => 'NULL',
+                                               'rev_comment_cid' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Revision, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_comment', [
+                                       'tables' => [
+                                               'temp_rev_comment' => 'revision_comment_temp',
+                                               'comment_rev_comment' => 'comment',
+                                       ],
+                                       'fields' => [
+                                               'rev_comment_text' => 'comment_rev_comment.comment_text',
+                                               'rev_comment_data' => 'comment_rev_comment.comment_data',
+                                               'rev_comment_cid' => 'comment_rev_comment.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+                                               'comment_rev_comment' => [ 'JOIN',
+                                                       'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       ],
+                               ],
+                       ],
 
                        'Image, old' => [
                                MIGRATION_OLD, 'img_description', [
@@ -373,6 +501,34 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        ],
                                ],
                        ],
+                       'Image, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'img_description', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'img_description_text' => 'img_description',
+                                               'img_description_data' => 'NULL',
+                                               'img_description_cid' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Image, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'img_description', [
+                                       'tables' => [
+                                               'comment_img_description' => 'comment',
+                                       ],
+                                       'fields' => [
+                                               'img_description_text' => 'comment_img_description.comment_text',
+                                               'img_description_data' => 'comment_img_description.comment_data',
+                                               'img_description_cid' => 'comment_img_description.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'comment_img_description' => [ 'JOIN',
+                                                       'comment_img_description.comment_id = img_description_id',
+                                               ],
+                                       ],
+                               ],
+                       ],
                ];
        }
 
@@ -413,6 +569,15 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                MIGRATION_NEW ],
                        MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
                        MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [
+                               MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
+                       ],
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [
+                               MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
+                       ],
                ];
 
                foreach ( $stages as $writeStage => $possibleReadStages ) {
@@ -427,12 +592,12 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                $fields = $wstore->insert( $this->db, $key, $comment, $data );
                        }
 
-                       if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
+                       if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
                        } else {
                                $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
                        }
-                       if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
+                       if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) {
                                $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
                        } else {
                                $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
@@ -463,13 +628,17 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        $queryInfo['joins']
                                );
 
+                               $expectForCombination = (
+                                       ( $writeStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_OLD ||
+                                       ( $readStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD
+                               ) ? $expectOld : $expect;
                                $this->assertComment(
-                                       $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+                                       $expectForCombination,
                                        $rstore->getCommentLegacy( $this->db, $key, $fieldRow ),
                                        "w=$writeStage, r=$readStage, from getFields()"
                                );
                                $this->assertComment(
-                                       $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+                                       $expectForCombination,
                                        $rstore->getComment( $key, $joinRow ),
                                        "w=$writeStage, r=$readStage, from getJoin()"
                                );
@@ -503,6 +672,15 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                MIGRATION_NEW ],
                        MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
                        MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [
+                               MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
+                       ],
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [
+                               MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
+                       ],
                ];
 
                foreach ( $stages as $writeStage => $possibleReadStages ) {
@@ -517,12 +695,12 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                $fields = $wstore->insert( $this->db, $comment, $data );
                        }
 
-                       if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
+                       if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
                        } else {
                                $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
                        }
-                       if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
+                       if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) {
                                $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
                        } else {
                                $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
@@ -553,13 +731,17 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        $queryInfo['joins']
                                );
 
+                               $expectForCombination = (
+                                       ( $writeStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_OLD ||
+                                       ( $readStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD
+                               ) ? $expectOld : $expect;
                                $this->assertComment(
-                                       $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+                                       $expectForCombination,
                                        $rstore->getCommentLegacy( $this->db, $fieldRow ),
                                        "w=$writeStage, r=$readStage, from getFields()"
                                );
                                $this->assertComment(
-                                       $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+                                       $expectForCombination,
                                        $rstore->getComment( $joinRow ),
                                        "w=$writeStage, r=$readStage, from getJoin()"
                                );
@@ -725,6 +907,9 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                        'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
                        'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
                        'MIGRATION_NEW' => [ MIGRATION_NEW ],
+
+                       'SCHEMA_COMPAT write-both/read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
+                       'SCHEMA_COMPAT write-both/read-new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
                ];
        }
 
index 4401410..35d06dd 100644 (file)
@@ -566,11 +566,11 @@ class HtmlTest extends MediaWikiTestCase {
                        '<div class="errorbox">err</div>'
                );
                $this->assertEquals(
-                       Html::errorBox( 'err', 'heading' ),
-                       '<div class="errorbox"><h2>heading</h2>err</div>'
+                       Html::errorBox( 'err', 'heading', 'errorbox-custom-class' ),
+                       '<div class="errorbox errorbox-custom-class"><h2>heading</h2>err</div>'
                );
                $this->assertEquals(
-                       Html::errorBox( 'err', '0' ),
+                       Html::errorBox( 'err', '0', '' ),
                        '<div class="errorbox"><h2>0</h2>err</div>'
                );
        }
index 4c92545..6fff68f 100644 (file)
@@ -316,8 +316,8 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
         * @dataProvider provideCommonDomainGTIDs
         * @covers Wikimedia\Rdbms\MySQLMasterPos
         */
-       public function testCommonGtidDomains( MySQLMasterPos $pos, MySQLMasterPos $ref, $gtids ) {
-               $this->assertEquals( $gtids, MySQLMasterPos::getCommonDomainGTIDs( $pos, $ref ) );
+       public function testGetRelevantActiveGTIDs( MySQLMasterPos $pos, MySQLMasterPos $ref, $gtids ) {
+               $this->assertEquals( $gtids, MySQLMasterPos::getRelevantActiveGTIDs( $pos, $ref ) );
        }
 
        public static function provideCommonDomainGTIDs() {
@@ -327,6 +327,12 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
                                new MySQLMasterPos( '255-11-1000', 1 ),
                                [ '255-13-99' ]
                        ],
+                       [
+                               ( new MySQLMasterPos( '255-13-99,256-12-50,257-14-50', 1 ) )
+                                       ->setActiveDomain( 257 ),
+                               new MySQLMasterPos( '255-11-1000,257-14-30', 1 ),
+                               [ '257-14-50' ]
+                       ],
                        [
                                new MySQLMasterPos(
                                        '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-5,' .
index dad9f1e..6ebbc3a 100644 (file)
@@ -292,9 +292,8 @@ TEXT
                        ->disableOriginalConstructor()
                        ->getMock();
                $image->method( 'getDataUri' )
-                       ->will( $this->returnValue( $dataUriReturnValue ) );
-               $image->expects( $this->any() )
-                       ->method( 'getUrl' )
+                       ->willReturn( $dataUriReturnValue );
+               $image->method( 'getUrl' )
                        ->will( $this->returnValueMap( [
                                [ $context, 'load.php', null, 'original', 'original.svg' ],
                                [ $context, 'load.php', null, 'rasterized', 'rasterized.png' ],
diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php
deleted file mode 100644 (file)
index c3fc55a..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-<?php
-
-/**
- * @group ResourceLoader
- */
-class ResourceLoaderImageTest extends ResourceLoaderTestCase {
-
-       protected $imagesPath;
-
-       protected function setUp() {
-               parent::setUp();
-               $this->imagesPath = __DIR__ . '/../../data/resourceloader';
-       }
-
-       protected function getTestImage( $name ) {
-               $options = ResourceLoaderImageModuleTest::$commonImageData[$name];
-               $fileDescriptor = is_string( $options ) ? $options : $options['file'];
-               $allowedVariants = ( is_array( $options ) && isset( $options['variants'] ) ) ?
-                       $options['variants'] : [];
-               $variants = array_fill_keys( $allowedVariants, [ 'color' => 'red' ] );
-               return new ResourceLoaderImageTestable(
-                       $name,
-                       'test',
-                       $fileDescriptor,
-                       $this->imagesPath,
-                       $variants
-               );
-       }
-
-       public static function provideGetPath() {
-               return [
-                       [ 'abc', 'en', 'abc.gif' ],
-                       [ 'abc', 'he', 'abc.gif' ],
-                       [ 'def', 'en', 'def.svg' ],
-                       [ 'def', 'he', 'def.svg' ],
-                       [ 'ghi', 'en', 'ghi.svg' ],
-                       [ 'ghi', 'he', 'jkl.svg' ],
-                       [ 'mno', 'en', 'mno-ltr.svg' ],
-                       [ 'mno', 'ar', 'mno-rtl.svg' ],
-                       [ 'mno', 'he', 'mno-ltr.svg' ],
-                       [ 'pqr', 'en', 'pqr-b.svg' ],
-                       [ 'pqr', 'en-gb', 'pqr-b.svg' ],
-                       [ 'pqr', 'de', 'pqr-f.svg' ],
-                       [ 'pqr', 'de-formal', 'pqr-f.svg' ],
-                       [ 'pqr', 'ar', 'pqr-f.svg' ],
-                       [ 'pqr', 'fr', 'pqr-a.svg' ],
-                       [ 'pqr', 'he', 'pqr-a.svg' ],
-               ];
-       }
-
-       /**
-        * @covers ResourceLoaderImage::getPath
-        * @dataProvider provideGetPath
-        */
-       public function testGetPath( $imageName, $languageCode, $path ) {
-               static $dirMap = [
-                       'en' => 'ltr',
-                       'en-gb' => 'ltr',
-                       'de' => 'ltr',
-                       'de-formal' => 'ltr',
-                       'fr' => 'ltr',
-                       'he' => 'rtl',
-                       'ar' => 'rtl',
-               ];
-
-               $image = $this->getTestImage( $imageName );
-               $context = $this->getResourceLoaderContext( [
-                       'lang' => $languageCode,
-                       'dir' => $dirMap[$languageCode],
-               ] );
-
-               $this->assertEquals( $image->getPath( $context ), $this->imagesPath . '/' . $path );
-       }
-
-       /**
-        * @covers ResourceLoaderImage::getExtension
-        * @covers ResourceLoaderImage::getMimeType
-        */
-       public function testGetExtension() {
-               $image = $this->getTestImage( 'def' );
-               $this->assertEquals( $image->getExtension(), 'svg' );
-               $this->assertEquals( $image->getExtension( 'original' ), 'svg' );
-               $this->assertEquals( $image->getExtension( 'rasterized' ), 'png' );
-               $image = $this->getTestImage( 'abc' );
-               $this->assertEquals( $image->getExtension(), 'gif' );
-               $this->assertEquals( $image->getExtension( 'original' ), 'gif' );
-               $this->assertEquals( $image->getExtension( 'rasterized' ), 'gif' );
-       }
-
-       /**
-        * @covers ResourceLoaderImage::getImageData
-        * @covers ResourceLoaderImage::variantize
-        * @covers ResourceLoaderImage::massageSvgPathdata
-        */
-       public function testGetImageData() {
-               $context = $this->getResourceLoaderContext();
-
-               $image = $this->getTestImage( 'def' );
-               $data = file_get_contents( $this->imagesPath . '/def.svg' );
-               $dataConstructive = file_get_contents( $this->imagesPath . '/def_variantize.svg' );
-               $this->assertEquals( $image->getImageData( $context, null, 'original' ), $data );
-               $this->assertEquals(
-                       $image->getImageData( $context, 'destructive', 'original' ),
-                       $dataConstructive
-               );
-               // Stub, since we don't know if we even have a SVG handler, much less what exactly it'll output
-               $this->assertEquals( $image->getImageData( $context, null, 'rasterized' ), 'RASTERIZESTUB' );
-
-               $image = $this->getTestImage( 'abc' );
-               $data = file_get_contents( $this->imagesPath . '/abc.gif' );
-               $this->assertEquals( $image->getImageData( $context, null, 'original' ), $data );
-               $this->assertEquals( $image->getImageData( $context, null, 'rasterized' ), $data );
-       }
-
-       /**
-        * @covers ResourceLoaderImage::massageSvgPathdata
-        */
-       public function testMassageSvgPathdata() {
-               $image = $this->getTestImage( 'ghi' );
-               $data = file_get_contents( $this->imagesPath . '/ghi.svg' );
-               $dataMassaged = file_get_contents( $this->imagesPath . '/ghi_massage.svg' );
-               $this->assertEquals( $image->massageSvgPathdata( $data ), $dataMassaged );
-       }
-}
-
-class ResourceLoaderImageTestable extends ResourceLoaderImage {
-       // Make some protected methods public
-       public function massageSvgPathdata( $svg ) {
-               return parent::massageSvgPathdata( $svg );
-       }
-
-       // Stub, since we don't know if we even have a SVG handler, much less what exactly it'll output
-       public function rasterize( $svg ) {
-               return 'RASTERIZESTUB';
-       }
-}
diff --git a/tests/phpunit/unit/includes/resourceloader/ResourceLoaderImageTest.php b/tests/phpunit/unit/includes/resourceloader/ResourceLoaderImageTest.php
new file mode 100644 (file)
index 0000000..5265b3e
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * @group ResourceLoader
+ */
+class ResourceLoaderImageTest extends MediaWikiUnitTestCase {
+
+       private $imagesPath;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->imagesPath = __DIR__ . '/../../../data/resourceloader';
+       }
+
+       protected function tearDown() {
+               Language::$dataCache = null;
+       }
+
+       protected function getTestImage( $name ) {
+               $options = ResourceLoaderImageModuleTest::$commonImageData[$name];
+               $fileDescriptor = is_string( $options ) ? $options : $options['file'];
+               $allowedVariants = ( is_array( $options ) && isset( $options['variants'] ) ) ?
+                       $options['variants'] : [];
+               $variants = array_fill_keys( $allowedVariants, [ 'color' => 'red' ] );
+               return new ResourceLoaderImageTestable(
+                       $name,
+                       'test',
+                       $fileDescriptor,
+                       $this->imagesPath,
+                       $variants
+               );
+       }
+
+       public static function provideGetPath() {
+               return [
+                       [ 'abc', 'en', 'abc.gif' ],
+                       [ 'abc', 'he', 'abc.gif' ],
+                       [ 'def', 'en', 'def.svg' ],
+                       [ 'def', 'he', 'def.svg' ],
+                       [ 'ghi', 'en', 'ghi.svg' ],
+                       [ 'ghi', 'he', 'jkl.svg' ],
+                       [ 'mno', 'en', 'mno-ltr.svg' ],
+                       [ 'mno', 'ar', 'mno-rtl.svg' ],
+                       [ 'mno', 'he', 'mno-ltr.svg' ],
+                       [ 'pqr', 'en', 'pqr-b.svg' ],
+                       [ 'pqr', 'en-gb', 'pqr-b.svg' ],
+                       [ 'pqr', 'de', 'pqr-f.svg' ],
+                       [ 'pqr', 'de-formal', 'pqr-f.svg' ],
+                       [ 'pqr', 'ar', 'pqr-f.svg' ],
+                       [ 'pqr', 'fr', 'pqr-a.svg' ],
+                       [ 'pqr', 'he', 'pqr-a.svg' ],
+               ];
+       }
+
+       /**
+        * @covers ResourceLoaderImage::getPath
+        * @dataProvider provideGetPath
+        */
+       public function testGetPath( $imageName, $languageCode, $path ) {
+               static $dirMap = [
+                       'en' => 'ltr',
+                       'en-gb' => 'ltr',
+                       'de' => 'ltr',
+                       'de-formal' => 'ltr',
+                       'fr' => 'ltr',
+                       'he' => 'rtl',
+                       'ar' => 'rtl',
+               ];
+
+               $image = $this->getTestImage( $imageName );
+               $context = new DerivativeResourceLoaderContext(
+                       $this->createMock( ResourceLoaderContext::class )
+               );
+               $context->setLanguage( $languageCode );
+               $context->setDirection( $dirMap[$languageCode] );
+               Language::$dataCache = $this->createMock( LocalisationCache::class );
+               Language::$dataCache->method( 'getItem' )->will( $this->returnCallback( function ( $code ) {
+                       return ( [
+                               'en-gb' => [ 'en' ],
+                               'de-formal' => [ 'de' ],
+                       ] )[ $code ] ?? [];
+               } ) );
+
+               $this->assertEquals( $image->getPath( $context ), $this->imagesPath . '/' . $path );
+       }
+
+       /**
+        * @covers ResourceLoaderImage::getExtension
+        * @covers ResourceLoaderImage::getMimeType
+        */
+       public function testGetExtension() {
+               $image = $this->getTestImage( 'def' );
+               $this->assertEquals( $image->getExtension(), 'svg' );
+               $this->assertEquals( $image->getExtension( 'original' ), 'svg' );
+               $this->assertEquals( $image->getExtension( 'rasterized' ), 'png' );
+               $image = $this->getTestImage( 'abc' );
+               $this->assertEquals( $image->getExtension(), 'gif' );
+               $this->assertEquals( $image->getExtension( 'original' ), 'gif' );
+               $this->assertEquals( $image->getExtension( 'rasterized' ), 'gif' );
+       }
+
+       /**
+        * @covers ResourceLoaderImage::getImageData
+        * @covers ResourceLoaderImage::variantize
+        * @covers ResourceLoaderImage::massageSvgPathdata
+        */
+       public function testGetImageData() {
+               $context = $this->createMock( ResourceLoaderContext::class );
+
+               $image = $this->getTestImage( 'def' );
+               $data = file_get_contents( $this->imagesPath . '/def.svg' );
+               $dataConstructive = file_get_contents( $this->imagesPath . '/def_variantize.svg' );
+               $this->assertEquals( $image->getImageData( $context, null, 'original' ), $data );
+               $this->assertEquals(
+                       $image->getImageData( $context, 'destructive', 'original' ),
+                       $dataConstructive
+               );
+               // Stub, since we don't know if we even have a SVG handler, much less what exactly it'll output
+               $this->assertEquals( $image->getImageData( $context, null, 'rasterized' ), 'RASTERIZESTUB' );
+
+               $image = $this->getTestImage( 'abc' );
+               $data = file_get_contents( $this->imagesPath . '/abc.gif' );
+               $this->assertEquals( $image->getImageData( $context, null, 'original' ), $data );
+               $this->assertEquals( $image->getImageData( $context, null, 'rasterized' ), $data );
+       }
+
+       /**
+        * @covers ResourceLoaderImage::massageSvgPathdata
+        */
+       public function testMassageSvgPathdata() {
+               $image = $this->getTestImage( 'ghi' );
+               $data = file_get_contents( $this->imagesPath . '/ghi.svg' );
+               $dataMassaged = file_get_contents( $this->imagesPath . '/ghi_massage.svg' );
+               $this->assertEquals( $image->massageSvgPathdata( $data ), $dataMassaged );
+       }
+}
+
+class ResourceLoaderImageTestable extends ResourceLoaderImage {
+       // Make some protected methods public
+       public function massageSvgPathdata( $svg ) {
+               return parent::massageSvgPathdata( $svg );
+       }
+
+       // Stub, since we don't know if we even have a SVG handler, much less what exactly it'll output
+       public function rasterize( $svg ) {
+               return 'RASTERIZESTUB';
+       }
+}
index 59672f4..16c3183 100644 (file)
                        [ 'test.load.circleB', '0', [ 'test.load.circleC' ] ],
                        [ 'test.load.circleC', '0', [ 'test.load.circleA' ] ]
                ] );
-               this.sandbox.stub( mw, 'track', function ( topic, data ) {
+               this.sandbox.stub( mw, 'trackError', function ( topic, data ) {
                        capture.push( {
                                topic: topic,
                                error: data.exception && data.exception.message,
                mw.loader.register( [
                        [ 'test.load.circleDirect', '0', [ 'test.load.circleDirect' ] ]
                ] );
-               this.sandbox.stub( mw, 'track', function ( topic, data ) {
+               this.sandbox.stub( mw, 'trackError', function ( topic, data ) {
                        capture.push( {
                                topic: topic,
                                error: data.exception && data.exception.message,
        // Regression test for T36853
        QUnit.test( '.load() - Error: Missing dependency', function ( assert ) {
                var capture = [];
-               this.sandbox.stub( mw, 'track', function ( topic, data ) {
+               this.sandbox.stub( mw, 'trackError', function ( topic, data ) {
                        capture.push( {
                                topic: topic,
                                error: data.exception && data.exception.message,
                this.useStubClock();
 
                // Don't actually emit an error event
-               this.sandbox.stub( mw, 'track' );
+               this.sandbox.stub( mw, 'trackError' );
 
                mw.loader.register( [
                        [ 'test.module1', '0' ],
                }, {}, {} );
                this.tick();
 
-               assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' );
-               assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' );
-               assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' );
+               assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'State of test.module1' );
+               assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'State of test.module2' );
+               assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'State of test.module3' );
 
-               assert.strictEqual( mw.track.callCount, 1 );
+               assert.strictEqual( mw.trackError.callCount, 1 );
        } );
 
        QUnit.test( 'Out-of-order implementation', function ( assert ) {
 
                mw.loader.implement( 'test.module4', function () {} );
                this.tick();
-               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
-               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
-               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' );
+               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'State of test.module4' );
+               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'State of test.module5' );
+               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'State of test.module6' );
 
                mw.loader.implement( 'test.module6', function () {} );
                this.tick();
-               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
-               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
-               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' );
+               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'State of test.module4' );
+               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'State of test.module5' );
+               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'State of test.module6' );
 
                mw.loader.implement( 'test.module5', function () {} );
                this.tick();
-               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
-               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' );
-               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' );
+               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'State of test.module4' );
+               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'State of test.module5' );
+               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'State of test.module6' );
        } );
 
        QUnit.test( 'Missing dependency', function ( assert ) {