Merge "Replace some wfMessage by ContextSource::msg"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 24 Aug 2018 05:18:09 +0000 (05:18 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 24 Aug 2018 05:18:09 +0000 (05:18 +0000)
82 files changed:
RELEASE-NOTES-1.32
api.php
autoload.php
includes/DefaultSettings.php
includes/Storage/NameTableStore.php
includes/Storage/RevisionStore.php
includes/api/ApiComparePages.php
includes/api/ApiMain.php
includes/api/i18n/ar.json
includes/api/i18n/de.json
includes/api/i18n/en.json
includes/api/i18n/fr.json
includes/api/i18n/he.json
includes/api/i18n/ja.json
includes/api/i18n/ko.json
includes/api/i18n/nb.json
includes/api/i18n/pt-br.json
includes/api/i18n/pt.json
includes/api/i18n/qqq.json
includes/api/i18n/ru.json
includes/api/i18n/uk.json
includes/api/i18n/zh-hans.json
includes/api/i18n/zh-hant.json
includes/diff/DifferenceEngine.php
includes/htmlform/fields/HTMLCheckMatrix.php
includes/installer/i18n/tr.json
includes/libs/filebackend/fsfile/TempFSFile.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/specials/pagers/UsersPager.php
includes/widget/CheckMatrixWidget.php [new file with mode: 0644]
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/bs.json
languages/i18n/ca.json
languages/i18n/ce.json
languages/i18n/cs.json
languages/i18n/da.json
languages/i18n/dsb.json
languages/i18n/es.json
languages/i18n/fa.json
languages/i18n/hr.json
languages/i18n/hsb.json
languages/i18n/ja.json
languages/i18n/kn.json
languages/i18n/ko.json
languages/i18n/lt.json
languages/i18n/mni.json
languages/i18n/nap.json
languages/i18n/nb.json
languages/i18n/nds-nl.json
languages/i18n/pl.json
languages/i18n/rue.json
languages/i18n/sk.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/szl.json
languages/i18n/te.json
languages/i18n/tr.json
languages/i18n/tt-cyrl.json
languages/i18n/yi.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
maintenance/Doxyfile
maintenance/deduplicateArchiveRevId.php
maintenance/jsduck/categories.json
maintenance/populateArchiveRevId.php
resources/Resources.php
resources/src/mediawiki.inspect.js
resources/src/mediawiki.user.js
resources/src/mediawiki.widgets/mw.widgets.CheckMatrixWidget.js [new file with mode: 0644]
resources/src/startup/mediawiki.js
resources/src/startup/profiler.js [new file with mode: 0644]
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/Storage/McrReadNewRevisionStoreDbTest.php
tests/phpunit/includes/Storage/McrRevisionStoreDbTest.php
tests/phpunit/includes/Storage/NameTableStoreTest.php
tests/phpunit/includes/Storage/PageUpdaterTest.php
tests/phpunit/includes/Storage/RevisionStoreDbTestBase.php
tests/phpunit/includes/api/ApiBlockTest.php
tests/phpunit/includes/api/ApiComparePagesTest.php
tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js

index 0ad2e41..a7fc232 100644 (file)
@@ -136,6 +136,18 @@ production.
 * action=query&prop=deletedrevisions, action=query&list=allrevisions, and
   action=query&list=alldeletedrevisions are changed similarly to
   &prop=revisions (see the three previous items).
+* (T174032) action=compare now supports multi-content revisions.
+  * It has a 'slots' parameter to select diffing of individual slots. The
+    default behavior is to return one combined diff.
+  * The 'fromtext', 'fromsection', 'fromcontentmodel', 'fromcontentformat',
+    'totext', 'tosection', 'tocontentmodel', and 'tocontentformat' parameters
+    are deprecated. Specify the new 'fromslots' and 'toslots' to identify which
+    slots have text supplied and the corresponding templated parameters for
+    each slot.
+  * The behavior of 'fromsection' and 'tosection' of extracting one section's
+    content is not being preserved. 'fromsection-{slot}' and 'tosection-{slot}'
+    instead expand the given text as if for a section edit. This effectively
+    declines T183823 in favor of T185723.
 
 === Action API internal changes in 1.32 ===
 * Added 'ApiParseMakeOutputPage' hook.
diff --git a/api.php b/api.php
index 9c5ac95..9cf7578 100644 (file)
--- a/api.php
+++ b/api.php
@@ -72,7 +72,11 @@ try {
        if ( !$processor instanceof ApiMain ) {
                throw new MWException( 'ApiBeforeMain hook set $processor to a non-ApiMain class' );
        }
-} catch ( Exception $e ) {
+} catch ( Exception $e ) { // @todo Remove this block when HHVM is no longer supported
+       // Crap. Try to report the exception in API format to be friendly to clients.
+       ApiMain::handleApiBeforeMainException( $e );
+       $processor = false;
+} catch ( Throwable $e ) {
        // Crap. Try to report the exception in API format to be friendly to clients.
        ApiMain::handleApiBeforeMainException( $e );
        $processor = false;
@@ -99,7 +103,9 @@ if ( $wgAPIRequestLog ) {
                try {
                        $manager = $processor->getModuleManager();
                        $module = $manager->getModule( $wgRequest->getVal( 'action' ), 'action' );
-               } catch ( Exception $ex ) {
+               } catch ( Exception $ex ) { // @todo Remove this block when HHVM is no longer supported
+                       $module = null;
+               } catch ( Throwable $ex ) {
                        $module = null;
                }
                if ( !$module || $module->mustBePosted() ) {
index ee2fac9..cef68b0 100644 (file)
@@ -936,6 +936,7 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Special\\SpecialPageFactory' => __DIR__ . '/includes/specialpage/SpecialPageFactory.php',
        'MediaWiki\\User\\UserIdentity' => __DIR__ . '/includes/user/UserIdentity.php',
        'MediaWiki\\User\\UserIdentityValue' => __DIR__ . '/includes/user/UserIdentityValue.php',
+       'MediaWiki\\Widget\\CheckMatrixWidget' => __DIR__ . '/includes/widget/CheckMatrixWidget.php',
        'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php',
        'MediaWiki\\Widget\\ComplexTitleInputWidget' => __DIR__ . '/includes/widget/ComplexTitleInputWidget.php',
        'MediaWiki\\Widget\\DateInputWidget' => __DIR__ . '/includes/widget/DateInputWidget.php',
index fdac10a..164eb46 100644 (file)
@@ -3792,6 +3792,16 @@ $wgResourceLoaderMaxQueryLength = false;
  */
 $wgResourceLoaderValidateJS = true;
 
+/**
+ * When enabled, execution of JavaScript modules is profiled client-side.
+ *
+ * Instrumentation happens in mw.loader.profiler.
+ * Use `mw.inspect('time')` from the browser console to display the data.
+ *
+ * @since 1.32
+ */
+$wgResourceLoaderEnableJSProfiler = false;
+
 /**
  * Whether ResourceLoader should attempt to persist modules in localStorage on
  * browsers that support the Web Storage API.
index 52e8f5b..6c7919d 100644 (file)
@@ -27,7 +27,6 @@ use Wikimedia\Assert\Assert;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\ILoadBalancer;
-use Wikimedia\Rdbms\LoadBalancer;
 
 /**
  * @author Addshore
@@ -35,7 +34,7 @@ use Wikimedia\Rdbms\LoadBalancer;
  */
 class NameTableStore {
 
-       /** @var LoadBalancer */
+       /** @var ILoadBalancer */
        private $loadBalancer;
 
        /** @var WANObjectCache */
@@ -159,11 +158,13 @@ class NameTableStore {
                if ( $searchResult === false ) {
                        $id = $this->store( $name );
                        if ( $id === null ) {
-                               // RACE: $name was already in the db, probably just inserted, so load from master
-                               // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs
-                               $table = $this->loadTable(
-                                       $this->getDBConnection( DB_MASTER, LoadBalancer::CONN_TRX_AUTOCOMMIT )
-                               );
+                               // RACE: $name was already in the db, probably just inserted, so load from master.
+                               // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs.
+                               // ...but not during unit tests, because we need the fake DB tables of the default
+                               // connection.
+                               $connFlags = defined( 'MW_PHPUNIT_TEST' ) ? 0 : ILoadBalancer::CONN_TRX_AUTOCOMMIT;
+                               $table = $this->reloadMap( $connFlags );
+
                                $searchResult = array_search( $name, $table, true );
                                if ( $searchResult === false ) {
                                        // Insert failed due to IGNORE flag, but DB_MASTER didn't give us the data
@@ -172,14 +173,15 @@ class NameTableStore {
                                        $this->logger->error( $m );
                                        throw new NameTableAccessException( $m );
                                }
-                               $this->purgeWANCache(
-                                       function () {
-                                               $this->cache->reap( $this->getCacheKey(), INF );
-                                       }
-                               );
+                       } elseif ( isset( $table[$id] ) ) {
+                               throw new NameTableAccessException(
+                                       "Expected unused ID from database insert for '$name' "
+                                       . " into '{$this->table}', but ID $id is already associated with"
+                                       . " the name '{$table[$id]}'! This may indicate database corruption!" );
                        } else {
                                $table[$id] = $name;
                                $searchResult = $id;
+
                                // As store returned an ID we know we inserted so delete from WAN cache
                                $this->purgeWANCache(
                                        function () {
@@ -193,6 +195,31 @@ class NameTableStore {
                return $searchResult;
        }
 
+       /**
+        * Reloads the name table from the master database, and purges the WAN cache entry.
+        *
+        * @note This should only be called in situations where the local cache has been detected
+        * to be out of sync with the database. There should be no reason to call this method
+        * from outside the NameTabelStore during normal operation. This method may however be
+        * useful in unit tests.
+        *
+        * @param int $connFlags ILoadBalancer::CONN_XXX flags. Optional.
+        *
+        * @return \string[] The freshly reloaded name map
+        */
+       public function reloadMap( $connFlags = 0 ) {
+               $this->tableCache = $this->loadTable(
+                       $this->getDBConnection( DB_MASTER, $connFlags )
+               );
+               $this->purgeWANCache(
+                       function () {
+                               $this->cache->reap( $this->getCacheKey(), INF );
+                       }
+               );
+
+               return $this->tableCache;
+       }
+
        /**
         * Get the id of the given name.
         * If the name doesn't exist this will throw.
index 14b1820..d219267 100644 (file)
@@ -746,6 +746,76 @@ class RevisionStore
                if ( !isset( $revisionRow['rev_id'] ) ) {
                        // only if auto-increment was used
                        $revisionRow['rev_id'] = intval( $dbw->insertId() );
+
+                       if ( $dbw->getType() === 'mysql' ) {
+                               // (T202032) MySQL until 8.0 and MariaDB until some version after 10.1.34 don't save the
+                               // auto-increment value to disk, so on server restart it might reuse IDs from deleted
+                               // revisions. We can fix that with an insert with an explicit rev_id value, if necessary.
+
+                               $maxRevId = intval( $dbw->selectField( 'archive', 'MAX(ar_rev_id)', '', __METHOD__ ) );
+                               $table = 'archive';
+                               if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
+                                       $maxRevId2 = intval( $dbw->selectField( 'slots', 'MAX(slot_revision_id)', '', __METHOD__ ) );
+                                       if ( $maxRevId2 >= $maxRevId ) {
+                                               $maxRevId = $maxRevId2;
+                                               $table = 'slots';
+                                       }
+                               }
+
+                               if ( $maxRevId >= $revisionRow['rev_id'] ) {
+                                       $this->logger->debug(
+                                               '__METHOD__: Inserted revision {revid} but {table} has revisions up to {maxrevid}.'
+                                                       . ' Trying to fix it.',
+                                               [
+                                                       'revid' => $revisionRow['rev_id'],
+                                                       'table' => $table,
+                                                       'maxrevid' => $maxRevId,
+                                               ]
+                                       );
+
+                                       if ( !$dbw->lock( 'fix-for-T202032', __METHOD__ ) ) {
+                                               throw new MWException( 'Failed to get database lock for T202032' );
+                                       }
+                                       $fname = __METHOD__;
+                                       $dbw->onTransactionResolution( function ( $trigger, $dbw ) use ( $fname ) {
+                                               $dbw->unlock( 'fix-for-T202032', $fname );
+                                       } );
+
+                                       $dbw->delete( 'revision', [ 'rev_id' => $revisionRow['rev_id'] ], __METHOD__ );
+
+                                       // The locking here is mostly to make MySQL bypass the REPEATABLE-READ transaction
+                                       // isolation (weird MySQL "feature"). It does seem to block concurrent auto-incrementing
+                                       // inserts too, though, at least on MariaDB 10.1.29.
+                                       //
+                                       // Don't try to lock `revision` in this way, it'll deadlock if there are concurrent
+                                       // transactions in this code path thanks to the row lock from the original ->insert() above.
+                                       //
+                                       // And we have to use raw SQL to bypass the "aggregation used with a locking SELECT" warning
+                                       // that's for non-MySQL DBs.
+                                       $row1 = $dbw->query(
+                                               $dbw->selectSqlText( 'archive', [ 'v' => "MAX(ar_rev_id)" ], '', __METHOD__ ) . ' FOR UPDATE'
+                                       )->fetchObject();
+                                       if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
+                                               $row2 = $dbw->query(
+                                                       $dbw->selectSqlText( 'slots', [ 'v' => "MAX(slot_revision_id)" ], '', __METHOD__ )
+                                                               . ' FOR UPDATE'
+                                               )->fetchObject();
+                                       } else {
+                                               $row2 = null;
+                                       }
+                                       $maxRevId = max(
+                                               $maxRevId,
+                                               $row1 ? intval( $row1->v ) : 0,
+                                               $row2 ? intval( $row2->v ) : 0
+                                       );
+
+                                       // If we don't have SCHEMA_COMPAT_WRITE_NEW, all except the first of any concurrent
+                                       // transactions will throw a duplicate key error here. It doesn't seem worth trying
+                                       // to avoid that.
+                                       $revisionRow['rev_id'] = $maxRevId + 1;
+                                       $dbw->insert( 'revision', $revisionRow, __METHOD__ );
+                               }
+                       }
                }
 
                $commentCallback( $revisionRow['rev_id'] );
@@ -1477,6 +1547,10 @@ class RevisionStore
                $slots = [];
 
                foreach ( $res as $row ) {
+                       // resolve role names and model names from in-memory cache, instead of joining.
+                       $row->role_name = $this->slotRoleStore->getName( (int)$row->slot_role_id );
+                       $row->model_name = $this->contentModelStore->getName( (int)$row->content_model );
+
                        $contentCallback = function ( SlotRecord $slot ) use ( $queryFlags, $row ) {
                                return $this->loadSlotContent( $slot, null, null, null, $queryFlags );
                        };
@@ -2198,6 +2272,9 @@ class RevisionStore
         *
         * @param array $options Any combination of the following strings
         *  - 'content': Join with the content table, and select content meta-data fields
+        *  - 'model': Join with the content_models table, and select the model_name field.
+        *             Only applicable if 'content' is also set.
+        *  - 'role': Join with the slot_roles table, and select the role_name field
         *
         * @return array With three keys:
         *  - tables: (string[]) to include in the `$table` to `IDatabase->select()`
@@ -2234,26 +2311,39 @@ class RevisionStore
                        }
                } else {
                        $ret['tables'][] = 'slots';
-                       $ret['tables'][] = 'slot_roles';
                        $ret['fields'] = array_merge( $ret['fields'], [
                                'slot_revision_id',
                                'slot_content_id',
                                'slot_origin',
-                               'role_name'
+                               'slot_role_id',
                        ] );
-                       $ret['joins']['slot_roles'] = [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ];
+
+                       if ( in_array( 'role', $options, true ) ) {
+                               // Use left join to attach role name, so we still find the revision row even
+                               // if the role name is missing. This triggers a more obvious failure mode.
+                               $ret['tables'][] = 'slot_roles';
+                               $ret['joins']['slot_roles'] = [ 'LEFT JOIN', [ 'slot_role_id = role_id' ] ];
+                               $ret['fields'][] = 'role_name';
+                       }
 
                        if ( in_array( 'content', $options, true ) ) {
                                $ret['tables'][] = 'content';
-                               $ret['tables'][] = 'content_models';
                                $ret['fields'] = array_merge( $ret['fields'], [
                                        'content_size',
                                        'content_sha1',
                                        'content_address',
-                                       'model_name'
+                                       'content_model',
                                ] );
                                $ret['joins']['content'] = [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ];
-                               $ret['joins']['content_models'] = [ 'INNER JOIN', [ 'content_model = model_id' ] ];
+
+                               if ( in_array( 'model', $options, true ) ) {
+                                       // Use left join to attach model name, so we still find the revision row even
+                                       // if the model name is missing. This triggers a more obvious failure mode.
+                                       $ret['tables'][] = 'content_models';
+                                       $ret['joins']['content_models'] = [ 'LEFT JOIN', [ 'content_model = model_id' ] ];
+                                       $ret['fields'][] = 'model_name';
+                               }
+
                        }
                }
 
index 93c35d3..6bfa35d 100644 (file)
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionStore;
+
 class ApiComparePages extends ApiBase {
 
-       private $guessed = false, $guessedTitle, $guessedModel, $props;
+       /** @var RevisionStore */
+       private $revisionStore;
+
+       private $guessedTitle = false, $props;
+
+       public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
+               parent::__construct( $mainModule, $moduleName, $modulePrefix );
+               $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
+       }
 
        public function execute() {
                $params = $this->extractRequestParams();
 
                // Parameter validation
-               $this->requireAtLeastOneParameter( $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext' );
-               $this->requireAtLeastOneParameter( $params, 'totitle', 'toid', 'torev', 'totext', 'torelative' );
+               $this->requireAtLeastOneParameter(
+                       $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext', 'fromslots'
+               );
+               $this->requireAtLeastOneParameter(
+                       $params, 'totitle', 'toid', 'torev', 'totext', 'torelative', 'toslots'
+               );
 
                $this->props = array_flip( $params['prop'] );
 
                // Cache responses publicly by default. This may be overridden later.
                $this->getMain()->setCacheMode( 'public' );
 
-               // Get the 'from' Revision and Content
-               list( $fromRev, $fromContent, $relRev ) = $this->getDiffContent( 'from', $params );
+               // Get the 'from' RevisionRecord
+               list( $fromRev, $fromRelRev, $fromValsRev ) = $this->getDiffRevision( 'from', $params );
 
-               // Get the 'to' Revision and Content
+               // Get the 'to' RevisionRecord
                if ( $params['torelative'] !== null ) {
-                       if ( !$relRev ) {
+                       if ( !$fromRelRev ) {
                                $this->dieWithError( 'apierror-compare-relative-to-nothing' );
                        }
                        switch ( $params['torelative'] ) {
                                case 'prev':
                                        // Swap 'from' and 'to'
-                                       $toRev = $fromRev;
-                                       $toContent = $fromContent;
-                                       $fromRev = $relRev->getPrevious();
-                                       $fromContent = $fromRev
-                                               ? $fromRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
-                                               : $toContent->getContentHandler()->makeEmptyContent();
-                                       if ( !$fromContent ) {
-                                               $this->dieWithError(
-                                                       [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent'
-                                               );
-                                       }
+                                       list( $toRev, $toRelRev2, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ];
+                                       $fromRev = $this->revisionStore->getPreviousRevision( $fromRelRev );
+                                       $fromRelRev = $fromRev;
+                                       $fromValsRev = $fromRev;
                                        break;
 
                                case 'next':
-                                       $toRev = $relRev->getNext();
-                                       $toContent = $toRev
-                                               ? $toRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
-                                               : $fromContent;
-                                       if ( !$toContent ) {
-                                               $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
-                                       }
+                                       $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
+                                       $toRelRev = $toRev;
+                                       $toValsRev = $toRev;
                                        break;
 
                                case 'cur':
-                                       $title = $relRev->getTitle();
-                                       $id = $title->getLatestRevID();
-                                       $toRev = $id ? Revision::newFromId( $id ) : null;
+                                       $title = $fromRelRev->getPageAsLinkTarget();
+                                       $toRev = $this->revisionStore->getRevisionByTitle( $title );
                                        if ( !$toRev ) {
+                                               $title = Title::newFromLinkTarget( $title );
                                                $this->dieWithError(
                                                        [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
                                                );
                                        }
-                                       $toContent = $toRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
-                                       if ( !$toContent ) {
-                                               $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
-                                       }
+                                       $toRelRev = $toRev;
+                                       $toValsRev = $toRev;
                                        break;
                        }
-                       $relRev2 = null;
                } else {
-                       list( $toRev, $toContent, $relRev2 ) = $this->getDiffContent( 'to', $params );
+                       list( $toRev, $toRelRev, $toValsRev ) = $this->getDiffRevision( 'to', $params );
                }
 
-               // Should never happen, but just in case...
-               if ( !$fromContent || !$toContent ) {
+               // Handle missing from or to revisions
+               if ( !$fromRev || !$toRev ) {
                        $this->dieWithError( 'apierror-baddiff' );
                }
 
-               // Extract sections, if told to
-               if ( isset( $params['fromsection'] ) ) {
-                       $fromContent = $fromContent->getSection( $params['fromsection'] );
-                       if ( !$fromContent ) {
-                               $this->dieWithError(
-                                       [ 'apierror-compare-nosuchfromsection', wfEscapeWikiText( $params['fromsection'] ) ],
-                                       'nosuchfromsection'
-                               );
-                       }
+               // Handle revdel
+               if ( !$fromRev->audienceCan(
+                       RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
+               ) ) {
+                       $this->dieWithError( [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' );
                }
-               if ( isset( $params['tosection'] ) ) {
-                       $toContent = $toContent->getSection( $params['tosection'] );
-                       if ( !$toContent ) {
-                               $this->dieWithError(
-                                       [ 'apierror-compare-nosuchtosection', wfEscapeWikiText( $params['tosection'] ) ],
-                                       'nosuchtosection'
-                               );
-                       }
+               if ( !$toRev->audienceCan(
+                       RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
+               ) ) {
+                       $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
                }
 
                // Get the diff
                $context = new DerivativeContext( $this->getContext() );
-               if ( $relRev && $relRev->getTitle() ) {
-                       $context->setTitle( $relRev->getTitle() );
-               } elseif ( $relRev2 && $relRev2->getTitle() ) {
-                       $context->setTitle( $relRev2->getTitle() );
+               if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
+                       $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
+               } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
+                       $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
                } else {
-                       $this->guessTitleAndModel();
-                       if ( $this->guessedTitle ) {
-                               $context->setTitle( $this->guessedTitle );
+                       $guessedTitle = $this->guessTitle();
+                       if ( $guessedTitle ) {
+                               $context->setTitle( $guessedTitle );
                        }
                }
-               $de = $fromContent->getContentHandler()->createDifferenceEngine(
-                       $context,
-                       $fromRev ? $fromRev->getId() : 0,
-                       $toRev ? $toRev->getId() : 0,
-                       /* $rcid = */ null,
-                       /* $refreshCache = */ false,
-                       /* $unhide = */ true
-               );
-               $de->setContent( $fromContent, $toContent );
-               $difftext = $de->getDiffBody();
-               if ( $difftext === false ) {
-                       $this->dieWithError( 'apierror-baddiff' );
+               $de = new DifferenceEngine( $context );
+               $de->setRevisions( $fromRev, $toRev );
+               if ( $params['slots'] === null ) {
+                       $difftext = $de->getDiffBody();
+                       if ( $difftext === false ) {
+                               $this->dieWithError( 'apierror-baddiff' );
+                       }
+               } else {
+                       $difftext = [];
+                       foreach ( $params['slots'] as $role ) {
+                               $difftext[$role] = $de->getDiffBodyForRole( $role );
+                       }
                }
 
                // Fill in the response
                $vals = [];
-               $this->setVals( $vals, 'from', $fromRev );
-               $this->setVals( $vals, 'to', $toRev );
+               $this->setVals( $vals, 'from', $fromValsRev );
+               $this->setVals( $vals, 'to', $toValsRev );
 
                if ( isset( $this->props['rel'] ) ) {
-                       if ( $fromRev ) {
-                               $rev = $fromRev->getPrevious();
+                       if ( !$fromRev instanceof MutableRevisionRecord ) {
+                               $rev = $this->revisionStore->getPreviousRevision( $fromRev );
                                if ( $rev ) {
                                        $vals['prev'] = $rev->getId();
                                }
                        }
-                       if ( $toRev ) {
-                               $rev = $toRev->getNext();
+                       if ( !$toRev instanceof MutableRevisionRecord ) {
+                               $rev = $this->revisionStore->getNextRevision( $toRev );
                                if ( $rev ) {
                                        $vals['next'] = $rev->getId();
                                }
@@ -161,10 +156,18 @@ class ApiComparePages extends ApiBase {
                }
 
                if ( isset( $this->props['diffsize'] ) ) {
-                       $vals['diffsize'] = strlen( $difftext );
+                       $vals['diffsize'] = 0;
+                       foreach ( (array)$difftext as $text ) {
+                               $vals['diffsize'] += strlen( $text );
+                       }
                }
                if ( isset( $this->props['diff'] ) ) {
-                       ApiResult::setContentValue( $vals, 'body', $difftext );
+                       if ( is_array( $difftext ) ) {
+                               ApiResult::setArrayType( $difftext, 'kvp', 'diff' );
+                               $vals['bodies'] = $difftext;
+                       } else {
+                               ApiResult::setContentValue( $vals, 'body', $difftext );
+                       }
                }
 
                // Diffs can be really big and there's little point in having
@@ -174,49 +177,55 @@ class ApiComparePages extends ApiBase {
        }
 
        /**
-        * Guess an appropriate default Title and content model for this request
+        * Load a revision by ID
         *
-        * Fills in $this->guessedTitle based on the first of 'fromrev',
-        * 'fromtitle', 'fromid', 'torev', 'totitle', and 'toid' that's present and
-        * valid.
+        * Falls back to checking the archive table if appropriate.
+        *
+        * @param int $id
+        * @return RevisionRecord|null
+        */
+       private function getRevisionById( $id ) {
+               $rev = $this->revisionStore->getRevisionById( $id );
+               if ( !$rev && $this->getUser()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
+                       // Try the 'archive' table
+                       $arQuery = $this->revisionStore->getArchiveQueryInfo();
+                       $row = $this->getDB()->selectRow(
+                               $arQuery['tables'],
+                               array_merge(
+                                       $arQuery['fields'],
+                                       [ 'ar_namespace', 'ar_title' ]
+                               ),
+                               [ 'ar_rev_id' => $id ],
+                               __METHOD__,
+                               [],
+                               $arQuery['joins']
+                       );
+                       if ( $row ) {
+                               $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
+                               $rev->isArchive = true;
+                       }
+               }
+               return $rev;
+       }
+
+       /**
+        * Guess an appropriate default Title for this request
         *
-        * Fills in $this->guessedModel based on the Revision or Title used to
-        * determine $this->guessedTitle, or the 'fromcontentmodel' or
-        * 'tocontentmodel' parameters if no title was guessed.
+        * @return Title|null
         */
-       private function guessTitleAndModel() {
-               if ( $this->guessed ) {
-                       return;
+       private function guessTitle() {
+               if ( $this->guessedTitle !== false ) {
+                       return $this->guessedTitle;
                }
 
-               $this->guessed = true;
+               $this->guessedTitle = null;
                $params = $this->extractRequestParams();
 
                foreach ( [ 'from', 'to' ] as $prefix ) {
                        if ( $params["{$prefix}rev"] !== null ) {
-                               $revId = $params["{$prefix}rev"];
-                               $rev = Revision::newFromId( $revId );
-                               if ( !$rev ) {
-                                       // Titles of deleted revisions aren't secret, per T51088
-                                       $arQuery = Revision::getArchiveQueryInfo();
-                                       $row = $this->getDB()->selectRow(
-                                               $arQuery['tables'],
-                                               array_merge(
-                                                       $arQuery['fields'],
-                                                       [ 'ar_namespace', 'ar_title' ]
-                                               ),
-                                               [ 'ar_rev_id' => $revId ],
-                                               __METHOD__,
-                                               [],
-                                               $arQuery['joins']
-                                       );
-                                       if ( $row ) {
-                                               $rev = Revision::newFromArchiveRow( $row );
-                                       }
-                               }
+                               $rev = $this->getRevisionById( $params["{$prefix}rev"] );
                                if ( $rev ) {
-                                       $this->guessedTitle = $rev->getTitle();
-                                       $this->guessedModel = $rev->getContentModel();
+                                       $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
                                        break;
                                }
                        }
@@ -238,38 +247,84 @@ class ApiComparePages extends ApiBase {
                        }
                }
 
-               if ( !$this->guessedModel ) {
-                       if ( $this->guessedTitle ) {
-                               $this->guessedModel = $this->guessedTitle->getContentModel();
-                       } elseif ( $params['fromcontentmodel'] !== null ) {
-                               $this->guessedModel = $params['fromcontentmodel'];
-                       } elseif ( $params['tocontentmodel'] !== null ) {
-                               $this->guessedModel = $params['tocontentmodel'];
+               return $this->guessedTitle;
+       }
+
+       /**
+        * Guess an appropriate default content model for this request
+        * @param string $role Slot for which to guess the model
+        * @return string|null Guessed content model
+        */
+       private function guessModel( $role ) {
+               $params = $this->extractRequestParams();
+
+               $title = null;
+               foreach ( [ 'from', 'to' ] as $prefix ) {
+                       if ( $params["{$prefix}rev"] !== null ) {
+                               $rev = $this->getRevisionById( $params["{$prefix}rev"] );
+                               if ( $rev ) {
+                                       if ( $rev->hasSlot( $role ) ) {
+                                               return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
+                                       }
+                               }
+                       }
+               }
+
+               $guessedTitle = $this->guessTitle();
+               if ( $guessedTitle && $role === 'main' ) {
+                       // @todo: Use SlotRoleRegistry and do this for all slots
+                       return $guessedTitle->getContentModel();
+               }
+
+               if ( isset( $params["fromcontentmodel-$role"] ) ) {
+                       return $params["fromcontentmodel-$role"];
+               }
+               if ( isset( $params["tocontentmodel-$role"] ) ) {
+                       return $params["tocontentmodel-$role"];
+               }
+
+               if ( $role === 'main' ) {
+                       if ( isset( $params['fromcontentmodel'] ) ) {
+                               return $params['fromcontentmodel'];
+                       }
+                       if ( isset( $params['tocontentmodel'] ) ) {
+                               return $params['tocontentmodel'];
                        }
                }
+
+               return null;
        }
 
        /**
-        * Get the Revision and Content for one side of the diff
+        * Get the RevisionRecord for one side of the diff
         *
-        * This uses the appropriate set of 'rev', 'id', 'title', 'text', 'pst',
-        * 'contentmodel', and 'contentformat' parameters to determine what content
+        * This uses the appropriate set of parameters to determine what content
         * should be diffed.
         *
         * Returns three values:
-        * - The revision used to retrieve the content, if any
-        * - The content to be diffed
-        * - The revision specified, if any, even if not used to retrieve the
-        *   Content
+        * - A RevisionRecord holding the content
+        * - The revision specified, if any, even if content was supplied
+        * - The revision to pass to setVals(), if any
         *
         * @param string $prefix 'from' or 'to'
         * @param array $params
-        * @return array [ Revision|null, Content, Revision|null ]
+        * @return array [ RevisionRecord|null, RevisionRecord|null, RevisionRecord|null ]
         */
-       private function getDiffContent( $prefix, array $params ) {
+       private function getDiffRevision( $prefix, array $params ) {
+               // Back compat params
+               $this->requireMaxOneParameter( $params, "{$prefix}text", "{$prefix}slots" );
+               $this->requireMaxOneParameter( $params, "{$prefix}section", "{$prefix}slots" );
+               if ( $params["{$prefix}text"] !== null ) {
+                       $params["{$prefix}slots"] = [ 'main' ];
+                       $params["{$prefix}text-main"] = $params["{$prefix}text"];
+                       $params["{$prefix}section-main"] = null;
+                       $params["{$prefix}contentmodel-main"] = $params["{$prefix}contentmodel"];
+                       $params["{$prefix}contentformat-main"] = $params["{$prefix}contentformat"];
+               }
+
                $title = null;
                $rev = null;
-               $suppliedContent = $params["{$prefix}text"] !== null;
+               $suppliedContent = $params["{$prefix}slots"] !== null;
 
                // Get the revision and title, if applicable
                $revId = null;
@@ -308,94 +363,146 @@ class ApiComparePages extends ApiBase {
                        }
                }
                if ( $revId !== null ) {
-                       $rev = Revision::newFromId( $revId );
-                       if ( !$rev && $this->getUser()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
-                               // Try the 'archive' table
-                               $arQuery = Revision::getArchiveQueryInfo();
-                               $row = $this->getDB()->selectRow(
-                                       $arQuery['tables'],
-                                       array_merge(
-                                               $arQuery['fields'],
-                                               [ 'ar_namespace', 'ar_title' ]
-                                       ),
-                                       [ 'ar_rev_id' => $revId ],
-                                       __METHOD__,
-                                       [],
-                                       $arQuery['joins']
-                               );
-                               if ( $row ) {
-                                       $rev = Revision::newFromArchiveRow( $row );
-                                       $rev->isArchive = true;
-                               }
-                       }
+                       $rev = $this->getRevisionById( $revId );
                        if ( !$rev ) {
                                $this->dieWithError( [ 'apierror-nosuchrevid', $revId ] );
                        }
-                       $title = $rev->getTitle();
+                       $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
 
                        // If we don't have supplied content, return here. Otherwise,
                        // continue on below with the supplied content.
                        if ( !$suppliedContent ) {
-                               $content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
-                               if ( !$content ) {
-                                       $this->dieWithError( [ 'apierror-missingcontent-revid', $revId ], 'missingcontent' );
+                               $newRev = $rev;
+
+                               // Deprecated 'fromsection'/'tosection'
+                               if ( isset( $params["{$prefix}section"] ) ) {
+                                       $section = $params["{$prefix}section"];
+                                       $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
+                                       $content = $rev->getContent( 'main', RevisionRecord::FOR_THIS_USER, $this->getUser() );
+                                       if ( !$content ) {
+                                               $this->dieWithError(
+                                                       [ 'apierror-missingcontent-revid-role', $rev->getId(), 'main' ], 'missingcontent'
+                                               );
+                                       }
+                                       $content = $content ? $content->getSection( $section ) : null;
+                                       if ( !$content ) {
+                                               $this->dieWithError(
+                                                       [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
+                                                       "nosuch{$prefix}section"
+                                               );
+                                       }
+                                       $newRev->setContent( 'main', $content );
                                }
-                               return [ $rev, $content, $rev ];
+
+                               return [ $newRev, $rev, $rev ];
                        }
                }
 
                // Override $content based on supplied text
-               $model = $params["{$prefix}contentmodel"];
-               $format = $params["{$prefix}contentformat"];
-
-               if ( !$model && $rev ) {
-                       $model = $rev->getContentModel();
-               }
-               if ( !$model && $title ) {
-                       $model = $title->getContentModel();
-               }
-               if ( !$model ) {
-                       $this->guessTitleAndModel();
-                       $model = $this->guessedModel;
-               }
-               if ( !$model ) {
-                       $model = CONTENT_MODEL_WIKITEXT;
-                       $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
-               }
-
                if ( !$title ) {
-                       $this->guessTitleAndModel();
-                       $title = $this->guessedTitle;
+                       $title = $this->guessTitle();
                }
-
-               try {
-                       $content = ContentHandler::makeContent( $params["{$prefix}text"], $title, $model, $format );
-               } catch ( MWContentSerializationException $ex ) {
-                       $this->dieWithException( $ex, [
-                               'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
+               if ( $rev ) {
+                       $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
+               } else {
+                       $newRev = $this->revisionStore->newMutableRevisionFromArray( [
+                               'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
                        ] );
                }
+               foreach ( $params["{$prefix}slots"] as $role ) {
+                       $text = $params["{$prefix}text-{$role}"];
+                       if ( $text === null ) {
+                               $newRev->removeSlot( $role );
+                               continue;
+                       }
+
+                       $model = $params["{$prefix}contentmodel-{$role}"];
+                       $format = $params["{$prefix}contentformat-{$role}"];
 
-               if ( $params["{$prefix}pst"] ) {
-                       if ( !$title ) {
-                               $this->dieWithError( 'apierror-compare-no-title' );
+                       if ( !$model && $rev && $rev->hasSlot( $role ) ) {
+                               $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
+                       }
+                       if ( !$model && $title && $role === 'main' ) {
+                               // @todo: Use SlotRoleRegistry and do this for all slots
+                               $model = $title->getContentModel();
+                       }
+                       if ( !$model ) {
+                               $model = $this->guessModel( $role );
+                       }
+                       if ( !$model ) {
+                               $model = CONTENT_MODEL_WIKITEXT;
+                               $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
+                       }
+
+                       try {
+                               $content = ContentHandler::makeContent( $text, $title, $model, $format );
+                       } catch ( MWContentSerializationException $ex ) {
+                               $this->dieWithException( $ex, [
+                                       'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
+                               ] );
+                       }
+
+                       if ( $params["{$prefix}pst"] ) {
+                               if ( !$title ) {
+                                       $this->dieWithError( 'apierror-compare-no-title' );
+                               }
+                               $popts = ParserOptions::newFromContext( $this->getContext() );
+                               $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
+                       }
+
+                       $section = $params["{$prefix}section-{$role}"];
+                       if ( $section !== null && $section !== '' ) {
+                               if ( !$rev ) {
+                                       $this->dieWithError( "apierror-compare-no{$prefix}revision" );
+                               }
+                               $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->getUser() );
+                               if ( !$oldContent ) {
+                                       $this->dieWithError(
+                                               [ 'apierror-missingcontent-revid-role', $rev->getId(), wfEscapeWikiText( $role ) ],
+                                               'missingcontent'
+                                       );
+                               }
+                               if ( !$oldContent->getContentHandler()->supportsSections() ) {
+                                       $this->dieWithError( [ 'apierror-sectionsnotsupported', $content->getModel() ] );
+                               }
+                               try {
+                                       $content = $oldContent->replaceSection( $section, $content, '' );
+                               } catch ( Exception $ex ) {
+                                       // Probably a content model mismatch.
+                                       $content = null;
+                               }
+                               if ( !$content ) {
+                                       $this->dieWithError( [ 'apierror-sectionreplacefailed' ] );
+                               }
+                       }
+
+                       // Deprecated 'fromsection'/'tosection'
+                       if ( $role === 'main' && isset( $params["{$prefix}section"] ) ) {
+                               $section = $params["{$prefix}section"];
+                               $content = $content->getSection( $section );
+                               if ( !$content ) {
+                                       $this->dieWithError(
+                                               [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
+                                               "nosuch{$prefix}section"
+                                       );
+                               }
                        }
-                       $popts = ParserOptions::newFromContext( $this->getContext() );
-                       $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
-               }
 
-               return [ null, $content, $rev ];
+                       $newRev->setContent( $role, $content );
+               }
+               return [ $newRev, $rev, null ];
        }
 
        /**
-        * Set value fields from a Revision object
+        * Set value fields from a RevisionRecord object
+        *
         * @param array &$vals Result array to set data into
         * @param string $prefix 'from' or 'to'
-        * @param Revision|null $rev
+        * @param RevisionRecord|null $rev
         */
        private function setVals( &$vals, $prefix, $rev ) {
                if ( $rev ) {
-                       $title = $rev->getTitle();
+                       $title = $rev->getPageAsLinkTarget();
                        if ( isset( $this->props['ids'] ) ) {
                                $vals["{$prefix}id"] = $title->getArticleID();
                                $vals["{$prefix}revid"] = $rev->getId();
@@ -408,41 +515,42 @@ class ApiComparePages extends ApiBase {
                        }
 
                        $anyHidden = false;
-                       if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+                       if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
                                $vals["{$prefix}texthidden"] = true;
                                $anyHidden = true;
                        }
 
-                       if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
+                       if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
                                $vals["{$prefix}userhidden"] = true;
                                $anyHidden = true;
                        }
-                       if ( isset( $this->props['user'] ) &&
-                               $rev->userCan( Revision::DELETED_USER, $this->getUser() )
-                       ) {
-                               $vals["{$prefix}user"] = $rev->getUserText( Revision::RAW );
-                               $vals["{$prefix}userid"] = $rev->getUser( Revision::RAW );
+                       if ( isset( $this->props['user'] ) ) {
+                               $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() );
+                               if ( $user ) {
+                                       $vals["{$prefix}user"] = $user->getName();
+                                       $vals["{$prefix}userid"] = $user->getId();
+                               }
                        }
 
-                       if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
+                       if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
                                $vals["{$prefix}commenthidden"] = true;
                                $anyHidden = true;
                        }
-                       if ( $rev->userCan( Revision::DELETED_COMMENT, $this->getUser() ) ) {
-                               if ( isset( $this->props['comment'] ) ) {
-                                       $vals["{$prefix}comment"] = $rev->getComment( Revision::RAW );
-                               }
-                               if ( isset( $this->props['parsedcomment'] ) ) {
+                       if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) {
+                               $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() );
+                               if ( $comment !== null ) {
+                                       if ( isset( $this->props['comment'] ) ) {
+                                               $vals["{$prefix}comment"] = $comment->text;
+                                       }
                                        $vals["{$prefix}parsedcomment"] = Linker::formatComment(
-                                               $rev->getComment( Revision::RAW ),
-                                               $rev->getTitle()
+                                               $comment->text, Title::newFromLinkTarget( $title )
                                        );
                                }
                        }
 
                        if ( $anyHidden ) {
                                $this->getMain()->setCacheMode( 'private' );
-                               if ( $rev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
+                               if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
                                        $vals["{$prefix}suppressed"] = true;
                                }
                        }
@@ -455,6 +563,12 @@ class ApiComparePages extends ApiBase {
        }
 
        public function getAllowedParams() {
+               $slotRoles = MediaWikiServices::getInstance()->getSlotRoleStore()->getMap();
+               if ( !in_array( 'main', $slotRoles, true ) ) {
+                       $slotRoles[] = 'main';
+               }
+               sort( $slotRoles, SORT_STRING );
+
                // Parameters for the 'from' and 'to' content
                $fromToParams = [
                        'title' => null,
@@ -464,24 +578,58 @@ class ApiComparePages extends ApiBase {
                        'rev' => [
                                ApiBase::PARAM_TYPE => 'integer'
                        ],
-                       'text' => [
-                               ApiBase::PARAM_TYPE => 'text'
+
+                       'slots' => [
+                               ApiBase::PARAM_TYPE => $slotRoles,
+                               ApiBase::PARAM_ISMULTI => true,
+                       ],
+                       'text-{slot}' => [
+                               ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+                               ApiBase::PARAM_TYPE => 'text',
+                       ],
+                       'section-{slot}' => [
+                               ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+                               ApiBase::PARAM_TYPE => 'string',
+                       ],
+                       'contentformat-{slot}' => [
+                               ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+                               ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+                       ],
+                       'contentmodel-{slot}' => [
+                               ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+                               ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
                        ],
-                       'section' => null,
                        'pst' => false,
+
+                       'text' => [
+                               ApiBase::PARAM_TYPE => 'text',
+                               ApiBase::PARAM_DEPRECATED => true,
+                       ],
                        'contentformat' => [
                                ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+                               ApiBase::PARAM_DEPRECATED => true,
                        ],
                        'contentmodel' => [
                                ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
-                       ]
+                               ApiBase::PARAM_DEPRECATED => true,
+                       ],
+                       'section' => [
+                               ApiBase::PARAM_DFLT => null,
+                               ApiBase::PARAM_DEPRECATED => true,
+                       ],
                ];
 
                $ret = [];
                foreach ( $fromToParams as $k => $v ) {
+                       if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
+                               $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots';
+                       }
                        $ret["from$k"] = $v;
                }
                foreach ( $fromToParams as $k => $v ) {
+                       if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
+                               $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots';
+                       }
                        $ret["to$k"] = $v;
                }
 
@@ -508,6 +656,12 @@ class ApiComparePages extends ApiBase {
                        ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
                ];
 
+               $ret['slots'] = [
+                       ApiBase::PARAM_TYPE => $slotRoles,
+                       ApiBase::PARAM_ISMULTI => true,
+                       ApiBase::PARAM_ALL => true,
+               ];
+
                return $ret;
        }
 
index 03d2952..3b305f9 100644 (file)
@@ -534,7 +534,11 @@ class ApiMain extends ApiBase {
                        MediaWikiServices::getInstance()->getStatsdDataFactory()->timing(
                                'api.' . $this->mModule->getModuleName() . '.executeTiming', 1000 * $runTime
                        );
-               } catch ( Exception $e ) {
+               } catch ( Exception $e ) { // @todo Remove this block when HHVM is no longer supported
+                       $this->handleException( $e );
+                       $this->logRequest( microtime( true ) - $t, $e );
+                       $isError = true;
+               } catch ( Throwable $e ) {
                        $this->handleException( $e );
                        $this->logRequest( microtime( true ) - $t, $e );
                        $isError = true;
@@ -558,9 +562,9 @@ class ApiMain extends ApiBase {
         * Handle an exception as an API response
         *
         * @since 1.23
-        * @param Exception $e
+        * @param Exception|Throwable $e
         */
-       protected function handleException( Exception $e ) {
+       protected function handleException( $e ) {
                // T65145: Rollback any open database transactions
                if ( !( $e instanceof ApiUsageException || $e instanceof UsageException ) ) {
                        // UsageExceptions are intentional, so don't rollback if that's the case
@@ -600,7 +604,10 @@ class ApiMain extends ApiBase {
                        foreach ( $ex->getStatusValue()->getErrors() as $error ) {
                                try {
                                        $this->mPrinter->addWarning( $error );
-                               } catch ( Exception $ex2 ) {
+                               } catch ( Exception $ex2 ) { // @todo Remove this block when HHVM is no longer supported
+                                       // WTF?
+                                       $this->addWarning( $error );
+                               } catch ( Throwable $ex2 ) {
                                        // WTF?
                                        $this->addWarning( $error );
                                }
@@ -631,17 +638,20 @@ class ApiMain extends ApiBase {
         * friendly to clients. If it fails, it will rethrow the exception.
         *
         * @since 1.23
-        * @param Exception $e
-        * @throws Exception
+        * @param Exception|Throwable $e
+        * @throws Exception|Throwable
         */
-       public static function handleApiBeforeMainException( Exception $e ) {
+       public static function handleApiBeforeMainException( $e ) {
                ob_start();
 
                try {
                        $main = new self( RequestContext::getMain(), false );
                        $main->handleException( $e );
                        $main->logRequest( 0, $e );
-               } catch ( Exception $e2 ) {
+               } catch ( Exception $e2 ) { // @todo Remove this block when HHVM is no longer supported
+                       // Nope, even that didn't work. Punt.
+                       throw $e;
+               } catch ( Throwable $e2 ) {
                        // Nope, even that didn't work. Punt.
                        throw $e;
                }
@@ -1009,7 +1019,7 @@ class ApiMain extends ApiBase {
         * text around the exception's (presumably English) message as a single
         * error (no warnings).
         *
-        * @param Exception $e
+        * @param Exception|Throwable $e
         * @param string $type 'error' or 'warning'
         * @return ApiMessage[]
         * @since 1.27
@@ -1054,7 +1064,7 @@ class ApiMain extends ApiBase {
 
        /**
         * Replace the result data with the information about an exception.
-        * @param Exception $e
+        * @param Exception|Throwable $e
         * @return string[] Error codes
         */
        protected function substituteResultWithError( $e ) {
@@ -1609,7 +1619,7 @@ class ApiMain extends ApiBase {
        /**
         * Log the preceding request
         * @param float $time Time in seconds
-        * @param Exception|null $e Exception caught while processing the request
+        * @param Exception|Throwable|null $e Exception caught while processing the request
         */
        protected function logRequest( $time, $e = null ) {
                $request = $this->getRequest();
index 241e71a..a1740a9 100644 (file)
        "apihelp-compare-param-fromtitle": "العنوان الأول للمقارنة.",
        "apihelp-compare-param-fromid": "رقم الصفحة الأول للمقارنة.",
        "apihelp-compare-param-fromrev": "أول مراجعة للمقارنة.",
-       "apihelp-compare-param-fromtext": "استخدم هذا النص بدلا من محتوى المراجعة المحدد بواسطة <var>fromtitle</var>، <var>fromid</var> أو <var>fromrev</var>.",
-       "apihelp-compare-param-fromsection": "استخدم فقط القسم المحدد في المحتوى 'من' المحدد.",
        "apihelp-compare-param-frompst": "قم بإجراء تحويل ما قبل الحفظ على <var>fromtext</var>.",
+       "apihelp-compare-param-fromtext": "استخدم هذا النص بدلا من محتوى المراجعة المحدد بواسطة <var>fromtitle</var>، <var>fromid</var> أو <var>fromrev</var>.",
        "apihelp-compare-param-fromcontentmodel": "نموذج محتوى <var>fromtext</var>، إذا لم يتم توفيره، فسيتم تخمينه استنادا إلى الوسائط الأخرى.",
        "apihelp-compare-param-fromcontentformat": "تنسيق محتوى تسلسل <var>fromtext</var>.",
+       "apihelp-compare-param-fromsection": "استخدم فقط القسم المحدد في المحتوى 'من' المحدد.",
        "apihelp-compare-param-totitle": "العنوان الثاني للمقارنة.",
        "apihelp-compare-param-toid": "رقم الصفحة الثاني للمقارنة.",
        "apihelp-compare-param-torev": "المراجعة الثانية للمقارنة.",
        "apihelp-compare-param-torelative": "استخدم مراجعة متعلقة بالمراجعة المحددة من <var>fromtitle</var> أو <var>fromid</var> أو <var>fromrev</var>، سيتم تجاهل جميع خيارات 'إلى' الأخرى.",
-       "apihelp-compare-param-totext": "استخدم هذا النص بدلا من محتوى المراجعة المحدد بواسطة <var>totitle</var> أو <var>toid</var> أو <var>torev</var>.",
-       "apihelp-compare-param-tosection": "استخدم فقط القسم المحدد في المحتوى 'إلى' المحدد.",
        "apihelp-compare-param-topst": "قم بإجراء تحويل ما قبل الحفظ على <var>totext</var>.",
+       "apihelp-compare-param-totext": "استخدم هذا النص بدلا من محتوى المراجعة المحدد بواسطة <var>totitle</var> أو <var>toid</var> أو <var>torev</var>.",
        "apihelp-compare-param-tocontentmodel": "نموذج محتوى <var>totext</var>، إذا لم يتم توفيره، فسيتم تخمينه استنادا إلى الوسائط الأخرى.",
        "apihelp-compare-param-tocontentformat": "تنسيق محتوى تسلسل <var>totext</var>.",
+       "apihelp-compare-param-tosection": "استخدم فقط القسم المحدد في المحتوى 'إلى' المحدد.",
        "apihelp-compare-param-prop": "أية قطعة من المعلومات للحصول عليها.",
        "apihelp-compare-paramvalue-prop-diff": "HTML الفرق.",
        "apihelp-compare-paramvalue-prop-diffsize": "حجم HTML الفرق، بالبايت.",
index 5bdc3c9..c3b75c3 100644 (file)
        "apihelp-query+protectedtitles-paramvalue-prop-level": "Ergänzt den Schutzstatus.",
        "apihelp-query+protectedtitles-example-simple": "Listet geschützte Titel auf.",
        "apihelp-query+querypage-param-limit": "Anzahl der zurückzugebenden Ergebnisse.",
+       "apihelp-query+random-summary": "Ruft einen Satz an zufälligen Seiten ab.",
        "apihelp-query+recentchanges-summary": "Listet die letzten Änderungen auf.",
        "apihelp-query+recentchanges-param-user": "Listet nur Änderungen von diesem Benutzer auf.",
        "apihelp-query+recentchanges-param-excludeuser": "Listet keine Änderungen von diesem Benutzer auf.",
        "apierror-badparameter": "Ungültiger Wert für den Parameter <var>$1</var>.",
        "apierror-badquery": "Ungültige Abfrage.",
        "apierror-cannot-async-upload-file": "Die Parameter <var>async</var> und <var>file</var> können nicht kombiniert werden. Falls du eine asynchrone Verarbeitung deiner hochgeladenen Datei wünschst, lade sie zuerst mithilfe des Parameters <var>stash</var> auf den Speicher hoch. Veröffentliche anschließend die gespeicherte Datei asynchron mithilfe <var>filekey</var> und <var>async</var>.",
+       "apierror-compare-nofromrevision": "Keine Version „from“. <var>fromrev</var>, <var>fromtitle</var> oder <var>fromid</var> angeben.",
+       "apierror-compare-notorevision": "Keine Version „to“. <var>torev</var>, <var>totitle</var> oder <var>toid</var> angeben.",
        "apierror-emptypage": "Das Erstellen neuer leerer Seiten ist nicht erlaubt.",
        "apierror-filedoesnotexist": "Die Datei ist nicht vorhanden.",
        "apierror-import-unknownerror": "Unbekannter Fehler beim Importieren: $1.",
        "apierror-invaliduserid": "Die Benutzerkennung <var>$1</var> ist nicht gültig.",
        "apierror-maxbytes": "Der Parameter <var>$1</var> kann nicht länger sein als {{PLURAL:$2|ein Byte|$2 Bytes}}",
        "apierror-maxchars": "Der Parameter <var>$1</var> kann nicht länger sein als {{PLURAL:$2|ein|$2}} Zeichen",
+       "apierror-missingcontent-revid-role": "Fehlender Inhalt für die Versionskennung $1 für die Rolle $2.",
        "apierror-nosuchsection": "Es gibt keinen Abschnitt $1.",
        "apierror-nosuchuserid": "Es gibt keinen Benutzer mit der Kennung $1.",
        "apierror-offline": "Aufgrund von Problemen bei der Netzwerkverbindung kannst du nicht weitermachen. Stelle sicher, dass du eine funktionierende Internetverbindung hast und versuche es erneut.",
index 3c74f25..e29ddf9 100644 (file)
        "apihelp-compare-param-fromtitle": "First title to compare.",
        "apihelp-compare-param-fromid": "First page ID to compare.",
        "apihelp-compare-param-fromrev": "First revision to compare.",
-       "apihelp-compare-param-fromtext": "Use this text instead of the content of the revision specified by <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>.",
+       "apihelp-compare-param-frompst": "Do a pre-save transform on <var>fromtext-&#x7B;slot}</var>.",
+       "apihelp-compare-param-fromslots": "Override content of the revision specified by <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>.\n\nThis parameter specifies the slots that are to be modified. Use <var>fromtext-&#x7B;slot}</var>, <var>fromcontentmodel-&#x7B;slot}</var>, and <var>fromcontentformat-&#x7B;slot}</var> to specify content for each slot.",
+       "apihelp-compare-param-fromtext-{slot}": "Text of the specified slot. If omitted, the slot is removed from the revision.",
+       "apihelp-compare-param-fromsection-{slot}": "When <var>fromtext-&#x7B;slot}</var> is the content of a single section, this is the section number. It will be merged into the revision specified by <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var> as if for a section edit.",
+       "apihelp-compare-param-fromcontentmodel-{slot}": "Content model of <var>fromtext-&#x7B;slot}</var>. If not supplied, it will be guessed based on the other parameters.",
+       "apihelp-compare-param-fromcontentformat-{slot}": "Content serialization format of <var>fromtext-&#x7B;slot}</var>.",
+       "apihelp-compare-param-fromtext": "Specify <kbd>fromslots=main</kbd> and use <var>fromtext-main</var> instead.",
+       "apihelp-compare-param-fromcontentmodel": "Specify <kbd>fromslots=main</kbd> and use <var>fromcontentmodel-main</var> instead.",
+       "apihelp-compare-param-fromcontentformat": "Specify <kbd>fromslots=main</kbd> and use <var>fromcontentformat-main</var> instead.",
        "apihelp-compare-param-fromsection": "Only use the specified section of the specified 'from' content.",
-       "apihelp-compare-param-frompst": "Do a pre-save transform on <var>fromtext</var>.",
-       "apihelp-compare-param-fromcontentmodel": "Content model of <var>fromtext</var>. If not supplied, it will be guessed based on the other parameters.",
-       "apihelp-compare-param-fromcontentformat": "Content serialization format of <var>fromtext</var>.",
        "apihelp-compare-param-totitle": "Second title to compare.",
        "apihelp-compare-param-toid": "Second page ID to compare.",
        "apihelp-compare-param-torev": "Second revision to compare.",
        "apihelp-compare-param-torelative": "Use a revision relative to the revision determined from <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>. All of the other 'to' options will be ignored.",
-       "apihelp-compare-param-totext": "Use this text instead of the content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.",
-       "apihelp-compare-param-tosection": "Only use the specified section of the specified 'to' content.",
        "apihelp-compare-param-topst": "Do a pre-save transform on <var>totext</var>.",
-       "apihelp-compare-param-tocontentmodel": "Content model of <var>totext</var>. If not supplied, it will be guessed based on the other parameters.",
-       "apihelp-compare-param-tocontentformat": "Content serialization format of <var>totext</var>.",
+       "apihelp-compare-param-toslots": "Override content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.\n\nThis parameter specifies the slots that are to be modified. Use <var>totext-&#x7B;slot}</var>, <var>tocontentmodel-&#x7B;slot}</var>, and <var>tocontentformat-&#x7B;slot}</var> to specify content for each slot.",
+       "apihelp-compare-param-totext-{slot}": "Text of the specified slot. If omitted, the slot is removed from the revision.",
+       "apihelp-compare-param-tosection-{slot}": "When <var>totext-&#x7B;slot}</var> is the content of a single section, this is the section number. It will be merged into the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var> as if for a section edit.",
+       "apihelp-compare-param-toslots": "Specify content to use instead of the content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.\n\nThis parameter specifies the slots that have content. Use <var>totext-&#x7B;slot}</var>, <var>tocontentmodel-&#x7B;slot}</var>, and <var>tocontentformat-&#x7B;slot}</var> to specify content for each slot.",
+       "apihelp-compare-param-totext-{slot}": "Text of the specified slot.",
+       "apihelp-compare-param-tocontentmodel-{slot}": "Content model of <var>totext-&#x7B;slot}</var>. If not supplied, it will be guessed based on the other parameters.",
+       "apihelp-compare-param-tocontentformat-{slot}": "Content serialization format of <var>totext-&#x7B;slot}</var>.",
+       "apihelp-compare-param-totext": "Specify <kbd>toslots=main</kbd> and use <var>totext-main</var> instead.",
+       "apihelp-compare-param-tocontentmodel": "Specify <kbd>toslots=main</kbd> and use <var>tocontentmodel-main</var> instead.",
+       "apihelp-compare-param-tocontentformat": "Specify <kbd>toslots=main</kbd> and use <var>tocontentformat-main</var> instead.",
+       "apihelp-compare-param-tosection": "Only use the specified section of the specified 'to' content.",
        "apihelp-compare-param-prop": "Which pieces of information to get.",
        "apihelp-compare-paramvalue-prop-diff": "The diff HTML.",
        "apihelp-compare-paramvalue-prop-diffsize": "The size of the diff HTML, in bytes.",
        "apihelp-compare-paramvalue-prop-comment": "The comment on the 'from' and 'to' revisions.",
        "apihelp-compare-paramvalue-prop-parsedcomment": "The parsed comment on the 'from' and 'to' revisions.",
        "apihelp-compare-paramvalue-prop-size": "The size of the 'from' and 'to' revisions.",
+       "apihelp-compare-param-slots": "Return individual diffs for these slots, rather than one combined diff for all slots.",
        "apihelp-compare-example-1": "Create a diff between revision 1 and 2.",
 
        "apihelp-createaccount-summary": "Create a new user account.",
        "apierror-compare-no-title": "Cannot pre-save transform without a title. Try specifying <var>fromtitle</var> or <var>totitle</var>.",
        "apierror-compare-nosuchfromsection": "There is no section $1 in the 'from' content.",
        "apierror-compare-nosuchtosection": "There is no section $1 in the 'to' content.",
+       "apierror-compare-nofromrevision": "No 'from' revision. Specify <var>fromrev</var>, <var>fromtitle</var>, or <var>fromid</var>.",
+       "apierror-compare-notorevision": "No 'to' revision. Specify <var>torev</var>, <var>totitle</var>, or <var>toid</var>.",
        "apierror-compare-relative-to-nothing": "No 'from' revision for <var>torelative</var> to be relative to.",
        "apierror-contentserializationexception": "Content serialization failed: $1",
        "apierror-contenttoobig": "The content you supplied exceeds the article size limit of $1 {{PLURAL:$1|kilobyte|kilobytes}}.",
        "apierror-mimesearchdisabled": "MIME search is disabled in Miser Mode.",
        "apierror-missingcontent-pageid": "Missing content for page ID $1.",
        "apierror-missingcontent-revid": "Missing content for revision ID $1.",
+       "apierror-missingcontent-revid-role": "Missing content for revision ID $1 for role $2.",
        "apierror-missingparam-at-least-one-of": "{{PLURAL:$2|The parameter|At least one of the parameters}} $1 is required.",
        "apierror-missingparam-one-of": "{{PLURAL:$2|The parameter|One of the parameters}} $1 is required.",
        "apierror-missingparam": "The <var>$1</var> parameter must be set.",
index a62b2ba..e8d4aac 100644 (file)
        "apihelp-compare-param-fromtitle": "Premier titre à comparer.",
        "apihelp-compare-param-fromid": "ID de la première page à comparer.",
        "apihelp-compare-param-fromrev": "Première révision à comparer.",
-       "apihelp-compare-param-fromtext": "Utiliser ce texte au lieu du contenu de la révision spécifié par <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.",
-       "apihelp-compare-param-fromsection": "N'utiliser que la section spécifiée du contenu 'from'.",
        "apihelp-compare-param-frompst": "Faire une transformation avant enregistrement sur <var>fromtext</var>.",
+       "apihelp-compare-param-fromtext": "Utiliser ce texte au lieu du contenu de la révision spécifié par <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.",
        "apihelp-compare-param-fromcontentmodel": "Modèle de contenu de <var>fromtext</var>. Si non fourni, il sera déduit d’après les autres paramètres.",
        "apihelp-compare-param-fromcontentformat": "Sérialisation du contenu de <var>fromtext</var>.",
+       "apihelp-compare-param-fromsection": "N'utiliser que la section spécifiée du contenu 'from'.",
        "apihelp-compare-param-totitle": "Second titre à comparer.",
        "apihelp-compare-param-toid": "ID de la seconde page à comparer.",
        "apihelp-compare-param-torev": "Seconde révision à comparer.",
        "apihelp-compare-param-torelative": "Utiliser une révision relative à la révision déterminée de <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>. Toutes les autres options 'to' seront ignorées.",
-       "apihelp-compare-param-totext": "Utiliser ce texte au lieu du contenu de la révision spécifié par <var>totitle</var>, <var>toid</var> ou <var>torev</var>.",
-       "apihelp-compare-param-tosection": "N'utiliser que la section spécifiée du contenu 'to'.",
        "apihelp-compare-param-topst": "Faire une transformation avant enregistrement sur <var>totext</var>.",
+       "apihelp-compare-param-totext": "Utiliser ce texte au lieu du contenu de la révision spécifié par <var>totitle</var>, <var>toid</var> ou <var>torev</var>.",
        "apihelp-compare-param-tocontentmodel": "Modèle de contenu de <var>totext</var>. Si non fourni, il sera deviné d’après les autres paramètres.",
        "apihelp-compare-param-tocontentformat": "Format de sérialisation du contenu de <var>totext</var>.",
+       "apihelp-compare-param-tosection": "N'utiliser que la section spécifiée du contenu 'to'.",
        "apihelp-compare-param-prop": "Quelles informations obtenir.",
        "apihelp-compare-paramvalue-prop-diff": "Le diff HTML.",
        "apihelp-compare-paramvalue-prop-diffsize": "La taille du diff HTML en octets.",
index cb018dc..4bfb522 100644 (file)
        "apihelp-compare-param-fromtitle": "כותרת ראשונה להשוואה.",
        "apihelp-compare-param-fromid": "מס׳ זיהוי של הדף הראשון להשוואה.",
        "apihelp-compare-param-fromrev": "גרסה ראשונה להשוואה.",
-       "apihelp-compare-param-fromtext": "להשתמש בטקסט הזה במקום תוכן הגרסה שהוגדרה על־ידי <var dir=\"ltr\">fromtitle</var>, <var dir=\"ltr\">fromid</var> או <var dir=\"ltr\">fromrev</var>.",
-       "apihelp-compare-param-fromsection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'from'.",
        "apihelp-compare-param-frompst": "לעשות התמרה לפני שמירה ב־<var>fromtext</var>.",
+       "apihelp-compare-param-fromtext": "להשתמש בטקסט הזה במקום תוכן הגרסה שהוגדרה על־ידי <var dir=\"ltr\">fromtitle</var>, <var dir=\"ltr\">fromid</var> או <var dir=\"ltr\">fromrev</var>.",
        "apihelp-compare-param-fromcontentmodel": "מודל התוכן של <var>fromtext</var>. אם זה לא סופק, ייעשה ניחוש על סמך פרמטרים אחרים.",
        "apihelp-compare-param-fromcontentformat": "תסדיר הסדרת תוכן של <var>fromtext</var>.",
+       "apihelp-compare-param-fromsection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'from'.",
        "apihelp-compare-param-totitle": "כותרת שנייה להשוואה.",
        "apihelp-compare-param-toid": "מס׳ מזהה של הדף השני להשוואה.",
        "apihelp-compare-param-torev": "גרסה שנייה להשוואה.",
        "apihelp-compare-param-torelative": "להשתמש בגרסה יחסית לגרסה שהוסקה מ<var dir=\"ltr\">fromtitle</var>, <var dir=\"ltr\">fromid</var> או <var dir=\"ltr\">fromrev</var>. לכל אפשריות ה־\"to\" האחרות לא תהיה השפעה.",
-       "apihelp-compare-param-totext": "להשתמש בטקסט הזה במקום התוכן של הגרסה שהוגדר ב־<var dir=\"ltr\">totitle</var>, <var dir=\"ltr\">toid</var> or <var dir=\"ltr\">torev</var>.",
-       "apihelp-compare-param-tosection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'to'.",
        "apihelp-compare-param-topst": "לעשות התמרה לפני שמירה ב־<var>totext</var>.",
+       "apihelp-compare-param-totext": "להשתמש בטקסט הזה במקום התוכן של הגרסה שהוגדר ב־<var dir=\"ltr\">totitle</var>, <var dir=\"ltr\">toid</var> or <var dir=\"ltr\">torev</var>.",
        "apihelp-compare-param-tocontentmodel": "מודל התוכן של <var>totext</var>. אם זה לא סופק, ייעשה ניחוש על סמך פרמטרים אחרים.",
        "apihelp-compare-param-tocontentformat": "תסדיר הסדרת תוכן של <var>fromtext</var>.",
+       "apihelp-compare-param-tosection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'to'.",
        "apihelp-compare-param-prop": "אילו פריטי מידע לקבל.",
        "apihelp-compare-paramvalue-prop-diff": "ה־HTML של ההשוואה.",
        "apihelp-compare-paramvalue-prop-diffsize": "גודל ה־HTML של ההשוואה, בבתים.",
index 399fc1f..0d2d2d1 100644 (file)
@@ -13,7 +13,8 @@
                        "Kkairri",
                        "ネイ",
                        "Omotecho",
-                       "Yusuke1109"
+                       "Yusuke1109",
+                       "Suyama"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|説明文書]]\n* [[mw:Special:MyLanguage/API:FAQ|よくある質問]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api メーリングリスト]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API 告知]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R バグの報告とリクエスト]\n</div>\n<strong>状態:</strong> MediaWiki APIは、積極的にサポートされ、改善された成熟した安定したインターフェースです。避けようとはしていますが、時には壊れた変更が加えられるかもしれません。アップデートの通知を受け取るには、[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce メーリングリスト]に参加してください。\n\n<strong>誤ったリクエスト:</strong> 誤ったリクエストが API に送られた場合、\"MediaWiki-API-Error\" HTTP ヘッダーが送信され、そのヘッダーの値と送り返されるエラーコードは同じ値にセットされます。より詳しい情報は [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Errors and warnings]] を参照してください。\n\n<p class=\"mw-apisandbox-link\"><strong>テスト:</strong> API のリクエストのテストは、[[Special:ApiSandbox]]で簡単に行えます。</p>",
        "apihelp-compare-param-fromtitle": "比較する1つ目のページ名。",
        "apihelp-compare-param-fromid": "比較する1つ目のページID。",
        "apihelp-compare-param-fromrev": "比較する1つ目の版。",
-       "apihelp-compare-param-fromtext": "<var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var> で指定された版の内容の代わりに、このテキストを使用します。",
-       "apihelp-compare-param-fromsection": "'from' の内容のうち指定された節のみを使用します。",
        "apihelp-compare-param-frompst": "<var>fromtext</var>に保存前変換を行います。",
+       "apihelp-compare-param-fromtext": "<var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var> で指定された版の内容の代わりに、このテキストを使用します。",
        "apihelp-compare-param-fromcontentmodel": "<var>fromtext</var>のコンテンツモデル。指定されていない場合は、他のパラメータに基づいて推測されます。",
+       "apihelp-compare-param-fromsection": "'from' の内容のうち指定された節のみを使用します。",
        "apihelp-compare-param-totitle": "比較する2つ目のページ名。",
        "apihelp-compare-param-toid": "比較する2つ目のページID。",
        "apihelp-compare-param-torev": "比較する2つ目の版。",
-       "apihelp-compare-param-tosection": "'to' の内容のうち指定された節のみを使用します。",
        "apihelp-compare-param-topst": "<var>totext</var>に保存前変換を行います。",
        "apihelp-compare-param-tocontentmodel": "<var>totext</var> のコンテンツモデル。指定されていない場合は、他のパラメータに基づいて推測されます。",
+       "apihelp-compare-param-tosection": "'to' の内容のうち指定された節のみを使用します。",
        "apihelp-compare-param-prop": "どの情報を取得するか:",
        "apihelp-compare-paramvalue-prop-diff": "差分HTML。",
        "apihelp-compare-paramvalue-prop-diffsize": "差分HTMLのサイズ (バイト数)。",
        "api-help-permissions": "{{PLURAL:$1|権限}}:",
        "api-help-permissions-granted-to": "{{PLURAL:$1|権限を持つグループ}}: $2",
        "api-help-open-in-apisandbox": "<small>[サンドボックスで開く]</small>",
+       "apierror-botsnotsupported": "この API インターフェースはボットをサポートしていません。",
        "apierror-filedoesnotexist": "ファイルが存在しません。",
        "apierror-invaliduser": "無効なユーザー名「$1」。",
        "apierror-missingparam": "パラメーター <var>$1</var> を設定してください。",
index e37bbbc..247c928 100644 (file)
        "apihelp-compare-param-fromtitle": "비교할 첫 이름.",
        "apihelp-compare-param-fromid": "비교할 첫 문서 ID.",
        "apihelp-compare-param-fromrev": "비교할 첫 판.",
-       "apihelp-compare-param-fromtext": "<var>fromtitle</var>, <var>fromid</var> 또는 <var>fromrev</var>로 지정된 판의 내용 대신 이 텍스트를 사용합니다.",
-       "apihelp-compare-param-fromsection": "지정된 'from' 내용의 지정된 문단만 사용합니다.",
        "apihelp-compare-param-frompst": "<var>fromtext</var>에 사전 저장 변환을 수행합니다.",
+       "apihelp-compare-param-fromtext": "<var>fromtitle</var>, <var>fromid</var> 또는 <var>fromrev</var>로 지정된 판의 내용 대신 이 텍스트를 사용합니다.",
        "apihelp-compare-param-fromcontentmodel": "<var>fromtext</var>의 콘텐츠 모델입니다. 지정하지 않으면 다른 변수를 참고하여 추정합니다.",
        "apihelp-compare-param-fromcontentformat": "<var>fromtext</var>의 콘텐츠 직렬화 포맷입니다.",
+       "apihelp-compare-param-fromsection": "지정된 'from' 내용의 지정된 문단만 사용합니다.",
        "apihelp-compare-param-totitle": "비교할 두 번째 제목.",
        "apihelp-compare-param-toid": "비교할 두 번째 문서 ID.",
        "apihelp-compare-param-torev": "비교할 두 번째 판.",
        "apihelp-compare-param-torelative": "<var>fromtitle</var>, <var>fromid</var> 또는 <var>fromrev</var>에서 결정된 판과 상대적인 판을 사용합니다. 다른 'to' 옵션들은 모두 무시됩니다.",
-       "apihelp-compare-param-totext": "<var>totitle</var>, <var>toid</var> 또는 <var>torev</var>로 지정된 판의 내용 대신 이 텍스트를 사용합니다.",
-       "apihelp-compare-param-tosection": "지정된 'to' 내용의 지정된 문단만 사용합니다.",
        "apihelp-compare-param-topst": "<var>totext</var>에 사전 저장 변환을 수행합니다.",
+       "apihelp-compare-param-totext": "<var>totitle</var>, <var>toid</var> 또는 <var>torev</var>로 지정된 판의 내용 대신 이 텍스트를 사용합니다.",
        "apihelp-compare-param-tocontentmodel": "<var>totext</var>의 콘텐츠 모델입니다. 지정하지 않으면 다른 변수를 참고하여 추정합니다.",
        "apihelp-compare-param-tocontentformat": "<var>totext</var>의 콘텐츠 직렬화 포맷입니다.",
+       "apihelp-compare-param-tosection": "지정된 'to' 내용의 지정된 문단만 사용합니다.",
        "apihelp-compare-param-prop": "가져올 정보입니다.",
        "apihelp-compare-paramvalue-prop-diff": "HTML의 차이입니다.",
        "apihelp-compare-paramvalue-prop-diffsize": "HTML 차이의 크기(바이트 단위)입니다.",
index be6e798..8edddda 100644 (file)
        "apihelp-compare-param-fromtitle": "Første tittel å sammenligne.",
        "apihelp-compare-param-fromid": "Første side-ID å sammenligne.",
        "apihelp-compare-param-fromrev": "Første revisjon å sammenligne.",
-       "apihelp-compare-param-fromtext": "Bruk denne teksten i stedet for innholdet i revisjonen som angis med <var>fromtitle</var>, <var>fromid</var> eller <var>fromrev</var>.",
        "apihelp-compare-param-frompst": "Gjør en transformering av <var>fromtext</var> før lagring.",
+       "apihelp-compare-param-fromtext": "Bruk denne teksten i stedet for innholdet i revisjonen som angis med <var>fromtitle</var>, <var>fromid</var> eller <var>fromrev</var>.",
        "apihelp-compare-param-fromcontentmodel": "Innholdsmodell for <var>fromtext</var>. Om den ikke angis vil den gjettes basert på de andre parameterne.",
        "apihelp-compare-param-fromcontentformat": "Innholdsserialiseringsformat for <var>fromtext</var>.",
        "apihelp-compare-param-totitle": "Andre tittel å sammenligne.",
        "apihelp-compare-param-toid": "Andre side-ID å sammenligne.",
        "apihelp-compare-param-torev": "Andre revisjon å sammenligne.",
        "apihelp-compare-param-torelative": "Bruk en revisjon som er relativ til revisjonen som hentes fra <var>fromtitle</var>, <var>fromid</var> eller <var>fromrev</var>. Alle de andre «to»-alternativene vil ignoreres.",
-       "apihelp-compare-param-totext": "Bruk denne teksten i stedet for innholdet i revisjonen spesifisert av <var>totitle</var>, <var>toid</var> eller <var>torev</var>.",
        "apihelp-compare-param-topst": "Gjør en transformering av <var>totext</var> før lagring.",
+       "apihelp-compare-param-totext": "Bruk denne teksten i stedet for innholdet i revisjonen spesifisert av <var>totitle</var>, <var>toid</var> eller <var>torev</var>.",
        "apihelp-compare-param-tocontentmodel": "Innholdsmodellen til <var>totext</var>. Om denne ikke angis vil den bli gjettet ut fra andre parametere.",
        "apihelp-compare-param-tocontentformat": "Innholdsserialiseringsformat for <var>totext</var>.",
        "apihelp-compare-param-prop": "Hvilke informasjonsdeler som skal hentes.",
index 7378ac4..5d6d431 100644 (file)
        "apihelp-compare-param-fromtitle": "Primeiro título para comparar.",
        "apihelp-compare-param-fromid": "Primeiro ID de página para comparar.",
        "apihelp-compare-param-fromrev": "Primeira revisão para comparar.",
-       "apihelp-compare-param-fromtext": "Use este texto em vez do conteúdo da revisão especificada por <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.",
-       "apihelp-compare-param-fromsection": "Utilizar apenas a secção especificada do conteúdo 'from' especificado.",
        "apihelp-compare-param-frompst": "Faz uma transformação pré-salvar em <var>fromtext</var>.",
+       "apihelp-compare-param-fromtext": "Use este texto em vez do conteúdo da revisão especificada por <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.",
        "apihelp-compare-param-fromcontentmodel": "Modelo de conteúdo de <var>fromtext</var>. Se não for fornecido, será adivinhado com base nos outros parâmetros.",
        "apihelp-compare-param-fromcontentformat": "Formato de serialização de conteúdo de <var>fromtext</var>.",
+       "apihelp-compare-param-fromsection": "Utilizar apenas a secção especificada do conteúdo 'from' especificado.",
        "apihelp-compare-param-totitle": "Segundo título para comparar.",
        "apihelp-compare-param-toid": "Segundo ID de página para comparar.",
        "apihelp-compare-param-torev": "Segunda revisão para comparar.",
        "apihelp-compare-param-torelative": "Use uma revisão relativa à revisão determinada de <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>. Todas as outras opções 'to' serão ignoradas.",
-       "apihelp-compare-param-totext": "Use este texto em vez do conteúdo da revisão especificada por <var>totitle</var>, <var>toid</var> ou <var>torev</var>.",
-       "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.",
        "apihelp-compare-param-topst": "Faz uma transformação pré-salvar em <var>totext</var>.",
+       "apihelp-compare-param-totext": "Use este texto em vez do conteúdo da revisão especificada por <var>totitle</var>, <var>toid</var> ou <var>torev</var>.",
        "apihelp-compare-param-tocontentmodel": "Modelo de conteúdo de <var>totext</var>. Se não for fornecido, será adivinhado com base nos outros parâmetros.",
        "apihelp-compare-param-tocontentformat": "Formato de serialização de conteúdo de <var>totext</var>.",
+       "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.",
        "apihelp-compare-param-prop": "Quais peças de informação incluir.",
        "apihelp-compare-paramvalue-prop-diff": "O dif do HTML.",
        "apihelp-compare-paramvalue-prop-diffsize": "O tamanho do diff HTML, em bytes.",
index 0733a2a..bf4c63e 100644 (file)
        "apihelp-compare-param-fromtitle": "Primeiro título a comparar.",
        "apihelp-compare-param-fromid": "Primeiro identificador de página a comparar.",
        "apihelp-compare-param-fromrev": "Primeira revisão a comparar.",
-       "apihelp-compare-param-fromtext": "Usar este texto em vez do conteúdo da revisão especificada por <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.",
-       "apihelp-compare-param-fromsection": "Utilizar apenas a secção especificada do conteúdo 'from' especificado.",
        "apihelp-compare-param-frompst": "Fazer uma transformação anterior à gravação, de <var>fromtext</var>.",
+       "apihelp-compare-param-fromtext": "Usar este texto em vez do conteúdo da revisão especificada por <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.",
        "apihelp-compare-param-fromcontentmodel": "Modelo de conteúdo de <var>fromtext</var>. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.",
        "apihelp-compare-param-fromcontentformat": "Formato de seriação do conteúdo de <var>fromtext</var>.",
+       "apihelp-compare-param-fromsection": "Utilizar apenas a secção especificada do conteúdo 'from' especificado.",
        "apihelp-compare-param-totitle": "Segundo título a comparar.",
        "apihelp-compare-param-toid": "Segundo identificador de página a comparar.",
        "apihelp-compare-param-torev": "Segunda revisão a comparar.",
        "apihelp-compare-param-torelative": "Usar uma revisão relativa à revisão determinada a partir de <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>. Todas as outras opções 'to' serão ignoradas.",
-       "apihelp-compare-param-totext": "Usar este texto em vez do conteúdo da revisão especificada por <var>totitle</var>, <var>toid</var> ou <var>torev</var>.",
-       "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.",
        "apihelp-compare-param-topst": "Fazer uma transformação anterior à gravação, de <var>totext</var>.",
+       "apihelp-compare-param-totext": "Usar este texto em vez do conteúdo da revisão especificada por <var>totitle</var>, <var>toid</var> ou <var>torev</var>.",
        "apihelp-compare-param-tocontentmodel": "Modelo de conteúdo de <var>totext</var>. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.",
        "apihelp-compare-param-tocontentformat": "Formato de seriação do conteúdo de <var>totext</var>.",
+       "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.",
        "apihelp-compare-param-prop": "As informações que devem ser obtidas.",
        "apihelp-compare-paramvalue-prop-diff": "O HTML da lista de diferenças.",
        "apihelp-compare-paramvalue-prop-diffsize": "O tamanho do HTML da lista de diferenças, em bytes.",
index f158f27..33f6613 100644 (file)
        "apihelp-compare-param-fromtitle": "{{doc-apihelp-param|compare|fromtitle}}",
        "apihelp-compare-param-fromid": "{{doc-apihelp-param|compare|fromid}}",
        "apihelp-compare-param-fromrev": "{{doc-apihelp-param|compare|fromrev}}",
-       "apihelp-compare-param-fromtext": "{{doc-apihelp-param|compare|fromtext}}",
-       "apihelp-compare-param-fromsection": "{{doc-apihelp-param|compare|fromsection}}",
        "apihelp-compare-param-frompst": "{{doc-apihelp-param|compare|frompst}}",
+       "apihelp-compare-param-fromslots": "{{doc-apihelp-param|compare|fromslots}}",
+       "apihelp-compare-param-fromtext-{slot}": "{{doc-apihelp-param|compare|fromtext-&#x7B;slot} }}",
+       "apihelp-compare-param-fromsection-{slot}": "{{doc-apihelp-param|compare|fromsection-&#x7B;slot} }}",
+       "apihelp-compare-param-fromcontentmodel-{slot}": "{{doc-apihelp-param|compare|fromcontentmodel-&#x7B;slot} }}",
+       "apihelp-compare-param-fromcontentformat-{slot}": "{{doc-apihelp-param|compare|fromcontentformat-&#x7B;slot} }}",
+       "apihelp-compare-param-fromtext": "{{doc-apihelp-param|compare|fromtext}}",
        "apihelp-compare-param-fromcontentmodel": "{{doc-apihelp-param|compare|fromcontentmodel}}",
        "apihelp-compare-param-fromcontentformat": "{{doc-apihelp-param|compare|fromcontentformat}}",
+       "apihelp-compare-param-fromsection": "{{doc-apihelp-param|compare|fromsection}}",
        "apihelp-compare-param-totitle": "{{doc-apihelp-param|compare|totitle}}",
        "apihelp-compare-param-toid": "{{doc-apihelp-param|compare|toid}}",
        "apihelp-compare-param-torev": "{{doc-apihelp-param|compare|torev}}",
        "apihelp-compare-param-torelative": "{{doc-apihelp-param|compare|torelative}}",
-       "apihelp-compare-param-totext": "{{doc-apihelp-param|compare|totext}}",
-       "apihelp-compare-param-tosection": "{{doc-apihelp-param|compare|tosection}}",
        "apihelp-compare-param-topst": "{{doc-apihelp-param|compare|topst}}",
+       "apihelp-compare-param-toslots": "{{doc-apihelp-param|compare|toslots}}",
+       "apihelp-compare-param-totext-{slot}": "{{doc-apihelp-param|compare|totext-&#x7B;slot} }}",
+       "apihelp-compare-param-tosection-{slot}": "{{doc-apihelp-param|compare|tosection-&#x7B;slot} }}",
+       "apihelp-compare-param-tocontentmodel-{slot}": "{{doc-apihelp-param|compare|tocontentmodel-&#x7B;slot} }}",
+       "apihelp-compare-param-tocontentformat-{slot}": "{{doc-apihelp-param|compare|tocontentformat-&#x7B;slot} }}",
+       "apihelp-compare-param-totext": "{{doc-apihelp-param|compare|totext}}",
        "apihelp-compare-param-tocontentmodel": "{{doc-apihelp-param|compare|tocontentmodel}}",
        "apihelp-compare-param-tocontentformat": "{{doc-apihelp-param|compare|tocontentformat}}",
+       "apihelp-compare-param-tosection": "{{doc-apihelp-param|compare|tosection}}",
        "apihelp-compare-param-prop": "{{doc-apihelp-param|compare|prop}}",
        "apihelp-compare-paramvalue-prop-diff": "{{doc-apihelp-paramvalue|compare|prop|diff}}",
        "apihelp-compare-paramvalue-prop-diffsize": "{{doc-apihelp-paramvalue|compare|prop|diffsize}}",
        "apihelp-compare-paramvalue-prop-comment": "{{doc-apihelp-paramvalue|compare|prop|comment}}",
        "apihelp-compare-paramvalue-prop-parsedcomment": "{{doc-apihelp-paramvalue|compare|prop|parsedcomment}}",
        "apihelp-compare-paramvalue-prop-size": "{{doc-apihelp-paramvalue|compare|prop|size}}",
+       "apihelp-compare-param-slots": "{{doc-apihelp-param|compare|slots}}",
        "apihelp-compare-example-1": "{{doc-apihelp-example|compare}}",
        "apihelp-createaccount-summary": "{{doc-apihelp-summary|createaccount}}",
        "apihelp-createaccount-param-preservestate": "{{doc-apihelp-param|createaccount|preservestate|info=This message is displayed in addition to {{msg-mw|api-help-authmanagerhelper-preservestate}}.}}",
        "apierror-compare-no-title": "{{doc-apierror}}",
        "apierror-compare-nosuchfromsection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.",
        "apierror-compare-nosuchtosection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.",
+       "apierror-compare-nofromrevision": "{{doc-apierror}}",
+       "apierror-compare-notorevision": "{{doc-apierror}}",
        "apierror-compare-relative-to-nothing": "{{doc-apierror}}",
        "apierror-contentserializationexception": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text, may end with punctuation. Currently this is probably English, hopefully we'll fix that in the future.",
        "apierror-contenttoobig": "{{doc-apierror}}\n\nParameters:\n* $1 - Maximum article size in kilobytes.",
        "apierror-mimesearchdisabled": "{{doc-apierror}}",
        "apierror-missingcontent-pageid": "{{doc-apierror}}\n\nParameters:\n* $1 - Page ID number.",
        "apierror-missingcontent-revid": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number",
+       "apierror-missingcontent-revid-role": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number\n* $2 - Role name",
        "apierror-missingparam-at-least-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names.\n* $2 - Number of parameters.",
        "apierror-missingparam-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names.\n* $2 - Number of parameters.",
        "apierror-missingparam": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
index d712765..df0e018 100644 (file)
        "apihelp-compare-param-fromtitle": "Заголовок первой сравниваемой страницы.",
        "apihelp-compare-param-fromid": "Идентификатор первой сравниваемой страницы.",
        "apihelp-compare-param-fromrev": "Первая сравниваемая версия.",
-       "apihelp-compare-param-fromtext": "Используйте этот текст вместо содержимого версии, заданной <var>fromtitle</var>, <var>fromid</var> или <var>fromrev</var>.",
-       "apihelp-compare-param-fromsection": "Использовать только указанную секцию из содержимого «from».",
        "apihelp-compare-param-frompst": "Выполнить преобразование перед записью правки (PST) над <var>fromtext</var>.",
+       "apihelp-compare-param-fromtext": "Используйте этот текст вместо содержимого версии, заданной <var>fromtitle</var>, <var>fromid</var> или <var>fromrev</var>.",
        "apihelp-compare-param-fromcontentmodel": "Модель содержимого <var>fromtext</var>. Если не задана, будет угадана по другим параметрам.",
        "apihelp-compare-param-fromcontentformat": "Формат сериализации содержимого <var>fromtext</var>.",
+       "apihelp-compare-param-fromsection": "Использовать только указанную секцию из содержимого «from».",
        "apihelp-compare-param-totitle": "Заголовок второй сравниваемой страницы.",
        "apihelp-compare-param-toid": "Идентификатор второй сравниваемой страницы.",
        "apihelp-compare-param-torev": "Вторая сравниваемая версия.",
        "apihelp-compare-param-torelative": "Использовать версию, относящуюся к определённой <var>fromtitle</var>, <var>fromid</var> или <var>fromrev</var>. Все другие опции 'to' будут проигнорированы.",
-       "apihelp-compare-param-totext": "Используйте этот текст вместо содержимого версии, заданной <var>totitle</var>, <var>toid</var> или <var>torev</var>.",
-       "apihelp-compare-param-tosection": "Использовать только указанную секцию из содержимого «to».",
        "apihelp-compare-param-topst": "Выполнить преобразование перед записью правки (PST) над <var>totext</var>.",
+       "apihelp-compare-param-totext": "Используйте этот текст вместо содержимого версии, заданной <var>totitle</var>, <var>toid</var> или <var>torev</var>.",
        "apihelp-compare-param-tocontentmodel": "Модель содержимого <var>totext</var>. Если не задана, будет угадана по другим параметрам.",
        "apihelp-compare-param-tocontentformat": "Формат сериализации содержимого <var>totext</var>.",
+       "apihelp-compare-param-tosection": "Использовать только указанную секцию из содержимого «to».",
        "apihelp-compare-param-prop": "Какую информацию получить.",
        "apihelp-compare-paramvalue-prop-diff": "HTML-код разницы.",
        "apihelp-compare-paramvalue-prop-diffsize": "Размер HTML-кода разницы в байтах.",
index e9078e0..cd1ccc5 100644 (file)
        "apihelp-compare-param-fromtitle": "Перший заголовок для порівняння.",
        "apihelp-compare-param-fromid": "Перший ID сторінки для порівняння.",
        "apihelp-compare-param-fromrev": "Перша версія для порівняння.",
-       "apihelp-compare-param-fromtext": "Використати цей текст замість контенту версії, вказаної через <var>fromtitle</var>, <var>fromid</var> або <var>fromrev</var>.",
-       "apihelp-compare-param-fromsection": "Використовувати лише вказану секцію із заданого вмісту «from».",
        "apihelp-compare-param-frompst": "Зробити трансформацію перед збереженням на <var>fromtext</var>.",
+       "apihelp-compare-param-fromtext": "Використати цей текст замість контенту версії, вказаної через <var>fromtitle</var>, <var>fromid</var> або <var>fromrev</var>.",
        "apihelp-compare-param-fromcontentmodel": "Контентна модель <var>fromtext</var>. Якщо не вказано, буде використано припущення на основі інших параметрів.",
        "apihelp-compare-param-fromcontentformat": "Формат серіалізації контенту <var>fromtext</var>.",
+       "apihelp-compare-param-fromsection": "Використовувати лише вказану секцію із заданого вмісту «from».",
        "apihelp-compare-param-totitle": "Другий заголовок для порівняння.",
        "apihelp-compare-param-toid": "Другий ID сторінки для порівняння.",
        "apihelp-compare-param-torev": "Друга версія для порівняння.",
        "apihelp-compare-param-torelative": "Використати версію, яка стосується версії, визначеної через <var>fromtitle</var>, <var>fromid</var> або <var>fromrev</var>. Усі інші опції 'to' буде проігноровано.",
-       "apihelp-compare-param-totext": "Використати цей текст замість контенту версії, вказаної через <var>totitle</var>, <var>toid</var> або <var>torev</var>.",
-       "apihelp-compare-param-tosection": "Використовувати лише вказану секцію із заданого вмісту «to».",
        "apihelp-compare-param-topst": "Виконати трансформацію перед збереженням на <var>totext</var>.",
+       "apihelp-compare-param-totext": "Використати цей текст замість контенту версії, вказаної через <var>totitle</var>, <var>toid</var> або <var>torev</var>.",
        "apihelp-compare-param-tocontentmodel": "Контентна модель <var>totext</var>. Якщо не вказано, буде використано припущення на основі інших параметрів.",
        "apihelp-compare-param-tocontentformat": "Формат серіалізації контенту <var>totext</var>.",
+       "apihelp-compare-param-tosection": "Використовувати лише вказану секцію із заданого вмісту «to».",
        "apihelp-compare-param-prop": "Які уривки інформації отримати.",
        "apihelp-compare-paramvalue-prop-diff": "HTML різниці версій.",
        "apihelp-compare-paramvalue-prop-diffsize": "Розмір HTML різниці версій, у байтах.",
index 8d618ea..cf29d27 100644 (file)
        "apihelp-compare-param-fromtitle": "要比较的第一个标题。",
        "apihelp-compare-param-fromid": "要比较的第一个页面 ID。",
        "apihelp-compare-param-fromrev": "要比较的第一个修订版本。",
-       "apihelp-compare-param-fromtext": "使用该文本而不是由<var>fromtitle</var>、<var>fromid</var>或<var>fromrev</var>指定的修订版本内容。",
-       "apihelp-compare-param-fromsection": "只使用指定“from”内容的指定章节。",
        "apihelp-compare-param-frompst": "在<var>fromtext</var>执行预保存转变。",
+       "apihelp-compare-param-fromtext": "使用该文本而不是由<var>fromtitle</var>、<var>fromid</var>或<var>fromrev</var>指定的修订版本内容。",
        "apihelp-compare-param-fromcontentmodel": "<var>fromtext</var>的内容模型。如果未指定,这将基于其他参数猜想。",
        "apihelp-compare-param-fromcontentformat": "<var>fromtext</var>的内容序列化格式。",
+       "apihelp-compare-param-fromsection": "只使用指定“from”内容的指定章节。",
        "apihelp-compare-param-totitle": "要比较的第二个标题。",
        "apihelp-compare-param-toid": "要比较的第二个页面 ID。",
        "apihelp-compare-param-torev": "要比较的第二个修订版本。",
        "apihelp-compare-param-torelative": "使用与定义自<var>fromtitle</var>、<var>fromid</var>或<var>fromrev</var>的修订版本相关的修订版本。所有其他“to”的选项将被忽略。",
-       "apihelp-compare-param-totext": "使用该文本而不是由<var>totitle</var>、<var>toid</var>或<var>torev</var>指定的修订版本内容。",
-       "apihelp-compare-param-tosection": "只使用指定“to”内容的指定章节。",
        "apihelp-compare-param-topst": "在<var>totext</var>执行预保存转换。",
+       "apihelp-compare-param-totext": "使用该文本而不是由<var>totitle</var>、<var>toid</var>或<var>torev</var>指定的修订版本内容。",
        "apihelp-compare-param-tocontentmodel": "<var>totext</var>的内容模型。如果未指定,这将基于其他参数猜想。",
        "apihelp-compare-param-tocontentformat": "<var>totext</var>的内容序列化格式。",
+       "apihelp-compare-param-tosection": "只使用指定“to”内容的指定章节。",
        "apihelp-compare-param-prop": "要获取的信息束。",
        "apihelp-compare-paramvalue-prop-diff": "差异HTML。",
        "apihelp-compare-paramvalue-prop-diffsize": "差异HTML的大小(字节)。",
index 1e590c9..82b3ea8 100644 (file)
        "apihelp-query+alldeletedrevisions-param-user": "此列出由該使用者作出的修訂。",
        "apihelp-query+alldeletedrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。",
        "apihelp-query+alldeletedrevisions-param-namespace": "僅列出此命名空間的頁面。",
+       "apihelp-query+allfileusages-param-prefix": "搜尋以此值為開頭的所有檔案標題。",
        "apihelp-query+allfileusages-param-prop": "要包含到的資訊部份:",
        "apihelp-query+allfileusages-paramvalue-prop-title": "添加檔案標題。",
        "apihelp-query+allfileusages-param-limit": "要回傳的項目總數。",
        "apihelp-query+allimages-param-sha1base36": "以 base 36 的圖片 SHA1 雜湊值(使用在 MediaWiki)。",
        "apihelp-query+allimages-param-mime": "所要搜尋的 MIME 類型,例如:<kbd>image/jpeg</kbd>。",
        "apihelp-query+allimages-param-limit": "要回傳的圖片總數。",
+       "apihelp-query+allimages-example-B": "搜尋以字母 <kbd>B</kbd> 為開頭的所有檔案清單。",
+       "apihelp-query+allimages-example-recent": "顯示近期已上傳檔案的清單,類似於 [[Special:NewFiles]]。",
        "apihelp-query+alllinks-param-from": "要起始列舉的連結標題。",
        "apihelp-query+alllinks-param-to": "要終止列舉的連結標題。",
        "apihelp-query+alllinks-param-prop": "要包含的資訊部份:",
        "apihelp-query+alllinks-param-namespace": "要列舉的命名空間。",
        "apihelp-query+alllinks-param-limit": "要回傳的項目總數。",
        "apihelp-query+alllinks-param-dir": "列出時所採用的方向。",
+       "apihelp-query+alllinks-example-unique": "列出唯一的連結標題。",
        "apihelp-query+alllinks-example-unique-generator": "取得所有已連結標題,標記為遺失。",
        "apihelp-query+alllinks-example-generator": "取得包含連結的頁面。",
        "apihelp-query+allmessages-summary": "返回來自該網站的訊息。",
        "apihelp-query+allusers-param-prop": "要包含的資訊部份:",
        "apihelp-query+allusers-paramvalue-prop-rights": "列出使用者所擁有的權限。",
        "apihelp-query+allusers-paramvalue-prop-editcount": "添加使用者的編輯次數。",
+       "apihelp-query+allusers-param-witheditsonly": "僅列出有做過編輯的使用者。",
        "apihelp-query+allusers-example-Y": "列出以<kbd>Y</kbd>開頭的使用者。",
        "apihelp-query+authmanagerinfo-summary": "取得目前身分核對狀態的資訊。",
        "apihelp-query+backlinks-summary": "找出連結至指定頁面的所有頁面。",
        "apihelp-query+blocks-paramvalue-prop-userid": "添加已封鎖使用者的使用者 ID。",
        "apihelp-query+blocks-paramvalue-prop-by": "添加進行封鎖中的使用者之使用者名稱。",
        "apihelp-query+blocks-paramvalue-prop-byid": "添加進行封鎖中的使用者之使用者 ID。",
+       "apihelp-query+blocks-paramvalue-prop-reason": "添加封鎖的原因。",
        "apihelp-query+blocks-example-simple": "列出封鎖。",
        "apihelp-query+blocks-example-users": "列出使用者 <kbd>Alice</kbd> 與 <kbd>Bob</kbd> 的封鎖。",
        "apihelp-query+categories-summary": "列出頁面隸屬的所有分類。",
+       "apihelp-query+categories-param-show": "要顯示出的分類種類。",
        "apihelp-query+categories-param-limit": "要回傳的分類數量。",
        "apihelp-query+categoryinfo-summary": "回傳有關指定分類的資訊。",
        "apihelp-query+categorymembers-summary": "在指定的分類中列出所有頁面。",
+       "apihelp-query+categorymembers-param-prop": "要包含的資訊部份:",
        "apihelp-query+categorymembers-paramvalue-prop-ids": "添加頁面 ID。",
+       "apihelp-query+categorymembers-paramvalue-prop-title": "添加標題與頁面的命名空間 ID。",
        "apihelp-query+categorymembers-param-limit": "回傳的頁面數量上限。",
        "apihelp-query+categorymembers-param-sort": "作為排序順序的屬性。",
        "apihelp-query+categorymembers-param-startsortkey": "請改用 $1starthexsortkey。",
        "apihelp-query+deletedrevs-param-end": "終止列舉的時間戳記。",
        "apihelp-query+deletedrevs-param-from": "在此標題開始列出。",
        "apihelp-query+deletedrevs-param-to": "在此標題停止列出。",
+       "apihelp-query+deletedrevs-param-tag": "僅列出以此標籤所標記的修訂。",
        "apihelp-query+deletedrevs-param-user": "此列出由該使用者作出的修訂。",
        "apihelp-query+deletedrevs-param-excludeuser": "不要列出由該使用者作出的修訂。",
        "apihelp-query+deletedrevs-param-namespace": "僅列出此命名空間的頁面。",
index 2ceda21..891f0fe 100644 (file)
@@ -1014,6 +1014,34 @@ class DifferenceEngine extends ContextSource {
                return $difftext;
        }
 
+       /**
+        * Get the diff table body for one slot, without header
+        *
+        * @param string $role
+        * @return string|false
+        */
+       public function getDiffBodyForRole( $role ) {
+               $diffRenderers = $this->getSlotDiffRenderers();
+               if ( !isset( $diffRenderers[$role] ) ) {
+                       return false;
+               }
+
+               $slotContents = $this->getSlotContents();
+               $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role]['old'],
+                       $slotContents[$role]['new'] );
+               if ( !$slotDiff ) {
+                       return false;
+               }
+
+               if ( $role !== 'main' ) {
+                       // TODO use human-readable role name at least
+                       $slotTitle = $role;
+                       $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
+               }
+
+               return $this->localiseDiff( $slotDiff );
+       }
+
        /**
         * Get a slot header for inclusion in a diff body (as a table row).
         *
index da68a62..a679e45 100644 (file)
@@ -129,7 +129,7 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
                                        $thisAttribs['class'] = 'checkmatrix-forced checkmatrix-forced-on';
                                }
 
-                               $checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs );
+                               $checkbox = $this->getOneCheckboxHTML( $checked, $attribs + $thisAttribs );
 
                                $rowContents .= Html::rawElement(
                                        'td',
@@ -148,24 +148,35 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
                return $html;
        }
 
-       protected function getOneCheckbox( $checked, $attribs ) {
-               if ( $this->mParent instanceof OOUIHTMLForm ) {
-                       return new OOUI\CheckboxInputWidget( [
-                               'name' => "{$this->mName}[]",
-                               'selected' => $checked,
-                       ] + OOUI\Element::configFromHtmlAttributes(
-                               $attribs
-                       ) );
-               } else {
-                       $checkbox = Xml::check( "{$this->mName}[]", $checked, $attribs );
-                       if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
-                               $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
-                                       $checkbox .
-                                       Html::element( 'label', [ 'for' => $attribs['id'] ] ) .
-                                       Html::closeElement( 'div' );
-                       }
-                       return $checkbox;
+       public function getInputOOUI( $value ) {
+               $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] );
+
+               return new MediaWiki\Widget\CheckMatrixWidget(
+                       [
+                               'name' => $this->mName,
+                               'infusable' => true,
+                               'id' => $this->mID,
+                               'rows' => $this->mParams['rows'],
+                               'columns' => $this->mParams['columns'],
+                               'tooltips' => $this->mParams['tooltips'],
+                               'forcedOff' => isset( $this->mParams['force-options-off'] ) ?
+                                       $this->mParams['force-options-off'] : [],
+                               'forcedOn' => isset( $this->mParams['force-options-on'] ) ?
+                                       $this->mParams['force-options-on'] : [],
+                               'values' => $value
+                       ] + OOUI\Element::configFromHtmlAttributes( $attribs )
+               );
+       }
+
+       protected function getOneCheckboxHTML( $checked, $attribs ) {
+               $checkbox = Xml::check( "{$this->mName}[]", $checked, $attribs );
+               if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+                       $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
+                               $checkbox .
+                               Html::element( 'label', [ 'for' => $attribs['id'] ] ) .
+                               Html::closeElement( 'div' );
                }
+               return $checkbox;
        }
 
        protected function isTagForcedOff( $tag ) {
@@ -262,4 +273,12 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
 
                return $res;
        }
+
+       protected function getOOUIModules() {
+               return [ 'mediawiki.widgets.CheckMatrixWidget' ];
+       }
+
+       protected function shouldInfuseOOUI() {
+               return true;
+       }
 }
index 93d44e4..ada4d36 100644 (file)
@@ -19,7 +19,8 @@
                        "Elftrkn",
                        "Vito Genovese",
                        "Incelemeelemani",
-                       "Hedda"
+                       "Hedda",
+                       "By erdo can"
                ]
        },
        "config-desc": "MediaWiki yükleyicisi",
@@ -92,6 +93,7 @@
        "config-using-uri": "Sunucu URLsi olarak \"<nowiki>$1$2</nowiki>\" kullanılıyor.",
        "config-uploads-not-safe": "<strong>Uyarı:</strong> Yüklemeler için varsayılan dizininiz <code>$1</code>, rastgele komut dosyalarının yürütülmesine karşı savunmasızdır.\nMediaWiki, karşıya yüklenen tüm dosyaları güvenlik tehditlerine karşı denetlese de, yüklemeleri etkinleştirmeden önce [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security bu güvenlik açığını kapatmanız] önemle tavsiye edilir.",
        "config-no-cli-uploads-check": "<strong>Uyarı:</strong> Yüklemeler için varsayılan dizininiz (<code>$1</code>), CLI yüklemesi sırasında rastgele kod yürütme güvenlik açığı açısından denetlenmez.",
+       "config-brokenlibxml": "Sisteminizde, \"buggy\" olan ve MediaWiki ve diğer web uygulamalarında gizli veri bozulmasına neden olabilecek PHP ve libxml2 sürümlerinin bir kombinasyonu vardır.\nLibxml2 2.7.3 veya sonraki bir sürüme yükseltin ([https://bugs.php.net/bug.php?id=45996 PHP ile dosyalanmış hata]).\nKurulum iptal edildi.",
        "config-db-type": "Veritabanı tipi:",
        "config-db-host": "Veritabanı sunucusu:",
        "config-db-host-help": "Veritabanı sunucunuz farklı bir sunucu üzerinde ise, ana bilgisayar adını veya IP adresini buraya girin.\n\nPaylaşılan ağ barındırma hizmeti kullanıyorsanız, barındırma sağlayıcınız size doğru bir ana bilgisayar adını kendi belgelerinde vermiştir.\n\nEğer MySQL kullanan bir Windows sunucusuna yükleme yapıyorsanız, sunucu adı olarak \"localhost\" kullanırsanız çalışmayabilir. Çalışmazsa, yerel IP adresi için \"127.0.0.1\" deneyin.\n\nPostgreSQL kullanıyorsanız, bu alanı bir Unix soketi ile bağlanmak için boş bırakın.",
        "config-extension-link": "Vikinizin [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions eklentileri] desteklediğini biliyor musunuz?\n\n[https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category Eklentileri kategorilerine göre] inceleyebilir ya da tüm eklentilerin listesini görmek için [https://www.mediawiki.org/wiki/Extension_Matrix Eklenti Matrisine] bakabilirsiniz.",
        "config-skins-screenshots": "$1 (ekran görüntüleri: $2)",
        "config-screenshot": "ekran görüntüsü",
-       "mainpagetext": "'''MediaWiki başarı ile kuruldu.'''",
-       "mainpagedocfooter": "Viki yazılımının kullanımı hakkında bilgi almak için [https://meta.wikimedia.org/wiki/Help:Contents kullanıcı rehberine] bakınız.\n\n== Yeni Başlayanlar ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Yapılandırma ayarlarının listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki SSS]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Kendi diliniz için MediaWiki yerelleştirmesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Kendi vikinizde spam ile nasıl savaşılacağını öğrennin]"
+       "mainpagetext": "<strong>MediaWiki başarı ile kuruldu.</strong>",
+       "mainpagedocfooter": "Viki yazılımının kullanımı hakkında bilgi almak için [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents kullanıcı rehberine] bakınız.\n\n== Yeni Başlayanlar ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Yapılandırma ayarlarının listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki SSS]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Kendi diliniz için MediaWiki yerelleştirmesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Kendi vikinizde spam ile nasıl savaşılacağını öğrennin]"
 }
index 00d2028..321424f 100644 (file)
@@ -61,7 +61,7 @@ class TempFSFile extends FSFile {
                        if ( !is_string( $tmpDirectory ) ) {
                                $tmpDirectory = self::getUsableTempDirectory();
                        }
-                       $path = wfTempDir() . '/' . $prefix . $hex . $ext;
+                       $path = $tmpDirectory . '/' . $prefix . $hex . $ext;
                        Wikimedia\suppressWarnings();
                        $newFileHandle = fopen( $path, 'x' );
                        Wikimedia\restoreWarnings();
index fbc3be9..00b4130 100644 (file)
@@ -938,10 +938,6 @@ class LoadBalancer implements ILoadBalancer {
                        $server = $this->servers[$i];
                        $server['serverIndex'] = $i;
                        $server['autoCommitOnly'] = $autoCommit;
-                       if ( $this->localDomain->getDatabase() !== null ) {
-                               // Use the local domain table prefix if the local domain is specified
-                               $server['tablePrefix'] = $this->localDomain->getTablePrefix();
-                       }
                        $conn = $this->reallyOpenConnection( $server, $this->localDomain );
                        $host = $this->getServerName( $i );
                        if ( $conn->isOpen() ) {
@@ -1037,7 +1033,6 @@ class LoadBalancer implements ILoadBalancer {
                                $this->errorConnection = $conn;
                                $conn = false;
                        } else {
-                               $conn->tablePrefix( $prefix ); // as specified
                                // Note that if $domain is an empty string, getDomainID() might not match it
                                $this->conns[$connInUseKey][$i][$conn->getDomainID()] = $conn;
                                $this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" );
@@ -1081,20 +1076,20 @@ class LoadBalancer implements ILoadBalancer {
         * Returns a Database object whether or not the connection was successful.
         *
         * @param array $server
-        * @param DatabaseDomain $domainOverride Use an unspecified domain to not select any database
+        * @param DatabaseDomain $domain Domain the connection is for, possibly unspecified
         * @return Database
         * @throws DBAccessError
         * @throws InvalidArgumentException
         */
-       protected function reallyOpenConnection( array $server, DatabaseDomain $domainOverride ) {
+       protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
                if ( $this->disabled ) {
                        throw new DBAccessError();
                }
 
-               // Handle $domainOverride being a specified or an unspecified domain
-               if ( $domainOverride->getDatabase() === null ) {
-                       // Normally, an RDBMS requires a DB name specified on connection and the $server
-                       // configuration array is assumed to already specify an appropriate DB name.
+               if ( $domain->getDatabase() === null ) {
+                       // The database domain does not specify a DB name and some database systems require a
+                       // valid DB specified on connection. The $server configuration array contains a default
+                       // DB name to use for connections in such cases.
                        if ( $server['type'] === 'mysql' ) {
                                // For MySQL, DATABASE and SCHEMA are synonyms, connections need not specify a DB,
                                // and the DB name in $server might not exist due to legacy reasons (the default
@@ -1102,10 +1097,16 @@ class LoadBalancer implements ILoadBalancer {
                                $server['dbname'] = null;
                        }
                } else {
-                       $server['dbname'] = $domainOverride->getDatabase();
-                       $server['schema'] = $domainOverride->getSchema();
+                       $server['dbname'] = $domain->getDatabase();
+               }
+
+               if ( $domain->getSchema() !== null ) {
+                       $server['schema'] = $domain->getSchema();
                }
 
+               // It is always possible to connect with any prefix, even the empty string
+               $server['tablePrefix'] = $domain->getTablePrefix();
+
                // Let the handle know what the cluster master is (e.g. "db1052")
                $masterName = $this->getServerName( $this->getWriterIndex() );
                $server['clusterMasterHost'] = $masterName;
index 2457fe8..99ffcd2 100644 (file)
@@ -388,6 +388,9 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                if ( $context->getDebug() ) {
                        $mwLoaderCode .= file_get_contents( "$IP/resources/src/startup/mediawiki.log.js" );
                }
+               if ( $this->getConfig()->get( 'ResourceLoaderEnableJSProfiler' ) ) {
+                       $mwLoaderCode .= file_get_contents( "$IP/resources/src/startup/profiler.js" );
+               }
 
                $mapToJson = function ( $value ) {
                        $value = FormatJson::encode( $value, ResourceLoader::inDebugMode(), FormatJson::ALL_OK );
@@ -397,9 +400,23 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                };
 
                // Perform replacements for mediawiki.js
-               $mwLoaderCode = strtr( $mwLoaderCode, [
+               $mwLoaderPairs = [
                        '$VARS.baseModules' => $mapToJson( $this->getBaseModules() ),
-               ] );
+               ];
+               $profilerStubs = [
+                       '$CODE.profileExecuteStart();' => 'mw.loader.profiler.onExecuteStart( module );',
+                       '$CODE.profileExecuteEnd();' => 'mw.loader.profiler.onExecuteEnd( module );',
+                       '$CODE.profileScriptStart();' => 'mw.loader.profiler.onScriptStart( module );',
+                       '$CODE.profileScriptEnd();' => 'mw.loader.profiler.onScriptEnd( module );',
+               ];
+               if ( $this->getConfig()->get( 'ResourceLoaderEnableJSProfiler' ) ) {
+                       // When profiling is enabled, insert the calls.
+                       $mwLoaderPairs += $profilerStubs;
+               } else {
+                       // When disabled (by default), insert nothing.
+                       $mwLoaderPairs += array_fill_keys( array_keys( $profilerStubs ), '' );
+               }
+               $mwLoaderCode = strtr( $mwLoaderCode, $mwLoaderPairs );
 
                // Perform replacements for startup.js
                $pairs = array_map( $mapToJson, [
@@ -434,13 +451,15 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
        public function getDefinitionSummary( ResourceLoaderContext $context ) {
                global $IP;
                $summary = parent::getDefinitionSummary( $context );
-               $summary[] = [
-                       // Detect changes to variables exposed in mw.config (T30899).
+               $startup = [
+                       // getScript() exposes these variables to mw.config (T30899).
                        'vars' => $this->getConfigSettings( $context ),
-                       // Changes how getScript() creates mw.Map for mw.config
+                       // getScript() uses this to decide how configure mw.Map for mw.config.
                        'wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
-                       // Detect changes to the module registrations
+                       // Detect changes to the module registrations output by getScript().
                        'moduleHashes' => $this->getAllModuleHashes( $context ),
+                       // Detect changes to base modules listed by getScript().
+                       'baseModules' => $this->getBaseModules(),
 
                        'fileHashes' => [
                                $this->safeFileHash( "$IP/resources/src/startup/startup.js" ),
@@ -448,6 +467,13 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                                $this->safeFileHash( "$IP/resources/src/startup/mediawiki.requestIdleCallback.js" ),
                        ],
                ];
+               if ( $context->getDebug() ) {
+                       $startup['fileHashes'][] = $this->safeFileHash( "$IP/resources/src/startup/mediawiki.log.js" );
+               }
+               if ( $this->getConfig()->get( 'ResourceLoaderEnableJSProfiler' ) ) {
+                       $startup['fileHashes'][] = $this->safeFileHash( "$IP/resources/src/startup/profiling.js" );
+               }
+               $summary[] = $startup;
                return $summary;
        }
 
index aa757e6..bc24d26 100644 (file)
@@ -333,7 +333,7 @@ class UsersPager extends AlphabeticPager {
                Hooks::run( 'SpecialListusersHeaderForm', [ $this, &$beforeSubmitButtonHookOut ] );
 
                if ( $beforeSubmitButtonHookOut !== '' ) {
-                       $formDescriptior[ 'beforeSubmitButtonHookOut' ] = [
+                       $formDescriptor[ 'beforeSubmitButtonHookOut' ] = [
                                'class' => HTMLInfoField::class,
                                'raw' => true,
                                'default' => $beforeSubmitButtonHookOut
@@ -349,7 +349,7 @@ class UsersPager extends AlphabeticPager {
                Hooks::run( 'SpecialListusersHeader', [ $this, &$beforeClosingFieldsetHookOut ] );
 
                if ( $beforeClosingFieldsetHookOut !== '' ) {
-                       $formDescriptior[ 'beforeClosingFieldsetHookOut' ] = [
+                       $formDescriptor[ 'beforeClosingFieldsetHookOut' ] = [
                                'class' => HTMLInfoField::class,
                                'raw' => true,
                                'default' => $beforeClosingFieldsetHookOut
diff --git a/includes/widget/CheckMatrixWidget.php b/includes/widget/CheckMatrixWidget.php
new file mode 100644 (file)
index 0000000..7783f31
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+
+namespace MediaWiki\Widget;
+
+/**
+ * Check matrix widget. Displays a matrix of checkboxes for given options
+ *
+ * @copyright 2018 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license MIT
+ */
+class CheckMatrixWidget extends \OOUI\Widget {
+
+       protected $name = '';
+       protected $columns = [];
+       protected $rows = [];
+       protected $tooltips = [];
+       protected $values = [];
+       protected $forcedOn = [];
+       protected $forcedOff = [];
+
+       /**
+        * CheckMatrixWidget constructor
+        *
+        * Operates similarly to MultiSelectWidget, but instead of using an array of
+        * options, uses an array of rows and an array of columns to dynamically
+        * construct a matrix of options. The tags used to identify a particular cell
+        * are of the form "columnName-rowName"
+        *
+        * @param array $config Configuration array with the following options:
+        *   - columns
+        *     - Required list of columns in the matrix.
+        *   - rows
+        *     - Required list of rows in the matrix.
+        *   - force-options-on
+        *     - Accepts array of column-row tags to be displayed as enabled but unavailable to change
+        *   - force-options-off
+        *     - Accepts array of column-row tags to be displayed as disabled but unavailable to change.
+        *   - tooltips
+        *     - Optional array mapping row label to tooltip content
+        *   - tooltip-class
+        *     - Optional CSS class used on tooltip container span. Defaults to mw-icon-question.
+        */
+       public function __construct( array $config = [] ) {
+               // Configuration initialization
+
+               parent::__construct( $config );
+
+               $this->name = isset( $config['name'] ) ?
+                       $config[ 'name' ] : null;
+               $this->id = isset( $config['id'] ) ?
+                       $config['id'] : null;
+
+               // Properties
+               $this->rows = isset( $config['rows'] ) ?
+                       $config['rows'] : [];
+               $this->columns = isset( $config['columns'] ) ?
+                       $config['columns'] : [];
+               $this->tooltips = isset( $config['tooltips'] ) ?
+                       $config['tooltips'] : [];
+
+               $this->values = isset( $config['values'] ) ?
+                       $config['values'] : [];
+
+               $this->forcedOn = isset( $config['forcedOn'] ) ?
+                       $config['forcedOn'] : [];
+               $this->forcedOff = isset( $config['forcedOff'] ) ?
+                       $config['forcedOff'] : [];
+
+               // Build the table
+               $table = new \OOUI\Tag( 'table' );
+               $tr = new \OOUI\Tag( 'tr' );
+               // Build the header
+               $tr->appendContent( $this->getCellTag( "\u{00A0}" ) );
+               foreach ( $this->columns as $columnLabel => $columnTag ) {
+                       $tr->appendContent(
+                               $this->getCellTag( $columnLabel )
+                       );
+               }
+               $table->appendContent( $tr );
+
+               // Build the options matrix
+               foreach ( $this->rows as $rowLabel => $rowTag ) {
+                       $table->appendContent(
+                               $this->getTableRow( $rowLabel, $rowTag )
+                       );
+               }
+
+               // Initialization
+               $this->addClasses( [ 'mw-widget-checkMatrixWidget' ] );
+               $this->appendContent( $table );
+       }
+
+       /**
+        * Get a formatted table row for the option, with
+        * a checkbox widget.
+        *
+        * @param  string $label Row label
+        * @param  string $tag   Row tag name
+        * @return \OOUI\Tag The resulting table row
+        */
+       private function getTableRow( $label, $tag ) {
+               $row = new \OOUI\Tag( 'tr' );
+               $tooltip = $this->getTooltip( $label );
+               $labelFieldConfig = $tooltip ? [ 'help' => $tooltip ] : [];
+               // Build label cell
+               $labelField = new \OOUI\FieldLayout(
+                       new \OOUI\Widget(), // Empty widget, since we don't have the checkboxes here
+                       [
+                               'label' => $label,
+                               'align' => 'inline',
+                       ] + $labelFieldConfig
+               );
+               $row->appendContent( $this->getCellTag( $labelField ) );
+
+               // Build checkbox column cells
+               foreach ( $this->columns as $columnTag ) {
+                       $thisTag = "$columnTag-$tag";
+
+                       // Construct a checkbox
+                       $checkbox = new \OOUI\CheckboxInputWidget( [
+                               'value' => $thisTag,
+                               'name' => $this->name ? "{$this->name}[]" : null,
+                               'id' => $this->id ? "{$this->id}-$thisTag" : null,
+                               'selected' => $this->isTagChecked( $thisTag ),
+                               'disabled' => $this->isTagDisabled( $thisTag ),
+                       ] );
+
+                       $row->appendContent( $this->getCellTag( $checkbox ) );
+               }
+               return $row;
+       }
+
+       /**
+        * Get an individual cell tag with requested content
+        *
+        * @param  string $content Content for the <td> cell
+        * @return \OOUI\Tag Resulting cell
+        */
+       private function getCellTag( $content ) {
+               $cell = new \OOUI\Tag( 'td' );
+               $cell->appendContent( $content );
+               return $cell;
+       }
+
+       /**
+        * Check whether the given tag's checkbox should
+        * be checked
+        *
+        * @param  string $tagName Tag name
+        * @return boolean Tag should be checked
+        */
+       private function isTagChecked( $tagName ) {
+               // If the tag is in the value list
+               return in_array( $tagName, (array)$this->values, true ) ||
+                       // Or if the tag is forced on
+                       in_array( $tagName, (array)$this->forcedOn, true );
+       }
+
+       /**
+        * Check whether the given tag's checkbox should
+        * be disabled
+        *
+        * @param  string $tagName Tag name
+        * @return boolean Tag should be disabled
+        */
+       private function isTagDisabled( $tagName ) {
+               return (
+                       // If the entire widget is disabled
+                       $this->isDisabled() ||
+                       // If the tag is 'forced on' or 'forced off'
+                       in_array( $tagName, (array)$this->forcedOn, true ) ||
+                       in_array( $tagName, (array)$this->forcedOff, true )
+               );
+       }
+
+       /**
+        * Get the tooltip help associated with this row
+        *
+        * @param  string $label Label name
+        * @return string Tooltip. Null if none is available.
+        */
+       private function getTooltip( $label ) {
+               return isset( $this->tooltips[ $label ] ) ?
+                       $this->tooltips[ $label ] : null;
+       }
+
+       protected function getJavaScriptClassName() {
+               return 'mw.widgets.CheckMatrixWidget';
+       }
+
+       public function getConfig( &$config ) {
+               $config += [
+                       'name' => $this->name,
+                       'id' => $this->id,
+                       'rows' => $this->rows,
+                       'columns' => $this->columns,
+                       'tooltips' => $this->tooltips,
+                       'forcedOff' => $this->forcedOff,
+                       'forcedOn' => $this->forcedOn,
+                       'values' => $this->values,
+               ];
+               return parent::getConfig( $config );
+       }
+}
index 0c51bfe..d8757d3 100644 (file)
        "upload-form-label-not-own-work-message-generic-foreign": "Калі вы ня можаце загрузіць гэты файл паводле правілаў агульнага сховішча, калі ласка, закрыйце гэты дыялёг і паспрабуйце іншы мэтад.",
        "upload-form-label-not-own-work-local-generic-foreign": "Вы можаце паспрабаваць скарыстацца [[Special:Upload|старонкай загрузкі {{GRAMMAR:родны|{{SITENAME}}}}]], калі гэты файл можна туды загрузіць згодна з правіламі.",
        "backend-fail-stream": "Немагчыма накіраваць файл $1.",
-       "backend-fail-backup": "Немагчыма зрабіць рэзэрвовую копію файла $1.",
+       "backend-fail-backup": "Немагчыма зрабіць рэзэрвовую копію файлу «$1».",
        "backend-fail-notexists": "Файл $1 не існуе.",
        "backend-fail-hashes": "Немагчыма атрымаць хэшы файлаў для параўнаньня.",
        "backend-fail-notsame": "Неідэнтыфікаваны файл ужо існуе $1.",
index 3e02cea..7d39af6 100644 (file)
        "cascadeprotected": "Тази страница е защитена против редактиране, защото е включена в {{PLURAL:$1|следната страница, която от своя страна има|следните страници, които от своя страна имат}} „каскадна“ защита:\n$2",
        "namespaceprotected": "Нямате права за редактиране на страници в именно пространство <strong>$1</strong>.",
        "customcssprotected": "Нямате права за редактиране на тази CSS страница, защото тя съдържа чужди потребителски настройки.",
+       "customjsonprotected": "Нямате права за редактиране на тази JSON страница, защото тя съдържа чужди потребителски настройки.",
        "customjsprotected": "Нямате права за редактиране на тази JavaScript страница, тъй като съдържа чужди потребителски настройки.",
        "mycustomcssprotected": "Нямате права за редактиране на тази CSS страница.",
+       "mycustomjsonprotected": "Нямате права за редактиране на тази JSON страница.",
        "mycustomjsprotected": "Нямате права за редактиране на тази JavaScript страница.",
        "myprivateinfoprotected": "Нямате права да редактирате личната си информация.",
        "mypreferencesprotected": "Нямате права да редактирате настройките си.",
index 822a79f..fc961f7 100644 (file)
        "cascadeprotected": "Uređivanje ove stranice zabranjeno je jer se koristi u {{PLURAL:$1|sljedećoj stranici, koja je zaštićena|sljedećim stranicama, koje su zaštićene}} prenosivom zaštitom:\n$2",
        "namespaceprotected": "Vi nemate dozvulu da mijenjate stranicu '''$1'''.",
        "customcssprotected": "Nemate dozvolu za mijenjanje ove CSS stranice jer sadrži osobne postavke nekog drugog korisnika.",
+       "customjsonprotected": "Nemate dozvolu za mijenjanje ove JSON stranice jer sadrži osobne postavke nekog drugog korisnika.",
        "customjsprotected": "Nemate dozvolu za mijenjanje ove JavaScript stranice jer sadrži osobne postavke nekog drugog korisnika.",
        "mycustomcssprotected": "Nemate dozvolu da uređujete ovu CSS stranicu.",
+       "mycustomjsonprotected": "Nemate dozvolu da uređujete ovu JSON stranicu.",
        "mycustomjsprotected": "Nemate dozvolu da uređujete ovu stranicu sa JavaScriptom.",
        "myprivateinfoprotected": "Nemate dozvolu da uređujete svoje privatne informacije.",
        "mypreferencesprotected": "Nemate dozvolu da uređujete svoje postavke.",
index e9577e1..f5c3f74 100644 (file)
        "grouppage-bureaucrat": "{{ns:project}}:Buròcrates",
        "grouppage-suppress": "{{ns:project}}:Supressors de Flow",
        "right-read": "Llegir pàgines",
-       "right-edit": "Modifica les pàgines",
+       "right-edit": "Modificar les pàgines",
        "right-createpage": "Crear pàgines (que no són de discussió)",
        "right-createtalk": "Crear pàgines de discussió",
        "right-createaccount": "Crear nous comptes",
index 351e40e..c6142e4 100644 (file)
        "botpasswords-label-appid": "Ботан цӀе:",
        "botpasswords-label-create": "Кхолла",
        "botpasswords-label-update": "Карлаяккха",
-       "botpasswords-label-cancel": "Юхаяккха",
+       "botpasswords-label-cancel": "Юхаяккхар",
        "botpasswords-label-delete": "ДӀаяккхар",
        "botpasswords-label-resetpassword": "Пароль кхоссар",
        "botpasswords-label-grants": "Лелош йолу шоралаш:",
        "rcfilters-savedqueries-remove": "ДӀаяккха",
        "rcfilters-savedqueries-new-name-label": "ЦӀе",
        "rcfilters-savedqueries-apply-label": "Ӏалашде нисъяр",
-       "rcfilters-savedqueries-cancel-label": "ЦаоÑ\8cÑ\88Ñ\83",
+       "rcfilters-savedqueries-cancel-label": "ЮÑ\85аÑ\8fккÑ\85аÑ\80",
        "rcfilters-savedqueries-add-new-title": "Ӏалашде литтар нисъяр",
        "rcfilters-restore-default-filters": "Литтарш Ӏадйитаран кепе меттахӀоттае",
        "rcfilters-clear-all-filters": "Ерриге литтарш цӀанъян",
        "anonymous": "{{PLURAL:$1|1=ЦӀе хьулйина декъашхо|ЦӀе хьулйина декъашхой}} {{grammar:genitive|{{SITENAME}}}}",
        "siteuser": "декъашхо {{grammar:genitive|{{SITENAME}}}} $1",
        "anonuser": "цӀе хьулйина декъашхо {{grammar:genitive|{{SITENAME}}}} $1",
-       "lastmodifiedatby": "Ð¥Ó\80аÑ\80а Ð°Ð³Ó\80о Ñ\82Ó\80аÑ\8cÑ\85Ñ\85Ñ\8cаÑ\80а Ñ\85ийÑ\86ина: $1 $2, Ñ\85ийÑ\86ам Ð±Ð¸Ð½Ð° — $3",
+       "lastmodifiedatby": "Ð¥Ó\80аÑ\80а Ð°Ð³Ó\80о Ñ\82Ó\80аÑ\8cÑ\85Ñ\85Ñ\8cаÑ\80а Ñ\85ийÑ\86ам Ð±Ð¸Ð½Ð°: $1 $2, Ñ\85ийÑ\86аман Ð°Ð²Ñ\82оÑ\80 — $3",
        "othercontribs": "Кхуллуш дакъалецира декъашхоша: $1.",
        "others": "кхин",
        "siteusers": "{{PLURAL:$2|1=декъашхо|декъашхой}} {{grammar:genitive|{{SITENAME}}}} $1",
        "tag-mw-changed-redirect-target": "хийцаран бахьна ду дӀасахьажорг",
        "tag-mw-blank": "цӀанъяр",
        "tag-mw-rollback": "Юхаяккха",
-       "tag-mw-undo": "Ñ\86аоÑ\8cÑ\88Ñ\83",
+       "tag-mw-undo": "Ñ\8eÑ\85аÑ\8fккÑ\85аÑ\80",
        "tags-title": "Билгалонаш",
        "tags-intro": "ХӀокху агӀона чохь гойтуш бу билгалонийн могӀам царца программин латторо билгал доху нисдарш, кхин билгалонийн маьӀна а.",
        "tags-tag": "Билгалона цӀе",
        "feedback-adding": "АгӀона хетарг тӀетохар...",
        "feedback-back": "ЮхагӀо",
        "feedback-bugornote": "Хьайн техникин халонах лаьцна яздан хӀума делахь, дехар до, [$1 хаам бе тхоьга].\nДацахь хьан йиш ю хӀокху атта кепаца «[$3 $2]» агӀонг коммент тӀетоха хьан декъашхочун цӀарца, кхин лелош йолу браузер билгал еш.",
-       "feedback-cancel": "ЦаоÑ\8cÑ\88Ñ\83",
+       "feedback-cancel": "ЮÑ\85аÑ\8fккÑ\85аÑ\80",
        "feedback-close": "Кийчча ю",
        "feedback-message": "Хаам:",
        "feedback-subject": "Тема:",
index 207ced9..ba03773 100644 (file)
        "import-mapping-namespace": "Importovat do jmenného prostoru:",
        "import-mapping-subpage": "Importovat jako podstránky následující stránky:",
        "import-upload-filename": "Jméno souboru:",
+       "import-upload-username-prefix": "Interwiki prefix:",
        "import-assign-known-users": "Přiřazovat editace lokálním uživatelům, pokud zde existuje uživatel s daným jménem",
        "import-comment": "Zdůvodnění:",
        "importtext": "Prosím exportujte soubor ze zdrojové wiki pomocí [[Special:Export|exportního nástroje]].\nUložte jej na svůj disk a nahrajte ho sem.",
        "pagedata-not-acceptable": "Nenalezen odpovídající formát. Podporované MIME typy: $1",
        "pagedata-bad-title": "Neplatný název: $1.",
        "unregistered-user-config": "Z bezpečnostních důvodů nelze načítat uživatelské podstránky s JavaScriptem, CSS nebo JSONem u neregistrovaných uživatelů.",
+       "passwordpolicies": "Zásady pro heslo",
        "passwordpolicies-group": "Skupina",
        "passwordpolicies-policy-minimalpasswordlength": "Heslo musí být alespoň {{PLURAL:$1|$1 znak|$1 znaky|$1 znaků}} dlouhé",
        "passwordpolicies-policy-minimumpasswordlengthtologin": "Pro přihlášení je vyžadováno alespoň {{PLURAL:$1|$1 znak|$1 znaky|$1 znaků}} dlouhé heslo",
index eebf96c..21cc02a 100644 (file)
        "resetpass-submit-loggedin": "Skift adgangskode",
        "resetpass-submit-cancel": "Annuller",
        "resetpass-wrong-oldpass": "Ugyldig midlertidig eller gældende adgangskode.\nDu har muligvis allerede ændret din adgangskode eller bedt om en ny midlertidig kode.",
-       "resetpass-recycled": "Vær venlig at ændre din adgangskode til noget andet end din nuværende adgangskode.",
+       "resetpass-recycled": "Ændr venligst din adgangskode til noget andet end din nuværende adgangskode.",
        "resetpass-temp-emailed": "Du loggede på med en midlertidig kode tilsendt på e-mail.\nFor at afslutte indlogning skal du angive en ny adgangskode her:",
        "resetpass-temp-password": "Midlertidig adgangskode",
        "resetpass-abort-generic": "Ændring af adgangskode er blevet afbrudt af en udvidelse",
        "resetpass-expired": "Din adgangskode er udløbet. Angiv en ny adgangskode for at logge på.",
-       "resetpass-expired-soft": "Din adgangskode er udløbet og skal ændres. Vær venlig at ændre den nu, eller tryk \"{{int:authprovider-resetpass-skip-label}}\" for at ændre den senere.",
-       "resetpass-validity-soft": "Din adgangskode er ikke gyldig:  $1 \n\nVær venlig at ændre den nu, eller tryk \"{{int:authprovider-resetpass-skip-label}}\" for at ændre den senere.",
+       "resetpass-expired-soft": "Din adgangskode er udløbet og skal ændres. Ændr den venligst nu, eller tryk \"{{int:authprovider-resetpass-skip-label}}\" for at ændre den senere.",
+       "resetpass-validity-soft": "Din adgangskode er ikke gyldig:  $1 \n\nVælg venligst en ny adgangskode nu, eller tryk \"{{int:authprovider-resetpass-skip-label}}\" for at ændre den senere.",
        "passwordreset": "Nulstil adgangskode",
        "passwordreset-text-one": "Udfyld denne formular for at nulstille din adgangskode.",
        "passwordreset-text-many": "{{PLURAL:$1|Udfyld et af felterne for at modtage en midlertidig adgangskode via e-mail.}}",
        "previewerrortext": "Der opstod en fejl under forsøget på at lave en forhåndsvisning af dine ændringer.",
        "blockedtitle": "Du eller din IP-adresse er blokeret",
        "blockedtext": "<strong>Dit brugernavn eller din IP-adresse er blevet blokeret.</strong>\n\nBlokeringen er foretaget af $1.\nDen anførte grund er <em>$2</em>.\n\nBlokeringen starter: $8\nBlokeringen udløber: $6\nBlokeringen er rettet mod: $7\n\nDu kan kontakte $1 eller en af de andre [[{{MediaWiki:Grouppage-sysop}}|administratorer]] for at diskutere blokeringen.\nDu kan ikke bruge funktionen \"{{int:emailuser}}\" medmindre der er angivet en gyldig e-mailadresse i dine [[Special:Preferences|kontoindstillinger]], og du ikke er blevet blokeret fra at bruge den.\n\nDin nuværende IP-adresse er $3, og blokerings-id er #$5.\nAngiv venligst alle ovenstående detaljer ved henvendelser om blokeringen.",
-       "autoblockedtext": "Din IP-adresse er blevet blokeret automatisk fordi den blev brugt af en anden bruger som er blevet blokeret af $1.\nBegrundelsen for det er:\n\n:''$2''\n\n* Blokeringsperiodens start: $8\n* Blokeringen udløber: $6\n* Blokeringen er ment for: $7\n\nDu kan kontakte $1 eller en af de andre [[{{MediaWiki:Grouppage-sysop}}|administratorer]] for at diskutere blokeringen.\n\nBemærk at du ikke kan bruge funktionen \"e-mail til denne bruger\" medmindre du har en gyldig e-mailadresse registreret i din [[Special:Preferences|brugerindstilling]], og du ikke er blevet blokeret fra at bruge den.\n\nDin nuværende IP-adresse er $3, og blokerings-id'et er #$5.\nAngiv venligst alle de ovenstående detaljer ved eventuelle henvendelser.",
+       "autoblockedtext": "Din IP-adresse er blevet blokeret automatisk fordi den blev brugt af en anden bruger som er blevet blokeret af $1.\nDen givne begrundelse er:\n\n:<em>$2</em>\n\n* Blokeringsperiodens start: $8\n* Blokeringen udløber: $6\n* Blokeringen er rettet mod: $7\n\nDu kan kontakte $1 eller en af de andre [[{{MediaWiki:Grouppage-sysop}}|administratorer]] for at diskutere blokeringen.\n\nBemærk at du ikke kan bruge funktionen \"{{int:emailuser}}\" medmindre du har en gyldig e-mailadresse registreret i dine [[Special:Preferences|brugerindstillinger]] og du ikke er blevet blokeret fra at bruge den.\n\nDin nuværende IP-adresse er $3, og blokerings-id'et er #$5.\nAngiv venligst alle de ovenstående detaljer ved eventuelle henvendelser.",
        "systemblockedtext": "Dit brugernavn eller din IP-adresse er automatisk blokeret af MediaWiki.\nBegrundelsen for det er:\n\n:<em>$2</em>\n\n* Blokeringsperiodens start: $8\n* Blokeringen udløber: $6\n* Blokeringen er ment for: $7\n\nDin nuværende IP-adresse er $3.\nAngiv venligst alle de ovenstående detaljer ved eventuelle henvendelser.",
        "blockednoreason": "ingen begrundelse givet",
        "whitelistedittext": "Du skal $1 for at kunne redigere sider.",
        "readonlywarning": "<strong>Advarsel: Databasen er låst på grund af vedligeholdelse, så du kan ikke gemme dine ændringer lige nu.</strong>\nDet kan være en god idé at kopiere din tekst over i en tekstfil og gemme den til senere.\n\nAdministratoren, som låste databasen, gav denne forklaring: $1",
        "protectedpagewarning": "'''ADVARSEL: Denne side er skrivebeskyttet, så kun administratorer kan redigere den.'''<br />\nDen seneste logpost vises nedenfor:",
        "semiprotectedpagewarning": "'''Bemærk: Siden er låst, så kun registrerede brugere kan ændre den.'''\n<br />Den seneste logpost vises nedenfor:",
-       "cascadeprotectedwarning": "<strong>Advarsel:</strong> Denne side er blevet beskyttet, så den kun kan ændres af brugere med administratorrettigheder, fordi indholdet er inkluderet i følgende {{PLURAL:$1|side|sider}} med nedarvet sidebeskyttelse:",
+       "cascadeprotectedwarning": "<strong>Advarsel:</strong> Denne side er blevet beskyttet, så kun brugere med [[Special:ListGroupRights|bestemte rettigheder]] kan ændre den, fordi indholdet er inkluderet i følgende {{PLURAL:$1|side|sider}} med nedarvet sidebeskyttelse:",
        "titleprotectedwarning": "ADVARSEL:  Den side er låst så kun [[Special:ListGroupRights|visse brugere]] kan oprette den.'''\n<br />Den seneste logpost vises nedenfor:",
        "templatesused": "{{PLURAL:$1|Skabelon|Skabeloner}} der er brugt på denne side:",
        "templatesusedpreview": "Følgende {{PLURAL:$1|skabelon|skabeloner}} bruges i denne forhåndsvisning:",
        "recentchangesdays": "Antal dage som skal vises i seneste ændringer:",
        "recentchangesdays-max": "(maks. $1 {{PLURAL:$1|dag|dage}})",
        "recentchangescount": "Antal redigeringer som skal vises som standard i sidste ændringer, sidehistorikker og logger:",
-       "prefs-help-recentchangescount": "Det gælder for seneste ændringer, historikker og logger.",
+       "prefs-help-recentchangescount": "Maksimalt antal: 1000",
        "prefs-help-watchlist-token2": "Dette er den hemmelige nøgle til web-feed af din overvågningsliste.\nHvis andre kender den, vil man være i stand til at læse din overvågningsliste, så del den ikke.\n[[Special:ResetTokens|Klik her]] hvis du har brug at nulstille den.",
        "savedprefs": "Dine indstillinger er blevet gemt.",
        "savedrights": "Brugergrupperne for {{GENDER:$1|$1}} er blevet gemt.",
        "uploadstash-summary": "Denne side giver adgang til filer, de er uploadet (eller er i gang med at blive det), men som endnu ikke er offentliggjort på wikien. Disse filer er kun synlige for brugeren, der har uploadet dem.",
        "uploadstash-clear": "Ryd stashede filer",
        "uploadstash-nofiles": "Du har ingen stashede filer.",
-       "uploadstash-badtoken": "Udførelse af handlingen mislykkedes, måske fordi dine redigerings legitimationsoplysninger udløbet. Prøv igen.",
+       "uploadstash-badtoken": "Udførelsen af handlingen mislykkedes, måske fordi dine legitimationsoplysninger for redigering er udløbet. Prøv venligst igen.",
        "uploadstash-errclear": "Rydning af filerne mislykkedes.",
        "uploadstash-refresh": "Opdatér filoversigten",
        "uploadstash-thumbnail": "vis miniature",
        "filehist-filesize": "Filstørrelse",
        "filehist-comment": "Kommentar",
        "imagelinks": "Filanvendelse",
-       "linkstoimage": "{{PLURAL:$1|Den følgende side|De følgende $1 sider}} henviser til denne fil:",
+       "linkstoimage": "{{PLURAL:$1|Den følgende side|De følgende $1 sider}} bruger denne fil:",
        "linkstoimage-more": "Flere end $1 {{PLURAL:$1|side|sider}} henviser til denne fil.\nDen følgende liste viser kun {{PLURAL:$1|den første henvisning|de $1 første henvisninger}}.\nEn [[Special:WhatLinksHere/$2|komplet liste]] er tilgængelig.",
-       "nolinkstoimage": "Der er ingen sider der henviser til denne fil.",
+       "nolinkstoimage": "Der er ingen sider der bruger denne fil.",
        "morelinkstoimage": "Se [[Special:WhatLinksHere/$1|flere henvisninger]] til denne fil.",
        "linkstoimage-redirect": "$1 (filomdirigering) $2",
        "duplicatesoffile": "Følgende {{PLURAL:$1|fil er en dublet|filer er dubletter}} af denne fil ([[Special:FileDuplicateSearch/$2|flere detaljer]]):",
        "editcomment": "Redigeringsbeskrivelsen var: <em>$1</em>.",
        "revertpage": "Gendannet til seneste version af [[User:$1|$1]], fjerner ændringer fra [[Special:Contributions/$2|$2]] ([[User talk:$2|diskussion]])",
        "revertpage-nouser": "Gendannet til seneste version af {{GENDER:$1|[[User:$1|$1]]}}, fjerner ændringer fra en skjult bruger",
-       "rollback-success": "Ændringerne fra $1 er fjernet,\nog den seneste version af $2 er gendannet.",
+       "rollback-success": "Ændringerne foretaget af {{GENDER:$3|$1}} er blevet tilbagestillet, og den seneste version af {{GENDER:$4|$2}} er gendannet.",
        "sessionfailure-title": "Sessionsfejl",
-       "sessionfailure": "Der lader til at være et problem med din loginsession; denne handling blev annulleret som en sikkerhedsforanstaltning mod kapring af sessionen. Tryk på \"tilbage\"-knappen og genindlæs den side du kom fra, og prøv dernæst igen.",
+       "sessionfailure": "Der lader til at være et problem med din loginsession; denne handling blev annulleret som en sikkerhedsforanstaltning mod kapring af sessionen. Genindsend venligst formularen.",
        "changecontentmodel-legend": "Ændr indholdsmodel",
        "changecontentmodel-title-label": "Sidetitel",
        "changecontentmodel-model-label": "Ny indholdsmodel",
        "sp-contributions-newbies-sub": "Fra nye kontoer",
        "sp-contributions-newbies-title": "Brugerbidrag fra nye konti",
        "sp-contributions-blocklog": "blokeringslog",
-       "sp-contributions-suppresslog": "undertrykte brugerbidrag",
+       "sp-contributions-suppresslog": "undertrykte {{GENDER:$1|brugerbidrag}}",
        "sp-contributions-deleted": "slettede {{GENDER:$1|brugerbidrag}}",
        "sp-contributions-uploads": "uploads",
        "sp-contributions-logs": "loglister",
        "sp-contributions-talk": "diskussion",
-       "sp-contributions-userrights": "håndtering af brugerrettigheder",
+       "sp-contributions-userrights": "håndtering af {{GENDER:$1|brugerrettigheder}}",
        "sp-contributions-blocked-notice": "Denne bruger er i øjeblikket blokeret. Loggen over den seneste blokering kan ses nedenfor:",
        "sp-contributions-blocked-notice-anon": "Denne IP-adresse er i øjeblikket blokeret.\nDen seneste post i blokeringsloggen vises nedenfor:",
        "sp-contributions-search": "Søg efter bidrag",
        "lockedbyandtime": "(af $1 den $2 kl. $3)",
        "move-page": "Flyt $1",
        "move-page-legend": "Flyt side",
-       "movepagetext": "Når du bruger formularen herunder, vil du få omdøbt en side og flyttet hele sidens historie til det nye navn.\nDen gamle titel vil blive en omdirigeringsside til den nye titel.\nDu kan opdatere omdirigeringer, der peger på den oprindelige titel, automatisk.\nHvis du vælger ikke at opdatere dem automatisk, så sørg for at tjekke efter [[Special:DoubleRedirects|dobbelte]] eller [[Special:BrokenRedirects|dårlige omdirigeringer]].\nDu er ansvarlig for, at alle henvisninger stadig peger derhen, hvor det er meningen de skal pege.\n\nBemærk at siden '''ikke''' kan flyttes, hvis der allerede er en side med den nye titel, medmindre den side er en omdirigering uden nogen redigeringshistorik.\nDet betyder, at du kan flytte en side tilbage hvor den kom fra, hvis du kommer til at lave en fejl, og det betyder, at du ikke kan overskrive en eksisterende side.\n\n'''ADVARSEL!'''\nDette kan være en drastisk og uventet ændring for en populær side; vær sikker på, at du forstår konsekvenserne af dette før du fortsætter.",
-       "movepagetext-noredirectfixer": "Brug formularen herunder du vil omdøbe en side og flyttet hele sidens historie til det nye navn.\nDen gamle titel vil blive en omdirigeringsside til den nye titel.\nVær sikker på at tjekke for [[Special:DoubleRedirects|dobbelte]] eller [[Special:BrokenRedirects|ødelagte omdirigeringer]].\nDu er ansvarlig for at sikre, at alle henvisninger stadig peger på et sted hvor det giver meningen at gå.\n\nBemærk, at siden '''ikke''' kan flyttes hvis der allerede er en side med den nye titel, medmindre den er tom eller er en omdirigering, og har ingen historie.\nDet betyder at du kan omdøbe en side tilbage hvor den kom fra, hvis du laver en fejl, og du kan ikke overskrive en eksisterende side.\n\n'''Advarsel!'''\nDette kan være en drastisk og uventet ændring for en populær side;\ndu skal være sikker på at du forstår konsekvenserne af dette før du fortsætter.",
+       "movepagetext": "Når du bruger formularen herunder, vil du få omdøbt en side og flyttet hele sidens historie til det nye navn.\nDen gamle titel vil blive en omdirigeringsside til den nye titel.\nDu kan opdatere omdirigeringer, der peger på den oprindelige titel, automatisk.\nHvis du vælger ikke at opdatere dem automatisk, så sørg for at tjekke efter [[Special:DoubleRedirects|dobbelte]] eller [[Special:BrokenRedirects|ødelagte omdirigeringer]].\nDu er ansvarlig for, at alle henvisninger stadig peger derhen, hvor det er meningen de skal pege.\n\nBemærk at siden <strong>ikke</strong> kan flyttes, hvis der allerede er en side med den nye titel, medmindre den side er en omdirigering uden nogen redigeringshistorik.\nDet betyder, at du kan flytte en side tilbage hvor den kom fra, hvis du kommer til at lave en fejl, og det betyder, at du ikke kan overskrive en eksisterende side.\n\n<strong>Bemærk:</strong>\nDette kan være en drastisk og uventet ændring for en populær side; vær sikker på, at du forstår konsekvenserne af dette før du fortsætter.",
+       "movepagetext-noredirectfixer": "Brug af formularen herunder vil omdøbe en side og flytte hele sidens historie til det nye navn.\nDen gamle titel vil blive en omdirigeringsside til den nye titel.\nVær sikker på at tjekke for [[Special:DoubleRedirects|dobbelte]] eller [[Special:BrokenRedirects|ødelagte omdirigeringer]].\nDu er ansvarlig for at sikre, at alle henvisninger stadig peger på det, som det er meningen, de skal pege på.\n\nBemærk, at siden <strong>ikke</strong> kan flyttes, hvis der allerede er en side med den nye titel, medmindre det er en omdirigeringsside uden historie.\nDet betyder, at du kan omdøbe en side tilbage hvor den kom fra, hvis du laver en fejl, og at du ikke kan overskrive en eksisterende side.\n\n<strong>Bemærk:</strong>\nDette kan være en drastisk og uventet ændring for en populær side; vær sikker på, at du forstår konsekvenserne af dette før du fortsætter.",
        "movepagetalktext": "Den tilhørende diskussionsside, hvis der er en, vil automatisk blive flyttet med siden '''medmindre:''' *Du flytter siden til et andet navnerum,\n*En ikke-tom diskussionsside allerede eksisterer under det nye navn, eller\n*Du fjerner markeringen i boksen nedenunder.\n\nI disse tilfælde er du nødt til at flytte eller sammenflette siden manuelt.",
        "moveuserpage-warning": "'''Advarsel:''' Du er ved at flytte en brugerside. Bemærk at det kun er siden, der vil blive flyttet – brugeren bliver ''ikke'' omdøbt.",
        "movenologintext": "Du skal være registreret bruger og [[Special:UserLogin|logget på]] for at flytte en side.",
        "delete_and_move_text": "==Sletning nødvendig==\n\nArtiklen \"[[:$1]]\" eksisterer allerede. Vil du slette den for at gøre plads til flytningen?",
        "delete_and_move_confirm": "Ja, slet siden",
        "delete_and_move_reason": "Slettet for at gøre plads til flytning fra \"[[$1]]\"",
-       "selfmove": "Begge sider har samme navn. Man kan ikke flytte en side oven i sig selv.",
+       "selfmove": "Titlen er den samme; man kan ikke flytte en side til samme side.",
        "immobile-source-namespace": "Kan ikke flytte sider i navnerummet \"$1\"",
        "immobile-target-namespace": "Kan ikke flytte sider til navnerummet \"$1\"",
        "immobile-target-namespace-iw": "En side kan ikke flyttes til en interwiki-henvisning.",
        "fix-double-redirects": "Opdater henvisninger til det oprindelige navn",
        "move-leave-redirect": "Efterlad en omdirigering",
        "protectedpagemovewarning": "'''Bemærk:''' Denne side er låst så kun administratorer kan flytte den.<br />\nDen seneste logpost vises nedenfor:",
-       "semiprotectedpagemovewarning": "'''Bemærk:''' Denne side er låst så kun registrerede brugere kan flytte den.<br />\nDen seneste logpost vises nedenfor:",
+       "semiprotectedpagemovewarning": "<strong>Bemærk:</strong> Denne side er låst, så kun automatisk bekræftede brugere kan flytte den.\nDen seneste logpost vises nedenfor som reference:",
        "move-over-sharedrepo": "== Fil findes ==\n[[:$1]] findes på en delt kilde. Ved at flytte en fil til denne titel vil overskrive den eksisterende delte fil.",
        "file-exists-sharedrepo": "Det valgte filnavn er allerede i brug på en delt kilde.\nVælg venligst et andet navn.",
        "export": "Eksportér sider",
index 0cff6d9..29651e9 100644 (file)
        "cascadeprotected": "Za toś ten bok jo se wobźěłowanje znjemóžniło, dokulaž jo zawězany do {{PLURAL:$1|slědujucego boka|slědujuceju bokowu|slědujucych bokow}}, {{PLURAL:$1|kótaryž jo|kótarejž stej|kótarež su}} pśez kaskadowu opciju {{PLURAL:$1|šćitany|šćitanej|šćitane}}: $2",
        "namespaceprotected": "Njejsy wopšawnjony, boki w rumje: '''$1''' wobźěłaś.",
        "customcssprotected": "Njamaš pšawo, aby toś ten CSS-bok wobźěłał, dokulaž wopśimujo  wósobinske nastajenja drugego wužywarja.",
+       "customjsonprotected": "Njamaš pšawo, aby toś ten JSON-bok wobźěłał, dokulaž wopśimujo  wósobinske nastajenja drugego wužywarja.",
        "customjsprotected": "Njamaš pšawo, aby toś ten JavaScriptowy bok wobźěłał, dokulaž wopśimujo  wósobinske nastajenja drugego wužywarja.",
        "mycustomcssprotected": "Njamaš pšawo toś ten CSS-bok wobźěłaś.",
+       "mycustomjsonprotected": "Njamaš pšawo toś ten JSON-bok wobźěłaś.",
        "mycustomjsprotected": "Njamaš pšawo toś ten JavaScript-bok wobźěłaś.",
        "myprivateinfoprotected": "Njamaš pšawo swóje priwatne informacije wobźěłaś.",
        "mypreferencesprotected": "Njamaš pšawo swóje nastajenja wobźěłaś.",
index b06bcc7..2fd9783 100644 (file)
                        "Amaia",
                        "Tiberius1701",
                        "Astroemi",
-                       "Jelou"
+                       "Jelou",
+                       "Ktranz"
                ]
        },
        "tog-underline": "Subrayar los enlaces:",
        "ns-specialprotected": "No se pueden editar las páginas especiales.",
        "titleprotected": "Este título ha sido protegido contra creación por [[User:$1|$1]].\nEl motivo proporcionado es <em>$2</em>.",
        "filereadonlyerror": "No se puede modificar el archivo \"$1\" porque el repositorio de archivos \"$2\" es de solo lectura.\n\nEl administrador del sistema que lo ha bloqueado ofrece esta explicación: \"$3\".",
+       "invalidtitle": "Título inválido",
        "invalidtitle-knownnamespace": "El título con el espacio de nombres «$2» y el texto «$3» no es válido",
        "invalidtitle-unknownnamespace": "El título con el espacio de nombres desconocido (n.º $1) y el texto «$2» no es válido",
        "exception-nologin": "No has accedido",
        "uploadstash-zero-length": "El archivo está vacío.",
        "invalid-chunk-offset": "Desplazamiento inválido del fragmento",
        "img-auth-accessdenied": "Acceso denegado",
-       "img-auth-nopathinfo": "Falta PATH_INFO.\nEl servidor no está configurado para proporcionar esta información.\nEs posible que esté basado en CGI y que no sea compatible con img_auth.\nConsulte https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
+       "img-auth-nopathinfo": "Falta la información de ruta.\nEl servidor tiene que estar configurado para proporcionar las variables REQUEST_URI y/o PATH_INFO.\nSi lo está, intentá habilitar $wgUsePathInfo.\nConsulte https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "img-auth-notindir": "La ruta solicitada no figura en la carpeta de subidas configurada.",
        "img-auth-badtitle": "Incapaz de construir un título válido de «$1».",
        "img-auth-nologinnWL": "No has iniciado sesión y «$1» no está en la lista blanca.",
        "filehist-filesize": "Tamaño del archivo",
        "filehist-comment": "Comentario",
        "imagelinks": "Usos del archivo",
-       "linkstoimage": "{{PLURAL:$1|La siguiente página enlaza|Las siguientes páginas enlazan}} a este archivo:",
-       "linkstoimage-more": "Hay más de {{PLURAL:$1|una página que enlaza|$1 páginas que enlazan}} con este archivo.\nLa lista siguiente sólo muestra {{PLURAL:$1|la primera página que enlaza|las primeras $1 páginas que enlazan}} con este archivo.\nTambién puedes consultar la [[Special:WhatLinksHere/$2|lista completa]].",
-       "nolinkstoimage": "No hay páginas que enlacen a esta imagen.",
+       "linkstoimage": "{{PLURAL:$1|La siguiente página usa|Las siguientes páginas usan}} a este archivo:",
+       "linkstoimage-more": "Hay más de {{PLURAL:$1|una página que usa|$1 páginas que usan}} este archivo.\nLa lista siguiente sólo muestra {{PLURAL:$1|la primera página que usa|las primeras $1 páginas que usan}} este archivo.\nTambién puedes consultar la [[Special:WhatLinksHere/$2|lista completa]].",
+       "nolinkstoimage": "No hay páginas que enlacen a este archivo.",
        "morelinkstoimage": "Mira [[Special:WhatLinksHere/$1|más enlaces]] a este archivo.",
        "linkstoimage-redirect": "$1 (archivo de redirección) $2",
        "duplicatesoffile": "{{PLURAL:$1|El siguiente archivo es un duplicado|Los siguientes $1 archivos son duplicados}} de éste ([[Special:FileDuplicateSearch/$2|más detalles]]):",
        "limitreport-expansiondepth": "Profundidad máxima de expansión",
        "limitreport-expensivefunctioncount": "Cuenta de la función expansiva del analizador",
        "limitreport-unstrip-depth": "Profundidad de recursión de función «unstrip»",
+       "limitreport-unstrip-size": "Unstrip tamaño post-expandido",
        "limitreport-unstrip-size-value": "$1/$2 {{PLURAL:$2|byte|bytes}}",
        "expandtemplates": "Expandir plantillas",
        "expand_templates_intro": "Esta página especial toma un texto wiki y expande todas sus plantillas recursivamente.\nTambién expande las funciones sintácticas como <code><nowiki>{{</nowiki>#language:…}}</code>, y variables como\n<code><nowiki>{{</nowiki>CURRENTDAY}}</code>. De hecho, expande casi cualquier cosa que esté entre llaves dobles.",
        "passwordpolicies-policy-passwordcannotmatchusername": "La contraseña no puede ser la misma que el nombre de usuario",
        "passwordpolicies-policy-passwordcannotmatchblacklist": "La contraseña no puede coincidir con la lista de contraseñas específicamente prohibidas",
        "passwordpolicies-policy-maximalpasswordlength": "La contraseña no puede tener más de $1 {{PLURAL:$1|caracter|caracteres}}",
-       "passwordpolicies-policy-passwordcannotbepopular": "La contraseña no puede {{PLURAL:$1|ser la contraseña más popular|encontrarse en la lista de $1 contraseñas populares}}"
+       "passwordpolicies-policy-passwordcannotbepopular": "La contraseña no puede {{PLURAL:$1|ser la contraseña más popular|encontrarse en la lista de $1 contraseñas populares}}",
+       "easydeflate-invaliddeflate": "El contenido proporcionado no esta comprimido correctamente"
 }
index a7aaabe..ea902bb 100644 (file)
        "ns-specialprotected": "صفحه‌های ویژه غیر قابل ویرایش هستند.",
        "titleprotected": "این عنوان توسط [[User:$1|$1]] در برابر ایجاد محافظت شده‌است.\nدلیل ارائه‌شده این است: <em>$2</em>.",
        "filereadonlyerror": "تغییر پروندهٔ «$1» ممکن نیست چون مخزن پروندهٔ «$2» در حالت فقط خواندنی قرار دارد.\n\nمدیری که آن را قفل کرده چنین توضیحی را ذکر کرده:  «$3».",
+       "invalidtitle": "عنوان نامعتبر",
        "invalidtitle-knownnamespace": "عنوان نامعتبر با فضای نام «$2» و متن «$3»",
        "invalidtitle-unknownnamespace": "عنوان نامعتبر با فضای نام ناشناختهٔ شمارهٔ $1 و متن «$2»",
        "exception-nologin": "به سامانه وارد نشده‌اید",
        "converter-manual-rule-error": "خطا در قوانین مبدل دستی زبان",
        "undo-success": "این ویرایش را می‌توان خنثی کرد.\nلطفاً تفاوت زیر را بررسی کنید تا تأیید کنید که این چیزی است که می‌خواهید انجام دهید، سپس تغییرات زیر را ذخیره کنید تا خنثی‌سازی ویرایش را به پایان ببرید.",
        "undo-failure": "به علت تعارض با ویرایش‌های میانی، این ویرایش را نمی‌توان خنثی کرد.",
+       "undo-main-slot-only": "ویرایش را نمی‌توان انجام داد زیرا شامل محتویات خارج از شیار اصلی است.",
        "undo-norev": "این ویرایش را نمی‌توان خنثی کرد چون وجود ندارد یا حذف شده‌است.",
        "undo-nochange": "به نظر می‌رسد ویرایش از پیش خنثی‌سازی شده است.",
        "undo-summary": "خنثی‌سازی ویرایش $1 توسط [[Special:Contributions/$2|$2]] ([[User talk:$2|بحث]])",
        "diff-paragraph-moved-toold": "پاراگراف جابه‌جا شده بود. کلیک کنید تا به جای قدیمش بروید.",
        "difference-missing-revision": "{{PLURAL:$2|یک ویرایش|$2 ویرایش}}  از تفاوت نسخه‌ها ($1) {{PLURAL:$2|یافت|یافت}}  نشد.\n\nاین اتفاق معمولاً در اثر دنبال کردن پیوند تفاوتی به یک صفحهٔ حذف‌شده پیش می‌آید.\nمی‌توانید جزئیات بیشتر را در [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سیاههٔ حذف] بیابید.",
        "searchresults": "نتایج جستجو",
+       "search-filter-title-prefix": "فقط در صفحاتی که عنوانش با «$1» شروع می‌شود",
        "search-filter-title-prefix-reset": "جستجوی همه صفحات",
        "searchresults-title": "نتایج جستجو برای «$1»",
        "titlematches": "تطبیق عنوان مقاله",
        "prefs-watchlist-edits": "تعداد ویرایش‌های نشان‌داده‌شده در فهرست پی‌گیری‌ها:",
        "prefs-watchlist-edits-max": "حداکثر تعداد: ۱۰۰۰",
        "prefs-watchlist-token": "رمز فهرست پی‌گیری:",
+       "prefs-watchlist-managetokens": "مدیریت بلیط‌ها",
        "prefs-misc": "متفرقه",
        "prefs-resetpass": "تغییر گذرواژه",
        "prefs-changeemail": "تغییر یا حذف نشانی ایمیل",
        "recentchangescount": "تعداد نمایش پیش‌فرض ویرایش‌ها در تغییرات اخیر، تاریخچه صفحه و سیاهه‌ها:",
        "prefs-help-recentchangescount": "حداکثر تعداد: ۱۰۰۰",
        "prefs-help-watchlist-token2": "این کلید رمز خوراک وب فهرست پی‌گیری‌های شماست.\nهرکس آن را بداند می‌تواند فهرست پی‌گیری‌هایتان را بخواند، بنابراین آن را به اشتراک نگذارید. اگر لازم باشد [[Special:ResetTokens|می‌توانید کلیدی نو ایجاد کنید]].",
+       "prefs-help-tokenmanagement": "برای حسابتان که به خوراک وب‌سایت فهرست پیگیری‌تان دسترسی دارد کلید محرمانه را می‌توانید ببینید و بازنشانی کنید. کلید قادر به خواندن فهرست پیگیری‌های شما خواهد بود، پس آن را به اشتراک نگذارید.",
        "savedprefs": "ترجیحات شما ذخیره شد.",
        "savedrights": "گروه‌های کاربری {{GENDER:$1|$1}} ذخیره شده‌است.",
        "timezonelegend": "منطقهٔ زمانی:",
        "group-autoconfirmed-member": "{{GENDER:$1|کاربر تأییدشده}}",
        "group-bot-member": "ربات",
        "group-sysop-member": "{{GENDER:$1|مدیر}}",
-       "group-interface-admin-member": "مدیر رابط کاربری",
+       "group-interface-admin-member": "{{GENDER:$1|مدیر رابط کاربری}}",
        "group-bureaucrat-member": "{{GENDER:$1|دیوان‌سالار}}",
        "group-suppress-member": "{{GENDER:$1|فرونشاننده}}",
        "grouppage-user": "{{ns:project}}:کاربران",
        "grouppage-autoconfirmed": "{{ns:project}}:کاربران تأییدشده",
        "grouppage-bot": "{{ns:project}}:ربات‌ها",
        "grouppage-sysop": "{{ns:project}}:مدیران",
+       "grouppage-interface-admin": "{{ns:project}}:مدیران رابط کاربری",
        "grouppage-bureaucrat": "{{ns:project}}:دیوان‌سالاران",
        "grouppage-suppress": "{{ns:project}}:فرونشانی",
        "right-read": "خواندن صفحه",
        "right-editcontentmodel": "ویرایش مدل محتوای یک صفحه",
        "right-editinterface": "ویرایش واسط کاربری",
        "right-editusercss": "ویرایش صفحه‌های CSS دیگر کاربرها",
+       "right-edituserjson": "ویرایش پرونده‌های JSON دیگر کاربرها",
        "right-edituserjs": "ویرایش صفحه‌های JS دیگر کاربرها",
+       "right-editsitecss": "ویرایش گسترده CSS وب‌گاه",
+       "right-editsitejson": "ویرایش گسترده JSON وب‌گاه",
+       "right-editsitejs": "ویرایش گسترده JavaScript وب‌گاه",
        "right-editmyusercss": "پرونده‌های سی‌اس‌اس کاربری خود را ویرایش کنید",
+       "right-editmyuserjson": "پرونده‌های JSON کاربری خود را ویرایش کنید",
        "right-editmyuserjs": "پرونده‌های جاوااسکریپت کاربری خود را ویرایش کنید",
        "right-viewmywatchlist": "فهرست پیگیری‌های خود را ببینید",
        "right-editmywatchlist": "فهرست پیگیری‌های خود را ویرایش کنید. توجه داشته باشید برخی از اقدامات حتی بدون این دسترسی هم صفحات را اضافه می‌کنند.",
        "grant-createaccount": "ایجاد حساب‌های کاربری",
        "grant-createeditmovepage": "ایجاد، ویرایش و انتقال صفحات",
        "grant-delete": "حذف صفحات، نسخه‌های ویرایش و سیاهه ورودی",
-       "grant-editinterface": "ویرایش CSS کاربر/جاوااسکریپت/JSON  و فضای نام مدیاویکی",
+       "grant-editinterface": "ویرایش صفحه‌های جی‌سان کاربری یا سراسری و فضای نام مدیاویکی",
        "grant-editmycssjs": "ویرایش  CSS /جاوااسکریپت/JSON  کاربری",
        "grant-editmyoptions": "اولویت‌های کاربری را ویرایش کنید",
        "grant-editmywatchlist": "ویرایش فهرست پی‌گیری‌هایتان",
+       "grant-editsiteconfig": "ویرایش گسترده CSS/JS کاربر",
        "grant-editpage": "ویرایش صفحات موجود",
        "grant-editprotected": "ویرایش صفحه محافظت شده",
        "grant-highvolume": "ویرایش با حجم بالا",
        "rcfilters-activefilters": "پالایه‌های فعال",
        "rcfilters-activefilters-hide": "نهفتن",
        "rcfilters-activefilters-show": "نمایش",
+       "rcfilters-activefilters-hide-tooltip": "پنهان کردن محیط پالایه فعال",
+       "rcfilters-activefilters-show-tooltip": "نمایش محیط پالایه فعال",
        "rcfilters-advancedfilters": "پالایه‌‌های پیشرفته",
        "rcfilters-limit-title": "تعداد تغییرات برای نمایش",
        "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|تغییر|تغییر}}, $2",
        "rcfilters-filter-humans-label": "انسان (ربات نه)",
        "rcfilters-filter-humans-description": "ویرایش توسط انسان.",
        "rcfilters-filtergroup-reviewstatus": "وضعیت بازبینی",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "ویرایش‌های غیردستی یا خودکار به عنوان گشت‌خورده.",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "گشت‌نخورده",
+       "rcfilters-filter-reviewstatus-manual-description": "ویرایش‌های دستی به عنوان گشت‌خورده.",
        "rcfilters-filter-reviewstatus-manual-label": "به طور دستی گشت خورد",
+       "rcfilters-filter-reviewstatus-auto-description": "ویرایش‌های کاربران باتجربه که ویرایشش به عنوان گشت‌خورده برچسب خورده‌است.",
+       "rcfilters-filter-reviewstatus-auto-label": "گشت خودکار",
        "rcfilters-filtergroup-significance": "اهمیت",
        "rcfilters-filter-minor-label": "ویرایش‌های جزئی",
        "rcfilters-filter-minor-description": "ویرایش‌هایی که به عنوان جزئی برچسب خورده‌اند.",
        "rcfilters-watchlist-showupdated": "تغییرات صفحاتی که شما بازدید نکردید از زمانی که تغییرات رخ داده به صورت <strong>پررنگ</strong>، با نشانگر توپر.",
        "rcfilters-preference-label": "مخفی کردن نسخه بهبود یافته تغییرات اخیر",
        "rcfilters-preference-help": "تغییرات رابط کاربری که در سال ۲۰۱۷ اضافه شده است را بر می‌گرداند.",
+       "rcfilters-watchlist-preference-label": "نمایش نسخهٔ بهبودیافتهٔ فهرست پیگیری",
+       "rcfilters-watchlist-preference-help": "واگردان در سال ۲۰۱۷ دوباره طراحی شد و تمام ابزارها اضافه و از آن زمان به بعد اضافه شدند.",
        "rcfilters-filter-showlinkedfrom-label": "نمایش تغییرات صفحاتی که پیوند شده‌اند",
        "rcfilters-filter-showlinkedfrom-option-label": "<strong>صفحات پیوند به</strong> صفحهٔ انتخاب شده",
        "rcfilters-filter-showlinkedto-label": "نمایش تغییرات در صفحاتی که در ون این صفحه پیوند شده‌اند",
        "uploadstash-zero-length": "اندازهٔ پرونده صفر است.",
        "invalid-chunk-offset": "جابجایی نامعتبر قطعه",
        "img-auth-accessdenied": "منع دسترسی",
-       "img-auth-nopathinfo": "PATH_INFO موجود نیست.\nسرور شما برای ردکردن این مقدار تنظیم نشده‌است.\nممکن است مبتنی بر سی‌جی‌آی باشد و از img_auth پشتیبانی نکند.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization را ببینید.",
+       "img-auth-nopathinfo": "مسیر اطلاعات موجود نیست.\nسرورتان برای ردکردن متغییرهای REQUEST_URI و/یا PATH_INFO باید تنظیم شود.\nاگر مبتنی قصد فعال‌کردن wgUsePathInfo دارد.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization را ببینید.",
        "img-auth-notindir": "مسیر درخواست شده در شاخهٔ بارگذاری تنظیم‌شده قرار ندارد.",
        "img-auth-badtitle": "امکان ایجاد یک عنوان مجاز از «$1» وجود ندارد.",
        "img-auth-nologinnWL": "شما به سامانه وارد نشده‌اید و «$1» در فهرست سفید قرار ندارد.",
        "http-timed-out": "مهلت درخواست اچ‌تی‌تی‌پی به سر رسید.",
        "http-curl-error": "خطا در آوردن نشانی اینترنتی: $1",
        "http-bad-status": "در حین درخواست اچ‌تی‌تی‌پی خطایی رخ داد: $1 $2",
+       "http-internal-error": "خطای درونی HTTP",
        "upload-curl-error6": "دسترسی به نشانی اینترنتی ممکن نشد",
        "upload-curl-error6-text": "نشانی اینترنتی داده شده قابل دسترسی نیست.\nلطفاً درستی آن و اینکه تارنما برقرار است را بررسی کنید.",
        "upload-curl-error28": "مهلت بارگذاری به سر رسید",
        "filehist-filesize": "اندازهٔ پرونده",
        "filehist-comment": "توضیح",
        "imagelinks": "کاربرد پرونده",
-       "linkstoimage": "{{PLURAL:$1|صفحهٔ|صفحه‌های}} زیر به این تصویر پیوند {{PLURAL:$1|دارد|دارند}}:",
-       "linkstoimage-more": "بÛ\8cØ´ Ø§Ø² $1 ØµÙ\81Ø­Ù\87 Ø¨Ù\87 Ø§Û\8cÙ\86 Ù¾Ø±Ù\88Ù\86دÙ\87 Ù¾Û\8cÙ\88Ù\86د {{PLURAL:$1|دارد|دارÙ\86د}}.\nÙ\81Ù\87رست Ø²Û\8cر ØªÙ\86Ù\87ا {{PLURAL:$1|اÙ\88Ù\84Û\8cÙ\86 Ù¾Û\8cÙ\88Ù\86د|اÙ\88Ù\84Û\8cÙ\86 $1 Ù¾Û\8cÙ\88Ù\86د}} Ø¨Ù\87 این صفحه را نشان می‌دهد.\n[[Special:WhatLinksHere/$2|فهرست کامل]] نیز موجود است.",
+       "linkstoimage": "{{PLURAL:$1|صفحهٔ|صفحه‌های}} زیر به این تصویر پیوند دارد:",
+       "linkstoimage-more": "بÛ\8cØ´ Ø§Ø² $1 ØµÙ\81Ø­Ù\87 Ø§Ø² Ø§Û\8cÙ\86 Ù¾Ø±Ù\88Ù\86دÙ\87 Ø§Ø³ØªÙ\81ادÙ\87 {{PLURAL:$1|Ù\85Û\8câ\80\8cÚ©Ù\86د|Ù\85Û\8câ\80\8cÚ©Ù\86Ù\86د}}.\nÙ\81Ù\87رست Ø²Û\8cر ØªÙ\86Ù\87ا {{PLURAL:$1|اÙ\88Ù\84Û\8cÙ\86 Ø§Ø³ØªÙ\81ادÙ\87|اÙ\88Ù\84Û\8cÙ\86 $1 Ø§Ø³ØªÙ\81ادÙ\87}} Ø§Ø² این صفحه را نشان می‌دهد.\n[[Special:WhatLinksHere/$2|فهرست کامل]] نیز موجود است.",
        "nolinkstoimage": "این پرونده در هیچ صفحه‌ای به کار نرفته‌است.",
        "morelinkstoimage": "[[Special:WhatLinksHere/$1|پیوندهای دیگر]] به این پرونده را ببینید.",
        "linkstoimage-redirect": "$1 (تغییرمسیر پرونده) $2",
        "protectedtitles-submit": "نمایش عناوین",
        "listusers": "فهرست کاربران",
        "listusers-editsonly": "فقط کاربرانی که ویرایش دارند را نشان بده",
+       "listusers-temporarygroupsonly": "نمایش کاربرانی که به صورت موقت در گروه کاربران هستند",
        "listusers-creationsort": "مرتب کردن بر اساس تاریخ ایجاد",
        "listusers-desc": "ترتیب نزولی",
        "usereditcount": "$1 {{PLURAL:$1|ویرایش|ویرایش}}",
        "apisandbox-dynamic-parameters-add-label": "افزودن پارامتر:",
        "apisandbox-dynamic-parameters-add-placeholder": "نام پارامتر",
        "apisandbox-dynamic-error-exists": "پارامتری به نام \"$1\"هم اکنون وجود دارد.",
+       "apisandbox-templated-parameter-reason": "این [[Special:ApiHelp/main#main/templatedparams|پارامتر الگو]] بر پایهٔ {{PLURAL:$1|مقدار|مقدار}} $2 پیشنهاد می‌شود.",
        "apisandbox-deprecated-parameters": "پارامتر های نامناسب",
        "apisandbox-fetch-token": "پرکردن خودکار توکن",
        "apisandbox-add-multi": "افزودن",
        "speciallogtitlelabel": "هدف (عنوان یا {{ns:user}}:نام کاربر برای کاربر):",
        "log": "سیاهه‌ها",
        "logeventslist-submit": "نمایش",
+       "logeventslist-more-filters": "نمایش سیاهه‌های بیشتر:",
        "logeventslist-patrol-log": "سیاههٔ گشت",
        "logeventslist-tag-log": "سیاهه برچسب",
        "all-logs-page": "تمام سیاهه‌های عمومی",
        "cachedspecial-refresh-now": "مشاهده آخرین.",
        "categories": "رده‌ها",
        "categories-submit": "نمایش",
-       "categoriespagetext": "{{PLURAL:$1|ردهٔ|رده‌های}} زیر دارای صفحات یا پرونده‌هایی {{PLURAL:$1|است|هستند}}.\n[[Special:UnusedCategories|رده‌های استفاده‌نشده]] در اینجا نمایش داده نشده‌اند.\nهمچنین [[Special:WantedCategories|رده‌های مورد نیاز]] را ببینید.",
+       "categoriespagetext": "{{PLURAL:$1|ردهٔ|رده‌های}} زیر دارای صفحات یا پرونده‌هایی است.\nرده‌های استفاده‌نشده در اینجا نمایش داده نشده‌اند.\nهمچنین [[Special:WantedCategories|رده‌های مورد نیاز]] را ببینید.",
        "categoriesfrom": "نمایش رده‌ها با شروع از:",
        "deletedcontributions": "مشارکت‌های حذف‌شده",
        "deletedcontributions-title": "مشارکت‌های حذف‌شده",
        "dellogpage": "سیاههٔ حذف",
        "dellogpagetext": "فهرست زیر فهرستی از آخرین حذف‌هاست.\nهمهٔ زمان‌های نشان‌داده‌شده زمان کارساز (وقت گرینویچ) است.",
        "deletionlog": "سیاههٔ حذف",
+       "log-name-create": "سیاههٔ ایجاد صفحه",
+       "log-description-create": "در زیر فهرست صفحاتی که اخیراً ایجاد شده‌اند قرار دارد.",
+       "logentry-create-create": "$1 صفحهٔ $3 را {{GENDER:$2|ساخته}}",
        "reverted": "به نسخهٔ قدیمی‌تر واگردانده شد",
        "deletecomment": "دلیل:",
        "deleteotherreason": "دلیل دیگر/اضافی:",
        "protect-othertime": "زمانی دیگر:",
        "protect-othertime-op": "زمانی دیگر",
        "protect-existing-expiry": "زمان انقضای موجود: $2، $3",
-       "protect-existing-expiry-infinity": "زÙ\85اÙ\86 Ø§Ù\86Ù\82ضاÛ\8c Ù\85Ù\88جÙ\88د: Ø¨Û\8câ\80\8cÙ\86Ù\87اÛ\8cت",
+       "protect-existing-expiry-infinity": "زÙ\85اÙ\86 Ø§Ù\86Ù\82ضاÛ\8c Ù\85Ù\88جÙ\88د: Ø¨Û\8câ\80\8cپاÛ\8cاÙ\86",
        "protect-otherreason": "دلیل دیگر/اضافی:",
        "protect-otherreason-op": "دلیل دیگر",
        "protect-dropdown": "*دلایل متداول محافظت\n** خرابکاری گسترده\n** هرزنگاری گسترده\n** جنگ ویرایشی غیر سازنده\n** صفحهٔ پر بازدید",
        "uctop": "(نسخهٔ کنونی)",
        "month": "در این ماه (و پیش از آن):",
        "year": "در این سال (و پیش از آن):",
+       "date": "از تاریخ (و زودتر):",
        "sp-contributions-newbies": "فقط مشارکت‌های تازه‌کاران نمایش داده شود",
        "sp-contributions-newbies-sub": "برای تازه‌کاران",
        "sp-contributions-newbies-title": "مشارکت‌های کاربری برای حساب‌های تازه‌کار",
        "interlanguage-link-title": "$1–$2",
        "interlanguage-link-title-nonlang": "$1 – $2",
        "common.css": "/* دستورات این بخش همهٔ کاربران را تحت تاثیر قرار می‌دهند. */",
+       "common.json": "/*همهٔ JSONهای اینجا برای همهٔ کاربران در همهٔ صفحات بارگذاری می‌شوند.*/",
        "anonymous": "{{PLURAL:$1|کاربر|کاربران}} گمنام {{SITENAME}}",
        "siteuser": "$1، کاربر {{SITENAME}}",
        "anonuser": "$1 کاربر ناشناس {{SITENAME}}",
        "unlinkaccounts-success": "پیوند کاربری بدون پیوند شد.",
        "authenticationdatachange-ignored": "به تغيير اطلاعات احراز هويت پرداخته نشد. آیا ممکن است که هيچ مهيا کننده‌ای برای اين کار تنظيم نشده باشد؟",
        "userjsispublic": "لطفاً توجه کنید: زیرصفحه‌های جاوااسکریپت نباید حاوی اطلاعات محرمانه باشند چون توسط دیگران قابل مشاهده هستند.",
+       "userjsonispublic": "لطفا توجه داشته باشید: صفحات JSON نباید شامل اطلاعاتی که دیگر کاربران نباید ببینند، باشد.",
        "usercssispublic": "لطفاً توجه کنید: زیرصفحه‌های سی‌اس‌اس نباید حاوی اطلاعات محرمانه باشند چون توسط دیگران قابل مشاهده هستند.",
        "restrictionsfield-badip": "نشانی یا بازهٔ آی‌پی نامعتبر: $1",
        "restrictionsfield-label": "بازه‌های آی‌پی مجاز:",
        "edit-error-long": "خطاها:\n\n$1",
        "revid": "نسخهٔ $1",
        "pageid": "شناسهٔ صفحهٔ $1",
+       "interfaceadmin-info": "\n$1\n\nدسترسی‌ها برای ویرایش فایل‌های CSS/JS/JSON که اخیراً از دسترسی <code>editinterface</code> جدا شده‌اند. اگر نمی دانید که چرا این خطا رخ داده‌است [[mw:MediaWiki_1.32/interface-admin]] را مطالعه کنید.",
        "rawhtml-notallowed": "برچسب‌های &lt;html&gt; را نمی‌توان خارج از صفحه‌های معمولی استفاده کرد.",
        "gotointerwiki": "در حال ترک {{SITENAME}}",
        "gotointerwiki-invalid": "عنوان مشخص شده نامجاز است.",
        "pagedata-text": "این صفحه یک رابط داده به صفحات است. لطفا نام صفحه را در آدرس به شکل زیرصفحه وارد کنید.\n* مذاکره محتوا با استفاده از هدر Accept ممکن است. این به این معنی است که داده‌ّای صفحه در قالبی که ترجیح دهید باز خواهد شد.",
        "pagedata-not-acceptable": "هیچ قالب تطبیقی یافت نشد. انواع MIME پشتیبانی شده: $1",
        "pagedata-bad-title": "عنوان نامعتبر: «$1».",
+       "unregistered-user-config": "برای موارد امنیتی صفحات JavaScript، CSS و JSON برای کاربران ثبت‌نام نکرده دیده نمی‌شوند.",
        "passwordpolicies": "سیاست‌های گذرواژه",
+       "passwordpolicies-summary": "این فهرست سیاست‌های موثر بر گذرواژه‌ها برای گروه‌های کاربری تعریف شده در این ویکی‌ست.",
        "passwordpolicies-group": "گروه",
-       "passwordpolicies-policies": "سیاست‌ها"
+       "passwordpolicies-policies": "سیاست‌ها",
+       "passwordpolicies-policy-minimalpasswordlength": "گذرواژه باید حداقل $1 {{PLURAL:$1|نویسه|نویسه}} طول داشته باشد",
+       "passwordpolicies-policy-minimumpasswordlengthtologin": "گذرواژه باید حداقل $1 {{PLURAL:$1|نویسه|نویسه}} طول داشته باشد تا بتواند به سامانه وارد شود",
+       "passwordpolicies-policy-passwordcannotmatchusername": "گذرواژه نمی تواند مانند نام کاربری باشد",
+       "passwordpolicies-policy-passwordcannotmatchblacklist": "گذرواژه نمی‌تواند مشابه گذرواژه‌های فهرست شده در فهرست سیاه باشد",
+       "passwordpolicies-policy-maximalpasswordlength": "گذرواژه باید کمتر از $1 {{PLURAL:$1|نویسه|نویسه}} طول داشته باشد",
+       "passwordpolicies-policy-passwordcannotbepopular": "گذرواژه نمی‌تواند {{PLURAL:$1|گذرواژه پراستفاده باشد|در فهرست $1 گذرواژه‌های پراستفاده باشد}}",
+       "easydeflate-invaliddeflate": "محتوی تهیه‌شده به صورت درست خالی نشده‌است"
 }
index 4c31ab0..5bdbad1 100644 (file)
        "cascadeprotected": "Ova je stranica zaključana za uređivanja jer je uključena u {{PLURAL:$1|sljedeću stranicu|sljedeće stranice}}, koje su zaštićene \"prenosivom zaštitom\":\n$2",
        "namespaceprotected": "Ne možete uređivati stranice u imenskom prostoru '''$1'''.",
        "customcssprotected": "Ne možete uređivati ovu CSS stranicu zato što ona sadrži osobne postavke drugog suradnika.",
+       "customjsonprotected": "Ne možete uređivati ovu JSON stranicu zato što ona sadrži osobne postavke drugog suradnika.",
        "customjsprotected": "Ne možete uređivati ovu JavaScript stranicu zato što ona sadrži osobne postavke drugog suradnika.",
        "mycustomcssprotected": "Nemate ovlasti za uređivanje ove CSS stranice.",
+       "mycustomjsonprotected": "Nemate ovlasti za uređivanje ove JSON stranice.",
        "mycustomjsprotected": "Nemate ovlasti za uređivanje ove JavaScript stranice.",
        "myprivateinfoprotected": "Nemate ovlasti za uređivanje Vaših osobnih informacija.",
        "mypreferencesprotected": "Nemate ovlasti za uređivanje Vaših postavki.",
index f979761..b5b1137 100644 (file)
@@ -15,7 +15,8 @@
                        "Mikławš",
                        "Macofe",
                        "Matma Rex",
-                       "Fitoschido"
+                       "Fitoschido",
+                       "Vlad5250"
                ]
        },
        "tog-underline": "Wotkazy podšmórnić:",
        "cascadeprotected": "Tuta strona je za wobdźěłowanje zawrjena, dokelž je w {{PLURAL:$1|slědowacej stronje|slědowacymaj stronomaj|slědowacych stronach}} zapřijata, {{PLURAL:$1|kotraž je|kotrejž stej|kotrež su}} přez kaskadowu opciju {{PLURAL:$1|škitana|škitanej|škitane}}:\n$2",
        "namespaceprotected": "Nimaš dowolnosć, zo by stronu w mjenowym rumje '''$1''' wobdźěłał.",
        "customcssprotected": "Nimaš prawo, zo by tutu CSS-stronu wobdźěłał, dokelž wosobinske nastajenja druheho wužiwarja wobsahuje.",
+       "customjsonprotected": "Nimaš prawo, zo by tutu JSON-stronu wobdźěłał, dokelž wosobinske nastajenja druheho wužiwarja wobsahuje.",
        "customjsprotected": "Nimaš prawo, zo by tutu JavaScript-stronu wobdźěłał, dokelž wosobinske nastajenja druheho wužiwarja wobsahuje.",
        "mycustomcssprotected": "Nimaš prawo tutu CSS-stronu wobdźěłać.",
+       "mycustomjsonprotected": "Nimaš prawo tutu JSON-stronu wobdźěłać.",
        "mycustomjsprotected": "Nimaš prawo tutu JavaScript-stronu wobdźěłać.",
        "myprivateinfoprotected": "Nimaš prawo swoje priwatne informacije wobdźěłać.",
        "mypreferencesprotected": "Nimaš prawo swoje nastajenja wobdźěłać.",
index b955139..297cf75 100644 (file)
        "botpasswords-restriction-failed": "ボットパスワード制限によりログインできません。",
        "botpasswords-invalid-name": "指定された利用者名には、ボット用パスワードの区切りである「$1」 が含まれていません。",
        "botpasswords-not-exist": "利用者「$1」はボット「$2」のパスワードを所持していません。",
+       "botpasswords-needs-reset": "{{GENDER:$1|利用者}}「$1」のボット名「$2」のためのパスワードはリセットする必要があります。",
        "resetpass_forbidden": "パスワードは変更できません",
        "resetpass_forbidden-reason": "パスワードは変更できません: $1",
        "resetpass-no-info": "このページに直接アクセスするためにはログインしている必要があります。",
        "grouppage-autoconfirmed": "{{ns:project}}:自動承認された利用者",
        "grouppage-bot": "{{ns:project}}:ボット",
        "grouppage-sysop": "{{ns:project}}:管理者",
+       "grouppage-interface-admin": "{{ns:project}}:インターフェース管理者",
        "grouppage-bureaucrat": "{{ns:project}}:ビューロクラット",
        "grouppage-suppress": "{{ns:project}}:秘匿者",
        "right-read": "ページを閲覧",
index 5038969..e751f0c 100644 (file)
@@ -32,7 +32,8 @@
                        "Yogesh",
                        "Lokesha kunchadka",
                        "Anoop rao",
-                       "Rakshika"
+                       "Rakshika",
+                       "Gopala Krishna A"
                ]
        },
        "tog-underline": "ಕೊಂಡಿಗಳ ಕೆಳಗೆ ಗೆರೆ ತೋರಿಸಿ",
        "botpasswords-existing": "ಆಸ್ಥಿತ್ವದಲ್ಲಿರುವ ಬಾಟ್ ಪ್ರವೇಶಪದ",
        "botpasswords-createnew": "ಹೊಸ ಬಾಟ್ ಪ್ರವೇಶಪದ ರಚಿಸಿ",
        "botpasswords-editexisting": "ಆಸ್ಥಿತ್ವದಲ್ಲಿರುವ ಬಾಟ್ ಪ್ರವೇಶಪದ ಸ೦ಪಾದಿಸಿ",
+       "botpasswords-label-create": "ಸೃಷ್ಟಿಸು",
        "resetpass_forbidden": "ಪ್ರವೇಶಪದಗಳನ್ನು ಬದಲಾಯಿಸುವಂತಿಲ್ಲ.",
        "resetpass-no-info": "ನೀವು ಈ ಪುಟವನ್ನು ನೇರತಲುಪಲು ಲಾಗಿನ್ ಆಗಿರುವುದು ಆವಶ್ಯಕ.",
        "resetpass-submit-loggedin": "ಪ್ರವೇಶಪದ ಬದಲಾಯಿಸು",
index f7ab4f8..1b8d809 100644 (file)
        "passwordpolicies-policy-passwordcannotmatchusername": "비밀번호는 사용자 이름과 같을 수 없습니다",
        "passwordpolicies-policy-passwordcannotmatchblacklist": "비밀번호는 블랙리스트에 있는 비밀번호와 일치할 수 없습니다",
        "passwordpolicies-policy-maximalpasswordlength": "비밀번호는 적어도 $1 {{PLURAL:$1|자}} 미만이어야 합니다",
-       "passwordpolicies-policy-passwordcannotbepopular": "비밀번호는 {{PLURAL:$1|저명한 비밀번호가 될|$1개의 저명한 비밀번호에 속할}} 수 없습니다"
+       "passwordpolicies-policy-passwordcannotbepopular": "비밀번호는 {{PLURAL:$1|저명한 비밀번호가 될|$1개의 저명한 비밀번호에 속할}} 수 없습니다",
+       "easydeflate-invaliddeflate": "주어진 컨텐츠가 적절히 압축되지 않았습니다"
 }
index 78a2748..cfd23ac 100644 (file)
        "resetpass-submit-loggedin": "Keisti slaptažodį",
        "resetpass-submit-cancel": "Atšaukti",
        "resetpass-wrong-oldpass": "Klaidingas laikinas ar esamas slaptažodis.\nJūs galbūt jau sėkmingai pakeitėte savo slaptažodį ar jau prašėte naujo laikino slaptažodžio.",
-       "resetpass-recycled": "Atkurkite savo slaptažodį kitokiu, nei buvo prieš tai.",
+       "resetpass-recycled": "Pakeiskite savo slaptažodį kitokiu, nei buvo prieš tai.",
        "resetpass-temp-emailed": "Jūs prisijungęs laikinu slaptažodžiu, gautu per elektroninį paštą. Kad baigtumėte jungtis, čia turite nustatyti naują slaptažodį:",
        "resetpass-temp-password": "Laikinas slaptažodis:",
        "resetpass-abort-generic": "Slaptažodžio keitimas buvo nutrauktas nuo ekstenzijos.",
        "resetpass-expired": "Jūsų slaptažodžio galiojimas baigėsi. Prašome nustatyti naują prisijungimo slaptažodį.",
-       "resetpass-expired-soft": "Jūsų slaptažodžio galiojimas baigėsi ir jį reikia atkurti iš naujo. Pasirinkite naują slaptažodį dabar arba spauskite \"{{int:authprovider-resetpass-skip-label}}\", kad būtų atstatytas vėliau.",
-       "resetpass-validity-soft": "Jūsų slaptažodis netinkamas: $1\n\nPasirinkite naują slaptažodį dabar arba spauskite \"{{int:authprovider-resetpass-skip-label}}\", kad būtų atkurtas vėliau.",
+       "resetpass-expired-soft": "Jūsų slaptažodžio galiojimas baigėsi ir jį reikia pakeisti. Pasirinkite naują slaptažodį dabar arba spauskite \"{{int:authprovider-resetpass-skip-label}}\", kad būtų pakeistas vėliau.",
+       "resetpass-validity-soft": "Jūsų slaptažodis netinkamas: $1\n\nPasirinkite naują slaptažodį dabar arba spauskite \"{{int:authprovider-resetpass-skip-label}}\", kad būtų pakeistas vėliau.",
        "passwordreset": "Atkurti slaptažodį",
        "passwordreset-text-one": "Užpildykite šią formą, norėdami atkurti savo slaptažodį.",
        "passwordreset-text-many": "{{PLURAL:$1|Užpildykite vieną iš laukelių, kad el. paštu gautumėte laikinąjį slaptažodį.}}",
        "filehist-filesize": "Rinkmenos dydis",
        "filehist-comment": "Paaiškinimas",
        "imagelinks": "Rinkmenos naudojimas",
-       "linkstoimage": "{{PLURAL:$1|Šis puslapis|Šie puslapiai}} nurodo į šią rinkmeną:",
-       "linkstoimage-more": "Daugiau nei $1 {{PLURAL:$1|puslapis|puslapiai|puslapių}} rodo į šį failą.\nŠis sąrašas rodo tik {{PLURAL:$1|puslapio|pirmų $1 puslapių}} nuorodas į šį failą.\nYra pasiekiamas ir [[Special:WhatLinksHere/$2|visas sąrašas]].",
-       "nolinkstoimage": "Į rinkmeną nenurodo joks puslapis.",
+       "linkstoimage": "{{PLURAL:$1|Šis puslapis|Šie puslapiai}} naudoja šią rinkmeną:",
+       "linkstoimage-more": "Daugiau nei $1 {{PLURAL:$1|puslapis|puslapiai|puslapių}} naudoja šią rinkmeną.\nŠis sąrašas rodo tik {{PLURAL:$1|puslapį, naudojantį|pirmus $1 puslapius, naudojančius|pirmus $1 puslapių, naudojančių}} šį failą.\nYra pasiekiamas ir [[Special:WhatLinksHere/$2|visas sąrašas]].",
+       "nolinkstoimage": "Rinkmena nėra naudojama jokiame puslapyje.",
        "morelinkstoimage": "Žiūrėti [[Special:WhatLinksHere/$1|daugiau nuorodų]] į šį failą.",
        "linkstoimage-redirect": "$1 (failo peradresavimas) $2",
        "duplicatesoffile": "Šis failas turi {{PLURAL:$1|$1 dublikatą|$1 dublikatus|$1 dublikatų}} ([[Special:FileDuplicateSearch/$2|daugiau informacijos]]):",
index a225307..d727456 100644 (file)
@@ -62,7 +62,7 @@
        "thursday": "ꯁꯥꯒꯣꯜꯁꯦꯟ",
        "friday": "ꯏꯔꯥꯏ",
        "saturday": "ꯊꯥꯡꯖꯥ",
-       "sun": "ê¯\85ꯨê¯\83ꯤꯠ",
+       "sun": "ê¯\85ꯣꯡ",
        "mon": "ꯅꯤꯡ",
        "tue": "ꯂꯩ",
        "wed": "ꯌꯨꯝ",
        "tool-link-emailuser": "Email this {{GENDER:$1|user}}",
        "imagepage": "File lamai du ootlu",
        "mediawikipage": "ꯄꯥꯎꯖꯦꯜꯒꯤ ꯂꯥꯃꯥꯏꯗꯨ ꯎꯨꯠꯂꯨ",
-       "templatepage": "ê¯\87ꯦê¯\9dê¯\84ê¯\82ꯦꯠê¯\80ꯤ ê¯\82ꯥê¯\83ꯥê¯\8fê¯\97ꯨ ê¯\8eꯨꯠê¯\82ꯨ",
+       "templatepage": "ꯇꯦꯝꯄꯂꯦꯠꯀꯤ ꯂꯃꯥꯏꯗꯨ ꯎꯨꯠꯂꯨ",
        "viewhelppage": "ꯃꯇꯦꯡ ꯄꯥꯡꯅꯕꯒꯤ ꯂꯥꯃꯥꯏꯗꯨ ꯎꯨꯠꯂꯨ",
-       "categorypage": "Macahkhaiba lamai oootlooo",
+       "categorypage": "ꯃꯆꯥꯈꯥꯏꯕ ꯂꯃꯥꯏꯗꯨ ꯎꯨꯠꯂꯨ",
        "viewtalkpage": "ꯈꯟꯅꯥ ꯅꯩꯅꯕꯗꯨ ꯎꯨꯠꯂꯨ",
        "otherlanguages": "ꯑꯇꯣꯞꯄꯥ ꯂꯣꯟꯁꯤꯡꯗꯥ",
        "redirectedfrom": "(Redirected from $1)",
        "edithelp": "ꯁꯦꯝꯒꯠꯅꯕꯥ ꯃꯥꯇꯦꯡ",
        "helppage-top-gethelp": "ꯃꯥꯇꯦꯡ",
        "mainpage": "ꯃꯔꯨꯑꯣꯏꯕ ꯂꯃꯥꯏ",
-       "mainpage-description": "ꯃꯔꯨ ꯑꯣꯏꯕꯥ ꯂꯃꯥꯏ",
+       "mainpage-description": "ꯃꯔꯨꯑꯣꯏꯕ ꯂꯃꯥꯏ",
        "policy-url": "Project:ꯈꯣꯡꯊꯥꯡ",
        "portal": "ꯃꯤꯌꯥꯝꯒꯤ ꯄꯣꯔꯇꯦꯜ",
        "portal-url": "Project:ꯃꯤꯌꯥꯝꯒꯤ ꯄꯣꯔꯇꯦꯜ",
        "newmessageslinkplural": "{{PLURAL:$1|a new message|999=new messages}}",
        "newmessagesdifflinkplural": "ꯑꯔꯣꯏꯕꯥ {{PLURAL:$1|change|999=changes}}",
        "youhavenewmessagesmulti": "$1 ꯅꯪꯒꯤ ꯑꯅꯧꯕꯥ ꯃꯦꯁꯦꯁ",
-       "editsection": "ꯁꯦꯝꯒꯠꯄ",
-       "editold": "ꯁꯦꯝꯒꯠꯄ",
+       "editsection": "ꯁꯦꯝꯒꯠꯄ",
+       "editold": "ꯁꯦꯝꯒꯠꯄ",
        "viewsourceold": "ꯍꯧꯔꯛꯐꯝ ꯎꯨꯇꯂꯨ",
        "editlink": "ꯁꯦꯝꯒꯠꯄꯥ",
        "viewsourcelink": "ꯍꯧꯔꯛꯐꯝ ꯎꯨꯇꯂꯨ",
        "editsectionhint": "ꯁꯦꯝꯒꯠꯄꯒꯤ ꯁꯔꯨꯛ: $1",
        "toc": "ꯑꯌꯥꯎꯕꯥ",
        "showtoc": "ꯎꯨꯠꯂꯨ",
-       "hidetoc": "ꯂꯣꯇꯄ",
+       "hidetoc": "ꯂꯣꯇꯄ",
        "collapsible-collapse": "ꯁꯨꯞꯆꯤꯟꯕꯥ",
-       "collapsible-expand": "ꯄꯥꯛꯊꯣꯛꯄ",
+       "collapsible-expand": "ꯄꯥꯛꯊꯣꯛꯄ",
        "confirmable-confirm": "Are {{GENDER:$1|you}} sure?",
        "confirmable-yes": "ꯍꯣꯏ",
        "confirmable-no": "ꯅꯠꯇꯦ",
        "red-link-title": "$1 ꯂꯃꯥꯏꯗꯨ ꯂꯩꯇꯔꯦ",
        "sort-descending": "ꯑꯇꯦꯟꯕꯥ ꯍꯟꯊꯔꯛꯂꯤꯕꯥ",
        "sort-ascending": "ꯑꯇꯦꯟꯕꯥ ꯍꯦꯟꯒꯠꯂꯛꯂꯤꯕꯥ",
-       "nstab-main": "ê¯\82ꯥê¯\83ꯥê¯\8f",
-       "nstab-user": "Sijinnariba Lamai",
-       "nstab-media": "ꯃꯦꯗꯤꯌꯥꯒꯤ ꯂꯥꯃꯥꯏ",
-       "nstab-special": "MediaWiki:Bs-wikiadmin-mediawiki-akhannaba-lamai-text/mni",
-       "nstab-project": "ê¯\84ꯥꯡê¯\8aꯣê¯\9bê¯\80ê¯\97ê¯\95ꯥ ê¯\82ꯥê¯\83ꯥê¯\8f",
+       "nstab-main": "ꯂꯃꯥꯏ",
+       "nstab-user": "ꯁꯤꯖꯤꯟꯅꯔꯤꯕ ꯂꯃꯥꯏ",
+       "nstab-media": "ꯃꯦꯗꯤꯌꯥ ꯂꯃꯥꯏ",
+       "nstab-special": "ꯑꯈꯟꯅꯕ ꯂꯃꯥꯏ",
+       "nstab-project": "ꯄꯥꯡꯊꯣꯛꯀꯗꯕꯥ ꯂꯃꯥꯏ",
        "nstab-image": "ꯈꯣꯝꯖꯤꯟꯗꯨꯅꯥ ꯍꯥꯞꯐꯝ",
        "nstab-mediawiki": "ꯄꯥꯎꯖꯦꯜ",
        "nstab-template": "ꯇꯦꯝꯄꯂꯦꯠ",
index 1918083..bc1eb57 100644 (file)
        "ns-specialprotected": "'E ppaggene spiciale nun se ponno cagnà.",
        "titleprotected": "'A criazione 'e stu titolo è stata bloccata 'a ll'utente [[User:$1|$1]].\n'A ragione è chesta: <em>$2</em>.",
        "filereadonlyerror": "Nun se può cagnà 'o file \"$1\" pecché 'o repository 'e file \"$2\" sta 'n modo sulo-lettura.\n\nL'ammenistratore 'e sistema che l'ave arrestato ha dato sta ragione: \"$3\".",
+       "invalidtitle": "Titolo invalido",
        "invalidtitle-knownnamespace": "Titolo nun buono c' 'o namespace \"$2\" e testo \"$3\"",
        "invalidtitle-unknownnamespace": "Titolo nun buono c' 'o namespace scanusciuto \"$1\" e testo \"$2\"",
        "exception-nologin": "Acciesso nun affettuato",
        "resetpass-submit-loggedin": "Cagna password",
        "resetpass-submit-cancel": "Scancella",
        "resetpass-wrong-oldpass": "'A password temporanea o attuale nun è bbona.\n'A password putesse avé cagnato, o pure s'è addimannata na password temporanea nova.",
-       "resetpass-recycled": "Pe piacere riabbiate 'a password e mettete na password differénte a chella 'e mmò.",
+       "resetpass-recycled": "Pe piacere cagnat 'a password e mettete na password differénte a chella 'e mmò.",
        "resetpass-temp-emailed": "Sì trasuto cu nu codece temporaneo, mannato via e-mail. Pe' fà cumpleta 'a riggistraziona, avite 'e abbià na password nova ccà:",
        "resetpass-temp-password": "Password temporanea:",
        "resetpass-abort-generic": "'O cagnamiento d' 'a password s'è spezzato 'a na stensione.",
        "resetpass-expired": "'A pasword è ammaturata. Avite 'e ffà na password nova pe putè trasì.",
-       "resetpass-expired-soft": "'A pasword toja è ammaturata e s'adda riabbià. Avite 'e scegliere na password nova mò, o ffà click ncopp'a \"{{int:authprovider-resetpass-skip-label}}\" p' 'a riabbià aroppo.",
-       "resetpass-validity-soft": "'A password toja nun è bbona: $1\n\nAvite 'e scegliere na password nova mò, o ffà click ncopp'a \"{{int:authprovider-resetpass-skip-label}}\" p' 'a riabbià aròppo.",
+       "resetpass-expired-soft": "'A pasword vuost è ammaturata e s'adda cagnà. Avite 'e scegliere na password nova mò, o ffà click ncopp'a \"{{int:authprovider-resetpass-skip-label}}\" p' 'a cagnà aroppo.",
+       "resetpass-validity-soft": "'A password toja nun è bbona: $1\n\nAvite 'e scegliere na password nova mò, o ffà click ncopp'a \"{{int:authprovider-resetpass-skip-label}}\" p' 'a cagnà aròppo.",
        "passwordreset": "Riabbìa 'a password",
        "passwordreset-text-one": "Ghienche stu modulo pe' ricevere na mmasciata e-mail c' 'a password temporanea.",
        "passwordreset-text-many": "{{PLURAL:$1|Ghienche uno d' 'e campe pe' ricevere na password temporanea cu na mmasciata e-mail.}}",
        "parser-template-loop-warning": "È stato scummigliato n'aniello d' 'o template: [[$1]]",
        "template-loop-category": "Paggene ca chiammassero a esse stisse",
        "template-loop-category-desc": "Sta paggena tenesse nu template ca chiammasse a essa stissa, cioè nu template addò sta mmescat' 'o template ca 'o chiammasse.",
+       "template-loop-warning": "<strong>Attenziò:</strong> sta paggena chiammass' a [[:$1]] e stu fatto 'a facess addeventà nu loop (na chiammata infinita d' 'o template).",
        "parser-template-recursion-depth-warning": "È arrivato 'o lemmeto 'e ricurzione d' 'o template ($1)",
        "language-converter-depth-warning": "'O fùto d' 'o lemmeto d' 'o scagnatòre 'e lengua è appassato ($1)",
        "node-count-exceeded-category": "Paggene addò 'o nummero 'e núrece è stato appassato",
        "expansion-depth-exceeded-warning": "Sta paggena ha appassato 'o lemmeto 'e futo 'e spansione",
        "parser-unstrip-loop-warning": "Scummigliato aniello Unstrip",
        "unstrip-depth-warning": "Appassato 'o lémmeto 'e ricurzione d' Unstrip ($1)",
+       "unstrip-depth-category": "Paggene addò ll' unstrip depth limit è assaje for o limmeto",
+       "unstrip-size-warning": "Appassato 'o lémmeto 'e gruosso d' Unstrip ($1)",
+       "unstrip-size-category": "Paggene addò 'o lémmeto 'e gruosso e unstrip è appassatt",
        "converter-manual-rule-error": "È stato scummigliato n'errore dint'a regola manuale 'e converziona 'e lengua",
        "undo-success": "'O cagnamiento se può annullà.\nPe' piacere vedete 'e differenze mmustate nfra 'e verziune pe' te ffà capace ca 'e cuntenute songo bbuone, e astipate 'e cagnamiente ccà abbascio pe' fernì e accussì turnà arreto.",
        "undo-failure": "Nun se può fà turnà arreto 'o cagnamiento pecché ce sta nu conflitto ch' 'e cagnamiente intermedie.",
+       "undo-main-slot-only": "Stu cagnamento nun se pò turnà arreto pecché ce vulessero 'e cuntenute for' 'o main slot.",
        "undo-norev": "Nun se può fà turnà arreto 'o cagnamiento pecché nun esiste o s'è scancellato.",
        "undo-nochange": "Pare ca sto cagnamiento s'ha scancellato già.",
        "undo-summary": "Scancella 'o càgno $1 'e [[Special:Contributions/$2|$2]] ([[User talk:$2|Chiàcchiera]])",
        "diff-multi-sameuser": "({{PLURAL:$1|Na verziona ntermedia|$1 verziune ntermedie}} 'e n'utente stisso nun {{PLURAL:$1|è mmustata|songo mmustate}})",
        "diff-multi-otherusers": "({{PLURAL:$1|Na virzione ntermedia|$1 verziune ntermedie}} 'a {{PLURAL:$2|n'at'utente|$2 n'ati ddoj'utente}} nun è mmustata)",
        "diff-multi-manyusers": "({{PLURAL:$1|Na virzione ntermedia|$1 verziune ntermedie}} 'a cchiù 'e $2 {{PLURAL:$2|utente|utente}} nun è mmustata)",
+       "diff-paragraph-moved-tonew": "'O paragrafo è stato spustat. Facite clic pe' puté cagnà dint'a nova posiziona.",
        "difference-missing-revision": "{{PLURAL:$2|Na virziona|$2 verziune}} 'e sta differenza ($1) {{PLURAL:$2|nun è stata truvata|nun so' state truvate}}.\n\nChest'è succiesso quanno s'è secutato nu diff obsoleto a na paggena scancellata.\n'E dettaglie se ponno truvà dint'a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 'o riggistro 'e scancellamiente].",
        "searchresults": "Risultato d''a recerca",
        "searchresults-title": "Ascià risultate ppe \"$1\"",
index cffe621..dee448f 100644 (file)
        "recentchangeslinked-page": "Sidenavn:",
        "recentchangeslinked-to": "Vis endringer på sider som lenker til den gitte siden istedet",
        "recentchanges-page-added-to-category": "[[:$1]] ble lagt til i kategorien",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] lagt til i kategori, [[Special:WhatLinksHere/$1|denne siden er inkludert i andre sider]]",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] lagt til i kategorien; [[Special:WhatLinksHere/$1|denne siden er inkludert i andre sider]]",
        "recentchanges-page-removed-from-category": "[[:$1]] fjernet fra kategori",
        "recentchanges-page-removed-from-category-bundled": "[[:$1]] fjernet fra kategori, [[Special:WhatLinksHere/$1|denne siden er inkludert i andre sider]]",
        "autochange-username": "Automatisk MediaWiki-endring",
index c3c8b24..e8d6c7a 100644 (file)
        "logout": "Ofmelden",
        "userlogout": "Aofmelden",
        "notloggedin": "Neet an-emelded",
-       "userlogin-noaccount": "Heb jy noch gyn gebrukersname?",
+       "userlogin-noaccount": "Heb jy noch geen gebrukersname?",
        "userlogin-joinproject": "Wörd lid van {{SITENAME}}",
        "createaccount": "Inskryven",
        "userlogin-resetpassword-link": "Juuw wachtwoord vergeaten?",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|dag|dagen}}",
        "rcfilters-days-show-hours": "$1 {{PLURAL:$1|uur|uren}}",
        "rcfilters-quickfilters": "Up-eslöägen filters",
-       "rcfilters-quickfilters-placeholder-title": "Noch gyn filters up-eslöägen",
+       "rcfilters-quickfilters-placeholder-title": "Noch geen filters up-eslöägen",
        "rcfilters-quickfilters-placeholder-description": "Üm juuw filterinstellingen up te slån en et låter te gebruken, klik up et bladwyserikoon underan by \"Aktive filters\".",
        "rcfilters-savedqueries-apply-label": "Instellingen opslaon",
        "rcfilters-savedqueries-cancel-label": "Aofbreken",
        "rcfilters-restore-default-filters": "Standardfilters weerummezetten",
        "rcfilters-clear-all-filters": "Alle filters vortdoon",
        "rcfilters-show-new-changes": "Låt nyste wysigingen seen",
-       "rcfilters-search-placeholder": "Filter wysigingen (gebruuk et menü of söök up filtername)",
+       "rcfilters-search-placeholder": "Filter wysigingen (gebruuk et menu of söök up filtername)",
        "rcfilters-filterlist-feedbacklink": "Låt uns weaten wat jy van disse (nye) filterhülpmiddels vinden",
        "rcfilters-highlightbutton-title": "Resultåten markeren",
        "rcfilters-highlightmenu-title": "Kies n kleur",
index 3faa6ba..5a7b140 100644 (file)
        "copyrightwarning2": "Wszelki wkład na {{SITENAME}} może być edytowany, zmieniany lub usunięty przez innych użytkowników.\nJeśli nie chcesz, żeby Twój tekst był dowolnie zmieniany przez każdego i rozpowszechniany bez ograniczeń, nie umieszczaj go tutaj.<br />\nZapisując swoją edycję, oświadczasz, że ten tekst jest Twoim dziełem lub pochodzi z materiałów dostępnych na warunkach ''domeny publicznej'' lub kompatybilnych (zobacz także $1).\n'''PROSZĘ NIE WPROWADZAĆ MATERIAŁÓW CHRONIONYCH PRAWEM AUTORSKIM BEZ POZWOLENIA WŁAŚCICIELA!'''",
        "editpage-cannot-use-custom-model": "Model zawartości tej strony nie może być zmieniony.",
        "longpageerror": "'''Błąd! Wprowadzony przez Ciebie tekst ma {{PLURAL:$1|1 kilobajt|$1 kilobajty|$1 kilobajtów}}. Długość tekstu nie może przekraczać {{PLURAL:$2|1 kilobajt|$2 kilobajty|$2 kilobajtów}}. Tekst nie może być zapisany.'''",
-       "readonlywarning": "<strong>Uwaga! Baza danych została zablokowana do celów administracyjnych. W tej chwili nie można zapisać nowej wersji strony. Jeśli chcesz, możesz skopiować ją do pliku, aby móc zapisać ją później.</strong>\n\nAdministrator systemu, który zablokował bazę, podał następujący powód: $1",
+       "readonlywarning": "<strong>Uwaga! Baza danych została zablokowana w celach konserwacyjnych i w tej chwili nie można zapisać nowej wersji strony. Jeśli chcesz, możesz skopiować ją do pliku, aby móc zapisać ją później.</strong>\n\nAdministrator systemu, który zablokował bazę, podał następujący powód: $1",
        "protectedpagewarning": "'''Uwaga! Możliwość modyfikacji tej strony została zabezpieczona. Mogą ją edytować jedynie użytkownicy z uprawnieniami administratora.'''\nOstatni wpis z rejestru jest pokazany poniżej.",
        "semiprotectedpagewarning": "<strong>Uwaga:</strong>  Ta strona została zabezpieczona i tylko zarejestrowani użytkownicy mogą ją edytować.\nOstatni wpis z rejestru jest pokazany poniżej:",
        "cascadeprotectedwarning": "<strong>Uwaga:</strong> Ta strona została zabezpieczona i tylko użytkownicy z [[Special:ListGroupRights|określonymi uprawnieniami]] mogą ją edytować. Została ona osadzona w {{PLURAL:$1|następującej stronie, która została zabezpieczona|następujących stronach, które zostały zabezpieczone}} z włączoną opcją dziedziczenia:",
index 591f262..1a81188 100644 (file)
        "cascadeprotected": "Сторінка є замнкута, бо є вложена до  {{PLURAL:$1|наслїдуючой сторінкы замкнуты|наслїдуючіх сторінок замнкнутых|наслїдуючіх сторінок замнкнутых}} каскадовым замком:\n$2",
        "namespaceprotected": "Не маєте права едітовати сторінкы в просторї  назв «$1».",
        "customcssprotected": "Не маєте права едітовати тоту сторінку з CSS, бо обсягує персоналны наставлїна іншого хоснователя.",
+       "customjsonprotected": "Не маєте права едітовати тоту сторінку з JSON, бо обсягує персоналны наставлїна іншого хоснователя.",
        "customjsprotected": "Не маєте права едітовати тоту сторінку з JavaScript-ом, бо обсягує персоналны наставлїна іншого хоснователя.",
        "mycustomcssprotected": "Не мате права на управы той CSS сторінкы.",
+       "mycustomjsonprotected": "Не мате права на едітованя той JSON сторінкы.",
        "mycustomjsprotected": "Не мате права на едітованя той JavaScript сторінкы.",
        "myprivateinfoprotected": "Не мате дозволїня мінити свої пріватны інформації.",
        "mypreferencesprotected": "Не мате дозволїня мінити свої наставлїня.",
index 18a7765..3b52124 100644 (file)
        "cascadeprotected": "Táto stránka bola zamknutá proti úpravám, pretože je použitá na {{PLURAL:$1|nasledovnej stránke, ktorá je zamknutá|nasledovných stránkach, ktoré sú zamknuté}} voľbou „kaskádového zamknutia“:\n$2",
        "namespaceprotected": "Nemáte povolenie upravovať stránky v mennom priestore '''$1'''.",
        "customcssprotected": "Nemáte právo upravovať túto CSS stránku, pretože obsahuje osobné nastavenie iného používateľa.",
+       "customjsonprotected": "Nemáte právo upravovať túto JSON stránku, pretože obsahuje osobné nastavenie iného používateľa.",
        "customjsprotected": "Nemáte právo upravovať túto JavaScript stránku, pretože obsahuje osobné nastavenie iného používateľa.",
        "mycustomcssprotected": "Nemáte povolenie na úpravu tejto CSS stránky.",
+       "mycustomjsonprotected": "Nemáte povolenie na úpravu tejto JSON stránky.",
        "mycustomjsprotected": "Nemáte povolenie na úpravu tejto JavaScriptovej stránky.",
        "myprivateinfoprotected": "Nemáte povolenie na úpravu vašich súkromných informácií.",
        "mypreferencesprotected": "Nemáte povolenie na úpravu vašich nastavení.",
index 6f69618..3d87639 100644 (file)
        "botpasswords-no-provider": "BotPasswordsSessionProvider није доступан.",
        "botpasswords-restriction-failed": "Не можете се пријавити због ограничења лозинки за ботове.",
        "botpasswords-not-exist": "Корисник „$1“ нема лозинку бота „$2“.",
-       "resetpass_forbidden": "Ð\9bозинка Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð±Ð¸Ñ\82и Ð¿Ñ\80омеÑ\9aена",
-       "resetpass_forbidden-reason": "Ð\9bозинке Ð½Ð¸Ñ\98е Ð¼Ð¾Ð³Ñ\83Ñ\9bе Ð¿Ñ\80омениÑ\82и: $1",
+       "resetpass_forbidden": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð´Ð° Ð¿Ñ\80оменим Ð»Ð¾Ð·Ð¸Ð½ÐºÐµ",
+       "resetpass_forbidden-reason": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð´Ð° Ð¿Ñ\80оменим Ð»Ð¾Ð·Ð¸Ð½ÐºÐµ: $1",
        "resetpass-no-info": "Морате бити пријављени да бисте приступили овој страници.",
        "resetpass-submit-loggedin": "Промени лозинку",
        "resetpass-submit-cancel": "Откажи",
        "diff-multi-sameuser": "({{PLURAL:$1|Једна међуревизија истог корисника није приказана|$1 међуревизија истог корисника нису приказане|$1 међуревизија истог корисника није приказано}})",
        "diff-multi-otherusers": "({{PLURAL:$1|Једна међуревизија|$1 међуревизије|$1 међуревизија}} од стране {{PLURAL:$2|још једног корисника није приказана|$2 корисника није приказано}})",
        "diff-multi-manyusers": "({{PLURAL:$1|Није приказана међуизмена|Нису приказане $1 међуизмене|Није приказано $1 међуизмена}} од више од $2 корисника)",
-       "diff-paragraph-moved-tonew": "Пасус је премештен. Кликните да пређете на његово ново место.",
-       "diff-paragraph-moved-toold": "Ð\9fаÑ\81Ñ\83Ñ\81 Ñ\98е Ð¿Ñ\80емеÑ\88Ñ\82ен. Ð\9aликниÑ\82е Ð´Ð° Ð¿Ñ\80еÑ\92еÑ\82е Ð½Ð° Ñ\9aегово Ñ\81Ñ\82аÑ\80о Ð¼ÐµÑ\81Ñ\82о.",
+       "diff-paragraph-moved-tonew": "Пасус је премештен. Кликните да пређете на нову локацију.",
+       "diff-paragraph-moved-toold": "Ð\9fаÑ\81Ñ\83Ñ\81 Ñ\98е Ð¿Ñ\80емеÑ\88Ñ\82ен. Ð\9aликниÑ\82е Ð´Ð° Ð¿Ñ\80еÑ\92еÑ\82е Ð½Ð° Ñ\81Ñ\82аÑ\80Ñ\83 Ð»Ð¾ÐºÐ°Ñ\86иÑ\98Ñ\83.",
        "difference-missing-revision": "{{PLURAL:$2|Једна ревизија|$2 ревизије}} ове разлике ($1) не {{PLURAL:$2|постоји|постоје}}.\n\nОво се обично дешава када пратите застарели линк до странице која је избрисана.\nДетаље можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].",
        "searchresults": "Резултати претраге",
        "search-filter-title-prefix-reset": "Претражи све странице",
        "right-block": "блокирање даљих измена других корисника",
        "right-blockemail": "блокирање корисника да шаљу имејл",
        "right-hideuser": "блокирање корисничког имена и његово сакривање од јавности",
-       "right-ipblock-exempt": "заобилажење IP блокада, самоблокада и блокада опсега",
+       "right-ipblock-exempt": "заобилажење IP блокада, аутоблокада и блокада опсега",
        "right-unblockself": "деблокирање самог себе",
        "right-protect": "мењање нивоа заштите и уређивање страница под преносивом заштитом",
        "right-editprotected": "уређивање страница под заштитом „{{int:protect-level-sysop}}“",
        "fileexists": "Датотека с овим именом већ постоји. Погледајте <strong>[[:$1]]</strong> ако нисте сигурни да ли желите да је промените.\n[[$1|thumb]]",
        "filepageexists": "Страница с описом ове датотеке је већ направљена овде <strong>[[:$1]]</strong>, иако датотека не постоји.\nОпис који сте навели се неће појавити на страници с описом.\nДа би се ваш опис овде нашао, потребно је да га ручно измените.\n[[$1|thumb]]",
        "fileexists-extension": "Датотека са сличним називом већ постоји: [[$2|thumb]]\n* Назив датотеке коју шаљете: <strong>[[:$1]]</strong>\n* Назив постојеће датотеке: <strong>[[:$2]]</strong>\nДа ли желите да користите препознатљивије име?",
-       "fileexists-thumbnail-yes": "Ð\98згледа Ð´Ð° Ñ\98е Ð´Ð°Ñ\82оÑ\82ека Ñ\83маÑ\9aено Ð¸Ð·Ð´Ð°Ñ\9aе Ñ\81лике ''(thumbnail)''.\n[[$1|thumb]]\nÐ\9fÑ\80овеÑ\80иÑ\82е Ð´Ð°Ñ\82оÑ\82екÑ\83 <strong>[[:$1]]</strong>.\nÐ\90ко Ñ\98е Ð¿Ñ\80овеÑ\80ена Ð´Ð°Ñ\82оÑ\82ека Ð¸Ñ\81Ñ\82а Ñ\81лика Ð¾Ñ\80игиналне Ð²ÐµÐ»Ð¸Ñ\87ине, Ð½Ð¸Ñ\98е Ð¿Ð¾Ñ\82Ñ\80ебно Ñ\81лаÑ\82и Ð´Ð¾Ð´Ð°Ñ\82нÑ\83 Ñ\81лику.",
-       "file-thumbnail-no": "Ð\94аÑ\82оÑ\82ека Ð¿Ð¾Ñ\87иÑ\9aе Ñ\81а <strong>$1</strong>.\nÐ\98згледа Ð´Ð° Ñ\81е Ñ\80ади Ð¾ Ñ\83маÑ\9aеноÑ\98 Ñ\81лиÑ\86и ''(thumbnail)''.\nУколико Ð¸Ð¼Ð°Ñ\82е Ð¾Ð²Ñ\83 Ñ\81ликÑ\83 Ñ\83 Ð¿Ñ\83ноÑ\98 Ð²ÐµÐ»Ð¸Ñ\87ини, Ð¿Ð¾Ñ\88аÑ\99иÑ\82е Ñ\98е, Ð° Ð°ÐºÐ¾ Ð½ÐµÐ¼Ð°Ñ\82е, Ð¿Ñ\80омениÑ\82е Ð½Ð°Ð·Ð¸Ð² датотеке.",
+       "fileexists-thumbnail-yes": "Ð\98згледа Ð´Ð° Ñ\98е Ð´Ð°Ñ\82оÑ\82ека Ñ\81лика Ñ\83маÑ\9aене Ð²ÐµÐ»Ð¸Ñ\87ине <em>(Ñ\81лиÑ\87иÑ\86а)</em>.\n[[$1|thumb]]\nÐ\9fÑ\80овеÑ\80иÑ\82е Ð´Ð°Ñ\82оÑ\82екÑ\83 <strong>[[:$1]]</strong>.\nÐ\90ко Ñ\98е Ð¿Ñ\80овеÑ\80ена Ð´Ð°Ñ\82оÑ\82ека Ð¸Ñ\81Ñ\82а Ñ\81лика Ð¿Ñ\80вобиÑ\82не Ð²ÐµÐ»Ð¸Ñ\87ине, Ð½Ð¸Ñ\98е Ð¿Ð¾Ñ\82Ñ\80ебно Ð¾Ñ\82пÑ\80емаÑ\82и Ð´Ð¾Ð´Ð°Ñ\82ну.",
+       "file-thumbnail-no": "Ð\98ме Ð´Ð°Ñ\82оÑ\82еке Ð¿Ð¾Ñ\87иÑ\9aе Ñ\81а <strong>$1</strong>.\nÐ\98згледа Ð´Ð° Ñ\81е Ñ\80ади Ð¾ Ñ\81лиÑ\86и Ñ\83маÑ\9aене Ð²ÐµÐ»Ð¸Ñ\87ине <em>(Ñ\81лиÑ\87иÑ\86а)</em>.\nÐ\90ко Ð¸Ð¼Ð°Ñ\82е Ð¾Ð²Ñ\83 Ñ\81ликÑ\83 Ñ\83 Ð¿Ñ\83ноÑ\98 Ñ\80езолÑ\83Ñ\86иÑ\98и, Ð¾Ñ\82пÑ\80емиÑ\82е Ñ\98е, Ñ\83 Ð¿Ñ\80оÑ\82ивном, Ð¿Ñ\80омениÑ\82е Ð¸Ð¼Ðµ датотеке.",
        "fileexists-forbidden": "Датотека с овим називом већ постоји и не може се заменити.\nАко и даље желите да пошаљете датотеку, вратите се и изаберите други назив.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Датотека са овим именом већ постоји у заједничкој остави.\nАко још увек желите да отпремите датотеку, вратите се и користите ново име.\n[[File:$1|thumb|center|$1]]",
        "fileexists-no-change": "Датотека је дупликат актуелне верзије <strong>[[:$1]]</strong>.",
        "uploadstash-badtoken": "Извршавање ове радње није успело, разлог томе може бити истек времена за уређивање. Покушајте поново.",
        "uploadstash-errclear": "Чишћење датотека није успело.",
        "uploadstash-refresh": "Освежи списак датотека",
-       "uploadstash-thumbnail": "погледај минијатуру",
+       "uploadstash-thumbnail": "погледај сличицу",
        "uploadstash-exception": "Не могу сачувати датотеку у складиште ($1): „$2“.",
        "uploadstash-bad-path": "Путања не постоји.",
        "uploadstash-bad-path-invalid": "Путања није валидна.",
        "uploadstash-bad-path-unrecognized-thumb-name": "Непрепознато име минијатуре.",
        "uploadstash-bad-path-bad-format": "Кључ „$1“ није у одговарајућем облику.",
        "uploadstash-file-not-found": "Кључ „$1” није пронађен у складишту.",
-       "uploadstash-file-not-found-no-thumb": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð´Ð¾Ð±Ð¸Ñ\82и Ð¼Ð¸Ð½Ð¸Ñ\98аÑ\82Ñ\83Ñ\80у.",
+       "uploadstash-file-not-found-no-thumb": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð´Ð° Ð¿Ñ\80ибавим Ñ\81лиÑ\87иÑ\86у.",
        "uploadstash-file-not-found-no-local-path": "Нема локалне путање за умањену ставку.",
-       "uploadstash-file-not-found-no-object": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð½Ð°Ð¿Ñ\80авиÑ\82и Ð»Ð¾ÐºÐ°Ð»Ð½Ð¸ Ð´Ð°Ñ\82оÑ\82еÑ\87ни Ð¾Ð±Ñ\98екаÑ\82 Ð·Ð° Ð¼Ð¸Ð½Ð¸Ñ\98аÑ\82Ñ\83Ñ\80у.",
+       "uploadstash-file-not-found-no-object": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð´Ð° Ð½Ð°Ð¿Ñ\80авим Ð»Ð¾ÐºÐ°Ð»Ð½Ð¸ Ð´Ð°Ñ\82оÑ\82еÑ\87ни Ð¾Ð±Ñ\98екаÑ\82 Ð·Ð° Ñ\81лиÑ\87иÑ\86у.",
        "uploadstash-file-not-found-no-remote-thumb": "Добављање минијатуре није успело: $1\nАдреса = $2",
        "uploadstash-file-not-found-missing-content-type": "Недостаје заглавље за тип садржаја.",
        "uploadstash-file-not-found-not-exists": "Не могу наћи путању или ово није обична датотека.",
        "listfiles-userdoesnotexist": "Кориснички налог „$1“ није отворен.",
        "imgfile": "датотека",
        "listfiles": "Списак датотека",
-       "listfiles_thumb": "Ð\9cиниÑ\98аÑ\82Ñ\83Ñ\80а",
+       "listfiles_thumb": "СлиÑ\87иÑ\86а",
        "listfiles_date": "Датум",
        "listfiles_name": "Назив",
        "listfiles_user": "Корисник",
        "filehist-revert": "врати",
        "filehist-current": "актуелна",
        "filehist-datetime": "Датум/време",
-       "filehist-thumb": "Ð\9cиниÑ\98аÑ\82Ñ\83Ñ\80а",
+       "filehist-thumb": "СлиÑ\87иÑ\86а",
        "filehist-thumbtext": "Минијатура за верзију на дан $1",
-       "filehist-nothumb": "Ð\9dема Ñ\83маÑ\9aеног Ð¿Ñ\80иказа",
+       "filehist-nothumb": "Ð\91ез Ñ\81лиÑ\87иÑ\86е",
        "filehist-user": "Корисник",
        "filehist-dimensions": "Димензије",
        "filehist-filesize": "Величина датотеке",
        "ipb-confirm": "Потврди блокирање",
        "badipaddress": "Неважећа IP адреса",
        "blockipsuccesssub": "Блокирање је успело",
-       "blockipsuccesstext": "[[Special:Contributions/$1|$1]] је {{GENDER:$1|блокиран|блокирана|блокиран}}.<br />\nБлокирања можете да погледате [[Special:BlockList|овде]].",
+       "blockipsuccesstext": "[[Special:Contributions/$1|$1]] је {{GENDER:$1|блокиран|блокирана}}.<br />\nПогледајте [[Special:BlockList|списак]] за преглед блокада.",
        "ipb-blockingself": "Овом радњом ћете блокирати себе! Јесте ли сигурни да то желите?",
        "ipb-confirmhideuser": "Управо ћете блокирати корисника с укљученом могућношћу „сакриј корисника“. Овим ће корисничко име бити сакривено у свим списковима и извештајима. Желите ли то да урадите?",
        "ipb-confirmaction": "Ако сте сигурни да желите наставити означите поље „{{int:ipb-confirm}}“ на дну странице.",
        "movepage-moved-redirect": "Преусмерење је направљено.",
        "movepage-moved-noredirect": "Стварање преусмерења је онемогућено.",
        "articleexists": "Страница са тим именом већ постоји или име које сте одабрали није важеће.\nОдаберите друго.",
-       "cantmove-titleprotected": "Не можете да преместите страницу на то место јер је жељени наслов заштићен од стварања",
+       "cantmove-titleprotected": "Не можете да преместите страницу на ову локацију јер је прављење новог наслова заштићено.",
        "movetalk": "Премести и страницу за разговор",
        "move-subpages": "Премести и подстранице (до $1)",
        "move-talk-subpages": "Премести подстранице странице за разговор (до $1)",
        "allmessages-filter-translate": "Преведи",
        "thumbnail-more": "Повећајте",
        "filemissing": "Недостаје датотека",
-       "thumbnail_error": "Грешка при стварању минијатуре: $1",
+       "thumbnail_error": "Грешка при прављењу сличице: $1",
        "thumbnail_error_remote": "Порука о грешци из $1:\n$2",
        "djvu_page_error": "DjVu страница је ван опсега",
        "djvu_no_xml": "Не могу да преузмем XML за DjVu датотеку.",
-       "thumbnail-temp-create": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð´Ð° Ð½Ð°Ð¿Ñ\80авим Ð¿Ñ\80ивÑ\80еменÑ\83 Ð´Ð°Ñ\82оÑ\82екÑ\83 Ð¼Ð¸Ð½Ð¸Ñ\98аÑ\82Ñ\83Ñ\80е",
+       "thumbnail-temp-create": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð´Ð° Ð½Ð°Ð¿Ñ\80авим Ð¿Ñ\80ивÑ\80еменÑ\83 Ð´Ð°Ñ\82оÑ\82екÑ\83 Ð·Ð° Ñ\81лиÑ\87иÑ\86Ñ\83",
        "thumbnail-dest-create": "Не могу да сачувам минијатуру у одредишту",
        "thumbnail_invalid_params": "Неважећи параметри сличице",
        "thumbnail_toobigimagearea": "Датотека са величинама већим од $1",
        "thumbnail_gd-library": "Недовршена подешавања графичке библиотеке: недостаје функција $1",
        "thumbnail_image-size-zero": "Изгледа да је величина датотеке нула.",
        "thumbnail_image-missing": "Датотека недостаје: $1",
-       "thumbnail_image-failure-limit": "Било је превише недавних неуспешних покушаја ($1 или више) рендеровања ове минијатуре. Покушајте поново касније.",
+       "thumbnail_image-failure-limit": "Било је превише недавних неуспелих покушаја ($1 или више) рендеровања ове сличице. Покушајте поново касније.",
        "import": "Увоз страница",
        "importinterwiki": "Увоз са другог викија",
        "import-interwiki-text": "Изаберите вики и наслов странице за увоз.\nДатуми ревизија и имена уредника ће бити сачувани.\nСве радње при увозу с других викија су евидентиране у [[Special:Log/import|евиденцији увоза]].",
        "tooltip-n-currentevents": "Пронађите додатне информације о актуелностима",
        "tooltip-n-recentchanges": "Списак недавних промена на викију",
        "tooltip-n-randompage": "Учитајте случајну страницу",
-       "tooltip-n-help": "Ð\9cеÑ\81Ñ\82о Ð³Ð´Ðµ Ð¼Ð¾Ð¶ÐµÑ\82е Ð´Ð° Ð½Ð°Ñ\83Ñ\87иÑ\82е Ð½ÐµÑ\88Ñ\82о",
+       "tooltip-n-help": "Ð\9cеÑ\81Ñ\82о Ð³Ð´Ðµ Ð¼Ð¾Ð¶ÐµÑ\82е Ð½ÐµÑ\88Ñ\82о Ð´Ð° Ð½Ð°Ñ\83Ñ\87иÑ\82е",
        "tooltip-t-whatlinkshere": "Списак свих вики страница које воде овде",
        "tooltip-t-recentchangeslinked": "Недавне промене на страницама које су повезане с овом страницом",
        "tooltip-feed-rss": "RSS фид за ову страницу",
        "nextdiff": "Новија измена →",
        "mediawarning": "<strong>Упозорење:</strong> овај тип датотеке може да садржи штетан код.\nЊеговим извршавањем можете да угрозите ваш систем.",
        "imagemaxsize": "Ограничење величине слике:<br /><em>(на страницама за опис датотека)</em>",
-       "thumbsize": "Величина минијатуре:",
+       "thumbsize": "Величина сличице:",
        "widthheight": "$1 × $2",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|страница|странице|страница}}",
        "file-info": "величина датотеке: $1, MIME тип: $2",
        "file-info-png-looped": "петља",
        "file-info-png-repeat": "поновљено $1 {{PLURAL:$1|пут|пута|пута}}",
        "file-info-png-frames": "$1 {{PLURAL:$1|кадар|кадра|кадрова}}",
-       "file-no-thumb-animation": "'''Напомена: због техничких ограничења, минијатуре ове датотеке се неће анимирати.'''",
+       "file-no-thumb-animation": "<strong>Напомена: Због техничких ограничења, сличице ове датотеке неће да се анимирају.</strong>",
        "file-no-thumb-animation-gif": "'''Напомена: због техничких ограничења, минијатуре GIF слика високе резолуције као што је ова неће се анимирати.'''",
        "newimages": "Галерија нових датотека",
        "imagelisttext": "Испод је списак од '''$1''' {{PLURAL:$1|датотеке|датотеке|датотека}} поређаних $2.",
        "newimages-hidepatrolled": "Сакриј патролирана отпремања",
        "newimages-mediatype": "Тип датотеке:",
        "noimages": "Нема ништа.",
-       "gallery-slideshow-toggle": "минијатуре",
+       "gallery-slideshow-toggle": "сличице",
        "ilsubmit": "Претражи",
        "bydate": "по датуму",
        "sp-newimages-showfrom": "прикажи нове датотеке почевши од $1, $2",
        "exif-ycbcrsubsampling": "Однос величине Y према C",
        "exif-ycbcrpositioning": "Положај Y и C",
        "exif-xresolution": "Водоравна резолуција",
-       "exif-yresolution": "УÑ\81пÑ\80авна резолуција",
-       "exif-stripoffsets": "Ð\9cеÑ\81Ñ\82о Ð¿Ð¾Ð´Ð°Ñ\82ака",
+       "exif-yresolution": "Ð\92еÑ\80Ñ\82икална резолуција",
+       "exif-stripoffsets": "Ð\9bокаÑ\86иÑ\98а Ð¿Ð¾Ð´Ð°Ñ\82ака Ñ\81лике",
        "exif-rowsperstrip": "Број редова по линији",
        "exif-stripbytecounts": "Бајтова по сажетом блоку",
        "exif-jpeginterchangeformat": "Почетак JPEG прегледа",
        "exif-urgency": "Хитност",
        "exif-fixtureidentifier": "Назив рубрике",
        "exif-locationdest": "Приказана локација",
-       "exif-locationdestcode": "Код приказаног места",
+       "exif-locationdestcode": "Кôд приказане локације",
        "exif-objectcycle": "Доба дана за који је медиј намењен",
        "exif-contact": "Подаци за контакт",
        "exif-writer": "Писац",
index c4b1e48..a5978dd 100644 (file)
        "cascadeprotected": "Ova stranica je zaključana jer sadrži {{PLURAL:$1|sledeću stranicu koja je zaštićena|sledeće stranice koje su zaštićene}} „prenosivom“ zaštitom:\n$2",
        "namespaceprotected": "Nemate dozvolu da uređujete stranice u imenskom prostoru: <strong>$1</strong>.",
        "customcssprotected": "Nemate dozvolu da menjate ovu CSS stranicu jer sadrži lična podešavanja drugog korisnika.",
+       "customjsonprotected": "Nemate dozvolu da menjate ovu JSON stranicu jer sadrži lična podešavanja drugog korisnika.",
        "customjsprotected": "Nemate dozvolu da menjate ovu stranicu JavaScript jer sadrži lična podešavanja drugog korisnika.",
        "mycustomcssprotected": "Nemate dozvolu za menjanje ove CSS stranice.",
        "mycustomjsonprotected": "Nemate dozvolu za menjanje ove JSON stranice.",
index 2b44968..f95b824 100644 (file)
        "cascadeprotected": "Ta zajta je chrōniōnŏ ôd edycyje, skuli tego co je ôna wkludzōnŏ do {{PLURAL:$1|nastympujōncyj zajty, kerŏ ôstała ôchrōniōnŏ|nastympujōncych zajtach, kere ôstały ôchrōniōne}} ze załōnczōnōm ôpcyjōm erbowaniŏ:\n$2",
        "namespaceprotected": "Ńy mosz uprowńyń, coby sprowjać zajty we raumje mjan '''$1'''.",
        "customcssprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty, bo na ńij sům uosobiste sztalowańo inkszego użytkowńika.",
+       "customjsonprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty, bo na ńij sům uosobiste sztalowańo inkszego użytkowńika.",
        "customjsprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty, bo na ńij sům uosobiste sztalowańo inkszego użytkowńika.",
        "mycustomcssprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty CSS.",
+       "mycustomjsonprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty JSON.",
        "mycustomjsprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty JavaScript.",
        "myprivateinfoprotected": "Ńy mosz uprowńyń coby sprowjić swoje prywatne dane.",
        "mypreferencesprotected": "Ńy mosz uprowńyń coby sprowjić swoje sztalowańo.",
index 0824294..6548aaa 100644 (file)
        "proxyblockreason": "మీ ఐపీ అడ్రసు ఒక ఓపెన్ ప్రాక్సీ కాబట్టి దాన్ని నిరోధించాం. మీ ఇంటర్నెట్ సేవాదారుని గానీ, సాంకేతిక సహాయకుని గానీ సంప్రదించి తీవ్రమైన ఈ భద్రతా వైఫల్యాన్ని గురించి తెలపండి.",
        "sorbsreason": "{{SITENAME}} వాడే DNSBLలో మీ ఐపీ అడ్రసు ఒక ఓపెన్ ప్రాక్సీగా నమోదై ఉంది.",
        "sorbs_create_account_reason": "మీ ఐపీ అడ్రసు DNSBL లో ఓపెను ప్రాక్సీగా నమోదయి ఉంది. మీరు ఎకౌంటును సృష్టించజాలరు.",
+       "softblockrangesreason": "మీ ఐపీ అడ్రసు ($1) నుండి అజ్ఞాతంగా చేసే మార్పులకు అనుమతి లేదు. దయచేసి లాగినవండి.",
        "cant-see-hidden-user": "మీరు నిరోధించదలచిన వాడుకరి ఇప్పటికే నిరోధించబడి, దాచబడి ఉన్నారు. మీకు హక్కు లేదు కాబట్టి, ఆ వాడుకరి నిరోధాన్ని చూడటంగానీ, దాన్ని మార్చడంగానీ చెయ్యలేరు.",
        "ipbblocked": "మీరు ఇతర వాడుకరులని నిరోధించలేరు లేదా అనిరోధించలేరు, ఎందుకంటే మిమ్మల్ని మీరే నిరోధించుకున్నారు",
        "ipbnounblockself": "మిమ్మల్ని మీరే అనిరోధించుకునే అనుమతి మీకు లేదు",
        "imported-log-entries": "$1 {{PLURAL:$1|చిట్టా పద్దు దిగుమతయ్యింది|చిట్టా పద్దులు దిగుమతయ్యాయి}}.",
        "importfailed": "దిగుమతి కాలేదు: $1",
        "importunknownsource": "దిగుమతి చేసుకుంటున్న దాని మాతృక రకం తెలియదు",
-       "importcantopen": "దిగుమతి చేయబోతున్న ఫైలును తెరవలేకపోతున్నాను",
+       "importnoprefix": "అంతర్వికీ ఆదిపదం (ప్రిఫిక్స్) ఇవ్వలేదు",
+       "importcantopen": "దిగుమతి చేయదలచిన ఫైలును తెరవలేకపోయాం",
        "importbadinterwiki": "చెడు అంతర్వికీ లింకు",
        "importsuccess": "దిగుమతి పూర్తయ్యింది!",
        "importnosources": "ఏ వికీనుండి దిగుమతి చేసుకోవాలో సూచించలేదు. సూటి చరిత్ర ఎక్కింపులను అచేతనం చేసాం.",
        "import-nonewrevisions": "కూర్పులేవీ దిగుమతి కాలేదు (అవన్నీ ఈసరికే ఉండి ఉండాలి, లేదా లోపాల కారణంగా వదిలెయ్యబడ్డాయి).",
        "xml-error-string": "$1 $2వ లైనులో, వరుస $3 ($4వ బైటు): $5",
        "import-upload": "XML డేటాను అప్‌లోడు చెయ్యి",
-       "import-token-mismatch": "సెషను డేటా పోయింది.\n\nమీరు లాగౌటై పోయి ఉండవచ్చు. <strong>లాగినై ఉన్నారో లేదో చూసుకుని, మళ్ళీ ప్రయత్నించండి</strong>.\nఅది కూడా పనిచెయ్యకపోతే, ఓసారి [[Special:UserLogout|లాగౌటై]] మళ్ళీ లాగినవండి. మీ బ్రౌజరు ఈ సైటు యొక్క కూకీలను అనుమతిస్తుందని నిర్ధారించుకోండి.",
+       "import-token-mismatch": "సెషను డేటా పోయింది.\n\nమీరు లాగౌటై పోయి ఉండవచ్చు. '''లాగినై ఉన్నారో లేదో చూసుకుని, మళ్ళీ ప్రయత్నించండి'''.\nఅయినా పనిచెయ్యకపోతే, ఓసారి [[Special:UserLogout|లాగౌటై]] మళ్ళీ లాగినవండి. మీ బ్రౌజరు ఈ సైటునుండి కూకీలను అనుమతిస్తోందో లేదో చూసుకోండి.",
        "import-invalid-interwiki": "మీరు చెప్పిన వికీనుండి దిగుమతి చేయలేము.",
        "import-error-edit": "\"$1\" పేజీలో మార్పుచేర్పులు చేసే అనుమతి మీకు లేదు కాబట్టి, దాన్ని దిగుమతి చెయ్యలేదు.",
        "import-error-create": "\"$1\" పేజీని సృష్టించే అనుమతి మీకు లేదు కాబట్టి దాన్ని దిగుమతి చెయ్యలేదు.",
        "pageinfo-file-hash": "హ్యాష్ వ్యాల్యూ",
        "markaspatrolleddiff": "పరీక్షించినట్లుగా గుర్తు పెట్టు",
        "markaspatrolledtext": "ఈ వ్యాసాన్ని పరీక్షించినట్లుగా గుర్తు పెట్టు",
+       "markaspatrolledtext-file": "దస్త్రపు ఈ కూర్పు నిఘాలో ఉందని గుర్తు పెట్టు",
        "markedaspatrolled": "పరీక్షింపబడినట్లు గుర్తింపబడింది",
        "markedaspatrolledtext": "[[:$1]] యొక్క ఎంచుకున్న కూర్పుని పరీక్షించినట్లుగా గుర్తించాం.",
        "rcpatroldisabled": "ఇటీవలి మార్పుల నిఘాను అశక్తం చేసాం",
        "markedaspatrollederrortext": "నిఘాలో ఉన్నట్లు గుర్తించేందుకుగాను, కూర్పును చూపించాలి.",
        "markedaspatrollederror-noautopatrol": "మీరు చేసిన మార్పులను మీరే నిఘాలో పెట్టలేరు.",
        "markedaspatrollednotify": "$1 లో చేసిన ఈ మార్పు పర్యవేక్షణలో ఉన్నట్టుగా గుర్తించబడింది.",
+       "markedaspatrollederrornotify": "నిఘాలో ఉన్నట్టుగా గుర్తించడం విఫలమైంది.",
        "patrol-log-page": "నిఘా చిట్టా",
        "patrol-log-header": "ఇది పర్యవేక్షించిన కూర్పుల చిట్టా.",
        "confirm-markpatrolled-button": "సరే",
+       "confirm-markpatrolled-top": "$2 యొక్క కూర్పు $3 నిఘాలో ఉన్నట్టుగా గుర్తు పెట్టాలా?",
        "deletedrevision": "పాత సంచిక $1 తొలగించబడినది.",
        "filedeleteerror-short": "ఫైలు తొలగించడంలో పొరపాటు: $1",
        "filedeleteerror-long": "ఫైలుని తొలగించడంలో పొరపాట్లు జరిగాయి:\n\n$1",
        "newimages-user": "ఐపీ చిరునామా లేదా వాడుకరి పేరు",
        "newimages-newbies": "కొత్త ఖాతాల రచనలని మాత్రమే చూపించు",
        "newimages-showbots": "బాట్లు చేసిన అప్లోడ్లు చూపించు",
+       "newimages-hidepatrolled": "నిఘాలో ఉన్న ఎక్కింపులను దాచు",
        "newimages-mediatype": "మాధ్యమ రకం:",
        "noimages": "చూసేందుకు ఏమీ లేదు.",
        "ilsubmit": "వెతుకు",
        "limitreport-expensivefunctioncount": "ఖరీదైన పార్సర్ ఫంక్షన్ల సంఖ్య",
        "limitreport-unstrip-size-value": "$1/$2 {{PLURAL:$2|బైటు|బైట్లు}}",
        "expandtemplates": "మూసలను విస్తరించు",
-       "expand_templates_intro": "à°\88 à°ªà±\8dà°°à°¤à±\8dà°¯à±\87à°\95 à°ªà±\87à°\9cà±\80 à°®à±\80à°°à°¿à°\9aà±\8dà°\9aà°¿à°¨ à°®à±\82సలనà±\81 à°ªà±\82à°°à±\8dతిà°\97à°¾ à°µà°¿à°¸à±\8dతరిà°\82à°\9aà°¿, చూపిస్తుంది. ఇది <code><nowiki>{{</nowiki>#language:...}}</code> వంటి పార్సరు ఫంక్షన్లను, <code><nowiki>{{</nowiki>CURRENTDAY}}</code> వంటి చరరాశులను (వేరియబుల్) కూడా విస్తరిస్తుంది. \nనిజానికి ఇది మీసాల బ్రాకెట్లలో ఉన్న ప్రతీదాన్నీ విస్తరిస్తుంది.",
+       "expand_templates_intro": "à°\88 à°ªà±\8dà°°à°¤à±\8dà°¯à±\87à°\95 à°ªà±\87à°\9cà±\80 à°µà°¿à°\95à±\80à°\9fà±\86à°\95à±\8dà°¸à±\8dà°\9fà±\81à°¨à±\81 à°¤à±\80à°¸à±\81à°\95à±\81ని à°\85à°\82à°¦à±\81à°²à±\8b à°\89à°¨à±\8dà°¨ à°®à±\82సలనà±\8dనిà°\9fà°¿à°¨à±\80 à°µà°¿à°¸à±\8dతరిà°\82à°\9aà°¿ చూపిస్తుంది. ఇది <code><nowiki>{{</nowiki>#language:...}}</code> వంటి పార్సరు ఫంక్షన్లను, <code><nowiki>{{</nowiki>CURRENTDAY}}</code> వంటి చరరాశులను (వేరియబుల్) కూడా విస్తరిస్తుంది. \nనిజానికి ఇది మీసాల బ్రాకెట్లలో ఉన్న ప్రతీదాన్నీ విస్తరిస్తుంది.",
        "expand_templates_title": "{{FULLPAGENAME}} మొదలగు వాటి కొరకు సందర్భ శీర్షిక:",
-       "expand_templates_input": "విసà±\8dతరిà°\82à°\9aవలసిన à°ªà°¾à° à±\8dà°¯à°\82:",
+       "expand_templates_input": "à°\87à°¨à±\8dâ\80\8cà°ªà±\81à°\9fà±\8d à°µà°¿à°\95à±\80à°\9fà±\86à°\95à±\8dà°¸à±\8dà°\9fà±\8d:",
        "expand_templates_output": "ఫలితం",
        "expand_templates_xml_output": "XML ఔట్&zwnj;పుట్",
        "expand_templates_html_output": "ముడి HTML ఔట్‍పుట్",
        "log-action-filter-managetags-deactivate": "ట్యాగు అచేతనం",
        "log-action-filter-protect-protect": "సంరక్షణ",
        "log-action-filter-upload-upload": "కొత్త ఎక్కింపు",
+       "authmanager-create-disabled": "ఖాతా సృష్టించడాన్ని అశక్తం చేసాం.",
+       "authmanager-create-from-login": "ఖాతా సృష్టించడానికి, ఫీల్డులను నింపండి.",
+       "authmanager-create-not-in-progress": "ఖాతా సృష్టించే పని జరగడం లేదు. లేదా సెషను డేటా పోయింది. మళ్ళీ మొదటినుండి మొదలుపెట్టండి.",
+       "authmanager-create-no-primary": "మీరిచ్చిన విశేషాలతో ఖాతాను సృష్టించలేకపోయాం.",
+       "authmanager-link-no-primary": "మీరిచ్చిన విశేషాలతో ఖాతాలను లింకు చెయ్యలేకపోయాం.",
+       "authmanager-link-not-in-progress": "ఖాతాలను లింకు చేసే పని జరగడం లేదు. లేదా సెషను డేటా పోయింది. మళ్ళీ మొదటినుండి మొదలుపెట్టండి.",
+       "authmanager-authplugin-setpass-failed-title": "సంకేతపదం మార్పు విఫలమైంది.",
+       "authmanager-authplugin-setpass-failed-message": "సంకేతపదం మార్పును ఆథెంటికేషన్ ప్లగిన్ తిరస్కరించింది.",
+       "authmanager-authplugin-create-fail": "ఖాతా సృష్టిని ఆథెంటికేషన్ ప్లగిన్ తిరస్కరించింది.",
+       "authmanager-authplugin-setpass-denied": "ఆథెంటికేషన్ ప్లగిన్ సంకేతపదం మార్పులను అనుమతించదు.",
+       "authmanager-authplugin-setpass-bad-domain": "తప్పు డొమెయిన్",
+       "authmanager-autocreate-noperm": "ఆటోమాటిక్ ఖాతా సృష్టికి అనుమతి లేదు.",
        "authmanager-userdoesnotexist": "వాడుకరి ఖాతా \"$1\" నమోదయి లేదు.",
        "authmanager-userlogin-remembermypassword-help": "సెషను ముగిసిన తరువాత కూడా సంకేతపదాన్ని గుర్తుంచుకోమంటారా",
        "authmanager-username-help": "ధ్రువీకరణ కోసం వాడుకరిపేరు.",
index 0616922..da95654 100644 (file)
        "filehist-filesize": "Dosya boyutu",
        "filehist-comment": "Açıklama",
        "imagelinks": "Dosya kullanımı",
-       "linkstoimage": "Bu görüntü dosyasına bağlantısı olan {{PLURAL:$1|sayfa|$1 sayfa}}:",
-       "linkstoimage-more": "$1'den fazla {{PLURAL:$1|sayfa|sayfa}} bu dosyaya bağlantı veriyor.\nSıradaki liste sadece bu dosyaya bağlantı veren {{PLURAL:$1|ilk dosyayı|ilk $1 dosyayı}} gösteriyor.\n[[Special:WhatLinksHere/$2|Tam bir liste]] mevcuttur.",
-       "nolinkstoimage": "Bu dosyaya bağlantı veren bir sayfa yok.",
+       "linkstoimage": "Aşağıdaki {{PLURAL:$1|sayfa|$1 sayfa}} bu dosyayı kullanmaktadır:",
+       "linkstoimage-more": "$1 {{PLURAL:$1|sayfadan|sayfadan}} fazlası bu dosyayı kullanıyor.\nAşağıdaki listede sadece bu dosyayı kullanan  {{PLURAL:$1|ilk sayfa|ilk $1 sayfa}} gösterilmektedir.\n[[Special:WhatLinksHere/$2|Tam listesi]] mevcuttur.",
+       "nolinkstoimage": "Bu dosyayı kullanan sayfa yok.",
        "morelinkstoimage": "Bu dosyaya [[Special:WhatLinksHere/$1|daha fazla bağlantıları]] gör.",
        "linkstoimage-redirect": "$1 (dosya yönlendirme) $2",
        "duplicatesoffile": "Şu {{PLURAL:$1|dosya|$1 dosya}}, bu dosyanın kopyası ([[Special:FileDuplicateSearch/$2|daha fazla ayrıntı]]):",
        "mimesearch": "MIME araması",
        "mimesearch-summary": "Bu sayfa, dosyaların MIME türlerine göre filtrelenmesini sağlar. Girdi: içerik_türü/alt_tür veya içerik_türü/*, örn. <code>image/jpeg</code>.",
        "mimetype": "MIME türü:",
-       "download": "yükle",
+       "download": "indir",
        "unwatchedpages": "İzlenmeyen sayfalar",
        "listredirects": "Yönlendirmeleri listele",
        "listduplicatedfiles": "Kopyası bulunan dosyalar listesi",
index f873f83..e215bd7 100644 (file)
        "rcfilters-liveupdates-button": "Автоматик яңарту",
        "rcfilters-watchlist-markseen-button": "Бар үзгәртүләрне каралган дип билгеләргә",
        "rcfilters-watchlist-edit-watchlist-button": "Күзәтү исемлегегезне үзгәртү",
+       "rcfilters-watchlist-showupdated": "Сезнең соңгы төзәтмәләрдән соң үзгәргән битләр <strong>калын</strong> һәм тулы маркер белән күрсәтелгән",
        "rcfilters-preference-label": "«Соңгы үзгәртүләр» битенең яңа юрамасын яшерү",
        "rcnotefrom": "Астарак <strong>$3, $4</strong> өчен {{PLURAL:$5|үзгәртүләр күрсәтелгән}} (<strong>$1</strong> артык түгел).",
        "rclistfrom": "$3 $2 башлап яңа үзгәртүләрне күрсәт",
index 4d3ec30..5227669 100644 (file)
        "ns-specialprotected": "מען קען נישט רעדאגירן ספעציעלע בלעטער.",
        "titleprotected": "דער טיטל איז געשיצט פון ווערן געשאפֿן דורך  [[User:$1|$1]].\nדי אורזאך איז <em>$2</em>.",
        "filereadonlyerror": "נישט מעגלעך צו ענדערן די טעקע \"$1\" ווייל די טעקע רעפאזיטאריום  \"$2\" איז אין נאר־ליינען מצב.\n\nדער סיסאפ וואס האט זי פארשפארט האט געגעבן דעם הסבר:  \"$3\"",
+       "invalidtitle": "אומגילטיקער טיטל",
        "invalidtitle-knownnamespace": "אומגילטירער טיטל מיט נאמענטייל \"$2\" און טעקסט \"$3\"",
        "invalidtitle-unknownnamespace": "אומגילטיקער טיטל מיט אומבאוואוסטן נאמענטייל נומער $1 און טעקסט \"$2\"",
        "exception-nologin": "נישט אַרײַנלאגירט",
        "diff-multi-manyusers": "({{PLURAL:$1|איין מיטלסטע ווערסיע |$1 מיטלסטע ווערסיעס}} פֿון מער ווי {{PLURAL:$2|איין באַניצער|$2 באַניצער}} נישט געוויזן.)",
        "difference-missing-revision": "{{PLURAL:$2|איין ווערסיע|$2 ווערסיעס}} פון דעם דיפערענץ ($1) {{PLURAL:$2|האט}} מען נישט געטראפן.\n\nדאס געשעט געוויינלעך פון פאלגן א פארעלטערטן היסטאריע לינק צו א בלאט וואס איז געווארן אויסגעמעקט.\nפרטים קען מען געפינען אינעם [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} אויסמעקונג לאגבוך].",
        "searchresults": "זוכן רעזולטאטן",
+       "search-filter-title-prefix-reset": "זוכן אלע בלעטער",
        "searchresults-title": "זוכן רעזולטאַטן פֿאַר \"$1\"",
        "titlematches": "בלאט קעפל שטימט",
        "textmatches": "בלעטער מיט פאַסנדיקן אינהאַלט",
        "grant-createaccount": "שאַפֿן קאנטעס",
        "grant-createeditmovepage": "שאפֿן, רעדאקטירן און באוועגן בלעטער",
        "grant-delete": "אויסמעקן בלעטער, ווערסיעס און לאגבוך פרטים",
-       "grant-editinterface": "רע×\93×\90ק×\98×\99ר×\9f ×\93×¢×\9d ×\9e×¢×\93×\99×¢×\95×\95×\99ק×\99 × ×\90×\9e×¢× ×\98×\99×\99×\9c ×\90×\95×\9f ×\91×\90× ×\99צער CSS/JSON/JavaScript",
+       "grant-editinterface": "רע×\93×\90ק×\98×\99ר×\9f ×\93×¢×\9d ×\9e×¢×\93×\99×¢×\95×\95×\99ק×\99 × ×\90×\9e×¢× ×\98×\99×\99×\9c ×\90×\95×\9f ×\95×\95×\99ק×\99×\95×\95×\99×\99×\98\91×\90× ×\99צער JSON",
        "grant-editmycssjs": "רעדאקטירן אייער באניצער CSS/JSON/JavaScript",
        "grant-editmyoptions": "רעדאקטירן אײַערע באניצער פרעפֿערענצן",
        "grant-editmywatchlist": "רעדאקטירן אײַער אויפֿפאסונג ליסטע",
        "rcfilters-other-review-tools": "אנדערע רעצענזיע ווערקצייג",
        "rcfilters-group-results-by-page": "גרופירן רעזולטאטן לויט בלאט",
        "rcfilters-activefilters": "אַקטיווע פילטערס",
+       "rcfilters-activefilters-hide": "באַהאַלטן",
+       "rcfilters-activefilters-show": "ווייזן",
        "rcfilters-advancedfilters": "פֿארגעשריטענע פֿילטערס",
        "rcfilters-limit-title": "רעזולטאטן צו ווייזן",
        "rcfilters-days-title": "לעצטיקע טעג",
        "filehist-comment": "באמערקונג",
        "imagelinks": "טעקע באַניץ",
        "linkstoimage": "{{PLURAL:$1|דער פאלגנדער בלאט ניצט|די פאלגנדע בלעטער ניצן}} דאס דאזיגע בילד:",
-       "linkstoimage-more": "×\9eער ×\95×\95×\99 $1 {{PLURAL:$1|×\91×\9c×\90Ö·×\98 ×¤Ö¿×\90ַר×\91×\99× ×\93×\98\91×\9c×¢×\98ער ×¤Ö¿×\90ַר×\91×\99× ×\93×\9f}} ×¦×\95 ×\93ער ×\93×\90×\96×\99×\92ער ×\98עקע.\n×\93×\99 ×¤Ö¿×\90×\9c×\92× ×\93×¢ ×\9c×\99ס×\98×¢ ×\95×\95ײַ×\96×\98  {{PLURAL:$1|×\93×¢×\9d ×¢×¨×©×\98×\9f ×\91×\9c×\90Ö·×\98 ×\9c×\99נק|×\93×\99 ×¢×¨×©×\98×¢ $1 ×\91×\9c×\90Ö·×\98 ×\9c×\99נקע×\9f}} ×¦×\95 ×\93ער ×\98עקע.\nס'×\90×\99×\96 ×¤Ö¿×\90ַר×\90Ö·×\9f[[Special:WhatLinksHere/$2|פֿולע רשימה]].",
+       "linkstoimage-more": "×\9eער ×\95×\95×\99 $1 {{PLURAL:$1|×\91×\9c×\90Ö·×\98 × ×\99צ×\98\91×\9c×¢×\98ער × ×\99צ×\9f}} ×\93×\99 ×\93×\90×\96×\99×\92×¢ ×\98עקע.\n×\93×\99 ×¤Ö¿×\90×\9c×\92× ×\93×¢ ×\9c×\99ס×\98×¢ ×\95×\95ײַ×\96×\98 × ×\90ר {{PLURAL:$1|×\93×¢×\9d ×¢×¨×©×\98×\9f ×\91×\9c×\90Ö·×\98 ×\95×\95×\90ס × ×\99צ×\98\93×\99 ×¢×¨×©×\98×¢ $1 ×\91×\9c×¢×\98ער ×\95×\95×\90ס × ×\99צ×\9f}} ×\93×\99 ×\98עקע.\nס'×\90×\99×\96 ×¤Ö¿×\90ַר×\90Ö·×\9f ×\90 [[Special:WhatLinksHere/$2|פֿולע רשימה]].",
        "nolinkstoimage": "נישטא קיין בלעטער וואס פארבינדן צו די טעקע.",
        "morelinkstoimage": "באַקוקן  [[Special:WhatLinksHere/$1|מער לינקען]] צו דער טעקע.",
        "linkstoimage-redirect": "$1 (טעקע ווײַטערפֿירונג) $2",
index 10922a6..ac26098 100644 (file)
        "action-delete": "删除本页",
        "action-deleterevision": "删除修订",
        "action-deletelogentry": "删除日志记录",
-       "action-deletedhistory": "查看页面被删除的历史",
+       "action-deletedhistory": "查看已被删除的页面历史",
        "action-deletedtext": "查看已删除的修订版本文字",
        "action-browsearchive": "搜索已被删除的页面",
        "action-undelete": "还原页面",
index a55372c..2f0614d 100644 (file)
        "youremail": "Email:",
        "username": "{{GENDER:$1|使用者名稱}}:",
        "prefs-memberingroups": "{{GENDER:$2|所屬}}{{PLURAL:$1|群組}}:",
-       "group-membership-link-with-expiry": "$1 (直到 $2)",
+       "group-membership-link-with-expiry": "$1(直到 $2)",
        "prefs-registration": "註冊時間:",
        "yourrealname": "真實姓名:",
        "yourlanguage": "語言:",
        "right-editmyuserjs": "編輯自己的使用者 JavaScript 檔",
        "right-viewmywatchlist": "檢視自己的監視清單",
        "right-editmywatchlist": "編輯自己的監視清單。注意,即使無此權限,某些操作仍會新增頁面至監視清單。",
-       "right-viewmyprivateinfo": "檢視自己的私隱資料 (如:電子郵件地址及真實姓名)",
+       "right-viewmyprivateinfo": "檢視自己的私隱資料(如:電子郵件地址及真實姓名)",
        "right-editmyprivateinfo": "編輯自己的隱私資料 (如:電子郵件地址及真實姓名)",
        "right-editmyoptions": "編輯自己的偏好設定",
        "right-rollback": "快速還原最後一位使用者對某一頁面的編輯",
        "action-delete": "刪除此頁面",
        "action-deleterevision": "刪除修訂",
        "action-deletelogentry": "刪除日誌項目",
-       "action-deletedhistory": "檢視頁面的刪除歷史",
+       "action-deletedhistory": "檢視已被刪除的頁面歷史",
        "action-deletedtext": "查看已刪除的修訂版本文字",
        "action-browsearchive": "搜尋已刪除頁面",
        "action-undelete": "取消刪除頁面",
        "listusersfrom": "顯示使用者開始自:",
        "listusers-submit": "顯示",
        "listusers-noresult": "查無使用者。",
-       "listusers-blocked": "(已封鎖)",
+       "listusers-blocked": "(已封鎖)",
        "activeusers": "活動的使用者清單",
        "activeusers-intro": "此清單為最近 $1 天有活動的使用者。",
        "activeusers-count": "最近 $3 天內有 $1 次動作",
index 7e9220c..bb88040 100644 (file)
@@ -165,26 +165,11 @@ FILE_PATTERNS          = *.c \
                          *.odl \
                          *.cs \
                          *.php \
-                         *.php5 \
                          *.inc \
                          *.m \
                          *.mm \
                          *.dox \
                          *.py \
-                         *.C \
-                         *.CC \
-                         *.C++ \
-                         *.II \
-                         *.I++ \
-                         *.H \
-                         *.HH \
-                         *.H++ \
-                         *.CS \
-                         *.PHP \
-                         *.PHP5 \
-                         *.M \
-                         *.MM \
-                         *.PY \
                          *.txt \
                          README
 RECURSIVE              = YES
index dad79b0..2442caa 100644 (file)
@@ -35,6 +35,7 @@ class DeduplicateArchiveRevId extends LoggedUpdateMaintenance {
                $this->output( "Deduplicating ar_rev_id...\n" );
 
                $dbw = $this->getDB( DB_MASTER );
+               PopulateArchiveRevId::checkMysqlAutoIncrementBug( $dbw );
 
                $minId = $dbw->selectField( 'archive', 'MIN(ar_rev_id)', [], __METHOD__ );
                $maxId = $dbw->selectField( 'archive', 'MAX(ar_rev_id)', [], __METHOD__ );
index bebee85..6f922a0 100644 (file)
@@ -83,7 +83,6 @@
                                "classes": [
                                        "mw.log",
                                        "mw.inspect",
-                                       "mw.inspect.reports",
                                        "mw.Debug"
                                ]
                        }
index e493506..60f5e8a 100644 (file)
@@ -21,6 +21,7 @@
  * @ingroup Maintenance
  */
 
+use Wikimedia\Rdbms\DBQueryError;
 use Wikimedia\Rdbms\IDatabase;
 
 require_once __DIR__ . '/Maintenance.php';
@@ -49,6 +50,7 @@ class PopulateArchiveRevId extends LoggedUpdateMaintenance {
        protected function doDBUpdates() {
                $this->output( "Populating ar_rev_id...\n" );
                $dbw = $this->getDB( DB_MASTER );
+               self::checkMysqlAutoIncrementBug( $dbw );
 
                // Quick exit if there are no rows needing updates.
                $any = $dbw->selectField(
@@ -86,6 +88,53 @@ class PopulateArchiveRevId extends LoggedUpdateMaintenance {
                }
        }
 
+       /**
+        * Check for (and work around) a MySQL auto-increment bug
+        *
+        * (T202032) MySQL until 8.0 and MariaDB until some version after 10.1.34
+        * don't save the auto-increment value to disk, so on server restart it
+        * might reuse IDs from deleted revisions. We can fix that with an insert
+        * with an explicit rev_id value, if necessary.
+        *
+        * @param IDatabase $dbw
+        */
+       public static function checkMysqlAutoIncrementBug( IDatabase $dbw ) {
+               if ( $dbw->getType() !== 'mysql' ) {
+                       return;
+               }
+
+               if ( !self::$dummyRev ) {
+                       self::$dummyRev = self::makeDummyRevisionRow( $dbw );
+               }
+
+               $ok = false;
+               while ( !$ok ) {
+                       try {
+                               $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
+                                       $dbw->insert( 'revision', self::$dummyRev, $fname );
+                                       $id = $dbw->insertId();
+                                       $toDelete[] = $id;
+
+                                       $maxId = max(
+                                               (int)$dbw->selectField( 'archive', 'MAX(ar_rev_id)', [], __METHOD__ ),
+                                               (int)$dbw->selectField( 'slots', 'MAX(slot_revision_id)', [], __METHOD__ )
+                                       );
+                                       if ( $id <= $maxId ) {
+                                               $dbw->insert( 'revision', [ 'rev_id' => $maxId + 1 ] + self::$dummyRev, $fname );
+                                               $toDelete[] = $maxId + 1;
+                                       }
+
+                                       $dbw->delete( 'revision', [ 'rev_id' => $toDelete ], $fname );
+                               } );
+                               $ok = true;
+                       } catch ( DBQueryError $e ) {
+                               if ( $e->errno != 1062 ) { // 1062 is "duplicate entry", ignore it and retry
+                                       throw $e;
+                               }
+                       }
+               }
+       }
+
        /**
         * Assign new ar_rev_ids to a set of ar_ids.
         * @param IDatabase $dbw
index 91c28bd..c40ef93 100644 (file)
@@ -2629,6 +2629,15 @@ return [
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
+       'mediawiki.widgets.CheckMatrixWidget' => [
+               'scripts' => [
+                       'resources/src/mediawiki.widgets/mw.widgets.CheckMatrixWidget.js',
+               ],
+               'dependencies' => [
+                       'oojs-ui-core',
+               ],
+               'targets' => [ 'desktop', 'mobile' ],
+       ],
        'mediawiki.widgets.CategoryMultiselectWidget' => [
                'scripts' => [
                        'resources/src/mediawiki.widgets/mw.widgets.CategoryTagItemWidget.js',
index 6c4b80d..f4545de 100644 (file)
        function sortByProperty( array, prop, descending ) {
                var order = descending ? -1 : 1;
                return array.sort( function ( a, b ) {
+                       if ( a[ prop ] === undefined || b[ prop ] === undefined ) {
+                               // Sort undefined to the end, regardless of direction
+                               return a[ prop ] !== undefined ? -1 : b[ prop ] !== undefined ? 1 : 0;
+                       }
                        return a[ prop ] > b[ prop ] ? order : a[ prop ] < b[ prop ] ? -order : 0;
                } );
        }
         *
         * When invoked without arguments, prints all available reports.
         *
-        * @param {...string} [reports] One or more of "size", "css", or "store".
+        * @param {...string} [reports] One or more of "size", "css", "store", or "time".
         */
        inspect.runReports = function () {
                var reports = arguments.length > 0 ?
        };
 
        /**
+        * @private
         * @class mw.inspect.reports
         * @singleton
         */
                                } catch ( e ) {}
                        }
                        return [ stats ];
+               },
+
+               /**
+                * Generate a breakdown of all loaded modules and their time
+                * spent during initialisation (measured in milliseconds).
+                *
+                * This timing data is collected by mw.loader.profiler.
+                *
+                * @return {Object[]} Table rows
+                */
+               time: function () {
+                       var modules;
+
+                       if ( !mw.loader.profiler ) {
+                               mw.log.warn( 'mw.inspect: The time report requires $wgResourceLoaderEnableJSProfiler.' );
+                               return [];
+                       }
+
+                       modules = inspect.getLoadedModules()
+                               .map( function ( moduleName ) {
+                                       return mw.loader.profiler.getProfile( moduleName );
+                               } )
+                               .filter( function ( perf ) {
+                                       // Exclude modules that reached "ready" state without involvement from mw.loader.
+                                       // This is primarily styles-only as loaded via <link rel="stylesheet">.
+                                       return perf !== null;
+                               } );
+
+                       // Sort by total time spent, highest first.
+                       sortByProperty( modules, 'total', true );
+
+                       // Add human-readable strings
+                       modules.forEach( function ( module ) {
+                               module.totalInMs = module.total;
+                               module.total = module.totalInMs.toLocaleString() + ' ms';
+                       } );
+
+                       return modules;
                }
        };
 
index b2985d1..bf23da1 100644 (file)
@@ -2,7 +2,7 @@
  * @class mw.user
  * @singleton
  */
-/* global Uint32Array */
+/* global Uint16Array */
 ( function ( mw, $ ) {
        var userInfoPromise, pageviewRandomId;
 
                 * mobile usages of this code is probably higher.
                 *
                 * Rationale:
-                * We need about 64 bits to make sure that probability of collision
-                * on 500 million (5*10^8) is <= 1%
-                * See https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
+                * We need about 80 bits to make sure that probability of collision
+                * on 155 billion  is <= 1%
                 *
-                * @return {string} 64 bit integer in hex format, padded
+                * See https://en.wikipedia.org/wiki/Birthday_attack#Mathematics
+                * n(p;H) = n(0.01,2^80)= sqrt (2 * 2^80 * ln(1/(1-0.01)))
+
+                * @return {string} 80 bit integer in hex format, padded
                 */
                generateRandomSessionId: function () {
                        var rnds, i,
-                               hexRnds = new Array( 2 ),
+                               hexRnds = new Array( 5 ),
                                // Support: IE 11
                                crypto = window.crypto || window.msCrypto;
 
-                       if ( crypto && crypto.getRandomValues && typeof Uint32Array === 'function' ) {
-                               // Fill an array with 2 random values, each of which is 32 bits.
-                               // Note that Uint32Array is array-like but does not implement Array.
-                               rnds = new Uint32Array( 2 );
+                       if ( crypto && crypto.getRandomValues && typeof Uint16Array === 'function' ) {
+
+                               // Fill an array with 5 random values, each of which is 16 bits.
+                               // Note that Uint16Array is array-like but does not implement Array.
+                               rnds = new Uint16Array( 5 );
                                crypto.getRandomValues( rnds );
+
                        } else {
-                               rnds = [
-                                       Math.floor( Math.random() * 0x100000000 ),
-                                       Math.floor( Math.random() * 0x100000000 )
-                               ];
+
+                               // 0x10000 is 2^16 so the operation below will return a number
+                               // between 2^16 and zero
+                               for ( i = 0; i < 5; i++ ) {
+                                       rnds[ i ] = Math.floor( Math.random() * 0x10000 );
+                               }
                        }
-                       // Convert number to a string with 16 hex characters
-                       for ( i = 0; i < 2; i++ ) {
-                               // Add 0x100000000 before converting to hex and strip the extra character
+                       // Convert the 5 16bit-numbers into 20 characters (4 hex chars per 16 bits)
+                       for ( i = 0; i < 5; i++ ) {
+                               // Add 0x1000 before converting to hex and strip the extra character
                                // after converting to keep the leading zeros.
-                               hexRnds[ i ] = ( rnds[ i ] + 0x100000000 ).toString( 16 ).slice( 1 );
+                               hexRnds[ i ] = ( rnds[ i ] + 0x10000 ).toString( 16 ).slice( 1 );
                        }
 
                        // Concatenation of two random integers with entropy n and m
diff --git a/resources/src/mediawiki.widgets/mw.widgets.CheckMatrixWidget.js b/resources/src/mediawiki.widgets/mw.widgets.CheckMatrixWidget.js
new file mode 100644 (file)
index 0000000..e13c6fa
--- /dev/null
@@ -0,0 +1,142 @@
+( function ( $, mw ) {
+       /**
+        * A JavaScript version of CheckMatrixWidget.
+        *
+        * @class
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {Object} [config] Configuration options
+        * @cfg {Object} columns Required object representing the column labels and associated
+        *  tags in the matrix.
+        * @cfg {Object} rows Required object representing the row labels and associated
+        *  tags in the matrix.
+        * @cfg {string[]} [forcedOn] An array of column-row tags to be displayed as
+        *  enabled but unavailable to change
+        * @cfg {string[]} [forcedOff] An array of column-row tags to be displayed as
+        *  disnabled but unavailable to change
+        * @cfg {Object} Object mapping row label to tooltip content
+        */
+       mw.widgets.CheckMatrixWidget = function MWWCheckMatrixWidget( config ) {
+               var $headRow = $( '<tr>' ),
+                       $table = $( '<table>' ),
+                       widget = this;
+               config = config || {};
+
+               // Parent constructor
+               mw.widgets.CheckMatrixWidget.parent.call( this, config );
+               this.checkboxes = {};
+               this.name = config.name;
+               this.id = config.id;
+               this.rows = config.rows || {};
+               this.columns = config.columns || {};
+               this.tooltips = config.tooltips || [];
+               this.values = config.values || [];
+               this.forcedOn = config.forcedOn || [];
+               this.forcedOff = config.forcedOff || [];
+
+               // Build header
+               $headRow.append( $( '<td>' ).html( '&#160;' ) );
+
+               // Iterate over the columns object (ignore the value)
+               $.each( this.columns, function ( columnLabel ) {
+                       $headRow.append( $( '<td>' ).text( columnLabel ) );
+               } );
+               $table.append( $headRow );
+
+               // Build table
+               $.each( this.rows, function ( rowLabel, rowTag ) {
+                       var $row = $( '<tr>' ),
+                               labelField = new OO.ui.FieldLayout(
+                                       new OO.ui.Widget(), // Empty widget, since we don't have the checkboxes here
+                                       {
+                                               label: rowLabel,
+                                               help: widget.tooltips[ rowLabel ],
+                                               align: 'inline'
+                                       }
+                               );
+
+                       // Label
+                       $row.append( $( '<td>' ).append( labelField.$element ) );
+
+                       // Columns
+                       $.each( widget.columns, function ( columnLabel, columnTag ) {
+                               var thisTag = columnTag + '-' + rowTag,
+                                       checkbox = new OO.ui.CheckboxInputWidget( {
+                                               value: thisTag,
+                                               name: widget.name ? widget.name + '[]' : undefined,
+                                               id: widget.id ? widget.id + '-' + thisTag : undefined,
+                                               selected: widget.isTagSelected( thisTag ),
+                                               disabled: widget.isTagDisabled( thisTag )
+                                       } );
+
+                               widget.checkboxes[ thisTag ] = checkbox;
+                               $row.append( $( '<td>' ).append( checkbox.$element ) );
+                       } );
+
+                       $table.append( $row );
+               } );
+
+               this.$element
+                       .addClass( 'mw-widget-checkMatrixWidget' )
+                       .append( $table );
+       };
+
+       /* Setup */
+
+       OO.inheritClass( mw.widgets.CheckMatrixWidget, OO.ui.Widget );
+
+       /* Methods */
+
+       /**
+        * Check whether the given tag is selected
+        *
+        * @param {string} tagName Tag name
+        * @return {boolean} Tag is selected
+        */
+       mw.widgets.CheckMatrixWidget.prototype.isTagSelected = function ( tagName ) {
+               return (
+                       // If tag is not forced off
+                       this.forcedOff.indexOf( tagName ) === -1 &&
+                       (
+                               // If tag is in values
+                               this.values.indexOf( tagName ) > -1 ||
+                               // If tag is forced on
+                               this.forcedOn.indexOf( tagName ) > -1
+                       )
+               );
+       };
+
+       /**
+        * Check whether the given tag is disabled
+        *
+        * @param {string} tagName Tag name
+        * @return {boolean} Tag is disabled
+        */
+       mw.widgets.CheckMatrixWidget.prototype.isTagDisabled = function ( tagName ) {
+               return (
+                       // If the entire widget is disabled
+                       this.isDisabled() ||
+                       // If tag is forced off or forced on
+                       this.forcedOff.indexOf( tagName ) > -1 ||
+                       this.forcedOn.indexOf( tagName ) > -1
+               );
+       };
+       /**
+        * @inheritdoc
+        */
+       mw.widgets.CheckMatrixWidget.prototype.setDisabled = function ( isDisabled ) {
+               var widget = this;
+
+               // Parent method
+               mw.widgets.CheckMatrixWidget.parent.prototype.setDisabled.call( this, isDisabled );
+
+               // setDisabled sometimes gets called before the widget is ready
+               if ( this.checkboxes && Object.keys( this.checkboxes ).length > 0 ) {
+                       // Propagate to all checkboxes and update their disabled state
+                       $.each( this.checkboxes, function ( name, checkbox ) {
+                               checkbox.setDisabled( widget.isTagDisabled( name ) );
+                       } );
+               }
+       };
+}( jQuery, mediaWiki ) );
index f9a69b8..6ed226c 100644 (file)
@@ -7,7 +7,7 @@
  * @alternateClassName mediaWiki
  * @singleton
  */
-/* global $VARS */
+/* global $VARS, $CODE */
 
 ( function () {
        'use strict';
                                }
 
                                registry[ module ].state = 'executing';
+                               $CODE.profileExecuteStart();
 
                                runScript = function () {
                                        var script, markModuleReady, nestedAddScript;
 
+                                       $CODE.profileScriptStart();
                                        script = registry[ module ].script;
                                        markModuleReady = function () {
+                                               $CODE.profileScriptEnd();
                                                registry[ module ].state = 'ready';
                                                handlePending( module );
                                        };
                                                // Use mw.track instead of mw.log because these errors are common in production mode
                                                // (e.g. undefined variable), and mw.log is only enabled in debug mode.
                                                registry[ module ].state = 'error';
+                                               $CODE.profileScriptEnd();
                                                mw.trackError( 'resourceloader.exception', {
                                                        exception: e, module:
                                                        module, source: 'module-execute'
                                        }
                                }
 
+                               // End profiling of execute()-self before we call checkCssHandles(),
+                               // which (sometimes asynchronously) calls runScript(), which we want
+                               // to measure separately without overlap.
+                               $CODE.profileExecuteEnd();
+
                                // Kick off.
                                cssHandlesRegistered = true;
                                checkCssHandles();
diff --git a/resources/src/startup/profiler.js b/resources/src/startup/profiler.js
new file mode 100644 (file)
index 0000000..5e9b6ab
--- /dev/null
@@ -0,0 +1,81 @@
+/*!
+ * Augment mw.loader to facilitate module-level profiling.
+ *
+ * @author Timo Tijhof
+ * @since 1.32
+ */
+/* global mw */
+( function () {
+       'use strict';
+
+       var moduleTimes = Object.create( null );
+
+       /**
+        * Private hooks inserted into mw.loader code if MediaWiki configuration
+        * `$wgResourceLoaderEnableJSProfiler` is `true`.
+        *
+        * To use this data, run `mw.inspect( 'time' )` from the browser console.
+        * See mw#inspect().
+        *
+        * @private
+        * @class
+        * @singleton
+        */
+       mw.loader.profiler = {
+               onExecuteStart: function ( moduleName ) {
+                       var time = performance.now();
+                       if ( moduleTimes[ moduleName ] ) {
+                               throw new Error( 'Unexpected perf record for "' + moduleName + '".' );
+                       }
+                       moduleTimes[ moduleName ] = {
+                               executeStart: time,
+                               executeEnd: null,
+                               scriptStart: null,
+                               scriptEnd: null
+                       };
+               },
+               onExecuteEnd: function ( moduleName ) {
+                       var time = performance.now();
+                       moduleTimes[ moduleName ].executeEnd = time;
+               },
+               onScriptStart: function ( moduleName ) {
+                       var time = performance.now();
+                       moduleTimes[ moduleName ].scriptStart = time;
+               },
+               onScriptEnd: function ( moduleName ) {
+                       var time = performance.now();
+                       moduleTimes[ moduleName ].scriptEnd = time;
+               },
+
+               /**
+                * For internal use by inspect.reports#time.
+                *
+                * @private
+                * @param {string} moduleName
+                * @return {Object|null}
+                * @throws {Error} If the perf record is incomplete.
+                */
+               getProfile: function ( moduleName ) {
+                       var times, key, execute, script, total;
+                       times = moduleTimes[ moduleName ];
+                       if ( !times ) {
+                               return null;
+                       }
+                       for ( key in times ) {
+                               if ( times[ key ] === null ) {
+                                       throw new Error( 'Incomplete perf record for "' + moduleName + '".', times );
+                               }
+                       }
+                       execute = times.executeEnd - times.executeStart;
+                       script = times.scriptEnd - times.scriptStart;
+                       total = execute + script;
+                       return {
+                               name: moduleName,
+                               execute: execute,
+                               script: script,
+                               total: total
+                       };
+               }
+       };
+
+}() );
index 6972165..34f93ad 100644 (file)
@@ -8,6 +8,7 @@ use Psr\Log\LoggerInterface;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\IMaintainableDatabase;
 use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\IResultWrapper;
 use Wikimedia\Rdbms\LBFactory;
 use Wikimedia\TestingAccessWrapper;
 
@@ -1732,10 +1733,14 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         */
        private function resetDB( $db, $tablesUsed ) {
                if ( $db ) {
+                       // NOTE: Do not reset the slot_roles and content_models tables, but let them
+                       // leak across tests. Resetting them would require to reset all NamedTableStore
+                       // instances for these tables, of which there may be several beyond the ones
+                       // known to MediaWikiServices. See T202641.
                        $userTables = [ 'user', 'user_groups', 'user_properties', 'actor' ];
                        $pageTables = [
                                'page', 'revision', 'ip_changes', 'revision_comment_temp', 'comment', 'archive',
-                               'revision_actor_temp', 'slots', 'content', 'content_models', 'slot_roles',
+                               'revision_actor_temp', 'slots', 'content',
                        ];
                        $coreDBDataTables = array_merge( $userTables, $pageTables );
 
@@ -1761,41 +1766,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                                }
                        }
 
-                       $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
                        foreach ( $tablesUsed as $tbl ) {
-                               if ( !$db->tableExists( $tbl ) ) {
-                                       continue;
-                               }
-
-                               if ( $truncate ) {
-                                       $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tbl ), __METHOD__ );
-                               } else {
-                                       $db->delete( $tbl, '*', __METHOD__ );
-                               }
-
-                               if ( in_array( $db->getType(), [ 'postgres', 'sqlite' ], true ) ) {
-                                       // Reset the table's sequence too.
-                                       $db->resetSequenceForTable( $tbl, __METHOD__ );
-                               }
-
-                               if ( $tbl === 'interwiki' ) {
-                                       if ( !$this->interwikiTable ) {
-                                               // @todo We should probably throw here, but this causes test failures that I
-                                               // can't figure out, so for now we silently continue.
-                                               continue;
-                                       }
-                                       $db->insert(
-                                               'interwiki',
-                                               array_values( array_map( 'get_object_vars', iterator_to_array( $this->interwikiTable ) ) ),
-                                               __METHOD__
-                                       );
-                               }
-
-                               if ( $tbl === 'page' ) {
-                                       // Forget about the pages since they don't
-                                       // exist in the DB.
-                                       MediaWikiServices::getInstance()->getLinkCache()->clear();
-                               }
+                               $this->truncateTable( $tbl, $db );
                        }
 
                        if ( array_intersect( $tablesUsed, $coreDBDataTables ) ) {
@@ -1805,6 +1777,56 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                }
        }
 
+       /**
+        * Empties the given table and resets any auto-increment counters.
+        * Will also purge caches associated with some well known tables.
+        * If the table is not know, this method just returns.
+        *
+        * @param string $tableName
+        * @param IDatabase|null $db
+        */
+       protected function truncateTable( $tableName, IDatabase $db = null ) {
+               if ( !$db ) {
+                       $db = $this->db;
+               }
+
+               if ( !$db->tableExists( $tableName ) ) {
+                       return;
+               }
+
+               $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
+
+               if ( $truncate ) {
+                       $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tableName ), __METHOD__ );
+               } else {
+                       $db->delete( $tableName, '*', __METHOD__ );
+               }
+
+               if ( in_array( $db->getType(), [ 'postgres', 'sqlite' ], true ) ) {
+                       // Reset the table's sequence too.
+                       $db->resetSequenceForTable( $tableName, __METHOD__ );
+               }
+
+               if ( $tableName === 'interwiki' ) {
+                       if ( !$this->interwikiTable ) {
+                               // @todo We should probably throw here, but this causes test failures that I
+                               // can't figure out, so for now we silently continue.
+                               return;
+                       }
+                       $db->insert(
+                               'interwiki',
+                               array_values( array_map( 'get_object_vars', iterator_to_array( $this->interwikiTable ) ) ),
+                               __METHOD__
+                       );
+               }
+
+               if ( $tableName === 'page' ) {
+                       // Forget about the pages since they don't
+                       // exist in the DB.
+                       MediaWikiServices::getInstance()->getLinkCache()->clear();
+               }
+       }
+
        private static function unprefixTable( &$tableName, $ind, $prefix ) {
                $tableName = substr( $tableName, strlen( $prefix ) );
        }
index cbae4c7..492d00c 100644 (file)
@@ -173,51 +173,84 @@ class McrReadNewRevisionStoreDbTest extends RevisionStoreDbTestBase {
        }
 
        public function provideGetSlotsQueryInfo() {
-               yield [
+               yield 'no options' => [
                        [],
+                       [
+                               'tables' => [
+                                       'slots'
+                               ],
+                               'fields' => [
+                                       'slot_revision_id',
+                                       'slot_content_id',
+                                       'slot_origin',
+                                       'slot_role_id',
+                               ],
+                               'joins' => [],
+                       ]
+               ];
+               yield 'role option' => [
+                       [ 'role' ],
                        [
                                'tables' => [
                                        'slots',
                                        'slot_roles',
                                ],
-                               'fields' => array_merge(
-                                       [
-                                               'slot_revision_id',
-                                               'slot_content_id',
-                                               'slot_origin',
-                                               'role_name',
-                                       ]
-                               ),
+                               'fields' => [
+                                       'slot_revision_id',
+                                       'slot_content_id',
+                                       'slot_origin',
+                                       'slot_role_id',
+                                       'role_name',
+                               ],
                                'joins' => [
-                                       'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
+                                       'slot_roles' => [ 'LEFT JOIN', [ 'slot_role_id = role_id' ] ],
                                ],
                        ]
                ];
-               yield [
+               yield 'content option' => [
                        [ 'content' ],
                        [
                                'tables' => [
                                        'slots',
-                                       'slot_roles',
+                                       'content',
+                               ],
+                               'fields' => [
+                                       'slot_revision_id',
+                                       'slot_content_id',
+                                       'slot_origin',
+                                       'slot_role_id',
+                                       'content_size',
+                                       'content_sha1',
+                                       'content_address',
+                                       'content_model',
+                               ],
+                               'joins' => [
+                                       'content' => [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ],
+                               ],
+                       ]
+               ];
+               yield 'content and model options' => [
+                       [ 'content', 'model' ],
+                       [
+                               'tables' => [
+                                       'slots',
                                        'content',
                                        'content_models',
                                ],
-                               'fields' => array_merge(
-                                       [
-                                               'slot_revision_id',
-                                               'slot_content_id',
-                                               'slot_origin',
-                                               'role_name',
-                                               'content_size',
-                                               'content_sha1',
-                                               'content_address',
-                                               'model_name',
-                                       ]
-                               ),
+                               'fields' => [
+                                       'slot_revision_id',
+                                       'slot_content_id',
+                                       'slot_origin',
+                                       'slot_role_id',
+                                       'content_size',
+                                       'content_sha1',
+                                       'content_address',
+                                       'content_model',
+                                       'model_name',
+                               ],
                                'joins' => [
-                                       'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
                                        'content' => [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ],
-                                       'content_models' => [ 'INNER JOIN', [ 'content_model = model_id' ] ],
+                                       'content_models' => [ 'LEFT JOIN', [ 'content_model = model_id' ] ],
                                ],
                        ]
                ];
index 9a118d7..af19f72 100644 (file)
@@ -3,6 +3,7 @@ namespace MediaWiki\Tests\Storage;
 
 use CommentStoreComment;
 use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\MutableRevisionRecord;
 use MediaWiki\Storage\RevisionRecord;
 use MediaWiki\Storage\SlotRecord;
 use TextContent;
@@ -189,54 +190,139 @@ class McrRevisionStoreDbTest extends RevisionStoreDbTestBase {
        }
 
        public function provideGetSlotsQueryInfo() {
-               yield [
+               yield 'no options' => [
                        [],
+                       [
+                               'tables' => [
+                                       'slots'
+                               ],
+                               'fields' => [
+                                       'slot_revision_id',
+                                       'slot_content_id',
+                                       'slot_origin',
+                                       'slot_role_id',
+                               ],
+                               'joins' => [],
+                       ]
+               ];
+               yield 'role option' => [
+                       [ 'role' ],
                        [
                                'tables' => [
                                        'slots',
                                        'slot_roles',
                                ],
-                               'fields' => array_merge(
-                                       [
-                                               'slot_revision_id',
-                                               'slot_content_id',
-                                               'slot_origin',
-                                               'role_name',
-                                       ]
-                               ),
+                               'fields' => [
+                                       'slot_revision_id',
+                                       'slot_content_id',
+                                       'slot_origin',
+                                       'slot_role_id',
+                                       'role_name',
+                               ],
                                'joins' => [
-                                       'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
+                                       'slot_roles' => [ 'LEFT JOIN', [ 'slot_role_id = role_id' ] ],
                                ],
                        ]
                ];
-               yield [
+               yield 'content option' => [
                        [ 'content' ],
                        [
                                'tables' => [
                                        'slots',
-                                       'slot_roles',
+                                       'content',
+                               ],
+                               'fields' => [
+                                       'slot_revision_id',
+                                       'slot_content_id',
+                                       'slot_origin',
+                                       'slot_role_id',
+                                       'content_size',
+                                       'content_sha1',
+                                       'content_address',
+                                       'content_model',
+                               ],
+                               'joins' => [
+                                       'content' => [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ],
+                               ],
+                       ]
+               ];
+               yield 'content and model options' => [
+                       [ 'content', 'model' ],
+                       [
+                               'tables' => [
+                                       'slots',
                                        'content',
                                        'content_models',
                                ],
-                               'fields' => array_merge(
-                                       [
-                                               'slot_revision_id',
-                                               'slot_content_id',
-                                               'slot_origin',
-                                               'role_name',
-                                               'content_size',
-                                               'content_sha1',
-                                               'content_address',
-                                               'model_name',
-                                       ]
-                               ),
+                               'fields' => [
+                                       'slot_revision_id',
+                                       'slot_content_id',
+                                       'slot_origin',
+                                       'slot_role_id',
+                                       'content_size',
+                                       'content_sha1',
+                                       'content_address',
+                                       'content_model',
+                                       'model_name',
+                               ],
                                'joins' => [
-                                       'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
                                        'content' => [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ],
-                                       'content_models' => [ 'INNER JOIN', [ 'content_model = model_id' ] ],
+                                       'content_models' => [ 'LEFT JOIN', [ 'content_model = model_id' ] ],
                                ],
                        ]
                ];
        }
 
+       /**
+        * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
+        * @covers \MediaWiki\Storage\RevisionStore::insertSlotRowOn
+        * @covers \MediaWiki\Storage\RevisionStore::insertContentRowOn
+        */
+       public function testInsertRevisionOn_T202032() {
+               // This test only makes sense for MySQL
+               if ( $this->db->getType() !== 'mysql' ) {
+                       $this->assertTrue( true );
+                       return;
+               }
+
+               // NOTE: must be done before checking MAX(rev_id)
+               $page = $this->getTestPage();
+
+               $maxRevId = $this->db->selectField( 'revision', 'MAX(rev_id)' );
+
+               // Construct a slot row that will conflict with the insertion of the next revision ID,
+               // to emulate the failure mode described in T202032. Nothing will ever read this row,
+               // we just need it to trigger a primary key conflict.
+               $this->db->insert( 'slots', [
+                       'slot_revision_id' => $maxRevId + 1,
+                       'slot_role_id' => 1,
+                       'slot_content_id' => 0,
+                       'slot_origin' => 0
+               ], __METHOD__ );
+
+               $rev = new MutableRevisionRecord( $page->getTitle() );
+               $rev->setTimestamp( '20180101000000' );
+               $rev->setComment( CommentStoreComment::newUnsavedComment( 'test' ) );
+               $rev->setUser( $this->getTestUser()->getUser() );
+               $rev->setContent( 'main', new WikitextContent( 'Text' ) );
+               $rev->setPageId( $page->getId() );
+
+               $store = MediaWikiServices::getInstance()->getRevisionStore();
+               $return = $store->insertRevisionOn( $rev, $this->db );
+
+               $this->assertSame( $maxRevId + 2, $return->getId() );
+
+               // is the new revision correct?
+               $this->assertRevisionCompleteness( $return );
+               $this->assertRevisionRecordsEqual( $rev, $return );
+
+               // can we find it directly in the database?
+               $this->assertRevisionExistsInDatabase( $return );
+
+               // can we load it from the store?
+               $loaded = $store->getRevisionById( $return->getId() );
+               $this->assertRevisionCompleteness( $loaded );
+               $this->assertRevisionRecordsEqual( $return, $loaded );
+       }
+
 }
index b5b2e0d..1517964 100644 (file)
@@ -26,6 +26,10 @@ class NameTableStoreTest extends MediaWikiTestCase {
                parent::setUp();
        }
 
+       protected function addCoreDBData() {
+               // The default implementation causes the slot_roles to already have content. Skip that.
+       }
+
        private function populateTable( $values ) {
                $insertValues = [];
                foreach ( $values as $name ) {
@@ -139,6 +143,9 @@ class NameTableStoreTest extends MediaWikiTestCase {
                $name,
                $expectedId
        ) {
+               // Make sure the table is empty!
+               $this->truncateTable( 'slot_roles' );
+
                $this->populateTable( $existingValues );
                $store = $this->getNameTableSqlStore( $cacheBag, (int)$needsInsert, $selectCalls );
 
@@ -266,6 +273,21 @@ class NameTableStoreTest extends MediaWikiTestCase {
                $this->assertSame( $expected, TestingAccessWrapper::newFromObject( $store )->tableCache );
        }
 
+       public function testReloadMap() {
+               $this->populateTable( [ 'foo' ] );
+               $store = $this->getNameTableSqlStore( new HashBagOStuff(), 0, 2 );
+
+               // force load
+               $this->assertCount( 1, $store->getMap() );
+
+               // add more stuff to the table, so the cache gets out of sync
+               $this->populateTable( [ 'bar' ] );
+
+               $expected = [ 1 => 'foo', 2 => 'bar' ];
+               $this->assertSame( $expected, $store->reloadMap() );
+               $this->assertSame( $expected, $store->getMap() );
+       }
+
        public function testCacheRaceCondition() {
                $wanHashBag = new HashBagOStuff();
                $store1 = $this->getNameTableSqlStore( $wanHashBag, 1, 1 );
index f01c6ba..758537d 100644 (file)
@@ -19,6 +19,13 @@ use WikiPage;
  */
 class PageUpdaterTest extends MediaWikiTestCase {
 
+       public static function setUpBeforeClass() {
+               parent::setUpBeforeClass();
+
+               // force service reset!
+               MediaWikiServices::getInstance()->resetServiceForTesting( 'RevisionStore' );
+       }
+
        private function getDummyTitle( $method ) {
                return Title::newFromText( $method, $this->getDefaultWikitextNS() );
        }
@@ -217,6 +224,7 @@ class PageUpdaterTest extends MediaWikiTestCase {
 
                // check site stats - this asserts that derived data updates where run.
                $stats = $this->db->selectRow( 'site_stats', '*', '1=1' );
+               $this->assertNotNull( $stats, 'site_stats' );
                $this->assertSame( $oldStats->ss_total_pages + 0, (int)$stats->ss_total_pages );
                $this->assertSame( $oldStats->ss_total_edits + 2, (int)$stats->ss_total_edits );
        }
index ad1e013..8137b27 100644 (file)
@@ -244,14 +244,14 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                $this->assertSame( 0, $count );
        }
 
-       private function assertLinkTargetsEqual( LinkTarget $l1, LinkTarget $l2 ) {
+       protected function assertLinkTargetsEqual( LinkTarget $l1, LinkTarget $l2 ) {
                $this->assertEquals( $l1->getDBkey(), $l2->getDBkey() );
                $this->assertEquals( $l1->getNamespace(), $l2->getNamespace() );
                $this->assertEquals( $l1->getFragment(), $l2->getFragment() );
                $this->assertEquals( $l1->getInterwiki(), $l2->getInterwiki() );
        }
 
-       private function assertRevisionRecordsEqual( RevisionRecord $r1, RevisionRecord $r2 ) {
+       protected function assertRevisionRecordsEqual( RevisionRecord $r1, RevisionRecord $r2 ) {
                $this->assertEquals(
                        $r1->getPageAsLinkTarget()->getNamespace(),
                        $r2->getPageAsLinkTarget()->getNamespace()
@@ -291,7 +291,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                }
        }
 
-       private function assertSlotRecordsEqual( SlotRecord $s1, SlotRecord $s2 ) {
+       protected function assertSlotRecordsEqual( SlotRecord $s1, SlotRecord $s2 ) {
                $this->assertSame( $s1->getRole(), $s2->getRole() );
                $this->assertSame( $s1->getModel(), $s2->getModel() );
                $this->assertSame( $s1->getFormat(), $s2->getFormat() );
@@ -303,7 +303,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                $s1->hasAddress() ? $this->assertSame( $s1->hasAddress(), $s2->hasAddress() ) : null;
        }
 
-       private function assertRevisionCompleteness( RevisionRecord $r ) {
+       protected function assertRevisionCompleteness( RevisionRecord $r ) {
                $this->assertTrue( $r->hasSlot( 'main' ) );
                $this->assertInstanceOf( SlotRecord::class, $r->getSlot( 'main' ) );
                $this->assertInstanceOf( Content::class, $r->getContent( 'main' ) );
@@ -313,7 +313,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                }
        }
 
-       private function assertSlotCompleteness( RevisionRecord $r, SlotRecord $slot ) {
+       protected function assertSlotCompleteness( RevisionRecord $r, SlotRecord $slot ) {
                $this->assertTrue( $slot->hasAddress() );
                $this->assertSame( $r->getId(), $slot->getRevision() );
 
index 374ea3c..65adedc 100644 (file)
@@ -125,10 +125,10 @@ class ApiBlockTest extends ApiTestCase {
                $this->doBlock( [ 'tags' => 'custom tag' ] );
 
                $dbw = wfGetDB( DB_MASTER );
-               $this->assertSame( 'custom tag', $dbw->selectField(
+               $this->assertSame( 1, (int)$dbw->selectField(
                        [ 'change_tag', 'logging' ],
-                       'ct_tag',
-                       [ 'log_type' => 'block' ],
+                       'COUNT(*)',
+                       [ 'log_type' => 'block', 'ct_tag' => 'custom tag' ],
                        __METHOD__,
                        [],
                        [ 'change_tag' => [ 'INNER JOIN', 'ct_log_id = log_id' ] ]
index 0428335..30e1d0c 100644 (file)
@@ -73,6 +73,9 @@ class ApiComparePagesTest extends ApiTestCase {
                self::$repl['revF1'] = $this->addPage( 'F', "== Section 1 ==\nF 1.1\n\n== Section 2 ==\nF 1.2" );
                self::$repl['pageF'] = Title::newFromText( 'ApiComparePagesTest F' )->getArticleId();
 
+               self::$repl['revG1'] = $this->addPage( 'G', "== Section 1 ==\nG 1.1", CONTENT_MODEL_TEXT );
+               self::$repl['pageG'] = Title::newFromText( 'ApiComparePagesTest G' )->getArticleId();
+
                WikiPage::factory( Title::newFromText( 'ApiComparePagesTest C' ) )
                        ->doDeleteArticleReal( 'Test for ApiComparePagesTest' );
 
@@ -132,6 +135,7 @@ class ApiComparePagesTest extends ApiTestCase {
 
                $params += [
                        'action' => 'compare',
+                       'errorformat' => 'none',
                ];
 
                $user = $sysop
@@ -153,6 +157,25 @@ class ApiComparePagesTest extends ApiTestCase {
                }
        }
 
+       private static function makeDeprecationWarnings( ...$params ) {
+               $warn = [];
+               foreach ( $params as $p ) {
+                       $warn[] = [
+                               'code' => 'deprecation',
+                               'data' => [ 'feature' => "action=compare&{$p}" ],
+                               'module' => 'compare',
+                       ];
+                       if ( count( $warn ) === 1 ) {
+                               $warn[] = [
+                                       'code' => 'deprecation-help',
+                                       'module' => 'main',
+                               ];
+                       }
+               }
+
+               return $warn;
+       }
+
        public static function provideDiff() {
                // phpcs:disable Generic.Files.LineLength.TooLong
                return [
@@ -269,10 +292,12 @@ class ApiComparePagesTest extends ApiTestCase {
                        ],
                        'Basic diff, text' => [
                                [
-                                       'fromtext' => 'From text',
-                                       'fromcontentmodel' => 'wikitext',
-                                       'totext' => 'To text {{subst:PAGENAME}}',
-                                       'tocontentmodel' => 'wikitext',
+                                       'fromslots' => 'main',
+                                       'fromtext-main' => 'From text',
+                                       'fromcontentmodel-main' => 'wikitext',
+                                       'toslots' => 'main',
+                                       'totext-main' => 'To text {{subst:PAGENAME}}',
+                                       'tocontentmodel-main' => 'wikitext',
                                ],
                                [
                                        'compare' => [
@@ -284,9 +309,11 @@ class ApiComparePagesTest extends ApiTestCase {
                        ],
                        'Basic diff, text 2' => [
                                [
-                                       'fromtext' => 'From text',
-                                       'totext' => 'To text {{subst:PAGENAME}}',
-                                       'tocontentmodel' => 'wikitext',
+                                       'fromslots' => 'main',
+                                       'fromtext-main' => 'From text',
+                                       'toslots' => 'main',
+                                       'totext-main' => 'To text {{subst:PAGENAME}}',
+                                       'tocontentmodel-main' => 'wikitext',
                                ],
                                [
                                        'compare' => [
@@ -298,15 +325,13 @@ class ApiComparePagesTest extends ApiTestCase {
                        ],
                        'Basic diff, guessed model' => [
                                [
-                                       'fromtext' => 'From text',
-                                       'totext' => 'To text',
+                                       'fromslots' => 'main',
+                                       'fromtext-main' => 'From text',
+                                       'toslots' => 'main',
+                                       'totext-main' => 'To text',
                                ],
                                [
-                                       'warnings' => [
-                                               'compare' => [
-                                                       'warnings' => 'No content model could be determined, assuming wikitext.',
-                                               ],
-                                       ],
+                                       'warnings' => [ [ 'code' => 'compare-nocontentmodel', 'module' => 'compare' ] ],
                                        'compare' => [
                                                'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
                                                        . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
@@ -316,9 +341,11 @@ class ApiComparePagesTest extends ApiTestCase {
                        ],
                        'Basic diff, text with title and PST' => [
                                [
-                                       'fromtext' => 'From text',
+                                       'fromslots' => 'main',
+                                       'fromtext-main' => 'From text',
                                        'totitle' => 'Test',
-                                       'totext' => 'To text {{subst:PAGENAME}}',
+                                       'toslots' => 'main',
+                                       'totext-main' => 'To text {{subst:PAGENAME}}',
                                        'topst' => true,
                                ],
                                [
@@ -331,9 +358,11 @@ class ApiComparePagesTest extends ApiTestCase {
                        ],
                        'Basic diff, text with page ID and PST' => [
                                [
-                                       'fromtext' => 'From text',
+                                       'fromslots' => 'main',
+                                       'fromtext-main' => 'From text',
                                        'toid' => '{{REPL:pageB}}',
-                                       'totext' => 'To text {{subst:PAGENAME}}',
+                                       'toslots' => 'main',
+                                       'totext-main' => 'To text {{subst:PAGENAME}}',
                                        'topst' => true,
                                ],
                                [
@@ -346,9 +375,11 @@ class ApiComparePagesTest extends ApiTestCase {
                        ],
                        'Basic diff, text with revision and PST' => [
                                [
-                                       'fromtext' => 'From text',
+                                       'fromslots' => 'main',
+                                       'fromtext-main' => 'From text',
                                        'torev' => '{{REPL:revB2}}',
-                                       'totext' => 'To text {{subst:PAGENAME}}',
+                                       'toslots' => 'main',
+                                       'totext-main' => 'To text {{subst:PAGENAME}}',
                                        'topst' => true,
                                ],
                                [
@@ -361,9 +392,11 @@ class ApiComparePagesTest extends ApiTestCase {
                        ],
                        'Basic diff, text with deleted revision and PST' => [
                                [
-                                       'fromtext' => 'From text',
+                                       'fromslots' => 'main',
+                                       'fromtext-main' => 'From text',
                                        'torev' => '{{REPL:revC2}}',
-                                       'totext' => 'To text {{subst:PAGENAME}}',
+                                       'toslots' => 'main',
+                                       'totext-main' => 'To text {{subst:PAGENAME}}',
                                        'topst' => true,
                                ],
                                [
@@ -378,20 +411,23 @@ class ApiComparePagesTest extends ApiTestCase {
                        'Basic diff, test with sections' => [
                                [
                                        'fromtitle' => 'ApiComparePagesTest F',
-                                       'fromsection' => 1,
-                                       'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
-                                       'tosection' => 2,
+                                       'fromslots' => 'main',
+                                       'fromtext-main' => "== Section 2 ==\nFrom text?",
+                                       'fromsection-main' => 2,
+                                       'totitle' => 'ApiComparePagesTest F',
+                                       'toslots' => 'main',
+                                       'totext-main' => "== Section 1 ==\nTo text?",
+                                       'tosection-main' => 1,
                                ],
                                [
                                        'compare' => [
                                                'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
                                                        . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
-                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>== Section <del class="diffchange diffchange-inline">1 </del>==</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>== Section <ins class="diffchange diffchange-inline">2 </ins>==</div></td></tr>' . "\n"
-                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n",
-                                               'fromid' => '{{REPL:pageF}}',
-                                               'fromrevid' => '{{REPL:revF1}}',
-                                               'fromns' => '0',
-                                               'fromtitle' => 'ApiComparePagesTest F',
+                                                       . '<tr><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 1 ==</div></td><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 1 ==</div></td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'> </td><td class=\'diff-context\'></td><td class=\'diff-marker\'> </td><td class=\'diff-context\'></td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 2 ==</div></td><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 2 ==</div></td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From text?</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">F 1.2</ins></div></td></tr>' . "\n",
                                        ]
                                ],
                        ],
@@ -517,6 +553,197 @@ class ApiComparePagesTest extends ApiTestCase {
                                        ]
                                ],
                        ],
+                       'Diff for specific slots' => [
+                               // @todo Use a page with multiple slots here
+                               [
+                                       'fromrev' => '{{REPL:revA1}}',
+                                       'torev' => '{{REPL:revA3}}',
+                                       'prop' => 'diff',
+                                       'slots' => 'main',
+                               ],
+                               [
+                                       'compare' => [
+                                               'bodies' => [
+                                                       'main' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                               . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                               . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>A <del class="diffchange diffchange-inline">1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>A <ins class="diffchange diffchange-inline">3</ins></div></td></tr>' . "\n",
+                                               ],
+                                       ],
+                               ],
+                       ],
+                       // @todo Add a test for diffing with a deleted slot. Deleting 'main' doesn't work.
+
+                       'Basic diff, deprecated text' => [
+                               [
+                                       'fromtext' => 'From text',
+                                       'fromcontentmodel' => 'wikitext',
+                                       'totext' => 'To text {{subst:PAGENAME}}',
+                                       'tocontentmodel' => 'wikitext',
+                               ],
+                               [
+                                       'warnings' => self::makeDeprecationWarnings( 'fromtext', 'fromcontentmodel', 'totext', 'tocontentmodel' ),
+                                       'compare' => [
+                                               'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                       . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">{{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
+                                       ]
+                               ],
+                       ],
+                       'Basic diff, deprecated text 2' => [
+                               [
+                                       'fromtext' => 'From text',
+                                       'totext' => 'To text {{subst:PAGENAME}}',
+                                       'tocontentmodel' => 'wikitext',
+                               ],
+                               [
+                                       'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext', 'tocontentmodel' ),
+                                       'compare' => [
+                                               'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                       . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">{{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
+                                       ]
+                               ],
+                       ],
+                       'Basic diff, deprecated text, guessed model' => [
+                               [
+                                       'fromtext' => 'From text',
+                                       'totext' => 'To text',
+                               ],
+                               [
+                                       'warnings' => array_merge( self::makeDeprecationWarnings( 'fromtext', 'totext' ), [
+                                               [ 'code' => 'compare-nocontentmodel', 'module' => 'compare' ],
+                                       ] ),
+                                       'compare' => [
+                                               'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                       . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text</div></td></tr>' . "\n",
+                                       ]
+                               ],
+                       ],
+                       'Basic diff, deprecated text with title and PST' => [
+                               [
+                                       'fromtext' => 'From text',
+                                       'totitle' => 'Test',
+                                       'totext' => 'To text {{subst:PAGENAME}}',
+                                       'topst' => true,
+                               ],
+                               [
+                                       'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+                                       'compare' => [
+                                               'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                       . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">Test</ins></div></td></tr>' . "\n",
+                                       ]
+                               ],
+                       ],
+                       'Basic diff, deprecated text with page ID and PST' => [
+                               [
+                                       'fromtext' => 'From text',
+                                       'toid' => '{{REPL:pageB}}',
+                                       'totext' => 'To text {{subst:PAGENAME}}',
+                                       'topst' => true,
+                               ],
+                               [
+                                       'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+                                       'compare' => [
+                                               'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                       . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest B</ins></div></td></tr>' . "\n",
+                                       ]
+                               ],
+                       ],
+                       'Basic diff, deprecated text with revision and PST' => [
+                               [
+                                       'fromtext' => 'From text',
+                                       'torev' => '{{REPL:revB2}}',
+                                       'totext' => 'To text {{subst:PAGENAME}}',
+                                       'topst' => true,
+                               ],
+                               [
+                                       'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+                                       'compare' => [
+                                               'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                       . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest B</ins></div></td></tr>' . "\n",
+                                       ]
+                               ],
+                       ],
+                       'Basic diff, deprecated text with deleted revision and PST' => [
+                               [
+                                       'fromtext' => 'From text',
+                                       'torev' => '{{REPL:revC2}}',
+                                       'totext' => 'To text {{subst:PAGENAME}}',
+                                       'topst' => true,
+                               ],
+                               [
+                                       'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+                                       'compare' => [
+                                               'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                       . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest C</ins></div></td></tr>' . "\n",
+                                       ]
+                               ],
+                               false, true
+                       ],
+                       'Basic diff, test with deprecated sections' => [
+                               [
+                                       'fromtitle' => 'ApiComparePagesTest F',
+                                       'fromsection' => 1,
+                                       'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
+                                       'tosection' => 2,
+                               ],
+                               [
+                                       'warnings' => self::makeDeprecationWarnings( 'fromsection', 'totext', 'tosection' ),
+                                       'compare' => [
+                                               'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                       . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>== Section <del class="diffchange diffchange-inline">1 </del>==</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>== Section <ins class="diffchange diffchange-inline">2 </ins>==</div></td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n",
+                                               'fromid' => '{{REPL:pageF}}',
+                                               'fromrevid' => '{{REPL:revF1}}',
+                                               'fromns' => '0',
+                                               'fromtitle' => 'ApiComparePagesTest F',
+                                       ]
+                               ],
+                       ],
+                       'Basic diff, test with deprecated sections and revdel, non-sysop' => [
+                               [
+                                       'fromrev' => '{{REPL:revB2}}',
+                                       'fromsection' => 0,
+                                       'torev' => '{{REPL:revB4}}',
+                                       'tosection' => 0,
+                               ],
+                               [],
+                               'missingcontent'
+                       ],
+                       'Basic diff, test with deprecated sections and revdel, sysop' => [
+                               [
+                                       'fromrev' => '{{REPL:revB2}}',
+                                       'fromsection' => 0,
+                                       'torev' => '{{REPL:revB4}}',
+                                       'tosection' => 0,
+                               ],
+                               [
+                                       'warnings' => self::makeDeprecationWarnings( 'fromsection', 'tosection' ),
+                                       'compare' => [
+                                               'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                       . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>B <del class="diffchange diffchange-inline">2</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>B <ins class="diffchange diffchange-inline">4</ins></div></td></tr>' . "\n",
+                                               'fromid' => '{{REPL:pageB}}',
+                                               'fromrevid' => '{{REPL:revB2}}',
+                                               'fromns' => 0,
+                                               'fromtitle' => 'ApiComparePagesTest B',
+                                               'fromtexthidden' => true,
+                                               'fromuserhidden' => true,
+                                               'fromcommenthidden' => true,
+                                               'toid' => '{{REPL:pageB}}',
+                                               'torevid' => '{{REPL:revB4}}',
+                                               'tons' => 0,
+                                               'totitle' => 'ApiComparePagesTest B',
+                                       ]
+                               ],
+                               false, true,
+                       ],
 
                        'Error, missing title' => [
                                [
@@ -647,6 +874,68 @@ class ApiComparePagesTest extends ApiTestCase {
                                [],
                                'missingcontent'
                        ],
+                       'Error, Relative diff, no prev' => [
+                               [
+                                       'fromrev' => '{{REPL:revA1}}',
+                                       'torelative' => 'prev',
+                                       'prop' => 'ids',
+                               ],
+                               [],
+                               'baddiff'
+                       ],
+                       'Error, Relative diff, no next' => [
+                               [
+                                       'fromrev' => '{{REPL:revA4}}',
+                                       'torelative' => 'next',
+                                       'prop' => 'ids',
+                               ],
+                               [],
+                               'baddiff'
+                       ],
+                       'Error, section diff with no revision' => [
+                               [
+                                       'fromtitle' => 'ApiComparePagesTest F',
+                                       'toslots' => 'main',
+                                       'totext-main' => "== Section 1 ==\nTo text?",
+                                       'tosection-main' => 1,
+                               ],
+                               [],
+                               'compare-notorevision',
+                       ],
+                       'Error, section diff with revdeleted revision' => [
+                               [
+                                       'fromtitle' => 'ApiComparePagesTest F',
+                                       'torev' => '{{REPL:revB2}}',
+                                       'toslots' => 'main',
+                                       'totext-main' => "== Section 1 ==\nTo text?",
+                                       'tosection-main' => 1,
+                               ],
+                               [],
+                               'missingcontent',
+                       ],
+                       'Error, section diff with a content model not supporting sections' => [
+                               [
+                                       'fromtitle' => 'ApiComparePagesTest G',
+                                       'torev' => '{{REPL:revG1}}',
+                                       'toslots' => 'main',
+                                       'totext-main' => "== Section 1 ==\nTo text?",
+                                       'tosection-main' => 1,
+                               ],
+                               [],
+                               'sectionsnotsupported',
+                       ],
+                       'Error, section diff with bad content model' => [
+                               [
+                                       'fromtitle' => 'ApiComparePagesTest F',
+                                       'torev' => '{{REPL:revF1}}',
+                                       'toslots' => 'main',
+                                       'totext-main' => "== Section 1 ==\nTo text?",
+                                       'tosection-main' => 1,
+                                       'tocontentmodel-main' => CONTENT_MODEL_TEXT,
+                               ],
+                               [],
+                               'sectionreplacefailed',
+                       ],
                ];
                // phpcs:enable
        }
index 7044069..f223ef7 100644 (file)
@@ -70,7 +70,7 @@
                result = mw.user.generateRandomSessionId();
                assert.strictEqual( typeof result, 'string', 'type' );
                assert.strictEqual( result.trim(), result, 'no whitespace at beginning or end' );
-               assert.strictEqual( result.length, 16, 'size' );
+               assert.strictEqual( result.length, 20, 'size' );
 
                result2 = mw.user.generateRandomSessionId();
                assert.notEqual( result, result2, 'different when called multiple times' );
@@ -91,7 +91,7 @@
                result = mw.user.generateRandomSessionId();
                assert.strictEqual( typeof result, 'string', 'type' );
                assert.strictEqual( result.trim(), result, 'no whitespace at beginning or end' );
-               assert.strictEqual( result.length, 16, 'size' );
+               assert.strictEqual( result.length, 20, 'size' );
 
                result2 = mw.user.generateRandomSessionId();
                assert.notEqual( result, result2, 'different when called multiple times' );
                var result = mw.user.getPageviewToken(),
                        result2 = mw.user.getPageviewToken();
                assert.strictEqual( typeof result, 'string', 'type' );
-               assert.strictEqual( /^[a-f0-9]{16}$/.test( result ), true, '16 HEX symbols string' );
+               assert.strictEqual( /^[a-f0-9]{20}$/.test( result ), true, '20 HEX symbols string' );
                assert.strictEqual( result2, result, 'sticky' );
        } );