Merge "parserTests: Use "fallback" skin unless otherwise specified"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 25 Jul 2017 23:47:48 +0000 (23:47 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 25 Jul 2017 23:47:48 +0000 (23:47 +0000)
232 files changed:
RELEASE-NOTES-1.30
autoload.php
composer.json
includes/Block.php
includes/EditPage.php
includes/Feed.php
includes/FileDeleteForm.php
includes/HistoryBlob.php
includes/LinkFilter.php
includes/Linker.php
includes/MWNamespace.php
includes/MagicWord.php
includes/MediaWiki.php
includes/MimeMagic.php
includes/Preferences.php
includes/Revision.php
includes/Sanitizer.php
includes/ServiceWiring.php
includes/Setup.php
includes/SiteStats.php
includes/Title.php
includes/WikiMap.php
includes/Xml.php
includes/actions/Action.php
includes/api/ApiBase.php
includes/api/ApiEmailUser.php
includes/api/ApiMain.php
includes/api/ApiPageSet.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryInfo.php
includes/api/ApiResult.php
includes/api/ApiUsageException.php
includes/api/i18n/he.json
includes/api/i18n/ko.json
includes/api/i18n/zh-hant.json
includes/auth/AuthenticationResponse.php
includes/cache/MessageCache.php
includes/changes/ChangesFeed.php
includes/changes/EnhancedChangesList.php
includes/changes/RecentChange.php
includes/collation/IcuCollation.php
includes/collation/NumericUppercaseCollation.php
includes/config/EtcdConfig.php
includes/content/ContentHandler.php
includes/context/DerivativeContext.php
includes/db/CloneDatabase.php
includes/debug/MWDebug.php
includes/deferred/LinksUpdate.php
includes/deferred/SearchUpdate.php
includes/diff/DifferenceEngine.php
includes/exception/HttpError.php
includes/export/WikiExporter.php
includes/filerepo/ForeignAPIRepo.php
includes/htmlform/HTMLForm.php
includes/http/Http.php
includes/import/ImportStreamSource.php
includes/installer/DatabaseUpdater.php
includes/installer/WebInstaller.php
includes/installer/i18n/csb.json
includes/jobqueue/JobQueue.php
includes/jobqueue/JobQueueFederated.php
includes/jobqueue/JobQueueSecondTestQueue.php [new file with mode: 0644]
includes/libs/CSSMin.php
includes/libs/IP.php
includes/libs/StringUtils.php
includes/libs/objectcache/APCBagOStuff.php
includes/libs/objectcache/APCUBagOStuff.php
includes/libs/objectcache/MemcachedPeclBagOStuff.php
includes/libs/objectcache/MultiWriteBagOStuff.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/logging/LogPage.php
includes/logging/LogPager.php
includes/mail/UserMailer.php
includes/media/DjVuImage.php
includes/media/Exif.php
includes/media/SVGMetadataExtractor.php
includes/page/Article.php
includes/page/WikiPage.php
includes/pager/IndexPager.php
includes/parser/Parser.php
includes/parser/ParserOptions.php
includes/parser/ParserOutput.php
includes/profiler/output/ProfilerOutput.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderClientHtml.php
includes/resourceloader/ResourceLoaderModule.php
includes/search/SearchDatabase.php
includes/skins/Skin.php
includes/specialpage/SpecialPage.php
includes/specials/SpecialActiveusers.php
includes/specials/SpecialAllMessages.php
includes/specials/SpecialAllPages.php
includes/specials/SpecialImport.php
includes/specials/SpecialListusers.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialSpecialpages.php
includes/specials/SpecialUndelete.php
includes/specials/SpecialUpload.php
includes/specials/pagers/UsersPager.php
includes/tidy/Balancer.php
includes/tidy/RemexCompatMunger.php
includes/user/BotPassword.php
includes/user/User.php
languages/ConverterRule.php
languages/Language.php
languages/data/Names.php
languages/i18n/ast.json
languages/i18n/ba.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/bho.json
languages/i18n/bn.json
languages/i18n/bs.json
languages/i18n/cdo.json
languages/i18n/ckb.json
languages/i18n/cs.json
languages/i18n/csb.json
languages/i18n/cy.json
languages/i18n/da.json
languages/i18n/de.json
languages/i18n/din.json
languages/i18n/en.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/fr.json
languages/i18n/frr.json
languages/i18n/gl.json
languages/i18n/gor.json
languages/i18n/gsw.json
languages/i18n/hak.json
languages/i18n/he.json
languages/i18n/hi.json
languages/i18n/hr.json
languages/i18n/hu.json
languages/i18n/hy.json
languages/i18n/it.json
languages/i18n/jv.json
languages/i18n/ko.json
languages/i18n/lb.json
languages/i18n/li.json
languages/i18n/mk.json
languages/i18n/nan.json
languages/i18n/nap.json
languages/i18n/nb.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/pl.json
languages/i18n/pnb.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/rif.json
languages/i18n/rm.json
languages/i18n/ro.json
languages/i18n/roa-tara.json
languages/i18n/ru.json
languages/i18n/sd.json
languages/i18n/shi.json
languages/i18n/skr-arab.json [new file with mode: 0644]
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/sv.json
languages/i18n/te.json
languages/i18n/tet.json
languages/i18n/tg-cyrl.json
languages/i18n/uk.json
languages/i18n/vi.json
languages/i18n/yi.json
languages/i18n/yue.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/messages/MessagesSkr.php [new file with mode: 0644]
languages/messages/MessagesSkr_arab.php [new file with mode: 0644]
maintenance/Maintenance.php
maintenance/addRFCandPMIDInterwiki.php
maintenance/generateSitemap.php
maintenance/language/checkLanguage.inc
maintenance/populatePPSortKey.php [new file with mode: 0644]
maintenance/sqlite.inc
maintenance/userOptions.inc
phpcs.xml
resources/Resources.php
resources/lib/qunitjs/qunit.css
resources/lib/qunitjs/qunit.js
resources/src/mediawiki.legacy/commonPrint.css
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js
resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js
resources/src/mediawiki.widgets.visibleByteLimit/mediawiki.widgets.visibleByteLimit.js
resources/src/mediawiki/mediawiki.hlist-allskins.less [new file with mode: 0644]
resources/src/mediawiki/mediawiki.hlist.css
resources/src/oojs-ui-local.js
tests/parser/ParserTestRunner.php
tests/parser/parserTests.txt
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/ResourceLoaderTestCase.php
tests/phpunit/includes/DeprecatedGlobalTest.php
tests/phpunit/includes/GitInfoTest.php
tests/phpunit/includes/PreferencesTest.php
tests/phpunit/includes/SanitizerTest.php
tests/phpunit/includes/SiteStatsTest.php [new file with mode: 0644]
tests/phpunit/includes/api/ApiErrorFormatterTest.php
tests/phpunit/includes/api/ApiMainTest.php
tests/phpunit/includes/config/ConfigFactoryTest.php
tests/phpunit/includes/config/EtcdConfigTest.php
tests/phpunit/includes/content/WikitextContentTest.php
tests/phpunit/includes/db/DatabaseMysqlBaseTest.php [deleted file]
tests/phpunit/includes/db/DatabaseSQLTest.php [deleted file]
tests/phpunit/includes/db/DatabaseSqliteTest.php
tests/phpunit/includes/db/DatabaseTest.php [deleted file]
tests/phpunit/includes/libs/CSSMinTest.php
tests/phpunit/includes/libs/objectcache/MultiWriteBagOStuffTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php [new file with mode: 0644]
tests/phpunit/includes/page/WikiPageTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php
tests/phpunit/includes/site/TestSites.php
tests/phpunit/includes/specials/SpecialRecentchangesTest.php
tests/phpunit/structure/DatabaseIntegrationTest.php [new file with mode: 0644]
tests/phpunit/suite.xml
tests/qunit/data/testrunner.js
tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js

index 51f9764..c5ab81a 100644 (file)
@@ -46,6 +46,7 @@ section).
 === Languages updated in 1.30 ===
 
 * Support for kbp (Kabɩyɛ / Kabiyè) was added.
+* Support for skr (Saraiki, سرائیکی) was added.
 
 === External library changes in 1.30 ===
 
index a6128a4..510eeee 100644 (file)
@@ -679,6 +679,7 @@ $wgAutoloadLocalClasses = [
        'JobQueueMemory' => __DIR__ . '/includes/jobqueue/JobQueueMemory.php',
        'JobQueueReadOnlyError' => __DIR__ . '/includes/jobqueue/JobQueue.php',
        'JobQueueRedis' => __DIR__ . '/includes/jobqueue/JobQueueRedis.php',
+       'JobQueueSecondTestQueue' => __DIR__ . '/includes/jobqueue/JobQueueSecondTestQueue.php',
        'JobRunner' => __DIR__ . '/includes/jobqueue/JobRunner.php',
        'JobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php',
        'JpegHandler' => __DIR__ . '/includes/media/Jpeg.php',
@@ -1118,6 +1119,7 @@ $wgAutoloadLocalClasses = [
        'PopulateInterwiki' => __DIR__ . '/maintenance/populateInterwiki.php',
        'PopulateLogSearch' => __DIR__ . '/maintenance/populateLogSearch.php',
        'PopulateLogUsertext' => __DIR__ . '/maintenance/populateLogUsertext.php',
+       'PopulatePPSortKey' => __DIR__ . '/maintenance/populatePPSortKey.php',
        'PopulateParentId' => __DIR__ . '/maintenance/populateParentId.php',
        'PopulateRecentChangesSource' => __DIR__ . '/maintenance/populateRecentChangesSource.php',
        'PopulateRevisionLength' => __DIR__ . '/maintenance/populateRevisionLength.php',
index 83fcda0..d04e9f5 100644 (file)
@@ -53,7 +53,7 @@
                "jakub-onderka/php-parallel-lint": "0.9.2",
                "jetbrains/phpstorm-stubs": "dev-master#1b9906084d6635456fcf3f3a01f0d7d5b99a578a",
                "justinrainbow/json-schema": "~5.2",
-               "mediawiki/mediawiki-codesniffer": "0.8.1",
+               "mediawiki/mediawiki-codesniffer": "0.10.1",
                "monolog/monolog": "~1.22.1",
                "nikic/php-parser": "2.1.0",
                "nmred/kafka-php": "0.1.5",
index 2c935df..2a04879 100644 (file)
@@ -485,7 +485,7 @@ class Block {
 
                # Periodic purge via commit hooks
                if ( mt_rand( 0, 9 ) == 0 ) {
-                       Block::purgeExpired();
+                       self::purgeExpired();
                }
 
                $row = $this->getDatabaseArray();
@@ -778,12 +778,12 @@ class Block {
                # It's okay to autoblock. Go ahead and insert/update the block...
 
                # Do not add a *new* block if the IP is already blocked.
-               $ipblock = Block::newFromTarget( $autoblockIP );
+               $ipblock = self::newFromTarget( $autoblockIP );
                if ( $ipblock ) {
                        # Check if the block is an autoblock and would exceed the user block
                        # if renewed. If so, do nothing, otherwise prolong the block time...
                        if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry?
-                               $this->mExpiry > Block::getAutoblockExpiry( $ipblock->mTimestamp )
+                               $this->mExpiry > self::getAutoblockExpiry( $ipblock->mTimestamp )
                        ) {
                                # Reset block timestamp to now and its expiry to
                                # $wgAutoblockExpiry in the future
@@ -810,11 +810,11 @@ class Block {
 
                if ( $this->mExpiry == 'infinity' ) {
                        # Original block was indefinite, start an autoblock now
-                       $autoblock->mExpiry = Block::getAutoblockExpiry( $timestamp );
+                       $autoblock->mExpiry = self::getAutoblockExpiry( $timestamp );
                } else {
                        # If the user is already blocked with an expiry date, we don't
                        # want to pile on top of that.
-                       $autoblock->mExpiry = min( $this->mExpiry, Block::getAutoblockExpiry( $timestamp ) );
+                       $autoblock->mExpiry = min( $this->mExpiry, self::getAutoblockExpiry( $timestamp ) );
                }
 
                # Insert the block...
@@ -870,7 +870,7 @@ class Block {
        public function updateTimestamp() {
                if ( $this->mAuto ) {
                        $this->mTimestamp = wfTimestamp();
-                       $this->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp );
+                       $this->mExpiry = self::getAutoblockExpiry( $this->mTimestamp );
 
                        $dbw = wfGetDB( DB_MASTER );
                        $dbw->update( 'ipblocks',
@@ -1111,8 +1111,8 @@ class Block {
         */
        public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) {
                list( $target, $type ) = self::parseTarget( $specificTarget );
-               if ( $type == Block::TYPE_ID || $type == Block::TYPE_AUTO ) {
-                       return Block::newFromID( $target );
+               if ( $type == self::TYPE_ID || $type == self::TYPE_AUTO ) {
+                       return self::newFromID( $target );
 
                } elseif ( $target === null && $vagueTarget == '' ) {
                        # We're not going to find anything useful here
@@ -1122,7 +1122,7 @@ class Block {
 
                } elseif ( in_array(
                        $type,
-                       [ Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ] )
+                       [ self::TYPE_USER, self::TYPE_IP, self::TYPE_RANGE, null ] )
                ) {
                        $block = new Block();
                        $block->fromMaster( $fromMaster );
@@ -1189,7 +1189,7 @@ class Block {
                }
                $selectFields = array_merge(
                        [ 'ipb_range_start', 'ipb_range_end' ],
-                       Block::selectFields()
+                       self::selectFields()
                );
                $rows = $db->select( 'ipblocks',
                        $selectFields,
@@ -1350,12 +1350,12 @@ class Block {
                        # off validation checking (which would exclude IP addresses)
                        return [
                                User::newFromName( IP::sanitizeIP( $target ), false ),
-                               Block::TYPE_IP
+                               self::TYPE_IP
                        ];
 
                } elseif ( IP::isValidBlock( $target ) ) {
                        # Can't create a User from an IP range
-                       return [ IP::sanitizeRange( $target ), Block::TYPE_RANGE ];
+                       return [ IP::sanitizeRange( $target ), self::TYPE_RANGE ];
                }
 
                # Consider the possibility that this is not a username at all
@@ -1370,11 +1370,11 @@ class Block {
                        # Note that since numbers are valid usernames, a $target of "12345" will be
                        # considered a User.  If you want to pass a block ID, prepend a hash "#12345",
                        # since hash characters are not valid in usernames or titles generally.
-                       return [ $userObj, Block::TYPE_USER ];
+                       return [ $userObj, self::TYPE_USER ];
 
                } elseif ( preg_match( '/^#\d+$/', $target ) ) {
                        # Autoblock reference in the form "#12345"
-                       return [ substr( $target, 1 ), Block::TYPE_AUTO ];
+                       return [ substr( $target, 1 ), self::TYPE_AUTO ];
 
                } else {
                        # WTF?
index 973327b..229a36a 100644 (file)
@@ -2783,7 +2783,7 @@ class EditPage {
                $wgOut->addHTML( $this->editFormTextBeforeContent );
 
                if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
-                       $wgOut->addHTML( EditPage::getEditToolbar( $this->mTitle ) );
+                       $wgOut->addHTML( self::getEditToolbar( $this->mTitle ) );
                }
 
                if ( $this->blankArticle ) {
@@ -3492,6 +3492,10 @@ HTML
                }
        }
 
+       /**
+        * Inserts optional text shown below edit and upload forms. Can be used to offer special characters not present on
+        * most keyboards for copying/pasting.
+        */
        protected function showEditTools() {
                global $wgOut;
                $wgOut->addHTML( '<div class="mw-editTools">' .
index 189fd9f..f76a634 100644 (file)
@@ -54,8 +54,6 @@ class FeedItem {
        public $rssIsPermalink = false;
 
        /**
-        * Constructor
-        *
         * @param string|Title $title Item's title
         * @param string $description
         * @param string $url URL uniquely designating the item.
index e7b4a1f..8a1cd35 100644 (file)
@@ -47,8 +47,6 @@ class FileDeleteForm {
        private $oldimage = '';
 
        /**
-        * Constructor
-        *
         * @param File $file File object we're deleting
         */
        public function __construct( $file ) {
index 56cf815..51bd7a9 100644 (file)
@@ -76,9 +76,6 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob {
        public $mMaxSize = 10000000;
        public $mMaxCount = 100;
 
-       /**
-        * Constructor
-        */
        public function __construct() {
                if ( !function_exists( 'gzdeflate' ) ) {
                        throw new MWException( "Need zlib support to read or write this "
index 2f50558..790e2be 100644 (file)
@@ -50,7 +50,7 @@ class LinkFilter {
 
                $text = $content->getNativeData();
 
-               $regex = LinkFilter::makeRegex( $filterEntry );
+               $regex = self::makeRegex( $filterEntry );
                return preg_match( $regex, $text );
        }
 
index f2e4ac4..4aae3ba 100644 (file)
@@ -1328,7 +1328,7 @@ class Linker {
                Title $title, $text, $wikiId = null, $options = []
        ) {
                if ( $wikiId !== null && !$title->isExternal() ) {
-                       $link = Linker::makeExternalLink(
+                       $link = self::makeExternalLink(
                                WikiMap::getForeignURL(
                                        $wikiId,
                                        $title->getNamespace() === 0
@@ -1341,7 +1341,7 @@ class Linker {
                                /* escape = */ false // Already escaped
                        );
                } else {
-                       $link = Linker::link( $title, $text, [], [], $options );
+                       $link = self::link( $title, $text, [], [], $options );
                }
 
                return $link;
@@ -2021,7 +2021,7 @@ class Linker {
                }
 
                if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
-                       return Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
+                       return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
                } else {
                        if ( $rev->getId() ) {
                                // RevDelete links using revision ID are stable across
@@ -2040,7 +2040,7 @@ class Linker {
                                        'ids' => $rev->getTimestamp()
                                ];
                        }
-                       return Linker::revDeleteLink( $query,
+                       return self::revDeleteLink( $query,
                                $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
                }
        }
index 89cb616..97dba26 100644 (file)
@@ -370,7 +370,7 @@ class MWNamespace {
         */
        public static function getSubjectNamespaces() {
                return array_filter(
-                       MWNamespace::getValidNamespaces(),
+                       self::getValidNamespaces(),
                        'MWNamespace::isSubject'
                );
        }
@@ -383,7 +383,7 @@ class MWNamespace {
         */
        public static function getTalkNamespaces() {
                return array_filter(
-                       MWNamespace::getValidNamespaces(),
+                       self::getValidNamespaces(),
                        'MWNamespace::isTalk'
                );
        }
index ee95918..1703179 100644 (file)
@@ -664,7 +664,7 @@ class MagicWord {
                $search = [];
                $replace = [];
                foreach ( $magicarr as $id => $replacement ) {
-                       $mw = MagicWord::get( $id );
+                       $mw = self::get( $id );
                        $search[] = $mw->getRegex();
                        $replace[] = $replacement;
                }
index 4df4d76..4e47184 100644 (file)
@@ -609,6 +609,7 @@ class MediaWiki {
                        $lbFactory->hasOrMadeRecentMasterChanges( INF )
                ) ? self::getUrlDomainDistance( $output->getRedirect(), $context ) : false;
 
+               $allowHeaders = !( $output->isDisabled() || headers_sent() );
                if ( $urlDomainDistance === 'local' || $urlDomainDistance === 'remote' ) {
                        // OutputPage::output() will be fast; $postCommitWork will not be useful for
                        // masking the latency of syncing DB positions accross all datacenters synchronously.
@@ -616,7 +617,7 @@ class MediaWiki {
                        $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
                        $cpPosTime = microtime( true );
                        // Client's next request should see 1+ positions with this DBMasterPos::asOf() time
-                       if ( $urlDomainDistance === 'local' ) {
+                       if ( $urlDomainDistance === 'local' && $allowHeaders ) {
                                // Client will stay on this domain, so set an unobtrusive cookie
                                $expires = time() + ChronologyProtector::POSITION_TTL;
                                $options = [ 'prefix' => '' ];
@@ -633,7 +634,7 @@ class MediaWiki {
                        // OutputPage::output() is fairly slow; run it in $postCommitWork to mask
                        // the latency of syncing DB positions accross all datacenters synchronously
                        $flags = $lbFactory::SHUTDOWN_CHRONPROT_SYNC;
-                       if ( $lbFactory->hasOrMadeRecentMasterChanges( INF ) ) {
+                       if ( $lbFactory->hasOrMadeRecentMasterChanges( INF ) && $allowHeaders ) {
                                $cpPosTime = microtime( true );
                                // Set a cookie in case the DB position store cannot sync accross datacenters.
                                // This will at least cover the common case of the user staying on the domain.
index 8670729..a2a44bb 100644 (file)
@@ -35,7 +35,7 @@ class MimeMagic extends MimeAnalyzer {
                $instance = MediaWikiServices::getInstance()->getMimeAnalyzer();
                Assert::postcondition(
                        $instance instanceof MimeMagic,
-                       __METHOD__ . ' should return an instance of ' . MimeMagic::class
+                       __METHOD__ . ' should return an instance of ' . self::class
                );
                return $instance;
        }
index 008963b..7efbef1 100644 (file)
@@ -1316,7 +1316,7 @@ class Preferences {
                $formClass = 'PreferencesForm',
                array $remove = []
        ) {
-               $formDescriptor = Preferences::getPreferences( $user, $context );
+               $formDescriptor = self::getPreferences( $user, $context );
                if ( count( $remove ) ) {
                        $removeKeys = array_flip( $remove );
                        $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
index c3782ba..537b7c1 100644 (file)
@@ -559,8 +559,6 @@ class Revision implements IDBAccessObject {
        }
 
        /**
-        * Constructor
-        *
         * @param object|array $row Either a database row or an array
         * @throws MWException
         * @access private
@@ -1004,7 +1002,7 @@ class Revision implements IDBAccessObject {
 
                return RecentChange::newFromConds(
                        [
-                               'rc_user_text' => $this->getUserText( Revision::RAW ),
+                               'rc_user_text' => $this->getUserText( self::RAW ),
                                'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
                                'rc_this_oldid' => $this->getId()
                        ],
@@ -1468,7 +1466,7 @@ class Revision implements IDBAccessObject {
                                ? $this->getPreviousRevisionId( $dbw )
                                : $this->mParentId,
                        'rev_sha1'       => $this->mSha1 === null
-                               ? Revision::base36Sha1( $this->mText )
+                               ? self::base36Sha1( $this->mText )
                                : $this->mSha1,
                ];
 
@@ -1557,7 +1555,7 @@ class Revision implements IDBAccessObject {
                        }
                }
 
-               $content = $this->getContent( Revision::RAW );
+               $content = $this->getContent( self::RAW );
                $prefixedDBkey = $title->getPrefixedDBkey();
                $revId = $this->mId;
 
index b08bc69..2def06a 100644 (file)
@@ -465,7 +465,7 @@ class Sanitizer {
                extract( self::getRecognizedTagData( $extratags, $removetags ) );
 
                # Remove HTML comments
-               $text = Sanitizer::removeHTMLcomments( $text );
+               $text = self::removeHTMLcomments( $text );
                $bits = explode( '<', $text );
                $text = str_replace( '>', '&gt;', array_shift( $bits ) );
                if ( !MWTidy::isEnabled() ) {
@@ -583,12 +583,12 @@ class Sanitizer {
                                                        call_user_func_array( $processCallback, [ &$params, $args ] );
                                                }
 
-                                               if ( !Sanitizer::validateTag( $params, $t ) ) {
+                                               if ( !self::validateTag( $params, $t ) ) {
                                                        $badtag = true;
                                                }
 
                                                # Strip non-approved attributes from the tag
-                                               $newparams = Sanitizer::fixTagAttributes( $params, $t );
+                                               $newparams = self::fixTagAttributes( $params, $t );
                                        }
                                        if ( !$badtag ) {
                                                $rest = str_replace( '>', '&gt;', $rest );
@@ -629,11 +629,11 @@ class Sanitizer {
                                                                call_user_func_array( $warnCallback, [ 'deprecated-self-close-category' ] );
                                                        }
                                                }
-                                               if ( !Sanitizer::validateTag( $params, $t ) ) {
+                                               if ( !self::validateTag( $params, $t ) ) {
                                                        $badtag = true;
                                                }
 
-                                               $newparams = Sanitizer::fixTagAttributes( $params, $t );
+                                               $newparams = self::fixTagAttributes( $params, $t );
                                                if ( !$badtag ) {
                                                        if ( $brace === '/>' && !isset( $htmlsingleonly[$t] ) ) {
                                                                # Interpret self-closing tags as empty tags even when
@@ -710,7 +710,7 @@ class Sanitizer {
         * @return bool
         */
        static function validateTag( $params, $element ) {
-               $params = Sanitizer::decodeTagAttributes( $params );
+               $params = self::decodeTagAttributes( $params );
 
                if ( $element == 'meta' || $element == 'link' ) {
                        if ( !isset( $params['itemprop'] ) ) {
@@ -746,8 +746,8 @@ class Sanitizer {
         * @todo Check for unique id attribute :P
         */
        static function validateTagAttributes( $attribs, $element ) {
-               return Sanitizer::validateAttributes( $attribs,
-                       Sanitizer::attributeWhitelist( $element ) );
+               return self::validateAttributes( $attribs,
+                       self::attributeWhitelist( $element ) );
        }
 
        /**
@@ -795,12 +795,12 @@ class Sanitizer {
                        # Strip javascript "expression" from stylesheets.
                        # https://msdn.microsoft.com/en-us/library/ms537634.aspx
                        if ( $attribute == 'style' ) {
-                               $value = Sanitizer::checkCss( $value );
+                               $value = self::checkCss( $value );
                        }
 
                        # Escape HTML id attributes
                        if ( $attribute === 'id' ) {
-                               $value = Sanitizer::escapeId( $value, 'noninitial' );
+                               $value = self::escapeId( $value, 'noninitial' );
                        }
 
                        # Escape HTML id reference lists
@@ -809,7 +809,7 @@ class Sanitizer {
                                || $attribute === 'aria-labelledby'
                                || $attribute === 'aria-owns'
                        ) {
-                               $value = Sanitizer::escapeIdReferenceList( $value, 'noninitial' );
+                               $value = self::escapeIdReferenceList( $value, 'noninitial' );
                        }
 
                        // RDFa and microdata properties allow URLs, URIs and/or CURIs.
@@ -907,7 +907,7 @@ class Sanitizer {
         */
        public static function normalizeCss( $value ) {
                // Decode character references like &#123;
-               $value = Sanitizer::decodeCharReferences( $value );
+               $value = self::decodeCharReferences( $value );
 
                // Decode escape sequences and line continuation
                // See the grammar in the CSS 2 spec, appendix D.
@@ -1087,14 +1087,14 @@ class Sanitizer {
                        return '';
                }
 
-               $decoded = Sanitizer::decodeTagAttributes( $text );
-               $stripped = Sanitizer::validateTagAttributes( $decoded, $element );
+               $decoded = self::decodeTagAttributes( $text );
+               $stripped = self::validateTagAttributes( $decoded, $element );
 
                if ( $sorted ) {
                        ksort( $stripped );
                }
 
-               return Sanitizer::safeEncodeTagAttributes( $stripped );
+               return self::safeEncodeTagAttributes( $stripped );
        }
 
        /**
@@ -1124,7 +1124,7 @@ class Sanitizer {
         * @return string HTML-encoded text fragment
         */
        static function safeEncodeAttribute( $text ) {
-               $encValue = Sanitizer::encodeAttribute( $text );
+               $encValue = self::encodeAttribute( $text );
 
                # Templates and links may be expanded in later parsing,
                # creating invalid or dangerous output. Suppress this.
@@ -1186,7 +1186,7 @@ class Sanitizer {
                global $wgExperimentalHtmlIds;
                $options = (array)$options;
 
-               $id = Sanitizer::decodeCharReferences( $id );
+               $id = self::decodeCharReferences( $id );
 
                if ( $wgExperimentalHtmlIds && !in_array( 'legacy', $options ) ) {
                        $id = preg_replace( '/[ \t\n\r\f_\'"&#%]+/', '_', $id );
@@ -1238,7 +1238,7 @@ class Sanitizer {
 
                # Escape each token as an id
                foreach ( $references as &$ref ) {
-                       $ref = Sanitizer::escapeId( $ref, $options );
+                       $ref = self::escapeId( $ref, $options );
                }
 
                # Merge the array back to a space delimited list string
@@ -1275,7 +1275,7 @@ class Sanitizer {
         * @return string Escaped input
         */
        static function escapeHtmlAllowEntities( $html ) {
-               $html = Sanitizer::decodeCharReferences( $html );
+               $html = self::decodeCharReferences( $html );
                # It seems wise to escape ' as well as ", as a matter of course.  Can't
                # hurt. Use ENT_SUBSTITUTE so that incorrectly truncated multibyte characters
                # don't cause the entire string to disappear.
@@ -1317,14 +1317,14 @@ class Sanitizer {
 
                foreach ( $pairs as $set ) {
                        $attribute = strtolower( $set[1] );
-                       $value = Sanitizer::getTagAttributeCallback( $set );
+                       $value = self::getTagAttributeCallback( $set );
 
                        // Normalize whitespace
                        $value = preg_replace( '/[\t\r\n ]+/', ' ', $value );
                        $value = trim( $value );
 
                        // Decode character references
-                       $attribs[$attribute] = Sanitizer::decodeCharReferences( $value );
+                       $attribs[$attribute] = self::decodeCharReferences( $value );
                }
                return $attribs;
        }
@@ -1340,7 +1340,7 @@ class Sanitizer {
                $attribs = [];
                foreach ( $assoc_array as $attribute => $value ) {
                        $encAttribute = htmlspecialchars( $attribute );
-                       $encValue = Sanitizer::safeEncodeAttribute( $value );
+                       $encValue = self::safeEncodeAttribute( $value );
 
                        $attribs[] = "$encAttribute=\"$encValue\"";
                }
@@ -1427,11 +1427,11 @@ class Sanitizer {
        static function normalizeCharReferencesCallback( $matches ) {
                $ret = null;
                if ( $matches[1] != '' ) {
-                       $ret = Sanitizer::normalizeEntity( $matches[1] );
+                       $ret = self::normalizeEntity( $matches[1] );
                } elseif ( $matches[2] != '' ) {
-                       $ret = Sanitizer::decCharReference( $matches[2] );
+                       $ret = self::decCharReference( $matches[2] );
                } elseif ( $matches[3] != '' ) {
-                       $ret = Sanitizer::hexCharReference( $matches[3] );
+                       $ret = self::hexCharReference( $matches[3] );
                }
                if ( is_null( $ret ) ) {
                        return htmlspecialchars( $matches[0] );
@@ -1468,7 +1468,7 @@ class Sanitizer {
         */
        static function decCharReference( $codepoint ) {
                $point = intval( $codepoint );
-               if ( Sanitizer::validateCodepoint( $point ) ) {
+               if ( self::validateCodepoint( $point ) ) {
                        return sprintf( '&#%d;', $point );
                } else {
                        return null;
@@ -1481,7 +1481,7 @@ class Sanitizer {
         */
        static function hexCharReference( $codepoint ) {
                $point = hexdec( $codepoint );
-               if ( Sanitizer::validateCodepoint( $point ) ) {
+               if ( self::validateCodepoint( $point ) ) {
                        return sprintf( '&#x%x;', $point );
                } else {
                        return null;
@@ -1550,11 +1550,11 @@ class Sanitizer {
         */
        static function decodeCharReferencesCallback( $matches ) {
                if ( $matches[1] != '' ) {
-                       return Sanitizer::decodeEntity( $matches[1] );
+                       return self::decodeEntity( $matches[1] );
                } elseif ( $matches[2] != '' ) {
-                       return Sanitizer::decodeChar( intval( $matches[2] ) );
+                       return self::decodeChar( intval( $matches[2] ) );
                } elseif ( $matches[3] != '' ) {
-                       return Sanitizer::decodeChar( hexdec( $matches[3] ) );
+                       return self::decodeChar( hexdec( $matches[3] ) );
                }
                # Last case should be an ampersand by itself
                return $matches[0];
@@ -1568,7 +1568,7 @@ class Sanitizer {
         * @private
         */
        static function decodeChar( $codepoint ) {
-               if ( Sanitizer::validateCodepoint( $codepoint ) ) {
+               if ( self::validateCodepoint( $codepoint ) ) {
                        return UtfNormal\Utils::codepointToUtf8( $codepoint );
                } else {
                        return UtfNormal\Constants::UTF8_REPLACEMENT;
@@ -1601,7 +1601,7 @@ class Sanitizer {
         * @return array
         */
        static function attributeWhitelist( $element ) {
-               $list = Sanitizer::setupAttributeWhitelist();
+               $list = self::setupAttributeWhitelist();
                return isset( $list[$element] )
                        ? $list[$element]
                        : [];
@@ -1876,7 +1876,7 @@ class Sanitizer {
        static function cleanUrl( $url ) {
                # Normalize any HTML entities in input. They will be
                # re-escaped by makeExternalLink().
-               $url = Sanitizer::decodeCharReferences( $url );
+               $url = self::decodeCharReferences( $url );
 
                # Escape any control characters introduced by the above step
                $url = preg_replace_callback( '/[\][<>"\\x00-\\x20\\x7F\|]/',
index e1244e7..d048007 100644 (file)
@@ -287,7 +287,7 @@ return [
                return ObjectFactory::constructClassInstance( $conf['class'], [ $conf ] );
        },
 
-       'ParserCache' => function( MediaWikiServices $services ) {
+       'ParserCache' => function ( MediaWikiServices $services ) {
                $config = $services->getMainConfig();
                $cache = ObjectCache::getInstance( $config->get( 'ParserCacheType' ) );
                wfDebugLog( 'caches', 'parser: ' . get_class( $cache ) );
@@ -298,7 +298,7 @@ return [
                );
        },
 
-       'LinkCache' => function( MediaWikiServices $services ) {
+       'LinkCache' => function ( MediaWikiServices $services ) {
                return new LinkCache(
                        $services->getTitleFormatter(),
                        $services->getMainWANObjectCache()
index ac00fab..3d5bee2 100644 (file)
@@ -687,7 +687,7 @@ $messageMemc = wfGetMessageCacheStorage();
 /**
  * @deprecated since 1.30
  */
-$parserMemc = new DeprecatedGlobal( 'parserMemc', function() {
+$parserMemc = new DeprecatedGlobal( 'parserMemc', function () {
        return MediaWikiServices::getInstance()->getParserCache()->getCacheStorage();
 }, '1.30' );
 
index 6ce1aed..6a2d0e2 100644 (file)
@@ -34,9 +34,6 @@ class SiteStats {
        /** @var bool */
        private static $loaded = false;
 
-       /** @var int */
-       private static $jobs;
-
        /** @var int[] */
        private static $pageCount = [];
 
@@ -213,24 +210,24 @@ class SiteStats {
        }
 
        /**
+        * Total number of jobs in the job queue.
         * @return int
         */
        static function jobs() {
-               if ( !isset( self::$jobs ) ) {
-                       try{
-                               self::$jobs = array_sum( JobQueueGroup::singleton()->getQueueSizes() );
-                       } catch ( JobQueueError $e ) {
-                               self::$jobs = 0;
-                       }
-                       /**
-                        * Zero rows still do single row read for row that doesn't exist,
-                        * but people are annoyed by that
-                        */
-                       if ( self::$jobs == 1 ) {
-                               self::$jobs = 0;
-                       }
-               }
-               return self::$jobs;
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+               return $cache->getWithSetCallback(
+                       $cache->makeKey( 'SiteStats', 'jobscount' ),
+                       $cache::TTL_MINUTE,
+                       function ( $oldValue, &$ttl, array &$setOpts ) {
+                               try{
+                                       $jobs = array_sum( JobQueueGroup::singleton()->getQueueSizes() );
+                               } catch ( JobQueueError $e ) {
+                                       $jobs = 0;
+                               }
+                               return $jobs;
+                       },
+                       [ 'pcTTL' => $cache::TTL_PROC_LONG ]
+               );
        }
 
        /**
@@ -296,7 +293,6 @@ class SiteStatsInit {
        private $mUsers = null, $mFiles = null;
 
        /**
-        * Constructor
         * @param bool|IDatabase $database
         * - boolean: Whether to use the master DB
         * - IDatabase: Database connection to use
index 083a725..edfdaca 100644 (file)
@@ -272,7 +272,7 @@ class Title implements LinkTarget {
                }
 
                try {
-                       return Title::newFromTextThrow( strval( $text ), $defaultNamespace );
+                       return self::newFromTextThrow( strval( $text ), $defaultNamespace );
                } catch ( MalformedTitleException $ex ) {
                        return null;
                }
@@ -411,7 +411,7 @@ class Title implements LinkTarget {
                        __METHOD__
                );
                if ( $row !== false ) {
-                       $title = Title::newFromRow( $row );
+                       $title = self::newFromRow( $row );
                } else {
                        $title = null;
                }
@@ -439,7 +439,7 @@ class Title implements LinkTarget {
 
                $titles = [];
                foreach ( $res as $row ) {
-                       $titles[] = Title::newFromRow( $row );
+                       $titles[] = self::newFromRow( $row );
                }
                return $titles;
        }
@@ -541,7 +541,7 @@ class Title implements LinkTarget {
                }
 
                $t = new Title();
-               $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
+               $t->mDbkeyform = self::makeName( $ns, $title, $fragment, $interwiki, true );
 
                try {
                        $t->secureAndSplit();
@@ -557,10 +557,10 @@ class Title implements LinkTarget {
         * @return Title The new object
         */
        public static function newMainPage() {
-               $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
+               $title = self::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
                // Don't give fatal errors if the message is broken
                if ( !$title ) {
-                       $title = Title::newFromText( 'Main Page' );
+                       $title = self::newFromText( 'Main Page' );
                }
                return $title;
        }
@@ -933,7 +933,7 @@ class Title implements LinkTarget {
         */
        public function getContentModel( $flags = 0 ) {
                if ( !$this->mForcedContentModel
-                       && ( !$this->mContentModel || $flags === Title::GAID_FOR_UPDATE )
+                       && ( !$this->mContentModel || $flags === self::GAID_FOR_UPDATE )
                        && $this->getArticleID( $flags )
                ) {
                        $linkCache = LinkCache::singleton();
@@ -1096,7 +1096,7 @@ class Title implements LinkTarget {
                        if ( $canonicalName ) {
                                $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
                                if ( $localName != $this->mDbkeyform ) {
-                                       return Title::makeTitle( NS_SPECIAL, $localName );
+                                       return self::makeTitle( NS_SPECIAL, $localName );
                                }
                        }
                }
@@ -1195,7 +1195,7 @@ class Title implements LinkTarget {
         * @return bool
         */
        public function isMainPage() {
-               return $this->equals( Title::newMainPage() );
+               return $this->equals( self::newMainPage() );
        }
 
        /**
@@ -1313,7 +1313,7 @@ class Title implements LinkTarget {
         * @return Title The object for the talk page
         */
        public function getTalkPage() {
-               return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
+               return self::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
        }
 
        /**
@@ -1328,7 +1328,7 @@ class Title implements LinkTarget {
                if ( $this->getNamespace() == $subjectNS ) {
                        return $this;
                }
-               return Title::makeTitle( $subjectNS, $this->getDBkey() );
+               return self::makeTitle( $subjectNS, $this->getDBkey() );
        }
 
        /**
@@ -1388,7 +1388,7 @@ class Title implements LinkTarget {
                if ( !$this->hasFragment() ) {
                        return '';
                } else {
-                       return '#' . Title::escapeFragmentForURL( $this->getFragment() );
+                       return '#' . self::escapeFragmentForURL( $this->getFragment() );
                }
        }
 
@@ -1535,7 +1535,7 @@ class Title implements LinkTarget {
         * @since 1.20
         */
        public function getRootTitle() {
-               return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
+               return self::makeTitle( $this->getNamespace(), $this->getRootText() );
        }
 
        /**
@@ -1575,7 +1575,7 @@ class Title implements LinkTarget {
         * @since 1.20
         */
        public function getBaseTitle() {
-               return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
+               return self::makeTitle( $this->getNamespace(), $this->getBaseText() );
        }
 
        /**
@@ -1611,7 +1611,7 @@ class Title implements LinkTarget {
         * @since 1.20
         */
        public function getSubpage( $text ) {
-               return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
+               return self::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
        }
 
        /**
@@ -2847,7 +2847,7 @@ class Title implements LinkTarget {
                                        $page_id = $row->pr_page;
                                        $page_ns = $row->page_namespace;
                                        $page_title = $row->page_title;
-                                       $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
+                                       $sources[$page_id] = self::makeTitle( $page_ns, $page_title );
                                        # Add groups needed for each restriction type if its not already there
                                        # Make sure this restriction type still exists
 
@@ -3172,7 +3172,7 @@ class Title implements LinkTarget {
                if ( $limit > -1 ) {
                        $options['LIMIT'] = $limit;
                }
-               $this->mSubpages = TitleArray::newFromResult(
+               return TitleArray::newFromResult(
                        $dbr->select( 'page',
                                [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
                                $conds,
@@ -3180,7 +3180,6 @@ class Title implements LinkTarget {
                                $options
                        )
                );
-               return $this->mSubpages;
        }
 
        /**
@@ -3329,7 +3328,7 @@ class Title implements LinkTarget {
         * @return int Int or 0 if the page doesn't exist
         */
        public function getLatestRevID( $flags = 0 ) {
-               if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
+               if ( !( $flags & self::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
                        return intval( $this->mLatestID );
                }
                if ( !$this->getArticleID( $flags ) ) {
@@ -3489,7 +3488,7 @@ class Title implements LinkTarget {
                if ( $res->numRows() ) {
                        $linkCache = LinkCache::singleton();
                        foreach ( $res as $row ) {
-                               $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
+                               $titleObj = self::makeTitle( $row->page_namespace, $row->page_title );
                                if ( $titleObj ) {
                                        $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
                                        $retVal[] = $titleObj;
@@ -3557,9 +3556,9 @@ class Title implements LinkTarget {
                $linkCache = LinkCache::singleton();
                foreach ( $res as $row ) {
                        if ( $row->page_id ) {
-                               $titleObj = Title::newFromRow( $row );
+                               $titleObj = self::newFromRow( $row );
                        } else {
-                               $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
+                               $titleObj = self::makeTitle( $row->$blNamespace, $row->$blTitle );
                                $linkCache->addBadLinkObj( $titleObj );
                        }
                        $retVal[] = $titleObj;
@@ -3615,7 +3614,7 @@ class Title implements LinkTarget {
 
                $retVal = [];
                foreach ( $res as $row ) {
-                       $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
+                       $retVal[] = self::makeTitle( $row->pl_namespace, $row->pl_title );
                }
                return $retVal;
        }
@@ -3827,7 +3826,7 @@ class Title implements LinkTarget {
                        }
                        # T16385: we need makeTitleSafe because the new page names may
                        # be longer than 255 characters.
-                       $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
+                       $newSubpage = self::makeTitleSafe( $newNs, $newPageName );
 
                        $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect, $changeTags );
                        if ( $success === true ) {
@@ -3989,7 +3988,7 @@ class Title implements LinkTarget {
                                        # Circular reference
                                        $stack[$parent] = [];
                                } else {
-                                       $nt = Title::newFromText( $parent );
+                                       $nt = self::newFromText( $parent );
                                        if ( $nt ) {
                                                $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
                                        }
index a03bc19..6a532e5 100644 (file)
@@ -115,7 +115,7 @@ class WikiMap {
         * @return string|int Wiki's name or $wiki_id if the wiki was not found
         */
        public static function getWikiName( $wikiID ) {
-               $wiki = WikiMap::getWiki( $wikiID );
+               $wiki = self::getWiki( $wikiID );
 
                if ( $wiki ) {
                        return $wiki->getDisplayName();
@@ -166,7 +166,7 @@ class WikiMap {
         * @return string|bool URL or false if the wiki was not found
         */
        public static function getForeignURL( $wikiID, $page, $fragmentId = null ) {
-               $wiki = WikiMap::getWiki( $wikiID );
+               $wiki = self::getWiki( $wikiID );
 
                if ( $wiki ) {
                        return $wiki->getFullUrl( $page, $fragmentId );
index d016433..16a5a9d 100644 (file)
@@ -225,7 +225,7 @@ class Xml {
                $selected = isset( $languages[$selected] ) ? $selected : $wgLanguageCode;
                $options = "\n";
                foreach ( $languages as $code => $name ) {
-                       $options .= Xml::option( "$code - $name", $code, $code == $selected ) . "\n";
+                       $options .= self::option( "$code - $name", $code, $code == $selected ) . "\n";
                }
 
                $attrs = [ 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' ];
@@ -235,8 +235,8 @@ class Xml {
                        $msg = wfMessage( 'yourlanguage' );
                }
                return [
-                       Xml::label( $msg->text(), $attrs['id'] ),
-                       Xml::tags( 'select', $attrs, $options )
+                       self::label( $msg->text(), $attrs['id'] ),
+                       self::tags( 'select', $attrs, $options )
                ];
        }
 
@@ -400,7 +400,7 @@ class Xml {
                $value = false, $attribs = []
        ) {
                return [
-                       Xml::label( $label, $id, $attribs ),
+                       self::label( $label, $id, $attribs ),
                        self::input( $name, $size, $value, [ 'id' => $id ] + $attribs )
                ];
        }
@@ -556,11 +556,11 @@ class Xml {
                        $attribs['tabindex'] = $tabindex;
                }
 
-               return Xml::openElement( 'select', $attribs )
+               return self::openElement( 'select', $attribs )
                        . "\n"
                        . $options
                        . "\n"
-                       . Xml::closeElement( 'select' );
+                       . self::closeElement( 'select' );
        }
 
        /**
@@ -575,15 +575,15 @@ class Xml {
         * @return string
         */
        public static function fieldset( $legend = false, $content = false, $attribs = [] ) {
-               $s = Xml::openElement( 'fieldset', $attribs ) . "\n";
+               $s = self::openElement( 'fieldset', $attribs ) . "\n";
 
                if ( $legend ) {
-                       $s .= Xml::element( 'legend', null, $legend ) . "\n";
+                       $s .= self::element( 'legend', null, $legend ) . "\n";
                }
 
                if ( $content !== false ) {
                        $s .= $content . "\n";
-                       $s .= Xml::closeElement( 'fieldset' ) . "\n";
+                       $s .= self::closeElement( 'fieldset' ) . "\n";
                }
 
                return $s;
@@ -644,7 +644,7 @@ class Xml {
         */
        public static function encodeJsCall( $name, $args, $pretty = false ) {
                foreach ( $args as &$arg ) {
-                       $arg = Xml::encodeJsVar( $arg, $pretty );
+                       $arg = self::encodeJsVar( $arg, $pretty );
                        if ( $arg === false ) {
                                return false;
                        }
@@ -702,7 +702,7 @@ class Xml {
                        $text .
                        '</html>';
 
-               return Xml::isWellFormed( $html );
+               return self::isWellFormed( $html );
        }
 
        /**
@@ -736,25 +736,25 @@ class Xml {
 
                foreach ( $fields as $labelmsg => $input ) {
                        $id = "mw-$labelmsg";
-                       $form .= Xml::openElement( 'tr', [ 'id' => $id ] );
+                       $form .= self::openElement( 'tr', [ 'id' => $id ] );
 
                        // TODO use a <label> here for accessibility purposes - will need
                        // to either not use a table to build the form, or find the ID of
                        // the input somehow.
 
-                       $form .= Xml::tags( 'td', [ 'class' => 'mw-label' ], wfMessage( $labelmsg )->parse() );
-                       $form .= Xml::openElement( 'td', [ 'class' => 'mw-input' ] )
-                               . $input . Xml::closeElement( 'td' );
-                       $form .= Xml::closeElement( 'tr' );
+                       $form .= self::tags( 'td', [ 'class' => 'mw-label' ], wfMessage( $labelmsg )->parse() );
+                       $form .= self::openElement( 'td', [ 'class' => 'mw-input' ] )
+                               . $input . self::closeElement( 'td' );
+                       $form .= self::closeElement( 'tr' );
                }
 
                if ( $submitLabel ) {
-                       $form .= Xml::openElement( 'tr' );
-                       $form .= Xml::tags( 'td', [], '' );
-                       $form .= Xml::openElement( 'td', [ 'class' => 'mw-submit' ] )
-                               . Xml::submitButton( wfMessage( $submitLabel )->text(), $submitAttribs )
-                               . Xml::closeElement( 'td' );
-                       $form .= Xml::closeElement( 'tr' );
+                       $form .= self::openElement( 'tr' );
+                       $form .= self::tags( 'td', [], '' );
+                       $form .= self::openElement( 'td', [ 'class' => 'mw-submit' ] )
+                               . self::submitButton( wfMessage( $submitLabel )->text(), $submitAttribs )
+                               . self::closeElement( 'td' );
+                       $form .= self::closeElement( 'tr' );
                }
 
                $form .= "</tbody></table>";
@@ -770,10 +770,10 @@ class Xml {
         * @return string
         */
        public static function buildTable( $rows, $attribs = [], $headers = null ) {
-               $s = Xml::openElement( 'table', $attribs );
+               $s = self::openElement( 'table', $attribs );
 
                if ( is_array( $headers ) ) {
-                       $s .= Xml::openElement( 'thead', $attribs );
+                       $s .= self::openElement( 'thead', $attribs );
 
                        foreach ( $headers as $id => $header ) {
                                $attribs = [];
@@ -782,9 +782,9 @@ class Xml {
                                        $attribs['id'] = $id;
                                }
 
-                               $s .= Xml::element( 'th', $attribs, $header );
+                               $s .= self::element( 'th', $attribs, $header );
                        }
-                       $s .= Xml::closeElement( 'thead' );
+                       $s .= self::closeElement( 'thead' );
                }
 
                foreach ( $rows as $id => $row ) {
@@ -794,10 +794,10 @@ class Xml {
                                $attribs['id'] = $id;
                        }
 
-                       $s .= Xml::buildTableRow( $attribs, $row );
+                       $s .= self::buildTableRow( $attribs, $row );
                }
 
-               $s .= Xml::closeElement( 'table' );
+               $s .= self::closeElement( 'table' );
 
                return $s;
        }
@@ -809,7 +809,7 @@ class Xml {
         * @return string
         */
        public static function buildTableRow( $attribs, $cells ) {
-               $s = Xml::openElement( 'tr', $attribs );
+               $s = self::openElement( 'tr', $attribs );
 
                foreach ( $cells as $id => $cell ) {
                        $attribs = [];
@@ -818,10 +818,10 @@ class Xml {
                                $attribs['id'] = $id;
                        }
 
-                       $s .= Xml::element( 'td', $attribs, $cell );
+                       $s .= self::element( 'td', $attribs, $cell );
                }
 
-               $s .= Xml::closeElement( 'tr' );
+               $s .= self::closeElement( 'tr' );
 
                return $s;
        }
index 88382b6..e8d9a3e 100644 (file)
@@ -151,7 +151,7 @@ abstract class Action implements MessageLocalizer {
                        return 'view';
                }
 
-               $action = Action::factory( $actionName, $context->getWikiPage(), $context );
+               $action = self::factory( $actionName, $context->getWikiPage(), $context );
                if ( $action instanceof Action ) {
                        return $action->getName();
                }
@@ -388,7 +388,7 @@ abstract class Action implements MessageLocalizer {
        public function addHelpLink( $to, $overrideBaseUrl = false ) {
                global $wgContLang;
                $msg = wfMessage( $wgContLang->lc(
-                       Action::getActionName( $this->getContext() )
+                       self::getActionName( $this->getContext() )
                        ) . '-helppage' );
 
                if ( !$msg->isDisabled() ) {
index bc3def8..034d243 100644 (file)
@@ -548,7 +548,7 @@ abstract class ApiBase extends ContextSource {
                // Main module has this method overridden
                // Safety - avoid infinite loop:
                if ( $this->isMain() ) {
-                       ApiBase::dieDebug( __METHOD__, 'base method was called on main module.' );
+                       self::dieDebug( __METHOD__, 'base method was called on main module.' );
                }
 
                return $this->getMain()->lacksSameOriginSecurity();
@@ -620,7 +620,7 @@ abstract class ApiBase extends ContextSource {
                // Main module has getResult() method overridden
                // Safety - avoid infinite loop:
                if ( $this->isMain() ) {
-                       ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
+                       self::dieDebug( __METHOD__, 'base method was called on main module. ' );
                }
 
                return $this->getMain()->getResult();
@@ -634,7 +634,7 @@ abstract class ApiBase extends ContextSource {
                // Main module has getErrorFormatter() method overridden
                // Safety - avoid infinite loop:
                if ( $this->isMain() ) {
-                       ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
+                       self::dieDebug( __METHOD__, 'base method was called on main module. ' );
                }
 
                return $this->getMain()->getErrorFormatter();
@@ -660,7 +660,7 @@ abstract class ApiBase extends ContextSource {
                // Main module has getContinuationManager() method overridden
                // Safety - avoid infinite loop:
                if ( $this->isMain() ) {
-                       ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
+                       self::dieDebug( __METHOD__, 'base method was called on main module. ' );
                }
 
                return $this->getMain()->getContinuationManager();
@@ -674,7 +674,7 @@ abstract class ApiBase extends ContextSource {
                // Main module has setContinuationManager() method overridden
                // Safety - avoid infinite loop:
                if ( $this->isMain() ) {
-                       ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
+                       self::dieDebug( __METHOD__, 'base method was called on main module. ' );
                }
 
                $this->getMain()->setContinuationManager( $manager );
@@ -1059,7 +1059,7 @@ abstract class ApiBase extends ContextSource {
                if ( $type == 'boolean' ) {
                        if ( isset( $default ) && $default !== false ) {
                                // Having a default value of anything other than 'false' is not allowed
-                               ApiBase::dieDebug(
+                               self::dieDebug(
                                        __METHOD__,
                                        "Boolean param $encParamName's default is set to '$default'. " .
                                                'Boolean parameters must default to false.'
@@ -1070,13 +1070,13 @@ abstract class ApiBase extends ContextSource {
                } elseif ( $type == 'upload' ) {
                        if ( isset( $default ) ) {
                                // Having a default value is not allowed
-                               ApiBase::dieDebug(
+                               self::dieDebug(
                                        __METHOD__,
                                        "File upload param $encParamName's default is set to " .
                                                "'$default'. File upload parameters may not have a default." );
                        }
                        if ( $multi ) {
-                               ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
+                               self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
                        }
                        $value = $this->getMain()->getUpload( $encParamName );
                        if ( !$value->exists() ) {
@@ -1138,7 +1138,7 @@ abstract class ApiBase extends ContextSource {
 
                $allSpecifier = ( is_string( $allowAll ) ? $allowAll : self::ALL_DEFAULT_STRING );
                if ( $allowAll && $multi && is_array( $type ) && in_array( $allSpecifier, $type, true ) ) {
-                       ApiBase::dieDebug(
+                       self::dieDebug(
                                __METHOD__,
                                "For param $encParamName, PARAM_ALL collides with a possible value" );
                }
@@ -1194,13 +1194,13 @@ abstract class ApiBase extends ContextSource {
                                                if ( !isset( $paramSettings[self::PARAM_MAX] )
                                                        || !isset( $paramSettings[self::PARAM_MAX2] )
                                                ) {
-                                                       ApiBase::dieDebug(
+                                                       self::dieDebug(
                                                                __METHOD__,
                                                                "MAX1 or MAX2 are not defined for the limit $encParamName"
                                                        );
                                                }
                                                if ( $multi ) {
-                                                       ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
+                                                       self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
                                                }
                                                $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
                                                if ( $value == 'max' ) {
@@ -1221,7 +1221,7 @@ abstract class ApiBase extends ContextSource {
                                                break;
                                        case 'boolean':
                                                if ( $multi ) {
-                                                       ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
+                                                       self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
                                                }
                                                break;
                                        case 'timestamp':
@@ -1255,7 +1255,7 @@ abstract class ApiBase extends ContextSource {
                                                }
                                                break;
                                        default:
-                                               ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
+                                               self::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
                                }
                        }
 
@@ -2077,19 +2077,19 @@ abstract class ApiBase extends ContextSource {
         * @return Message
         */
        public function getFinalSummary() {
-               $msg = ApiBase::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
+               $msg = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
                        $this->getModulePrefix(),
                        $this->getModuleName(),
                        $this->getModulePath(),
                ] );
                if ( !$msg->exists() ) {
                        wfDeprecated( 'API help "description" messages', '1.30' );
-                       $msg = ApiBase::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [
+                       $msg = self::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [
                                $this->getModulePrefix(),
                                $this->getModuleName(),
                                $this->getModulePath(),
                        ] );
-                       $msg = ApiBase::makeMessage( 'rawmessage', $this->getContext(), [
+                       $msg = self::makeMessage( 'rawmessage', $this->getContext(), [
                                preg_replace( '/\n.*/s', '', $msg->text() )
                        ] );
                }
@@ -2116,12 +2116,12 @@ abstract class ApiBase extends ContextSource {
                        $desc = (string)$desc;
                }
 
-               $summary = ApiBase::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
+               $summary = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
                        $this->getModulePrefix(),
                        $this->getModuleName(),
                        $this->getModulePath(),
                ] );
-               $extendedDescription = ApiBase::makeMessage(
+               $extendedDescription = self::makeMessage(
                        $this->getExtendedDescription(), $this->getContext(), [
                                $this->getModulePrefix(),
                                $this->getModuleName(),
@@ -2133,7 +2133,7 @@ abstract class ApiBase extends ContextSource {
                        $msgs = [ $summary, $extendedDescription ];
                } else {
                        wfDeprecated( 'API help "description" messages', '1.30' );
-                       $description = ApiBase::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [
+                       $description = self::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [
                                $this->getModulePrefix(),
                                $this->getModuleName(),
                                $this->getModulePath(),
@@ -2165,10 +2165,10 @@ abstract class ApiBase extends ContextSource {
 
                if ( $this->needsToken() ) {
                        $params['token'] = [
-                               ApiBase::PARAM_TYPE => 'string',
-                               ApiBase::PARAM_REQUIRED => true,
-                               ApiBase::PARAM_SENSITIVE => true,
-                               ApiBase::PARAM_HELP_MSG => [
+                               self::PARAM_TYPE => 'string',
+                               self::PARAM_REQUIRED => true,
+                               self::PARAM_SENSITIVE => true,
+                               self::PARAM_HELP_MSG => [
                                        'api-help-param-token',
                                        $this->needsToken(),
                                ],
@@ -2205,7 +2205,7 @@ abstract class ApiBase extends ContextSource {
                }
                $desc = self::escapeWikiText( $desc );
 
-               $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
+               $params = $this->getFinalParams( self::GET_VALUES_FOR_HELP );
                $msgs = [];
                foreach ( $params as $param => $settings ) {
                        if ( !is_array( $settings ) ) {
@@ -2224,15 +2224,15 @@ abstract class ApiBase extends ContextSource {
                                $d = implode( ' ', $d );
                        }
 
-                       if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) {
-                               $msg = $settings[ApiBase::PARAM_HELP_MSG];
+                       if ( isset( $settings[self::PARAM_HELP_MSG] ) ) {
+                               $msg = $settings[self::PARAM_HELP_MSG];
                        } else {
                                $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
                                if ( !$msg->exists() ) {
                                        $msg = $this->msg( 'api-help-fallback-parameter', $d );
                                }
                        }
-                       $msg = ApiBase::makeMessage( $msg, $this->getContext(),
+                       $msg = self::makeMessage( $msg, $this->getContext(),
                                [ $prefix, $param, $name, $path ] );
                        if ( !$msg ) {
                                self::dieDebug( __METHOD__,
@@ -2240,11 +2240,11 @@ abstract class ApiBase extends ContextSource {
                        }
                        $msgs[$param] = [ $msg ];
 
-                       if ( isset( $settings[ApiBase::PARAM_TYPE] ) &&
-                               $settings[ApiBase::PARAM_TYPE] === 'submodule'
+                       if ( isset( $settings[self::PARAM_TYPE] ) &&
+                               $settings[self::PARAM_TYPE] === 'submodule'
                        ) {
-                               if ( isset( $settings[ApiBase::PARAM_SUBMODULE_MAP] ) ) {
-                                       $map = $settings[ApiBase::PARAM_SUBMODULE_MAP];
+                               if ( isset( $settings[self::PARAM_SUBMODULE_MAP] ) ) {
+                                       $map = $settings[self::PARAM_SUBMODULE_MAP];
                                } else {
                                        $prefix = $this->isMain() ? '' : ( $this->getModulePath() . '+' );
                                        $map = [];
@@ -2282,29 +2282,29 @@ abstract class ApiBase extends ContextSource {
                                        $arr[] = $m->setContext( $this->getContext() );
                                }
                                $msgs[$param] = array_merge( $msgs[$param], $submodules, $deprecatedSubmodules );
-                       } elseif ( isset( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
-                               if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
+                       } elseif ( isset( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
+                               if ( !is_array( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
                                        self::dieDebug( __METHOD__,
                                                'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
                                }
-                               if ( !is_array( $settings[ApiBase::PARAM_TYPE] ) ) {
+                               if ( !is_array( $settings[self::PARAM_TYPE] ) ) {
                                        self::dieDebug( __METHOD__,
                                                'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
                                                'ApiBase::PARAM_TYPE is an array' );
                                }
 
-                               $valueMsgs = $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE];
-                               $deprecatedValues = isset( $settings[ApiBase::PARAM_DEPRECATED_VALUES] )
-                                       ? $settings[ApiBase::PARAM_DEPRECATED_VALUES]
+                               $valueMsgs = $settings[self::PARAM_HELP_MSG_PER_VALUE];
+                               $deprecatedValues = isset( $settings[self::PARAM_DEPRECATED_VALUES] )
+                                       ? $settings[self::PARAM_DEPRECATED_VALUES]
                                        : [];
 
-                               foreach ( $settings[ApiBase::PARAM_TYPE] as $value ) {
+                               foreach ( $settings[self::PARAM_TYPE] as $value ) {
                                        if ( isset( $valueMsgs[$value] ) ) {
                                                $msg = $valueMsgs[$value];
                                        } else {
                                                $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
                                        }
-                                       $m = ApiBase::makeMessage( $msg, $this->getContext(),
+                                       $m = self::makeMessage( $msg, $this->getContext(),
                                                [ $prefix, $param, $name, $path, $value ] );
                                        if ( $m ) {
                                                $m = new ApiHelpParamValueMessage(
@@ -2321,13 +2321,13 @@ abstract class ApiBase extends ContextSource {
                                }
                        }
 
-                       if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
-                               if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
+                       if ( isset( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
+                               if ( !is_array( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
                                        self::dieDebug( __METHOD__,
                                                'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
                                }
-                               foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $m ) {
-                                       $m = ApiBase::makeMessage( $m, $this->getContext(),
+                               foreach ( $settings[self::PARAM_HELP_MSG_APPEND] as $m ) {
+                                       $m = self::makeMessage( $m, $this->getContext(),
                                                [ $prefix, $param, $name, $path ] );
                                        if ( $m ) {
                                                $msgs[$param][] = $m;
@@ -2614,6 +2614,7 @@ abstract class ApiBase extends ContextSource {
         * @param string $warning Warning message
         */
        public function setWarning( $warning ) {
+               wfDeprecated( __METHOD__, '1.29' );
                $msg = new ApiRawMessage( $warning, 'warning' );
                $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg );
        }
@@ -2632,6 +2633,7 @@ abstract class ApiBase extends ContextSource {
         * @throws ApiUsageException always
         */
        public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
+               wfDeprecated( __METHOD__, '1.29' );
                $this->dieWithError(
                        new RawMessage( '$1', [ $description ] ),
                        $errorCode,
@@ -2651,6 +2653,7 @@ abstract class ApiBase extends ContextSource {
         * @throws MWException
         */
        public function getErrorFromStatus( $status, &$extraData = null ) {
+               wfDeprecated( __METHOD__, '1.29' );
                if ( $status->isGood() ) {
                        throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
                }
@@ -2860,6 +2863,7 @@ abstract class ApiBase extends ContextSource {
         * @return [ 'code' => code, 'info' => info ]
         */
        public function parseMsg( $error ) {
+               wfDeprecated( __METHOD__, '1.29' );
                // Check whether someone passed the whole array, instead of one element as
                // documented. This breaks if it's actually an array of fallback keys, but
                // that's long-standing misbehavior introduced in r87627 to incorrectly
@@ -2889,6 +2893,7 @@ abstract class ApiBase extends ContextSource {
         * @throws ApiUsageException always
         */
        public function dieUsageMsg( $error ) {
+               wfDeprecated( __METHOD__, '1.29' );
                $this->dieWithError( $this->parseMsgInternal( $error ) );
        }
 
@@ -2901,6 +2906,7 @@ abstract class ApiBase extends ContextSource {
         * @since 1.21
         */
        public function dieUsageMsgOrDebug( $error ) {
+               wfDeprecated( __METHOD__, '1.29' );
                $this->dieWithErrorOrDebug( $this->parseMsgInternal( $error ) );
        }
 
index 72c7c35..4b4b76b 100644 (file)
@@ -71,7 +71,7 @@ class ApiEmailUser extends ApiBase {
                }
 
                $result = array_filter( [
-                       'result' => $retval->isGood() ? 'Success' : $retval->isOk() ? 'Warnings' : 'Failure',
+                       'result' => $retval->isGood() ? 'Success' : ( $retval->isOk() ? 'Warnings' : 'Failure' ),
                        'warnings' => $this->getErrorFormatter()->arrayFromStatus( $retval, 'warning' ),
                        'errors' => $this->getErrorFormatter()->arrayFromStatus( $retval, 'error' ),
                ] );
index 52f79ee..b7d4529 100644 (file)
@@ -1041,7 +1041,7 @@ class ApiMain extends ApiBase {
                        // None of the rest have any messages for non-error types
                } elseif ( $e instanceof UsageException ) {
                        // User entered incorrect parameters - generate error response
-                       $data = $e->getMessageArray();
+                       $data = MediaWiki\quietCall( [ $e, 'getMessageArray' ] );
                        $code = $data['code'];
                        $info = $data['info'];
                        unset( $data['code'], $data['info'] );
@@ -1829,7 +1829,7 @@ class ApiMain extends ApiBase {
                                ApiBase::PARAM_TYPE => 'submodule',
                        ],
                        'format' => [
-                               ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT,
+                               ApiBase::PARAM_DFLT => self::API_DEFAULT_FORMAT,
                                ApiBase::PARAM_TYPE => 'submodule',
                        ],
                        'maxlag' => [
index 599b3de..cfac761 100644 (file)
@@ -121,7 +121,7 @@ class ApiPageSet extends ApiBase {
        public function __construct( ApiBase $dbSource, $flags = 0, $defaultNamespace = NS_MAIN ) {
                parent::__construct( $dbSource->getMain(), $dbSource->getModuleName() );
                $this->mDbSource = $dbSource;
-               $this->mAllowGenerator = ( $flags & ApiPageSet::DISABLE_GENERATORS ) == 0;
+               $this->mAllowGenerator = ( $flags & self::DISABLE_GENERATORS ) == 0;
                $this->mDefaultNamespace = $defaultNamespace;
 
                $this->mParams = $this->extractRequestParams();
@@ -166,7 +166,7 @@ class ApiPageSet extends ApiBase {
                        }
                        // Create a temporary pageset to store generator's output,
                        // add any additional fields generator may need, and execute pageset to populate titles/pageids
-                       $tmpPageSet = new ApiPageSet( $dbSource, ApiPageSet::DISABLE_GENERATORS );
+                       $tmpPageSet = new ApiPageSet( $dbSource, self::DISABLE_GENERATORS );
                        $generator->setGeneratorMode( $tmpPageSet );
                        $this->mCacheMode = $generator->getCacheMode( $generator->extractRequestParams() );
 
index b2664df..bfd5b17 100644 (file)
@@ -677,7 +677,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                ApiBase::PARAM_DFLT => -1,
                                ApiBase::PARAM_HELP_MSG => [
                                        'apihelp-query+imageinfo-param-urlwidth',
-                                       ApiQueryImageInfo::TRANSFORM_LIMIT,
+                                       self::TRANSFORM_LIMIT,
                                ],
                        ],
                        'urlheight' => [
index 6b8f98c..ecdebd4 100644 (file)
@@ -128,7 +128,7 @@ class ApiQueryInfo extends ApiQueryBase {
         * @deprecated since 1.24
         */
        public static function resetTokenCache() {
-               ApiQueryInfo::$cachedTokens = [];
+               self::$cachedTokens = [];
        }
 
        /**
@@ -144,11 +144,11 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                // The token is always the same, let's exploit that
-               if ( !isset( ApiQueryInfo::$cachedTokens['edit'] ) ) {
-                       ApiQueryInfo::$cachedTokens['edit'] = $wgUser->getEditToken();
+               if ( !isset( self::$cachedTokens['edit'] ) ) {
+                       self::$cachedTokens['edit'] = $wgUser->getEditToken();
                }
 
-               return ApiQueryInfo::$cachedTokens['edit'];
+               return self::$cachedTokens['edit'];
        }
 
        /**
@@ -161,11 +161,11 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                // The token is always the same, let's exploit that
-               if ( !isset( ApiQueryInfo::$cachedTokens['delete'] ) ) {
-                       ApiQueryInfo::$cachedTokens['delete'] = $wgUser->getEditToken();
+               if ( !isset( self::$cachedTokens['delete'] ) ) {
+                       self::$cachedTokens['delete'] = $wgUser->getEditToken();
                }
 
-               return ApiQueryInfo::$cachedTokens['delete'];
+               return self::$cachedTokens['delete'];
        }
 
        /**
@@ -178,11 +178,11 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                // The token is always the same, let's exploit that
-               if ( !isset( ApiQueryInfo::$cachedTokens['protect'] ) ) {
-                       ApiQueryInfo::$cachedTokens['protect'] = $wgUser->getEditToken();
+               if ( !isset( self::$cachedTokens['protect'] ) ) {
+                       self::$cachedTokens['protect'] = $wgUser->getEditToken();
                }
 
-               return ApiQueryInfo::$cachedTokens['protect'];
+               return self::$cachedTokens['protect'];
        }
 
        /**
@@ -195,11 +195,11 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                // The token is always the same, let's exploit that
-               if ( !isset( ApiQueryInfo::$cachedTokens['move'] ) ) {
-                       ApiQueryInfo::$cachedTokens['move'] = $wgUser->getEditToken();
+               if ( !isset( self::$cachedTokens['move'] ) ) {
+                       self::$cachedTokens['move'] = $wgUser->getEditToken();
                }
 
-               return ApiQueryInfo::$cachedTokens['move'];
+               return self::$cachedTokens['move'];
        }
 
        /**
@@ -212,11 +212,11 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                // The token is always the same, let's exploit that
-               if ( !isset( ApiQueryInfo::$cachedTokens['block'] ) ) {
-                       ApiQueryInfo::$cachedTokens['block'] = $wgUser->getEditToken();
+               if ( !isset( self::$cachedTokens['block'] ) ) {
+                       self::$cachedTokens['block'] = $wgUser->getEditToken();
                }
 
-               return ApiQueryInfo::$cachedTokens['block'];
+               return self::$cachedTokens['block'];
        }
 
        /**
@@ -237,11 +237,11 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                // The token is always the same, let's exploit that
-               if ( !isset( ApiQueryInfo::$cachedTokens['email'] ) ) {
-                       ApiQueryInfo::$cachedTokens['email'] = $wgUser->getEditToken();
+               if ( !isset( self::$cachedTokens['email'] ) ) {
+                       self::$cachedTokens['email'] = $wgUser->getEditToken();
                }
 
-               return ApiQueryInfo::$cachedTokens['email'];
+               return self::$cachedTokens['email'];
        }
 
        /**
@@ -254,11 +254,11 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                // The token is always the same, let's exploit that
-               if ( !isset( ApiQueryInfo::$cachedTokens['import'] ) ) {
-                       ApiQueryInfo::$cachedTokens['import'] = $wgUser->getEditToken();
+               if ( !isset( self::$cachedTokens['import'] ) ) {
+                       self::$cachedTokens['import'] = $wgUser->getEditToken();
                }
 
-               return ApiQueryInfo::$cachedTokens['import'];
+               return self::$cachedTokens['import'];
        }
 
        /**
@@ -271,11 +271,11 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                // The token is always the same, let's exploit that
-               if ( !isset( ApiQueryInfo::$cachedTokens['watch'] ) ) {
-                       ApiQueryInfo::$cachedTokens['watch'] = $wgUser->getEditToken( 'watch' );
+               if ( !isset( self::$cachedTokens['watch'] ) ) {
+                       self::$cachedTokens['watch'] = $wgUser->getEditToken( 'watch' );
                }
 
-               return ApiQueryInfo::$cachedTokens['watch'];
+               return self::$cachedTokens['watch'];
        }
 
        /**
@@ -288,11 +288,11 @@ class ApiQueryInfo extends ApiQueryBase {
                }
 
                // The token is always the same, let's exploit that
-               if ( !isset( ApiQueryInfo::$cachedTokens['options'] ) ) {
-                       ApiQueryInfo::$cachedTokens['options'] = $wgUser->getEditToken();
+               if ( !isset( self::$cachedTokens['options'] ) ) {
+                       self::$cachedTokens['options'] = $wgUser->getEditToken();
                }
 
-               return ApiQueryInfo::$cachedTokens['options'];
+               return self::$cachedTokens['options'];
        }
 
        public function execute() {
index 6734740..468d878 100644 (file)
@@ -287,12 +287,12 @@ class ApiResult implements ApiSerializable {
         * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
         */
        public static function setValue( array &$arr, $name, $value, $flags = 0 ) {
-               if ( ( $flags & ApiResult::NO_VALIDATE ) !== ApiResult::NO_VALIDATE ) {
+               if ( ( $flags & self::NO_VALIDATE ) !== self::NO_VALIDATE ) {
                        $value = self::validateValue( $value );
                }
 
                if ( $name === null ) {
-                       if ( $flags & ApiResult::ADD_ON_TOP ) {
+                       if ( $flags & self::ADD_ON_TOP ) {
                                array_unshift( $arr, $value );
                        } else {
                                array_push( $arr, $value );
@@ -301,8 +301,8 @@ class ApiResult implements ApiSerializable {
                }
 
                $exists = isset( $arr[$name] );
-               if ( !$exists || ( $flags & ApiResult::OVERRIDE ) ) {
-                       if ( !$exists && ( $flags & ApiResult::ADD_ON_TOP ) ) {
+               if ( !$exists || ( $flags & self::OVERRIDE ) ) {
+                       if ( !$exists && ( $flags & self::ADD_ON_TOP ) ) {
                                $arr = [ $name => $value ] + $arr;
                        } else {
                                $arr[$name] = $value;
@@ -403,13 +403,13 @@ class ApiResult implements ApiSerializable {
         * @since 1.21 int $flags replaced boolean $override
         */
        public function addValue( $path, $name, $value, $flags = 0 ) {
-               $arr = &$this->path( $path, ( $flags & ApiResult::ADD_ON_TOP ) ? 'prepend' : 'append' );
+               $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
 
-               if ( $this->checkingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
+               if ( $this->checkingSize && !( $flags & self::NO_SIZE_CHECK ) ) {
                        // self::size needs the validated value. Then flag
                        // to not re-validate later.
                        $value = self::validateValue( $value );
-                       $flags |= ApiResult::NO_VALIDATE;
+                       $flags |= self::NO_VALIDATE;
 
                        $newsize = $this->size + self::size( $value );
                        if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
@@ -459,7 +459,7 @@ class ApiResult implements ApiSerializable {
                        $name = array_pop( $path );
                }
                $ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
-               if ( $this->checkingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
+               if ( $this->checkingSize && !( $flags & self::NO_SIZE_CHECK ) ) {
                        $newsize = $this->size - self::size( $ret );
                        $this->size = max( $newsize, 0 );
                }
@@ -511,7 +511,7 @@ class ApiResult implements ApiSerializable {
        public function addParsedLimit( $moduleName, $limit ) {
                // Add value, allowing overwriting
                $this->addValue( 'limits', $moduleName, $limit,
-                       ApiResult::OVERRIDE | ApiResult::NO_SIZE_CHECK );
+                       self::OVERRIDE | self::NO_SIZE_CHECK );
        }
 
        /**@}*/
@@ -551,7 +551,7 @@ class ApiResult implements ApiSerializable {
         * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
         */
        public function addContentField( $path, $name, $flags = 0 ) {
-               $arr = &$this->path( $path, ( $flags & ApiResult::ADD_ON_TOP ) ? 'prepend' : 'append' );
+               $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
                self::setContentField( $arr, $name, $flags );
        }
 
@@ -1156,7 +1156,7 @@ class ApiResult implements ApiSerializable {
                $bools = [];
                foreach ( $vars as $k => $v ) {
                        if ( is_array( $v ) || is_object( $v ) ) {
-                               $vars[$k] = ApiResult::addMetadataToResultVars( (array)$v, is_object( $v ) );
+                               $vars[$k] = self::addMetadataToResultVars( (array)$v, is_object( $v ) );
                        } elseif ( is_bool( $v ) ) {
                                // Better here to use real bools even in BC formats
                                $bools[] = $k;
@@ -1176,22 +1176,22 @@ class ApiResult implements ApiSerializable {
                        // Get the list of keys we actually care about. Unfortunately, we can't support
                        // certain keys that conflict with ApiResult metadata.
                        $keys = array_diff( array_keys( $vars ), [
-                               ApiResult::META_TYPE, ApiResult::META_PRESERVE_KEYS, ApiResult::META_KVP_KEY_NAME,
-                               ApiResult::META_INDEXED_TAG_NAME, ApiResult::META_BC_BOOLS
+                               self::META_TYPE, self::META_PRESERVE_KEYS, self::META_KVP_KEY_NAME,
+                               self::META_INDEXED_TAG_NAME, self::META_BC_BOOLS
                        ] );
 
                        return [
-                               ApiResult::META_TYPE => 'kvp',
-                               ApiResult::META_KVP_KEY_NAME => 'key',
-                               ApiResult::META_PRESERVE_KEYS => $keys,
-                               ApiResult::META_BC_BOOLS => $bools,
-                               ApiResult::META_INDEXED_TAG_NAME => 'var',
+                               self::META_TYPE => 'kvp',
+                               self::META_KVP_KEY_NAME => 'key',
+                               self::META_PRESERVE_KEYS => $keys,
+                               self::META_BC_BOOLS => $bools,
+                               self::META_INDEXED_TAG_NAME => 'var',
                        ] + $vars;
                } else {
                        return [
-                               ApiResult::META_TYPE => 'array',
-                               ApiResult::META_BC_BOOLS => $bools,
-                               ApiResult::META_INDEXED_TAG_NAME => 'value',
+                               self::META_TYPE => 'array',
+                               self::META_BC_BOOLS => $bools,
+                               self::META_INDEXED_TAG_NAME => 'value',
                        ] + $vars;
                }
        }
index 9dc1f92..fb49e2d 100644 (file)
@@ -45,6 +45,10 @@ class UsageException extends MWException {
                $this->mCodestr = $codestr;
                $this->mExtraData = $extradata;
 
+               if ( !$this instanceof ApiUsageException ) {
+                       wfDeprecated( __METHOD__, '1.29' );
+               }
+
                // This should never happen, so throw an exception about it that will
                // hopefully get logged with a backtrace (T138585)
                if ( !is_string( $codestr ) || $codestr === '' ) {
@@ -58,6 +62,7 @@ class UsageException extends MWException {
         * @return string
         */
        public function getCodeString() {
+               wfDeprecated( __METHOD__, '1.29' );
                return $this->mCodestr;
        }
 
@@ -65,6 +70,7 @@ class UsageException extends MWException {
         * @return array
         */
        public function getMessageArray() {
+               wfDeprecated( __METHOD__, '1.29' );
                $result = [
                        'code' => $this->mCodestr,
                        'info' => $this->getMessage()
@@ -183,6 +189,7 @@ class ApiUsageException extends UsageException implements ILocalizedException {
         * @inheritdoc
         */
        public function getCodeString() {
+               wfDeprecated( __METHOD__, '1.29' );
                return $this->getApiMessage()->getApiCode();
        }
 
@@ -192,6 +199,7 @@ class ApiUsageException extends UsageException implements ILocalizedException {
         * @inheritdoc
         */
        public function getMessageArray() {
+               wfDeprecated( __METHOD__, '1.29' );
                $enMsg = clone $this->getApiMessage();
                $enMsg->inLanguage( 'en' )->useDatabase( false );
 
index 6fea718..8918620 100644 (file)
        "apihelp-query+langlinks-param-lang": "להחזיר רק קישורי שפה עם קוד השפה הזה.",
        "apihelp-query+langlinks-param-title": "קישור לחיפוש. חובה להשתמש עם <var>$1lang</var>.",
        "apihelp-query+langlinks-param-dir": "באיזה כיוון לרשום.",
-       "apihelp-query+langlinks-param-inlanguagecode": "ק×\95×\93 ×©×¤×\94 ×©שמות שפות מתורגמות.",
+       "apihelp-query+langlinks-param-inlanguagecode": "ק×\95×\93 ×©×¤×\94 ×\91ש×\91×\99×\9c שמות שפות מתורגמות.",
        "apihelp-query+langlinks-example-simple": "קבלת קישורים בין־לשוניים מהדף <kbd>Main Page</kbd>.",
        "apihelp-query+links-summary": "החזרת כל הקישורים מהדפים שצוינו.",
        "apihelp-query+links-param-namespace": "להציג קישורים רק במרחבי השם האלה.",
        "apihelp-query+siteinfo-param-filteriw": "החזרה רק של עיולים מקומיים או רק של עיולים לא מקומיים ממפת הבינוויקי.",
        "apihelp-query+siteinfo-param-showalldb": "רשימת כל שרתי מסד הנתונים, לא רק אלה שהכי מתעכבים.",
        "apihelp-query+siteinfo-param-numberingroup": "רשימת מספרי משתמשים בקבוצות משתמשים.",
-       "apihelp-query+siteinfo-param-inlanguagecode": "ק×\95×\93 ×©×¤×\94 ×©שמות שפות מתורגמות (מאמץ טוב ביותר) ושמות עיצובים.",
+       "apihelp-query+siteinfo-param-inlanguagecode": "ק×\95×\93 ×©×¤×\94 ×\91ש×\91×\99×\9c שמות שפות מתורגמות (מאמץ טוב ביותר) ושמות עיצובים.",
        "apihelp-query+siteinfo-example-simple": "איזור מידע על האתר.",
        "apihelp-query+siteinfo-example-interwiki": "אחזור תחיליות בינוויקי מקומיות.",
        "apihelp-query+siteinfo-example-replag": "בדיקת שיהוי השכפול הנוכחי.",
index 36b580e..391c06c 100644 (file)
        "apihelp-setpagelanguage-param-lang": "문서를 변경할 언어의 언어 코드입니다. 문서를 위키의 기본 콘텐츠 언어로 재설정하려면 <kbd>default</kbd>를 사용하십시오.",
        "apihelp-setpagelanguage-param-reason": "변경 이유.",
        "apihelp-setpagelanguage-example-language": "<kbd>Main Page</kbd>의 언어를 바스크어로 변경합니다.",
+       "apihelp-stashedit-summary": "공유된 캐시에서 편집을 준비합니다.",
        "apihelp-stashedit-param-sectiontitle": "새 문단을 위한 제목.",
        "apihelp-stashedit-param-text": "문서 내용.",
        "apihelp-stashedit-param-contentmodel": "새 콘텐츠의 콘텐츠 모델.",
index 1dfeb34..a311728 100644 (file)
@@ -11,7 +11,8 @@
                        "Winstonyin",
                        "Arthur2e5",
                        "烈羽",
-                       "Corainn"
+                       "Corainn",
+                       "A2093064"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|說明文件]]\n* [[mw: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>本頁所展示的所有功能都應正常工作,但是API仍在開發當中,將會隨時變化。請訂閱[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 郵件清單]以便得到更新通知。\n\n<strong>錯誤的請求:</strong>當API收到錯誤的請求時,會發出以「MediaWiki-API-Error」為鍵的HTTP頭欄位,隨後頭欄位的值與錯誤碼將會被設為相同的值。詳細資訊請參閱[[mw:API:Errors_and_warnings|API: 錯誤與警告]]。\n\n<strong>測試:</strong>要簡化API請求的測試過程,請見[[Special:ApiSandbox]]。",
@@ -57,7 +58,7 @@
        "apihelp-createaccount-summary": "建立新使用者帳號。",
        "apihelp-createaccount-param-name": "使用者名稱。",
        "apihelp-createaccount-param-password": "密碼 (若有設定 <var>$1mailpassword</var> 則可略過)。",
-       "apihelp-createaccount-param-domain": "å¤\96é\83¨èª\8d証使用的網域 (選填)。",
+       "apihelp-createaccount-param-domain": "å¤\96é\83¨èª\8dè­\89使用的網域 (選填)。",
        "apihelp-createaccount-param-token": "在第一次請求時已取得的帳號建立金鑰。",
        "apihelp-createaccount-param-email": "使用者的電子郵件地址 (選填) 。",
        "apihelp-createaccount-param-realname": "使用者的真實姓名 (選填)。",
index 6684fb9..956c985 100644 (file)
@@ -133,7 +133,7 @@ class AuthenticationResponse {
         */
        public static function newPass( $username = null ) {
                $ret = new AuthenticationResponse;
-               $ret->status = AuthenticationResponse::PASS;
+               $ret->status = self::PASS;
                $ret->username = $username;
                return $ret;
        }
@@ -145,7 +145,7 @@ class AuthenticationResponse {
         */
        public static function newFail( Message $msg ) {
                $ret = new AuthenticationResponse;
-               $ret->status = AuthenticationResponse::FAIL;
+               $ret->status = self::FAIL;
                $ret->message = $msg;
                $ret->messageType = 'error';
                return $ret;
@@ -158,7 +158,7 @@ class AuthenticationResponse {
         */
        public static function newRestart( Message $msg ) {
                $ret = new AuthenticationResponse;
-               $ret->status = AuthenticationResponse::RESTART;
+               $ret->status = self::RESTART;
                $ret->message = $msg;
                return $ret;
        }
@@ -169,7 +169,7 @@ class AuthenticationResponse {
         */
        public static function newAbstain() {
                $ret = new AuthenticationResponse;
-               $ret->status = AuthenticationResponse::ABSTAIN;
+               $ret->status = self::ABSTAIN;
                return $ret;
        }
 
@@ -189,7 +189,7 @@ class AuthenticationResponse {
                }
 
                $ret = new AuthenticationResponse;
-               $ret->status = AuthenticationResponse::UI;
+               $ret->status = self::UI;
                $ret->neededRequests = $reqs;
                $ret->message = $msg;
                $ret->messageType = $msgtype;
@@ -209,7 +209,7 @@ class AuthenticationResponse {
                }
 
                $ret = new AuthenticationResponse;
-               $ret->status = AuthenticationResponse::REDIRECT;
+               $ret->status = self::REDIRECT;
                $ret->neededRequests = $reqs;
                $ret->redirectTarget = $redirectTarget;
                $ret->redirectApiData = $redirectApiData;
index ad1fffb..f9f9a08 100644 (file)
@@ -816,7 +816,7 @@ class MessageCache {
                }
 
                // Normalise title-case input (with some inlining)
-               $lckey = MessageCache::normalizeKey( $key );
+               $lckey = self::normalizeKey( $key );
 
                Hooks::run( 'MessageCache::get', [ &$lckey ] );
 
index cffb59a..99dc899 100644 (file)
@@ -31,8 +31,6 @@ class ChangesFeed {
        public $format, $type, $titleMsg, $descMsg;
 
        /**
-        * Constructor
-        *
         * @param string $format Feed's format (either 'rss' or 'atom')
         * @param string $type Type of feed (for cache keys)
         */
index 30c6995..55cb9ed 100644 (file)
@@ -102,15 +102,17 @@ class EnhancedChangesList extends ChangesList {
                        $rc->mAttribs['rc_timestamp'],
                        $this->getUser()
                );
+               if ( $this->lastdate === '' ) {
+                       $this->lastdate = $date;
+               }
 
                $ret = '';
 
-               # If it's a new day, add the headline and flush the cache
-               if ( $date != $this->lastdate ) {
-                       # Process current cache
+               # If it's a new day, flush the cache and update $this->lastdate
+               if ( $date !== $this->lastdate ) {
+                       # Process current cache (uses $this->lastdate to generate a heading)
                        $ret = $this->recentChangesBlock();
                        $this->rc_cache = [];
-                       $ret .= Xml::element( 'h4', null, $date ) . "\n";
                        $this->lastdate = $date;
                }
 
@@ -763,7 +765,11 @@ class EnhancedChangesList extends ChangesList {
                        }
                }
 
-               return '<div>' . $blockOut . '</div>';
+               if ( $blockOut === '' ) {
+                       return '';
+               }
+               // $this->lastdate is kept up to date by recentChangesLine()
+               return Xml::element( 'h4', null, $this->lastdate ) . "\n<div>" . $blockOut . '</div>';
        }
 
        /**
index e8e35a3..5fad8fd 100644 (file)
@@ -130,7 +130,7 @@ class RecentChange {
                if ( is_array( $type ) ) {
                        $retval = [];
                        foreach ( $type as $t ) {
-                               $retval[] = RecentChange::parseToRCType( $t );
+                               $retval[] = self::parseToRCType( $t );
                        }
 
                        return $retval;
@@ -459,7 +459,7 @@ class RecentChange {
 
                $change = $change instanceof RecentChange
                        ? $change
-                       : RecentChange::newFromId( $change );
+                       : self::newFromId( $change );
 
                if ( !$change instanceof RecentChange ) {
                        return null;
index e2f82d4..d93362b 100644 (file)
@@ -535,7 +535,7 @@ class IcuCollation extends Collation {
         * @return string|bool
         */
        static function getUnicodeVersionForICU() {
-               $icuVersion = IcuCollation::getICUVersion();
+               $icuVersion = self::getICUVersion();
                if ( !$icuVersion ) {
                        return false;
                }
index 2d2ca47..8dd7a38 100644 (file)
@@ -40,8 +40,6 @@ class NumericUppercaseCollation extends UppercaseCollation {
        private $digitTransformLang;
 
        /**
-        * Constructor
-        *
         * @param $lang Language How to convert digits.
         *  For example, if given language "my" than ၇ is treated like 7.
         *
index c57eba7..6605c38 100644 (file)
@@ -243,7 +243,7 @@ class EtcdConfig implements Config, LoggerAwareInterface {
 
                $info = json_decode( $rbody, true );
                if ( $info === null || !isset( $info['node']['nodes'] ) ) {
-                       return [ null, $rcode, "Unexpected JSON response; missing 'nodes' list.", false ];
+                       return [ null, "Unexpected JSON response; missing 'nodes' list.", false ];
                }
 
                $config = [];
index f85b00d..8603360 100644 (file)
@@ -136,7 +136,7 @@ abstract class ContentHandler {
                        $modelId = $title->getContentModel();
                }
 
-               $handler = ContentHandler::getForModelID( $modelId );
+               $handler = self::getForModelID( $modelId );
 
                return $handler->unserializeContent( $text, $format );
        }
@@ -240,7 +240,7 @@ abstract class ContentHandler {
        public static function getForTitle( Title $title ) {
                $modelId = $title->getContentModel();
 
-               return ContentHandler::getForModelID( $modelId );
+               return self::getForModelID( $modelId );
        }
 
        /**
@@ -256,7 +256,7 @@ abstract class ContentHandler {
        public static function getForContent( Content $content ) {
                $modelId = $content->getModel();
 
-               return ContentHandler::getForModelID( $modelId );
+               return self::getForModelID( $modelId );
        }
 
        /**
@@ -293,8 +293,8 @@ abstract class ContentHandler {
        public static function getForModelID( $modelId ) {
                global $wgContentHandlers;
 
-               if ( isset( ContentHandler::$handlers[$modelId] ) ) {
-                       return ContentHandler::$handlers[$modelId];
+               if ( isset( self::$handlers[$modelId] ) ) {
+                       return self::$handlers[$modelId];
                }
 
                if ( empty( $wgContentHandlers[$modelId] ) ) {
@@ -327,9 +327,9 @@ abstract class ContentHandler {
                wfDebugLog( 'ContentHandler', 'Created handler for ' . $modelId
                        . ': ' . get_class( $handler ) );
 
-               ContentHandler::$handlers[$modelId] = $handler;
+               self::$handlers[$modelId] = $handler;
 
-               return ContentHandler::$handlers[$modelId];
+               return self::$handlers[$modelId];
        }
 
        /**
@@ -372,7 +372,7 @@ abstract class ContentHandler {
                $formats = [];
 
                foreach ( $wgContentHandlers as $model => $class ) {
-                       $handler = ContentHandler::getForModelID( $model );
+                       $handler = self::getForModelID( $model );
                        $formats = array_merge( $formats, $handler->getSupportedFormats() );
                }
 
index 0d0c149..6e3eda6 100644 (file)
@@ -75,7 +75,6 @@ class DerivativeContext extends ContextSource implements MutableContext {
        private $timing;
 
        /**
-        * Constructor
         * @param IContextSource $context Context to inherit from
         */
        public function __construct( IContextSource $context ) {
index 6d18444..3d22c03 100644 (file)
@@ -46,8 +46,6 @@ class CloneDatabase {
        private $db;
 
        /**
-        * Constructor
-        *
         * @param IMaintainableDatabase $db A database subclass
         * @param array $tablesToClone An array of tables to clone, unprefixed
         * @param string $newTablePrefix Prefix to assign to the tables
index e67a0b3..012837f 100644 (file)
@@ -425,7 +425,7 @@ class MWDebug {
                $html = '';
 
                if ( self::$enabled ) {
-                       MWDebug::log( 'MWDebug output complete' );
+                       self::log( 'MWDebug output complete' );
                        $debugInfo = self::getDebugInfo( $context );
 
                        // Cannot use OutputPage::addJsConfigVars because those are already outputted
@@ -495,7 +495,7 @@ class MWDebug {
                        }
                }
 
-               MWDebug::log( 'MWDebug output complete' );
+               self::log( 'MWDebug output complete' );
                $debugInfo = self::getDebugInfo( $context );
 
                ApiResult::setIndexedTagName( $debugInfo, 'debuginfo' );
index 072c1e0..c6facd9 100644 (file)
@@ -102,8 +102,6 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate {
        private $db;
 
        /**
-        * Constructor
-        *
         * @param Title $title Title of the page we're updating
         * @param ParserOutput $parserOutput Output from a full parse of this page
         * @param bool $recursive Queue jobs for recursive updates?
index b9a259b..2766bcb 100644 (file)
@@ -44,8 +44,6 @@ class SearchUpdate implements DeferrableUpdate {
        private $page;
 
        /**
-        * Constructor
-        *
         * @param int $id Page id to update
         * @param Title|string $title Title of page to update
         * @param Content|string|bool $c Content of the page to update. Default: false.
index 95420d9..d4bee29 100644 (file)
@@ -104,7 +104,6 @@ class DifferenceEngine extends ContextSource {
        /**#@-*/
 
        /**
-        * Constructor
         * @param IContextSource $context Context to use, anything else will be ignored
         * @param int $old Old ID we want to show and diff with.
         * @param string|int $new Either revision ID or 'prev' or 'next'. Default: 0.
index 48bc3bd..f464d8a 100644 (file)
@@ -31,8 +31,6 @@ class HttpError extends MWException {
        private $httpCode, $header, $content;
 
        /**
-        * Constructor
-        *
         * @param int $httpCode HTTP status code to send to the client
         * @param string|Message $content Content of the message
         * @param string|Message|null $header Content of the header (\<title\> and \<h1\>)
index a307468..943aa04 100644 (file)
@@ -91,8 +91,8 @@ class WikiExporter {
         * @param int $buffer One of WikiExporter::BUFFER or WikiExporter::STREAM
         * @param int $text One of WikiExporter::TEXT or WikiExporter::STUB
         */
-       function __construct( $db, $history = WikiExporter::CURRENT,
-                       $buffer = WikiExporter::BUFFER, $text = WikiExporter::TEXT ) {
+       function __construct( $db, $history = self::CURRENT,
+                       $buffer = self::BUFFER, $text = self::TEXT ) {
                $this->db = $db;
                $this->history = $history;
                $this->buffer = $buffer;
@@ -272,7 +272,7 @@ class WikiExporter {
                        # Get logging table name for logging.* clause
                        $logging = $this->db->tableName( 'logging' );
 
-                       if ( $this->buffer == WikiExporter::STREAM ) {
+                       if ( $this->buffer == self::STREAM ) {
                                $prev = $this->db->bufferResults( false );
                        }
                        $result = null; // Assuring $result is not undefined, if exception occurs early
@@ -284,7 +284,7 @@ class WikiExporter {
                                        [ 'ORDER BY' => 'log_id', 'USE INDEX' => [ 'logging' => 'PRIMARY' ] ]
                                );
                                $this->outputLogStream( $result );
-                               if ( $this->buffer == WikiExporter::STREAM ) {
+                               if ( $this->buffer == self::STREAM ) {
                                        $this->db->bufferResults( $prev );
                                }
                        } catch ( Exception $e ) {
@@ -303,7 +303,7 @@ class WikiExporter {
 
                                // Putting database back in previous buffer mode
                                try {
-                                       if ( $this->buffer == WikiExporter::STREAM ) {
+                                       if ( $this->buffer == self::STREAM ) {
                                                $this->db->bufferResults( $prev );
                                        }
                                } catch ( Exception $e2 ) {
@@ -341,10 +341,10 @@ class WikiExporter {
                                if ( !empty( $this->history['limit'] ) ) {
                                        $opts['LIMIT'] = intval( $this->history['limit'] );
                                }
-                       } elseif ( $this->history & WikiExporter::FULL ) {
+                       } elseif ( $this->history & self::FULL ) {
                                # Full history dumps...
                                # query optimization for history stub dumps
-                               if ( $this->text == WikiExporter::STUB && $orderRevs ) {
+                               if ( $this->text == self::STUB && $orderRevs ) {
                                        $tables = [ 'revision', 'page' ];
                                        $opts[] = 'STRAIGHT_JOIN';
                                        $opts['ORDER BY'] = [ 'rev_page ASC', 'rev_id ASC' ];
@@ -353,13 +353,13 @@ class WikiExporter {
                                } else {
                                        $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page' ];
                                }
-                       } elseif ( $this->history & WikiExporter::CURRENT ) {
+                       } elseif ( $this->history & self::CURRENT ) {
                                # Latest revision dumps...
                                if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
                                        $this->do_list_authors( $cond );
                                }
                                $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' ];
-                       } elseif ( $this->history & WikiExporter::STABLE ) {
+                       } elseif ( $this->history & self::STABLE ) {
                                # "Stable" revision dumps...
                                # Default JOIN, to be overridden...
                                $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' ];
@@ -367,7 +367,7 @@ class WikiExporter {
                                if ( Hooks::run( 'WikiExporter::dumpStableQuery', [ &$tables, &$opts, &$join ] ) ) {
                                        throw new MWException( __METHOD__ . " given invalid history dump type." );
                                }
-                       } elseif ( $this->history & WikiExporter::RANGE ) {
+                       } elseif ( $this->history & self::RANGE ) {
                                # Dump of revisions within a specified range
                                $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page' ];
                                $opts['ORDER BY'] = [ 'rev_page ASC', 'rev_id ASC' ];
@@ -381,12 +381,12 @@ class WikiExporter {
                                $opts['USE INDEX']['page'] = 'PRIMARY';
                        }
                        # Build text join options
-                       if ( $this->text != WikiExporter::STUB ) { // 1-pass
+                       if ( $this->text != self::STUB ) { // 1-pass
                                $tables[] = 'text';
                                $join['text'] = [ 'INNER JOIN', 'rev_text_id=old_id' ];
                        }
 
-                       if ( $this->buffer == WikiExporter::STREAM ) {
+                       if ( $this->buffer == self::STREAM ) {
                                $prev = $this->db->bufferResults( false );
                        }
                        $result = null; // Assuring $result is not undefined, if exception occurs early
@@ -399,7 +399,7 @@ class WikiExporter {
                                # Output dump results
                                $this->outputPageStream( $result );
 
-                               if ( $this->buffer == WikiExporter::STREAM ) {
+                               if ( $this->buffer == self::STREAM ) {
                                        $this->db->bufferResults( $prev );
                                }
                        } catch ( Exception $e ) {
@@ -418,7 +418,7 @@ class WikiExporter {
 
                                // Putting database back in previous buffer mode
                                try {
-                                       if ( $this->buffer == WikiExporter::STREAM ) {
+                                       if ( $this->buffer == self::STREAM ) {
                                                $this->db->bufferResults( $prev );
                                        }
                                } catch ( Exception $e2 ) {
index 43f1d21..56ccc64 100644 (file)
@@ -528,7 +528,7 @@ class ForeignAPIRepo extends FileRepo {
                }
 
                $req = MWHttpRequest::factory( $url, $options, __METHOD__ );
-               $req->setUserAgent( ForeignAPIRepo::getUserAgent() );
+               $req->setUserAgent( self::getUserAgent() );
                $status = $req->execute();
 
                if ( $status->isOK() ) {
index 399147b..61d0d89 100644 (file)
@@ -285,7 +285,7 @@ class HTMLForm extends ContextSource {
                                return ObjectFactory::constructClassInstance( OOUIHTMLForm::class, $arguments );
                        default:
                                /** @var HTMLForm $form */
-                               $form = ObjectFactory::constructClassInstance( HTMLForm::class, $arguments );
+                               $form = ObjectFactory::constructClassInstance( self::class, $arguments );
                                $form->setDisplayFormat( $displayFormat );
                                return $form;
                }
index 4f21ce2..c10b312 100644 (file)
@@ -106,7 +106,7 @@ class Http {
                        $options['timeout'] = $args[1];
                        $caller = __METHOD__;
                }
-               return Http::request( 'GET', $url, $options, $caller );
+               return self::request( 'GET', $url, $options, $caller );
        }
 
        /**
@@ -119,7 +119,7 @@ class Http {
         * @return string|bool false on error
         */
        public static function post( $url, $options = [], $caller = __METHOD__ ) {
-               return Http::request( 'POST', $url, $options, $caller );
+               return self::request( 'POST', $url, $options, $caller );
        }
 
        /**
index 8034400..94a2b93 100644 (file)
@@ -93,7 +93,7 @@ class ImportStreamSource implements ImportSource {
                }
                $fname = $upload['tmp_name'];
                if ( is_uploaded_file( $fname ) ) {
-                       return ImportStreamSource::newFromFile( $fname );
+                       return self::newFromFile( $fname );
                } else {
                        return Status::newFatal( 'importnofile' );
                }
@@ -178,6 +178,6 @@ class ImportStreamSource implements ImportSource {
 
                $url = wfAppendQuery( $link, $params );
                # For interwikis, use POST to avoid redirects.
-               return ImportStreamSource::newFromURL( $url, "POST" );
+               return self::newFromURL( $url, "POST" );
        }
 }
index e5cbb7c..7b6ac5e 100644 (file)
@@ -83,6 +83,7 @@ abstract class DatabaseUpdater {
                FixDefaultJsonContentPages::class,
                CleanupEmptyCategories::class,
                AddRFCAndPMIDInterwiki::class,
+               PopulatePPSortKey::class
        ];
 
        /**
@@ -105,8 +106,6 @@ abstract class DatabaseUpdater {
        protected $holdContentHandlerUseDB = true;
 
        /**
-        * Constructor
-        *
         * @param Database $db To perform updates on
         * @param bool $shared Whether to perform updates on shared tables
         * @param Maintenance $maintenance Maintenance object which created us
index a311ce9..27300f3 100644 (file)
@@ -703,7 +703,7 @@ class WebInstaller extends Installer {
                        "<span class=\"config-help-field-hint\" title=\"" .
                        wfMessage( 'config-help-tooltip' )->escaped() . "\">" .
                        wfMessage( 'config-help' )->escaped() . "</span>\n" .
-                       "<span class=\"config-help-field-data\">" . $html . "</span>\n" .
+                       "<div class=\"config-help-field-data\">" . $html . "</div>\n" .
                        "</div>\n";
        }
 
index 2ba47f1..defaf1b 100644 (file)
@@ -1,4 +1,64 @@
 {
-       "@metadata": [],
-       "mainpagetext": "'''MediaWiki òsta zainstalowónô.'''"
+       "@metadata": {
+               "authors": [
+                       "Kaszeba"
+               ]
+       },
+       "config-desc": "Jinstalownik MediaWiki",
+       "config-title": "Jinstalowanié MediaWiki $1",
+       "config-information": "Wëdowiédzô",
+       "config-localsettings-key": "Klucz aktualizacëji:",
+       "config-localsettings-badkey": "Lëchi klucz aktualizacëji.",
+       "config-session-error": "Fela zrëszeniô sesëji – $1",
+       "config-your-language": "Twój jãzëk:",
+       "config-your-language-help": "Wybierzë jãzëk procesu jinstalacëji.",
+       "config-wiki-language": "Jãzëk wiki:",
+       "config-back": "← Nazôd",
+       "config-continue": "Dali →",
+       "config-page-language": "Jãzëk",
+       "config-page-welcome": "Witómë w MediaWiki!",
+       "config-page-dbconnect": "Sparłãczë z bazą pòdôwków",
+       "config-page-upgrade": "Zaktualnienié jinstalacëji",
+       "config-page-dbsettings": "Nastôw bazë pòdôwków",
+       "config-page-name": "Miono",
+       "config-page-options": "Òptacëje",
+       "config-page-install": "Wjinstalëjë",
+       "config-page-complete": "Fardich!",
+       "config-page-restart": "Zrëszë jinstalacëjã znowa",
+       "config-page-readme": "Spòdlowô wëdowiédzô",
+       "config-page-releasenotes": "Wëdowiédzô ò wersëji",
+       "config-page-copying": "Kòpérowanié",
+       "config-page-upgradedoc": "Zaktualnienié",
+       "config-page-existingwiki": "Egyzstëjącô wiki",
+       "config-restart": "Jo, zrëszë znowa",
+       "config-env-php": "PHP $1 je wjinastalowóné",
+       "config-env-hhvm": "HHVM $1 je wjinastalowóné",
+       "config-memory-raised": "Paraméter PHP <code>memory_limit</code> $1 òstôł zwikszony do $2.",
+       "config-xcache": "[Http://trac.lighttpd.net/xcache/ XCache] je wjinstalowóny",
+       "config-apc": "[Http://www.php.net/apc APC] je wjinstalowóny",
+       "config-apcu": "[http://www.php.net/apcu APCu] je wjinstalowóny",
+       "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] je wjinstalowóny",
+       "config-diff3-bad": "Felënk GNU diff3.",
+       "config-mysql-innodb": "InnoDB",
+       "config-mysql-myisam": "MyISAM",
+       "config-mysql-binary": "binarny",
+       "config-mysql-utf8": "UTF‐8",
+       "config-site-name": "Miono wiki:",
+       "config-site-name-blank": "Wpiszë miono starnów.",
+       "config-ns-other-default": "MòjôWiki",
+       "config-admin-box": "Kònto sprôwnika",
+       "config-admin-name": "Twòjé miono brëkòwnika:",
+       "config-admin-password": "Parola:",
+       "config-admin-password-confirm": "Pòwtórzë parolã:",
+       "config-admin-name-blank": "Wpiszë miono brëkòwnika, chtëren bãdze sprôwnikã.",
+       "config-admin-email": "E-mailowô adresa:",
+       "config-license-none": "Felënk stopczi z licencëją",
+       "config-email-settings": "Nastôwë e-mail",
+       "config-logo": "Adresa URL logo:",
+       "config-skins": "Wëzdrzatk",
+       "config-skins-use-as-default": "Ùżëjë tegò wëzdrzatkù za domëslny",
+       "config-install-step-done": "fardich",
+       "config-install-step-failed": "nie dzrzëło sã",
+       "config-help": "pòmòc",
+       "mainpagetext": "<strong>MediaWiki òsta wjinstalowónô.<strong>"
 }
index d20a233..1f4f179 100644 (file)
@@ -378,7 +378,7 @@ abstract class JobQueue {
                // Flag this job as an old duplicate based on its "root" job...
                try {
                        if ( $job && $this->isRootJobOldDuplicate( $job ) ) {
-                               JobQueue::incrStats( 'dupe_pops', $this->type );
+                               self::incrStats( 'dupe_pops', $this->type );
                                $job = DuplicateJob::newFromJob( $job ); // convert to a no-op
                        }
                } catch ( Exception $e ) {
index 7fdd617..44721d9 100644 (file)
@@ -28,7 +28,7 @@
  * For example, one can set $wgJobTypeConf['refreshLinks'] to point to a
  * JobQueueFederated instance, which itself would consist of three JobQueueRedis
  * instances, each using their own redis server. This would allow for the jobs
- * to be split (evenly or based on weights) accross multiple servers if a single
+ * to be split (evenly or based on weights) across multiple servers if a single
  * server becomes impractical or expensive. Different JobQueue classes can be mixed.
  *
  * The basic queue configuration (e.g. "order", "claimTTL") of a federated queue
diff --git a/includes/jobqueue/JobQueueSecondTestQueue.php b/includes/jobqueue/JobQueueSecondTestQueue.php
new file mode 100644 (file)
index 0000000..a1935df
--- /dev/null
@@ -0,0 +1,280 @@
+<?php
+
+/**
+ * A wrapper for the JobQueue that delegates all the method calls to a single,
+ * main queue, and also pushes all the jobs to a second job queue that's being
+ * debugged.
+ *
+ * This class was temporary added to test transitioning to the JobQueueEventBus
+ * and will removed after the transition is completed. This code is only needed
+ * while we are testing the new infrastructure to be able to compare the results
+ * between the queue implementations and make sure that they process the same jobs,
+ * deduplicate correctly, compare the delays, backlogs and make sure no jobs are lost.
+ * When the new infrastructure is well tested this will not be needed any more.
+ *
+ * @deprecated since 1.30
+ * @since 1.30
+ */
+class JobQueueSecondTestQueue extends JobQueue {
+
+       /**
+        * @var JobQueue
+        */
+       private $mainQueue;
+
+       /**
+        * @var JobQueue
+        */
+       private $debugQueue;
+
+       protected function __construct( array $params ) {
+               if ( !isset( $params['mainqueue'] ) ) {
+                       throw new MWException( "mainqueue parameter must be provided to the debug queue" );
+               }
+
+               if ( !isset( $params['debugqueue'] ) ) {
+                       throw new MWException( "debugqueue parameter must be provided to the debug queue" );
+               }
+
+               $conf = [ 'wiki' => $params['wiki'], 'type' => $params['type'] ];
+               $this->mainQueue = JobQueue::factory( $params['mainqueue'] + $conf );
+               $this->debugQueue = JobQueue::factory( $params['debugqueue'] + $conf );
+
+               // We need to construct parent after creating the main and debug queue
+               // because super constructor calls some methods we delegate to the main queue.
+               parent::__construct( $params );
+       }
+
+       /**
+        * Get the allowed queue orders for configuration validation
+        *
+        * @return array Subset of (random, timestamp, fifo, undefined)
+        */
+       protected function supportedOrders() {
+               return $this->mainQueue->supportedOrders();
+       }
+
+       /**
+        * Get the default queue order to use if configuration does not specify one
+        *
+        * @return string One of (random, timestamp, fifo, undefined)
+        */
+       protected function optimalOrder() {
+               return $this->mainQueue->optimalOrder();
+       }
+
+       /**
+        * Find out if delayed jobs are supported for configuration validation
+        *
+        * @return bool Whether delayed jobs are supported
+        */
+       protected function supportsDelayedJobs() {
+               return $this->mainQueue->supportsDelayedJobs();
+       }
+
+       /**
+        * @see JobQueue::isEmpty()
+        * @return bool
+        */
+       protected function doIsEmpty() {
+               return $this->mainQueue->doIsEmpty();
+       }
+
+       /**
+        * @see JobQueue::getSize()
+        * @return int
+        */
+       protected function doGetSize() {
+               return $this->mainQueue->doGetSize();
+       }
+
+       /**
+        * @see JobQueue::getAcquiredCount()
+        * @return int
+        */
+       protected function doGetAcquiredCount() {
+               return $this->mainQueue->doGetAcquiredCount();
+       }
+
+       /**
+        * @see JobQueue::getDelayedCount()
+        * @return int
+        */
+       protected function doGetDelayedCount() {
+               return $this->mainQueue->doGetDelayedCount();
+       }
+
+       /**
+        * @see JobQueue::getAbandonedCount()
+        * @return int
+        */
+       protected function doGetAbandonedCount() {
+               return $this->mainQueue->doGetAbandonedCount();
+       }
+
+       /**
+        * @see JobQueue::batchPush()
+        * @param IJobSpecification[] $jobs
+        * @param int $flags
+        */
+       protected function doBatchPush( array $jobs, $flags ) {
+               $this->mainQueue->doBatchPush( $jobs, $flags );
+
+               try {
+                       $this->debugQueue->doBatchPush( $jobs, $flags );
+               } catch ( Exception $exception ) {
+                       MWExceptionHandler::logException( $exception );
+               }
+       }
+
+       /**
+        * @see JobQueue::pop()
+        * @return Job|bool
+        */
+       protected function doPop() {
+               return $this->mainQueue->doPop();
+       }
+
+       /**
+        * @see JobQueue::ack()
+        * @param Job $job
+        */
+       protected function doAck( Job $job ) {
+               return $this->mainQueue->doAck( $job );
+       }
+
+       /**
+        * @see JobQueue::deduplicateRootJob()
+        * @param IJobSpecification $job
+        * @throws MWException
+        * @return bool
+        */
+       protected function doDeduplicateRootJob( IJobSpecification $job ) {
+               return $this->mainQueue->doDeduplicateRootJob( $job );
+       }
+
+       /**
+        * @see JobQueue::isRootJobOldDuplicate()
+        * @param Job $job
+        * @return bool
+        */
+       protected function doIsRootJobOldDuplicate( Job $job ) {
+               return $this->mainQueue->doIsRootJobOldDuplicate( $job );
+       }
+
+       /**
+        * @param string $signature Hash identifier of the root job
+        * @return string
+        */
+       protected function getRootJobCacheKey( $signature ) {
+               return $this->mainQueue->getRootJobCacheKey( $signature );
+       }
+
+       /**
+        * @see JobQueue::delete()
+        * @throws MWException
+        */
+       protected function doDelete() {
+               return $this->mainQueue->doDelete();
+       }
+
+       /**
+        * @see JobQueue::waitForBackups()
+        * @return void
+        */
+       protected function doWaitForBackups() {
+               $this->mainQueue->doWaitForBackups();
+       }
+
+       /**
+        * @see JobQueue::flushCaches()
+        * @return void
+        */
+       protected function doFlushCaches() {
+               $this->mainQueue->doFlushCaches();
+       }
+
+       /**
+        * Get an iterator to traverse over all available jobs in this queue.
+        * This does not include jobs that are currently acquired or delayed.
+        * Note: results may be stale if the queue is concurrently modified.
+        *
+        * @return Iterator
+        * @throws JobQueueError
+        */
+       public function getAllQueuedJobs() {
+               return $this->mainQueue->getAllQueuedJobs();
+       }
+
+       /**
+        * Get an iterator to traverse over all delayed jobs in this queue.
+        * Note: results may be stale if the queue is concurrently modified.
+        *
+        * @return Iterator
+        * @throws JobQueueError
+        * @since 1.22
+        */
+       public function getAllDelayedJobs() {
+               return $this->mainQueue->getAllDelayedJobs();
+       }
+
+       /**
+        * Get an iterator to traverse over all claimed jobs in this queue
+        *
+        * Callers should be quick to iterator over it or few results
+        * will be returned due to jobs being acknowledged and deleted
+        *
+        * @return Iterator
+        * @throws JobQueueError
+        * @since 1.26
+        */
+       public function getAllAcquiredJobs() {
+               return $this->mainQueue->getAllAcquiredJobs();
+       }
+
+       /**
+        * Get an iterator to traverse over all abandoned jobs in this queue
+        *
+        * @return Iterator
+        * @throws JobQueueError
+        * @since 1.25
+        */
+       public function getAllAbandonedJobs() {
+               return $this->mainQueue->getAllAbandonedJobs();
+       }
+
+       /**
+        * Do not use this function outside of JobQueue/JobQueueGroup
+        *
+        * @return string
+        * @since 1.22
+        */
+       public function getCoalesceLocationInternal() {
+               return $this->mainQueue->getCoalesceLocationInternal();
+       }
+
+       /**
+        * @see JobQueue::getSiblingQueuesWithJobs()
+        * @param array $types List of queues types
+        * @return array|null (list of queue types) or null if unsupported
+        */
+       protected function doGetSiblingQueuesWithJobs( array $types ) {
+               return $this->mainQueue->doGetSiblingQueuesWithJobs( $types );
+       }
+
+       /**
+        * @see JobQueue::getSiblingQueuesSize()
+        * @param array $types List of queues types
+        * @return array|null (list of queue types) or null if unsupported
+        */
+       protected function doGetSiblingQueueSizes( array $types ) {
+               return $this->mainQueue->doGetSiblingQueueSizes( $types );
+       }
+
+       /**
+        * @throws JobQueueReadOnlyError
+        */
+       protected function assertNotReadOnly() {
+               $this->mainQueue->assertNotReadOnly();
+       }
+}
index ea0f1b7..9e060cd 100644 (file)
@@ -78,7 +78,12 @@ class CSSMin {
                                $url = $match['file'][0];
 
                                // Skip fully-qualified and protocol-relative URLs and data URIs
-                               if ( substr( $url, 0, 2 ) === '//' || parse_url( $url, PHP_URL_SCHEME ) ) {
+                               // Also skips the rare `behavior` property specifying application's default behavior
+                               if (
+                                       substr( $url, 0, 2 ) === '//' ||
+                                       parse_url( $url, PHP_URL_SCHEME ) ||
+                                       substr( $url, 0, 9 ) === '#default#'
+                               ) {
                                        break;
                                }
 
@@ -252,7 +257,7 @@ class CSSMin {
                // quotation marks (e.g. "foo /* bar").
                $comments = [];
 
-               $pattern = '/(?!' . CSSMin::EMBED_REGEX . ')(' . CSSMin::COMMENT_REGEX . ')/s';
+               $pattern = '/(?!' . self::EMBED_REGEX . ')(' . self::COMMENT_REGEX . ')/s';
 
                $source = preg_replace_callback(
                        $pattern,
@@ -355,7 +360,7 @@ class CSSMin {
                        }, $source );
 
                // Re-insert comments
-               $pattern = '/' . CSSMin::PLACEHOLDER . '(\d+)x/';
+               $pattern = '/' . self::PLACEHOLDER . '(\d+)x/';
                $source = preg_replace_callback( $pattern, function ( $match ) use ( &$comments ) {
                        return $comments[ $match[1] ];
                }, $source );
@@ -474,7 +479,12 @@ class CSSMin {
 
                // Pass thru fully-qualified and protocol-relative URLs and data URIs, as well as local URLs if
                // we can't expand them.
-               if ( self::isRemoteUrl( $url ) || self::isLocalUrl( $url ) ) {
+               // Also skips the rare `behavior` property specifying application's default behavior
+               if (
+                       self::isRemoteUrl( $url ) ||
+                       self::isLocalUrl( $url ) ||
+                       substr( $url, 0, 9 ) === '#default#'
+               ) {
                        return $url;
                }
 
index e8b0e6a..b22f06d 100644 (file)
@@ -549,7 +549,7 @@ class IP {
         */
        private static function parseCIDR6( $range ) {
                # Explode into <expanded IP,range>
-               $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
+               $parts = explode( '/', self::sanitizeIP( $range ), 2 );
                if ( count( $parts ) != 2 ) {
                        return [ false, false ];
                }
@@ -590,7 +590,7 @@ class IP {
         */
        private static function parseRange6( $range ) {
                # Expand any IPv6 IP
-               $range = IP::sanitizeIP( $range );
+               $range = self::sanitizeIP( $range );
                // CIDR notation...
                if ( strpos( $range, '/' ) !== false ) {
                        list( $network, $bits ) = self::parseCIDR6( $range );
@@ -732,8 +732,8 @@ class IP {
        public static function getSubnet( $ip ) {
                $matches = [];
                $subnet = false;
-               if ( IP::isIPv6( $ip ) ) {
-                       $parts = IP::parseRange( "$ip/64" );
+               if ( self::isIPv6( $ip ) ) {
+                       $parts = self::parseRange( "$ip/64" );
                        $subnet = $parts[0];
                } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
                        // IPv4
index cffb5a3..9638706 100644 (file)
@@ -276,7 +276,7 @@ class StringUtils {
 
                // Replace instances of the separator inside HTML-like tags with the placeholder
                $replacer = new DoubleReplacer( $separator, $placeholder );
-               $cleaned = StringUtils::delimiterReplaceCallback( '<', '>', $replacer->cb(), $text );
+               $cleaned = self::delimiterReplaceCallback( '<', '>', $replacer->cb(), $text );
 
                // Explode, then put the replaced separators back in
                $items = explode( $separator, $cleaned );
@@ -303,7 +303,7 @@ class StringUtils {
 
                // Replace instances of the separator inside HTML-like tags with the placeholder
                $replacer = new DoubleReplacer( $search, $placeholder );
-               $cleaned = StringUtils::delimiterReplaceCallback( '<', '>', $replacer->cb(), $text );
+               $cleaned = self::delimiterReplaceCallback( '<', '>', $replacer->cb(), $text );
 
                // Explode, then put the replaced separators back in
                $cleaned = str_replace( $search, $replace, $cleaned );
index 9bfcee7..e41c3a2 100644 (file)
@@ -42,8 +42,6 @@ class APCBagOStuff extends BagOStuff {
        const KEY_SUFFIX = ':2';
 
        /**
-        * Constructor
-        *
         * Available parameters are:
         *   - nativeSerialize:     If true, pass objects to apc_store(), and trust it
         *                          to serialize them correctly. If false, serialize
index 6e6a3ad..a26e560 100644 (file)
@@ -28,8 +28,6 @@
  */
 class APCUBagOStuff extends APCBagOStuff {
        /**
-        * Constructor
-        *
         * Available parameters are:
         *   - nativeSerialize:     If true, pass objects to apcu_store(), and trust it
         *                          to serialize them correctly. If false, serialize
index c568e7b..e3e66d5 100644 (file)
@@ -29,8 +29,6 @@
 class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
 
        /**
-        * Constructor
-        *
         * Available parameters are:
         *   - servers:             The list of IP:port combinations holding the memcached servers.
         *   - persistent:          Whether to use a persistent connection
index 687c67c..d94578d 100644 (file)
@@ -181,6 +181,12 @@ class MultiWriteBagOStuff extends BagOStuff {
                $ret = true;
                $args = array_slice( func_get_args(), 3 );
 
+               if ( $count > 1 && $asyncWrites ) {
+                       // Deep-clone $args to prevent misbehavior when something writes an
+                       // object to the BagOStuff then modifies it afterwards, e.g. T168040.
+                       $args = unserialize( serialize( $args ) );
+               }
+
                foreach ( $this->caches as $i => $cache ) {
                        if ( $i >= $count ) {
                                break; // ignore the lower tiers
index acb4dce..fc50961 100644 (file)
@@ -233,12 +233,13 @@ interface ILoadBalancer {
 
        /**
         * Open a connection to the server given by the specified index
-        * Index must be an actual index into the array.
-        * If the server is already open, returns it.
+        *
+        * The index must be an actual index into the array. If a connection to the server is
+        * already open and not considered an "in use" foreign connection, this simply returns it.
         *
         * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
         *
-        * @param int $i Server index or DB_MASTER/DB_REPLICA
+        * @param int $i Server index (does not support DB_MASTER/DB_REPLICA)
         * @param string|bool $domain Domain ID, or false for the current domain
         * @return Database|bool Returns false on errors
         * @throws DBAccessError
index 0b70010..72217da 100644 (file)
@@ -41,7 +41,7 @@ use Exception;
 class LoadBalancer implements ILoadBalancer {
        /** @var array[] Map of (server index => server config array) */
        private $mServers;
-       /** @var Database[][][] Map of local/foreignUsed/foreignFree => server index => IDatabase array */
+       /** @var Database[][][] Map of local/foreignUsed/foreignFree => server index => IDatabase[] */
        private $mConns;
        /** @var float[] Map of (server index => weight) */
        private $mLoads;
@@ -126,6 +126,10 @@ class LoadBalancer implements ILoadBalancer {
        /** @var integer Seconds to cache master server read-only status */
        const TTL_CACHE_READONLY = 5;
 
+       const KEY_LOCAL = 'local';
+       const KEY_FOREIGN_FREE = 'foreignFree';
+       const KEY_FOREIGN_INUSE = 'foreignInUse';
+
        public function __construct( array $params ) {
                if ( !isset( $params['servers'] ) ) {
                        throw new InvalidArgumentException( __CLASS__ . ': missing servers parameter' );
@@ -148,9 +152,9 @@ class LoadBalancer implements ILoadBalancer {
 
                $this->mReadIndex = -1;
                $this->mConns = [
-                       'local'       => [],
-                       'foreignUsed' => [],
-                       'foreignFree' => []
+                       self::KEY_LOCAL => [],
+                       self::KEY_FOREIGN_INUSE => [],
+                       self::KEY_FOREIGN_FREE => []
                ];
                $this->mLoads = [];
                $this->mWaitForPos = false;
@@ -711,19 +715,19 @@ class LoadBalancer implements ILoadBalancer {
                }
 
                $domain = $conn->getDomainID();
-               if ( !isset( $this->mConns['foreignUsed'][$serverIndex][$domain] ) ) {
+               if ( !isset( $this->mConns[self::KEY_FOREIGN_INUSE][$serverIndex][$domain] ) ) {
                        throw new InvalidArgumentException( __METHOD__ .
                                ": connection $serverIndex/$domain not found; it may have already been freed." );
-               } elseif ( $this->mConns['foreignUsed'][$serverIndex][$domain] !== $conn ) {
+               } elseif ( $this->mConns[self::KEY_FOREIGN_INUSE][$serverIndex][$domain] !== $conn ) {
                        throw new InvalidArgumentException( __METHOD__ .
                                ": connection $serverIndex/$domain mismatched; it may have already been freed." );
                }
                $conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
                if ( $refCount <= 0 ) {
-                       $this->mConns['foreignFree'][$serverIndex][$domain] = $conn;
-                       unset( $this->mConns['foreignUsed'][$serverIndex][$domain] );
-                       if ( !$this->mConns['foreignUsed'][$serverIndex] ) {
-                               unset( $this->mConns[ 'foreignUsed' ][$serverIndex] ); // clean up
+                       $this->mConns[self::KEY_FOREIGN_FREE][$serverIndex][$domain] = $conn;
+                       unset( $this->mConns[self::KEY_FOREIGN_INUSE][$serverIndex][$domain] );
+                       if ( !$this->mConns[self::KEY_FOREIGN_INUSE][$serverIndex] ) {
+                               unset( $this->mConns[ self::KEY_FOREIGN_INUSE ][$serverIndex] ); // clean up
                        }
                        $this->connLogger->debug( __METHOD__ . ": freed connection $serverIndex/$domain" );
                } else {
@@ -772,8 +776,8 @@ class LoadBalancer implements ILoadBalancer {
 
                if ( $domain !== false ) {
                        $conn = $this->openForeignConnection( $i, $domain );
-               } elseif ( isset( $this->mConns['local'][$i][0] ) ) {
-                       $conn = $this->mConns['local'][$i][0];
+               } elseif ( isset( $this->mConns[self::KEY_LOCAL][$i][0] ) ) {
+                       $conn = $this->mConns[self::KEY_LOCAL][$i][0];
                } else {
                        if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) {
                                throw new InvalidArgumentException( "No server with index '$i'." );
@@ -785,7 +789,7 @@ class LoadBalancer implements ILoadBalancer {
                        $serverName = $this->getServerName( $i );
                        if ( $conn->isOpen() ) {
                                $this->connLogger->debug( "Connected to database $i at '$serverName'." );
-                               $this->mConns['local'][$i][0] = $conn;
+                               $this->mConns[self::KEY_LOCAL][$i][0] = $conn;
                        } else {
                                $this->connLogger->warning( "Failed to connect to database $i at '$serverName'." );
                                $this->errorConnection = $conn;
@@ -830,20 +834,20 @@ class LoadBalancer implements ILoadBalancer {
                $dbName = $domainInstance->getDatabase();
                $prefix = $domainInstance->getTablePrefix();
 
-               if ( isset( $this->mConns['foreignUsed'][$i][$domain] ) ) {
-                       // Reuse an already-used connection
-                       $conn = $this->mConns['foreignUsed'][$i][$domain];
+               if ( isset( $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] ) ) {
+                       // Reuse an in-use connection for the same domain that is not in-use
+                       $conn = $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain];
                        $this->connLogger->debug( __METHOD__ . ": reusing connection $i/$domain" );
-               } elseif ( isset( $this->mConns['foreignFree'][$i][$domain] ) ) {
-                       // Reuse a free connection for the same domain
-                       $conn = $this->mConns['foreignFree'][$i][$domain];
-                       unset( $this->mConns['foreignFree'][$i][$domain] );
-                       $this->mConns['foreignUsed'][$i][$domain] = $conn;
+               } elseif ( isset( $this->mConns[self::KEY_FOREIGN_FREE][$i][$domain] ) ) {
+                       // Reuse a free connection for the same domain that is not in-use
+                       $conn = $this->mConns[self::KEY_FOREIGN_FREE][$i][$domain];
+                       unset( $this->mConns[self::KEY_FOREIGN_FREE][$i][$domain] );
+                       $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] = $conn;
                        $this->connLogger->debug( __METHOD__ . ": reusing free connection $i/$domain" );
-               } elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) {
+               } elseif ( !empty( $this->mConns[self::KEY_FOREIGN_FREE][$i] ) ) {
                        // Reuse a connection from another domain
-                       $conn = reset( $this->mConns['foreignFree'][$i] );
-                       $oldDomain = key( $this->mConns['foreignFree'][$i] );
+                       $conn = reset( $this->mConns[self::KEY_FOREIGN_FREE][$i] );
+                       $oldDomain = key( $this->mConns[self::KEY_FOREIGN_FREE][$i] );
                        // The empty string as a DB name means "don't care".
                        // DatabaseMysqlBase::open() already handle this on connection.
                        if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) {
@@ -853,8 +857,8 @@ class LoadBalancer implements ILoadBalancer {
                                $conn = false;
                        } else {
                                $conn->tablePrefix( $prefix );
-                               unset( $this->mConns['foreignFree'][$i][$oldDomain] );
-                               $this->mConns['foreignUsed'][$i][$domain] = $conn;
+                               unset( $this->mConns[self::KEY_FOREIGN_FREE][$i][$oldDomain] );
+                               $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] = $conn;
                                $this->connLogger->debug( __METHOD__ .
                                        ": reusing free connection from $oldDomain for $domain" );
                        }
@@ -874,7 +878,7 @@ class LoadBalancer implements ILoadBalancer {
                                $conn = false;
                        } else {
                                $conn->tablePrefix( $prefix );
-                               $this->mConns['foreignUsed'][$i][$domain] = $conn;
+                               $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] = $conn;
                                $this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" );
                        }
                }
@@ -1085,9 +1089,9 @@ class LoadBalancer implements ILoadBalancer {
                } );
 
                $this->mConns = [
-                       'local' => [],
-                       'foreignFree' => [],
-                       'foreignUsed' => [],
+                       self::KEY_LOCAL => [],
+                       self::KEY_FOREIGN_FREE => [],
+                       self::KEY_FOREIGN_INUSE => [],
                ];
                $this->connsOpened = 0;
        }
@@ -1621,13 +1625,19 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        public function setDomainPrefix( $prefix ) {
-               if ( $this->mConns['foreignUsed'] ) {
-                       // Do not switch connections to explicit foreign domains unless marked as free
-                       $domains = [];
-                       foreach ( $this->mConns['foreignUsed'] as $i => $connsByDomain ) {
-                               $domains = array_merge( $domains, array_keys( $connsByDomain ) );
+               // Find connections to explicit foreign domains still marked as in-use...
+               $domainsInUse = [];
+               $this->forEachOpenConnection( function ( IDatabase $conn ) use ( &$domainsInUse ) {
+                       // Once reuseConnection() is called on a handle, its reference count goes from 1 to 0.
+                       // Until then, it is still in use by the caller (explicitly or via DBConnRef scope).
+                       if ( $conn->getLBInfo( 'foreignPoolRefCount' ) > 0 ) {
+                               $domainsInUse[] = $conn->getDomainID();
                        }
-                       $domains = implode( ', ', $domains );
+               } );
+
+               // Do not switch connections to explicit foreign domains unless marked as safe
+               if ( $domainsInUse ) {
+                       $domains = implode( ', ', $domainsInUse );
                        throw new DBUnexpectedError( null,
                                "Foreign domain connections are still in use ($domains)." );
                }
index f2b1670..ec97b50 100644 (file)
@@ -72,8 +72,6 @@ class LogPage {
        private $target;
 
        /**
-        * Constructor
-        *
         * @param string $type One of '', 'block', 'protect', 'rights', 'delete',
         *   'upload', 'move'
         * @param bool $rc Whether to update recent changes as well as the logging table
@@ -207,7 +205,7 @@ class LogPage {
         * @return bool
         */
        public static function isLogType( $type ) {
-               return in_array( $type, LogPage::validTypes() );
+               return in_array( $type, self::validTypes() );
        }
 
        /**
@@ -350,7 +348,7 @@ class LogPage {
                $this->action = $action;
                $this->target = $target;
                $this->comment = $comment;
-               $this->params = LogPage::makeParamBlob( $params );
+               $this->params = self::makeParamBlob( $params );
 
                if ( $doer === null ) {
                        global $wgUser;
index 11dce31..f79fcfa 100644 (file)
@@ -49,8 +49,6 @@ class LogPager extends ReverseChronologicalPager {
        public $mLogEventsList;
 
        /**
-        * Constructor
-        *
         * @param LogEventsList $list
         * @param string|array $types Log types to show
         * @param string $performer The user who made the log entries
index 3858f27..1f8489f 100644 (file)
@@ -175,18 +175,18 @@ class UserMailer {
                                // first send to non-split address list, then to split addresses one by one
                                $status = Status::newGood();
                                if ( $to ) {
-                                       $status->merge( UserMailer::sendInternal(
+                                       $status->merge( self::sendInternal(
                                                $to, $from, $subject, $body, $options ) );
                                }
                                foreach ( $splitTo as $newTo ) {
-                                       $status->merge( UserMailer::sendInternal(
+                                       $status->merge( self::sendInternal(
                                                [ $newTo ], $from, $subject, $body, $options ) );
                                }
                                return $status;
                        }
                }
 
-               return UserMailer::sendInternal( $to, $from, $subject, $body, $options );
+               return self::sendInternal( $to, $from, $subject, $body, $options );
        }
 
        /**
index 57b5b36..d25111c 100644 (file)
@@ -40,8 +40,6 @@ class DjVuImage {
        const DJVUTXT_MEMORY_LIMIT = 300000;
 
        /**
-        * Constructor
-        *
         * @param string $filename The DjVu file name.
         */
        function __construct( $filename ) {
index 9bfbc96..cd457f0 100644 (file)
@@ -96,8 +96,6 @@ class Exif {
        private $byteOrder;
 
        /**
-        * Constructor
-        *
         * @param string $file Filename.
         * @param string $byteOrder Type of byte ordering either 'BE' (Big Endian)
         *   or 'LE' (Little Endian). Default ''.
@@ -120,162 +118,162 @@ class Exif {
                        # TIFF Rev. 6.0 Attribute Information (p22)
                        'IFD0' => [
                                # Tags relating to image structure
-                               'ImageWidth' => Exif::SHORT_OR_LONG, # Image width
-                               'ImageLength' => Exif::SHORT_OR_LONG, # Image height
-                               'BitsPerSample' => [ Exif::SHORT, 3 ], # Number of bits per component
+                               'ImageWidth' => self::SHORT_OR_LONG, # Image width
+                               'ImageLength' => self::SHORT_OR_LONG, # Image height
+                               'BitsPerSample' => [ self::SHORT, 3 ], # Number of bits per component
                                # "When a primary image is JPEG compressed, this designation is not"
                                # "necessary and is omitted." (p23)
-                               'Compression' => Exif::SHORT, # Compression scheme #p23
-                               'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23
-                               'Orientation' => Exif::SHORT, # Orientation of image #p24
-                               'SamplesPerPixel' => Exif::SHORT, # Number of components
-                               'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
-                               'YCbCrSubSampling' => [ Exif::SHORT, 2 ], # Subsampling ratio of Y to C #p24
-                               'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
-                               'XResolution' => Exif::RATIONAL, # Image resolution in width direction
-                               'YResolution' => Exif::RATIONAL, # Image resolution in height direction
-                               'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
+                               'Compression' => self::SHORT, # Compression scheme #p23
+                               'PhotometricInterpretation' => self::SHORT, # Pixel composition #p23
+                               'Orientation' => self::SHORT, # Orientation of image #p24
+                               'SamplesPerPixel' => self::SHORT, # Number of components
+                               'PlanarConfiguration' => self::SHORT, # Image data arrangement #p24
+                               'YCbCrSubSampling' => [ self::SHORT, 2 ], # Subsampling ratio of Y to C #p24
+                               'YCbCrPositioning' => self::SHORT, # Y and C positioning #p24-25
+                               'XResolution' => self::RATIONAL, # Image resolution in width direction
+                               'YResolution' => self::RATIONAL, # Image resolution in height direction
+                               'ResolutionUnit' => self::SHORT, # Unit of X and Y resolution #(p26)
 
                                # Tags relating to recording offset
-                               'StripOffsets' => Exif::SHORT_OR_LONG, # Image data location
-                               'RowsPerStrip' => Exif::SHORT_OR_LONG, # Number of rows per strip
-                               'StripByteCounts' => Exif::SHORT_OR_LONG, # Bytes per compressed strip
-                               'JPEGInterchangeFormat' => Exif::SHORT_OR_LONG, # Offset to JPEG SOI
-                               'JPEGInterchangeFormatLength' => Exif::SHORT_OR_LONG, # Bytes of JPEG data
+                               'StripOffsets' => self::SHORT_OR_LONG, # Image data location
+                               'RowsPerStrip' => self::SHORT_OR_LONG, # Number of rows per strip
+                               'StripByteCounts' => self::SHORT_OR_LONG, # Bytes per compressed strip
+                               'JPEGInterchangeFormat' => self::SHORT_OR_LONG, # Offset to JPEG SOI
+                               'JPEGInterchangeFormatLength' => self::SHORT_OR_LONG, # Bytes of JPEG data
 
                                # Tags relating to image data characteristics
-                               'TransferFunction' => Exif::IGNORE, # Transfer function
-                               'WhitePoint' => [ Exif::RATIONAL, 2 ], # White point chromaticity
-                               'PrimaryChromaticities' => [ Exif::RATIONAL, 6 ], # Chromaticities of primarities
+                               'TransferFunction' => self::IGNORE, # Transfer function
+                               'WhitePoint' => [ self::RATIONAL, 2 ], # White point chromaticity
+                               'PrimaryChromaticities' => [ self::RATIONAL, 6 ], # Chromaticities of primarities
                                # Color space transformation matrix coefficients #p27
-                               'YCbCrCoefficients' => [ Exif::RATIONAL, 3 ],
-                               'ReferenceBlackWhite' => [ Exif::RATIONAL, 6 ], # Pair of black and white reference values
+                               'YCbCrCoefficients' => [ self::RATIONAL, 3 ],
+                               'ReferenceBlackWhite' => [ self::RATIONAL, 6 ], # Pair of black and white reference values
 
                                # Other tags
-                               'DateTime' => Exif::ASCII, # File change date and time
-                               'ImageDescription' => Exif::ASCII, # Image title
-                               'Make' => Exif::ASCII, # Image input equipment manufacturer
-                               'Model' => Exif::ASCII, # Image input equipment model
-                               'Software' => Exif::ASCII, # Software used
-                               'Artist' => Exif::ASCII, # Person who created the image
-                               'Copyright' => Exif::ASCII, # Copyright holder
+                               'DateTime' => self::ASCII, # File change date and time
+                               'ImageDescription' => self::ASCII, # Image title
+                               'Make' => self::ASCII, # Image input equipment manufacturer
+                               'Model' => self::ASCII, # Image input equipment model
+                               'Software' => self::ASCII, # Software used
+                               'Artist' => self::ASCII, # Person who created the image
+                               'Copyright' => self::ASCII, # Copyright holder
                        ],
 
                        # Exif IFD Attribute Information (p30-31)
                        'EXIF' => [
                                # @todo NOTE: Nonexistence of this field is taken to mean nonconformance
                                # to the Exif 2.1 AND 2.2 standards
-                               'ExifVersion' => Exif::UNDEFINED, # Exif version
-                               'FlashPixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
+                               'ExifVersion' => self::UNDEFINED, # Exif version
+                               'FlashPixVersion' => self::UNDEFINED, # Supported Flashpix version #p32
 
                                # Tags relating to Image Data Characteristics
-                               'ColorSpace' => Exif::SHORT, # Color space information #p32
+                               'ColorSpace' => self::SHORT, # Color space information #p32
 
                                # Tags relating to image configuration
-                               'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
-                               'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
-                               'PixelYDimension' => Exif::SHORT_OR_LONG, # Valid image height
-                               'PixelXDimension' => Exif::SHORT_OR_LONG, # Valid image width
+                               'ComponentsConfiguration' => self::UNDEFINED, # Meaning of each component #p33
+                               'CompressedBitsPerPixel' => self::RATIONAL, # Image compression mode
+                               'PixelYDimension' => self::SHORT_OR_LONG, # Valid image height
+                               'PixelXDimension' => self::SHORT_OR_LONG, # Valid image width
 
                                # Tags relating to related user information
-                               'MakerNote' => Exif::IGNORE, # Manufacturer notes
-                               'UserComment' => Exif::UNDEFINED, # User comments #p34
+                               'MakerNote' => self::IGNORE, # Manufacturer notes
+                               'UserComment' => self::UNDEFINED, # User comments #p34
 
                                # Tags relating to related file information
-                               'RelatedSoundFile' => Exif::ASCII, # Related audio file
+                               'RelatedSoundFile' => self::ASCII, # Related audio file
 
                                # Tags relating to date and time
-                               'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36
-                               'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation
-                               'SubSecTime' => Exif::ASCII, # DateTime subseconds
-                               'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds
-                               'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds
+                               'DateTimeOriginal' => self::ASCII, # Date and time of original data generation #p36
+                               'DateTimeDigitized' => self::ASCII, # Date and time of original data generation
+                               'SubSecTime' => self::ASCII, # DateTime subseconds
+                               'SubSecTimeOriginal' => self::ASCII, # DateTimeOriginal subseconds
+                               'SubSecTimeDigitized' => self::ASCII, # DateTimeDigitized subseconds
 
                                # Tags relating to picture-taking conditions (p31)
-                               'ExposureTime' => Exif::RATIONAL, # Exposure time
-                               'FNumber' => Exif::RATIONAL, # F Number
-                               'ExposureProgram' => Exif::SHORT, # Exposure Program #p38
-                               'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity
-                               'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating
-                               'OECF' => Exif::IGNORE,
+                               'ExposureTime' => self::RATIONAL, # Exposure time
+                               'FNumber' => self::RATIONAL, # F Number
+                               'ExposureProgram' => self::SHORT, # Exposure Program #p38
+                               'SpectralSensitivity' => self::ASCII, # Spectral sensitivity
+                               'ISOSpeedRatings' => self::SHORT, # ISO speed rating
+                               'OECF' => self::IGNORE,
                                # Optoelectronic conversion factor. Note: We don't have support for this atm.
-                               'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed
-                               'ApertureValue' => Exif::RATIONAL, # Aperture
-                               'BrightnessValue' => Exif::SRATIONAL, # Brightness
-                               'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias
-                               'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture
-                               'SubjectDistance' => Exif::RATIONAL, # Subject distance
-                               'MeteringMode' => Exif::SHORT, # Metering mode #p40
-                               'LightSource' => Exif::SHORT, # Light source #p40-41
-                               'Flash' => Exif::SHORT, # Flash #p41-42
-                               'FocalLength' => Exif::RATIONAL, # Lens focal length
-                               'SubjectArea' => [ Exif::SHORT, 4 ], # Subject area
-                               'FlashEnergy' => Exif::RATIONAL, # Flash energy
-                               'SpatialFrequencyResponse' => Exif::IGNORE, # Spatial frequency response. Not supported atm.
-                               'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
-                               'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
-                               'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
-                               'SubjectLocation' => [ Exif::SHORT, 2 ], # Subject location
-                               'ExposureIndex' => Exif::RATIONAL, # Exposure index
-                               'SensingMethod' => Exif::SHORT, # Sensing method #p46
-                               'FileSource' => Exif::UNDEFINED, # File source #p47
-                               'SceneType' => Exif::UNDEFINED, # Scene type #p47
-                               'CFAPattern' => Exif::IGNORE, # CFA pattern. not supported atm.
-                               'CustomRendered' => Exif::SHORT, # Custom image processing #p48
-                               'ExposureMode' => Exif::SHORT, # Exposure mode #p48
-                               'WhiteBalance' => Exif::SHORT, # White Balance #p49
-                               'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
-                               'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
-                               'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
-                               'GainControl' => Exif::SHORT, # Scene control #p49-50
-                               'Contrast' => Exif::SHORT, # Contrast #p50
-                               'Saturation' => Exif::SHORT, # Saturation #p50
-                               'Sharpness' => Exif::SHORT, # Sharpness #p50
-                               'DeviceSettingDescription' => Exif::IGNORE,
+                               'ShutterSpeedValue' => self::SRATIONAL, # Shutter speed
+                               'ApertureValue' => self::RATIONAL, # Aperture
+                               'BrightnessValue' => self::SRATIONAL, # Brightness
+                               'ExposureBiasValue' => self::SRATIONAL, # Exposure bias
+                               'MaxApertureValue' => self::RATIONAL, # Maximum land aperture
+                               'SubjectDistance' => self::RATIONAL, # Subject distance
+                               'MeteringMode' => self::SHORT, # Metering mode #p40
+                               'LightSource' => self::SHORT, # Light source #p40-41
+                               'Flash' => self::SHORT, # Flash #p41-42
+                               'FocalLength' => self::RATIONAL, # Lens focal length
+                               'SubjectArea' => [ self::SHORT, 4 ], # Subject area
+                               'FlashEnergy' => self::RATIONAL, # Flash energy
+                               'SpatialFrequencyResponse' => self::IGNORE, # Spatial frequency response. Not supported atm.
+                               'FocalPlaneXResolution' => self::RATIONAL, # Focal plane X resolution
+                               'FocalPlaneYResolution' => self::RATIONAL, # Focal plane Y resolution
+                               'FocalPlaneResolutionUnit' => self::SHORT, # Focal plane resolution unit #p46
+                               'SubjectLocation' => [ self::SHORT, 2 ], # Subject location
+                               'ExposureIndex' => self::RATIONAL, # Exposure index
+                               'SensingMethod' => self::SHORT, # Sensing method #p46
+                               'FileSource' => self::UNDEFINED, # File source #p47
+                               'SceneType' => self::UNDEFINED, # Scene type #p47
+                               'CFAPattern' => self::IGNORE, # CFA pattern. not supported atm.
+                               'CustomRendered' => self::SHORT, # Custom image processing #p48
+                               'ExposureMode' => self::SHORT, # Exposure mode #p48
+                               'WhiteBalance' => self::SHORT, # White Balance #p49
+                               'DigitalZoomRatio' => self::RATIONAL, # Digital zoom ration
+                               'FocalLengthIn35mmFilm' => self::SHORT, # Focal length in 35 mm film
+                               'SceneCaptureType' => self::SHORT, # Scene capture type #p49
+                               'GainControl' => self::SHORT, # Scene control #p49-50
+                               'Contrast' => self::SHORT, # Contrast #p50
+                               'Saturation' => self::SHORT, # Saturation #p50
+                               'Sharpness' => self::SHORT, # Sharpness #p50
+                               'DeviceSettingDescription' => self::IGNORE,
                                # Device settings description. This could maybe be supported. Need to find an
                                # example file that uses this to see if it has stuff of interest in it.
-                               'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51
+                               'SubjectDistanceRange' => self::SHORT, # Subject distance range #p51
 
-                               'ImageUniqueID' => Exif::ASCII, # Unique image ID
+                               'ImageUniqueID' => self::ASCII, # Unique image ID
                        ],
 
                        # GPS Attribute Information (p52)
                        'GPS' => [
-                               'GPSVersion' => Exif::UNDEFINED,
+                               'GPSVersion' => self::UNDEFINED,
                                # Should be an array of 4 Exif::BYTE's. However php treats it as an undefined
                                # Note exif standard calls this GPSVersionID, but php doesn't like the id suffix
-                               'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
-                               'GPSLatitude' => [ Exif::RATIONAL, 3 ], # Latitude
-                               'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
-                               'GPSLongitude' => [ Exif::RATIONAL, 3 ], # Longitude
-                               'GPSAltitudeRef' => Exif::UNDEFINED,
+                               'GPSLatitudeRef' => self::ASCII, # North or South Latitude #p52-53
+                               'GPSLatitude' => [ self::RATIONAL, 3 ], # Latitude
+                               'GPSLongitudeRef' => self::ASCII, # East or West Longitude #p53
+                               'GPSLongitude' => [ self::RATIONAL, 3 ], # Longitude
+                               'GPSAltitudeRef' => self::UNDEFINED,
                                # Altitude reference. Note, the exif standard says this should be an EXIF::Byte,
                                # but php seems to disagree.
-                               'GPSAltitude' => Exif::RATIONAL, # Altitude
-                               'GPSTimeStamp' => [ Exif::RATIONAL, 3 ], # GPS time (atomic clock)
-                               'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
-                               'GPSStatus' => Exif::ASCII, # Receiver status #p54
-                               'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
-                               'GPSDOP' => Exif::RATIONAL, # Measurement precision
-                               'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55
-                               'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver
-                               'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55
-                               'GPSTrack' => Exif::RATIONAL, # Direction of movement
-                               'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56
-                               'GPSImgDirection' => Exif::RATIONAL, # Direction of image
-                               'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used
-                               'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56
-                               'GPSDestLatitude' => [ Exif::RATIONAL, 3 ], # Latitude destination
-                               'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57
-                               'GPSDestLongitude' => [ Exif::RATIONAL, 3 ], # Longitude of destination
-                               'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57
-                               'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination
-                               'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58
-                               'GPSDestDistance' => Exif::RATIONAL, # Distance to destination
-                               'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method
-                               'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area
-                               'GPSDateStamp' => Exif::ASCII, # GPS date
-                               'GPSDifferential' => Exif::SHORT, # GPS differential correction
+                               'GPSAltitude' => self::RATIONAL, # Altitude
+                               'GPSTimeStamp' => [ self::RATIONAL, 3 ], # GPS time (atomic clock)
+                               'GPSSatellites' => self::ASCII, # Satellites used for measurement
+                               'GPSStatus' => self::ASCII, # Receiver status #p54
+                               'GPSMeasureMode' => self::ASCII, # Measurement mode #p54-55
+                               'GPSDOP' => self::RATIONAL, # Measurement precision
+                               'GPSSpeedRef' => self::ASCII, # Speed unit #p55
+                               'GPSSpeed' => self::RATIONAL, # Speed of GPS receiver
+                               'GPSTrackRef' => self::ASCII, # Reference for direction of movement #p55
+                               'GPSTrack' => self::RATIONAL, # Direction of movement
+                               'GPSImgDirectionRef' => self::ASCII, # Reference for direction of image #p56
+                               'GPSImgDirection' => self::RATIONAL, # Direction of image
+                               'GPSMapDatum' => self::ASCII, # Geodetic survey data used
+                               'GPSDestLatitudeRef' => self::ASCII, # Reference for latitude of destination #p56
+                               'GPSDestLatitude' => [ self::RATIONAL, 3 ], # Latitude destination
+                               'GPSDestLongitudeRef' => self::ASCII, # Reference for longitude of destination #p57
+                               'GPSDestLongitude' => [ self::RATIONAL, 3 ], # Longitude of destination
+                               'GPSDestBearingRef' => self::ASCII, # Reference for bearing of destination #p57
+                               'GPSDestBearing' => self::RATIONAL, # Bearing of destination
+                               'GPSDestDistanceRef' => self::ASCII, # Reference for distance to destination #p57-58
+                               'GPSDestDistance' => self::RATIONAL, # Distance to destination
+                               'GPSProcessingMethod' => self::UNDEFINED, # Name of GPS processing method
+                               'GPSAreaInformation' => self::UNDEFINED, # Name of GPS area
+                               'GPSDateStamp' => self::ASCII, # GPS date
+                               'GPSDifferential' => self::SHORT, # GPS differential correction
                        ],
                ];
 
@@ -761,43 +759,43 @@ class Exif {
                }
                // Does not work if not typecast
                switch ( (string)$etype ) {
-                       case (string)Exif::BYTE:
+                       case (string)self::BYTE:
                                $this->debug( $val, __FUNCTION__, $debug );
 
                                return $this->isByte( $val );
-                       case (string)Exif::ASCII:
+                       case (string)self::ASCII:
                                $this->debug( $val, __FUNCTION__, $debug );
 
                                return $this->isASCII( $val );
-                       case (string)Exif::SHORT:
+                       case (string)self::SHORT:
                                $this->debug( $val, __FUNCTION__, $debug );
 
                                return $this->isShort( $val );
-                       case (string)Exif::LONG:
+                       case (string)self::LONG:
                                $this->debug( $val, __FUNCTION__, $debug );
 
                                return $this->isLong( $val );
-                       case (string)Exif::RATIONAL:
+                       case (string)self::RATIONAL:
                                $this->debug( $val, __FUNCTION__, $debug );
 
                                return $this->isRational( $val );
-                       case (string)Exif::SHORT_OR_LONG:
+                       case (string)self::SHORT_OR_LONG:
                                $this->debug( $val, __FUNCTION__, $debug );
 
                                return $this->isShort( $val ) || $this->isLong( $val );
-                       case (string)Exif::UNDEFINED:
+                       case (string)self::UNDEFINED:
                                $this->debug( $val, __FUNCTION__, $debug );
 
                                return $this->isUndefined( $val );
-                       case (string)Exif::SLONG:
+                       case (string)self::SLONG:
                                $this->debug( $val, __FUNCTION__, $debug );
 
                                return $this->isSlong( $val );
-                       case (string)Exif::SRATIONAL:
+                       case (string)self::SRATIONAL:
                                $this->debug( $val, __FUNCTION__, $debug );
 
                                return $this->isSrational( $val );
-                       case (string)Exif::IGNORE:
+                       case (string)self::IGNORE:
                                $this->debug( $val, __FUNCTION__, $debug );
 
                                return false;
index 2cf4d23..9b22cbe 100644 (file)
@@ -58,8 +58,6 @@ class SVGReader {
        private $languagePrefixes = [];
 
        /**
-        * Constructor
-        *
         * Creates an SVGReader drawing from the source provided
         * @param string $source URI from which to read
         * @throws MWException|Exception
index d13fb3c..e2cf2cf 100644 (file)
@@ -1594,7 +1594,7 @@ class Article implements Page {
                        [ 'delete', $this->getTitle()->getPrefixedText() ] )
                ) {
                        # Flag to hide all contents of the archived revisions
-                       $suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
+                       $suppress = $request->getCheck( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
 
                        $this->doDelete( $reason, $suppress );
 
@@ -1669,7 +1669,6 @@ class Article implements Page {
                $title = $this->getTitle();
                $ctx = $this->getContext();
                $outputPage = $ctx->getOutput();
-               $useMediaWikiUIEverywhere = $ctx->getConfig()->get( 'UseMediaWikiUIEverywhere' );
                $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
                $outputPage->addBacklinkSubtitle( $title );
                $outputPage->setRobotPolicy( 'noindex,nofollow' );
@@ -1692,14 +1691,6 @@ class Article implements Page {
                Hooks::run( 'ArticleConfirmDelete', [ $this, $outputPage, &$reason ] );
 
                $user = $this->getContext()->getUser();
-               if ( $user->isAllowed( 'suppressrevision' ) ) {
-                       $suppress = Html::openElement( 'div', [ 'id' => 'wpDeleteSuppressRow' ] ) .
-                               Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(),
-                                       'wpSuppress', 'wpSuppress', false, [ 'tabindex' => '4' ] ) .
-                               Html::closeElement( 'div' );
-               } else {
-                       $suppress = '';
-               }
                $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
 
                $outputPage->enableOOUI();
@@ -1770,6 +1761,21 @@ class Article implements Page {
                        );
                }
 
+               if ( $user->isAllowed( 'suppressrevision' ) ) {
+                       $fields[] = new OOUI\FieldLayout(
+                               new OOUI\CheckboxInputWidget( [
+                                       'name' => 'wpSuppress',
+                                       'inputId' => 'wpSuppress',
+                                       'tabIndex' => 4,
+                               ] ),
+                               [
+                                       'label' => $ctx->msg( 'revdelete-suppress' )->text(),
+                                       'align' => 'inline',
+                                       'infusable' => true,
+                               ]
+                       );
+               }
+
                $fields[] = new OOUI\FieldLayout(
                        new OOUI\ButtonInputWidget( [
                                'name' => 'wpConfirmB',
index 20fb9be..c05ba45 100644 (file)
@@ -50,7 +50,7 @@ class WikiPage implements Page, IDBAccessObject {
        public $mLatest = false;             // !< Integer (false means "not loaded")
        /**@}}*/
 
-       /** @var stdClass Map of cache fields (text, parser output, ect) for a proposed/new edit */
+       /** @var PreparedEdit Map of cache fields (text, parser output, ect) for a proposed/new edit */
        public $mPreparedEdit = false;
 
        /**
@@ -782,7 +782,7 @@ class WikiPage implements Page, IDBAccessObject {
         * Determine whether a page would be suitable for being counted as an
         * article in the site_stats table based on the title & its content
         *
-        * @param object|bool $editInfo (false): object returned by prepareTextForEdit(),
+        * @param PreparedEdit|bool $editInfo (false): object returned by prepareTextForEdit(),
         *   if false, the current database state will be used
         * @return bool
         */
@@ -1607,7 +1607,7 @@ class WikiPage implements Page, IDBAccessObject {
                $meta = [
                        'bot' => ( $flags & EDIT_FORCE_BOT ),
                        'minor' => ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' ),
-                       'serialized' => $editInfo->pst,
+                       'serialized' => $pstContent->serialize( $serialFormat ),
                        'serialFormat' => $serialFormat,
                        'baseRevId' => $baseRevId,
                        'oldRevision' => $old_revision,
@@ -1961,7 +1961,9 @@ class WikiPage implements Page, IDBAccessObject {
 
        /**
         * Prepare content which is about to be saved.
-        * Returns a stdClass with source, pst and output members
+        *
+        * Prior to 1.30, this returned a stdClass object with the same class
+        * members.
         *
         * @param Content $content
         * @param Revision|int|null $revision Revision object. For backwards compatibility, a
@@ -2971,7 +2973,7 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                // Clear caches
-               WikiPage::onArticleDelete( $this->mTitle );
+               self::onArticleDelete( $this->mTitle );
                ResourceLoaderWikiModule::invalidateModuleCache(
                        $this->mTitle, $revision, null, wfWikiID()
                );
index 0b867ef..6620c47 100644 (file)
@@ -737,6 +737,6 @@ abstract class IndexPager extends ContextSource implements Pager {
         * @return bool
         */
        protected function getDefaultDirections() {
-               return IndexPager::DIR_ASCENDING;
+               return self::DIR_ASCENDING;
        }
 }
index 5da2546..b035b02 100644 (file)
@@ -227,7 +227,7 @@ class Parser {
         * @var string Deprecated accessor for the strip marker prefix.
         * @deprecated since 1.26; use Parser::MARKER_PREFIX instead.
         */
-       public $mUniqPrefix = Parser::MARKER_PREFIX;
+       public $mUniqPrefix = self::MARKER_PREFIX;
 
        /**
         * @var array Array with the language name of each language link (i.e. the
@@ -1295,7 +1295,7 @@ class Parser {
                        if ( !$frame->depth ) {
                                $flag = 0;
                        } else {
-                               $flag = Parser::PTD_FOR_INCLUSION;
+                               $flag = self::PTD_FOR_INCLUSION;
                        }
                        $dom = $this->preprocessToDom( $text, $flag );
                        $text = $frame->expand( $dom );
index 96a4368..73a9927 100644 (file)
@@ -927,7 +927,6 @@ class ParserOptions {
        }
 
        /**
-        * Constructor
         * @warning For interaction with the parser cache, use
         *  WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
         *  ParserOptions::newCanonical() instead.
index 10ac192..cfb0c3e 100644 (file)
@@ -253,7 +253,7 @@ class ParserOutput extends CacheTime {
                $text = $this->mText;
                if ( $this->mEditSectionTokens ) {
                        $text = preg_replace_callback(
-                               ParserOutput::EDITSECTION_REGEX,
+                               self::EDITSECTION_REGEX,
                                function ( $m ) {
                                        global $wgOut, $wgLang;
                                        $editsectionPage = Title::newFromText( htmlspecialchars_decode( $m[1] ) );
@@ -274,7 +274,7 @@ class ParserOutput extends CacheTime {
                                $text
                        );
                } else {
-                       $text = preg_replace( ParserOutput::EDITSECTION_REGEX, '', $text );
+                       $text = preg_replace( self::EDITSECTION_REGEX, '', $text );
                }
 
                // If you have an old cached version of this class - sorry, you can't disable the TOC
index ddf084e..20b0780 100644 (file)
@@ -31,7 +31,6 @@ abstract class ProfilerOutput {
        protected $params = [];
 
        /**
-        * Constructor
         * @param Profiler $collector The actual profiler
         * @param array $params Configuration array, passed down from $wgProfiler
         */
index 8553116..2f29200 100644 (file)
@@ -178,7 +178,7 @@ class ResourceLoader implements LoggerAwareInterface {
         * @return string Filtered data, or a comment containing an error message
         */
        public static function filter( $filter, $data, array $options = [] ) {
-               if ( strpos( $data, ResourceLoader::FILTER_NOMIN ) !== false ) {
+               if ( strpos( $data, self::FILTER_NOMIN ) !== false ) {
                        return $data;
                }
 
@@ -1079,7 +1079,7 @@ MESSAGE;
                                                                // mw.loader.implement will use globalEval if scripts is a string.
                                                                // Minify manually here, because general response minification is
                                                                // not effective due it being a string literal, not a function.
-                                                               if ( !ResourceLoader::inDebugMode() ) {
+                                                               if ( !self::inDebugMode() ) {
                                                                        $scripts = self::filter( 'minify-js', $scripts ); // T107377
                                                                }
                                                        } else {
@@ -1139,7 +1139,7 @@ MESSAGE;
                } else {
                        if ( count( $states ) ) {
                                $this->errors[] = 'Problematic modules: ' .
-                                       FormatJson::encode( $states, ResourceLoader::inDebugMode() );
+                                       FormatJson::encode( $states, self::inDebugMode() );
                        }
                }
 
@@ -1214,7 +1214,7 @@ MESSAGE;
                ];
                self::trimArray( $module );
 
-               return Xml::encodeJsCall( 'mw.loader.implement', $module, ResourceLoader::inDebugMode() );
+               return Xml::encodeJsCall( 'mw.loader.implement', $module, self::inDebugMode() );
        }
 
        /**
@@ -1228,7 +1228,7 @@ MESSAGE;
                return Xml::encodeJsCall(
                        'mw.messages.set',
                        [ (object)$messages ],
-                       ResourceLoader::inDebugMode()
+                       self::inDebugMode()
                );
        }
 
@@ -1285,13 +1285,13 @@ MESSAGE;
                        return Xml::encodeJsCall(
                                'mw.loader.state',
                                [ $name ],
-                               ResourceLoader::inDebugMode()
+                               self::inDebugMode()
                        );
                } else {
                        return Xml::encodeJsCall(
                                'mw.loader.state',
                                [ $name, $state ],
-                               ResourceLoader::inDebugMode()
+                               self::inDebugMode()
                        );
                }
        }
@@ -1317,7 +1317,7 @@ MESSAGE;
                return Xml::encodeJsCall(
                        "( function ( name, version, dependencies, group, source ) {\n\t$script\n} )",
                        [ $name, $version, $dependencies, $group, $source ],
-                       ResourceLoader::inDebugMode()
+                       self::inDebugMode()
                );
        }
 
@@ -1409,7 +1409,7 @@ MESSAGE;
                        return Xml::encodeJsCall(
                                'mw.loader.register',
                                [ $name ],
-                               ResourceLoader::inDebugMode()
+                               self::inDebugMode()
                        );
                } else {
                        $registration = [ $name, $version, $dependencies, $group, $source, $skip ];
@@ -1417,7 +1417,7 @@ MESSAGE;
                        return Xml::encodeJsCall(
                                'mw.loader.register',
                                $registration,
-                               ResourceLoader::inDebugMode()
+                               self::inDebugMode()
                        );
                }
        }
@@ -1441,13 +1441,13 @@ MESSAGE;
                        return Xml::encodeJsCall(
                                'mw.loader.addSource',
                                [ $id ],
-                               ResourceLoader::inDebugMode()
+                               self::inDebugMode()
                        );
                } else {
                        return Xml::encodeJsCall(
                                'mw.loader.addSource',
                                [ $id, $loadUrl ],
-                               ResourceLoader::inDebugMode()
+                               self::inDebugMode()
                        );
                }
        }
@@ -1494,7 +1494,7 @@ MESSAGE;
                return Xml::encodeJsCall(
                        'mw.config.set',
                        [ $configuration ],
-                       ResourceLoader::inDebugMode()
+                       self::inDebugMode()
                );
        }
 
index 197ac51..06f9841 100644 (file)
@@ -149,9 +149,7 @@ class ResourceLoaderClientHtml {
                                continue;
                        }
 
-                       $group = $module->getGroup();
-
-                       if ( $group === 'private' ) {
+                       if ( $module->shouldEmbedModule( $this->context ) ) {
                                // Embed via mw.loader.implement per T36907.
                                $data['embed']['general'][] = $name;
                                // Avoid duplicate request from mw.loader
@@ -186,7 +184,7 @@ class ResourceLoaderClientHtml {
                                // Avoid needless request for empty module
                                $data['states'][$name] = 'ready';
                        } else {
-                               if ( $group === 'private' ) {
+                               if ( $module->shouldEmbedModule( $this->context ) ) {
                                        // Embed via style element
                                        $data['embed']['styles'][] = $name;
                                        // Avoid duplicate request from mw.loader
@@ -392,62 +390,75 @@ class ResourceLoaderClientHtml {
                foreach ( $sortedModules as $source => $groups ) {
                        foreach ( $groups as $group => $grpModules ) {
                                $context = self::makeContext( $mainContext, $group, $only, $extraQuery );
-                               $context->setModules( array_keys( $grpModules ) );
-
-                               if ( $group === 'private' ) {
-                                       // Decide whether to use style or script element
-                                       if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
-                                               $chunks[] = Html::inlineStyle(
-                                                       $rl->makeModuleResponse( $context, $grpModules )
-                                               );
-                                       } else {
-                                               $chunks[] = ResourceLoader::makeInlineScript(
-                                                       $rl->makeModuleResponse( $context, $grpModules )
-                                               );
-                                       }
-                                       continue;
-                               }
-
-                               // See if we have one or more raw modules
-                               $isRaw = false;
-                               foreach ( $grpModules as $key => $module ) {
-                                       $isRaw |= $module->isRaw();
-                               }
 
-                               // Special handling for the user group; because users might change their stuff
-                               // on-wiki like user pages, or user preferences; we need to find the highest
-                               // timestamp of these user-changeable modules so we can ensure cache misses on change
-                               // This should NOT be done for the site group (T29564) because anons get that too
-                               // and we shouldn't be putting timestamps in CDN-cached HTML
-                               if ( $group === 'user' ) {
-                                       // Must setModules() before makeVersionQuery()
-                                       $context->setVersion( $rl->makeVersionQuery( $context ) );
+                               // Separate sets of linked and embedded modules while preserving order
+                               $moduleSets = [];
+                               $idx = -1;
+                               foreach ( $grpModules as $name => $module ) {
+                                       $shouldEmbed = $module->shouldEmbedModule( $context );
+                                       if ( !$moduleSets || $moduleSets[$idx][0] !== $shouldEmbed ) {
+                                               $moduleSets[++$idx] = [ $shouldEmbed, [] ];
+                                       }
+                                       $moduleSets[$idx][1][$name] = $module;
                                }
 
-                               $url = $rl->createLoaderURL( $source, $context, $extraQuery );
-
-                               // Decide whether to use 'style' or 'script' element
-                               if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
-                                       $chunk = Html::linkedStyle( $url );
-                               } else {
-                                       if ( $context->getRaw() || $isRaw ) {
-                                               $chunk = Html::element( 'script', [
-                                                       // In SpecialJavaScriptTest, QUnit must load synchronous
-                                                       'async' => !isset( $extraQuery['sync'] ),
-                                                       'src' => $url
-                                               ] );
+                               // Link/embed each set
+                               foreach ( $moduleSets as list( $embed, $moduleSet ) ) {
+                                       $context->setModules( array_keys( $moduleSet ) );
+                                       if ( $embed ) {
+                                               // Decide whether to use style or script element
+                                               if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
+                                                       $chunks[] = Html::inlineStyle(
+                                                               $rl->makeModuleResponse( $context, $moduleSet )
+                                                       );
+                                               } else {
+                                                       $chunks[] = ResourceLoader::makeInlineScript(
+                                                               $rl->makeModuleResponse( $context, $moduleSet )
+                                                       );
+                                               }
                                        } else {
-                                               $chunk = ResourceLoader::makeInlineScript(
-                                                       Xml::encodeJsCall( 'mw.loader.load', [ $url ] )
-                                               );
+                                               // See if we have one or more raw modules
+                                               $isRaw = false;
+                                               foreach ( $moduleSet as $key => $module ) {
+                                                       $isRaw |= $module->isRaw();
+                                               }
+
+                                               // Special handling for the user group; because users might change their stuff
+                                               // on-wiki like user pages, or user preferences; we need to find the highest
+                                               // timestamp of these user-changeable modules so we can ensure cache misses on change
+                                               // This should NOT be done for the site group (T29564) because anons get that too
+                                               // and we shouldn't be putting timestamps in CDN-cached HTML
+                                               if ( $group === 'user' ) {
+                                                       // Must setModules() before makeVersionQuery()
+                                                       $context->setVersion( $rl->makeVersionQuery( $context ) );
+                                               }
+
+                                               $url = $rl->createLoaderURL( $source, $context, $extraQuery );
+
+                                               // Decide whether to use 'style' or 'script' element
+                                               if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
+                                                       $chunk = Html::linkedStyle( $url );
+                                               } else {
+                                                       if ( $context->getRaw() || $isRaw ) {
+                                                               $chunk = Html::element( 'script', [
+                                                                       // In SpecialJavaScriptTest, QUnit must load synchronous
+                                                                       'async' => !isset( $extraQuery['sync'] ),
+                                                                       'src' => $url
+                                                               ] );
+                                                       } else {
+                                                               $chunk = ResourceLoader::makeInlineScript(
+                                                                       Xml::encodeJsCall( 'mw.loader.load', [ $url ] )
+                                                               );
+                                                       }
+                                               }
+
+                                               if ( $group == 'noscript' ) {
+                                                       $chunks[] = Html::rawElement( 'noscript', [], $chunk );
+                                               } else {
+                                                       $chunks[] = $chunk;
+                                               }
                                        }
                                }
-
-                               if ( $group == 'noscript' ) {
-                                       $chunks[] = Html::rawElement( 'noscript', [], $chunk );
-                               } else {
-                                       $chunks[] = $chunk;
-                               }
                        }
                }
 
index 1c767fa..1608901 100644 (file)
@@ -918,6 +918,20 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
                return false;
        }
 
+       /**
+        * Check whether this module should be embeded rather than linked
+        *
+        * Modules returning true here will be embedded rather than loaded by
+        * ResourceLoaderClientHtml.
+        *
+        * @since 1.30
+        * @param ResourceLoaderContext $context
+        * @return bool
+        */
+       public function shouldEmbedModule( ResourceLoaderContext $context ) {
+               return $this->getGroup() === 'private';
+       }
+
        /** @var JSParser Lazy-initialized; use self::javaScriptParser() */
        private static $jsParser;
        private static $parseCacheVersion = 1;
index 1d7a4a3..643c2c1 100644 (file)
@@ -35,7 +35,6 @@ class SearchDatabase extends SearchEngine {
        protected $db;
 
        /**
-        * Constructor
         * @param IDatabase $db The database to search from
         */
        public function __construct( IDatabase $db = null ) {
index a8f9d0c..40905a5 100644 (file)
@@ -95,7 +95,7 @@ abstract class Skin extends ContextSource {
        static function normalizeKey( $key ) {
                global $wgDefaultSkin, $wgFallbackSkin;
 
-               $skinNames = Skin::getSkinNames();
+               $skinNames = self::getSkinNames();
 
                // Make keys lowercase for case-insensitive matching.
                $skinNames = array_change_key_case( $skinNames, CASE_LOWER );
index 67c14d8..4c3ca54 100644 (file)
@@ -383,7 +383,7 @@ class SpecialPage implements MessageLocalizer {
                        return true;
                } elseif ( $securityStatus === AuthManager::SEC_REAUTH ) {
                        $request = $this->getRequest();
-                       $title = SpecialPage::getTitleFor( 'Userlogin' );
+                       $title = self::getTitleFor( 'Userlogin' );
                        $query = [
                                'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
                                'returntoquery' => wfArrayToCgi( array_diff_key( $request->getQueryValues(),
index e7c9423..9028787 100644 (file)
@@ -28,9 +28,6 @@
  */
 class SpecialActiveUsers extends SpecialPage {
 
-       /**
-        * Constructor
-        */
        public function __construct() {
                parent::__construct( 'Activeusers' );
        }
index 4056709..9e66447 100644 (file)
@@ -33,9 +33,6 @@ class SpecialAllMessages extends SpecialPage {
         */
        protected $table;
 
-       /**
-        * Constructor
-        */
        public function __construct() {
                parent::__construct( 'Allmessages' );
        }
index 17f6cca..4d84e31 100644 (file)
@@ -44,8 +44,6 @@ class SpecialAllPages extends IncludableSpecialPage {
        protected $nsfromMsg = 'allpagesfrom';
 
        /**
-        * Constructor
-        *
         * @param string $name Name of the special page, as seen in links and URLs (default: 'Allpages')
         */
        function __construct( $name = 'Allpages' ) {
index a2930fc..a827e89 100644 (file)
@@ -46,9 +46,6 @@ class SpecialImport extends SpecialPage {
        private $pageLinkDepth;
        private $importSources;
 
-       /**
-        * Constructor
-        */
        public function __construct() {
                parent::__construct( 'Import', 'import' );
        }
index 1a8dccf..dee2968 100644 (file)
@@ -29,9 +29,7 @@
  * @ingroup SpecialPage
  */
 class SpecialListUsers extends IncludableSpecialPage {
-       /**
-        * Constructor
-        */
+
        public function __construct() {
                parent::__construct( 'Listusers' );
        }
index 15bbffd..a05900b 100644 (file)
@@ -315,7 +315,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                $opts = parent::getDefaultOptions();
                $user = $this->getUser();
 
-               $opts->add( 'days', $user->getIntOption( 'rcdays' ) );
+               $opts->add( 'days', $user->getIntOption( 'rcdays' ), FormOptions::FLOAT );
                $opts->add( 'limit', $user->getIntOption( 'rclimit' ) );
                $opts->add( 'from', '' );
 
@@ -359,7 +359,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                        if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
                                $opts['limit'] = $m[1];
                        }
-                       if ( preg_match( '/^days=(\d+)$/', $bit, $m ) ) {
+                       if ( preg_match( '/^days=(\d+(?:\.\d+)?)$/', $bit, $m ) ) {
                                $opts['days'] = $m[1];
                        }
                        if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
@@ -388,7 +388,6 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
 
                // Calculate cutoff
                $cutoff_unixtime = time() - ( $opts['days'] * 86400 );
-               $cutoff_unixtime = $cutoff_unixtime - ( $cutoff_unixtime % 86400 );
                $cutoff = $dbr->timestamp( $cutoff_unixtime );
 
                $fromValid = preg_match( '/^[0-9]{14}$/', $opts['from'] );
@@ -669,6 +668,15 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                                [ 'class' => 'rcfilters-container' ]
                        );
 
+                       $loadingContainer = Html::rawElement(
+                               'div',
+                               [ 'class' => 'rcfilters-spinner' ],
+                               Html::element(
+                                       'div',
+                                       [ 'class' => 'rcfilters-spinner-bounce' ]
+                               )
+                       );
+
                        // Wrap both with rcfilters-head
                        $this->getOutput()->addHTML(
                                Html::rawElement(
@@ -677,6 +685,9 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                                        $rcfilterContainer . $rcoptions
                                )
                        );
+
+                       // Add spinner
+                       $this->getOutput()->addHTML( $loadingContainer );
                } else {
                        $this->getOutput()->addHTML( $rcoptions );
                }
index 451669c..4f29082 100644 (file)
@@ -130,9 +130,29 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
                        );
                }
 
-               if ( $includesRestrictedPages || $includesCachedPages ) {
-                       $out->wrapWikiMsg( "<h2 class=\"mw-specialpages-note-top\">$1</h2>", 'specialpages-note-top' );
-                       $out->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' );
+               // add legend
+               $notes = [];
+               if ( $includesRestrictedPages ) {
+                       $restricedMsg = $this->msg( 'specialpages-note-restricted' );
+                       if ( !$restricedMsg->isDisabled() ) {
+                               $notes[] = $restricedMsg->plain();
+                       }
+               }
+               if ( $includesCachedPages ) {
+                       $cachedMsg = $this->msg( 'specialpages-note-cached' );
+                       if ( !$cachedMsg->isDisabled() ) {
+                               $notes[] = $cachedMsg->plain();
+                       }
+               }
+               if ( $notes !== [] ) {
+                       $out->wrapWikiMsg(
+                               "<h2 class=\"mw-specialpages-note-top\">$1</h2>", 'specialpages-note-top'
+                       );
+                       $out->addWikiText(
+                               "<div class=\"mw-specialpages-notes\">\n" .
+                               implode( "\n", $notes ) .
+                               "\n</div>"
+                       );
                }
        }
 }
index 810f8fb..39904b0 100644 (file)
@@ -858,11 +858,12 @@ class SpecialUndelete extends SpecialPage {
                        $misc = Html::hidden( 'target', $this->mTarget );
                        $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
                        $history .= $misc;
-               }
-
-               $form->appendContent( new OOUI\HtmlSnippet( $history ) );
 
-               $out->addHTML( $form );
+                       $form->appendContent( new OOUI\HtmlSnippet( $history ) );
+                       $out->addHTML( $form );
+               } else {
+                       $out->addHTML( $history );
+               }
 
                return true;
        }
index 073e58d..4cdc78f 100644 (file)
@@ -33,7 +33,6 @@ use MediaWiki\MediaWikiServices;
  */
 class SpecialUpload extends SpecialPage {
        /**
-        * Constructor : initialise object
         * Get data POSTed through the form and assign them to the object
         * @param WebRequest $request Data posted.
         */
index 7fa03ba..10baadf 100644 (file)
@@ -275,17 +275,6 @@ class UsersPager extends AlphabeticPager {
                        $groupOptions[ $groupText ] = $group;
                }
 
-               $optionsDefault = [];
-               if ( $this->editsOnly ) {
-                       $optionsDefault[] = 'editsOnly';
-               }
-               if ( $this->creationSort ) {
-                       $optionsDefault[] = 'creationSort';
-               }
-               if ( $this->mDefaultDirection ) {
-                       $optionsDefault[] = 'desc';
-               }
-
                $formDescriptor = [
                        'user' => [
                                'class' => 'HTMLUserTextField',
@@ -300,14 +289,26 @@ class UsersPager extends AlphabeticPager {
                                'class' => 'HTMLSelectField',
                                'options' => $groupOptions,
                        ],
-                       'options' => [
-                               'class' => 'HTMLMultiSelectField',
-                               'options' => [
-                                       $this->msg( 'listusers-editsonly' )->text() => 'editsOnly',
-                                       $this->msg( 'listusers-creationsort' )->text() => 'creationSort',
-                                       $this->msg( 'listusers-desc' )->text() => 'desc'
-                               ],
-                               'default' => $optionsDefault
+                       'editsOnly' => [
+                               'type' => 'check',
+                               'label' => $this->msg( 'listusers-editsonly' )->text(),
+                               'name' => 'editsOnly',
+                               'id' => 'editsOnly',
+                               'value' => $this->editsOnly
+                       ],
+                       'creationSort' => [
+                               'type' => 'check',
+                               'label' => $this->msg( 'listusers-creationsort' )->text(),
+                               'name' => 'creationSort',
+                               'id' => 'creationSort',
+                               'value' => $this->creationSort
+                       ],
+                       'desc' => [
+                               'type' => 'check',
+                               'label' => $this->msg( 'listusers-desc' )->text(),
+                               'name' => 'desc',
+                               'id' => 'desc',
+                               'value' => $this->mDefaultDirection
                        ],
                        'limithiddenfield' => [
                                'class' => 'HTMLHiddenField',
index b0c12e4..4852ce5 100644 (file)
@@ -2156,7 +2156,7 @@ class Balancer {
                if (
                        $this->allowComments &&
                        !( $this->inRCDATA || $this->inRAWTEXT ) &&
-                       preg_match( Balancer::VALID_COMMENT_REGEX, $x, $regs, PREG_OFFSET_CAPTURE ) &&
+                       preg_match( self::VALID_COMMENT_REGEX, $x, $regs, PREG_OFFSET_CAPTURE ) &&
                        // verify EOF condition where necessary
                        ( $regs[4][1] < 0 || !$this->bitsIterator->valid() )
                ) {
index dbcf568..a797398 100644 (file)
@@ -81,8 +81,6 @@ class RemexCompatMunger implements TreeHandler {
        ];
 
        /**
-        * Constructor
-        *
         * @param Serializer $serializer
         */
        public function __construct( Serializer $serializer ) {
index 9a955fb..25625e7 100644 (file)
@@ -411,7 +411,7 @@ class BotPassword implements IDBAccessObject {
         * @return array|false
         */
        public static function canonicalizeLoginData( $username, $password ) {
-               $sep = BotPassword::getSeparator();
+               $sep = self::getSeparator();
                // the strlen check helps minimize the password information obtainable from timing
                if ( strlen( $password ) >= 32 && strpos( $username, $sep ) !== false ) {
                        // the separator is not valid in new usernames but might appear in legacy ones
index 7bf6be5..fa84c94 100644 (file)
@@ -603,7 +603,7 @@ class User implements IDBAccessObject {
                        ]
                );
 
-               return $id ? User::newFromId( $id ) : null;
+               return $id ? self::newFromId( $id ) : null;
        }
 
        /**
@@ -842,7 +842,7 @@ class User implements IDBAccessObject {
                global $wgContLang, $wgMaxNameChars;
 
                if ( $name == ''
-                       || User::isIP( $name )
+                       || self::isIP( $name )
                        || strpos( $name, '/' ) !== false
                        || strlen( $name ) > $wgMaxNameChars
                        || $name != $wgContLang->ucfirst( $name )
@@ -1109,17 +1109,17 @@ class User implements IDBAccessObject {
                        case false:
                                break;
                        case 'valid':
-                               if ( !User::isValidUserName( $name ) ) {
+                               if ( !self::isValidUserName( $name ) ) {
                                        $name = false;
                                }
                                break;
                        case 'usable':
-                               if ( !User::isUsableName( $name ) ) {
+                               if ( !self::isUsableName( $name ) ) {
                                        $name = false;
                                }
                                break;
                        case 'creatable':
-                               if ( !User::isCreatableName( $name ) ) {
+                               if ( !self::isCreatableName( $name ) ) {
                                        $name = false;
                                }
                                break;
@@ -1591,7 +1591,7 @@ class User implements IDBAccessObject {
                // since extensions may change the set of searchable namespaces depending
                // on user groups/permissions.
                foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
-                       $defOpt['searchNs' . $nsnum] = (boolean)$val;
+                       $defOpt['searchNs' . $nsnum] = (bool)$val;
                }
                $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
 
@@ -2212,7 +2212,7 @@ class User implements IDBAccessObject {
         * @return int The user's ID; 0 if the user is anonymous or nonexistent
         */
        public function getId() {
-               if ( $this->mId === null && $this->mName !== null && User::isIP( $this->mName ) ) {
+               if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
                        // Special case, we know the user is anonymous
                        return 0;
                } elseif ( !$this->isItemLoaded( 'id' ) ) {
@@ -4131,7 +4131,7 @@ class User implements IDBAccessObject {
                }
                $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] );
                if ( $dbw->affectedRows() ) {
-                       $newUser = User::newFromId( $dbw->insertId() );
+                       $newUser = self::newFromId( $dbw->insertId() );
                } else {
                        $newUser = null;
                }
@@ -5036,7 +5036,7 @@ class User implements IDBAccessObject {
                        // Do nothing
                } elseif ( $wgGroupsAddToSelf[$group] === true ) {
                        // No idea WHY this would be used, but it's there
-                       $groups['add-self'] = User::getAllGroups();
+                       $groups['add-self'] = self::getAllGroups();
                } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
                        $groups['add-self'] = $wgGroupsAddToSelf[$group];
                }
@@ -5044,7 +5044,7 @@ class User implements IDBAccessObject {
                if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
                        // Do nothing
                } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
-                       $groups['remove-self'] = User::getAllGroups();
+                       $groups['remove-self'] = self::getAllGroups();
                } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
                        $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
                }
@@ -5065,7 +5065,7 @@ class User implements IDBAccessObject {
                        // compatibility with old "userrights lets you change
                        // everything")
                        // Using array_merge to make the groups reindexed
-                       $all = array_merge( User::getAllGroups() );
+                       $all = array_merge( self::getAllGroups() );
                        return [
                                'add' => $all,
                                'remove' => $all,
@@ -5491,7 +5491,7 @@ class User implements IDBAccessObject {
                global $wgLang;
 
                $groups = [];
-               foreach ( User::getGroupsWithPermission( $permission ) as $group ) {
+               foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
                        $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
                }
 
index e51a8ed..8dfe00f 100644 (file)
@@ -38,8 +38,6 @@ class ConverterRule {
        public $mUnidtable = [];// array of the translation in each variant
 
        /**
-        * Constructor
-        *
         * @param string $text The text between -{ and }-
         * @param LanguageConverter $converter
         */
index 83dff65..12f26c3 100644 (file)
@@ -208,11 +208,11 @@ class Language {
         * @return Language
         */
        protected static function newFromCode( $code, $fallback = false ) {
-               if ( !Language::isValidCode( $code ) ) {
+               if ( !self::isValidCode( $code ) ) {
                        throw new MWException( "Invalid language code \"$code\"" );
                }
 
-               if ( !Language::isValidBuiltInCode( $code ) ) {
+               if ( !self::isValidBuiltInCode( $code ) ) {
                        // It's not possible to customise this code with class files, so
                        // just return a Language object. This is to support uselang= hacks.
                        $lang = new Language;
@@ -228,9 +228,9 @@ class Language {
                }
 
                // Keep trying the fallback list until we find an existing class
-               $fallbacks = Language::getFallbacksFor( $code );
+               $fallbacks = self::getFallbacksFor( $code );
                foreach ( $fallbacks as $fallbackCode ) {
-                       if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
+                       if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
                                throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
                        }
 
@@ -829,7 +829,7 @@ class Language {
                global $wgExtraLanguageNames, $wgUsePigLatinVariant;
 
                // If passed an invalid language code to use, fallback to en
-               if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) {
+               if ( $inLanguage !== null && !self::isValidCode( $inLanguage ) ) {
                        $inLanguage = 'en';
                }
 
@@ -1192,7 +1192,7 @@ class Language {
                                case 'D':
                                        $usedDay = true;
                                        $s .= $this->getWeekdayAbbreviation(
-                                               Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
+                                               self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
                                        );
                                        break;
                                case 'j':
@@ -1223,7 +1223,7 @@ class Language {
                                case 'l':
                                        $usedDay = true;
                                        $s .= $this->getWeekdayName(
-                                               Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
+                                               self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
                                        );
                                        break;
                                case 'F':
@@ -1404,36 +1404,36 @@ class Language {
                                case 'O':
                                case 'P':
                                case 'T':
-                                       $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
+                                       $s .= self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
                                        break;
                                case 'w':
                                case 'N':
                                case 'z':
                                        $usedDay = true;
-                                       $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
+                                       $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
                                        break;
                                case 'W':
                                        $usedWeek = true;
-                                       $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
+                                       $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
                                        break;
                                case 't':
                                        $usedMonth = true;
-                                       $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
+                                       $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
                                        break;
                                case 'L':
                                        $usedIsLeapYear = true;
-                                       $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
+                                       $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
                                        break;
                                case 'o':
                                        $usedISOYear = true;
-                                       $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
+                                       $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
                                        break;
                                case 'U':
                                        $usedSecond = true;
                                        // fall through
                                case 'I':
                                case 'Z':
-                                       $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
+                                       $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
                                        break;
                                case '\\':
                                        # Backslash escaping
@@ -1467,7 +1467,7 @@ class Language {
                                        $s .= $num;
                                        $raw = false;
                                } elseif ( $roman ) {
-                                       $s .= Language::romanNumeral( $num );
+                                       $s .= self::romanNumeral( $num );
                                        $roman = false;
                                } elseif ( $hebrewNum ) {
                                        $s .= self::hebrewNumeral( $num );
@@ -1509,7 +1509,7 @@ class Language {
                                substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
                        if ( $usedWeek ) {
                                $possibleTtls[] =
-                                       ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
+                                       ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
                                        $timeRemainingInDay;
                        } elseif ( $usedISOYear ) {
                                // December 28th falls on the last ISO week of the year, every year.
@@ -1519,29 +1519,29 @@ class Language {
                                        substr( $ts, 0, 4 ) . '1228',
                                        $zone ?: new DateTimeZone( 'UTC' )
                                )->format( 'W' );
-                               $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
+                               $currentISOWeek = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
                                $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
                                $timeRemainingInWeek =
-                                       ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
+                                       ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
                                        + $timeRemainingInDay;
                                $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
                        }
 
                        if ( $usedMonth ) {
                                $possibleTtls[] =
-                                       ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
+                                       ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
                                                substr( $ts, 6, 2 ) ) * 86400
                                        + $timeRemainingInDay;
                        } elseif ( $usedYear ) {
                                $possibleTtls[] =
-                                       ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
-                                               Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
+                                       ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
+                                               self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
                                        + $timeRemainingInDay;
                        } elseif ( $usedIsLeapYear ) {
                                $year = substr( $ts, 0, 4 );
                                $timeRemainingInYear =
-                                       ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
-                                               Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
+                                       ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
+                                               self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
                                        + $timeRemainingInDay;
                                $mod = $year % 4;
                                if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
@@ -3956,7 +3956,7 @@ class Language {
         * @return string Text, wrapped in LRE...PDF or RLE...PDF or nothing
         */
        public function embedBidi( $text = '' ) {
-               $dir = Language::strongDirFromContent( $text );
+               $dir = self::strongDirFromContent( $text );
                if ( $dir === 'ltr' ) {
                        // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
                        return self::$lre . $text . self::$pdf;
@@ -4264,7 +4264,7 @@ class Language {
                        $this->mParentLanguage = null;
                        return null;
                }
-               $lang = Language::factory( $code );
+               $lang = self::factory( $code );
                if ( !$lang->hasVariant( $this->getCode() ) ) {
                        $this->mParentLanguage = null;
                        return null;
@@ -4420,7 +4420,7 @@ class Language {
         * @return array Non-empty array, ending in "en"
         */
        public static function getFallbacksFor( $code ) {
-               if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
+               if ( $code === 'en' || !self::isValidBuiltInCode( $code ) ) {
                        return [];
                }
                // For unknown languages, fallbackSequence returns an empty array,
@@ -4821,7 +4821,7 @@ class Language {
         */
        public function getCompiledPluralRules() {
                $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
-               $fallbacks = Language::getFallbacksFor( $this->mCode );
+               $fallbacks = self::getFallbacksFor( $this->mCode );
                if ( !$pluralRules ) {
                        foreach ( $fallbacks as $fallbackCode ) {
                                $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
@@ -4840,7 +4840,7 @@ class Language {
         */
        public function getPluralRules() {
                $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
-               $fallbacks = Language::getFallbacksFor( $this->mCode );
+               $fallbacks = self::getFallbacksFor( $this->mCode );
                if ( !$pluralRules ) {
                        foreach ( $fallbacks as $fallbackCode ) {
                                $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
@@ -4859,7 +4859,7 @@ class Language {
         */
        public function getPluralRuleTypes() {
                $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
-               $fallbacks = Language::getFallbacksFor( $this->mCode );
+               $fallbacks = self::getFallbacksFor( $this->mCode );
                if ( !$pluralRuleTypes ) {
                        foreach ( $fallbacks as $fallbackCode ) {
                                $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
index 00d91ce..19ff2a4 100644 (file)
@@ -383,6 +383,8 @@ class Names {
                'si' => 'සිංහල', # Sinhalese
                'simple' => 'Simple English', # Simple English
                'sk' => 'slovenčina', # Slovak
+               'skr' => 'سرائیکی', # Saraiki (multiple scripts - defaults to Arabic)
+               'skr-arab' => 'سرائیکی', # Saraiki (Arabic script)
                'sl' => 'slovenščina', # Slovenian
                'sli' => 'Schläsch', # Lower Selisian
                'sm' => 'Gagana Samoa', # Samoan
index 870d7b7..c098ef2 100644 (file)
        "rcfilters-legend-heading": "<strong>Llista d'abreviatures:</strong>",
        "rcfilters-activefilters": "Filtros activos",
        "rcfilters-advancedfilters": "Filtros avanzaos",
+       "rcfilters-limit-title": "Cambios a amosar",
+       "rcfilters-limit-shownum": "Amosar los últimos $1 cambios",
+       "rcfilters-days-title": "Últimos díes",
+       "rcfilters-hours-title": "Últimes hores",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|día|díes}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|hora|hores}}",
        "rcfilters-quickfilters": "Filtros guardaos",
        "rcfilters-quickfilters-placeholder-title": "Entá nun se guardaron enllaces",
        "rcfilters-quickfilters-placeholder-description": "Pa guardar les preferencies del filtru y volver a usales sero, pulsia nel iconu del marcador del área de Filtru Activu más abaxo.",
        "rcfilters-invalid-filter": "Filtru inválidu",
        "rcfilters-empty-filter": "Nun hai filtros activos. Amuésense toles contribuciones.",
        "rcfilters-filterlist-title": "Filtros",
-       "rcfilters-filterlist-whatsthis": "¿Qué ye esto?",
+       "rcfilters-filterlist-whatsthis": "¿Como funciona esto?",
        "rcfilters-filterlist-feedbacklink": "Comentar sobro los nuevos filtros (beta)",
        "rcfilters-highlightbutton-title": "Resaltar resultaos",
        "rcfilters-highlightmenu-title": "Seleiciona un color",
        "rcfilters-filter-editsbyself-description": "Contribuciones de to.",
        "rcfilters-filter-editsbyother-label": "Cambios d'otros",
        "rcfilters-filter-editsbyother-description": "Tolos cambios menos los de to.",
-       "rcfilters-filtergroup-userExpLevel": "Nivel d'esperiencia (solo pa usuarios rexistraos)",
+       "rcfilters-filtergroup-userExpLevel": "Rexistru d'usuarios y esperiencia",
        "rcfilters-filter-user-experience-level-registered-label": "Rexistraos",
        "rcfilters-filter-user-experience-level-registered-description": "Editores coneutaos.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Non rexistraos",
-       "rcfilters-filter-user-experience-level-unregistered-description": "Editores ensin coneutar.",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Editores que nun tán coneutaos.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Recién llegaos",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Menos de 10 ediciones y 4 díes d'actividá.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Editores rexistraos con menos de 10 ediciones y 4 díes d'actividá.",
        "rcfilters-filter-user-experience-level-learner-label": "Aprendices",
-       "rcfilters-filter-user-experience-level-learner-description": "Más esperiencia que los «Recién llegaos», pero menos que los «Usuarios espertos».",
+       "rcfilters-filter-user-experience-level-learner-description": "Editores rexistraos con esperiencia ente «Recién llegaos» y «Usuarios espertos».",
        "rcfilters-filter-user-experience-level-experienced-label": "Usuarios espertos",
-       "rcfilters-filter-user-experience-level-experienced-description": "Más de 30 díes d'actividá y 500 ediciones.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Editores rexistraos con más de 500 ediciones y 30 díes d'actividá.",
        "rcfilters-filtergroup-automated": "Contribuciones automátiques",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-bots-description": "Ediciones feches con ferramientes automátiques.",
        "rcfilters-hideminor-conflicts-typeofchange-global": "El filtru «Ediciones menores» fai conflictu con un filtru «Tipu de cambiu» o más, porque dellos tipos de cambiu nun pueden designase como «menores». Los filtros que faen conflictu tan marcaos nel área de Filtros Activos, más arriba.",
        "rcfilters-hideminor-conflicts-typeofchange": "Dellos tipos de cambiu nun pueden designase como «menores», de manera qu'esti filtru fai conflictu colos siguientes filtros «Tipu de cambiu»: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Esti filtru de «Tipu de cambiu» fai conflictu col filtru «Ediciones menores». Dellos tipos de cambiu nun pueden designase como «menores».",
-       "rcfilters-filtergroup-lastRevision": "Última revisión",
+       "rcfilters-filtergroup-lastRevision": "Últimes revisiones",
        "rcfilters-filter-lastrevision-label": "Última revisión",
-       "rcfilters-filter-lastrevision-description": "El cambio más recien d'una páxina.",
-       "rcfilters-filter-previousrevision-label": "Revisiones anteriores",
-       "rcfilters-filter-previousrevision-description": "Tolos cambios que nun son los más recien d'una páxina.",
+       "rcfilters-filter-lastrevision-description": "Sólo el cambiu más recien d'una páxina.",
+       "rcfilters-filter-previousrevision-label": "Non la cabera revisión",
+       "rcfilters-filter-previousrevision-description": "Tolos cambios que nun son la «cabera revisión».",
        "rcfilters-filter-excluded": "Escluíu",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:non</strong> $1",
+       "rcfilters-exclude-button-off": "Torgar los seleicionaos",
+       "rcfilters-exclude-button-on": "Torgando los seleicionaos",
        "rcfilters-view-tags": "Ediciones etiquetaes",
        "rcfilters-view-namespaces-tooltip": "Filtriar los resultaos por espaciu de nomes",
        "rcfilters-view-tags-tooltip": "Filtriar los resultaos usando les etiquetes d'edición",
        "delete-warning-toobig": "Esta páxina tien un historial d'ediciones grande, más de $1 {{PLURAL:$1|revisión|revisiones}}.\nEsborralu pue perturbar les operaciones de la base de datos de {{SITENAME}};\nobra con precaución.",
        "deleteprotected": "Nun pues desaniciar esta páxina porque ta protexida.",
        "deleting-backlinks-warning": "<strong>Avisu:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Otres páxines]] enllacen a, o trescluyen de, la páxina que tas a piques de desaniciar.",
+       "deleting-subpages-warning": "<strong>Avisu:</strong> La páxina que vas desaniciar tien [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|una subpáxina|$1 subpáxines|51=más de 50 subpáxines}}]].",
        "rollback": "Revertir ediciones",
        "rollbacklink": "revertir",
        "rollbacklinkcount": "revertir $1 {{PLURAL:$1|edición|ediciones}}",
        "fileduplicatesearch-noresults": "Nun s'alcontró dengún ficheru nomáu «$1».",
        "specialpages": "Páxines especiales",
        "specialpages-note-top": "Lleenda",
-       "specialpages-note": "* Páxines especiales normales.\n* <span class=\"mw-specialpagerestricted\">Páxines especiales restrinxíes.</span>",
        "specialpages-group-maintenance": "Informes de mantenimientu",
        "specialpages-group-other": "Otres páxines especiales",
        "specialpages-group-login": "Entrar / crear cuenta",
index 44e67df..246ddaa 100644 (file)
        "thu": "Кс",
        "fri": "Йм",
        "sat": "Шб",
-       "january": "Ò\93инÑ\83аÑ\80",
-       "february": "февраль",
-       "march": "маÑ\80Ñ\82",
-       "april": "апÑ\80елÑ\8c",
-       "may_long": "май (Ò»абанай)",
-       "june": "иÑ\8eнÑ\8c",
-       "july": "иÑ\8eлÑ\8c",
-       "august": "авгÑ\83Ñ\81Ñ\82",
-       "september": "сентябрь",
-       "october": "окÑ\82Ñ\8fбÑ\80Ñ\8c",
-       "november": "ноÑ\8fбÑ\80Ñ\8c",
-       "december": "декабÑ\80Ñ\8c",
-       "january-gen": "Ò\93инÑ\83аÑ\80",
-       "february-gen": "февраль",
-       "march-gen": "маÑ\80Ñ\82",
-       "april-gen": "апÑ\80елÑ\8c",
-       "may-gen": "май",
-       "june-gen": "иÑ\8eнÑ\8c",
-       "july-gen": "иÑ\8eлÑ\8c",
-       "august-gen": "авгÑ\83Ñ\81Ñ\82",
-       "september-gen": "сентябрь",
-       "october-gen": "окÑ\82Ñ\8fбÑ\80Ñ\8c",
-       "november-gen": "ноÑ\8fбÑ\80Ñ\8c",
-       "december-gen": "декабÑ\80Ñ\8c",
-       "jan": "Ò\93ин",
-       "feb": "фев",
-       "mar": "мар",
-       "apr": "апр",
-       "may": "май",
-       "jun": "июн",
-       "jul": "июл",
-       "aug": "авг",
-       "sep": "сен",
-       "oct": "окт",
-       "nov": "ноя",
-       "dec": "дек",
+       "january": "Ò\92инÑ\83аÑ\80 (ÒºÑ\8bÑ\83Ñ\8bÒ\93ай)",
+       "february": "Февраль (Шаҡай)",
+       "march": "Ð\9cаÑ\80Ñ\82 (Ð\91Ñ\83Ñ\80анай)",
+       "april": "Ð\90пÑ\80елÑ\8c (Ð\90лаÒ\93аÑ\80ай)",
+       "may_long": "Ð\9cай (Òºабанай)",
+       "june": "Ð\98Ñ\8eнÑ\8c (ÒºÓ©Ñ\82ай)",
+       "july": "Ð\98Ñ\8eлÑ\8c (Ð\9cайай)",
+       "august": "Ð\90вгÑ\83Ñ\81Ñ\82 (УÑ\80аÒ\93ай)",
+       "september": "Сентябрь (Һарысай)",
+       "october": "Ð\9eкÑ\82Ñ\8fбÑ\80Ñ\8c (ҠаÑ\80аÑ\81ай)",
+       "november": "Ð\9dоÑ\8fбÑ\80Ñ\8c (Ò Ñ\8bÑ\80паÒ\93ай)",
+       "december": "Ð\94екабÑ\80Ñ\8c (Ð\90Ò¡Ñ\8aÑ\8eлай)",
+       "january-gen": "Ò\92инÑ\83аÑ\80 (ÒºÑ\8bÑ\83Ñ\8bÒ\93ай)",
+       "february-gen": "Февраль (Шаҡай)",
+       "march-gen": "Ð\9cаÑ\80Ñ\82 (Ð\91Ñ\83Ñ\80анай)",
+       "april-gen": "Ð\90пÑ\80елÑ\8c (Ð\90лаÒ\93аÑ\80ай)",
+       "may-gen": "Ð\9cай (Һабанай)",
+       "june-gen": "Ð\98Ñ\8eнÑ\8c (ÒºÓ©Ñ\82ай)",
+       "july-gen": "Ð\98Ñ\8eлÑ\8c (Ð\9cайай)",
+       "august-gen": "Ð\90вгÑ\83Ñ\81Ñ\82 (УÑ\80аÒ\93ай)",
+       "september-gen": "Сентябрь (Һарысай)",
+       "october-gen": "Ð\9eкÑ\82Ñ\8fбÑ\80Ñ\8c (ҠаÑ\80аÑ\81ай)",
+       "november-gen": "Ð\9dоÑ\8fбÑ\80Ñ\8c (Ò Ñ\8bÑ\80паÒ\93ай)",
+       "december-gen": "Ð\94екабÑ\80Ñ\8c (Ð\90Ò¡Ñ\8aÑ\8eлай)",
+       "jan": "Ò\92ин",
+       "feb": "Фев",
+       "mar": "Ð\9cар",
+       "apr": "Ð\90пр",
+       "may": "Ð\9cай",
+       "jun": "Ð\98юн",
+       "jul": "Ð\98юл",
+       "aug": "Ð\90вг",
+       "sep": "Сен",
+       "oct": "Ð\9eкт",
+       "nov": "Ð\9dоя",
+       "dec": "Ð\94ек",
        "january-date": "Ғинуар $1",
        "february-date": "Февраль $1",
        "march-date": "Март $1",
        "september-date": "Сентябрь $1",
        "october-date": "Октябрь $1",
        "november-date": "Ноябрь $1",
-       "december-date": "СенÑ\82Ñ\8fбрь $1",
+       "december-date": "Ð\94екабрь $1",
        "period-am": "ТК",
        "period-pm": "ТС",
        "pagecategories": "{{PLURAL:$1|1=Категория|Категориялар}}",
        "category-empty": "\"Был категория әлегә буш.\"",
        "hidden-categories": "{{PLURAL:$1|Йәшерен категория|Йәшерен категориялар}}",
        "hidden-category-category": "Йәшерен категориялар",
-       "category-subcat-count": "{{PLURAL:$2|Был категорияла тик киләһе эске категория ғына бар.|Барлығы $2 категориянан, был категорияла киләһе  {{PLURAL:$1|эске категория|$1 эске категория}} күрһәтелә.}}",
+       "category-subcat-count": "{{PLURAL:$2|Был категорияла тик киләһе эске категория ғына бар.|Барлығы $2 категориянан, был категорияла киләһе {{PLURAL:$1|эске категория|$1 эске категория}} күрһәтелә.}}",
        "category-subcat-count-limited": "Был категорияға киләһе {{PLURAL:$1|эске категория|$1 эске категория}} ингән.",
        "category-article-count": "{{PLURAL:$2|1=Был категорияла бер генә бит бар.|Категориялағы $2 биттең $1 бите күрһәтелгән.}}",
        "category-article-count-limited": "Был категорияла {{PLURAL:$1|$1 бит}} бар.",
        "category-file-count": "{{PLURAL:$2|Был категорияла бер генә файл бар.|Категориялағы $2 файлдың {{PLURAL:$1|$1 файлы күрһәтелгән}}.}}",
        "category-file-count-limited": "Был категорияла {{PLURAL:$1|$1 файл}} бар.",
-       "listingcontinuesabbrev": "(дауамы)",
+       "listingcontinuesabbrev": "дауамы",
        "index-category": "Индексланған биттәр",
        "noindex-category": "Индексланмаған биттәр",
        "broken-file-category": "Файлға һылтанмалары эшләмәгән биттәр",
        "newwindow": "(яңы биттә)",
        "cancel": "Кире алырға",
        "moredotdotdot": "Дауамы...",
-       "morenotlisted": "Был исемлек тулы түгел",
+       "morenotlisted": "Был исемлек тулы түгел.",
        "mypage": "Бит",
        "mytalk": "Әңгәмә",
        "anontalk": "Әңгәмә",
        "navigation": "Төп йүнәлештәр",
        "and": "&#32;һәм",
-       "faq": "ЙБҺ",
+       "faq": "ЙБҺ (ЧаВо)",
        "actions": "Ғәмәлдәр",
        "namespaces": "Исем арауыҡтары",
        "variants": "Варианттар",
        "tagline": "{{SITENAME}} проектынан",
        "help": "Белешмә",
        "search": "Эҙләү",
-       "search-ignored-headings": " #<!-- был юлды нисек бар шулай ҡалдырығыҙ --> <pre>\n# Эҙләүҙәр инҡар иткән атамалар.\n# Атамаһы булған бит индексланғас та, үҙгәртмәләр үҙ көсөнә инәсәк.\n# Буш төҙәтеү менән һеҙ битте яңынан индекслата алаһығыҙ\n# Синтаксис шулай күренә:\n#   * Ошо символға «#» башланған юлдың аҙағына тиклем комментарий була\n#   * Һәр буш булмаған юл - инҡар ителгәндең атамаһы, быға регистр ҙа инә\nИҫкәрмәләр\nҺылтанмалар\nҠарағыҙ шулай уҡ\n#</pre> <!-- был юлды шул көйө ҡалдырығыҙ -->",
+       "search-ignored-headings": " #<!-- был юлды нисек бар, шулай ҡалдырығыҙ --> <pre>\n# Эҙләүҙәр инҡар иткән атамалар.\n# Атамаһы булған бит индексланғас та, үҙгәртмәләр үҙ көсөнә инәсәк.\n# Буш төҙәтеү менән һеҙ битте яңынан индекслата алаһығыҙ\n# Синтаксис шулай күренә:\n#   * Ошо символға «#» башланған юлдың аҙағына тиклем комментарий була\n#   * Һәр буш булмаған юл - инҡар ителгәндең атамаһы, быға регистр ҙа инә\nИҫкәрмәләр\nҺылтанмалар\nҠарағыҙ шулай уҡ\n#</pre> <!-- был юлды шул көйө ҡалдырығыҙ -->",
        "searchbutton": "Эҙләү",
        "go": "Күсеү",
        "searcharticle": "Күсеү",
        "updatedmarker": "һуңғы инеүемдән һуң яңыртылған",
        "printableversion": "Баҫтырыу өлгөһө",
        "permalink": "Даими һылтанма",
-       "print": "Баҫыу",
+       "print": "Ð\91аҫÑ\82Ñ\8bÑ\80Ñ\8bÑ\83",
        "view": "Ҡарау",
        "view-foreign": "$1 сайтында ҡарау",
        "edit": "Үҙгәртеү",
        "edit-local": "Локаль тасуирламаны үҙгәртергә",
-       "create": "Төҙөргә",
+       "create": "Төҙөү",
        "create-local": "Локаль тасуирлама өҫтәргә",
-       "delete": "Юҡ  итергә",
+       "delete": "Юйырға",
        "undelete_short": "$1 {{PLURAL:$1|үҙгәртеүҙе}} тергеҙергә",
        "viewdeleted_short": "{{PLURAL:$1|1=1 юйылған үҙгәртеүҙе|$1 юйылған үҙгәртеүҙе}} ҡарау",
        "protect": "Һаҡларға",
        "talkpagelinktext": "әңг.",
        "specialpage": "Ярҙамсы бит",
        "personaltools": "Шәхси ҡоралдар",
-       "talk": "Әңгәмә",
+       "talk": "Фекер алышыу",
        "views": "Ҡарауҙар",
        "toolbox": "Ҡоралдар",
        "tool-link-userrights": "{{GENDER:$1|Ҡатнашыусы}} төркөмдәрен үҙгәртергә",
-       "tool-link-userrights-readonly": "{{GENDER:$1|Ҡатнашыусы|Ҡатнашыулар}} төркөмдәрен ҡарарға",
+       "tool-link-userrights-readonly": "{{GENDER:$1|Ҡатнашыусы}} төркөмдәрен ҡарарға",
        "tool-link-emailuser": "{{GENDER:$1|Ҡатнашыусыға}} хат яҙырға",
        "imagepage": "Файл битен ҡарарға",
        "mediawikipage": "Хәбәрҙәр битен ҡарарға",
        "jumpto": "Унда күсергә:",
        "jumptonavigation": "төп йүнәлештәр",
        "jumptosearch": "эҙләү",
-       "view-pool-error": "Ò\92Ó\99Ñ\84Ò¯ Ð¸Ñ\82егеÒ\99, Ñ\85Ó\99Ò\99еÑ\80ге Ð²Ð°Ò¡Ñ\8bÑ\82Ñ\82а Ñ\81еÑ\80веÑ\80Ò\99аÑ\80 Ð°Ñ\80Ñ\82Ñ\8bÒ¡ Ñ\82ейÓ\99лгÓ\99н.\nБыл битте ҡарарға теләүселәр бик күп.\nБыл биткә һуңғарак кереп ҡарағыҙ.\n\n$1",
-       "generic-pool-error": "Ò\92Ó\99Ñ\84Ò¯ Ð¸Ñ\82егеÒ\99, Ñ\85Ó\99Ò\99еÑ\80ге Ð²Ð°Ò¡Ñ\8bÑ\82Ñ\82а Ñ\81еÑ\80веÑ\80Ò\99аÑ\80 ÐºÓ©Ñ\81Ó©Ñ\80гÓ\99неÑ\88ле Ñ\8dÑ\88лÓ\99й.\nÐ\91Ñ\8bл Ð±Ð¾Ð»Ð´Ñ\8b Ò¡Ð°Ñ\80аÑ\80Ò\93а Ñ\82елÓ\99Ò¯Ñ\81елÓ\99Ñ\80 Ð±Ð¸Ðº ÐºÒ¯Ð¿.\nÐ\97инһаÑ\80, Ð±ер ни тиклем көтөгөҙ һәм һуңыраҡ тағы мөрәжәғәт итеп ҡарағыҙ.",
+       "view-pool-error": "Ò\92Ó\99Ñ\84Ò¯ Ð¸Ñ\82егеÒ\99, Ñ\85Ó\99Ò\99еÑ\80ге Ð²Ð°Ò¡Ñ\8bÑ\82Ñ\82а Ñ\81еÑ\80веÑ\80Ò\99аÑ\80 ÐºÓ©Ñ\81Ó©Ñ\80гÓ\99неÑ\88ле Ñ\8dÑ\88лÓ\99й.\nБыл битте ҡарарға теләүселәр бик күп.\nБыл биткә һуңғарак кереп ҡарағыҙ.\n\n$1",
+       "generic-pool-error": "Ò\92Ó\99Ñ\84Ò¯ Ð¸Ñ\82егеÒ\99, Ñ\85Ó\99Ò\99еÑ\80ге Ð²Ð°Ò¡Ñ\8bÑ\82Ñ\82а Ñ\81еÑ\80веÑ\80Ò\99аÑ\80 ÐºÓ©Ñ\81Ó©Ñ\80гÓ\99неÑ\88ле Ñ\8dÑ\88лÓ\99й.\nÐ\91Ñ\8bл Ð±Ð¾Ð»Ð´Ñ\8b Ò¡Ð°Ñ\80аÑ\80Ò\93а Ñ\82елÓ\99Ò¯Ñ\81елÓ\99Ñ\80 Ð±Ð¸Ðº ÐºÒ¯Ð¿.\nÐ\91ер ни тиклем көтөгөҙ һәм һуңыраҡ тағы мөрәжәғәт итеп ҡарағыҙ.",
        "pool-timeout": "Блоклауҙы көтөү ваҡыты үтте",
        "pool-queuefull": "Һорауҙар сираты тулы",
        "pool-errorunknown": "Билдәһеҙ хата",
        "disclaimerpage": "Project:Яуаплылыҡтан баш тартыу",
        "edithelp": "Төҙәтеү белешмәһе",
        "helppage-top-gethelp": "Ярҙам",
-       "mainpage": "Ð\91аÑ\88 Ð±ит",
+       "mainpage": "Ð\91аÑ\88 Ð\91ит",
        "mainpage-description": "Баш бит",
        "policy-url": "Project:Ҡағиҙәләр",
        "portal": "Берләшмә",
        "badaccess": "Кереү хатаһы",
        "badaccess-group0": "Һоратылған ғәмәлде үтәй алмайһығыҙ.",
        "badaccess-groups": "Һоратылған ғәмәлде киләһе {{PLURAL:$2|1=төркөм|төркөмдәр}} ҡулланыусылары ғына башҡара ала: $1.",
-       "versionrequired": "MediaWiki-ның $1 версияһы кәрәкле",
+       "versionrequired": "MediaWiki-ның $1 версияһы кәрәк",
        "versionrequiredtext": "Был бит менән эшләү өсөн MediaWiki-ның $1 версияһы кәрәк. [[Special:Version|Ҡулланылған версия тураһында мәғлүмәт битен]] ҡара.",
        "ok": "Тамам",
        "pagetitle": "{{SITENAME}} проектынан",
        "retrievedfrom": "Сығанағы — «$1»",
-       "youhavenewmessages": "Яңы $1 бар ($2).",
+       "youhavenewmessages": "{{PLURAL:$3|Һеҙгә}} $1 ($2) бар.",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|Һеҙгә}} {{PLURAL:$3|$3 ҡатнашыусыһынан}} $1 килде ($2).",
        "youhavenewmessagesmanyusers": "Һеҙгә күп ҡатнашыусынан $1 бар ($2).",
        "newmessageslinkplural": "{{PLURAL:$1|1=яңы хәбәр|яңы хәбәр}}",
        "botpasswords-updated-body": "$1 роботы өсөн $2 ҡулланыусыһы серһүҙе яңыртылды.",
        "botpasswords-deleted-title": "Робот серһүҙе юйылды.",
        "botpasswords-deleted-body": "$1 роботы өсөн $2 ҡулланыусыһы серһүҙе юйылды.",
-       "botpasswords-newpassword": "Ð\98неү Ó©Ñ\81өн Ñ\8fÒ£Ñ\8b Ñ\81еÑ\80Ò»Ò¯Ò\99 <strong>$1</strong> â\80\94 <strong>$2</strong>. <em>Ð\90Ñ\80Ñ\82абан Ò¡Ñ\83лланÑ\8bÑ\83 Ó©Ñ\81өн Ñ\8fÒ»Ñ\8bп Ð°Ð»Ñ\8bÒ\93Ñ\8bÒ\99.</em><strong>$3</strong> Ò¡Ð°Ñ\82наÑ\88Ñ\8bÑ\83Ñ\81Ñ\8b Ð¸Ñ\81еме <strong>$4</strong> Ð¿Ð°Ñ\80олÑ\8c Ñ\81иÑ\84аÑ\82Ñ\8b)",
+       "botpasswords-newpassword": "Ð\98неү Ó©Ñ\81өн Ñ\8fÒ£Ñ\8b Ñ\81еÑ\80Ò»Ò¯Ò\99 <strong>$1</strong> â\80\94 <strong>$2</strong>. <em>Ð\90Ñ\80Ñ\82абан Ò¡Ñ\83лланÑ\8bÑ\83 Ó©Ñ\81өн Ñ\8fÒ\99Ñ\8bп Ð°Ð»Ñ\8bÒ\93Ñ\8bÒ\99.</em> <br /> (Ð\98Ò«Ó\99п Ñ\8fÒ\99маһÑ\8b Ð¼ÐµÐ½Ó\99н Ò¡Ð°Ñ\82наÑ\88Ñ\8bÑ\83Ñ\81Ñ\8bнÑ\8bÒ£ Ð¸Ñ\81еме Ð±ÐµÑ\80 Ð±Ñ\83лÑ\8bÑ\83Ñ\8bн Ñ\82алап Ð¸Ñ\82кÓ\99н Ð¸Ò«ÐºÐµ Ð±Ð¾Ñ\82Ñ\82аÑ\80 Ó©Ñ\81өн, <strong>$3</strong> Ò¡Ð°Ñ\82наÑ\88Ñ\8bÑ\83Ñ\81Ñ\8b Ð¸Ñ\81еме Ð¸Ñ\82еп Ò»Ó\99м <strong>$4</strong> Ñ\81еÑ\80Ò»Ò¯Ò\99 Ð¸Ñ\82еп Ò¡Ñ\83ллана Ð°Ð»Ð°Ò»Ñ\8bÒ\93Ñ\8bÒ\99.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider ғәмәлдә түгел.",
        "botpasswords-restriction-failed": "Робот серһүҙе менән бәйле сәбәптәр булғанға инеү башҡарылманы.",
        "botpasswords-invalid-name": "Күрһәтелгән ҡулланыусы исемендә робот $1 серһүҙен бүлеүсе тамға юҡ.",
        "passwordreset-emailelement": "Ҡулланыусы исеме: \n$1\n\nВаҡытлыса серһүҙ: \n$2",
        "passwordreset-emailsentemail": "Серһүҙҙе ташлау тураһындағы мәғлүмәт менән электрон почта аша хат ебәрелде.",
        "passwordreset-emailsentusername": "Әгәр был ҡатнашыусының исеменә бәйле  электрон почтаһының адресы булһа, ул саҡта  серһүҙҙе тергеҙеү өсөн  хат ебәреләсәк.",
+       "passwordreset-nocaller": "Мөрәжәғәт сығанағы күрһәтелергә тейеш",
+       "passwordreset-nosuchcaller": "Мөрәжәғәт сығанағы юҡ: $1",
+       "passwordreset-ignored": "Серһүҙҙе ташлау эшләнмәне. Бәлки, бер провайдер ҙа көйләнмәгәндер?",
        "passwordreset-invalidemail": "Электрон почта адресы ҡабул ителмәй",
+       "passwordreset-nodata": "Ҡатнашыусы исеме лә, электрон почта адресы ла күрһәтелмәгән",
        "changeemail": "Электрон почта адресын үҙгәртергә",
        "changeemail-header": "Электрон почта адресын үҙгәртеү",
        "changeemail-no-info": "Был биткә туранан ирешеү өсөн һеҙгә системала танылыу кәрәк.",
        "preview": "Ҡарап сығыу",
        "showpreview": "Ҡарап сығырға",
        "showdiff": "Индерелгән үҙгәрештәр",
-       "blankarticle": "<strong>Иҫкәртеү:</strong> Һеҙ булдырасаҡ бит буш.\nӘгәр тағы ла «$1» кнопкаға баҫһағыҙ, шул уҡ йөкмәткеле бит  яңынан барлыҡҡа киләсәк.",
+       "blankarticle": "<strong>Иҫкәртеү:</strong> Һеҙ булдырасаҡ бит буш.\nӘгәр тағы ла «$1» төймәһенә баҫһағыҙ, йөкмәткеһеҙ бит барлыҡҡа киләсәк.",
        "anoneditwarning": "<strong>Иғтибар!</strong> Һеҙ сайтта теркәлмәнегеҙ. Әгәр ҙә һеҙ ниндәй ҙә булһа төҙәтмәләр  йәки үҙгәртүҙәр индерһәгеҙ, һеҙҙең IP-адрес башҡаларға ла күрһәтеләсәк. Сайтҡа <strong>[$1 керһәгеҙ]</strong> йәки <strong>[$2 ҡуллануысы яҙмаһын төҙөһәгеҙ]</strong>, һеҙ индергән үҙгәртеүҙәр һеҙҙең ҡулланыусы яҙмағыҙға бәйләнгән була, шулай уҡ башҡа мөмкинлектәр ҙә тыуасаҡ.",
        "anonpreviewwarning": "''Һеҙ танылмағанһығыҙ. Яҙҙырыу ваҡытында IP-адресығыҙ был биттең үҙгәртеүҙәр тарихына яҙыласаҡ.''",
        "missingsummary": "'''Иҫкәртеү.''' Һеҙ үҙгәртеүҙергә ҡыҫҡа тасуирлама яҙманығыҙ. Ҡабаттан «Битте һаҡларға» төймәһенә баҫһағыҙ, үҙгәртеүҙәрегеҙ тасуирламаһыҙ һаҡланасаҡ.",
        "selfredirect": "<strong>Иғтибар:</strong> Һеҙ шул уҡ мәҡәләгә йүнәлтеү эшләйһегеҙ.\n «$1» төәмәһенә баҫһағыҙ тағы шул биткә йүнәлтеләсәк.",
        "missingcommenttext": "Зинһар, аҫҡа үҙ тасуирламағыҙҙы керетегеҙ.",
        "missingcommentheader": "'''Иҫкәртеү:''' Һеҙ был комментарий өсөн тема/исем яҙманығыҙ.\n«$1» төймәһенә ҡабат баҫыу менән үҙгәртеүҙерегеҙ исемһеҙ яҙыласаҡ.",
-       "summary-preview": "Буласаҡ тасуирлама:",
-       "subject-preview": "Тема/башлыҡты алдан ҡарау:",
+       "summary-preview": "Үҙгәртеүҙәр аңлатмаһын ҡарап сығыу:",
+       "subject-preview": "Теманы/баш исемде алдан ҡарау:",
        "previewerrortext": "Алдан ҡарау ваҡытында хата китте.",
        "blockedtitle": "Ҡулланыусы блокланған",
        "blockedtext": "'''Иҫәп яҙыуығыҙ йәки IP-адресығыҙ блокланған.'''\n\nБлоклаусы хаким: $1.\nБелдерелгән сәбәп: ''$2''.\n\n* Блоклау башланған ваҡыт: $8\n* Блоклау  аҙағы: $6\n* Блоклауҙар һаны: $7\n\nҺеҙ $1 йәки башҡа [[{{MediaWiki:Grouppage-sysop}}|хакимгә]] блоклау буйынса һорауҙарығыҙҙы ебәрә алаһығыҙ.\nИҫегеҙҙе тотоғоҙ: әгәр һеҙ теркәлмәгән һәм электрон почта адресығыҙҙы раҫламаған булһағыҙ ([[Special:Preferences|көйләүҙәрем битендә]]), хакимгә хат ебәрә алмайһығыҙ. Шулай ук блоклау ваҡытында һеҙҙең хат ебәреү мөмкинлегегеҙ сикләгән булырға ла мөмкин.\nҺеҙҙең IP-адрес — $3, блоклау идентификаторы — #$5.\nХаттарҙа был мәғлүмәттәрҙе күрһәтергә онотмағыҙ.",
        "readonlywarning": "<strong>КИҪӘТЕҮ: Техник хеҙмәтләндереү сәбәпле мәғлүмәттәр базаһы блокланған, шунлыҡтан үҙгәртеүҙәрегеҙҙе хәҙер һаҡлай алмайһығыҙ.<strong>\nТексты аҙаҡтан ҡулланыу өсөн башҡа файлда һаҡлап тора алаһығыҙ.\n\nХаким белдергән сәбәп: $1",
        "protectedpagewarning": "'''КИҪӘТЕҮ: Һеҙ был битте үҙгәртә алмайһығыҙ, был хоҡуҡҡа хакимдәр генә эйә.'''\nБелешмә өсөн түбәндә һуңғы үҙгәртеү тураһында мәғлүмәт бирелә:",
        "semiprotectedpagewarning": "'''Киҫәтеү:''' был бит һаҡланған. Уны теркәлгән ҡулланыусылар ғына үҙгәртә ала.\nБелешмә өсөн түбәндә һуңғы үҙгәртеү тураһында мәғлүмәт бирелә:",
-       "cascadeprotectedwarning": "<strong>Киҫәтеү:</strong> Был битте тик хакимдәр генә үҙгәртә ала.  Сөнки бит {{PLURAL:$1|каскадлы яҡлау исемлегенә индерелгән}}:",
+       "cascadeprotectedwarning": "<strong>Киҫәтеү:</strong> Был битте тик [[Special:ListGroupRights|махсус хоҡуҡлы]] ҡатнашыусылар ғына үҙгәртә ала.  Сөнки бит {{PLURAL:$1|1=түбәндәге каскадлы яҡлау битенә индерелгән}}:",
        "titleprotectedwarning": "'''Киҫәтеү: Бындый исемле бит һаҡланған, уны үҙгәртеү өсөн [[Special:ListGroupRights|тейешле хоҡуҡҡа]] эйә булыу кәрәк.'''\nБелешмә өсөн түбәндә һуңғы үҙгәртеү тураһында мәғлүмәт бирелә:",
        "templatesused": "Был биттә ҡулланылған {{PLURAL:$1|1=ҡалып|ҡалыптар}}:",
        "templatesusedpreview": "Алдан ҡаралған биттә ҡулланылған {{PLURAL:$1|1=ҡалып|ҡалыптар}}:",
        "invalid-content-data": "Тыйылған мәғлүмәт",
        "content-not-allowed-here": "\"$1\" эстәлеге [[$2]] бит өсөн ярамай",
        "editwarning-warning": "Икенсе биткә күсеү һеҙ индергән үҙгәрештәрҙең юғалыуына килтереүе мөмкин.\nӘгәр системала танылыу үтһәгеҙ, көйләүҙәрегеҙ битенең \"Мөхәррирләү\" бүлегендә был киҫәтеүҙе һүндерә алаһығыҙ.",
+       "editpage-invalidcontentmodel-title": "Йөкмәтке форматы ҡабул ителмәй",
        "editpage-notsupportedcontentformat-title": "Йөкмәтке форматы асылмай",
        "editpage-notsupportedcontentformat-text": "$1 эстәлеге форматы $2 моделе форматы менән тап килмәй.",
        "content-model-wikitext": "викитекст",
        "recentchangeslinked-feed": "Бәйле үҙгәртеүҙәр",
        "recentchangeslinked-toolbox": "Бәйле үҙгәртеүҙәр",
        "recentchangeslinked-title": "\"$1\" битенә бәйле үҙгәртеүҙәр",
-       "recentchangeslinked-summary": "Был күрһәтелгән бит һылтанма яһаған (йәки күрһәтелгән категорияға кергән) һуңғы үҙгәртеүҙәр исемлеге.\n[[Special:Watchlist|Күҙәтеү исемлегегеҙгә]] керә торған биттәр '''ҡалын''' итеп күрһәтелгән.",
+       "recentchangeslinked-summary": "Был күрһәтелгән бит һылтанма яһаған (йәки күрһәтелгән категорияға кергән) һуңғы үҙгәртеүҙәр исемлеге.\n[[Special:Watchlist|Күҙәтеү исемлегегеҙгә]] керә торған биттәр '''ҡалын''' итеп күрһәтелгән.",
        "recentchangeslinked-page": "Бит исеме:",
        "recentchangeslinked-to": "Киреһенсә, был биткә һылтанма яһаған биттәрҙәге үҙгәртеүҙәрҙе күрһәтергә",
        "recentchanges-page-added-to-category": "[[:$1]] категорияға өҫтәлгән",
        "tooltip-invert": "Һайланған исемдәр арауығындағы (һәм бәйле исемдәр арауығындағы, әгәр күрһәтелһә) биттәрҙәге үҙгәртеүҙәрҙе йәшерер өсөн был билдәне ҡуйығыҙ.",
        "tooltip-whatlinkshere-invert": "Был тамғаны һайланған исемдәр арауығындағы һылтанмаларҙы йәшереү өсөн ҡуйығыҙ.",
        "namespace_association": "Бәйле арауыҡ",
-       "tooltip-namespace_association": "Һайланған исемдәр арауығы менән бәйле әңгәмә(йәки тема) исем арауыҡтарын ҡушыр өсөн был билдәне ҡуйығыҙ.",
+       "tooltip-namespace_association": "Һайланған исемдәр арауығы менән бәйле әңгәмә (йәки тема) исем арауыҡтарын ҡушыр өсөн был билдәне ҡуйығыҙ.",
        "blanknamespace": "(Төп)",
        "contributions": "{{GENDER:$1|Ҡатнашыусы}} башҡарған эш",
        "contributions-title": "$1 исемле ҡатнашыусы башҡарған эш",
        "tooltip-pt-watchlist": "Һеҙ күҙәткән биттәр исемлеге",
        "tooltip-pt-mycontris": "{{GENDER:|Һеҙҙең}} төҙәтеүҙәр исемлеге",
        "tooltip-pt-anoncontribs": "Был IP-адрестан яһалған төҙәтеүҙәр",
-       "tooltip-pt-login": "Бында теркәлеү үтергә була, әммә был эш мәжбүри түгел.",
+       "tooltip-pt-login": "Бында теркәлеү үтергә була, әммә был эш мәжбүри түгел",
        "tooltip-pt-logout": "Сығырға",
-       "tooltip-pt-createaccount": "Ð\9cоÑ\82лаҡ Ð±Ñ\83лмаһа Ð»Ð°, ÒºÐµÒ\99гÓ\99 Ð¸Ò«Ó\99п Ñ\8fÒ\99маһÑ\8b Ñ\82Ó©Ò\99Ó©Ñ\80гө Ò»Ó\99м Ñ\81иÑ\81Ñ\82емала Ñ\82анÑ\8bлÑ\8bÑ\80Ò\93а Ñ\82Ó\99ҡдим Ð¸Ñ\82Ó\99беÒ\99.",
+       "tooltip-pt-createaccount": "Ð\9cоÑ\82лаҡ Ð±Ñ\83лмаһа Ð»Ð°, ÒºÐµÒ\99гÓ\99 Ð¸Ò«Ó\99п Ñ\8fÒ\99маһÑ\8b Ñ\82Ó©Ò\99Ó©Ñ\80гÓ\99 Ò»Ó\99м Ñ\81иÑ\81Ñ\82емала Ñ\82анÑ\8bлÑ\8bÑ\80Ò\93а Ñ\82Ó\99ҡдим Ð¸Ñ\82Ó\99беÒ\99",
        "tooltip-ca-talk": "Биттең эстәлеге тураһында фекерләшеү",
        "tooltip-ca-edit": "Был битте үҙгәртергә",
        "tooltip-ca-addsection": "Яңы бүлек эшләргә",
        "tooltip-watchlistedit-raw-submit": "Күҙәтеү исемлеген яңыртырға",
        "tooltip-recreate": "Битте юйылған булыуына ҡарамаҫтан тергеҙергә",
        "tooltip-upload": "Күсерә башларға",
-       "tooltip-rollback": "Бер баҫыу менән аҙаҡҡы мөхәррирләүсенең үҙгәртеүҙәрен кире ала.",
+       "tooltip-rollback": "Бер баҫыу менән аҙаҡҡы мөхәррирләүсенең үҙгәртеүҙәрен кире ала",
        "tooltip-undo": "\"Кире ал\" төҙәтеүҙе кире ала һәм төҙәтеү формаһын \"алдан байҡау\"ҙа күрһәтә. Һәм кире алыуҙың сәбәбен белдерергә була.",
        "tooltip-preferences-save": "Көйләүҙәрҙе һаҡларға",
        "tooltip-summary": "Ҡыҫҡаса тасуирлама керетегеҙ",
        "fileduplicatesearch-noresults": "\"$1\" исемле файл табылманы",
        "specialpages": "Махсус биттәр",
        "specialpages-note-top": "Легенда",
-       "specialpages-note": "* Ябай махсус биттәр.\n* <span class=\"mw-specialpagerestricted\">Сикле махсус биттәр.</span>\n* <span class=\"mw-specialpagecached\">Кешланған махсус биттәр (иҫкергән булыуы мөмкин).</span>",
        "specialpages-group-maintenance": "Техник хеҙмәтләндереү хисапламалары",
        "specialpages-group-other": "Башҡа махсус биттәр",
        "specialpages-group-login": "Танылыу йәки теркәлеү",
        "logentry-patrol-patrol": "$1 $3 битенең $4 версияһын {{GENDER:$2|тикшерҙе}}.",
        "logentry-patrol-patrol-auto": "$1 $3 битенең $4 версияһын автоматик рәүештә {{GENDER:$2|тикшерҙе}}.",
        "logentry-newusers-newusers": " {{GENDER:$2|ҡатнашыусы}} $1 иҫәп яҙмаһы булдырҙы",
-       "logentry-newusers-create": "{{GENDER:$2|ҡатнашыусы}} $1 иҫәп яҙмаһы булдырҙы.",
+       "logentry-newusers-create": "{{GENDER:$2|ҡатнашыусы}} $1 иҫәп яҙмаһы булдырҙы",
        "logentry-newusers-create2": "$1 {{GENDER:$2|ҡатнашыусы}} $3 иҫәп яҙмаһын булдырҙы",
        "logentry-newusers-byemail": "$1 {{GENDER:$2|}} $3 иҫәп яҙмаһын булдырҙы һәм серһүҙ электрон почта аша ебәрелде",
        "logentry-newusers-autocreate": "Автоматик рәүештә {{GENDER:$2| ҡатнашыусының}} $1 иҫәп яҙмаһы яһалды",
index 989a466..07fde8b 100644 (file)
        "rcfilters-activefilters": "Актыўныя фільтры",
        "rcfilters-advancedfilters": "Пашыраныя фільтры",
        "rcfilters-limit-title": "Паказаць зьменаў",
+       "rcfilters-limit-shownum": "Паказаць апошнія $1 зьменаў",
+       "rcfilters-days-title": "Апошнія дні",
+       "rcfilters-hours-title": "Апошнія гадзіны",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|дзень|дні|дзён}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|гадзіна|гадзіны|гадзінаў}}",
        "rcfilters-quickfilters": "Захаваныя фільтры",
        "rcfilters-quickfilters-placeholder-title": "Спасылкі яшчэ не захаваныя",
        "rcfilters-quickfilters-placeholder-description": "Каб захаваць налады вашага фільтру і выкарыстаць іх пазьней, націсьніце на выяву закладкі ў зоне актыўнага фільтру ніжэй.",
        "rcfilters-filter-editsbyself-description": "Ваш уласны ўнёсак.",
        "rcfilters-filter-editsbyother-label": "Зьмены, зробленыя іншымі",
        "rcfilters-filter-editsbyother-description": "Усе зьмены, за выключэньнем вашых.",
-       "rcfilters-filtergroup-userExpLevel": "УзÑ\80овенÑ\8c Ð´Ð¾Ñ\81Ñ\8cведÑ\83 (Ñ\82олÑ\8cкÑ\96 Ð´Ð»Ñ\8f Ð·Ð°Ñ\80Ñ\8dгÑ\96Ñ\81Ñ\82Ñ\80аванÑ\8bÑ\85 Ñ\83дзелÑ\8cнÑ\96каÑ\9e)",
+       "rcfilters-filtergroup-userExpLevel": "РÑ\8dгÑ\96Ñ\81Ñ\82Ñ\80аÑ\86Ñ\8bÑ\8f Ñ\9eдзелÑ\8cнÑ\96каÑ\9e Ñ\96 Ð´Ð¾Ñ\81Ñ\8cвед",
        "rcfilters-filter-user-experience-level-registered-label": "Зарэгістраваныя",
        "rcfilters-filter-user-experience-level-registered-description": "Рэдактары, якія ўвайшлі ў сыстэму.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Незарэгістраваныя",
        "rcfilters-filter-user-experience-level-unregistered-description": "Рэдактары, якія не ўвайшлі ў сыстэму",
        "rcfilters-filter-user-experience-level-newcomer-label": "Навічкі",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Ð\9cенÑ\88 Ð·Ð° 10 Ð¿Ñ\80авак Ñ\96 4 Ð´Ð½і актыўнасьці.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Ð\97аÑ\80Ñ\8dгÑ\96Ñ\81Ñ\82Ñ\80аванÑ\8bÑ\8f Ñ\80Ñ\8dдакÑ\82аÑ\80Ñ\8b Ð·Ñ\8c Ð¼ÐµÐ½Ñ\88 Ñ\87Ñ\8bм 10 Ð¿Ñ\80аÑ\9eкамÑ\96 Ñ\96 4 Ð´Ð½Ñ\8fмі актыўнасьці.",
        "rcfilters-filter-user-experience-level-learner-label": "Вучні",
-       "rcfilters-filter-user-experience-level-learner-description": "Ð\91олÑ\8cÑ\88 Ð´Ð¾Ñ\81Ñ\8cведÑ\83, чым у «навічкоў», але меней чым у «дасьведчаных удзельнікаў».",
+       "rcfilters-filter-user-experience-level-learner-description": "Ð\97аÑ\80Ñ\8dгÑ\96Ñ\81Ñ\82Ñ\80аванÑ\8bÑ\8f Ñ\80Ñ\8dдакÑ\82аÑ\80Ñ\8b, Ñ\87Ñ\8bй Ð´Ð¾Ñ\81Ñ\8cвед Ð±Ð¾Ð»Ñ\8cÑ\88 чым у «навічкоў», але меней чым у «дасьведчаных удзельнікаў».",
        "rcfilters-filter-user-experience-level-experienced-label": "Дасьведчаныя ўдзельнікі",
        "rcfilters-filter-user-experience-level-experienced-description": "Больш за 30 дзён актыўнасьці і 500 правак.",
        "rcfilters-filtergroup-automated": "Аўтаматычны ўнёсак",
        "fileduplicatesearch-noresults": "Файл з назвай «$1» ня знойдзены.",
        "specialpages": "Спэцыяльныя старонкі",
        "specialpages-note-top": "Легенда",
-       "specialpages-note": "* Звычайныя спэцыяльныя старонкі.\n* <strong class=\"mw-specialpagerestricted\">Спэцыяльныя старонкі з абмежаваным доступам.</strong>",
        "specialpages-group-maintenance": "Тэхнічныя справаздачы",
        "specialpages-group-other": "Іншыя спэцыяльныя старонкі",
        "specialpages-group-login": "Уваход / стварэньне рахунку",
index 1190fab..9a6dd50 100644 (file)
@@ -31,7 +31,8 @@
                        "Liashko",
                        "Mechanizatar",
                        "Artsiom91",
-                       "Andrus"
+                       "Andrus",
+                       "Da voli"
                ]
        },
        "tog-underline": "Падкрэсліваць спасылкі:",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (гл. асобна [[Special:NewPages|новыя старонкі]])",
        "recentchanges-submit": "Паказаць",
        "rcfilters-activefilters": "Актыўныя фільтры",
+       "rcfilters-days-title": "Апошнія дні",
+       "rcfilters-hours-title": "Апошнія гадзіны",
        "rcfilters-savedqueries-rename": "Перайменаваць",
        "rcfilters-savedqueries-setdefault": "Устанавіць прадвызначаным",
        "rcfilters-savedqueries-unsetdefault": "Зняць прадвызначэнне",
        "rcfilters-filter-user-experience-level-registered-label": "Зарэгістраваны",
        "rcfilters-filter-user-experience-level-registered-description": "Залагіненыя рэдактары",
        "rcfilters-filter-user-experience-level-unregistered-label": "Незарэгістраваны",
-       "rcfilters-filter-user-experience-level-learner-description": "Болей дзён актыўнасці і правак, чым у «навічкоў», але меней чым у «дасведчаных удзельнікаў».",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Зарэгістраваныя рэдактары з менш за 10 правак і 4 дзён актыўнасці.",
+       "rcfilters-filter-user-experience-level-learner-description": "Зарэгістраваныя рэдактары, вопыт якіх знаходзіцца паміж «навічкамі» і «дасведчанымі удзельнікамі».",
        "rcfilters-filter-user-experience-level-experienced-description": "Зарэгістраваныя рэдактары з больш за 500 правак і 30 дзён актыўнасці.",
        "rcfilters-filter-bots-label": "Бот",
        "rcfilters-filter-humans-label": "Чалавек (не бот)",
        "rcfilters-filter-unpatrolled-label": "Недагледжаны",
        "rcfilters-filtergroup-lastRevision": "Цяперашняя версія",
        "rcfilters-filter-lastrevision-label": "Актуальная версія",
+       "rcfilters-exclude-button-on": "За выключэннем выбранага",
        "rcnotefrom": "Ніжэй {{PLURAL:$5|паказана змяненне|паказаны змены}} з <strong>$3, $4</strong> (не больш за <strong>$1</strong>).",
        "rclistfrom": "Паказаць змены з $3 $2",
        "rcshowhideminor": "$1 дробныя праўкі",
        "fileduplicatesearch-noresults": "Не знойдзены файл з іменем «$1».",
        "specialpages": "Адмысловыя старонкі",
        "specialpages-note-top": "Легенда",
-       "specialpages-note": "* Звычайныя адмысловыя старонкі.\n* <span class=\"mw-specialpagerestricted\">Адмысловыя старонкі з абмежаваным доступам.</span>\n* <span class=\"mw-specialpagecached\">Закэшаваныя адмысловыя старонкі (могуць быць састарэлымі).</span>",
        "specialpages-group-maintenance": "Звесткі аб працы",
        "specialpages-group-other": "Іншыя адмысловыя старонкі",
        "specialpages-group-login": "Прадстаўленне / рэгістрацыя",
index e1efe59..4f086f9 100644 (file)
        "fileduplicatesearch-noresults": "Не беше открит файл с име „$1“.",
        "specialpages": "Специални страници",
        "specialpages-note-top": "Легенда",
-       "specialpages-note": "* Обикновени специални страници.\n* <strong class=\"mw-specialpagerestricted\">Специални страници с ограничения.</strong>",
        "specialpages-group-maintenance": "Доклади по поддръжката",
        "specialpages-group-other": "Други специални страници",
        "specialpages-group-login": "Влизане / създаване на сметка",
index 15d7df2..074c99f 100644 (file)
        "rollbacklinkcount": "रोलबैक $1 {{PLURAL:$1|संपादन|संपादन सब}}",
        "protectlogpage": "सुरक्षा लॉग",
        "protectlogtext": "नीचे पन्ना सुरक्षा में भइल बदलावकुल के सूची बा।\nहाल में सुरक्षित पन्नन के सूची खातिर [[Special:ProtectedPages|सुरक्षित पन्नन के सूची]] देखीं।",
+       "protectedarticle": "\"[[$1]]\" सुरक्षित कइल गइल",
        "restriction-move": "स्थानांतरण",
        "restriction-create": "बनावे पर",
        "restriction-upload": "अपलोड",
        "contributions-title": " $1 खातिर प्रयोगकर्ता योगदान",
        "mycontris": "योगदान",
        "anoncontribs": "योगदान",
+       "contribsub2": "{{GENDER:$3|$1}} ($2) खातिर",
        "nocontribs": "ई मानदंड से मिलत जुलत कौनो बदलाव ना मिलल।",
        "uctop": "(वर्तमान)",
        "month": "महीना से (आ ओ से पहिले):",
        "sp-contributions-newbies-title": "नया खाता खातिर प्रयोगकर्ता के योगदान।",
        "sp-contributions-blocklog": "ब्लॉक लॉग",
        "sp-contributions-deleted": "नष्ट प्रयोगकर्ता के योगदान।",
+       "sp-contributions-uploads": "अपलोड",
        "sp-contributions-logs": "लॉग",
        "sp-contributions-talk": "बातचीत",
        "sp-contributions-userrights": "प्रयोगकर्ता अधिकार प्रबन्धन",
        "sp-contributions-blocked-notice": "ई प्रयोगकर्ता के ई समय निष्क्रीय करल गईल बा।\nनविनतम नष्ट लौग प्रविष्टी उद्धरण खातिर निचे दिहल बा:",
+       "sp-contributions-search": "योगदान खातिर खोज करीं",
+       "sp-contributions-username": "आइपी पता भा प्रयोगकर्तानाँव:",
+       "sp-contributions-newonly": "खाली उहे संपादन देखीं जेकरा से नया पन्ना बनल होखे",
+       "sp-contributions-submit": "खोजीं",
        "whatlinkshere": "इहाँ का जुड़ल बा",
        "whatlinkshere-title": "पन्ना जेवन \"$1\" से जुड़ल बा",
        "whatlinkshere-page": "पन्ना:",
        "tooltip-ca-delete": "ई पन्ना मिटाईं",
        "tooltip-ca-move": "एह पन्ना के स्थानांतरण करीं",
        "tooltip-ca-watch": "ए पन्ना के अपनी धियानसूची में जोड़ीं",
+       "tooltip-ca-unwatch": "ई पन्ना अपना धियानसूची से हटाईं",
        "tooltip-search": "{{SITENAME}} में खोजीं",
        "tooltip-search-go": "अगर ठीक एही नाँव के पन्ना मौजूद होखे तब ओहपर जाईं",
        "tooltip-search-fulltext": "अइसन पन्ना खोजीं जिनहन में ई पाठ (शब्द भा वाक्य) बाटे",
        "tooltip-undo": "\"वापस लीं\" ए संपादन के पलट देला आ संपादन फार्म के झलक देखावे वाला मोड में खोलेला। ई छोट सारांश में कारण जोड़े के मोका देला।",
        "tooltip-summary": "संछेप में एगो सारांश लिखीं",
        "simpleantispam-label": "स्पैम-बिरोधी रोक (Anti-spam check)\nएके <strong>मत</strong> भरीं!",
+       "pageinfo-length": "पन्ना लंबाई (बाइट में)",
        "pageinfo-toolboxlink": "पन्ना से जुड़ल जानकारी",
        "previousdiff": "← पुरान संपादन",
        "nextdiff": "नया संपादन →",
index 2af08f2..6015fa9 100644 (file)
        "rcfilters-filter-editsbyself-description": "আপনার নিজস্ব অবদান।",
        "rcfilters-filter-editsbyother-label": "অন্যদের দ্বারা পরিবর্তিত",
        "rcfilters-filter-editsbyother-description": "আপনার নিজস্বগুলি ছাড়া সকল পরিবর্তন।",
-       "rcfilters-filtergroup-userExpLevel": "নিবন্ধন ও অভিজ্ঞতা",
+       "rcfilters-filtergroup-userExpLevel": "বà§\8dযবহারà¦\95ারà§\80 à¦¨à¦¿à¦¬à¦¨à§\8dধন à¦\93 à¦\85ভিà¦\9cà§\8dà¦\9eতা",
        "rcfilters-filter-user-experience-level-registered-label": "নিবন্ধিত",
        "rcfilters-filter-user-experience-level-registered-description": "প্রবেশকৃত সম্পাদকবৃন্দ।",
        "rcfilters-filter-user-experience-level-unregistered-label": "অনিবন্ধিত",
        "rcfilters-typeofchange-conflicts-hideminor": "এই \"পরিবর্তনের ধরন\"-সংক্রান্ত ছাঁকনিটির সাথে \"অনুল্লেখ্য সম্পাদনা\" ছাঁকনিটির সংঘর্ষ আছে। কিছু নির্দিষ্ট ধরনের সম্পাদনা \"অনুল্লেখ্য\" হিসেবে চিহ্নিত করা সম্ভব নয়।",
        "rcfilters-filtergroup-lastRevision": "সর্বশেষ সংস্করণ",
        "rcfilters-filter-lastrevision-label": "সর্বশেষ সংশোধন",
-       "rcfilters-filter-lastrevision-description": "একটি পাতার সর্বশেষ সাম্প্রতিক পরিবর্তন।",
-       "rcfilters-filter-previousrevision-label": "পà§\82রà§\8dববরà§\8dতà§\80 à¦¸à¦\82শà§\8bধন",
-       "rcfilters-filter-previousrevision-description": "সব পরিবর্তন যা একটি পাতার সর্বশেষ সাম্প্রতিক পরিবর্তন নয়।",
+       "rcfilters-filter-lastrevision-description": "শà§\81ধà§\81মাতà§\8dর à¦\8fà¦\95à¦\9fি à¦ªà¦¾à¦¤à¦¾à¦° à¦¸à¦°à§\8dবশà§\87ষ à¦¸à¦¾à¦®à§\8dপà§\8dরতিà¦\95 à¦ªà¦°à¦¿à¦¬à¦°à§\8dতন।",
+       "rcfilters-filter-previousrevision-label": "সরà§\8dবশà§\87ষ à¦¸à¦\82শà§\8bধন à¦¨à¦¯à¦¼",
+       "rcfilters-filter-previousrevision-description": "সব পরিবর্তন যা \"সর্বশেষ সংশোধন\" নয়।",
        "rcfilters-filter-excluded": "বর্জিত",
+       "rcfilters-tag-prefix-namespace-inverted": "$1 <strong>:নয়</strong>",
        "rcfilters-view-tags": "ট্যাগকৃত সম্পাদনা",
+       "rcfilters-view-tags-tooltip": "সম্পাদনা ট্যাগ ব্যবহার করে ফলাফল ছাঁকুন",
        "rcfilters-view-return-to-default-tooltip": "মূল ছাঁকনির মেনুতে ফিরুন",
        "rcfilters-liveupdates-button": "সরাসরি হালনাগাদ",
        "rcnotefrom": "<strong>$2</strong>টা থেকে সংঘটিত পরিবর্তনগুলি (সর্বোচ্চ <strong>$1টি</strong> দেখানো হয়েছে)।",
        "delete-warning-toobig": "এই পাতাটির একটি বৃহৎ সম্পাদনা ইতিহাস রয়েছে, যা $1 {{PLURAL:$1|সংস্করণেরও|সংস্করণেরও}} বেশি।\nএই পাতাটি মুছে ফেললে তা {{SITENAME}} সাইটের ডেটাবেজ সমস্যার কারণ হতে পারে;\nসাবধানতার সাথে এগিয়ে যান।",
        "deleteprotected": "আপনি এই পাতাটি মুছে ফেলতে পারবেন না কারণ এটি সুরক্ষিত করা হয়েছে।",
        "deleting-backlinks-warning": "<strong>সতর্কীকরণ:</strong> আপনি যেটি মুছে ফেলতে যাচ্ছেন তা [[Special:WhatLinksHere/{{FULLPAGENAME}}|অন্যান্য পাতাসমূহে]] সংযুক্ত অথবা অন্তর্ভুক্ত রয়েছে।",
+       "deleting-subpages-warning": "<strong>সতর্কীকরণ:</strong> আপনি যে পাতাটি মুছে ফেলতে যাচ্ছেন তাঁর [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|একটি উপপাতা|$1টি উপপাতা|51=৫০টির বেশী}}]] রয়েছে।",
        "rollback": "সম্পাদনা ফিরিয়ে নিন",
        "rollbacklink": "পুনর্বহাল",
        "rollbacklinkcount": "$1টি {{PLURAL:$1|সম্পাদনা}} রোলব্যাক করুন",
        "undeletepage": "মুছে ফেলা পাতাগুলি দেখুন ও ফিরিয়ে আনুন",
        "undeletepagetitle": "'''[[:$1|$1]] এর অপসারিত সংস্করণগুলোর সমন্বয়ে দেখানো হচ্ছে'''।",
        "viewdeletedpage": "মুছে ফেলা হয়েছে, এমন পাতাগুলো দেখুন",
-       "undeletepagetext": "নিচের {{PLURAL:$1|পাতাটি মুছে ফেলা হয়েছে কিন্তু এটি|$1 পাতাগুলি মুছে ফেলা হয়েছে কিন্তু এগুলি}} এখনও আর্কাইভে আছে ও পুনরুদ্ধার করা সম্ভব। আর্কাইভ পর্যায়ক্রমিকভাবে পরিষ্কার করা হতে পারে।",
+       "undeletepagetext": "নিচের {{PLURAL:$1|পাতাটি মুছে ফেলা হয়েছে কিন্তু এটি|$1টি পাতা মুছে ফেলা হয়েছে কিন্তু এগুলি}} এখনও আর্কাইভে আছে ও পুনরুদ্ধার করা সম্ভব। আর্কাইভ পর্যায়ক্রমিকভাবে পরিষ্কার করা হতে পারে।",
        "undelete-fieldset-title": "সংশোধন পুনরুদ্ধার",
        "undeleteextrahelp": "সম্পূর্ণ পাতাটি পুনরুদ্ধার করার জন্য সবগুলি টিকবাক্স অনির্বাচিত করুন এবং '''''{{int:undeletebtn}}''''' বোতামে ক্লিক করুন।\nনির্বাচিত পুনরুদ্ধারের জন্য যেসব সংশোধন পুনরুদ্ধার করতে চান, তার পাশের বাক্সে টিক দিন এবং '''''{{int:undeletebtn}}''''' বোতামে ক্লিক করুন।",
        "undeleterevisions": "$1{{PLURAL:$1|টি সংশোধন}} অপসারিত",
        "fileduplicatesearch-noresults": "\"$1\" নামের কোনো ফাইল খুঁজে পাওয়া যায়নি।",
        "specialpages": "বিশেষ পাতাসমূহ",
        "specialpages-note-top": "ব্যাখ্যা",
-       "specialpages-note": "* সাধারণ বিশেষ পাতাসমূহ।\n* <span class=\"mw-specialpagerestricted\">সীমাবদ্ধ বিশেষ পাতা।</span>",
+       "specialpages-note-restricted": "* সাধারণ বিশেষ পাতাসমূহ।\n* <span class=\"mw-specialpagerestricted\">সীমাবদ্ধ বিশেষ পাতাসমূহ।</span>",
        "specialpages-group-maintenance": "রক্ষণাবেক্ষণের কার্যবিবরণীসমূহ",
        "specialpages-group-other": "অন্যান্য বিশেষ পাতাসমূহ",
        "specialpages-group-login": "প্রবেশ/নতুন অ্যাকাউন্ট",
index 03a2b69..0926cc1 100644 (file)
        "recentchanges-submit": "Prikaži",
        "rcfilters-activefilters": "Aktivni filteri",
        "rcfilters-advancedfilters": "Napredni filteri",
-       "rcfilters-quickfilters": "Sačuvane postavke filtera",
+       "rcfilters-quickfilters": "Sačuvani filteri",
        "rcfilters-quickfilters-placeholder-title": "Zasad nema sačuvanih linkova",
        "rcfilters-quickfilters-placeholder-description": "Da sačuvate postavke filtera da biste ih kasnije ponovo upotrijebili, kliknite na ikonu markera pod \"Aktivni filterima\" ispod.",
        "rcfilters-savedqueries-defaultlabel": "Sačuvani filteri",
        "move-page": "Premjesti $1",
        "move-page-legend": "Premjesti stranicu",
        "movepagetext": "Korištenjem ovog formulara možete preimenovati stranicu, premještajući cijelu historiju na novo ime.\nČlanak pod starim imenom postat će stranica koja preusmjerava na članak pod novim imenom. \nMožete automatski izmijeniti preusmjerenje do izvornog naslova.\nAko se ne odlučite na to, provjerite [[Special:DoubleRedirects|dvostruka]] ili [[Special:BrokenRedirects|neispravna preusmjeravanja]].\nDužni ste provjeriti da svi linkovi i dalje nastave voditi na prave stranice.\n\nImajte na umu da članak <strong>neće</strong> biti premješten ako već postoji članak pod imenom na koje ga namjeravate preusmjeriti osim u slučaju stranice za preusmjeravanje koja nema nikakvih starih izmjena.\nTo znači da možete vratiti stranicu na prethodno mjesto ako pogriješite, ali ne možete zamijeniti postojeću stranicu.\n\n<strong>Napomena:</strong>\nOvo može biti drastična i neočekivana promjena kad su u pitanju popularne stranice.\nMolimo da dobro razmislite prije no što premjestite stranicu.",
-       "movepagetext-noredirectfixer": "Koristeći donji obrazac, preimenovat ćete stranicu i premjestiti cijelu njenu historiju na novi naziv.\nStari naziv postat će preusmjerenje na novi naziv.\nMolimo da provjerite postoje li [[Special:DoubleRedirects|dvostruka]] ili [[Special:BrokenRedirects|nedovršena preusmjerenja]].\nVi ste za to odgovorni te morate provjeriti jesu li linkovi ispravni i vode li tamo kamo bi trebali voditi.\n\nImajte na umu da stranica '''neće''' biti premještena ako već postoji stranica s tim imenom, osim ako je prazna ili je preusmjerenje ili nema ranije historije.\nOvo znači da možete preimenovati stranicu nazad gdje je ranije bila preimenovana ako ste pogriješili, ali ne možete ponovo preimenovati postojeću stranicu.\n\n<strong>Napomena:</strong>\nImajte na umu da premještanje popularnog članka može biti\ndrastična i neočekivana promjena za korisnike; molimo da budete sigurni da ste shvatili posljedice prije no što nastavite.",
+       "movepagetext-noredirectfixer": "Koristeći donji obrazac, preimenovat ćete stranicu i premjestiti cijelu njenu historiju na novi naziv.\nStari naziv postat će preusmjerenje na novi naziv.\nMolimo da provjerite postoje li [[Special:DoubleRedirects|dvostruka]] ili [[Special:BrokenRedirects|nedovršena preusmjerenja]].\nVi ste za to odgovorni te morate provjeriti jesu li linkovi ispravni i vode li tamo kamo bi trebali voditi.\n\nImajte na umu da stranica '''neće''' biti premještena ako već postoji stranica s tim imenom, osim ako je prazna ili je preusmjerenje ili nema ranije historije.\nOvo znači da možete preimenovati stranicu nazad gdje je ranije bila preimenovana ako ste pogriješili, ali ne možete ponovo preimenovati postojeću stranicu.\n\n<strong>Napomena:</strong>\nOvo može biti drastična i neočekivana promjena za popularnu stranicu;\ndobro razmislite o posljedicama prije nego što nastavite.",
        "movepagetalktext": "Ako označite ovu kutijicu, odgovarajuća stranica za razgovor, ako postoji, automatski će biti premještena na novi naziv, osim ako već postoji sadržaj na odredišnoj stranici za razgovor.\n\nU tom slučaju, morat ćete ručno premjestiti ili prekopirati stranicu ako to želite.",
        "moveuserpage-warning": "<strong>Upozorenje:</strong> Premještate korisničku stranicu. Imajte u vidu da će samo stranica biti premještena, a sam korisnik <em>neće</em> biti preimenovan.",
        "movecategorypage-warning": "<strong>Upozorenje:</strong> Premještate stranicu kategorije. Imajte na umu da će samo stranica biti premještena i da sve stranice u staroj kategoriji <em>neće</em> biti ponovo kategorirane u novu kategoriju.",
        "fileduplicatesearch-noresults": "Nije pronađena datoteka s imenom \"$1\".",
        "specialpages": "Posebne stranice",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Normalne posebne stranice.\n* <strong class=\"mw-specialpagerestricted\">Zaštićene posebne stranice.</strong>",
        "specialpages-group-maintenance": "Izvještaji za održavanje",
        "specialpages-group-other": "Ostale posebne stranice",
        "specialpages-group-login": "Prijava / otvaranje računa",
        "htmlform-user-not-exists": "<strong>$1</strong> ne postoji.",
        "htmlform-user-not-valid": "<strong>$1</strong> nije ispravno korisničko ime.",
        "logentry-delete-delete": "$1 {{GENDER:$2|obrisao|obrisala}} je stranicu $3",
-       "logentry-delete-delete_redir": "$1 {{GENDER:$2|obrisao|obrisala}} je preusmjerenje $3 prepisivanjem",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|obrisao|obrisala}} je preusmjerenje $3 presnimavanjem",
        "logentry-delete-restore": "$1 {{GENDER:$2|vratio|vratila}} je stranicu $3 ($4)",
        "logentry-delete-restore-nocount": "$1 {{GENDER:$2|vratio|vratila}} je stranicu $3",
        "restore-count-revisions": "{{PLURAL:$1|1 izmjena|$1 izmjene|$1 izmjena}}",
index 8b8e77f..d150323 100644 (file)
        "anontalk": "Păng-gōng",
        "navigation": "Īng-dô̤:",
        "and": "&#32;gâe̤ng",
-       "qbfind": "討",
-       "qbbrowse": "覷蜀覷",
-       "qbedit": "修改",
-       "qbpageoptions": "茲蜀頁",
-       "qbmyoptions": "我其頁面",
        "faq": "真稠碰著其問題",
-       "faqpage": "Project:稠問其問題",
        "actions": "動作",
        "namespaces": "Miàng-kŭng-găng",
        "variants": "Biéng-tā̤",
        "edit-local": "編輯當地描述",
        "create": "創建",
        "create-local": "添加當地描述",
-       "editthispage": "修改茲頁",
-       "create-this-page": "創建茲蜀頁",
        "delete": "刪除",
-       "deletethispage": "刪除茲頁",
-       "undeletethispage": "恢復茲蜀頁",
        "undelete_short": "恢復$1回修改{{PLURAL:$1}}",
        "viewdeleted_short": "覷蜀覷$1回刪掉其修改{{PLURAL:$1}}",
        "protect": "保護",
        "protect_change": "改變",
-       "protectthispage": "保護茲蜀頁",
        "unprotect": "改變保護其狀態",
-       "unprotectthispage": "改變茲蜀頁其保護狀態",
        "newpage": "新頁",
-       "talkpage": "討論茲頁",
        "talkpagelinktext": "páng-gōng",
        "specialpage": "特殊頁",
        "personaltools": "Gó̤-ìng gì gă-sĭ-huă",
-       "articlepage": "覷蜀覷內容頁面",
        "talk": "Tō̤-lâung",
        "views": "Ché̤ṳ-siŏh-ché̤ṳ",
        "toolbox": "Gă-sĭ-huă",
-       "userpage": "覷蜀覷用戶頁面",
-       "projectpage": "看工程頁",
        "imagepage": "覷蜀覷文件頁面",
        "mediawikipage": "看消息頁",
        "templatepage": "看模板頁",
        "minoredit": "過幼修改",
        "watchthis": "監視茲頁",
        "savearticle": "Bō̤-còng ciā hiĕk",
+       "publishpage": "Huák-buó ùng-ciŏng",
+       "publishchanges": "Huák-buó siŭ-gāi",
        "preview": "預覽",
        "showpreview": "顯示預覽",
        "showdiff": "看改變其部分",
        "permissionserrorstext-withaction": "因為下底其{{PLURAL:$1|原因}},汝無能耐 $2 :",
        "recreate-moveddeleted-warn": "'''注意:汝敆𡅏重新創建舊底已經乞刪唻其頁面。'''\n\n汝應該考慮蜀下繼續去編輯茲蜀頁到底是伓是合適其。茲蜀頁其刪除記錄共移動記錄都敆嚽塊:",
        "edit-conflict": "編輯衝突",
+       "postedit-confirmation-saved": "Nṳ̄ gì siŭ-gāi ī-gĭng bō̤-còng.",
        "content-model-wikitext": "維基文本",
        "content-model-text": "純文本",
        "content-model-javascript": "JavaScript",
index 117b558..3044336 100644 (file)
        "parser-template-loop-warning": "ئەڵقەی داڕێژە دۆزرایەوە: [[$1]]",
        "parser-template-recursion-depth-warning": "سنووری قووڵی گەڕانەوەی داڕێژە تێپەڕیوە ($1)",
        "undo-success": "دەکرێ دەستکاریەکە پووچەڵبکرێتەوە.\nتکایە چاو لەو هەڵسەنگاندنەی خوارەوە بکە تا دڵنیا بیت ئەمە ئەوەیە کە‌ دەتویست بیکەی و دواتر گۆڕانکارییەکانی خوارەوە پاشەکەوت بکە بۆ تەواوکردنی پووچەڵکردنەوەکە.",
-       "undo-failure": "Ù\84Û\95بÛ\95ر Ú©Û\8eØ´Û\95Û\8c Ø¯Û\95ستâ\80\8cتÛ\8eâ\80\8cÙ\88Û\95رداÙ\86Ø\8c Ù\86اتÙ\88اÙ\86Û\8c Ø¯Û\95ستکارÛ\8cÛ\95Ú©Û\95 Ø¦Û\95Ù\86جاÙ\85â\80\8cÙ\86Û\95دراÙ\88 Ø¨Ú©Û\95Û\8cت.",
+       "undo-failure": "Ù\86Û\95تÙ\88اÙ\86درا Ø¯Û\95ستکارÛ\8cÛ\8cÛ\95Ú©Û\95 Ù¾Ù\88Ù\88Ú\86Û\95Úµ Ø¨Ú©Ø±Û\8eتÛ\95Ù\88Û\95 Ù\84Û\95بÛ\95ر Ú©Û\8eØ´Û\95Û\8c Ø¯Û\95ستتÛ\8eÙ\88Û\95رداÙ\86.",
        "undo-norev": "ناتوانی دەستکاریەکە ئەنجام‌نەدراو بکەی لەبەر ئەوەی بوونی نیە یا سڕدراوەتەوە.",
        "undo-nochange": "وا دیارە دەستکارییەکە پووچەڵ کراوەتەوە.",
        "undo-summary": "گەڕاندنەوەی پێداچوونەوەی $1 لە لایەن [[Special:Contributions/$2|$2]] ([[User talk:$2|لێدوان]])",
        "rcfilters-filterlist-title": "فیلتەرەکان",
        "rcfilters-filterlist-whatsthis": "ئەمە چییە؟",
        "rcfilters-highlightmenu-title": "ڕەنگێکی نوێ ھەڵبژێرە",
-       "rcfilters-filter-registered-label": "تۆمارکراو",
-       "rcfilters-filter-registered-description": "ئەو بەکارھێنەرانەی لە ژوورەوەن",
-       "rcfilters-filter-unregistered-label": "تۆمارنەکراوەکان",
-       "rcfilters-filter-unregistered-description": "ئەو بەکارھێنەرانەی لە ژوورەوە نین",
        "rcfilters-filter-editsbyself-label": "مافەکانی خۆت",
        "rcfilters-filter-editsbyself-description": "دەستکارییەکانی خۆت.",
        "rcfilters-filter-editsbyother-label": "دەستکارییەکانی کەسانی تر",
        "rcfilters-filter-editsbyother-description": "ھەموو گۆڕانکارییەکان بێجگە لەوەی خۆت",
+       "rcfilters-filter-user-experience-level-registered-label": "تۆمارکراو",
+       "rcfilters-filter-user-experience-level-registered-description": "ئەو بەکارھێنەرانەی لە ژوورەوەن",
+       "rcfilters-filter-user-experience-level-unregistered-label": "تۆمارنەکراوەکان",
+       "rcfilters-filter-user-experience-level-unregistered-description": "ئەو بەکارھێنەرانەی لە ژوورەوە نین",
        "rcfilters-filter-user-experience-level-newcomer-label": "تازەکاران",
        "rcfilters-filter-user-experience-level-newcomer-description": "کەمتر لە ١٠ دەستکاری و ٤ ڕۆژ لە چالاک بوون",
        "rcfilters-filter-user-experience-level-experienced-label": "بەکارھێنەرانی پێشکەوتوو",
        "fileduplicatesearch-result-n": "پەڕگەی «$1» {{PLURAL:$2|١ دووپاتکراوەی کوتوموتی|$2 دووپاتکراوەی کوتوموتی}} ھەیە.",
        "fileduplicatesearch-noresults": "پەڕگەیەک بە ناوی «$1» نەدۆزرایەوە.",
        "specialpages": "پەڕە تایبەتەکان",
-       "specialpages-note": "* پەڕە تایبەتە ئاساییەکان.\n* <span class=\"mw-specialpagerestricted\">پەڕە تایبەتە بەرگریلێکراوەکان.</span>",
        "specialpages-group-maintenance": "ڕاپۆرتەکانی چاکسازی",
        "specialpages-group-other": "پەڕە تایبەتەکانی دیکە",
        "specialpages-group-login": "چوونەژوورەوە / دروستکردنی ھەژمار",
index 9d4f46f..bc4e002 100644 (file)
        "rcfilters-filter-editsbyself-description": "Vaše vlastní příspěvky.",
        "rcfilters-filter-editsbyother-label": "Změny ostatních",
        "rcfilters-filter-editsbyother-description": "Všechny změny kromě vašich.",
-       "rcfilters-filtergroup-userExpLevel": "Úroveň zkušeností (pouze registrovaných uživatelů)",
+       "rcfilters-filtergroup-userExpLevel": "Registrace a zkušenost uživatelů",
        "rcfilters-filter-user-experience-level-registered-label": "Registrovaní",
        "rcfilters-filter-user-experience-level-registered-description": "Přihlášení editoři.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Neregistrovaní",
        "rcfilters-typeofchange-conflicts-hideminor": "Tento filtr podle typu změny je v konfliktu s filtrem „Malé editace“. Určité typy změn nelze označit jako malé.",
        "rcfilters-filtergroup-lastRevision": "Aktuální verze",
        "rcfilters-filter-lastrevision-label": "Aktuální verze",
-       "rcfilters-filter-lastrevision-description": "Poslední změna stránky.",
+       "rcfilters-filter-lastrevision-description": "Jen poslední změna stránky.",
        "rcfilters-filter-previousrevision-label": "Dřívější verze",
        "rcfilters-filter-previousrevision-description": "Všechny změny, které nejsou nejnovější úpravou stránky.",
        "rcfilters-view-tags": "Označené editace",
        "delete-warning-toobig": "Tato stránka má velkou historii editací, přes $1 {{PLURAL:$1|verzi|verze|verzí}}. Mazání takových stránek může narušit databázové operace {{grammar:2sg|{{SITENAME}}}}; postupujte opatrně.",
        "deleteprotected": "Tuto stránku nemůžete smazat, protože je zamčena.",
        "deleting-backlinks-warning": "<strong>Upozornění:</strong> Stránka, kterou se chystáte smazat, je [[Special:WhatLinksHere/{{FULLPAGENAME}}|na jiných stránkách]] odkazována nebo je do nich vložena.",
+       "deleting-subpages-warning": "<strong>Upozornění:</strong> Stránka, kterou se chystáte smazat, má [[Special:PrefixIndex/{{FULLPAGENAME}}|{{PLURAL:$1|podstránku|$1 podstránky|$1 podstránek|51=více než 50 podstránek}}]].",
        "rollback": "Vrátit zpět editace",
        "rollbacklink": "vrácení zpět",
        "rollbacklinkcount": "vrácení $1 {{PLURAL:$1|editace|editací}} zpět",
        "fileduplicatesearch-noresults": "Žádný soubor s názvem „$1“ nebyl nalezen.",
        "specialpages": "Speciální stránky",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Normální speciální stránky\n* <span class=\"mw-specialpagerestricted\">Speciální stránky s&nbsp;vyhrazeným přístupem</span>",
        "specialpages-group-maintenance": "Údržba",
        "specialpages-group-other": "Ostatní",
        "specialpages-group-login": "Přihlášení / vytvoření účtu",
index f604e6a..6a78683 100644 (file)
        "viewsourcetext": "Zdrojowi tekst starnë mòże przezérac ë kòpiérowac.",
        "editinginterface": "'''ÒSTRZÉGA:''' Editëjesz starnã, jakô zamëkô w se tekst interfejsu softwôrë. Wszëtczé zmianë tu zrobioné bãdze widzec na interfejse jinszëch brëkòwników.\nPrzemëszlë dolmaczënié na [https://translatewiki.net/wiki/Main_Page?setlang=csb translatewiki.net], ekstra ùdbie lokalizacëji softwôrë MediaWiki.",
        "exception-nologin": "Nie jes wlogòwôny/a",
-       "logouttext": "'''Jes wëlogòwóny.'''\nMòżesz robic dali na {{SITENAME}} jakno anonimòwi brëkòwnik abò sã <span class='plainlinks'>[$1 wlogòwac]</span> znowa jakno równy, a bò jinszi brëkòwnik.\nBôczë, że do czasu wëczëszczenia pòdrãczny pamiãcë przezérnika, niejedné starnë bãdą wëzdrzëc jakbë të bëł wlogòwóny.",
+       "logouttext": "<strong>Jes terô wëlogòwóny.</strong>\n\nBôczë, że do czasu wëczëszczeniô pòdrãczny pamiãcë przezérnika, niejedné starnë bãdą wëzdrzëc jakbë të bëł wlogòwóny.",
        "welcomeuser": "Witôj, $1!",
        "yourname": "Miono brëkòwnika",
        "userlogin-yourname": "Miono brëkòwnika",
        "passwordremindertitle": "Nowô doczasnô parola dlô {{SITENAME}}",
        "passwordremindertext": "Chtos (gwës Të, z adresë $1) pòprosëł ò wësłanié nowi\nparolë dlô {{SITENAME}} ($4). Aktualnô parola dlô brëkòwnika\n\"$2\" òsta ùsôdzonô ë nastôwionô jakno \"$3\". Jeżlë to bëło twòją\njintencëją, mùszisz sã terô wlogòwac ë zmienic swòją parolã.\nNowô parola je wôznô {{PLURAL:$5|dzéń|$5 dni}}.\nJeżlë chto jinszi wësłôł to zapëtanié, abò pamiãtôsz swòją parolã\në chcesz jã dali bez zmianë brëkòwac, zjignorëje to wiadło ë\nrobi dali ze starną parolą.",
        "noemail": "W baze ni ma email-adresë dlô brëkòwnika \"$1\".",
-       "acct_creation_throttle_hit": "Môsz ùsôdzoné ju {{PLURAL:$1|1 kònto|$1 kontów}}.\nNi mòżesz miec ju wicy.",
+       "acct_creation_throttle_hit": "Z adresë IP chtërny brëkùjesz òstało, w slédnych $2 ùsôdzonych {{PLURAL:$1|1 kònto|$1 kontów}}, co je maksymalną wielëną, \nBrëkòwnicy ti IP-adresë ni mògą terôczasno ùsôdzac wicy kòntów.",
        "emailauthenticated": "Twój adres e-mail òstôł pòcwierdzóny $2 ò $3.",
        "accountcreated": "Konto założone",
        "accountcreatedtext": "Kònto brëkòwnika dlô [[{{ns:User}}:$1|$1]], [[{{ns:User talk}}:$1|talk]] òstało ùsadzóné.",
        "headline_sample": "Tekst nadgłówka",
        "headline_tip": "Nadgłówk 2 lédżi",
        "nowiki_sample": "Wstawi tuwò niesfòrmatowóny tekst",
-       "nowiki_tip": "Ignorëjë wiki-fòrmatowanié",
+       "nowiki_tip": "Jignorëjë wiki-fòrmatowanié",
        "image_sample": "Przëmiôr.jpg",
        "image_tip": "Òbsôdzony lopk (n.p. òbrôzk)",
        "media_sample": "Przëmiôr.ogg",
        "showdiff": "Wëskrzëni zjinaczi",
        "anoneditwarning": "<strong>Bôczë:</strong> Të nie jes wlogòwóny. Jeżlë wëkònôsz jakąs zmianã, twòja adresa IP mdze widocznô dlô wszëtczich. Jeżlë <strong>[$1 wlogùjesz sã]</strong> abò <strong>[$2 ùsadzysz kònto]</strong>twòje zjinaczi òstóną przëpisóné do kònta, co wicy mającë kònto dobëjesz rozmajité ùdogòdnienia.",
        "anonpreviewwarning": "Të nie jes wlogòwóny. Jeżlë wprowadzysz jaczés zjinaczi, twòja adresa IP mdze ùmieszczónô w historie edicji starnë.",
-       "summary-preview": "Pòdzérk òpisënka:",
+       "summary-preview": "Pòdzérk òpisënka zjinaków:",
        "blockedtitle": "Brëkòwnik je zascëgóny",
        "blockedtext": "<strong>Twòje kònto abò ë IP-adresa òstałë zablokòwóné.</strong>\n\nZablokòwôł je $1.\nPòdónô przëczëna to:<em>$2</em>.\n\n * Zôczątk blokadë: $8\n * Kùńc blokadë: $6\n * Cél blokadë: $7\n\n\nBë zgwësnic sprawã zablokòwaniô mòżesz skòntaktowac sã z $1 abò jińszim [[{{MediaWiki:Grouppage-sysop}}|administratorã]].\nBoczë, że të ni mòżesz stądka sélac e-mailów, jeżlë nié môsz jesz zaregisterowóné e-mailowé adresë w [[Special:Preferences|nastôwach]].\nTwòjô aktualnô adresa IP to $3, a zablokòwónô adresa ID to #$5.\nProszëmë pòdac wëższé pòdôłczi przë wszëtczich pëtaniach.",
        "loginreqlink": "Wlogùjë",
        "yourdiff": "Zjinaczi",
        "copyrightwarning": "Bôczë, że wszëtczé edicëje w {{SITENAME}} są wprowadzané pòd zastrzégą $2 (òb. $1 dlô detalów). Jeżlë nie chcesz bë to co napiszesz bëło editowóné czë kòpijowóné, tedë nie zacwierdzôj nëch edicëjów.<br />Zacwierdzając zmianë dôwôsz parolã, że to co môsz napisóné je Twòjégò aùtorstwa, abò skòpijowóné z dostónków public domain abò jinëch wòlnëch licencëjów. '''NIE DODÔWÔJ CËZËCH TEKSTÓW BEZ ZEZWÒLENIÔ!'''",
        "copyrightwarning2": "Bôczë, że wszëtczé edicëje w {{SITENAME}} mògą bëc editowóné, zmienióné abò rëmniãté bez jinëch brëkòwników.\nJeżlë nie chcesz bë Twòja robòta bëła editowónô, tedë nie dodôwôj ji tuwò.<br />\nZacwierdzając zmianë dôwôsz zgòdã na to, że to co môsz napisóné je Twòjégò aùtorstwa, abò skòpijowóné z dostónków public domain abò jinëch wòlnëch licencëjów (zdrzë za detalama na $1).\n'''NIE DODÔWÔJ ROBÒTË CHRONIONY ÙSÔDZKÒWIMA PRAWAMA BEZ ZEZWÒLENIÔ!'''",
-       "readonlywarning": "'''BÔCZËNK: Pòdôwkòwô baza òsta sztërkòwô zablokòwónô dlô administracjowich célów. Ni mòże tej timczasã zapisac nowi wersëji artikla.\nBédëjemë przeniesc ji tekst do priwatnégò lopka (wëtnij/wstôw) ë ùchòwac na pózni.'''\n\nAdministrator, chtëren jã zablokòwôł, pòdôł przëczënã: $1",
+       "readonlywarning": "<strong>Bôczënk: Pòdôwkòwô baza òsta sztërkòwô zablokòwónô dlô administracjowich célów. Ni mòże tej timczasã zapisac nowi wersëji artikla.Jeżlë chcesz, mòżesz skòpirowac jã do lopka, abë móc jã pòzdze zapisac</strong>\nSprôwnik, chtëren jã zablokòwôł, pòdôł nôslédną ji przëczënã: $1",
        "titleprotectedwarning": "'''Czó! Starna ò ti pòzwie òsta zazychrowónô. Dlô ùsadzeniô ti starnë pòtrzébné są [[Special:ListGroupRights|apartné ùdowierzenia]].'''\nNiżi  je widzec slédny wpisënk z registru:",
        "templatesused": "{{PLURAL:$1|Ùżëtô szablona|Ùżëté szablónë}} w tim artiklu:",
        "templatesusedpreview": "{{PLURAL:$1|Szablóna ùżëtô|Szablónë użëté}} w tim pòdzérkù:",
        "template-protected": "(zazychrowónô)",
        "template-semiprotected": "(dzélowò zazychrowóné)",
        "hiddencategories": "Na starna przënôleżi do w {{PLURAL:$1|1 zatacony kategòrëji|$1 zataconych kategòrëjów}}:",
-       "permissionserrors": "Fela przistspù",
+       "permissionserrors": "Fela przistãpù",
        "permissionserrorstext-withaction": "Ni môsz przëstãpù do $2, z {{PLURAL:$1|nôslédny przëczënë|nôslédnych przëczënów}}:",
        "recreate-moveddeleted-warn": "<strong>Bôczënk! Chcesz usadzëc starnã, chtërna wczasni òsta rëmniãtô.</strong>\n\nÙgwësni sã, czë pònowné ùsôdzenié ti starnë je kònieczné. \nNiżi je widzec register rëmaniów i zmian pòzwë ti starnë:",
        "moveddeleted-notice": "Na starna òsta rëmniãtô.\nSpisënk rëmaniô ë zjinaków miona ti starnë je niżi.",
        "page_last": "kùńc",
        "histlegend": "Legenda: (aktualnô) = różnice w przërównanim do aktualny wersëje,\n(wczasniészô) = różnice w przërównanim do wczasniészi wersëje, D = drobné edicëje",
        "history-fieldset-title": "Szëkôj za wersëją",
-       "history-show-deleted": "Leno rëmniãté",
+       "history-show-deleted": "Blós rëmniãté edicëje",
        "histfirst": "òd nôstarszich",
        "histlast": "òd nônowszich",
        "history-feed-title": "Historëjô wersëji",
        "search-section": "(dzél $1)",
        "search-file-match": "(pasëje do zamkłoscë lopka)",
        "search-suggest": "Të mëszlôł ò: $1",
-       "search-interwiki-caption": "Sosterné ùdbë",
+       "search-interwiki-caption": "Skùtczi ze sostrnych ùdbów",
        "search-interwiki-default": "Wëniczi òd $1:",
        "search-interwiki-more": "(wicy)",
        "searchall": "wszëtczé",
        "saveprefs": "Zapiszë",
        "prefs-editing": "Edicëjô",
        "searchresultshead": "Szëkba",
-       "stub-threshold": "Greńca dlô fòrmatowaniô <a href=\"#\" class=\"stub\">lënków stubów</a>:",
+       "stub-threshold": "Fòrmat lënka dlô mniszich starnów ($1):",
        "recentchangesdays": "Kùli dni pòkazëwac w slédnëch edicëjach:",
        "recentchangescount": "Domëslnô wielëna wëskrzëniónych edicëjów",
        "savedprefs": "Twòjé nastôwë òstałë zapisóné.",
        "gender-female": "Białka",
        "email": "E-mail",
        "prefs-help-realname": "Prôwdzëwé miono je òptacjowé, a czej je dôsz, òstanié ùżëté do pòdpisaniô Twòjégò wkładu",
-       "prefs-help-email": "Adresa e-mail je òptacëjnô, zezwôlô równak sélac do ce nową parolã jak tã zabëjesz.\nMòżesz zezwòlëc jinszim brëkòwniką na łączbã z Tobą przez Twòją starnã abò starnã diskùsëji, bez mùszebnotë wëskrzënianiô swòjich pòdôwków.",
-       "editinguser": "Zmiana praw brëkòwnika '''[[User:$1|$1]]''' ([[User talk:$1|{{int:talkpagelinktext}}]]{{int:pipe-separator}}[[Special:Contributions/$1|{{int:contribslink}}]])",
+       "prefs-help-email": "Adresa e-mail je òptacjowô, zezwôlô równak na zresetowanié zabëti przez ce parolë.",
+       "editinguser": "Zmiana prawa przistãpù {{GENDER:$1|brëkòwnika|brëkòwniczczi}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-reason": "Przëczëna:",
        "group": "Karno:",
        "group-user": "Brëkòwnicë",
        "group-bot-member": "{{GENDER:$1|bòt}}",
        "group-sysop-member": "{{GENDER:$1|sprôwnik}}",
        "group-bureaucrat-member": "{{GENDER:$1|biórokrata|biórokratka}}",
-       "group-suppress-member": "rewizora",
+       "group-suppress-member": "{{GENDER:$1|rewizora|rewizorka}}",
        "grouppage-user": "{{ns:project}}:Brëkòwnicë",
        "grouppage-autoconfirmed": "{{ns:project}}:Aùtomatno zacwierdzeni brëkòwnicë",
        "grouppage-bot": "{{ns:project}}:Bòtë",
        "right-reupload-shared": "Môlowé nadpisëwanié egzystëjącegò lopka, we wespóldzelnych dostónkach",
        "right-upload_by_url": "Wladënk lopka z adresë URL",
        "right-purge": "Czëszczenié pòdrãczny pamiãcë starnë bez pëtaniô ò pòcwierdzenié",
-       "right-autoconfirmed": "Edicëjô dzélowò zazychrowónych starnów",
+       "right-autoconfirmed": "Bez ògrańczeniów przez òpiartë na adresë IP limitë",
        "right-bot": "Nacéchòwanié edicëjó jakno aùtomatnych",
        "right-writeapi": "Zapisënk przez jinterfejs API",
        "newuserlogpage": "Nowi brëkòwnicë",
        "newpageletter": "N",
        "boteditletter": "b",
        "rc-change-size-new": "$1 {{PLURAL:$1|bajt|bajtë|bajtów}} pò zjinace",
-       "rc-enhanced-expand": "Pòkażë detale (wëmôgô JavaScript)",
+       "rc-enhanced-expand": "Pòkażë detale",
        "rc-enhanced-hide": "Zatacë detale",
        "rc-old-title": "originalno ùsôdzoné jakno \"$1\"",
        "recentchangeslinked": "Zmianë w dolënkòwónëch",
        "uploadnologin": "Felënk logòwaniô",
        "uploadtext": "Brëkùjë negò fòrmùlara do wladënkù lopków.\nJeżlë chcesz przezdrzec abò szëkac w dotenczas wladowónëch lopkach, biéj do [[Special:FileList|lësta lopków]]. Kòżdi wladënk je registrowóny w [[Special:Log/upload|registrze wladënkù]], a rëmniãcé w [[Special:Log/delete|registrze rëmaniô]].\n\nAbë dodac lopk do starnë, ùżëjë ùniższegò lënka wedle nôslédnëch mùstrów:\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Lopk.jpg]]</nowiki></code>''' wëskrzëni całi lopk\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Lopk.png|200px|thumb|left|pòdpisënk òbrôzka]]</nowiki></code>''' wëskrzëni z lewi starnë, przë ùbrzégù, miniaturkã w szérzë 200 pikslów w ramie, z nôdpisã 'pòdpisënk òbrôzka'\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Lopk.ogg]]</nowiki></code>''' òtemknie prosti lënk do lopka bez wëskrzënianiô sómegò lopka",
        "uploadlogpage": "Dołączoné",
-       "uploadlogpagetext": "Hewò je lësta slédno wladowónëch lopków.\nWszëtczé gòdzënë tikają conë ùniwersalnégò czasë.",
+       "uploadlogpagetext": "Lësta slédno wladowónëch lopków.\nBiéj do [[Special:NewFiles|galerëji nowich lopków]] abë òbôczëc jich miniaturczi.",
        "filename": "Miono lopka",
        "filedesc": "Òpisënk",
        "fileuploadsummary": "Pòdrechòwanié:",
        "statistics-edits-average": "Strzédnô lëczba edicji na starnã",
        "statistics-users": "Zaregistrowónëch [[Special:ListUsers|brëkòwników]]",
        "statistics-users-active": "Aktiwnëch brëkòwników",
-       "statistics-users-active-desc": "Brekòwnicë, jaczi bëlë aktiwni òb òstatné $1 dni",
+       "statistics-users-active-desc": "Brekòwnicë, chtërni bëlë aktiwny {{PLURAL:$1|slédnegò dnia|slédnych $1 dni}}",
        "doubleredirects": "Dëbeltné przeczérowania",
        "double-redirect-fixer": "Naprôwiôcz przeczérowaniów",
        "brokenredirects": "Zerwóné przeczerowania",
        "categories": "Kategòrëje",
        "deletedcontributions": "Rëmniãti wkłôd brëkòwnika",
        "deletedcontributions-title": "Rëmniãti wkłôd brëkòwnika",
-       "linksearch": "Bùtnowé lënczi",
+       "linksearch": "Szëkba bùtnowich lënków",
        "activeusers": "Lësta aktiwnëch brëkòwników",
        "listgrouprights-members": "(lësta nôlëżników karna)",
        "emailuser": "Wëslë e-maila do negò brëkòwnika",
-       "defemailsubject": "E-mail òd {{SITENAME}}",
+       "defemailsubject": "{{SITENAME}} – e‐mail òd brëkòwnika \"$1\"",
        "noemailtitle": "Felënk email-adresë",
        "emailusername": "Pòzwa brëkòwnika",
        "emailfrom": "Òd:",
        "mywatchlist": "Lësta ùzérónëch artiklów",
        "watchlistfor2": "Dlô $1 $2",
        "watchnologin": "Felënk logòwóniô",
-       "addedwatchtext": "Starna \"[[:$1]]\" òsta dodónô do twòji [[Special:Watchlist|lëstë ùzérónëch artiklów]].\nNa ti lësce są registre przińdnëch zjinak ti starne ë na ji starnie dyskùsëji, a samò miono starnë mdze '''wëtłëszczone''' na [[Special:RecentChanges|lësce slédnich edicëji]], bë të mògł to òbaczëc.\n\nCzej chcesz remôc starnã z lëste ùzéronëch artiklów, klikni ''Òprzestôj ùzérac''.",
-       "removedwatchtext": "Starna \"[[:$1]]\" òsta rëmniãtô z Twòji [[Special:Watchlist|lëstë ùzérónych]].",
+       "addedwatchtext": "Starna \"[[:$1]]\" òsta dodónô do twòji [[Special:Watchlist|lëstë ùzérónëch artiklów]].",
+       "removedwatchtext": "Starna \"[[:$1]]\" ze starną diskùsëji òsta rëmniãtô z Twòji [[Special:Watchlist|lëstë ùzérónych artiklów]].",
        "watch": "Ùzérôj",
        "watchthispage": "Ùzérôj ną starnã",
        "unwatch": "Òprzestôj ùzerac",
        "rollbackfailed": "Nie szło copnąc zmianë",
        "alreadyrolled": "Ni mòże copnąc slédny edicëji starnë [[:$1]], chtërny ùsôdzcą je [[User:$2|$2]] ([[User talk:$2|Diskùsëjô]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nchtos jiny ju zeditowôł starnã abò copnął zmianë.\n\nSlédnym ùsódzcą starnë bëł [[User:$3|$3]] ([[User talk:$3|Diskùsëjô]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "revertpage": "Edicje brëkòwnika [[Special:Contributions/$2|$2]] ([[User talk:$2|diskùsjô]]) òstałë òdrzucóné. Aùtorã przëwrócóny wersji je [[User:$1|$1]].",
-       "rollback-success": "Edicje brëkòwnika $1 òstałë òdrzucóné; \nòsta przëwrócónô òstatnô wersjô, aùtorã chtërny je $2.",
+       "rollback-success": "Copniãto edicëje{{GENDER:$3|brëkòwnmika|brëkòwniczczi}} $1;\ndoprowpdzono nazôd slédną wersëjã ùsôdzcë {{GENDER:$4|$2}}.",
        "rollback-success-notify": "Edicje brëkòwnika $1 òstałë òdrzucóné; \nòsta przëwrócónô òstatnô wersjô, aùtorã chtërny je $2. [$3 Pòkażë zjinaczi]",
        "protectlogpage": "Zazychrowóné",
        "protectedarticle": "zazychrowónô [[$1]]",
        "modifiedarticleprotection": "zmienionô léga zazychrowaniô [[$1]]",
-       "unprotectedarticle": "òdzychrowóny [[$1]]",
+       "unprotectedarticle": "òdzychrowôÅ\82(wa) \"[[$1]]\"",
        "protectedarticle-comment": "{{GENDER:$2|Zazychrowôł|Zazychrowała}} „[[$1]]”",
        "prot_1movedto2": "$1 przeniesłé do $2",
        "protect-legend": "Pòcwierdzë zazychrowanié",
        "protect_expiry_old": "Czas wëgasniãcô leżi w przińdnocë.",
        "protect-text": "Mòżesz tuwò sprôwdzëc ë zjinaczëc légã zazychrowaniô starnë '''$1'''.",
        "protect-locked-access": "Ni môsz dosc prawa do zjinaczi lédżi zazychrowaniô starnë. Aktualny nastôw dlô starnë '''$1''':",
-       "protect-cascadeon": "Na starna je zazychrowónô przed edicëją, dlôte że je brëkòwónô przez {{PLURAL:$1|nôslédną starnã, chtërnô òsta zazychrowónô|nôslédné starnë, chtërné òstałe zazychrowóné}} z aktiwną kaskadową òpatcëją zazychrowëwaniô.\nMòżesz zmienic légã zazychrowaniô, nie bãdze to równak miało cëskù na kaskadowé zazychrowanié.",
+       "protect-cascadeon": "Na starna je zazychrowónô przed edicëją, dlôte że je brëkòwónô przez {{PLURAL:$1|nôslédną starnã, chtërnô òsta zazychrowónô|nôslédné starnë, chtërné òstałe zazychrowóné}} z aktiwną kaskadową òpatcëją zazychrowëwaniô.\nZmiana miarë zazychrowaniô ni mô cëskù na kaskadowé zazychrowanié.",
        "protect-default": "Zezwòlë wszëtczim brëkòwnikòm",
-       "protect-fallback": "Wëmôgô prawów \"$1\"",
-       "protect-level-autoconfirmed": "Blokùjë nowich ë nieregistrowónëch brëkòwników",
-       "protect-level-sysop": "blós sprôwnicë (sysopë)",
+       "protect-fallback": "Zezwòlë blós brëkòwnikòm z prawama \"$1\"",
+       "protect-level-autoconfirmed": "Zezwòlë blós aùtomatno zacwierdzonym brëkòwnikòm",
+       "protect-level-sysop": "Zezwòlë blós sprôwnikòm",
        "protect-summary-cascade": "kaskadowanié",
        "protect-expiring": "wëgasô $1 (UTC)",
        "protect-expiry-indefinite": "na wiedno",
        "sp-contributions-newbies": "Pòkażë edicëjã blós nowich brëkòwników",
        "sp-contributions-newbies-sub": "Dlô nowich brëkòwników",
        "sp-contributions-blocklog": "historëjô blokòwaniô",
-       "sp-contributions-deleted": "rëmniãti wkłôd brëkòwnika",
+       "sp-contributions-deleted": "rëmniãtô robòta {{GENDER:$1|brëkòwnika|brëkòwniczczi}}",
        "sp-contributions-uploads": "Wësłóné lopczi",
        "sp-contributions-logs": "Rejestr logòwaniô",
-       "sp-contributions-talk": "diskùsjô",
+       "sp-contributions-talk": "diskùsëjô",
        "sp-contributions-blocked-notice-anon": "Ta adresa IP je w tim sztërkù zablokòwónô.\nSlédny wpisënk z registru blokòwaniów je widzec niżi:",
        "sp-contributions-search": "Szëkba za edicëjama",
        "sp-contributions-username": "Adresa IP abò miono brëkòwnika:",
        "whatlinkshere-hidelinks": "$1 lënczi",
        "whatlinkshere-hideimages": "$1 lënk z lopków",
        "whatlinkshere-filters": "Filtrë",
-       "blockip": "Zascëgôj IP-adresã",
+       "blockip": "Blokùjë {{GENDER:$1|brëkòwnika|brëkòwniczkã}}",
        "blockiptext": "Brëkùje formùlarza niżi abë zascëgòwac prawò zapisënkù spòd gwësny adresë IP. To robi sã blós dlôte abë zascëgnąc wandalëznom, a bëc w zgòdze ze [[{{MediaWiki:Policy-url}}|wskôzama]]. Pòdôj przëczënã (np. dając miona starn, na chtërnëch dopùszczono sã wandalëzny).",
        "ipbreason": "Przëczëna:",
        "ipboptions": "2 gòdzënë:2 hours,1 dzéń:1 day,3 dni:3 days,1 tidzéń:1 week,2 tigòdnie:2 weeks,1 ksãżëc:1 month,3 ksãżëcë:3 months,6 ksãżëców:6 months,1 rok:1 year,na wiedno:infinite",
        "badipaddress": "IP-adresa nie je richtich pòdónô.",
        "blockipsuccesssub": "Zascëgónié dało sã",
        "blockipsuccesstext": "Brëkòwnik [[Special:Contributions/$1|$1]] òstał zascëgóny.<br />\nBiéj do [[Special:BlockList|lëstë zascëgónëch adresów IP]] abë òbaczëc zascëdżi.",
-       "ipblocklist": "Lësta zablokòwónëch adresów IP ë mionów brëkòwników",
+       "ipblocklist": "Zablokòwóni brëkòwnicë",
        "blocklist-timestamp": "Czasowô sygnatura",
        "blocklist-target": "Cél",
        "blocklist-expiry": "Ùpłiwô",
        "unblocklink": "òdblokùjë",
        "change-blocklink": "zmieni blokòwanié",
        "contribslink": "wkłôd",
-       "autoblocker": "Zablokòwóno ce aùtomatnie, ga brëkùjesz ti sami adresë IP co brëkòwnik \"[[User:$1|$1]]\". Przëczënô blokòwóniô $1 to: \"'''$2'''\".",
+       "autoblocker": "Zablokòwóno ce aùtomatno bò brëkùjesz ti sómy adresë IP co brëkòwnik \"[[User:$1|$1]]\". \nPrzëczënô blokòwaniô $1 to: \"$2\"",
        "blocklogpage": "Historëjô blokòwaniô",
        "blocklogentry": "zablokòwôł [[$1]], czas blokadë: $2 $3",
        "reblock-logentry": "{{GENDER:$2|zjinacził|zjinacziła}} unastôw blokadë dlô [[$1]], czas blokadë: $2 $3",
        "proxyblocker": "Blokòwanié proxy",
        "lockbtn": "Zascëgôj bazã pòdôwków",
        "move-page-legend": "Przeniesë starnã",
-       "movepagetext": "Z pòmòcą ùiższegò fòrmùlôra zjinaczisz miono starnë, przenosząc równoczasno ji historëjã.\nPòd stôrim titlã bãdze ùsôdzonô przeczérowùjącô starna.\nMòżesz aùtomatno zaktualniac przeczérowania wskazëwôjące titel przed zjinaką.\nJeżlë nie wëbiérzesz ti òptacëji, ùgwësni sã pò przenieseniu starnë, czë nie òstałé ùsôdzoné [[Special:DoubleRedirects|dëbeltné]] abò [[Special:BrokenRedirects|zerwóné przeczérowania]].\nJes òdpòwiedzalny za to, abë lënczi dali robiłë tam dze mają.\n\nStarna '''ni''' bãdze przeniosłô, jeżlë starna ò nowim mionie ju je, chòba że je òna pùstô abò je przeczérowaniém ë mô pùstą historëjã edicëji.\nTo òznôczô, że lëchą òperacëjã zjinaczi miona mòże doprowôdzëc bezpieczno nazôd, zjinaczając nowé miono starnë nawczasniészą, ë że ni mòże nadpisac stranë chtërną ju dô.\n\n'''BÔCZËNK!'''\nTo mòże bëc drasticznô abò nieprzewidëwólnô zjinaka w przëtrôfkù pòpùlarnych starnów.\nÙgwësni sã co do skùtków ti òperacëji, niglë to zrobisz.",
+       "movepagetext": "Z pòmòcą ùiższegò fòrmùlôra zjinaczisz miono starnë, przenosząc równoczasno ji historëjã.\nPòd stôrim titlã bãdze ùsôdzonô przeczérowùjącô starna.\nMòżesz aùtomatno zaktualniac przeczérowania wskazëwôjące titel przed zjinaką.\nJeżlë nie wëbiérzesz ti òptacëji, ùgwësni sã pò przenieseniu starnë, czë nie òstałé ùsôdzoné [[Special:DoubleRedirects|dëbeltné]] abò [[Special:BrokenRedirects|zerwóné przeczérowania]].\nJes òdpòwiedzalny za to, abë lënczi dali robiłë tam dze mają.\n\nStarna <strong>ni</strong> bãdze przeniosłô, jeżlë starna ò nowim mionie ju je, chòba że je òna pùstô abò je przeczérowaniém ë mô pùstą historëjã edicëji.\nTo òznôczô, że lëchą òperacëjã zjinaczi miona mòże doprowôdzëc bezpieczno nazôd, zjinaczając nowé miono starnë nawczasniészą, ë że ni mòże nadpisac stranë chtërną ju dô.\n\n<strong>BÔCZËNK!</strong>\nTo mòże bëc drasticznô abò nieprzewidëwólnô zjinaka w przëtrôfkù pòpùlarnych starnów.\nÙgwësni sã co do skùtków ti òperacëji, niglë to zrobisz.",
        "movepagetalktext": "Sparłãczonô starna diskùsëji, jeżlë ju je, to bãdze przeniosłô aùtomatno, chòba że:\n*niepùstô starna diskùsëji ju je z nowim mionã\n*rëmniész nacéchòwanié z niższegò pòla wëbiérkù\n\nW taczich przëtrôfkach zamkłosc diskùsëji mòże przeniesc blós rãczno.",
        "newtitle": "Nowi titel:",
        "move-watch": "Ùzérôj tã starnã",
        "allmessagesnotsupportedDB": "'''{{ns:special}}:Allmessages''' nie mòże bëc brëkòwónô, temù że '''$wgUseDatabaseMessages''' je wëłączony.",
        "thumbnail-more": "Zwikszi",
        "import": "Impòrtëjë starnë",
-       "importlogpage": "Log impòrtu",
+       "importlogpage": "Log jimpòrtu",
        "tooltip-pt-userpage": "{{GENDER:|Twòja}} starna brëkòwnika",
        "tooltip-pt-mytalk": "{{GENDER:|Mòjô}} starna diskùsëji",
        "tooltip-pt-anontalk": "Diskùsjô brëkòwnika dlô ti adresë IP",
        "tooltip-summary": "Wpiszë wãzłowati òpisënk",
        "anonymous": "Anonimòwi {{PLURAL:$1|brëkòwnik|brëkòwnicë}} na {{SITENAME}}",
        "siteuser": "Brëkòwnik {{SITENAME}} $1",
-       "lastmodifiedatby": "Na starna bëła slédno editowónô $2, $1 przez $3.",
+       "lastmodifiedatby": "Slédno edicëjô ti starnë: $2, $1, ùsôdzca: $3.",
        "othercontribs": "Òpiarté na prôcë $1.",
        "others": "jiné",
        "spamprotectiontitle": "Anti-spamòwi filter",
        "pageinfo-robot-policy": "Jindeksowanié przez robòtë",
        "pageinfo-robot-index": "Zezwòloné",
        "pageinfo-robot-noindex": "Niedozwóloné",
-       "pageinfo-watchers": "Wielëna ùżérających",
+       "pageinfo-watchers": "Wielëna ùzérających",
        "pageinfo-few-watchers": "Mni jak $1 {{PLURAL:$1|ùzyrający|ùzyrających}}",
        "pageinfo-redirects-name": "Wielëna przeczérowaniów do ti starnë",
        "pageinfo-subpages-name": "Wielëna pòdstarnów ti starnë",
        "patrol-log-page": "Log patrolowaniô",
        "previousdiff": "← Pòprzédnô edicëjô",
        "nextdiff": "Nôslédnô edicëjô →",
-       "imagemaxsize": "Ògrańczë na starnie òpisënkù òbrôzków jich miarã do:",
+       "imagemaxsize": "Ograńczenié wielgòscë òbrôzków:<br /><em>(na starnach òpisënkù lopków)</em>",
        "thumbsize": "Miara miniaturków:",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|starna|starnë|starnów}}",
        "file-info-size": "$1 × $2 pikslów, miara lopka: $3, ôrt MIME: $4",
        "imgmultigo": "Biéj!",
        "imgmultigoto": "Biéj do starnë $1",
        "autoredircomment": "Przeczérowanié do [[$1]]",
-       "autosumm-new": "Pòwsta nowô starna:",
+       "autosumm-new": "Ùsôdzonô nowô starna \"$1\"",
        "watchlisttools-clear": "Wëczësczë ùzérówną lëstã",
        "watchlisttools-view": "Òbaczë wôżnészé zmianë",
        "watchlisttools-edit": "Òbaczë a editëjë lëstã ùzérónëch artiklów",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|diskùsëjô]])",
        "version": "Wersëjô",
        "redirect": "Przeczérëjë z jidentyfikatora lopka, brëkòwnika, starnë, wersëji abò wpisënka loga",
-       "redirect-summary": "Na szpecjalnô starna przczerowùje do: lopka(ò pòdónym mionie), do sstarny (ò pòdónym numrze wersëji abò jidentyfikatorze starë), do starnë brëkòwnika (ò pòdónym numerowim jidentyfikatorze) abò do rejestru (ò pòdónym numrze akcëji). Òrt ùżëcô: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] abò [[{{#Special:Redirect}}/logid/186]].",
+       "redirect-summary": "Na specjalnô starna przczerowùje do: lopka(ò pòdónym mionie), do starny (ò pòdónym numrze wersëji abò jidentyfikatorze starë), do starnë brëkòwnika (ò pòdónym numerowim jidentyfikatorze) abò do rejestru (ò pòdónym numrze akcëji). Òrt ùżëcô: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] abò [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Biéj",
        "redirect-lookup": "Szëkôj:",
        "redirect-value": "Wôrtnota:",
index de353e4..d4ad69f 100644 (file)
        "fileduplicatesearch-noresults": "Ni ddaethpwyd o hyd i ffeil o'r enw \"$1\".",
        "specialpages": "Tudalennau arbennig",
        "specialpages-note-top": "Allwedd",
-       "specialpages-note": "* Tudalennau arbennig ar gael i bawb.\n* <span class=\"mw-specialpagerestricted\">Tudalennau arbennig cyfyngedig.</span>",
        "specialpages-group-maintenance": "Adroddiadau cynnal a chadw",
        "specialpages-group-other": "Eraill",
        "specialpages-group-login": "Mewngofnodi / creu cyfrif",
index 68727dd..2c080f6 100644 (file)
@@ -62,7 +62,8 @@
                        "Jhertel",
                        "IBDJ",
                        "SimmeD",
-                       "BoBrandt"
+                       "BoBrandt",
+                       "R12ntech"
                ]
        },
        "tog-underline": "Understreg henvisninger:",
        "rcfilters-highlightmenu-help": "Vælg en farve for at fremhæve denne egenskab",
        "rcfilters-filterlist-noresults": "Ingen filtre fundet",
        "rcfilters-noresults-conflict": "Ingen resultater fundet fordi søgekriterierne er i konflikt",
-       "rcfilters-filtergroup-registration": "Brugerregistrering",
-       "rcfilters-filter-registered-label": "Registrerede",
-       "rcfilters-filter-registered-description": "Indloggede brugere",
-       "rcfilters-filter-unregistered-label": "Uregistrerede",
-       "rcfilters-filter-unregistered-description": "Redaktører, der ikke er logget ind.",
        "rcfilters-filtergroup-authorship": "Bidragets forfatter",
        "rcfilters-filter-editsbyself-label": "Ændringer af dig",
        "rcfilters-filter-editsbyself-description": "Dine egne bidrag.",
        "rcfilters-filter-editsbyother-label": "Ændringer af andre",
        "rcfilters-filter-editsbyother-description": "Alle ændringer undtagen din egen.",
        "rcfilters-filtergroup-userExpLevel": "Erfaringsniveau (kun for registrerede brugere)",
-       "rcfilters-filtergroup-user-experience-level-conflicts-unregistered": "Erfaringsfiltre finder kun registrerede brugere, så dette filter er i konflikt med filtret \"Uregistrerede\".",
+       "rcfilters-filter-user-experience-level-registered-label": "Registrerede",
+       "rcfilters-filter-user-experience-level-registered-description": "Indloggede brugere",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Uregistrerede",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Redaktører, der ikke er logget ind.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Nybegyndere",
        "rcfilters-filter-user-experience-level-newcomer-description": "Færre end 10 redigeringer og 4 dages aktivitet",
        "rcfilters-filter-user-experience-level-learner-label": "Let øvede",
        "version-libraries-license": "Licens",
        "version-libraries-description": "Beskrivelse",
        "version-libraries-authors": "Forfattere",
-       "redirect": "Omdirigering pga. fil, bruger-, side- eller udgave-ID",
-       "redirect-summary": "Denne specialside omdirigerer til en fil (hvis filnavnet er angivet), en side (hvis udgave ID'et eller side ID'et er angivet) eller en brugerside (hvis et numerisk brugernummer er angivet). Eksempler på brug: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]],[[{{#Special:Redirect}}/revision/328429]] eller [[{{#Special:Redirect}}/user/101]].",
+       "redirect": "Omdiriger via filnavn, bruge-, side-, revision- eller log-ID.",
+       "redirect-summary": "Denne spesialsiden omdirigerer til en fil (hvis et filnavn angis), en lave (om revisions- eller side-ID angis), en brugerside (om bruge-ID angis), eller en loggoppføring (om log-ID angis). Bruk: [[{{#Special:Redirect}}/file/Eksempel.jpg]], [[{{#Special:Redirect}}/page/#64308]], [[{{#Special:Redirect}}/revision/#328429]], [[{{#Special:Redirect}}/user/#101]] eller [[{{#Special:Redirect}}/logid/#186]].",
        "redirect-submit": "Vis",
        "redirect-lookup": "Slå op:",
        "redirect-value": "Værdi:",
        "fileduplicatesearch-noresults": "Ingen fil med navnet \"$1\" blev fundet.",
        "specialpages": "Specialsider",
        "specialpages-note-top": "Forklaring",
-       "specialpages-note": "* Normale specialsider.\n* <span class=\"mw-specialpagerestricted\">Specialsider med begrænset adgang.</span>",
        "specialpages-group-maintenance": "Vedligeholdelsesside",
        "specialpages-group-other": "Andre specialsider",
        "specialpages-group-login": "Log på / opret bruger",
        "htmlform-title-not-exists": "$1 findes ikke.",
        "logentry-delete-delete": "$1 {{GENDER:$2|slettede}} siden $3",
        "logentry-delete-delete_redir": "$1 {{GENDER:$2|slettede}} omdirigering $3 ved overskrivning",
-       "logentry-delete-restore": "$1 {{GENDER:$2|gendannede}} siden $3",
+       "logentry-delete-restore": "$1 {{GENDER:$2|gendannede}} siden $3 ($4)",
        "logentry-delete-event": "$1 {{GENDER:$2|ændrede}} synligheden af {{PLURAL:$5|en loghændelse|$5 loghændelser}} for siden $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|ændrede}} synligheden af {{PLURAL:$5|en version|$5 versioner}} af siden $3: $4",
        "logentry-delete-event-legacy": "$1 {{GENDER:$2|ændrede}} synligheden af loghændelser for siden $3",
index 207f478..0969376 100644 (file)
        "rcfilters-filter-editsbyself-description": "Deine eigenen Beiträge.",
        "rcfilters-filter-editsbyother-label": "Änderungen von anderen",
        "rcfilters-filter-editsbyother-description": "Alle Änderungen außer deine eigenen.",
-       "rcfilters-filtergroup-userExpLevel": "Anmeldung und Erfahrung",
+       "rcfilters-filtergroup-userExpLevel": "Benutzeranmeldung und -erfahrung",
        "rcfilters-filter-user-experience-level-registered-label": "Angemeldet",
        "rcfilters-filter-user-experience-level-registered-description": "Angemeldete Autoren.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Unangemeldet",
        "rcfilters-hideminor-conflicts-typeofchange-global": "Der Filter „Kleine Bearbeitungen“ kollidiert mit einem oder mehreren Änderungstypfiltern, da bestimmte Änderungstypen nicht als „klein“ festgelegt werden können. Die kollidierenden Filter sind oben im Bereich der aktiven Filter markiert.",
        "rcfilters-hideminor-conflicts-typeofchange": "Bestimmte Änderungstypen können nicht als „klein“ festgelegt werden, so dass dieser Filter mit den folgenden Änderungstypfiltern kollidiert: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Dieser Änderungstypfilter kollidiert mit dem Filter „Kleine Bearbeitungen“. Bestimmte Änderungstypen können nicht als „klein“ festgelegt werden.",
-       "rcfilters-filtergroup-lastRevision": "Letzte Version",
-       "rcfilters-filter-lastrevision-label": "Letzte Version",
-       "rcfilters-filter-lastrevision-description": "Die aktuellste Änderung an einer Seite.",
-       "rcfilters-filter-previousrevision-label": "Frühere Versionen",
-       "rcfilters-filter-previousrevision-description": "Alle Änderungen, die nicht die aktuellste Änderung an einer Seite sind.",
+       "rcfilters-filtergroup-lastRevision": "Aktuellste Versionen",
+       "rcfilters-filter-lastrevision-label": "Aktuellste Version",
+       "rcfilters-filter-lastrevision-description": "Nur die aktuellste Änderung an einer Seite.",
+       "rcfilters-filter-previousrevision-label": "Nicht die aktuellste Version",
+       "rcfilters-filter-previousrevision-description": "Alle Änderungen, die nicht die „aktuellste Version“ sind.",
        "rcfilters-filter-excluded": "Ausgeschlossen",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:nicht</strong> $1",
        "rcfilters-exclude-button-off": "Ausgewählte ausschließen",
        "delete-warning-toobig": "Diese Seite hat mit mehr als $1 {{PLURAL:$1|Version|Versionen}} eine sehr lange Versionsgeschichte. Das Löschen kann zu Störungen im Datenbankbetrieb führen.",
        "deleteprotected": "Du kannst diese Seite nicht löschen, da sie geschützt wurde.",
        "deleting-backlinks-warning": "<strong>Warnung:</strong> Es verweisen noch [[Special:WhatLinksHere/{{FULLPAGENAME}}|andere Seiten]] auf diese zu löschende Seite oder sie ist noch an anderer Stelle eingebunden.",
+       "deleting-subpages-warning": "<strong>Warnung:</strong> Die Seite, die du löschen möchtest, hat [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|eine Unterseite|$1 Unterseiten|51=über 50 Unterseiten}}]].",
        "rollback": "Zurücksetzen der Änderungen",
        "rollbacklink": "Zurücksetzen",
        "rollbacklinkcount": "{{PLURAL:$1|Eine Version|$1 Versionen}} zurücksetzen",
        "fileduplicatesearch-noresults": "Es wurde keine Datei namens „$1“ gefunden.",
        "specialpages": "Spezialseiten",
        "specialpages-note-top": "Legende",
-       "specialpages-note": "* Reguläre Spezialseiten\n* <span class=\"mw-specialpagerestricted\">Zugriffsbeschränkte Spezialseiten</span>",
+       "specialpages-note-restricted": "* Reguläre Spezialseiten\n* <span class=\"mw-specialpagerestricted\">Zugriffsbeschränkte Spezialseiten</span>",
        "specialpages-group-maintenance": "Wartungslisten",
        "specialpages-group-other": "Andere Spezialseiten",
        "specialpages-group-login": "Benutzerkonto",
index b9b8980..eed494d 100644 (file)
        "oct": "Pthi",
        "nov": "Ptht",
        "dec": "Pthr",
-       "pagecategories": "{{PLURAL:$1|bekätakthook|bekätakthuɔk}}",
+       "pagecategories": "{{PLURAL:$1|Bekätakthook|Bekätakthuɔk}}",
        "category_header": "Apääm në bekätakthook \"$1\"ic",
        "subcategories": "Bekätakthuɔkkor",
-       "category-media-header": "Kuat në bekätakthook  $1 yic",
+       "category-media-header": "Kuat në bekätakthook \"$1\" yic",
        "hidden-categories": "{{PLURAL:$1|Bekätakthook cï thiaan|Bekätakthuɔk cï thiaan}}",
        "category-subcat-count": "{{PLURAL:$2|Bekätakthookë anɔŋ bekätakthookkorkɛ̈ kepɛ̈c.|Bekätakthookë anɔŋ {{PLURAL:$1|bekätakthookkorë|$1 bekätakthuɔkkorkɛ̈}}, në $2 yic̈;}}",
        "category-article-count": "{{PLURAL:$2|Bekätakthookë anɔŋic yärë yetök.|{{PLURAL:$1|Yärë atɔ̈|$1 yɔ̈rkɛ̈ aatɔ̈}} bekätakthook thiöökë yic, në $2 yic.}}",
        "recentchangeslinked-toolbox": "Kaceyiicwar nɔŋ kar",
        "recentchangeslinked-title": "Weer thöŋ kekë \"$1\"",
        "recentchangeslinked-summary": "Kän areny de wɛ̈r cïloi wɛ̈ramɛn tënɔŋ apam nuɛtke apam nhic (nadëk ka nuɛtke kɔcakuut de bekätakthook nhic).\nApam tɔ̈ [[Special:Watchlist|abërtïtdu]] aa <strong>gɔ̈tdïtnyin</strong>.",
-       "recentchangeslinked-page": "Rin ë akap:",
+       "recentchangeslinked-page": "Rin ë apam:",
        "recentchangeslinked-to": "Nyuɔɔthë kä cï ke waar në apɛ̈m cï nuɛ̈ɛ̈t ke apam tiöökë, ku acie kä cï ke waar në yen apam thiöökë yic",
        "upload": "Wälë apamduööt",
        "filedesc": "Cuutyic",
        "watchlist": "Abërtït",
        "mywatchlist": "Abërtït",
        "watch": "Ɣoi",
+       "watchthispage": "Watch this page",
        "watchlist-hide": "Thaan",
        "watchlist-submit": "Nyooth",
        "historyaction-submit": "Nyooth",
index fd7921f..85dd493 100644 (file)
        "rcfilters-hideminor-conflicts-typeofchange-global": "The \"Minor edits\" filter conflicts with one or more Type of change filters, because certain types of change cannot be designated as \"minor\". The conflicting filters are marked in the Active filters area, above.",
        "rcfilters-hideminor-conflicts-typeofchange": "Certain types of change cannot be designated as \"minor\", so this filter conflicts with the following Type of Change filters: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "This Type of change filter conflicts with the \"Minor edits\" filter. Certain types of change cannot be designated as \"minor\".",
-       "rcfilters-filtergroup-lastRevision": "Last revision",
-       "rcfilters-filter-lastrevision-label": "Last revision",
-       "rcfilters-filter-lastrevision-description": "The most recent change to a page.",
-       "rcfilters-filter-previousrevision-label": "Earlier revisions",
-       "rcfilters-filter-previousrevision-description": "All changes that are not the most recent change to a page.",
+       "rcfilters-filtergroup-lastRevision": "Latest revisions",
+       "rcfilters-filter-lastrevision-label": "Latest revision",
+       "rcfilters-filter-lastrevision-description": "Only the most recent change to a page.",
+       "rcfilters-filter-previousrevision-label": "Not the latest revision",
+       "rcfilters-filter-previousrevision-description": "All changes that are not the \"latest revision\".",
        "rcfilters-filter-excluded": "Excluded",
        "rcfilters-tag-prefix-namespace": ":$1",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:not</strong> $1",
        "specialpages": "Special pages",
        "specialpages-summary": "",
        "specialpages-note-top": "Legend",
-       "specialpages-note": "* Normal special pages.\n* <span class=\"mw-specialpagerestricted\">Restricted special pages.</span>",
+       "specialpages-note-restricted": "* Normal special pages.\n* <span class=\"mw-specialpagerestricted\">Restricted special pages.</span>",
+       "specialpages-note-cached": "-",
        "specialpages-group-maintenance": "Maintenance reports",
        "specialpages-group-other": "Other special pages",
        "specialpages-group-login": "Login / create account",
index 9dbfd7f..965bc54 100644 (file)
        "rcfilters-legend-heading": "<strong>Lista de abreviaturas:</strong>",
        "rcfilters-activefilters": "Filtros activos",
        "rcfilters-advancedfilters": "Filtros avanzados",
+       "rcfilters-limit-title": "Cambios para mostrar",
+       "rcfilters-limit-shownum": "Mostrar los últimos $1 cambios",
+       "rcfilters-days-title": "Días recientes",
+       "rcfilters-hours-title": "Horas recientes",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|día|días}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|hora|horas}}",
        "rcfilters-quickfilters": "Filtros guardados",
        "rcfilters-quickfilters-placeholder-title": "Ningún enlace guardado aún",
        "rcfilters-quickfilters-placeholder-description": "Para guardar tus ajustes de filtro y reutilizarlos más tarde, pulsa en el icono del marcador en el área de Filtro activo que se encuentra a continuación.",
        "rcfilters-invalid-filter": "Filtro no válido",
        "rcfilters-empty-filter": "No hay filtros activos. Se muestran todas las contribuciones.",
        "rcfilters-filterlist-title": "Filtros",
-       "rcfilters-filterlist-whatsthis": "¿Qué es esto?",
+       "rcfilters-filterlist-whatsthis": "¿Cómo funcionan?",
        "rcfilters-filterlist-feedbacklink": "Comparte tus comentarios sobre los filtros (beta) nuevos",
        "rcfilters-highlightbutton-title": "Resaltar los resultados",
        "rcfilters-highlightmenu-title": "Selecciona un color",
        "rcfilters-hideminor-conflicts-typeofchange-global": "El filtro \"Ediciones menores\" está en conflicto con uno o más Tipos de filtros de Cambio, ya que ciertos tipos de cambio no pueden ser designados como \"menores\". Los filtros en conflicto están marcados en el área Filtros activos, anterior.",
        "rcfilters-hideminor-conflicts-typeofchange": "Ciertos tipos de cambio no pueden ser designados como \"menores\", por lo que este filtro entra en conflicto con los siguientes  Tipos de filtros de Cambio: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Este filtro «Tipo de cambio» entra en conflicto con el filtro «Ediciones menores». Hay ciertos tipos de cambios que no pueden denominarse «menores».",
-       "rcfilters-filtergroup-lastRevision": "Revisión actual",
-       "rcfilters-filter-lastrevision-label": "Revisión actual",
-       "rcfilters-filter-lastrevision-description": "El cambio más reciente a una página.",
-       "rcfilters-filter-previousrevision-label": "Revisiones anteriores",
-       "rcfilters-filter-previousrevision-description": "Todos los cambios que no son los más recientes cambian a una página.",
+       "rcfilters-filtergroup-lastRevision": "Últimas revisiones",
+       "rcfilters-filter-lastrevision-label": "Última revisión",
+       "rcfilters-filter-lastrevision-description": "Solo el cambio más reciente a una página.",
+       "rcfilters-filter-previousrevision-label": "No la última revisión",
+       "rcfilters-filter-previousrevision-description": "Todos los cambios que no son la \"última revisión\".",
        "rcfilters-filter-excluded": "Excluido",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>Estado:</strong> $1",
+       "rcfilters-exclude-button-off": "Excluir los seleccionados",
+       "rcfilters-exclude-button-on": "Excluyendo los seleccionados",
        "rcfilters-view-tags": "Ediciones etiquetadas",
        "rcfilters-view-namespaces-tooltip": "Filtrar resultados por espacio de nombres",
        "rcfilters-view-tags-tooltip": "filtrado de resultados usando etiquetas de edición",
        "fileduplicatesearch-noresults": "Ningún archivo con el nombre «$1» encontrado.",
        "specialpages": "Páginas especiales",
        "specialpages-note-top": "Leyenda",
-       "specialpages-note": "* Páginas especiales normales.\n* <span class=\"mw-specialpagerestricted\">Páginas especiales restringidas.</span>",
        "specialpages-group-maintenance": "Informes de mantenimiento",
        "specialpages-group-other": "Otras páginas especiales",
        "specialpages-group-login": "Acceder/crear cuenta",
index b3bed3d..a8d1e96 100644 (file)
        "recentchanges-legend-heading": "<strong>Seletus:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (vaata ka [[Special:NewPages|uute lehekülgede loendit]])",
        "recentchanges-submit": "Näita",
+       "rcfilters-legend-heading": "<strong>Lühendite loetelu:</strong>",
        "rcfilters-activefilters": "Aktiivsed filtrid",
        "rcfilters-advancedfilters": "Täpsemad filtrid",
+       "rcfilters-limit-title": "Näita nii mitut muudatust",
+       "rcfilters-limit-shownum": "Näita viimast $1 muudatust",
+       "rcfilters-days-title": "Viimased päevad",
+       "rcfilters-hours-title": "Viimased tunnid",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|päev|päeva}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|tund|tundi}}",
        "rcfilters-quickfilters": "Salvestatud filtrid",
        "rcfilters-quickfilters-placeholder-title": "Linke pole veel salvestatud",
        "rcfilters-quickfilters-placeholder-description": "Et filtri sätted salvestada ja et neid hiljem uuesti kasutada, klõpsa alloleva aktiivsete filtrite loendi juures järjehoidjaikooni.",
        "rcfilters-invalid-filter": "Vigane filter",
        "rcfilters-empty-filter": "Aktiivsed filtrid puuduvad. Näidatakse kogu kaastööd.",
        "rcfilters-filterlist-title": "Filtrid",
-       "rcfilters-filterlist-whatsthis": "Mis see on?",
+       "rcfilters-filterlist-whatsthis": "Kuidas see töötab?",
        "rcfilters-filterlist-feedbacklink": "Anna uute filtrite beetaversiooni kohta tagasisidet",
        "rcfilters-highlightbutton-title": "Tulemuste esiletõst",
        "rcfilters-highlightmenu-title": "Vali värvus",
        "rcfilters-noresults-conflict": "Tulemusi ei leitud, sest otsikriteeriumid on konfliktsed.",
        "rcfilters-state-message-subset": "See filter ei tee midagi, sest selle tulemused on kaasatud {{PLURAL:$2|järgmise laiema filtri|järgmiste laiemate filtrite}} tulemustes (tulemuste eristamiseks proovi esiletõstu): $1",
        "rcfilters-state-message-fullcoverage": "Ühe rühma kõigi filtrite valimine on samaväärne mitte ühegi filtri valimisega, mistõttu ei tee see filter midagi. Rühmas on: $1",
-       "rcfilters-filtergroup-registration": "Registreerumine",
-       "rcfilters-filter-registered-label": "Registreerunud",
-       "rcfilters-filter-registered-description": "Sisse logitud kasutajad.",
-       "rcfilters-filter-unregistered-label": "Registreerumata",
-       "rcfilters-filter-unregistered-description": "Kasutajad, kes pole sisse logitud.",
-       "rcfilters-filter-unregistered-conflicts-user-experience-level": "See filter on konfliktis {{PLURAL:$2|järgmise kogemustaseme filtriga|järgmiste kogemustasemete filtritega}}, mis {{PLURAL:$2|leiab|leiavad}} ainult registreerunud kasutajaid: $1",
        "rcfilters-filtergroup-authorship": "Kaastöö autorsus",
        "rcfilters-filter-editsbyself-label": "Enda muudatused",
        "rcfilters-filter-editsbyself-description": "Sinu enda muudatused.",
        "rcfilters-filter-editsbyother-label": "Teiste muudatused",
        "rcfilters-filter-editsbyother-description": "Kõik muudatused peale sinu enda omade.",
-       "rcfilters-filtergroup-userExpLevel": "Kogemustase (ainult registreerunud kasutajate puhul)",
-       "rcfilters-filtergroup-user-experience-level-conflicts-unregistered": "Kogemustaseme filtrid leiavad ainult registreerunud kasutajaid, mistõttu on see filter konfliktis filtriga \"{{int:rcfilters-filter-unregistered-label}}\".",
-       "rcfilters-filtergroup-user-experience-level-conflicts-unregistered-global": "Filter \"{{int:rcfilters-filter-unregistered-label}}\" on konfliktis vähemalt ühe kogemustaseme filtriga, mis leiab ainult registreerunud kasutajaid. Konfliktsed filtrid on ära märgitud ülal aktiivsete filtrite loendis.",
+       "rcfilters-filtergroup-userExpLevel": "Registreerumine ja kasutaja kogemus",
+       "rcfilters-filter-user-experience-level-registered-label": "Registreerunud",
+       "rcfilters-filter-user-experience-level-registered-description": "Sisse logitud kasutajad.",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Registreerumata",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Kasutajad, kes pole sisse logitud.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Äsjaalustanud",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Alla 10 muudatuse või tegutsenud alla 4 päeva.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Registreerunud toimetajad, teinud alla 10 muudatuse või tegutsenud alla 4 päeva.",
        "rcfilters-filter-user-experience-level-learner-label": "Tutvujad",
-       "rcfilters-filter-user-experience-level-learner-description": "Rohkem kogemust kui äsjaalustanutel, aga vähem kui kogenud kasutajatel.",
+       "rcfilters-filter-user-experience-level-learner-description": "Registreerunud toimetajad, kellel on rohkem kogemust kui äsjaalustanutel, aga vähem kui kogenud kasutajatel.",
        "rcfilters-filter-user-experience-level-experienced-label": "Kogenud kasutajad",
-       "rcfilters-filter-user-experience-level-experienced-description": "Üle 500 muudatuse ja tegutsenud üle 30 päeva.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Registreerunud toimetajad, teinud üle 500 muudatuse ja tegutsenud üle 30 päeva.",
        "rcfilters-filtergroup-automated": "Automaatne kaastöö",
        "rcfilters-filter-bots-label": "Robot",
        "rcfilters-filter-bots-description": "Automaattööriistade tehtud muudatused.",
        "rcfilters-hideminor-conflicts-typeofchange-global": "Filter \"{{int:rcfilters-filter-minor-label}}\" on konfliktis vähemalt ühe muudatuste tüübifiltriga, sest teatud tüüpi muudatusi ei saa märkida pisimuudatusteks. Konfliktsed filtrid on ära märgitud ülal aktiivsete filtrite loendis.",
        "rcfilters-hideminor-conflicts-typeofchange": "Teatud tüüpi muudatusi ei saa märkida pisimuudatusteks. Seetõttu on see filter konfliktis järgmiste tüübifiltritega: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "See muudatuste tüübifilter on konfliktis filtriga \"{{int:rcfilters-filter-minor-label}}\". Teatud tüüpi muudatusi ei saa märkida pisimuudatusteks.",
-       "rcfilters-filtergroup-lastRevision": "Viimane redaktsioon",
+       "rcfilters-filtergroup-lastRevision": "Viimased redaktsioonid",
        "rcfilters-filter-lastrevision-label": "Viimane redaktsioon",
-       "rcfilters-filter-lastrevision-description": "Muudatus, mis on leheküljel kõige viimane.",
-       "rcfilters-filter-previousrevision-label": "Varasemad redaktsioonid",
-       "rcfilters-filter-previousrevision-description": "Kõik muudatused, mis pole leheküljel kõige viimased.",
+       "rcfilters-filter-lastrevision-description": "Ainult muudatus, mis on leheküljel kõige viimane.",
+       "rcfilters-filter-previousrevision-label": "Pole viimane redaktsioon",
+       "rcfilters-filter-previousrevision-description": "Kõik muudatused, mis pole kõige viimased.",
        "rcfilters-filter-excluded": "Välja arvatud",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:mitte</strong> $1",
+       "rcfilters-exclude-button-off": "Jäta valitud välja",
+       "rcfilters-exclude-button-on": "Valitud välja jäetud",
        "rcfilters-view-tags": "Märgistatud muudatused",
+       "rcfilters-view-namespaces-tooltip": "Filtri tulemusi nimeruumide lõikes",
+       "rcfilters-view-tags-tooltip": "Filtri tulemusi muudatusmärgiste lõikes",
+       "rcfilters-view-return-to-default-tooltip": "Naase filtri peamenüüsse",
+       "rcfilters-liveupdates-button": "Uuendused reaalajas",
        "rcnotefrom": "Allpool on toodud {{PLURAL:$5|muudatus|muudatused}} alates: <strong>$3, kell $4</strong> (näidatakse kuni <strong>$1</strong> muudatust)",
        "rclistfromreset": "Lähtesta kuupäeva valik",
        "rclistfrom": "Näita muudatusi alates: $3, kell $2",
        "delete-warning-toobig": "See lehekülg on pika redigeerimislooga – üle {{PLURAL:$1|ühe muudatuse|$1 muudatuse}}.\nEttevaatust, selle kustutamine võib esile kutsuda häireid {{GRAMMAR:genitive|{{SITENAME}}}} andmebaasi töös.",
        "deleteprotected": "Seda lehekülge ei saa kustutada, sest see on kaitstud.",
        "deleting-backlinks-warning": "<strong>Hoiatus:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Teised leheküljed]] viitavad leheküljele, mida oled kustutamas, või see lehekülg on kasutuses mallina.",
+       "deleting-subpages-warning": "<strong>Hoiatus:</strong> Oled kustutamas lehekülge, millel on [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|alamlehekülg|$1 alamlehekülge|51=üle 50 alamlehekülje}}]].",
        "rollback": "Tühista muudatused",
        "rollbacklink": "tühista",
        "rollbacklinkcount": "tühista {{PLURAL:$1|üks muudatus|$1 muudatust}}",
        "undelete-search-title": "Kustutatud lehekülgede otsimine",
        "undelete-search-box": "Kustutatud lehekülgede otsimine",
        "undelete-search-prefix": "Näita lehekülgi, mille pealkiri algab nii:",
+       "undelete-search-full": "Näita leheküljepealkirju, milles sisaldub:",
        "undelete-search-submit": "Otsi",
        "undelete-no-results": "Kustutatud lehekülgede arhiivist sellist lehekülge ei leidunud.",
        "undelete-filename-mismatch": "Failiversiooni ajatempliga $1 ei saa taastada, sest failinimed ei klapi.",
        "fileduplicatesearch-noresults": "Faili nimega \"$1\" ei leidu.",
        "specialpages": "Erileheküljed",
        "specialpages-note-top": "Seletus",
-       "specialpages-note": "* Harilikud erileheküljed.\n* <span class=\"mw-specialpagerestricted\">Piiranguga erileheküljed.</span>",
        "specialpages-group-maintenance": "Hooldusaruanded",
        "specialpages-group-other": "Teised erileheküljed",
        "specialpages-group-login": "Sisselogimine ja konto loomine",
index 30c00cb..19e3f20 100644 (file)
        "rcfilters-filter-newpages-description": "Orri berriak egiten dituzten aldaketak",
        "rcfilters-filter-categorization-label": "Kategoria aldaketak",
        "rcfilters-filter-logactions-label": "Erregistratutako ekintzak",
-       "rcfilters-filtergroup-lastRevision": "Azken berrikuspena",
+       "rcfilters-filtergroup-lastRevision": "Azken berrikuspenak",
        "rcfilters-filter-lastrevision-label": "Azken berrikuspena",
        "rcfilters-filter-lastrevision-description": "Orrialde bati eginiko aldaketarik berriena.",
-       "rcfilters-filter-previousrevision-label": "Aurreko berrikuspenak",
+       "rcfilters-filter-previousrevision-label": "Ez da azken berrikuspena",
+       "rcfilters-filter-previousrevision-description": "\"Azken berrikuspena\" ez diren aldaketa guztiak.",
        "rcfilters-filter-excluded": "Baztertua",
        "rcfilters-exclude-button-off": "Baztertzea aukeratuta",
        "rcfilters-exclude-button-on": "Baztertzea aukeratuta",
        "fileduplicatesearch-noresults": "Ez da aurkitu \"$1\" izeneko fitxategirik.",
        "specialpages": "Orri bereziak",
        "specialpages-note-top": "Azalpenak",
-       "specialpages-note": "* Orri berezi arruntak.\n* <strong class=\"mw-specialpagerestricted\">Mugatutako orri bereziak.</strong>",
        "specialpages-group-maintenance": "Mantentze-oharrak",
        "specialpages-group-other": "Beste orri berezi batzuk",
        "specialpages-group-login": "Hasi saioa / sortu kontua",
index 3c9b3aa..900871b 100644 (file)
        "rcfilters-filter-editsbyself-description": "Vos propres contributions.",
        "rcfilters-filter-editsbyother-label": "Modifications faites par les autres.",
        "rcfilters-filter-editsbyother-description": "Toutes les modifications sauf les votres.",
-       "rcfilters-filtergroup-userExpLevel": "Enregistrement de l'expérience et expérience",
+       "rcfilters-filtergroup-userExpLevel": "Enregistrement des utilisateurs et expérience",
        "rcfilters-filter-user-experience-level-registered-label": "Connecté",
        "rcfilters-filter-user-experience-level-registered-description": "Éditeurs connectés.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Non connecté",
        "rcfilters-hideminor-conflicts-typeofchange-global": "Le filtre « Modifications mineures » est en conflit avec au moins un filtre de Type de modification, parce que certains types de modification ne peuvent être marqués comme « mineurs ». Les filtres en conflit sont marqués dans la zone Filtres actifs ci-dessus.",
        "rcfilters-hideminor-conflicts-typeofchange": "Certains types de modification ne peuvent pas être qualifiés de « mineurs », donc ce filtre est en conflit avec les filtres de Type de modification suivants : $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Ce filtre de Type de modification est en conflit avec le filtre « Modifications mineures ». Certains type sde modification ne peuvent pas être indiqués comme « mineurs ».",
-       "rcfilters-filtergroup-lastRevision": "Version actuelle",
-       "rcfilters-filter-lastrevision-label": "Version actuelle",
-       "rcfilters-filter-lastrevision-description": "Dernière modification apportée à une page.",
-       "rcfilters-filter-previousrevision-label": "Versions précédentes",
-       "rcfilters-filter-previousrevision-description": "Toutes les modifications apportées à une page et qui ne sont pas la dernière.",
+       "rcfilters-filtergroup-lastRevision": "Dernières révisions",
+       "rcfilters-filter-lastrevision-label": "Dernière révision",
+       "rcfilters-filter-lastrevision-description": "Uniquement la dernière modification apportée à une page.",
+       "rcfilters-filter-previousrevision-label": "Pas la dernière version",
+       "rcfilters-filter-previousrevision-description": "Toutes les modifications apportées à une page et qui ne concernent pas la « dernière version ».",
        "rcfilters-filter-excluded": "Exclu",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:not</strong> $1",
        "rcfilters-exclude-button-off": "Exclure les sélectionnés",
        "delete-warning-toobig": "Cette page possède un historique important de modifications, dépassant $1 version{{PLURAL:$1||s}}.\nLa supprimer peut perturber le fonctionnement de la base de données de {{SITENAME}} ;\nveuillez procéder avec prudence.",
        "deleteprotected": "Vous ne pouvez pas supprimer cette page car elle a été protégée.",
        "deleting-backlinks-warning": "<strong>Attention :</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|D’autres pages]] ont un lien vers ou incorporent la page que vous allez supprimer.",
+       "deleting-subpages-warning": "<strong>Attention :</strong> la page que vous essayez de supprimer possède  [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|une sous-page|$1 sous-pages|51=plus de 50 sous-pages}}]].",
        "rollback": "Révoquer les modifications",
        "rollbacklink": "révoquer",
        "rollbacklinkcount": "révoquer $1 {{PLURAL:$1|modification|modifications}}",
        "fileduplicatesearch-noresults": "Aucun fichier nommé « $1 » n'a été trouvé.",
        "specialpages": "Pages spéciales",
        "specialpages-note-top": "Légende",
-       "specialpages-note": "* Pages spéciales normales.\n* <span class=\"mw-specialpagerestricted\">Pages spéciales restreintes.</span>",
+       "specialpages-note-restricted": "* Pages spéciales normales.\n* <span class=\"mw-specialpagerestricted\">Pas spéciales restreintes.</span>",
        "specialpages-group-maintenance": "Rapports de maintenance",
        "specialpages-group-other": "Autres pages spéciales",
        "specialpages-group-login": "S'identifier / s'inscrire",
index c811a1b..f2fe856 100644 (file)
        "errorpagetitle": "Diar as wat skiaf gingen",
        "returnto": "Turag tu sidj $1.",
        "tagline": "Faan {{SITENAME}}",
-       "help": "Halep",
+       "help": "MediaWiki Halep",
        "search": "Schük",
        "search-ignored-headings": " #<!-- Detdiar rä ei feranre --> <pre>\n# Auerskraften, diar bi't schüken ei beaachtet wurd.\n# Jodiar feranrangen wurd seekert, wan det sidj mä det auerskraft indeksiaret wurden as.\n# Dü könst det sidjenindeksiarang föörtji, wan dü en nul-edit maagest.\n# Syntax:\n#   * Ales, wat bääft en dobelkrüs („#“) stäänt, as en komentaar.\n#   * Arke rä, wat ei leesag as, as di akeroot tiitel, diar ei beaachtet woort.\nFutnuuten\nFerwisangen\nLuke uk diar\n #</pre> <!-- Detdiar rä ei feranre -->",
        "searchbutton": "Schük",
        "fileduplicatesearch-noresults": "Nian datei mä di nööm „$1“ fünjen.",
        "specialpages": "Spezial-sidjen",
        "specialpages-note-top": "Legend",
-       "specialpages-note": "* Normool spezial-sidjen\n* <span class=\"mw-specialpagerestricted\">Spezial-sidjen mä tugripsrochten</span>",
        "specialpages-group-maintenance": "Werksteedsidjen",
        "specialpages-group-other": "Ööder spezial-sidjen",
        "specialpages-group-login": "Melde di uun of skriiw di iin",
index 2be194e..03ca4f2 100644 (file)
        "rcfilters-legend-heading": "<strong>Lista de abreviaturas:</strong>",
        "rcfilters-activefilters": "Filtros activos",
        "rcfilters-advancedfilters": "Filtros avanzados",
+       "rcfilters-limit-title": "Modificacións a amosar",
+       "rcfilters-limit-shownum": "Amosar as últimas $1 modificacións",
+       "rcfilters-days-title": "Últimos días",
+       "rcfilters-hours-title": "Últimas horas",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|día|días}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|hora|horas}}",
        "rcfilters-quickfilters": "Filtros gardados",
        "rcfilters-quickfilters-placeholder-title": "Aínda non se gardou ningunha ligazón",
        "rcfilters-quickfilters-placeholder-description": "Para gardar a configuración dos seus filtros e reutilizala máis tarde, prema na icona do marcador na área de Filtro activo que se atopa a abaixo.",
        "rcfilters-invalid-filter": "Filtro no válido",
        "rcfilters-empty-filter": "Non hai filtros activos. Móstranse tódalas contribucións.",
        "rcfilters-filterlist-title": "Filtros",
-       "rcfilters-filterlist-whatsthis": "Que é isto?",
+       "rcfilters-filterlist-whatsthis": "Como funciona isto?",
        "rcfilters-filterlist-feedbacklink": "Deixar comentarios sobre os novos filtros (en fase beta)",
        "rcfilters-highlightbutton-title": "Resaltar resultados",
        "rcfilters-highlightmenu-title": "Seleccione unha cor",
        "rcfilters-filter-editsbyself-description": "As súas contribucións",
        "rcfilters-filter-editsbyother-label": "Modificacións doutros.",
        "rcfilters-filter-editsbyother-description": "Tódolos cambios, excepto os seus.",
-       "rcfilters-filtergroup-userExpLevel": "Nivel de experiencia (só para usuarios rexistrados)",
-       "rcfilters-filter-user-experience-level-registered-label": "Rexistrado",
+       "rcfilters-filtergroup-userExpLevel": "Rexistro de usuarios e experiencia",
+       "rcfilters-filter-user-experience-level-registered-label": "Rexistrados",
        "rcfilters-filter-user-experience-level-registered-description": "Editores autenticados.",
-       "rcfilters-filter-user-experience-level-unregistered-label": "Non rexistrado",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Non rexistrados",
        "rcfilters-filter-user-experience-level-unregistered-description": "Editores que non están autenticados.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Chegados recentemente",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Menos de 10 edicións e 4 días de actividade.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Editores rexistrados con menos de 10 edicións e 4 días de actividade.",
        "rcfilters-filter-user-experience-level-learner-label": "Aprendices",
-       "rcfilters-filter-user-experience-level-learner-description": "Máis experimentado que os \"usuarios novatos\" pero menos que os \"usuarios experimentados\".",
+       "rcfilters-filter-user-experience-level-learner-description": "Editores rexistrados cuxa experiencia está entre os \"usuarios novatos\" e os \"usuarios experimentados\".",
        "rcfilters-filter-user-experience-level-experienced-label": "Usuarios experimentados",
-       "rcfilters-filter-user-experience-level-experienced-description": "Máis de 30 días de actividade e 500 edicións.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Editores rexistrados con máis de 500 edicións e 30 días de actividade.",
        "rcfilters-filtergroup-automated": "Contribucións automatizadas",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-bots-description": "Edicións realizadas por ferramentas automatizadas.",
        "rcfilters-hideminor-conflicts-typeofchange-global": "O filtro \"edicións menores\" está en conflito con un ou máis filtros Tipo de modificación, porque certos tipos de modificación non poden designarse como \"menores\". Os filtros en conflito están marcados na zona Filtros activos, arriba.",
        "rcfilters-hideminor-conflicts-typeofchange": "Certos tipos de modificación non poden designarse como \"menores\", polo que este filtro entra en conflito cos seguintes filtros Tipo de modificaciónː $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Este filtro Tipo de modificación entra en conflito co filtro \"Modificacións menores\". Certos tipos de modificación non poden designarse como \"menores\".",
-       "rcfilters-filtergroup-lastRevision": "Versión actual",
-       "rcfilters-filter-lastrevision-label": "Versión actual",
-       "rcfilters-filter-lastrevision-description": "A última modificación a unha páxina.",
-       "rcfilters-filter-previousrevision-label": "Versións anteriores",
-       "rcfilters-filter-previousrevision-description": "Tódolos cambios realizados nunha páxina e que non son os máis recentes.",
+       "rcfilters-filtergroup-lastRevision": "Últimas revisións",
+       "rcfilters-filter-lastrevision-label": "Últimas revisións",
+       "rcfilters-filter-lastrevision-description": "Só a última modificación a unha páxina.",
+       "rcfilters-filter-previousrevision-label": "Non a última edición",
+       "rcfilters-filter-previousrevision-description": "Tódolos cambios realizados nunha páxina e que non son a \"última modificación\".",
        "rcfilters-filter-excluded": "Excluído",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:non</strong> $1",
+       "rcfilters-exclude-button-off": "Excluír os seleccionados",
+       "rcfilters-exclude-button-on": "Excluíndo os seleccionados",
        "rcfilters-view-tags": "Edicións marcadas",
        "rcfilters-view-namespaces-tooltip": "Filtrar resultados por espazo de nomes",
        "rcfilters-view-tags-tooltip": "Filtrar resultados usando etiquetas de edición",
        "delete-warning-toobig": "Esta páxina conta cun historial de edicións longo, de máis {{PLURAL:$1|dunha revisión|de $1 revisións}}.\nAo eliminala pódense provocar problemas de funcionamento nas operacións da base de datos de {{SITENAME}};\nproceda con coidado.",
        "deleteprotected": "Non pode borrar esta páxina porque está protexida.",
        "deleting-backlinks-warning": "<strong>Atención:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Outras páxinas]] conteñen unha ligazón ou unha transclusión da páxina que está a piques de borrar.",
+       "deleting-subpages-warning": "<strong>Aviso:</strong> A páxina que quere eliminar ten [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|unha subpáxina|$1 subpáxinas|51=máis de 50 subpáxinas}}]].",
        "rollback": "Reverter as edicións",
        "rollbacklink": "reverter",
        "rollbacklinkcount": "reverter $1 {{PLURAL:$1|edición|edicións}}",
        "fileduplicatesearch-noresults": "Non se atopou ningún ficheiro chamado \"$1\".",
        "specialpages": "Páxinas especiais",
        "specialpages-note-top": "Lenda",
-       "specialpages-note": "* Páxinas especiais normais.\n* <span class=\"mw-specialpagerestricted\">Páxinas especiais restrinxidas.</span>",
        "specialpages-group-maintenance": "Informes de mantemento",
        "specialpages-group-other": "Outras páxinas especiais",
        "specialpages-group-login": "Rexistro",
index bafdb74..b1c3880 100644 (file)
        "newarticletext": "Yi'o lodudu'a wumbuta ode halaman diya'a. \nWonu mohutu halaman botiye, ketik tuwango halaman to kotak to tibawa botiye (bilohi [$1 halaman wubodu] ode habari wumbutiyo). \nWonu Yi'o ja sangaja tilumuwota ode halaman botiye, kutiya tombol <strong>mohuwalingo</strong>.",
        "noarticletext": "Sa'ati botiye diya'a teks to halaman botiye.\nYi'o mowali [[Special:Search/{{PAGENAME}}|mololohu  judul halaman botiye]] to halaman-halaman uweewo, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mololohu log a'ayita], meyalo [{{fullurl:{{FULLPAGENAME}}|action=edit}} mohutu halaman botiye]</span>.",
        "noarticletext-nopermission": "!Sa'ati botiye diya'a teks to halaman boptiye.\nYi'o mowali [[Special:Search/{{PAGENAME}}|mololohu judul halaman botiye]] to halaman-halaman uweewo, meyalo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mololohu log a'ayita]</span>, dabo Yi'o ja o ijin mohutu halaman botiye.",
+       "userpage-userdoesnotexist-view": "Ta ohu'uwo \"$1\" diyaalu to daputari.",
        "editing": "Momoli'o $1",
        "creating": "Mohutu $1",
        "editingsection": "Momoli'o $1 (tayadu)",
        "moveddeleted-notice": "Halaman botiye ma yiluluto.\nSebagai referensi, botiya log piloluluta wawu piloheyiya halaman botiye.",
        "postedit-confirmation-saved": "Biloli'umu ma tilahu.",
        "edit-already-exists": "Ja mowali mohutu halaman bohu. Ma woluwo.",
+       "content-model-wikitext": "tuladu wiki",
        "viewpagelogs": "Bilohi log lo halaman botiye",
        "currentrev-asof": "Biloli'o pulitiyo to $1",
        "revisionasof": "Biloli'o to $1",
        "history-fieldset-title": "Lolohe u biloli'o",
        "histfirst": "mohihewo da'a",
        "histlast": "bohu da'a",
+       "history-feed-title": "Riwayati lo'u biloli'o",
+       "history-feed-description": "Riwayati bilolio to halaman wiki botiye",
+       "history-feed-item-nocomment": "$1 to $2",
        "rev-delundel": "popobilohe/wanto'a",
        "history-title": "Riwayati lo'u loboli'a lonto \"$1\"",
        "difference-title": "$1 hihede revisi",
        "lineno": "Baarisi $1:",
+       "compareselectedversions": "Popotadenga u tilulawoto",
        "editundo": "pohuwalinga",
        "diff-multi-sameuser": "({{PLURAL:$1|$1 revisi wolota}} pilohutu lo tawu ngota ja pilopobilohu)",
        "searchresults": "U yilotapu",
        "search-result-size": "$1 ({{PLURAL:$2|1 tahe|$2 tahe}})",
        "search-redirect": "(pilobale lonto $1)",
        "search-section": "(tayadu) $1",
+       "search-file-match": "(sama lo tuwango berkas)",
        "search-suggest": "Patujumu yito:$1",
        "searchall": "nga'amila",
        "search-showingresults": "{{PLURAL:$4|hASIL <strong>$1</strong> of <strong>$3</strong>|Hasil <strong>$1 - $2</strong> lonto <strong>$3</strong>}}",
        "search-nonefound": "Diya'a hasili mohumayawa lo kriteria",
        "powersearch-toggleall": "Nga'amila",
        "powersearch-togglenone": "Diya'a",
-       "powersearch-remember": "Eelayi u tilulawoto wonu mololohe pe'eentamayi",
+       "powersearch-remember": "Toloma u tilulawoto wonu mololohe pe'eenta mayi",
        "mypreferences": "Preperensi",
        "prefs-skin": "Alipo",
        "searchresultshead": "Lolohe",
        "prefs-searchoptions": "Lolohe",
        "prefs-namespaces": "Huwali lo tanggulo",
-       "default": "Kakali",
+       "default": "kakali",
        "yourrealname": "Tanggula banari",
        "yourlanguage": "Bahasa",
        "grouppage-bot": "{{ns:project}}:Bot",
        "recentchangeslinked-page": "Tanggulo halaman:",
        "recentchangeslinked-to": "Poppobilohe loboli'a to halaman wayitiyo wolo halaman hepoposadiyalo",
        "upload": "Detohe berkas",
+       "uploadlogpage": "Detohu log",
        "filedesc": "Limbu'o",
+       "license": "Lisensi",
        "license-header": "Tayadu lisensi",
        "imgfile": "berkas",
        "file-anchor-link": "Berkas",
        "linkstoimage": "{{PLURAL:$1|halaman lapatiyoma'o}} o wumbuta ode berkas botiye:",
        "nolinkstoimage": "Diya'a halaman u owumbuta ode berkas botiye",
        "sharedupload-desc-here": "Berkas botiye lonto $1 wawu hepohunaliyo to poroyek uweewo.\nDeskripsi lonto [$2 halaman deskripsiliyo] woluwo to tibawa botiya.",
+       "filepage-nofile": "Diya'a berkas lo tanggula botiye",
        "upload-disallowed-here": "Yi'o diila mowali modeehe berkas botiye",
        "randompage": "Halaman totonula",
        "statistics": "Statistik",
        "nmembers": "$1 {{PLURAL:$1|tuwango}}",
        "listusers": "Daputari ta ohu'uwo",
        "newpages": "Halaman bohu",
+       "move": "Heyiya",
        "pager-newer-n": "{{PLURAL:$1|bohu da'a|$1bohu da'a}}",
        "pager-older-n": "{{PLURAL:$1|$1 mohihewo}}",
        "booksources": "Bungo buku",
        "booksources-search-legend": "Lolohe to bungo lo buku",
        "booksources-search": "Lolohe",
        "log": "Log",
+       "all-logs-page": "Nga'amila log publik",
+       "allpages": "Nga'amila halaman",
        "allarticles": "Nga'amila halaman",
        "allpagessubmit": "Ntali",
        "categories": "Kategori",
+       "usermessage-editor": "Sistem lo tahuli",
+       "watchlist": "U he'awasiyalo",
        "mywatchlist": "Daputari he'awasiyalo",
        "watch": "Dahayi",
+       "unwatch": "Batali mongawasi",
        "wlshowlast": "Popobilohe $1 jam $2 dulahe pulitiyo",
        "dellogpage": "Log loluluto",
        "rollbacklink": "wuwalinga",
        "protectlogpage": "Log mopo'aamani",
        "protectedarticle": "modaha \"[[$1]]\"",
        "protect-default": "Poluliya nga'amila ta ohu'uwo",
+       "restriction-edit": "Boli'a",
+       "restriction-move": "Heyiya",
        "namespace": "Huwali lo tanggulo",
        "invert": "Pohuwalinga tilulawoto",
        "tooltip-invert": "Centang kotak botiye u mopowanto'o halaman yiloboli'a to delomo huwali lo tanggulo tilulawoto (wawu huwali lo tanggulo a'ayita wanu dicentang)",
        "contributions": "Kontribusi {{GENDER:$1|Ta ohu'uwo}}",
        "mycontris": "Kontribusi",
        "anoncontribs": "Kontribusi",
+       "contribsub2": "Ode {{GENDER:$3|$1}} ($2)",
        "uctop": "(masatiya)",
        "month": "Lonto hulalo (wawu to'udiipo)",
        "year": "Lonto taawunu (wawu to'udiipo)",
+       "sp-contributions-newbies": "Popobilohe bo lonto ta ohu'uwo bohu",
+       "sp-contributions-blocklog": "bubuli log",
+       "sp-contributions-uploads": "u diletohu",
        "sp-contributions-logs": "log",
        "sp-contributions-talk": "lo'iya",
        "sp-contributions-search": "Lolohe kontribusi",
+       "sp-contributions-username": "Alamat IP meyalo tanggulo ta ohu'uwo",
+       "sp-contributions-toponly": "Popobiloho bo biloli'a to yitaato",
+       "sp-contributions-newonly": "Popobilohe biloli'o bo u lohutu halaman",
        "sp-contributions-submit": "Lolohe",
        "whatlinkshere": "Wumbuta",
        "whatlinkshere-title": "Halaman botiye o wumbuta ode \"$1\"",
        "whatlinkshere-hidelinks": "$1 wumbuta",
        "whatlinkshere-hideimages": "$1 berkas wumbuta",
        "whatlinkshere-filters": "U'ayahu",
+       "ipboptions": "2 jam:2 hours,1 huyi:1 day,3 huyi:3 days,1 diminggu:1 week,2 diminggu:2 weeks,1 hula:1 month,3 hula:3 months,6 hula:6 months,1 taawunu:1 year,layito:infinite",
        "blocklink": "tangguwalo",
        "contribslink": "kontrib",
+       "blocklogpage": "Bubuli log",
+       "blocklogentry": "momubulo [[$1]] wolo pulito wakutu $2 $3",
+       "proxyblocker": "Bubulo proxi",
        "movelogpage": "Log piloheyiya",
        "export": "Ekspor halaman",
        "thumbnail-more": "Po'odamanga",
        "tooltip-ca-nstab-special": "Utiye halaman istimewa, wawu ja mowali boli'olo",
        "tooltip-ca-nstab-project": "Bilohi halaman poroyek",
        "tooltip-ca-nstab-image": "Bilohi berkas lo halaman",
+       "tooltip-ca-nstab-mediawiki": "Bilohi tahuli lo sistem",
        "tooltip-ca-nstab-template": "Bilohi template",
        "tooltip-ca-nstab-category": "Bilohi kategori halaman",
        "tooltip-save": "Tahuwa u biloli'umu",
        "tooltip-preview": "Bilohipo u biloli'umu. Popopasiya utiye to'u diipo molahu.",
        "tooltip-diff": "Bilohi u loboli'o pilohutumu",
        "tooltip-compareselectedversions": "Bilohi hihede lohalaman duluwo u tilulawoto.",
+       "tooltip-watch": "Poduhengama'o halaman botiye to daputari he'awasiyalo",
        "tooltip-rollback": "\"Wuwalingo\" lopobatali u pilo'opiyohu to halaman botiye ode kontributor pulitiyo pe'enta lo klik.",
        "tooltip-undo": "\"wuwalingo\" lopobatali u biloli'a botiye wawu lomu'o kotak momoli'o wolo mode pratayang. Alasani mowali duhengalo to kotak limbu-limbu'o.",
        "tooltip-summary": "Tuwota tulade limbu-limbu'o",
        "simpleantispam-label": "Momarakisa anti-spam.\n<strong>kekeya</strong> tuwangalo!",
+       "pageinfo-title": "Informasi untuk \"$1\"",
+       "pageinfo-header-basic": "Bungo lo habari",
+       "pageinfo-header-restrictions": "Dudaha halaman",
+       "pageinfo-display-title": "Judul bibilohu",
+       "pageinfo-length": "Haya'o halaman (to delomo bita)",
+       "pageinfo-article-id": "ID Halaman",
+       "pageinfo-language": "Bahasa tuwango halaman",
+       "pageinfo-robot-policy": "Pengindeksan monto robot",
+       "pageinfo-watchers": "Jumula lo ta hemongawasi halaman",
+       "pageinfo-redirects-name": "Jumula u pilobale ode halaman botiya",
+       "pageinfo-firsttime": "Tanggal pilohutuwa halaman",
+       "pageinfo-recent-edits": "Jumula boheli biloli'a mola (to delomo $1 pulitiyo)",
        "pageinfo-toolboxlink": "Halaman habari",
+       "pageinfo-contentpage-yes": "Jo",
        "previousdiff": "← Biloli'o to'udiipo",
        "nextdiff": "Biloli'o lapatiyoma'o →",
        "file-info-size": "$1 x $2 piksel, tu'udu berkas:$3, MIME tipe: $4",
        "namespacesall": "nga'amila",
        "monthsall": "nga'amila",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|bisala]])",
+       "redirect-submit": "Ntali",
+       "redirect-lookup": "Yilolohu",
+       "redirect-page": "ID Halaman",
+       "redirect-revision": "Halaman biloli'o",
+       "redirect-file": "Tanggulo berkas",
        "specialpages": "Halaman Spesial",
        "tag-filter": "[[Special:Tags|Tag]]filter:",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag}}]]: $2)",
        "tags-active-yes": "Jo",
        "logentry-delete-delete": "$1 {{GENDER:$2|moluluto}}halaman $3",
+       "revdelete-content-hid": "tuwango yilanto'o",
        "logentry-move-move": "$1 {{GENDER:$2|moheyi}} halaman $3 ode $4",
+       "logentry-move-move-noredirect": "$1 {{GENDER:$2|loheyi}} halaman $3 ode $4 ja lohutu pengalihan",
+       "logentry-move-move_redir": "$1 {{GENDER:$2|loheyi}} halaman $3 ode $4 lodeehu pengalihan",
        "logentry-newusers-create": "Ta ohu'uwo akun $1 {{GENDER:$2|mohutu}}",
+       "logentry-newusers-autocreate": "Akun $1 {{GENDER:$2|pilohutu}} otomatis",
        "logentry-upload-upload": "$1 {{GENDER:$2|mengunggah}} $3",
-       "searchsuggest-search": "Lolohe {{SITENAME}}"
+       "searchsuggest-search": "Lolohe {{SITENAME}}",
+       "duration-days": "$1 {{PLURAL:$1|huyi}}",
+       "randomrootpage": "Halaman totonulalo"
 }
index a81f2df..9306bff 100644 (file)
        "accmailtext": "E zuefällig generiert Passwort fir [[User talk:$1|$1]] isch an $2 gschickt wore.\n\nS Passwort fir des nej Benutzerkonto cha uf dr Spezialsyte „[[Special:ChangePassword|Passwort ändere]]“ gänderet wäre.",
        "newarticle": "(Nej)",
        "newarticletext": "Du bisch eme Link nogange zuen ere Syte, wu s nid git.\nZum die Syte aalege, chasch do in däm Chaschte unte aafange schrybe (lueg [$1 Hilfe] fir meh Informatione).\nWänn do nid hesch welle aane goh, no druck in Dyynem Browser uf '''Zruck'''.",
-       "anontalkpagetext": "----''Des isch e Diskussionssyte vun eme anonyme Benutzer, wu kei Zuegang aagleit het oder wu ne nit bruucht. Sälleweg mien mir di numerisch IP-Adräss bruuche zum ihn oder si z identifiziere. So ne IP-Adräss cha au vu mehrere Benutzer teilt wäre. Wenn Du ne anonyme Benutzer bisch un s Gfiel hesch, ass do irrelevanti Kommentar an di grichtet wäre, derno [[Special:CreateAccount|leg e Konto aa]] oder [[Special:UserLogin|mäld di aa]] zum in Zuekumft Verwirrige mit andere anonyme Benutzer z vermyyde.''",
+       "anontalkpagetext": "----\n<em>Des isch e Diskussionssyte vun eme anonyme Benutzer, wu kei Zuegang aagleit het oder wu ne nit bruucht.</em>\nSälleweg mien mir di numerisch IP-Adräss bruuche zum ihn oder si z identifiziere. So ne IP-Adräss cha au vu mehrere Benutzer teilt wäre. Wenn Du ne anonyme Benutzer bisch un s Gfiel hesch, ass do irrelevanti Kommentar an di grichtet wäre, derno [[Special:CreateAccount|leg e Konto aa]] oder [[Special:UserLogin|mäld di aa]] zum in Zuekumft Verwirrige mit andere anonyme Benutzer z vermyyde.",
        "noarticletext": "Uf däre Syte het s no kei Täxt. \nDu chasch uf andere Syte [[Special:Search/{{PAGENAME}}|dä Yytrag sueche]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} dr Logbuechyytrag sueche, wo dezue ghert],\noder [{{fullurl:{{FULLPAGENAME}}|action=edit}} die Syte erstelle]</span>.",
        "noarticletext-nopermission": "In däre Syte het s zur Zyt no kei Text.\nDu chasch dää Titel uf andre Syte [[Special:Search/{{PAGENAME}}|sueche]]\noder <span class=\"plainlinks\">in dr zuegherige [{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} Logbiecher sueche].</span> Du derfsch aber die Syte nit aalege.",
        "missing-revision": "D Version $1 vu dr Syte mit Name „{{FULLPAGENAME}}“ git s nit.\n\nDää Fähler chunnt normalerwyys dur e veraltete Link zue dr Versionsgschicht vun ere Syte, wu in dr Zwischezyt glescht woren isch.\nEinzelheite chasch im [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Lesch-Logbuech] bschaue.",
        "userpage-userdoesnotexist": "S Benutzerkonto „<nowiki>$1</nowiki>“ git s nit. Bitte prief, eb Du die Syte wirkli wit aalege/bearbeite.",
        "userpage-userdoesnotexist-view": "S Benutzerkonto „$1“ isch nit registriert.",
        "blocked-notice-logextract": "Dää Benutzer isch zur Zyt gsperrt.\nAs Information chunnt do ne aktuälle Uuszug us em Benutzersperr-Logbuech:",
-       "clearyourcache": "'''Hiiwys:''' Noch em Spycheremuesch no dr Browser-Zwischespycher lääre go d Änderige sää.\n* '''Firefox/ Safari:''' ''Umschaltig'' drucken un glychzytig ''Aktualisiere'' aaklicken oder entwäder ''Strg+F5'' oder ''Strg+R'' (''Befehlstaste-R'' uf em Mac) drucke\n* '''Google Chrome:''' ''Umschaltig+Strg+R'' (''Befählstaschte-R'' uf em Mac) drucke\n* '''Internet Explorer:''' ''Strg+F5'' drucken oder ''Strg'' drucken un glychzytig ''Aktualisiere'' aaklicke\n* '''Opera:''' ''Extra → Internetspure lesche … → Individuäll Uuswahl → Dr komplett Cache lesche''",
+       "clearyourcache": "<strong>Hiiwys:</strong> Noch em Spycheremuesch no dr Browser-Zwischespycher lääre go d Änderige sää.\n* <strong>Firefox/ Safari:</strong> <em>Umschaltig</em> drucken un glychzytig <em>Aktualisiere</em> aaklicken oder entwäder <em>Strg+F5</em> oder <em>Strg+R</em> (<em>Befehlstaste-R</em> uf em Mac) drucke\n* <strong>Google Chrome:</strong> <em>Umschaltig+Strg+R</em> (<em>Befählstaschte-R</em> uf em Mac) drucke\n* <strong>Internet Explorer:</strong> <em>Strg+F5</em> drucken oder <em>Strg</em> drucken un glychzytig <em>Aktualisiere</em> aaklicke\n* <strong>Opera:</strong> Gang uff <em>Menü → Yystellige</em> (<em>Opera → Yystellige</em> uff eme Mac) un deno uff <em>Dateschutz & Sicherheit → Browserdate lösche → Gspyycherti Bilder un Dateie</em>.",
        "usercssyoucanpreview": "'''Tipp:''' Nimm dr „{{int:showpreview}}”-Chnopf, zum Dyy nej CSS vor em Spichere z teschte.",
        "userjsyoucanpreview": "'''Tipp:''' „Nimm dr {{int:showpreview}}”-Chnopf, zum Dyy nej JS vor em Spichere z teschte.",
        "usercsspreview": "== Vorschau vu Dyynem Benutzer-CSS. ==\n'''Wichtig:''' Noch em Spichere muesch Dyynem Browser sage, ass er die nej Version ladet:\n\n'''Mozilla:''' ''Strg-Shift-R'', '''IE:''' ''Strg-F5'', '''Safari:''' ''Cmd-Shift-R'', '''Konqueror:''' ''F5''.",
        "tooltip-feed-rss": "RSS-Feed für selli Syte",
        "tooltip-feed-atom": "Atom-Feed für selli Syte",
        "tooltip-t-contributions": "E Lischt vo de Byträg vo {{GENDER:$1|däm Benutzer}}",
-       "tooltip-t-emailuser": "Schick däm Benutzer e E-Bost",
+       "tooltip-t-emailuser": "Schigg e E-Mail aa {{GENDER:$1|de Benutzer|die Benutzeri}}",
        "tooltip-t-info": "Meh Informationen über die Syte",
        "tooltip-t-upload": "Dateien ufelade",
        "tooltip-t-specialpages": "Lischte vo allne Spezialsyte",
        "version-libraries-license": "Lizänz",
        "version-libraries-description": "Beschrybig",
        "version-libraries-authors": "Autor/inne",
-       "redirect": "Wyterleitig uf Benutzersyte, Syte, Syteversion oder Datei",
-       "redirect-summary": "Die Spezialsyte leitet wyter uf e Benutzersyte (numerischi Benutzerkännig aagee), Syte (Sytekännig aagee), Syteversion (Versionskännig aagee) oder Datei (Dateiname aagee). Benutzig: [[{{#Special:Redirect}}/user/101]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]] oder [[{{#Special:Redirect}}/file/Example.jpg]].",
+       "redirect": "Wyterleitig uf Datei, Benutzersyte, Syte, Syteversion oder Logbuechyytraag.",
+       "redirect-summary": "Die Spezialsyte leitet wyter uf e Benutzersyte (numerischi Benutzerkännig aagee), Syte (Sytekännig aagee), Syteversion (Versionskännig aagee), e Datei (Dateiname aagee) oder en Logbeuchyytrag (Logbuechkennig aagee). Benutzig: Usage: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], oder [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Gang",
        "redirect-lookup": "Sueche:",
        "redirect-value": "Wärt:",
        "fileduplicatesearch-noresults": "S isch kei Datei mit em Name „$1“ gfunde wore.",
        "specialpages": "Spezialsytene",
        "specialpages-note-top": "Zeichenerklärig:",
-       "specialpages-note": "* Normali Spezialsyte.\n* <span class=\"mw-specialpagerestricted\">Spezialsyte mit bschränktem Zuegang.</span>",
        "specialpages-group-maintenance": "Wartigslischte",
        "specialpages-group-other": "Andri Spezialsyte",
        "specialpages-group-login": "Aamälde/Konto aalege",
        "htmlform-user-not-exists": "<strong>$1</strong> git’s nid.",
        "htmlform-user-not-valid": "<strong>$1</strong> isch ke gültige Name.",
        "logentry-delete-delete": "{{GENDER:$2|Dr|D|}} $1 het d Syte $3 glöscht",
-       "logentry-delete-restore": "{{GENDER:$2|Der $1|D $1|$1}} het d Syte $3 wider härgstellt",
+       "logentry-delete-restore": "{{GENDER:$2|Der $1|D $1|$1}} het d Syte $3 wider härgstellt ($4)",
        "logentry-delete-event": "{{GENDER:$2|Der $1|D $1|$1}} het d Sichtbarkeit {{PLURAL:$5|vumene Logbuechyytrag|vo $5 Logbuechyyträg}} gänderet uff $3: $4",
        "logentry-delete-revision": "{{GENDER:$2|Der $1|D $1|$1}} het d Sichtbarkeit {{PLURAL:$5|vunere Version|vo $5 Versione}} gänderet uff $3: $4",
        "logentry-delete-event-legacy": "{{GENDER:$2|Der $1|D $1|$1}} het d Sichtbarkeit vo Logbuechyyträg uff $3 gänderet",
index 03f044c..a46eebc 100644 (file)
        "anontalk": "Kâu-liù",
        "navigation": "Thô-hòng",
        "and": "&#32;lâu",
-       "qbfind": "Cháu-chhìm",
-       "qbbrowse": "Liù-lám",
-       "qbedit": "Phiên-siá",
-       "qbpageoptions": "Ya̍p-mien sién-hong",
-       "qbmyoptions": "Ngài-ke ya̍p-mien",
        "faq": "Sòng-kien mun-thì kié-tap",
-       "faqpage": "Project:Sòng-kien mun-thì kié-tap",
        "actions": "Thûng-chok",
        "namespaces": "Miàng-sṳ khûng-kiên",
        "variants": "Pien-von",
        "edit": "Phiên-siá",
        "create": "Kien-li̍p",
        "create-local": "Sîn-chen pún-thi sot-mìn",
-       "editthispage": "Phiên-siá liá ya̍p",
-       "create-this-page": "Kien-li̍p pún-ya̍p",
        "delete": "San-chhù",
-       "deletethispage": "San-chhù pún-ya̍p",
-       "undeletethispage": "Chhí-sêu san-chhù liá-ya̍p.",
        "undelete_short": "恢復$1隻分删除个编寫",
        "viewdeleted_short": "查看$1項已刪除个修訂",
        "protect": "Pó-fu",
        "protect_change": "Kiên-kói",
-       "protectthispage": "Pó-fu pún-ya̍p",
        "unprotect": "更改保護",
-       "unprotectthispage": "更改本頁保護",
        "newpage": "Sîn ya̍p-mien",
-       "talkpage": "Thó-lun pún-ya̍p",
        "talkpagelinktext": "kâu-liù",
        "specialpage": "Thi̍t-sû ya̍p-mien",
        "personaltools": "Sṳ̂-ngìn kûng-khí",
-       "articlepage": "Khon nui-yùng ya̍p",
        "talk": "Thó-lun",
        "views": "Chhà-khon-sú",
        "toolbox": "Kûng-khí-siông",
-       "userpage": "查看用戶頁面",
-       "projectpage": "查看項目頁面",
        "imagepage": "Chhà-khon vùn-khien ya̍p-mien",
        "mediawikipage": "Chhà-khon sêu-sit ya̍p-mien",
        "templatepage": "Chhà-khon mù-pán ya̍p-mien",
        "minoredit": "Liá-he yit-chak se-mì siû-kói",
        "watchthis": "Kâm-sṳ pún-ya̍p",
        "savearticle": "Pó-chhùn pún-ya̍p",
+       "publishpage": "Fat-péu vùn-chông",
+       "publishchanges": "Fat-péu siû-kói",
        "preview": "預覽",
        "showpreview": "Chán-sṳ yi-lám",
        "showdiff": "Chán-sṳ chhâ-phe̍t",
        "edit-gone-missing": "毋做得更新頁面。\n其可能正正分刪除。",
        "edit-conflict": "Phiên-siá chhûng-thu̍t.",
        "edit-no-change": "汝嘅編寫已經略過,因為文字無任何改動。",
+       "postedit-confirmation-saved": "Ngì ke siû-kói yí-kîn pó-chhùn.",
        "edit-already-exists": "毋做得建立一隻新頁面。\n其已經存在。",
        "defaultmessagetext": "Me̍t-ngin sêu-sit vùn-sṳ",
        "invalid-content-data": "無效嘅數據內容",
index 487d889..2422012 100644 (file)
        "rcfilters-filter-editsbyself-description": "תרומות שביצעת בעצמך.",
        "rcfilters-filter-editsbyother-label": "שינויים של אחרים",
        "rcfilters-filter-editsbyother-description": "כל השינויים מלבד אלה שלך.",
-       "rcfilters-filtergroup-userExpLevel": "ר×\9eת × ×\99ס×\99×\95×\9f (×\9c×\9eשת×\9eש×\99×\9d ×¨×©×\95×\9e×\99×\9d ×\91×\9c×\91×\93)",
+       "rcfilters-filtergroup-userExpLevel": "×\94רש×\9eת ×\9eשת×\9eש×\99×\9d ×\95ר×\9eת × ×\99ס×\99×\95×\9f",
        "rcfilters-filter-user-experience-level-registered-label": "רשומים",
        "rcfilters-filter-user-experience-level-registered-description": "עורכים שנכנסו לחשבון.",
        "rcfilters-filter-user-experience-level-unregistered-label": "לא רשומים",
        "rcfilters-filter-user-experience-level-newcomer-label": "חדשים",
        "rcfilters-filter-user-experience-level-newcomer-description": "עורכים רשומים עם פחות מ־10 עריכות ומ־4 ימים של פעילות.",
        "rcfilters-filter-user-experience-level-learner-label": "לומדים",
-       "rcfilters-filter-user-experience-level-learner-description": "×¢×\95ר×\9b×\99×\9d ×¨×©×\95×\9e×\99×\9d ×¢×\9d ×©×\94× ×\99ס×\99×\95×\9f ×©×\9c×\94×\9d ×\94×\95א בין \"חדשים\" לבין \"מנוסים\".",
+       "rcfilters-filter-user-experience-level-learner-description": "×¢×\95ר×\9b×\99×\9d ×¨×©×\95×\9e×\99×\9d ×©×¨×\9eת ×\94× ×\99ס×\99×\95×\9f ×©×\9c×\94×\9d ×\94×\99א בין \"חדשים\" לבין \"מנוסים\".",
        "rcfilters-filter-user-experience-level-experienced-label": "משתמשים מנוסים",
        "rcfilters-filter-user-experience-level-experienced-description": "עורכים רשומים עם יותר מ־500 עריכות ו־30 ימים של פעילות.",
        "rcfilters-filtergroup-automated": "תרומות אוטומטיות",
        "rcfilters-hideminor-conflicts-typeofchange-global": "מסנן \"עריכות משניות\" מתנגש עם מסנן סוג השינויים אחד או יותר, כי סוגים מסוימים של שינויים אינם יכולים להיות מסווגים בתור \"משניים\". המסננים המתנגשים מסומנים באזור המסננים הפעילים לעיל.",
        "rcfilters-hideminor-conflicts-typeofchange": "סוגים מסוימים של שינויים אינם יכולים להיות מסווגים כ\"משניים\", כך שמסנן זה מתנגש עם מסנן סוג השינויים הבא: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "מסנן סוג השינויים הזה מתנגש עם מסנן \"עריכות משניות\". סוגים מסוימים של שינויים אינם יכולים מסווגים כ\"משניים\".",
-       "rcfilters-filtergroup-lastRevision": "×\92רס×\94 ×\90×\97ר×\95× ×\94",
+       "rcfilters-filtergroup-lastRevision": "×\92רס×\90×\95ת ×\90×\97ר×\95× ×\95ת",
        "rcfilters-filter-lastrevision-label": "הגרסה האחרונה",
-       "rcfilters-filter-lastrevision-description": "השינוי האחרון בדף.",
-       "rcfilters-filter-previousrevision-label": "×\92רס×\90×\95ת ×§×\95×\93×\9e×\95ת",
-       "rcfilters-filter-previousrevision-description": "כל השינויים שאינם השינוי האחרון בדף.",
+       "rcfilters-filter-lastrevision-description": "רק ×\94ש×\99× ×\95×\99 ×\94×\90×\97ר×\95×\9f ×\91×\93×£.",
+       "rcfilters-filter-previousrevision-label": "×\9c×\90 ×\94×\92רס×\94 ×\94×\90×\97ר×\95× ×\94",
+       "rcfilters-filter-previousrevision-description": "כל השינויים שאינם \"הגרסה האחרונה\".",
        "rcfilters-filter-excluded": "מוחרג",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:לא</strong> $1",
        "rcfilters-exclude-button-off": "להחריג את המסומנים",
        "delete-warning-toobig": "דף זה כולל מעל {{PLURAL:$1|גרסה אחת|$1 גרסאות}} בהיסטוריית העריכות שלו. מחיקה שלו עלולה להפריע לפעולות בבסיס הנתונים; אנא שקלו שנית את המחיקה.",
        "deleteprotected": "אין {{GENDER:|באפשרותך|באפשרותך|באפשרותכם}} למחוק את הדף כי הוא מוגן.",
        "deleting-backlinks-warning": "<strong>אזהרה:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|דפים אחרים]] מקשרים לדף ש{{GENDER:|אתה עומד|את עומדת|אתם עומדים}} למחוק או מכלילים אותו.",
+       "deleting-subpages-warning": "<strong>אזהרה:</strong> לדף ש{{GENDER:|אתה עומד|את עומדת|אתם עומדים}} למחוק יש [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|דף משנה|$1 דפי משנה|51=יותר מ־50 דפי משנה}}]].",
        "rollback": "שחזור עריכות",
        "rollbacklink": "שחזור",
        "rollbacklinkcount": "שחזור {{PLURAL:$1|עריכה אחת|$1 עריכות}}",
        "fileduplicatesearch-noresults": "לא נמצא קובץ בשם \"$1\".",
        "specialpages": "דפים מיוחדים",
        "specialpages-note-top": "מקרא",
-       "specialpages-note": "* דפים מיוחדים רגילים.\n* <span class=\"mw-specialpagerestricted\">דפים מיוחדים מוגבלים.</span>",
        "specialpages-group-maintenance": "דיווחי תחזוקה",
        "specialpages-group-other": "דפים מיוחדים אחרים",
        "specialpages-group-login": "כניסה לחשבון / הרשמה",
index b48188e..5f2ba29 100644 (file)
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|नए पन्नों की सूची]] को भी देखें)",
        "recentchanges-submit": "दिखाएँ",
        "rcfilters-activefilters": "सक्रिय फिल्टर",
+       "rcfilters-limit-shownum": "पिछले $1 बदलाव दिखायें",
+       "rcfilters-days-title": "कुछ दिनों के",
+       "rcfilters-hours-title": "कुछ घंटों के",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|दिन}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|घंटा|घंटे}}",
        "rcfilters-quickfilters": "सुरक्षित फ़िल्टर",
        "rcfilters-quickfilters-placeholder-title": "कोई कड़ी अभी तक सहेजा नहीं गया",
        "rcfilters-quickfilters-placeholder-description": "अपने फ़िल्टर सेटिंग को सहेजने और बाद में उपयोग करने के लिए नीचे दिये बूकमार्क छवि पर क्लिक करें।",
        "rcfilters-savedqueries-unsetdefault": "मूल के रूप से हटाएँ",
        "rcfilters-savedqueries-remove": "निकालें",
        "rcfilters-savedqueries-new-name-label": "नाम",
-       "rcfilters-savedqueries-apply-label": "सेटिंग संजोयें",
+       "rcfilters-savedqueries-new-name-placeholder": "फ़िल्टर का उद्देश्य समझाएँ",
+       "rcfilters-savedqueries-apply-label": "फ़िल्टर बनायें",
        "rcfilters-savedqueries-cancel-label": "रद्द करें",
        "rcfilters-savedqueries-add-new-title": "वर्तमान फ़िल्टर सेटिंग को सहेजें",
        "rcfilters-restore-default-filters": "मूलभूत फिल्टर पुनर्स्थापित करे",
        "rcfilters-invalid-filter": "अमान्य फ़िल्टर",
        "rcfilters-empty-filter": "कोई सक्रिय फिल्टर नहीं। सभी योगदान दिखाए गए है।",
        "rcfilters-filterlist-title": "फिल्टर",
-       "rcfilters-filterlist-whatsthis": "यह à¤\95à¥\8dया है?",
+       "rcfilters-filterlist-whatsthis": "यह à¤\95à¥\88सà¥\87 à¤\95ारà¥\8dय à¤\95रता है?",
        "rcfilters-filterlist-feedbacklink": "नए (बीटा) फिल्टर पर प्रतिक्रिया दें",
        "rcfilters-highlightbutton-title": "Highlight results",
        "rcfilters-highlightmenu-title": "रंग चुनें",
        "rcfilters-filter-editsbyother-label": "दूसरों के द्वारा बदलाव",
        "rcfilters-filter-editsbyother-description": "आपके बदलावों को छोड़ कर सभी के बदलाव।",
        "rcfilters-filtergroup-userExpLevel": "अनुभव स्तर (केवल पंजीकृत सदस्यों के लिए)",
-       "rcfilters-filter-user-experience-level-registered-label": "पंजीकृत:",
+       "rcfilters-filter-user-experience-level-registered-label": "पंजीकृत",
        "rcfilters-filter-user-experience-level-registered-description": "लॉग-इन संपादक।",
        "rcfilters-filter-user-experience-level-unregistered-label": "अपंजीकृत",
        "rcfilters-filter-user-experience-level-unregistered-description": "संपादक जो लॉग इन नहीं हैं।",
        "rcfilters-hideminor-conflicts-typeofchange-global": "\"लघु संपादन\" फ़िल्टर एक या एक से अधिक प्रकार के परिवर्तन फ़िल्टर के साथ संघर्ष करता है, क्योंकि कुछ प्रकार के परिवर्तन को \"लघु\" के रूप में निर्दिष्ट नहीं किया जा सकता है। परस्पर विरोधी फिल्टर ऊपर सक्रिय फिल्टर क्षेत्र में चिह्नित हैं।",
        "rcfilters-hideminor-conflicts-typeofchange": "कुछ प्रकार के परिवर्तन को \"लघु\" के रूप में निर्दिष्ट नहीं किया जा सकता है\", इसलिए यह फ़िल्टर निम्न प्रकार के परिवर्तन फिल्टर के साथ संघर्ष करता है: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "इस प्रकार का परिवर्तन फ़िल्टर \"लघु संपादन\" फ़िल्टर के साथ संघर्ष करता है। कुछ प्रकार के परिवर्तन को \"लघु\" के रूप में निर्दिष्ट नहीं किया जा सकता है।",
-       "rcfilters-filtergroup-lastRevision": "सदà¥\8dय अवतरण",
-       "rcfilters-filter-lastrevision-label": "à¤\85à¤\82तिम अवतरण",
+       "rcfilters-filtergroup-lastRevision": "नया अवतरण",
+       "rcfilters-filter-lastrevision-label": "नया अवतरण",
        "rcfilters-filter-lastrevision-description": "पृष्ठ का सबसे हाल में हुआ बदलाव",
        "rcfilters-filter-previousrevision-label": "पहले के अवतरण",
        "rcfilters-filter-previousrevision-description": "सभी परिवर्तन जो एक पृष्ठ में सबसे हाल के परिवर्तन नहीं हैं।",
        "rcfilters-filter-excluded": "अपवर्जित",
        "rcfilters-tag-prefix-namespace-inverted": " $1 <strong>:नहीं</strong>",
-       "rcfilters-view-tags": "à¤\9aिपà¥\8dपियाà¤\81",
+       "rcfilters-view-tags": "à¤\9fà¥\88à¤\97 à¤µà¤¾à¤²à¥\87 à¤¸à¤®à¥\8dपादन",
        "rcnotefrom": "नीचे <strong>$2</strong> के बाद से (<strong>$1</strong> तक) {{PLURAL:$5|हुआ बदलाव दर्शाया गया है|हुए बदलाव दर्शाए गये हैं}}।",
        "rclistfromreset": "चुने दिनांक पहले जैसा करें",
        "rclistfrom": "$3 $2 से नये बदलाव दिखाएँ",
        "fileduplicatesearch-noresults": "कोई फ़ाइल नाम \"$1\" मिला नहीं ।",
        "specialpages": "विशेष पृष्ठ",
        "specialpages-note-top": "कुंजी",
-       "specialpages-note": "* साधारण विशेष पृष्ठ।\n* <span class=\"mw-specialpagerestricted\">प्रतिबंधित विशेष पृष्ठ।</span>",
        "specialpages-group-maintenance": "अनुरक्षण रिपोर्ट",
        "specialpages-group-other": "अन्य विशेष पृष्ठ",
        "specialpages-group-login": "सत्र आरम्भ / खाता खोलें",
index 3fb2671..0362e77 100644 (file)
        "rcfilters-filterlist-noresults": "Nema filtera",
        "rcfilters-noresults-conflict": "Rezultati pretrage nisu pronađeni zbog sukoba kriterija pretrage",
        "rcfilters-state-message-fullcoverage": "Označavanje svih filtera u grupi je isto kao da nije označen niti jedan, tako da filter nema učinka. Grupa uključuje: $1",
-       "rcfilters-filtergroup-registration": "Registracija suradnika",
-       "rcfilters-filter-registered-label": "Prijavljeni",
-       "rcfilters-filter-registered-description": "Prijavljeni suradnici.",
-       "rcfilters-filter-unregistered-label": "Neprijavljeni",
-       "rcfilters-filter-unregistered-description": "Suradnici koji nisu prijavljeni.",
        "rcfilters-filtergroup-authorship": "Doprinosi prema autorima",
        "rcfilters-filter-editsbyself-label": "Uređivanja koja ste Vi napravili",
        "rcfilters-filter-editsbyself-description": "Vaša uređivanja.",
        "rcfilters-filter-editsbyother-label": "Promjene drugih suradnika",
        "rcfilters-filter-editsbyother-description": "Sve promjene osim Vaših.",
        "rcfilters-filtergroup-userExpLevel": "Napredna razina (samo za registrirane suradnike)",
+       "rcfilters-filter-user-experience-level-registered-label": "Prijavljeni",
+       "rcfilters-filter-user-experience-level-registered-description": "Prijavljeni suradnici.",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Neprijavljeni",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Suradnici koji nisu prijavljeni.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Novopridošli",
        "rcfilters-filter-user-experience-level-newcomer-description": "Manje od 10 uređivanja i 4 dana aktivnosti.",
        "rcfilters-filter-user-experience-level-learner-label": "Početnici",
        "fileduplicatesearch-noresults": "Nije pronađena datoteka s imenom \"$1\".",
        "specialpages": "Posebne stranice",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Normalne posebne stranice\n* <span class=\"mw-specialpagerestricted\">Posebne stranice s ograničenim pristupom.</span>",
        "specialpages-group-maintenance": "Izvješća održavanja",
        "specialpages-group-other": "Ostale posebne stranice",
        "specialpages-group-login": "Prijava/otvaranje računa",
index a6c70ed..225abb8 100644 (file)
        "fileduplicatesearch-noresults": "Nincs „$1” nevű fájl.",
        "specialpages": "Speciális lapok",
        "specialpages-note-top": "Jelmagyarázat",
-       "specialpages-note": "* Mindenki számára elérhető speciális lapok.\n* <span class=\"mw-specialpagerestricted\">Korlátozott hozzáférésű speciális lapok.</span>",
        "specialpages-group-maintenance": "Állapotjelentések",
        "specialpages-group-other": "További speciális lapok",
        "specialpages-group-login": "Bejelentkezés / fiók létrehozása",
index 1ef5576..bd41893 100644 (file)
        "fileduplicatesearch-result-n": "$1 նիշքն ունի {{PLURAL:$2|1 նույնական կրկնօրինակ|$2 նույնական կրկնօրինակ}}.",
        "fileduplicatesearch-noresults": "$1 անունով նիշք չի գտնվել",
        "specialpages": "Սպասարկող էջեր",
-       "specialpages-note": "* Հասարակ հատուկ էջեր։\n* <span class=\"mw-specialpagerestricted\">Սահմանափակված հատուկ էջեր։</span>",
        "specialpages-group-maintenance": "Տեխնիկական սպասարկման տեղեկատուներ",
        "specialpages-group-other": "Այլ հատուկ էջեր",
        "specialpages-group-login": "Մտնել / Գրանցվել",
index 0008dc3..de9f286 100644 (file)
        "rcfilters-filter-editsbyself-description": "I tuoi contributi.",
        "rcfilters-filter-editsbyother-label": "Modifiche di altri",
        "rcfilters-filter-editsbyother-description": "Tutte le modifiche eccetto le tue.",
-       "rcfilters-filtergroup-userExpLevel": "Registrazione ed esperienza",
+       "rcfilters-filtergroup-userExpLevel": "Registrazione utente ed esperienza",
        "rcfilters-filter-user-experience-level-registered-label": "Registrato",
        "rcfilters-filter-user-experience-level-registered-description": "Contributori che hanno effettuato l'accesso.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Non registrato",
        "rcfilters-hideminor-conflicts-typeofchange-global": "Il filtro \"Modifiche minori\" è in confitto con uno o più dei filtri \"Tipo di modifica\", perché certe modifiche non possono essere indicate come \"minori\". I filtri in conflitto sono indicati nell'area \"Filtri attivi\" qui sopra.",
        "rcfilters-hideminor-conflicts-typeofchange": "Alcuni tipi di modifiche non possono essere indicate come \"minori\", quindi questo filtro è in conflitto con i seguenti filtri \"Tipo di modifica\": $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Questo filtro \"Tipo di modifica\" è in conflitto con il filtro \"Modifiche minori\". Alcuni tipi di modifiche non possono essere indicati come \"minori\".",
-       "rcfilters-filtergroup-lastRevision": "Ultima versione",
-       "rcfilters-filter-lastrevision-label": "Ultima versione",
-       "rcfilters-filter-lastrevision-description": "Le ultime modifiche ad una pagina.",
-       "rcfilters-filter-previousrevision-label": "Versioni precedenti",
-       "rcfilters-filter-previousrevision-description": "Tutte le modifiche che non sono l'ultima modifica effettuata sulla voce.",
+       "rcfilters-filtergroup-lastRevision": "Ultime versioni",
+       "rcfilters-filter-lastrevision-label": "Versione attuale",
+       "rcfilters-filter-lastrevision-description": "Solo l'ultima modifica ad una pagina.",
+       "rcfilters-filter-previousrevision-label": "Non l'ultima versione",
+       "rcfilters-filter-previousrevision-description": "Tutte le modifiche che non sono la \"versione attuale\".",
        "rcfilters-filter-excluded": "Escluso",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:non</strong> $1",
        "rcfilters-exclude-button-off": "Escludi selezionato",
        "delete-warning-toobig": "La cronologia di questa pagina è molto lunga (oltre $1 {{PLURAL:$1|versione|versioni}}). La sua cancellazione può creare dei problemi di funzionamento al database di {{SITENAME}}; procedere con cautela.",
        "deleteprotected": "Non puoi cancellare questa pagina perché è stata protetta.",
        "deleting-backlinks-warning": "<strong>Attenzione:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|altre pagine]] contengono collegamenti o inclusioni alla pagina che stai per cancellare.",
+       "deleting-subpages-warning": "<strong>Attenzione:</strong> la pagina che stai per cancellare ha [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|una sotto-pagina|$1 sotto-pagine|51=più di 50 sotto-pagine}}]].",
        "rollback": "Annulla le modifiche",
        "rollbacklink": "rollback",
        "rollbacklinkcount": "rollback di {{PLURAL:$1|una modifica|$1 modifiche}}",
        "fileduplicatesearch-noresults": "Nessun file di nome \"$1\" trovato.",
        "specialpages": "Pagine speciali",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Pagine speciali non riservate.\n* <span class=\"mw-specialpagerestricted\">Pagine speciali riservate ad alcune categorie di utenti.</span>",
        "specialpages-group-maintenance": "Resoconti di manutenzione",
        "specialpages-group-other": "Altre pagine speciali",
        "specialpages-group-login": "Accesso / creazione utenze",
index af33996..023ca90 100644 (file)
        "searchbutton": "Golèk",
        "go": "Menyang",
        "searcharticle": "Menyang",
-       "history": "Sujarah kaca",
-       "history_short": "Sujarah",
-       "history_small": "sujarah",
+       "history": "Sajarah kaca",
+       "history_short": "Sajarah",
+       "history_small": "sajarah",
        "updatedmarker": "wis dianyari kawit tekaku mréné pungkasan",
        "printableversion": "Vèrsi cap-capan",
        "permalink": "Pranala permanèn",
        "showpreview": "Deleng pratuduh",
        "showdiff": "Tuduhaké owahan",
        "anoneditwarning": "<strong>Pènget:</strong> Panjenengan durung mlebu log. Alamat IP-né panjenengan bakal katon marang wong akèh manawa panjenengan mbesut. Manawa panjenengan <strong>[$1 mlebu log]</strong> utawa <strong>[$2 nggawé akun]</strong>, besutané panjenengan bakal dadi darbéné naragunané panjenengan lan uga ana kauntungan liya.",
-       "anonpreviewwarning": "<em>Panjenengan durung mlebu log. Yèn disimpen, alamat IP panjenengan bakal kacathet ing sujarah besutan kaca iki.</em>",
+       "anonpreviewwarning": "<em>Panjenengan durung mlebu log. Yèn disimpen, alamat IP panjenengan bakal kacathet ing sajarah besutan kaca iki.</em>",
        "missingsummary": "<strong>Pangéling-éling:</strong> Panjenengan ora ngisèni ringkesané besutan.\nManawa panjenengan mencèt \"$1\" manèh, besutané panjengan bakal kasimpen tanpa katerangan.",
        "selfredirect": "<strong>Pélik:</strong> Sampéyan ngalih kaca iki iya nyang kaca iki dhéwé.\nSampéyan mungkin salah wènèh tujuan kanggo alihan utawa salah mbesut kaca.\nYèn sampéyan ngeklik \"$1\" manèh, kaca alihan bakal digawé.",
        "missingcommenttext": "Mangga isi tanggepan ing ngisor iki.",
        "anontalkpagetext": "----\n<em>Iki kaca parembugané panganggo anonim sing durung gawé akun, utawa sing ora nganggo akuné.</em>\nMula, awak dhéwé kudu nganggo alamat IP awujud angka kanggo nglacak dhèwèké.\nAlamat IP mangkono bisa dianggo déning sawenèh panganggo.\nManawa panjenengan panganggo anonim lan rumasa yèn ana tanggepan sing ora ilok dieneraké marang panjenengan, mangga [[Special:CreateAccount|gawéa akun]] utawa [[Special:UserLogin|mlebua log]] kanggo ngéndhani salah pangira karo panganggo anonim liyané ing tembé buri.",
        "noarticletext": "Kala saiki kaca iki durung ana tulisané.\nSampéyan bisa [[Special:Search/{{PAGENAME}}|nggolèki sesirahing kaca iki]] sajeroning kaca liya,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} nggolèki log sing magepokan],\nutawa [{{fullurl:{{FULLPAGENAME}}|action=edit}} nggawé kaca iki]</span>.",
        "noarticletext-nopermission": "Saiki lagi ora ana tèks ing kaca iki. \nPanjenengan bisa [[Special:Search/{{PAGENAME}}|nggolèk sesirah kaca iki]] ing kaca liyané, \nutawa <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{urlencode:{{FULLPAGENAME}}}}}} nggolèk ing log sing gegayutan]</span>, nanging panjenengan ora kawogan nggawé kaca iki.",
-       "missing-revision": "Révisi #$1 saka kaca ajeneng \"{{FULLPAGENAME}}\" ora ana.\n\nIki biyasané kasababaké awit nututi pranala sujarah sing wis lawas saka sawijiné kaca sing wis dibusak.\nRerincèné bisa digolèki ing [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log busak].",
+       "missing-revision": "Révisi #$1 saka kaca ajeneng \"{{FULLPAGENAME}}\" ora ana.\n\nIki biyasané kasababaké awit nututi pranala sajarah sing wis lawas saka sawijiné kaca sing wis dibusak.\nRerincèné bisa digolèki ing [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log busak].",
        "userpage-userdoesnotexist": "Akun panganggo \"$1\" ora kadhaftar.\nMangga pesthèkaké dhisik yèn panjenengan péngin nggawé/mbesut kaca iki.",
        "userpage-userdoesnotexist-view": "Akun panganggo \"$1\" ora kadhaftar.",
        "blocked-notice-logextract": "Panganggo iki saiki lagi diblokir.\nLog pamblokiran pungkasan dituduhaké ing ngisor iki minangka bahan rujukan:",
        "histlast": "anyar dhéwé",
        "historysize": "($1 {{PLURAL:$1|bét|bét}})",
        "historyempty": "(suwung)",
-       "history-feed-title": "Sujarah owahan",
-       "history-feed-description": "Sujarah owahaning kaca iki ing wiki",
+       "history-feed-title": "Sajarah owahan",
+       "history-feed-description": "Sajarah owahaning kaca iki ing wiki",
        "history-feed-item-nocomment": "$1 ing $2",
        "history-feed-empty": "Kaca sing disuwun ora ditemokaké. Mbokmenawa wis dibusak saka wiki, utawa diwènèhi jeneng anyar. Coba [[Special:Search|golèka ing wiki]] kanggo kaca anyar sing rélevan.",
        "rev-deleted-comment": "(tingkesaning besutan dibusak)",
        "logdelete-failure": "'''Aturan pandhelikan ora bisa disèt:'''\n$1",
        "revdel-restore": "Ngowahi visiblitas (pangatonan)",
        "pagehist": "Babading kaca",
-       "deletedhist": "Sujarah kabusak",
+       "deletedhist": "Sajarah kabusak",
        "revdelete-hide-current": "Gagal ndhelikaké révisi tanggal $2, $1: iki arupa révisi paling anyar.\nRévisi iki ora bisa didhelikaké.",
        "revdelete-show-no-access": "Gagal nampilaké révisi tanggal $1, jam $2: révisi iki wis ditandhani \"kawates\".\nPanjenengan ora nduwèni aksès menyang révisi iki.",
        "revdelete-modify-no-access": "Gagal ngowahi révisi tanggal $1, jam $2: révisi iki wis ditandhani \"kawates\".\nPanjenengan ora nduwèni aksès menyang révisi iki.",
        "revdelete-offender": "Juru pangriptaning owahan:",
        "suppressionlog": "Log barang-barang sing didelikaké (''oversight'')",
        "suppressionlogtext": "Ngisor iki daptar apa-apa waé sing wis dibusak lan diblokir kalebu kontèn sing didhelikaké saka para pangurus.\nDelok [[Special:BlockList|daptar blokiran]] sing isiné daptar apa-apa waé sing lagi dilarang lan diblokir.",
-       "mergehistory": "Gabung sujarah kaca",
+       "mergehistory": "Gabung sajarah kaca",
        "mergehistory-header": "Ing kaca iki panjenengan bisa nggabung révisi-révisi sajarah saka sawijining kaca sumber menyang kaca anyar.\nPastèkna yèn owah-owahan iki bakal netepaké kasinambungan sajarah kaca.",
        "mergehistory-box": "Gabungna revisi-revisi saka rong kaca:",
        "mergehistory-from": "Kaca sumber:",
        "mergehistory-into": "Kaca paran:",
-       "mergehistory-list": "Sujarah besutan sing bisa digabung",
+       "mergehistory-list": "Sajarah besutan sing bisa digabung",
        "mergehistory-merge": "Révisi-révisi sing kapacak ing ngisor iki saka [[:$1]] bisa digabungaké menyang [[:$2]].\nGunakna tombol radio kanggo nggabungaké révisi-révisi sing digawé sadurungé wektu tartamtu. Gatèkna, menawa nganggo pranala navigasi bakal ngesèt ulang kolom iki.",
        "mergehistory-go": "Tuduhaké besutan sing bisa digabung",
        "mergehistory-submit": "Gabung owahan",
        "mergehistory-fail": "Ora bisa nggabung sajarah, coba dipriksa manèh kacané lan paramèter wektuné.",
        "mergehistory-fail-invalid-source": "Kaca sumber ora trep.",
        "mergehistory-fail-invalid-dest": "Kaca paran ora trep.",
-       "mergehistory-fail-no-change": "Panggabung sujarah ora nggabungaké rèvisi. Mangga priksanen kaca lan paramèter wektuné.",
+       "mergehistory-fail-no-change": "Panggabung sajarah ora nggabungaké rèvisi. Mangga priksanen kaca lan paramèter wektuné.",
        "mergehistory-fail-self-merge": "Kaca asal lan kaca paran padha.",
        "mergehistory-fail-timestamps-overlap": "Rèvisi asal tumpuk-undhung utawa njedhul sawisé révisi paran.",
-       "mergehistory-fail-toobig": "Ora bisa nggabungaké sujarah amarga {{PLURAL:$1|révisi}} sing arep dilih munjuli $1.",
+       "mergehistory-fail-toobig": "Ora bisa nggabungaké sajarah amarga {{PLURAL:$1|révisi}} sing arep dilih munjuli $1.",
        "mergehistory-no-source": "Kaca sumber $1 ora ana.",
        "mergehistory-no-destination": "Kaca paran $1 ora ana.",
        "mergehistory-invalid-source": "Kaca sumber kudu asesirah sing sah.",
        "mergelog": "Gabung log",
        "revertmerge": "Wurung gabung",
        "mergelogpagetext": "Ing ngisor iki kapacak daftar panggabungan sajarah kaca ing kaca liyané.",
-       "history-title": "Sujarah owahaning \"$1\"",
+       "history-title": "Sajarah owahaning \"$1\"",
        "difference-title": "Prabéda antara owahan \"$1\"",
        "difference-title-multipage": "Béda antarané kaca \"$1\" lan \"$2\"",
        "difference-multipage": "(Prabédhan antar kaca)",
        "right-autopatrol": "Gawé supaya suntingan-suntingan ditandhani minangka wis dipatroli",
        "right-patrolmarks": "Ndeleng tandha-tandha patroli owah-owahan anyar",
        "right-unwatchedpages": "Tuduhna daftar kaca-kaca sing ora diawasi",
-       "right-mergehistory": "Gabung sujarah kaca",
+       "right-mergehistory": "Gabung sajarah kaca",
        "right-userrights": "Besut kabèh hak panganggo",
        "right-userrights-interwiki": "Besut hak-haking panganggo asal wiki jaba",
        "right-siteadmin": "Kunci lan buka kunci basis data",
        "action-delete": "busak kaca iki",
        "action-deleterevision": "busak révisi",
        "action-deletelogentry": "busak isian log",
-       "action-deletedhistory": "deleng sujarah sing dibusak sawijiné kaca",
+       "action-deletedhistory": "deleng sajarah sing dibusak sawijiné kaca",
        "action-deletedtext": "deleng tèks révisi sing dibusak",
        "action-browsearchive": "nggolèki kaca-kaca sing wis dibusak",
        "action-undelete": "wurung busak kaca",
        "action-purge": "buwang kaca iki",
        "nchanges": "$1 {{PLURAL:$1|pangowahan|owah-owahan}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|saka keri dhewe mrene}}",
-       "enhancedrc-history": "sujarah",
+       "enhancedrc-history": "sajarah",
        "recentchanges": "Owahan pungkasan",
        "recentchanges-legend": "Pilihan owah-owahan pungkasan",
        "recentchanges-summary": "Runutna owah-owahan pungkasan ing wiki iki ing kaca iki.",
        "listfiles-latestversion-yes": "Iya",
        "listfiles-latestversion-no": "Ora",
        "file-anchor-link": "Barkas",
-       "filehist": "Sujarah barkas",
+       "filehist": "Sajarah barkas",
        "filehist-help": "Klik ing tanggal/wektuné saprelu ndeleng rupané barkasé nalika tanggal iku.",
        "filehist-deleteall": "busaken kabèh",
        "filehist-deleteone": "busaken iki",
        "deletereasonotherlist": "Alesan liya",
        "deletereason-dropdown": "*Alesan pambusakan\n** Spam\n** Vandalisme\n** Nglanggar hak cipta\n** Disuwun sing nulis\n** Pangalihan rusak",
        "delete-edit-reasonlist": "Besut alesané pambusak",
-       "delete-toobig": "Kaca iki darbé sujarah besutan sing dawa, punjul $1 {{PLURAL:$1|owahan}}.\nPambusak tumrap kaca sing kaya mangkono wis ora diidinaké nedya njagani murih ora ana karusakan ing {{SITENAME}}.",
-       "delete-warning-toobig": "Kaca iki duwé sujarah besut sing dawa, punjul $1 {{PLURAL:$1|révisi}}.\nMbusak kaca iki bisa ngrusak lakuné basis dhata ing {{SITENAME}};\nkudu diayahi kanthi ngati-ati.",
+       "delete-toobig": "Kaca iki darbé sajarah besutan sing dawa, punjul $1 {{PLURAL:$1|owahan}}.\nPambusak tumrap kaca sing kaya mangkono wis ora diidinaké nedya njagani murih ora ana karusakan ing {{SITENAME}}.",
+       "delete-warning-toobig": "Kaca iki duwé sajarah besut sing dawa, punjul $1 {{PLURAL:$1|révisi}}.\nMbusak kaca iki bisa ngrusak lakuné basis dhata ing {{SITENAME}};\nkudu diayahi kanthi ngati-ati.",
        "deleteprotected": "Panjenengan ora bisa mbusak kaca iki amarga direksa.",
        "deleting-backlinks-warning": "'''Awas:''' Kaca liyane mungkin ana sing nautake ing kaca sing arep sampeyan busak.",
        "rollback": "Pulihaké besutan",
        "lockedbyandtime": "(déning {{GENDER:$1|$1}} tanggal $2 wanci $3)",
        "move-page": "Ngalih $1",
        "move-page-legend": "Mindhah kaca",
-       "movepagetext": "Formulir ing ngisor iki bakal ngganti jeneng kaca lan ngalihaké kabèh sujarahé nyang jeneng anyar.\nJeneng lawas bakal dadi kaca alihan marang jeneng anyar.\nPanjenengan bisa ndandani kaca alihan sing otomatis nggayut nyang kaca asliné.\nYèn ora, pesthèkaké yèn panjenengan wis mriksa ana-orané kaca alihan [[Special:DoubleRedirects|dhobel]] utawa [[Special:BrokenRedirects|rusak]].\nPanjenengan kudu tanggon saperlu mesthèkaké yèn pranalané menyang kaca sing samesthiné.\n\nÉling-élingen yèn kacané <strong>ora</strong> bakal dilih yèn jeneng sing dituju wis ana kacané, kajaba isiné kaca alihan sing ora ana sujarah besutané.\nIki ateges panjenengan bisa ngganti jeneng kaca bali nyang asliné manawa ana salah, lan panjenengan ora bisa ngamblegi kaca sing wis ana.\n\n<strong>Cathetan:</strong>\nTumindak iki bisa dadi owahan sing ora kinira lan gedhé mungguh ing kaca sing misuwur;\nmangga pesthèkaké dhisik yèn panjenengan mudheng temahané sadurungé mbacutaké.",
-       "movepagetext-noredirectfixer": "Formulir ing ngisor iki bakal ngganti jeneng kaca lan ngalihaké kabèh sujarahé nyang jeneng anyar.\nJeneng lawas bakal dadi kaca alihan marang jeneng anyar.\nPanjenengan kudu yakin yèn wis mriksa ana-orané kaca alihan [[Special:DoubleRedirects|dhobel]] utawa [[Special:BrokenRedirects|rusak]].\nPanjenengan kudu tanggon saperlu mesthèkaké yèn pranalané menyang kaca sing samesthiné.\n\nÉling-élingen yèn kacané <strong>ora</strong> bakal dilih yèn jeneng sing dituju wis ana kacané, kajaba isiné kaca alihan sing ora ana sujarah besutané.\nIki ateges panjenengan bisa ngganti jeneng kaca bali nyang asliné manawa ana salah, lan panjenengan ora bisa ngamblegi kaca sing wis ana.\n\n<strong>Cathetan:</strong>\nTumindak iki bisa dadi owahan sing ora kinira lan gedhé mungguh ing kaca sing misuwur;\nmangga pesthèkaké dhisik yèn panjenengan mudheng temahané sadurungé mbacutaké.",
+       "movepagetext": "Formulir ing ngisor iki bakal ngganti jeneng kaca lan ngalihaké kabèh sajarahé nyang jeneng anyar.\nJeneng lawas bakal dadi kaca alihan marang jeneng anyar.\nPanjenengan bisa ndandani kaca alihan sing otomatis nggayut nyang kaca asliné.\nYèn ora, pesthèkaké yèn panjenengan wis mriksa ana-orané kaca alihan [[Special:DoubleRedirects|dhobel]] utawa [[Special:BrokenRedirects|rusak]].\nPanjenengan kudu tanggon saperlu mesthèkaké yèn pranalané menyang kaca sing samesthiné.\n\nÉling-élingen yèn kacané <strong>ora</strong> bakal dilih yèn jeneng sing dituju wis ana kacané, kajaba isiné kaca alihan sing ora ana sajarah besutané.\nIki ateges panjenengan bisa ngganti jeneng kaca bali nyang asliné manawa ana salah, lan panjenengan ora bisa ngamblegi kaca sing wis ana.\n\n<strong>Cathetan:</strong>\nTumindak iki bisa dadi owahan sing ora kinira lan gedhé mungguh ing kaca sing misuwur;\nmangga pesthèkaké dhisik yèn panjenengan mudheng temahané sadurungé mbacutaké.",
+       "movepagetext-noredirectfixer": "Formulir ing ngisor iki bakal ngganti jeneng kaca lan ngalihaké kabèh sajarahé nyang jeneng anyar.\nJeneng lawas bakal dadi kaca alihan marang jeneng anyar.\nPanjenengan kudu yakin yèn wis mriksa ana-orané kaca alihan [[Special:DoubleRedirects|dhobel]] utawa [[Special:BrokenRedirects|rusak]].\nPanjenengan kudu tanggon saperlu mesthèkaké yèn pranalané menyang kaca sing samesthiné.\n\nÉling-élingen yèn kacané <strong>ora</strong> bakal dilih yèn jeneng sing dituju wis ana kacané, kajaba isiné kaca alihan sing ora ana sajarah besutané.\nIki ateges panjenengan bisa ngganti jeneng kaca bali nyang asliné manawa ana salah, lan panjenengan ora bisa ngamblegi kaca sing wis ana.\n\n<strong>Cathetan:</strong>\nTumindak iki bisa dadi owahan sing ora kinira lan gedhé mungguh ing kaca sing misuwur;\nmangga pesthèkaké dhisik yèn panjenengan mudheng temahané sadurungé mbacutaké.",
        "movepagetalktext": "Menawa sampéyan nyénthang kothak iki, kaca parembugan sing magepokan bakal otomatis dilih nyang sesirah anyar, kajaba kaca parembugané sing dituju wis ana isiné.\n\nYèn mangkéné, sampéyan kudu ngalih utawa nggabung kaca-kaca iku kanthi manual.",
        "moveuserpage-warning": "<strong>Pènget:</strong> Panjenengan iki arep ngalih kaca panganggo. Mangga èlingana yèn mung kacané waé sing bakal dilih, déné panganggoné <em>ora</em> bakal ganti jeneng.",
        "movecategorypage-warning": "<strong>Pélik:</strong> Panjenengan arep ngalih kaca kategori. Tulung gatèkaké yèn mung kacané thok sing bakal dilih déné samubarang kaca sing ana ing kategori lawas <em>ora</em> bakal mèlu dilih nyang kaca kategori anyar.",
        "pageinfo-title": "Inpormasi kanggo \"$1\"",
        "pageinfo-not-current": "Maaf, tidak mungkin memberikan informasi ini ke revisi lama.",
        "pageinfo-header-basic": "Informasi dhasar",
-       "pageinfo-header-edits": "Sujarah besutan",
+       "pageinfo-header-edits": "Sajarah besutan",
        "pageinfo-header-restrictions": "Perlindungan halaman",
        "pageinfo-header-properties": "Properti kaca",
        "pageinfo-display-title": "Sesirah pajangan",
        "fileduplicatesearch-noresults": "Ora tinemu barkas kanthi jeneng \"$1\".",
        "specialpages": "Kaca mirunggan",
        "specialpages-note-top": "Katrangan",
-       "specialpages-note": "* Kaca mirunggan sedhengan.\n* <span class=\"mw-specialpagerestricted\">Kaca mirunggan winatesan.</span>",
        "specialpages-group-maintenance": "Lapuran pangopèn",
        "specialpages-group-other": "Kaca mirunggan liyané",
        "specialpages-group-login": "Mlebu log / nggawé akun",
index 4fd7a36..18b1110 100644 (file)
        "rcfilters-legend-heading": "<strong>약어 목록:</strong>",
        "rcfilters-activefilters": "사용 중인 필터",
        "rcfilters-advancedfilters": "고급 필터",
+       "rcfilters-limit-shownum": "최근 $1개의 변경사항 표시",
        "rcfilters-days-show-days": "$1{{PLURAL:$1|일}}",
        "rcfilters-days-show-hours": "$1{{PLURAL:$1|시간}}",
        "rcfilters-quickfilters": "저장된 필터",
        "rcfilters-filter-editsbyself-description": "당신의 기여.",
        "rcfilters-filter-editsbyother-label": "다른 사용자의 변경사항",
        "rcfilters-filter-editsbyother-description": "당신을 제외한 모든 변경사항.",
-       "rcfilters-filtergroup-userExpLevel": "경험 수준 (등록된 사용자만)",
+       "rcfilters-filtergroup-userExpLevel": "사용자 등록 및 경험",
        "rcfilters-filter-user-experience-level-registered-label": "등록됨",
        "rcfilters-filter-user-experience-level-registered-description": "로그인된 편집자.",
        "rcfilters-filter-user-experience-level-unregistered-label": "등록 안 됨",
        "rcfilters-hideminor-conflicts-typeofchange-global": "특정한 유형의 변경사항을 \"사소한 편집\"으로 지정할 수 없기 때문에 \"사소한 편집\" 필터는 하나 이상의 변경사항 유형 필터와 충돌합니다. 충돌되는 필터들은 위의 사용 중인 필터 영역에 표시됩니다.",
        "rcfilters-hideminor-conflicts-typeofchange": "특정한 종류의 변경사항은 \"사소한 편집\"으로 지정할 수 없으므로 이 필터는 다음 유형의 변경사항 필터와 충돌합니다: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "이 유형의 변경사항 필터는 \"사소한 편집\" 필터와 충돌합니다. 특정한 종류의 변경사항은 \"사소한 편집\"으로 지정할 수 없습니다.",
-       "rcfilters-filtergroup-lastRevision": "마지막 판",
-       "rcfilters-filter-lastrevision-label": "마지막 판",
-       "rcfilters-filter-lastrevision-description": "문서의 최근 변경사항입니다.",
-       "rcfilters-filter-previousrevision-label": "ì\9d´ì \84 í\8c\90",
-       "rcfilters-filter-previousrevision-description": "문서에 대한 최근 변경사항이 아닌 모든 변경사항입니다.",
+       "rcfilters-filtergroup-lastRevision": "최신판",
+       "rcfilters-filter-lastrevision-label": "최신판",
+       "rcfilters-filter-lastrevision-description": "문서의 최근 변경사항입니다.",
+       "rcfilters-filter-previousrevision-label": "ìµ\9cì\8b í\8c\90ì\9d´ ì\95\84ë\8b\98",
+       "rcfilters-filter-previousrevision-description": "\"최신판\"이 아닌 모든 변경사항입니다.",
        "rcfilters-filter-excluded": "제외됨",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:아님</strong> $1",
        "rcfilters-view-tags": "태그된 편집",
        "delete-warning-toobig": "이 문서에는 {{PLURAL:$1|편집 역사}}가 $1개 있습니다.\n편집 역사가 긴 문서를 삭제하면 {{SITENAME}} 데이터베이스 동작에 큰 영향을 줄 수 있습니다.\n주의해 주세요.",
        "deleteprotected": "이 문서가 보호되어 있기 때문에 삭제할 수 없습니다.",
        "deleting-backlinks-warning": "<strong>경고:</strong> 삭제하려는 문서가 [[Special:WhatLinksHere/{{FULLPAGENAME}}|다른 문서]]에 링크되어 있거나 끼워져 있습니다.",
+       "deleting-subpages-warning": "<strong>경고:</strong> 삭제하려는 문서에 [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|하나의 하위 문서|$1개의 하위 문서|51=50개 이상의 하위 문서}}]]가 있습니다.",
        "rollback": "편집 되돌리기",
        "rollbacklink": "되돌리기",
        "rollbacklinkcount": "{{PLURAL:$1|편집}} $1회 되돌리기",
        "fileduplicatesearch-noresults": "\"$1\"이라는 이름을 가진 파일이 없습니다.",
        "specialpages": "특수 문서 목록",
        "specialpages-note-top": "범례",
-       "specialpages-note": "* 일반 특수 문서입니다.\n* <span class=\"mw-specialpagerestricted\">제한된 특수 문서입니다.</span>",
        "specialpages-group-maintenance": "관리용 목록",
        "specialpages-group-other": "다른 특수 문서",
        "specialpages-group-login": "로그인 / 계정 만들기",
index 3718d88..874f2b5 100644 (file)
        "rcfilters-legend-heading": "<strong>Lëscht vun Ofkierzungen:</strong>",
        "rcfilters-activefilters": "Aktiv Filteren",
        "rcfilters-advancedfilters": "Erweidert Filteren",
+       "rcfilters-limit-title": "Ännerunge fir ze weisen",
+       "rcfilters-limit-shownum": "Lescht $1 Ännerunge weisen",
+       "rcfilters-days-title": "Rezent Deeg",
+       "rcfilters-hours-title": "Rezent Stonnen",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|Dag|Deeg}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|Stonn|Stonnen}}",
        "rcfilters-quickfilters": "Gespäichert Filteren",
        "rcfilters-quickfilters-placeholder-title": "Nach keng Linke gespäichert",
        "rcfilters-quickfilters-placeholder-description": "Fir Är Filterastellungen z'änneren a méi spéit nees ze benotzen, klickt op d'Zeeche  fir Lieszeechen (bookmark) am Beräich vun den Aktive Filteren hei drënner.",
        "rcfilters-invalid-filter": "Net valabele Filter",
        "rcfilters-empty-filter": "Keen aktive Filter. All Kontributioune gi gewisen.",
        "rcfilters-filterlist-title": "Filteren",
-       "rcfilters-filterlist-whatsthis": "Wat ass dat?",
+       "rcfilters-filterlist-whatsthis": "Wéi geet dat?",
        "rcfilters-highlightbutton-title": "Resultater ervirhiewen",
        "rcfilters-highlightmenu-title": "Eng Faarf eraussichen",
        "rcfilters-filterlist-noresults": "Keng Filtere fonnt",
        "rcfilters-filter-editsbyself-description": "Är eegen Ännerungen.",
        "rcfilters-filter-editsbyother-label": "Ännerunge vun Aneren",
        "rcfilters-filter-editsbyother-description": "All Ännerunge ausser Ären eegenen.",
-       "rcfilters-filtergroup-userExpLevel": "Niveau vun der Erfahrung (just fir registréiert Benotzer)",
+       "rcfilters-filtergroup-userExpLevel": "Umeldung an Erfarung vu Benotzer",
        "rcfilters-filter-user-experience-level-registered-label": "Ugemellt",
        "rcfilters-filter-user-experience-level-unregistered-label": "Net-ugemellt",
        "rcfilters-filter-user-experience-level-unregistered-description": "Auteuren déi net ageloggt sinn.",
        "rcfilters-filter-logactions-label": "Protokolléiert Aktiounen",
        "rcfilters-filter-logactions-description": "Administrativ Aktiounen, Uleeë vu Benotzerkonten, Läsche vu Säiten, Eropgeluede Fichieren, ...",
        "rcfilters-hideminor-conflicts-typeofchange": "Verschidden Type vu Ännerunge kënnen net als \"kleng\" markéiert ginn, dofir ass dëse Filter a Konflikt mat dësem Typ vun Ännerungsfilteren: $1",
-       "rcfilters-filtergroup-lastRevision": "Lescht Versioun",
+       "rcfilters-filtergroup-lastRevision": "Lescht Versiounen",
        "rcfilters-filter-lastrevision-label": "Lescht Versioun",
-       "rcfilters-filter-lastrevision-description": "Déi lescht Ännerung op enger Säit",
-       "rcfilters-filter-previousrevision-label": "Méi fréi Versiounen",
+       "rcfilters-filter-lastrevision-description": "Nëmmen déi lescht Ännerung op enger Säit.",
+       "rcfilters-filter-previousrevision-label": "Net déi lescht Versioun",
        "rcfilters-filter-previousrevision-description": "All Ännerungen, déi net déi rezenst Ännerung vun enger Säit sinn.",
        "rcfilters-filter-excluded": "Ausgeschloss",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:net</strong> $1",
        "fileduplicatesearch-noresults": "Et gouf kee Fichier mam Numm \"$1\" fonnt.",
        "specialpages": "Spezialsäiten",
        "specialpages-note-top": "Erklärung",
-       "specialpages-note": "* Normal Spezialsäiten.\n* <span class=\"mw-specialpagerestricted\">Spezialsäite fir Benotzer mat méi Rechter.</span>",
        "specialpages-group-maintenance": "Maintenance-Rapporten",
        "specialpages-group-other": "Aner Spezialsäiten",
        "specialpages-group-login": "Aloggen / Benotzerkont uleeën",
index 96c2aa3..2a89684 100644 (file)
        "changepassword-success": "Dien wachwaord is verangerd!",
        "changepassword-throttled": "Doe höbs te huifig geperbeerd aan te melje.\nDoe mós effe $1 wachte ierdets te 't obbenuuts kens perbere.",
        "botpasswords": "Botwachwäörd",
+       "botpasswords-summary": "<em>Botwachwäörd</em> zörge veur tougank tot de API via 'ne gebroeker zónger gebroek te make van aanmeljgegaeves van daen gebroeker. De gebroekersrechte die besjikbaar zint kónne aafwieke wen me is aangemeldj mit e botwachwaord.\n\nWen se neet wèts waat hie de gevölge van zeen den is 't henjiger dit neet te doon. Nemes huuert dich te vraoge e botwachwaord aan te make en dem aan hem door te gaeve.",
        "botpasswords-disabled": "Botwachwäörd zint oetgezatj.",
        "botpasswords-no-central-id": "Veur botwachwäörd te gebroeke mós se aangemeldj zeen mit 'ne gecentraliseerdje gebroeker.",
        "botpasswords-existing": "Bestaonde botwachwäörd",
        "botpasswords-updated-body": "'t Botwachwaord veur de botnaam \"$1\" van gebroeker \"$2\" is biegewirk.",
        "botpasswords-deleted-title": "Botwachwaord eweggesjaf",
        "botpasswords-deleted-body": "'t Botwachwaord veure botnaam \"$1\" vanne gebroeker \"$2\" is eweggesjaf.",
+       "botpasswords-newpassword": "'t Nuuj wachwaord veur aan te melje mit <strong>$1</strong> is <strong>$2</strong>. <em>Bewaar dit goed voor toekomstig gebruik.</em> <br> (Veur aaj bots die vereisje det de aameljnaam 'tzelfde is es d'n eventuele gebroekersnaam, kan ouch <strong>$3</strong> es gebroekersnaam en <strong>$4</strong> es wachwaord waere gebroek.)",
+       "botpasswords-no-provider": "BotPasswordsSessionProvider is neet besjikbaar.",
+       "botpasswords-restriction-failed": "Botwachwaordbepirkinge verkómme aanmelje.",
+       "botpasswords-invalid-name": "De gebroekersnaam bevatj neet 't sjeijingsteike van 't botwachwaord (\"$1\").",
+       "botpasswords-not-exist": "Gebroekers \"$1\" haet gei botwachwaord genaamp \"$2\".",
        "resetpass_forbidden": "Wachwäörd kónne neet verangerd waere",
+       "resetpass_forbidden-reason": "Wachwäörd kónne neet verangerd waere: $1",
        "resetpass-no-info": "Doe moos aangemeld zien ierdets doe dees pagina gebroeke kens.",
        "resetpass-submit-loggedin": "Wachwaord wiezige",
        "resetpass-submit-cancel": "Aafbraeke",
        "resetpass-wrong-oldpass": "'t Hujig of tiedelik wachwaord is ongeljig.\nMeugelik höbs doe dien wachwaord al gewiezig of 'n nuuj tiedelik wachwaord aangevraog.",
+       "resetpass-recycled": "Veranger die wachwaord nao get anges es 't hujig wachwaord.",
+       "resetpass-temp-emailed": "Doe bös aangemeldj mit 'n tiejelike code die se per e-mail höbs gekrege.\nVeur 't aanmelje aaf te make mós se hie e nuuj wachwaord instèlle:",
        "resetpass-temp-password": "Tiedelik wachwaord:",
+       "resetpass-abort-generic": "De wachwaordverangering is aafgebraoke door 'n oetbreijing.",
+       "resetpass-expired": "Die wachwaord is verloupe. Stèl e nuuj wachwaord in veur dich aan te melje.",
+       "resetpass-expired-soft": "Die wachwaord is verloupe en mót oppernuuj waeren ingestèldj.\nKees noe e nuuj wachwaord of klik op \"{{int:authprovider-resetpass-skip-label}}\" veur dit spaejer te doon.",
+       "resetpass-validity-soft": "Die wachwaord is neet geljig: $1\n\nKees noe e nuuj wachwaord of klik op \"{{int:authprovider-resetpass-skip-label}}\" veur dit spaejer oppernuuj in te stèlle.",
        "passwordreset": "Wachwaord obbenuuts insjtèlle",
+       "passwordreset-text-one": "Völ dit formeleer in veur die wachwaord oppernuuj in te stèlle (doe kriegs e berich via de e-mail).",
+       "passwordreset-text-many": "{{PLURAL:$1|Völ ei van de gegaevesveljer in veur per e-mail e tiedelijk wachwaord te kriege.}}",
        "passwordreset-disabled": "'t Is hie neet meugelik óm die wachwaord óbbenuits in te sjtelle.",
+       "passwordreset-emaildisabled": "E-mailmeugelikheje staon oet op deze wiki.",
        "passwordreset-username": "Gebroekersnaam:",
        "passwordreset-domain": "Domein:",
        "passwordreset-email": "E-mailadres:",
        "passwordreset-emailtitle": "Gebroekersgegaeves óp {{SITENAME}}",
-       "passwordreset-emailtext-ip": "Emes, wersjienlik doe, vanaaf 't IP-adres $1, haet dien gebroekersgegaeves veur {{SITENAME}} ($4) ópgevraog.\nDe volgende {{PLURAL:$3|gebroeker is|gebroekers zint}} gekoppeld aan dit e-mailadres:\n\n$2\n\n{{PLURAL:$3|Dit tiedelik wachwaord vervilt|Dees tiedelike wachweurd vervallen}} euver {{PLURAL:$5|einen daag|$5 daag}}.\nMel dich aan en veranger 't wachwaord noe. Es se dit verzeuk neet zelf hes gedaon, of es se 't oorspronkelik wachwaord nog kins en 't neet anges wils, laot dit berich den en blief dien aad wachwaord gebroeke.",
-       "passwordreset-emailtext-user": "Gebroeker $1 op de site {{SITENAME}} haet dien gebroekersgegaeves veur {{SITENAME}} ($4) ópgevraog.\nDe volgende {{PLURAL:$3|gebroeker is|gebroekers zint}} gekoppeld aan dit e-mailadres:\n\n$2\n\n{{PLURAL:$3|Dit tiedelik wachwaord vervilt|Dees tiedelike wachweurd vervallen}} euver {{PLURAL:$5|einen daag|$5 daag}}.\nMel dich aan en veranger 't wachwaord noe. Es se dit verzeuk neet zelf hes gedaon, of es se 't oorspronkelik wachwaord nog kins en 't neet anges wils, laot dit berich den en blief dien aad wachwaord gebroeke.",
+       "passwordreset-emailtext-ip": "Emes, wersjienlik doe, vanaaf 't IP-adres $1, haet dien gebroekersgegaeves veur {{SITENAME}} ($4) ópgevraog veur 't wachwaord oppernuuj in te stèlle.\nDe volgende {{PLURAL:$3|gebroeker is|gebroekers zint}} gekoppeld aan dit e-mailadres:\n\n$2\n\n{{PLURAL:$3|Dit tiedelik wachwaord vervilt|Dees tiedelike wachweurd vervallen}} euver {{PLURAL:$5|einen daag|$5 daag}}.\nMel dich aan en veranger 't wachwaord noe. Es se dit verzeuk neet zelf hes gedaon, of es se 't oorspronkelik wachwaord nog kins en 't neet anges wils, laot dit berich den en blief dien aad wachwaord gebroeke.",
+       "passwordreset-emailtext-user": "Gebroeker $1 op de site {{SITENAME}} haet dien gebroekersgegaeves veur {{SITENAME}} ($4) ópgevraog veur die wachwaord oppernuuj in te stèlle.\nDe volgende {{PLURAL:$3|gebroeker is|gebroekers zint}} gekoppeld aan dit e-mailadres:\n\n$2\n\n{{PLURAL:$3|Dit tiedelik wachwaord vervilt|Dees tiedelike wachweurd vervallen}} euver {{PLURAL:$5|einen daag|$5 daag}}.\nMel dich aan en veranger 't wachwaord noe. Es se dit verzeuk neet zelf hes gedaon, of es se 't oorspronkelik wachwaord nog kins en 't neet anges wils, laot dit berich den en blief dien aad wachwaord gebroeke.",
        "passwordreset-emailelement": "Gebroekersnaam: \n$1\n\nTiedelik wachwaord: \n$2",
-       "passwordreset-emailsentemail": "d'r Is per mail 'n herinnering versjik.",
+       "passwordreset-emailsentemail": "Es dit e-mailadres aan diene gebroeker is gekoppeldj, den weurt 'nen e-mail gesjik veur die wachwaord oppernuuj in te stèlle.",
+       "passwordreset-emailsentusername": "Wen 'n e-mailadres is geregistreerd veur daen gebroekersnaam, den weurt 'nen e-mail gesjik veur die wachwaord oppernuuj in te stèlle.",
+       "passwordreset-nocaller": "'nen Aanroper mót waeren opgegaove",
        "passwordreset-nosuchcaller": "Aanroper besteit neet: $1",
-       "changeemail": "Veranger dien e-mailadres",
-       "changeemail-header": "Veranger 't e-mailadres van miene gebroekersnaam",
+       "passwordreset-ignored": "'t Oppernuuj instèlle van 't wachwaord is neet aafgehanjeldj gewaore.\nMesjiens steit geine provider geconfigureerdj?",
+       "passwordreset-invalidemail": "Óngeljig e-mailadres",
+       "passwordreset-nodata": "Zowaal geine gebroekersnaam es 'n e-mailadres is opgegaove gewaore",
+       "changeemail": "Veranger of haol dien e-mailadres eweg",
+       "changeemail-header": "Völ dit formeleer in veur dien e-mailadres te verangere. Wens se 't e-mailadres wils óntkoppele van diene gebroeker, laot 't e-mailadres den laeg wens se 't formeleer opsleis.",
        "changeemail-no-info": "Doe moos aangemeld zien ierdets doe dees pagina gebroeke kens.",
        "changeemail-oldemail": "Hujig mailadres:",
        "changeemail-newemail": "Nuuj mailadres:",
+       "changeemail-newemail-help": "Laot dit veldj laeg wens se dien e-mailadres eweg wils haole. Nao 't ewegsjaffe kóns se nimmieë e vergaete wachwaord oppernuuj instèlle en kriegs se gein e-mails mieë van deze wiki.",
        "changeemail-none": "(gein)",
+       "changeemail-password": "Die wachwaord veur {{SITENAME}}:",
        "changeemail-submit": "Veranger e-mail",
+       "changeemail-throttled": "Doe höbs te huifig geperbeerd dit aan te melje.\nDoe mós effe $1 wachte ierdets te 't obbenuuts kens perbere.",
+       "changeemail-nochange": "Veur 'n anger e-mailadres in.",
+       "resettokens": "Stèl teikes oppernuuj in",
+       "resettokens-no-tokens": "'t Geuf gein teikes veur oppernuuj in te stèlle.",
+       "resettokens-tokens": "Teikes:",
+       "resettokens-token-label": "$1 (hujige waerd: $2)",
+       "resettokens-watchlist-token": "Teike veur webfeed van [[Special:Watchlist|dien volglies]] (Atom/RSS)",
+       "resettokens-done": "Teikes oppernuuj ingestèldj.",
+       "resettokens-resetbutton": "Stèl gesillekteerde teikes oppernuuj in",
        "bold_sample": "Vètten teks",
        "bold_tip": "Vetten teks",
        "italic_sample": "Sjuunsen tèks",
        "sig_tip": "Dien handjteikening mit datum en tied",
        "hr_tip": "Horizontaal lien (gebroek spaarzaam)",
        "summary": "Samevatting:",
-       "subject": "Ongerwerp/kop:",
+       "subject": "Óngerwirp:",
        "minoredit": "Dit is 'n klein verangering",
        "watchthis": "Volg dees pagina",
-       "savearticle": "Pagina opsjlaon",
+       "savearticle": "Sjlaon pagina op",
+       "savechanges": "Slaon verangeringe op",
        "publishpage": "Pagina publicere",
        "publishchanges": "Verangeringe publicere",
        "preview": "Naokieke",
        "showpreview": "Betrach dees bewirking",
        "showdiff": "Toen verangeringe",
+       "blankarticle": "<strong>Waorsjoewing:</strong> de pagina die se wils aanmake is laeg.\nWens se oppernuuj op \"$1\" kliks, wuuertj de pagina aangemaak zónger welchen inhawd den ouch.",
        "anoneditwarning": "<strong>Waorsjoewing:</strong> Doe bös neet aangemeldj.\nDien IP-adres wuuertj opgeslage wen se verangeringe maaks op dees pagina. Wens doe <strong>[$1 dich aanmeljs]</strong> of <strong>[$2 'ne gebroeker aanmaaks]</strong> versjiene dien bewirkinge ónger diene gebroekersnaam, naeve anges veurdeiler.",
        "anonpreviewwarning": "''Doe bös neet aangemeldj.''\n''Door dien bewèrking op te slaon wört dien IP-adres opgeslagen in de paginagesjiedenis.''",
        "missingsummary": "'''Herinnering:''' doe höbs gein samevatting opgegaeve veur dien bewirking. Es te weer op ''Pagina opslaon'' kliks weurt de bewirking zonger samevatting opgesjlage.",
+       "selfredirect": "<strong>Waorsjoewing:</strong> Doe höbs 'ne redirek gemaak nao dees pagina.\nMeugelik höbs se 'n verkieërdje bestumming veure redirek gebroek of bewirks se de verkieërdje pagina.\nDoor nans op \"$1\" te klikke wuuertj de redirek tonna gemaak.",
        "missingcommenttext": "Plaats dien opmèrking hiej onger, a.u.b.",
        "missingcommentheader": "'''Let op:''' Doe höbs gén ongerwerp/kop veur deze opmèrking opgegaeve. Esse oppernuuj op \"$1\" kliks, wörd dien verangering zonger ongerwerp/kop opgeslage.",
-       "summary-preview": "Naokieke samevatting:",
-       "subject-preview": "Naokieke ongerwerp/kop:",
+       "summary-preview": "Veurvertoeaning van de bewirkingssamevatting:",
+       "subject-preview": "Veurvertoeaning van 't óngerwirp:",
+       "previewerrortext": "'n Fout is opgetraoje tiejes 't waergaeve van dien verangeringe.",
        "blockedtitle": "Gebroeker is geblokkeerd",
        "blockedtext": "'''Dien gebroekersaccount of IP-adres is geblokkeerd.'''\n\nDe blokkade is oetgeveurd door $1. De opgegaeve raej is ''$2''.\n\n* Aanvang blokkade: $8\n* Ènj blokkade: $6\n* Bedoeld te blokkere: $7\n\nDe kèns contak opnumme mit $1 of 'ne angere [[{{MediaWiki:Grouppage-sysop}}|systeemwèrker]] óm de blokkade te besjpraeke.\nDe kèns gein gebroek make van de functie 'e-mail deze gebroeker', behauve es te 'n geldig e-mailadres höbs opgegaeve in dien [[Special:Preferences|veurkäöre]] en 't gebroek van deze fónksie neet geblokkeerd is.\nDien hujig IP-adres is $3 en 't nómmer van de blokkade is #$5. Vermeld beide gegaeves wens te örges op dees blokkade reageers.",
        "autoblockedtext": "Dien IP-adres is automatisch geblokkeerd omdet 't gebroek is door 'ne gebroeker, dae is geblokkeerd door $1.\nDe opgegaeve reje is:\n\n:''$2''\n\n* Aanvang blokkade: $8\n* Einde blokkade: $6\n* Blóksmeining: $7\n\nDoe kins deze blokkaasj bespraeke mèt $1 of 'ne angere [[{{MediaWiki:Grouppage-sysop}}|beheerder]]. Doe kins gén gebroek make van de functie 'e-mail deze gebroeker', tenzijse 'n geldig e-mailadres opgegaeve höbs in dien [[Special:Preferences|veurkeure]] en 't gebroek van deze functie neet is geblokkeerd.\n\nDien nömmer vanne blokkaasj is #$5 èn dien IP-adres is $3.\nVermeld det esse örges euver deze blokkaasj reageers.",
        "edit-gone-missing": "De pagina is neet biegewirk.\nZe lik eweggesjaf te zien.",
        "edit-conflict": "Bewirkingsconflik.",
        "edit-no-change": "Dien bewirking is genegeerd, ómdet d'r gein verangering in de teks is gemaak.",
+       "postedit-confirmation-created": "De pagina is aangemaak gewaore.",
+       "postedit-confirmation-restored": "De pagina is herstèldj gewaore.",
        "postedit-confirmation-saved": "Dien bewirking is opgeslage gewaore.",
        "edit-already-exists": "De pagina is neet aangemaak.\nZie besjteit al.",
        "defaultmessagetext": "Obligaten teks",
+       "invalid-content-data": "Óngeljige inhawdsgegaeves",
        "editwarning-warning": "Es se dees pagina verleets verluus se meugelik wieziginge die se haes gemaak.\nEs se bös aangemeld, kins se dees waorsjoewing oetzètten in 't bewerkingstabblaad in dien veurkäöre.",
+       "editpage-invalidcontentmodel-title": "Inhaadsmodel wuuertj neet óngersteund",
+       "editpage-invalidcontentmodel-text": "'t Inhawdsmodel \"$1\" weurt neet óngersteund",
+       "editpage-notsupportedcontentformat-title": "Inhawdsformaat neet óngersteund",
+       "editpage-notsupportedcontentformat-text": "'t Inhawdstype $1 weurt neet óngersteund door 't inhawdsmodel $2.",
        "content-model-wikitext": "wikiteks",
        "content-model-text": "teks zónger opmaak",
        "content-model-javascript": "JavaScript",
        "content-json-empty-object": "Laeg objek",
        "content-json-empty-array": "Laege rits",
+       "deprecated-self-close-category": "Pagina's mit óngeljige zelfsloetende HTML-tags",
        "expensive-parserfunction-warning": "'''Waarschuwing:''' dees pagina gebroek te väöl kosbare parserfuncties.\n\nNoe {{PLURAL:$1|is|zeen}} 't d'r $1, terwiel 't d'r minder es $2 {{PLURAL:$2|mótte|mótte}} zeen.",
        "expensive-parserfunction-category": "Pagina's die te väöl kosbare parserfuncties gebroeke",
        "post-expand-template-inclusion-warning": "Waorsjuwing: de maximaal transclusiegruudje veur sjablone is euversjri-jje.\nSommige sjablone waere neet getranscludeerd.",
        "post-expand-template-argument-warning": "Waarsjoewing: dees pagina bevat winnigstes eine sjabloonparameter mit 'n te groete transclusiegruutde.\nDees parameters zeen eweggelaote.",
        "post-expand-template-argument-category": "Pagina's die missende sjabloonillemènte bevatte",
        "parser-template-loop-warning": "D'r is 'ne krinkloup in sjablone geconstateerd: [[$1]]",
+       "template-loop-category": "Pagina's mit sjeblone die zichzelf insloete",
        "parser-template-recursion-depth-warning": "De recursiedeepte veur sjablone is euversjrede ($1)",
        "language-converter-depth-warning": "De deepdjelimiet veure spraokómzètter is euversjreje ($1)",
        "node-count-exceeded-category": "Pagina's wo 't maximaal aantal nodes te väöl is",
        "history-feed-empty": "De gevraogde pagina besjteit neet.\nWellich is ze gewis of verplaats.\n[[Special:Search|Doorzeuk de wiki]] veur relevante pagina's.",
        "rev-deleted-comment": "(bewirkingssamevatting eweggesjaf)",
        "rev-deleted-user": "(gebroeker weggehaold)",
-       "rev-deleted-event": "(actie weggehaold)",
+       "rev-deleted-event": "(logbookregel weggehaold)",
        "rev-deleted-user-contribs": "[gebroeker of IP gewösj - bewèrking verbórge in biedraag]",
        "rev-deleted-text-permission": "Dees bewerking is '''gewusj'''.\nDao kónne details aanwezig zeen in 't [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} wusjlogbook].",
+       "rev-suppressed-text-permission": "Dees paginaversie is <strong>óngerdrók</strong>.\nAchtergrönj zeen te vinjen in 't [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} logbook ven óngerdrökdje versies].",
        "rev-deleted-text-unhide": "Dees versie van de pagina is '''eweggesjaf'''.\nDetails zien meugelik te vinde in 't [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} wislogbook].\nEs beheerder kins se [$1 dees versie bekieke] es se wils.",
        "rev-suppressed-text-unhide": "Dees paginaversie is '''óngerdrök'''.\nAchtergrönj zeen meugelik te vinje in 't [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} logbook ven óngerdrökdje versies].\nEs behierder kèns toe [$1 de versjille bekieken] es se wils.",
        "rev-deleted-text-view": "Dees bewèrking is '''gewösj'''.\nEs beheerder kèns te deze zeen;\ndao kónne details aanwezig zeen in 't [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} wusjlogbook].",
        "revdelete-hide-user": "Verberg gebroekersnaam/IP van de gebroeker",
        "revdelete-hide-restricted": "Pas deze beperkinge toe op zowaal beheerders es angere",
        "revdelete-radio-same": "(anger neet)",
-       "revdelete-radio-set": "Jao",
-       "revdelete-radio-unset": "Nein",
+       "revdelete-radio-set": "Verstaoke",
+       "revdelete-radio-unset": "Zichbaar",
        "revdelete-suppress": "Ongerdruk gegaeves veur zowaal admins es angere",
        "revdelete-unsuppress": "Verwijder beperkinge op truuk gezatte wieziginge",
        "revdelete-log": "Reeje:",
        "revdelete-submit": "Pas toe op de geselecteerde {{PLURAL:$1|bewèrking|bewèrkinger}}",
-       "revdelete-success": "'''Wieziging zichbaarheid succesvol ingesteld.'''",
+       "revdelete-success": "Zichbaarheid van verangering biegewirk.",
        "revdelete-failure": "'''De zichbaarheid veur de versie kos neet ingesteld waere.'''\n$1",
-       "logdelete-success": "'''Zichbaarheid van de gebeurtenis succesvol ingesteld.'''",
+       "logdelete-success": "Zichbaarheid van gebäörtenis is ingestèldj gewaore.",
        "logdelete-failure": "'''De zichbaarheid van de logbookregel kos neet ingesteldj waere:'''\n$1",
        "revdel-restore": "Zichbaarheid verangere",
        "pagehist": "Paginagesjiedenis",
        "editundo": "maak óngedaon",
        "diff-empty": "(gei versjil)",
        "diff-multi-sameuser": "({{PLURAL:$1|Ein tösseligkendje versie|$1 tösseligkendje versies}} door dezelfdje gebroeker neet getoeandj)",
+       "diff-multi-otherusers": "({{PLURAL:$1|Ein tösseligkendje versie|$1 tösseligkendje versies}} door {{PLURAL:$2|einen angere gebroeker|$2 gebroekers}} neet getuind)",
        "diff-multi-manyusers": "($1 tösseligkende versies door mier es $2 gebroekers waere neet waergaeve)",
        "searchresults": "Zeukresultate",
        "searchresults-title": "Zeukresultate veur \"$1\"",
        "notextmatches": "Geen artikel gevonden met opgegeven zoekterm",
        "prevn": "veurige {{PLURAL:$1|$1}}",
        "nextn": "volgende {{PLURAL:$1|$1}}",
+       "prev-page": "veurige pazjena",
+       "next-page": "volgende pazjena",
        "prevn-title": "Vörge {{PLURAL:$1|resultaat|$1 resultate}}",
        "nextn-title": "Volgende {{PLURAL:$1|resultaat|$1 resultate}}",
        "shown-title": "$1 {{PLURAL:$1|resultaat|resultate}} per pagina weergaeve",
        "search-result-category-size": "{{PLURAL:$1|1 categorielid|$1 categorielede}} ({{PLURAL:$2|1 ongercategorie|$2 ongercategorieë}}, {{PLURAL:$3|1 bestandj|$3 bestenj}})",
        "search-redirect": "(redirek vanaaf $1)",
        "search-section": "(subkop $1)",
+       "search-category": "(categorie $1)",
        "search-file-match": "(kump euverein mit de bestandjsinhawd)",
        "search-suggest": "Meins te sóms: $1",
-       "search-interwiki-caption": "Zösterprojecte",
-       "search-interwiki-default": "$1 resultate:",
+       "search-rewritten": "De rizzeltaote veur $1 waere waergaeve. Zeuk inplaats nao $2.",
+       "search-interwiki-caption": "Rizzeltaote van zösterprojecte",
+       "search-interwiki-default": "Rizzeltaote van $1:",
        "search-interwiki-more": "(meer)",
+       "search-interwiki-more-results": "mieë rizzeltaote",
        "search-relatedarticle": "Gerelateerd",
        "searchrelated": "gerelateerd",
        "searchall": "alle",
        "showingresults": "Hieonger staon de <b>$1</b> {{PLURAL:$1|resultaat|resultaat}}, vanaaf #<b>$2</b>.",
        "search-showingresults": "{{PLURAL:$4|Rizzeltaot <strong>$1</strong> van <strong>$2</strong>|Rizzeltaote <strong>$1 - $2</strong> van <strong>$3</strong>}}",
        "search-nonefound": "D'r zien gein resultate veur diene zeukopdrach.",
+       "search-nonefound-thiswiki": "'t Goof gein rizzeltaote veur dien zeukopdrach op dees site.",
        "powersearch-legend": "Oetgebreid zeuke",
        "powersearch-ns": "Zeuke in naamruumdes:",
        "powersearch-togglelabel": "Conterleer:",
        "powersearch-toggleall": "Alle",
        "powersearch-togglenone": "Gein",
+       "powersearch-remember": "Ónthawt selectie veur toukumstige zeukopdrachte",
        "search-external": "Extern zeuke",
        "searchdisabled": "Zeuke op {{SITENAME}} is oetgesjakeld vanweige gebrek aan servercapaciteit.\nZoelang as de servers nog neet sjterk genog zunt kins e zeuke bie Google.\nMèrk op dat hun indexe van {{SITENAME}} content e bietje gedatierd kint zien.",
+       "search-error": "'n Fout is opgetraoje tiedes 't zeuke: $1",
+       "search-warning": "'n Waorsjoewing is opgetraoje tiedes 't zeuke: $1",
        "preferences": "Veurkäöre",
        "mypreferences": "Veurkäöre",
        "prefs-edits": "Aantal bewèrkinge:",
+       "prefsnologintext2": "Doe mós aanmelje veur dien veurkäöre in te stèlle.",
        "prefs-skin": "{{SITENAME}}-uterlik",
        "skin-preview": "Veurbesjouwing",
        "datedefault": "Gein veurkäör",
        "prefs-personal": "Gebroekersinfo",
        "prefs-rc": "Recènte verangeringe en weergaaf van sjtumpkes",
        "prefs-watchlist": "Volglies",
+       "prefs-editwatchlist": "Bewirk volglies",
+       "prefs-editwatchlist-label": "Bewirk items op dien volglies:",
+       "prefs-editwatchlist-edit": "Betrach en haol items op dien volglies eweg",
+       "prefs-editwatchlist-raw": "Bewirk roew volglies",
+       "prefs-editwatchlist-clear": "Maak dien volglies laeg",
        "prefs-watchlist-days": "Te tuine daag in de volglies:",
        "prefs-watchlist-days-max": "Maximaal $1 {{PLURAL:$1|daag|daag}}",
        "prefs-watchlist-edits": "Maximaal aantal bewirkinge in de oetgebreide volglies:",
        "prefs-watchlist-token": "Volgliessläötel:",
        "prefs-misc": "Anger insjtèllinge",
        "prefs-resetpass": "Wachwaord wiezige",
-       "prefs-changeemail": "Veranger e-mail",
+       "prefs-changeemail": "Veranger of haol dien e-mailadres eweg",
        "prefs-setemail": "Stel 'n e-mailadres in",
        "prefs-email": "E-mailopsjes",
        "prefs-rendering": "Oeterlik",
        "saveprefs": "Veurkäöre opsjlaon",
-       "restoreprefs": "Terug nao standaardinstellinge",
+       "restoreprefs": "Herstèl dien veurkäöre (veur alle instèllinge)",
        "prefs-editing": "Aafmeitinge tèksveld",
        "searchresultshead": "Insjtèllinge veur zeukresultate",
-       "stub-threshold": "Drempel veur markering <a href=\"#\" class=\"stub\">begske</a>:",
+       "stub-threshold": "Dörpel veur markering es sjtumpke ($1):",
+       "stub-threshold-sample-link": "veurbild",
        "stub-threshold-disabled": "Oetgezatj",
        "recentchangesdays": "Aantal daag te tuine in de recènte verangeringe:",
        "recentchangesdays-max": "(maximaal $1 {{PLURAL:$1|daag|daag}})",
        "timezoneregion-indian": "Indische Oceaan",
        "timezoneregion-pacific": "Stille Oceaan",
        "allowemail": "E-mail van anger gebroekers toesjtaon",
-       "prefs-searchoptions": "Zeukinstellinge",
+       "prefs-searchoptions": "Zeuke",
        "prefs-namespaces": "Naamruimte",
        "default": "sjtandaard",
        "prefs-files": "Bestenj",
        "prefs-reset-intro": "Gebroek dees functie om dien veurkäöre te herstelle nao de standaardinstellinge.\nDees hanjeling kin neet ongedaon gemaak waere.",
        "prefs-emailconfirm-label": "E-mailbevestiging:",
        "youremail": "Dien e-mailadres",
-       "username": "Gebroekersnaam:",
-       "prefs-memberingroups": "Lid van {{PLURAL:$1|gróp|gróppe}}:",
+       "username": "{{GENDER:$1|Gebroekersnaam}}:",
+       "prefs-memberingroups": "{{GENDER:$2|Lid}} van {{PLURAL:$1|gróp|gróppe}}:",
+       "group-membership-link-with-expiry": "$1 (toet $2)",
        "prefs-registration": "Registratiedatum:",
        "yourrealname": "Dienen echte naam*",
        "yourlanguage": "Taal van de gebroekersinterface",
        "badsiglength": "De handjteikening is te lank.\nZie maag neet mie es $1 {{PLURAL:$1|karakter|karakters}} bevatte.",
        "yourgender": "Geslach:",
        "gender-unknown": "Neet aangegaeve",
-       "gender-male": "Miensj",
-       "gender-female": "Vrów",
+       "gender-male": "Hae bewirk de wiki",
+       "gender-female": "Hèt bewirk de wiki",
        "prefs-help-gender": "Optioneel: dit wört gebroek om gebroekers correk aan te spraeke in de software.\nDeze informatie is zichbaar veur angere gebroekers.",
        "email": "E-mail",
-       "prefs-help-realname": "* Echte naam (opsjeneel): esse deze opgufs kin deze naam gebroek waere om dich erkinning te gaeve veur dien wèrk.",
+       "prefs-help-realname": "Echte naam is optioneel.\nEs se dezen opgeufs, kan deze naam waere gebroek veur dich erkènning te gaeve veur die werk.",
        "prefs-help-email": "E-mailadres is optioneel, mer maak 't muuëgelik óm dich e wachwaord te sjikke es s'n 't vergaete höbs.",
        "prefs-help-email-others": "Doe kans ouch angere in staat stelle per-email kóntak mit uch op te numme via 'n verwiezing op eur gebroekers- en euverlègkpazjena zónger det se diene identiteit luuëts weite.",
        "prefs-help-email-required": "Hiej veur is 'n e-mailadres neudig.",
        "prefs-signature": "Handjteikening",
        "prefs-dateformat": "Datumópmaak:",
        "prefs-timeoffset": "Tiedsversjèl",
-       "prefs-advancedediting": "Wiejer instèllinger",
+       "prefs-advancedediting": "Algemein instèllinge",
+       "prefs-editor": "Bewirker",
+       "prefs-preview": "Veurbesjouwing",
        "prefs-advancedrc": "Wiejer instèllinger",
        "prefs-advancedrendering": "Wiejer instèllinger",
        "prefs-advancedsearchoptions": "Wiejer instèllinger",
        "prefs-advancedwatchlist": "Wiejer instèllinger",
        "prefs-displayrc": "Toeaningsinstèllinger",
        "prefs-displaywatchlist": "Toeaningsinstèllinger",
+       "prefs-tokenwatchlist": "Teike",
        "prefs-diffs": "Vers",
-       "userrights": "Gebroekersrechtebeheer",
-       "userrights-lookup-user": "Beheer gebroekersgróppe",
+       "prefs-help-prefershttps": "Deze veurkäör weurt tougepas bie de volgendje aanmeljing",
+       "prefswarning-warning": "Doe höbs dees verangeringe gemaak in dien veurkäöre die nag neet zint opgeslage. Wen se de pagina verleuts zónger op \"$1\" te klikke waere dien veurkäöre neet biegewirk.",
+       "prefs-tabs-navigation-hint": "Hölp: doe kans de pielkesknuup nao links en rechs broeke veur te navigere tösse de tabblajer inne lies.",
+       "userrights": "Gebroekersrechte",
+       "userrights-lookup-user": "Selecteer 'ne gebroeker",
        "userrights-user-editname": "Veur 'ne gebroekersnaam in:",
-       "editusergroup": "Bewirk gebroekersgróppe",
-       "editinguser": "Bezig mit 't bewèrke van de gebroekersrechte van gebroeker '''[[User:$1|$1]]''' $2",
-       "userrights-editusergroup": "Bewirk gebroekersgróppe",
-       "saveusergroups": "Gebroekersgróppe opsjlaon",
+       "editusergroup": "Laaj gebroekersgruup",
+       "editinguser": "Bezig mit 't verangere van de gebroekersrechte van {{GENDER:$1|gebroeker}} '''[[User:$1|$1]]''' $2",
+       "viewinguserrights": "Gebroekersrechte betrachte van {{GENDER:$1|gebroeker}} <strong>[[User:$1|$1]]</strong> $2",
+       "userrights-editusergroup": "Bewirk {{GENDER:$1|gebroekersgruup}}",
+       "userrights-viewusergroup": "Toean {{GENDER:$1|gebroekersgruup}}",
+       "saveusergroups": "Slaon {{GENDER:$1|gebroekersgruup}} op",
        "userrights-groupsmember": "Leed van:",
        "userrights-groupsmember-auto": "Impliciet lid van:",
-       "userrights-groups-help": "De kèns de gróppe verangere woe deze gebroeker lid van is.\n* 'n Aangekruuts vinkvekske beteikent det de gebroeker lid is van de gróp.\n* 'n Neet aangekruuts vinkvekske beteikent det de gebroeker neet lid is van de gróp.\n* \"*\" Beteikent dets te 'ne gebroeker neet oet 'ne gróp eweg kèns haole naodets te die daobie höbs gedoon, of angersóm.",
+       "userrights-groups-help": "De kèns de gróppe verangere woe deze gebroeker lid van is.\n* 'n Aangekruuts vinkvekske beteikent det de gebroeker lid is van de gróp.\n* 'n Neet aangekruuts vinkvekske beteikent det de gebroeker neet lid is van de gróp.\n* \"*\" beteikent dets te 'ne gebroeker neet oet 'ne gróp eweg kèns haole naodets te die daobie höbs gedoon, of angersóm.\n* \"#\" beteikebt dets te dit grópslidmaotsjap allein kans verlinge. De kans 't neet verkorte.",
        "userrights-reason": "Reeje:",
        "userrights-no-interwiki": "Doe höbs gein rechte om gebroekersrechte op anger wiki's te wiezige.",
        "userrights-nodatabase": "Database $1 besteit neet of is gein plaatselike database.",
        "userrights-changeable-col": "Gróppe dies te kèns behere",
        "userrights-unchangeable-col": "Gróppe dies te neet kèns behere",
+       "userrights-expiry-current": "Verlöp $1",
+       "userrights-expiry-none": "Verlöp neet",
+       "userrights-expiry": "Verlöp:",
+       "userrights-expiry-existing": "Bestaonde verloupdatum: $2 $3",
+       "userrights-expiry-othertime": "Angere doer:",
+       "userrights-expiry-options": "1 daag:1 day,1 waek:1 week,1 maondj:1 month,3 maondj:3 months,6 maondj:6 months,1 jaor:1 year",
+       "userrights-invalid-expiry": "De verloiptied veure groep \"$1\" is óngeljig.",
+       "userrights-expiry-in-past": "De verlouptied veure groep \"$1\" is al gewaes.",
+       "userrights-cannot-shorten-expiry": "Doe kans de verlouptied van 't groepslidmaotsjap van groep \"$1\" neet verkorte. Allein gebroekers mit 't rech óm dees groep tou te veuge of eweg te sjaffe kónne de verlouptied verkorte.",
        "group": "Gróp:",
        "group-user": "Gebroekers",
        "group-autoconfirmed": "Geregistreerde gebroekers",
        "grouppage-bot": "{{ns:project}}:Bots",
        "grouppage-sysop": "{{ns:project}}:Beheerders",
        "grouppage-bureaucrat": "{{ns:project}}:Bureaucrate",
-       "grouppage-suppress": "{{ns:project}}:Euverzich",
+       "grouppage-suppress": "{{ns:project}}:Toezich",
        "right-read": "Pagina's bekieke",
        "right-edit": "Pagina's bewerke",
        "right-createpage": "Pagina's aanmake",
        "right-editinterface": "De gebroekersinterface bewerke",
        "right-editusercss": "De CSS-bestande van angere gebroekers bewerke",
        "right-edituserjs": "De JS-bestande van angere gebroekers bewerke",
+       "right-editmyoptions": "Bewirk dien eige veurkäöre",
        "right-rollback": "Snel de letste bewerking(e) van 'n gebroeker van 'n pagina terugdraaie",
        "right-markbotedits": "Teruggedraaide bewerkinge markere es botbewerkinge",
        "right-noratelimit": "Heet gein ti'jdsafhankelijke beperkinge",
        "right-siteadmin": "De database blokkere en weer vriegaeve",
        "right-override-export-depth": "Export paazjes midin geslinkdje paazjes mit 'n deepdje ven 5",
        "right-sendemail": "Versjik e-mail aan anger gebroekers",
+       "grant-generic": "Rechtegroep \"$1\"",
+       "grant-group-page-interaction": "Wirk mit pagina's",
+       "grant-group-file-interaction": "Wirk mit media",
+       "grant-group-watchlist-interaction": "Wirk mit diene volglies",
+       "grant-group-email": "Sjik e-mail",
+       "grant-group-high-volume": "Veur aktiviteite mit hoeag voluum oet",
+       "grant-group-customization": "Aanpassinge en veurkäöre",
+       "grant-group-administration": "Veur behieërdershanjelinge oet",
+       "grant-group-private-information": "Betrach privaatgegaeves euver dich",
+       "grant-group-other": "Divers hanjelinge",
+       "grant-blockusers": "Blokkeer en deblokkeer gebroekers",
+       "grant-createaccount": "Maak gebroekers aan",
+       "grant-createeditmovepage": "Maak, bewirk en verplaats pagina's",
+       "grant-delete": "Wösj pagina's, bewirkinge en logbookregele",
+       "grant-basic": "Basisrechte",
        "newuserlogpage": "Logbook nuuj gebroekers",
        "newuserlogpagetext": "Hiej ónger saton de nuuj ingesjreve gebroekers.",
        "rightslog": "Gebroekersrechtelogbook",
        "recentchanges-label-plusminus": "Dees paginagruuedje is verangerdj mit dit aantaal aan bytes",
        "recentchanges-legend-heading": "<strong>Legenda:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (zuuch ouch [[Special:NewPages|de nuuj pagina's]])",
+       "rcfilters-savedqueries-rename": "Herneum",
+       "rcfilters-savedqueries-setdefault": "Stèl in es standerd",
+       "rcfilters-savedqueries-unsetdefault": "Sjaf eweg es standerd",
+       "rcfilters-savedqueries-remove": "Sjaf eweg",
+       "rcfilters-savedqueries-new-name-label": "Naam",
+       "rcfilters-savedqueries-new-name-placeholder": "Besjrief 't doel van de filtjer",
+       "rcfilters-savedqueries-apply-label": "Maak filtjer aan",
+       "rcfilters-savedqueries-cancel-label": "Braek aaf",
+       "rcfilters-savedqueries-add-new-title": "Slaon hujige filtjerinstèllinge op",
+       "rcfilters-restore-default-filters": "Zèt standerd filtjers trögk",
+       "rcfilters-clear-all-filters": "Sjaf alle filtjers eweg",
+       "rcfilters-search-placeholder": "Filtjer recènte verangeringe (blajer of begin mit intikke)",
+       "rcfilters-invalid-filter": "Óngeljige filtjer",
+       "rcfilters-empty-filter": "Gein aktief filtjers. Alle biedrage waere waergaeve.",
+       "rcfilters-filterlist-title": "Filtjers",
+       "rcfilters-filterlist-whatsthis": "Wie wirk dit?",
+       "rcfilters-filterlist-feedbacklink": "Gaef trögksjaking op de nuuj (bèta)filtjers",
+       "rcfilters-highlightbutton-title": "Markeer rizzeltaote",
+       "rcfilters-highlightmenu-title": "Kees 'n kluuer",
+       "rcfilters-highlightmenu-help": "Kees 'n kluuer veur dees eigesjappe oet te lichte",
+       "rcfilters-filterlist-noresults": "Gein filtjers gevónje",
+       "rcfilters-filter-bots-label": "Bot",
+       "rcfilters-tag-prefix-namespace-inverted": "<strong>:neet</strong> $1",
        "rcnotefrom": "{{PLURAL:$5|Verangering|Verangeringe}} saer <strong>$3 óm $4</strong> (maximaal <strong>$1</strong> {{PLURAL:$1|verangering|verangeringe}}).",
        "rclistfrom": "Tuin de verangeringe vanaaf $3 $2",
        "rcshowhideminor": "$1 klein bewèrkinge",
        "booksources-text": "Hiej onger stuit 'n lies met koppelinge nao anger websites die nuuje of gebroekde beuk verkoupe, en die wellich meer informatie euver 't book detse zeuks höbbe:",
        "booksources-invalid-isbn": "t Ingegaeve ISBN liek neet geldig te zeen.\nControleer of se wellich n fout höbs gemaak bie de inveur.",
        "specialloguserlabel": "Oetveurder:",
-       "speciallogtitlelabel": "Doel (pagina of gebroeker):",
+       "speciallogtitlelabel": "Doel (paginanaam of {{ns:user}}:gebroekersnaam veur gebroeker):",
        "log": "Logbeuk",
        "all-logs-page": "Alle aopenbaar logbeuk",
        "alllogstext": "Dit is 't gecombineerd logbook ven {{SITENAME}}. De kins ouch 'n bepaald logbook keze, of filtere op gebroekersnaam of  pazjena, beide huidlettergeveulig.",
        "unwatchthispage": "Neet mië volge",
        "notanarticle": "Is gein artikel",
        "notvisiblerev": "Bewèrking is verwiederd",
-       "watchlist-details": "D'r {{PLURAL:$1|sjteit ein pagina|sjtaon $1 pagina's}} op dien volglies mit oetzunjering van de euverlèkpagina's.",
+       "watchlist-details": "D'r {{PLURAL:$1|sjteit ein pagina|sjtaon $1 pagina's}} op dien volglies mit de euverlèkpagina's neet mitgetèldj.",
        "wlheader-enotif": "Doe wörs per e-mail gewaarsjuwd",
        "wlheader-showupdated": "Pazjena's die verangerd zeen saers doe ze veur 't lètste bekeeks sjtaon '''vet'''",
-       "wlnote": "Hieónger {{PLURAL:$1|steit de lètste verangering|staon de lètste $1 verangeringe}} van {{PLURAL:$2|'t lètste oer|de lètste <b>$2</b> oer}} óp $3 óm $4.",
-       "wlshowlast": "Tuin lètste $1 ore $2 daag",
+       "wlnote": "Hieónger {{PLURAL:$1|steit de lètste verangering|staon de lètste <strong>$1</strong> verangeringe}} van {{PLURAL:$2|'t lètste oer|de lètste <strong>$2</strong> oer}} óp $3 óm $4.",
+       "wlshowlast": "Tuin lètste $1 oere $2 daag",
        "watchlist-options": "Opties veur volglies",
        "watching": "Bezig mit plaatse op de volglies...",
        "unwatching": "Oet de volglies aan 't haole...",
        "whatlinkshere-hideredirs": "$1 redireks",
        "whatlinkshere-hidetrans": "$1 transclusies",
        "whatlinkshere-hidelinks": "$1 links",
-       "whatlinkshere-hideimages": "$1 bestandjslinker",
+       "whatlinkshere-hideimages": "$1 bestandjslinke",
        "whatlinkshere-filters": "Filters",
        "autoblockid": "Autoblock #$1",
        "block": "Blok gebroeker",
        "tooltip-feed-rss": "RSS feed veur dees pagina",
        "tooltip-feed-atom": "Atom feed veur dees pagina",
        "tooltip-t-contributions": "Lies mit biedrages van {{GENDER:$1|deze gebroeker}}",
-       "tooltip-t-emailuser": "Sjtuur inne mail noa dizze gebroeker",
+       "tooltip-t-emailuser": "Sjtuur inne mail noa dizze {{GENDER:$1|gebroeker}}",
        "tooltip-t-upload": "Upload besjtande",
        "tooltip-t-specialpages": "Lies van alle speciaal pagina's",
        "tooltip-t-print": "Printvruntelike versie van deze pagina",
        "pageinfo-header-basic": "Basisgegaeves",
        "pageinfo-header-edits": "Bewirkingsgesjiechte",
        "pageinfo-header-restrictions": "Paginabesjirming",
+       "pageinfo-header-properties": "Pagina-eigesjappe",
        "pageinfo-display-title": "Toean paginanaam",
        "pageinfo-default-sort": "Standerd sortering",
        "pageinfo-length": "Paginalingdje (in bytes)",
        "pageinfo-language": "Spraok worin dees pagina steit",
        "pageinfo-content-model": "Paginainhawdmodel",
        "pageinfo-robot-policy": "Robot-indexering",
+       "pageinfo-robot-index": "Tougestange",
+       "pageinfo-robot-noindex": "Neet toegestange",
        "pageinfo-watchers": "Aantal paginavolgers",
+       "pageinfo-few-watchers": "Minder es  {{PLURAL:$1|eine volger|$1 volgers}}",
        "pageinfo-redirects-name": "Aantaal redireks nao dees pagina",
+       "pageinfo-subpages-name": "Subpagina's van dees pagina",
+       "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|redirek|redireks}}; $3 {{PLURAL:$3|neet-redirek|neet-redireks}})",
        "pageinfo-firstuser": "Aanmaker",
        "pageinfo-firsttime": "Datum vannen aanmaak",
        "pageinfo-lastuser": "Litste bewirker",
+       "pageinfo-lasttime": "Litste bewirking",
        "pageinfo-edits": "Aantal bewèrkinge",
        "pageinfo-authors": "Aantal versjillende sjrievers",
+       "pageinfo-recent-edits": "Recènte bewirkinge (binne de aafgeloupe $1)",
+       "pageinfo-recent-authors": "Recènte sjrievers",
        "pageinfo-magic-words": "{{PLURAL:$1|Magisch waord|Magische wäörd}} ($1)",
+       "pageinfo-hidden-categories": "Verstaoke {{PLURAL:$1|categorie|categorieje}} ($1)",
+       "pageinfo-templates": "{{PLURAL:$1|Gebroek sjebloon|Gebroekde sjeblone}} ($1)",
        "pageinfo-toolboxlink": "Pazjena-infermasie",
+       "pageinfo-contentpage": "Getèldj es pagina mit inhawd",
        "pageinfo-contentpage-yes": "Jao",
        "markaspatrolleddiff": "Markeer es gecontroleerd",
        "markaspatrolledtext": "Markeer deze pagina es gecontroleerd",
        "watchlistedit-raw-done": "Dien volglies is biegewirk.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 pazjena is|$1 pazjena's zeen}} toegevoog:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 pazjena is|$1 pazjena's zeen}} eweggesjaf:",
+       "watchlisttools-clear": "Maak de volglies laeg",
        "watchlisttools-view": "Volglies bekieke",
        "watchlisttools-edit": "Volglies bekieke en bewirke",
        "watchlisttools-raw": "Roew volglies bewirke",
        "version-entrypoints": "Ingang-URLs",
        "version-entrypoints-header-entrypoint": "Ingank",
        "version-entrypoints-header-url": "URL",
+       "redirect": "Redirek op bestandj, gebroeker, pagina, versie of log-ID",
+       "redirect-summary": "Dees speciaal pagina verwies door nao e bestandj (es 'ne bestandjsnaam weurt opgegaeve), 'n pagina (es e paginanómmer of versienómmer weurt opgegaeve), 'ne gebroekerspagina (es e gebroekersnómmer weurt opgegaeve) of 'ne logbookregel (es 'n logboekregel-ID weurt opgegaeve). Gebroek: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] of [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Gank",
        "redirect-lookup": "Zeuk op:",
        "redirect-value": "Waerd",
        "fileduplicatesearch-result-n": "'t Bestandj \"$1\" haet {{PLURAL:$2|1 identieke döbbelversie|$2 identiek döbbelversies}}.",
        "fileduplicatesearch-noresults": "d'r Is gei bestandj mitte naam \"$1\" gevónje.",
        "specialpages": "Speciaal pagina's",
-       "specialpages-note": "* Normaal speciaal pagina's\n* <strong class=\"mw-specialpagerestricted\">Beperk toegankelike speciaal pagina's</strong>\n* <span class=\"mw-specialpagecached\">Speciaal pagina's mit allein gegaeves oete cache (meugelik verajerd)</span>",
        "specialpages-group-maintenance": "Óngerhajingsrapporter",
        "specialpages-group-other": "Euverige speciaal pazjena's",
        "specialpages-group-login": "Aanmelje / registrere",
        "tags-description-header": "Volledige beschrieving van betekenis",
        "tags-hitcount-header": "Gelabelde bewerkinge",
        "tags-active-yes": "Jao",
+       "tags-active-no": "Nae",
        "tags-edit": "bewerking",
        "tags-hitcount": "$1 {{PLURAL:$1|wieziging|wieziginge}}",
        "comparepages": "Vergeliek pazjena's",
        "htmlform-reset": "Maak verangeringe óngedaon",
        "htmlform-selectorother-other": "Anges",
        "logentry-delete-delete": "$1 {{GENDER:$1|haet}} de pagina $3 gewösj",
-       "logentry-delete-restore": "$1 haet de pagina $3 trögkgezat",
+       "logentry-delete-restore": "$1 {{GENDER:$2|haet}} de pagina $3 ($4) trögkgezatte",
        "logentry-delete-event": "$1 haet de zichbaarheid van {{PLURAL:$5|'ne logbookregel|$5 logbookregels}} van $3 gewiezig: $4",
-       "logentry-delete-revision": "$1 haet de zichbaarheid van {{PLURAL:$5|'n versie|$5 versies}} van $3 gewiezig: $4",
+       "logentry-delete-revision": "$1 {{GENDER:$2|haet}} de zichbaarheid van {{PLURAL:$5|'n versie|$5 versies}} van de pagina $3 verangerdj: $4",
        "logentry-delete-event-legacy": "$1 haet de zichbaarheid van logbookregels van $3 gewiezig",
        "logentry-delete-revision-legacy": "$1 haet de zichbaarheid van versies van de pagina $3 gewiezig.",
        "logentry-suppress-delete": "$1 haet de pagina $3 ongerdrök",
        "logentry-move-move_redir": "$1 {{GENDER:$2|verplaatsde}} pagina $3 nao $4 euver 'ne redirek",
        "logentry-move-move_redir-noredirect": "$1 verplaatsde pagina $3 nao $4 euver 'ne redirek zonger 'n doorverwiezing achter te laote",
        "logentry-patrol-patrol": "$1 haet versie $4 van pagina $3 es gecontroleerd gemarkeerd",
-       "logentry-patrol-patrol-auto": "$1 haet versie $4 van pagina $3 autematis es gecontroleerd gemarkeerd",
+       "logentry-patrol-patrol-auto": "$1 {{GENDER:$2|haet}} versie $4 van pagina $3 autematis gemarkeerd es gecontroleerd",
        "logentry-newusers-newusers": "$1 haet 'ne gebroeker aangemaak",
        "logentry-newusers-create": "Gebroeker $1 {{GENDER:$2|is}} aangemaak gewaore",
        "logentry-newusers-create2": "$1 haet 'ne gebroeker $3 aangemaak",
-       "logentry-newusers-autocreate": "De gebroeker $1 is autematis aangemaak",
+       "logentry-newusers-autocreate": "De gebroeker $1 {{GENDER:$2|is}} autematis aangemaak gewaore",
        "logentry-upload-upload": "$1 {{GENDER:$2|loadje}} $3 up",
+       "logentry-upload-overwrite": "$1 {{GENDER:$2|haet}} 'n nuuj versie van $3 hoaggelaje",
        "rightsnone": "(gein)",
        "feedback-adding": "Feedback weurt aan pagina toegevoeg...",
        "feedback-bugcheck": "Good! Kónterleer ef of 't neet al ein vanne [$1 bekèndje bugs] is.",
        "special-characters-group-lao": "Lao",
        "special-characters-group-khmer": "Cambodzjaans",
        "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-DD",
-       "mw-widgets-dateinput-placeholder-month": "JJJJ-MM"
+       "mw-widgets-dateinput-placeholder-month": "JJJJ-MM",
+       "randomrootpage": "Willekäörige wrootpagina"
 }
index 128694d..5a81bab 100644 (file)
        "rcfilters-legend-heading": "<strong>Список на кратенки:</strong>",
        "rcfilters-activefilters": "Активни филтри",
        "rcfilters-advancedfilters": "Напредни филтри",
+       "rcfilters-limit-title": "Промени за приказ",
+       "rcfilters-limit-shownum": "Прикажи ги последните $1 промени",
+       "rcfilters-days-title": "Последниве денови",
+       "rcfilters-hours-title": "Последниве часови",
+       "rcfilters-days-show-days": "{{PLURAL:$1|еден ден|$1 дена}}",
+       "rcfilters-days-show-hours": "{{PLURAL:$1|еден час|$1 часа}}",
        "rcfilters-quickfilters": "Зачувани филтри",
        "rcfilters-quickfilters-placeholder-title": "Засега нема зачувани врски",
        "rcfilters-quickfilters-placeholder-description": "За да ги зачувате вашите филтерски псотавки за да ги употребите другпат, стиснете на иконката за бележник во подрачјето „Активен филтер“ подолу.",
        "rcfilters-invalid-filter": "Неважечки филтер",
        "rcfilters-empty-filter": "Нема активни филтри. Прикажани се сите придонеси.",
        "rcfilters-filterlist-title": "Филтри",
-       "rcfilters-filterlist-whatsthis": "ШÑ\82о Ðµ ова?",
+       "rcfilters-filterlist-whatsthis": "Ð\9aако Ñ\80абоÑ\82и ова?",
        "rcfilters-filterlist-feedbacklink": "Дајте мислење за новите (бета) филтри",
        "rcfilters-highlightbutton-title": "Истакнување на исход",
        "rcfilters-highlightmenu-title": "Изберете боја",
        "rcfilters-filter-editsbyself-description": "Ваши сопствени придонеси.",
        "rcfilters-filter-editsbyother-label": "Туѓи промени",
        "rcfilters-filter-editsbyother-description": "Сите промени направени од други уредници",
-       "rcfilters-filtergroup-userExpLevel": "Корисничка искусност (само за регистрирани)",
+       "rcfilters-filtergroup-userExpLevel": "Корисничка регистрација и искусност",
        "rcfilters-filter-user-experience-level-registered-label": "Регистрирани",
        "rcfilters-filter-user-experience-level-registered-description": "Најавени уредници.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Нерегистрирани",
        "rcfilters-filter-user-experience-level-unregistered-description": "Уредници кои не се најавени.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Новодојденци",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Ð\9fомалку од 10 уредувања и 4 дена активност.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "РегиÑ\81Ñ\82Ñ\80иÑ\80ани Ñ\83Ñ\80едниÑ\86и Ñ\81о Ð¿омалку од 10 уредувања и 4 дена активност.",
        "rcfilters-filter-user-experience-level-learner-label": "Ученици",
-       "rcfilters-filter-user-experience-level-learner-description": "Ð\9fоиÑ\81кÑ\83Ñ\81ни Ð¾Ð´ â\80\9eноводоÑ\98денÑ\86иÑ\82еâ\80\9c, Ð½Ð¾ Ð¿Ð¾Ð¼Ð°Ð»ÐºÑ\83 Ð¾Ð´ â\80\9eиÑ\81кÑ\83Ñ\81ниÑ\82е корисници“.",
+       "rcfilters-filter-user-experience-level-learner-description": "РегиÑ\81Ñ\82Ñ\80иÑ\80ани Ñ\83Ñ\80едниÑ\86и Ñ\87ие Ð¸Ñ\81кÑ\83Ñ\81Ñ\82во Ðµ Ð½ÐµÐºÐ°Ð´Ðµ Ð¼ÐµÑ\88Ñ\83 â\80\9eноводоÑ\98денÑ\86иâ\80\9c Ð¸ â\80\9eиÑ\81кÑ\83Ñ\81ни корисници“.",
        "rcfilters-filter-user-experience-level-experienced-label": "Искусни корисници",
-       "rcfilters-filter-user-experience-level-experienced-description": "Ð\9fовеÑ\9cе Ð¾Ð´ 30 Ð´ÐµÐ½Ð° Ð°ÐºÑ\82ивноÑ\81Ñ\82 Ð¸ 500 Ñ\83Ñ\80едÑ\83ваÑ\9aа.",
+       "rcfilters-filter-user-experience-level-experienced-description": "РегиÑ\81Ñ\82Ñ\80иÑ\80ани Ñ\83Ñ\80едниÑ\86и Ñ\81о Ð¿Ð¾Ð²ÐµÑ\9cе Ð¾Ð´ 500 Ñ\83Ñ\80едÑ\83ваÑ\9aа Ð¸ 30 Ð´ÐµÐ½Ð° Ð°ÐºÑ\82ивноÑ\81Ñ\82.",
        "rcfilters-filtergroup-automated": "Автоматизирани придонеси",
        "rcfilters-filter-bots-label": "Ботовски",
        "rcfilters-filter-bots-description": "Уредувања со автоматизирани алатки.",
        "rcfilters-hideminor-conflicts-typeofchange-global": "Филтерот „Ситни уредувања“ е спротиставен на еден или повеќе од филтрите за видови измена, бидејќи извеси видови не можат да се означат како ситни. Спротиставените филтри се означени во делот Неактивни филтри погоре.",
        "rcfilters-hideminor-conflicts-typeofchange": "Извезни видови промени не можат да се означат како „ситни“, па затоа овој филтер е во спротиставеност со следниве филтри за видови промени: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Овој филтер за видови промени е во спротиставеност со филтерот „Ситни уредувања“. Извсни видови промени не можат да се означат како „ситни“.",
-       "rcfilters-filtergroup-lastRevision": "Ð\9fоÑ\81ледна Ð¿Ñ\80еÑ\80абоÑ\82ка",
+       "rcfilters-filtergroup-lastRevision": "Ð\9fоÑ\81ледни Ð¿Ñ\80еÑ\80абоÑ\82ки",
        "rcfilters-filter-lastrevision-label": "Последна преработка",
-       "rcfilters-filter-lastrevision-description": "Ð\9dаÑ\98нови Ð¿Ñ\80еÑ\80абоÑ\82ки Ð½Ð° страница.",
-       "rcfilters-filter-previousrevision-label": "Ð\9fÑ\80еÑ\82Ñ\85одни Ð¿Ñ\80еÑ\80абоÑ\82ки",
-       "rcfilters-filter-previousrevision-description": "Сите промени кои не се најнови преработки на страницата.",
+       "rcfilters-filter-lastrevision-description": "Само Ð½Ð°Ñ\98нови Ð¿Ñ\80еÑ\80абоÑ\82ки Ð²Ð¾ страница.",
+       "rcfilters-filter-previousrevision-label": "Ð\9dе Ð¿Ð¾Ñ\81леднаÑ\82а Ð¿Ñ\80еÑ\80абоÑ\82ка",
+       "rcfilters-filter-previousrevision-description": "Сите промени кои не се „последна преработка“.",
        "rcfilters-filter-excluded": "Исклучени",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:не</strong> $1",
+       "rcfilters-exclude-button-off": "Изземи избрано",
+       "rcfilters-exclude-button-on": "Изземи избрано",
        "rcfilters-view-tags": "Означени уредувања",
        "rcfilters-view-namespaces-tooltip": "Филтрирај исход по именски постор",
        "rcfilters-view-tags-tooltip": "Филтрирај исход по уредувачки ознаки",
        "delete-warning-toobig": "Оваа страница има долга историја на уредување, преку $1 {{PLURAL:$1|преработка|преработки}}.\nБришењето може да предизвика проблеми при работењето на базата на податоци на {{SITENAME}};\nпродолжете доколку сте сигруни дека треба тоа да го сторите.",
        "deleteprotected": "Не можете да ја избришете страницава бидејќи е заштитена.",
        "deleting-backlinks-warning": "<strong>Предупредување:</strong>  До страницата што сакате да ја избришете водат [[Special:WhatLinksHere/{{FULLPAGENAME}}|други страници]] или пак се превметнуваат во неа.",
+       "deleting-subpages-warning": "<strong>Предупредување:</strong> Страницата што сакате да ја избришете има [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|потстраница|$1 потстраници|51=преку 50 потстраници}}]].",
        "rollback": "Отповикај промени",
        "rollbacklink": "отповикај",
        "rollbacklinkcount": "отповикај $1 {{PLURAL:$1|уредување|уредувања}}",
        "fileduplicatesearch-noresults": "Не пронајдов податотека со име „$1“.",
        "specialpages": "Службени страници",
        "specialpages-note-top": "Легенда",
-       "specialpages-note": "* Нормални службени страници.\n* <span class=\"mw-specialpagerestricted\">Ограничени службени страници.</span>",
+       "specialpages-note-restricted": "* Нормални службени страници.\n* <span class=\"mw-specialpagerestricted\">Ограничени службени страници.</span>",
        "specialpages-group-maintenance": "Извештаи за одржување",
        "specialpages-group-other": "Други службени страници",
        "specialpages-group-login": "Најава / направи сметка",
index a3b47fb..f98e7f6 100644 (file)
        "minoredit": "Che sī sió siu-kái",
        "watchthis": "Kàm-sī chit ia̍h",
        "savearticle": "Pó-chûn chit ia̍h",
+       "publishpage": "Hoat-pò͘ bûn-chiuⁿ",
+       "publishchanges": "Hoat-pò͘ siu-kái",
        "preview": "Seng khoàⁿ-māi",
        "showpreview": "Seng khoàⁿ-māi",
        "showdiff": "Khòaⁿ kái-piàn ê pō·-hūn",
        "permissionserrorstext-withaction": "Lí bô ún-chún chò $2, in-ūi ē-kha\n{{PLURAL:$1|iân-kò͘|iân-kò͘}}:",
        "recreate-moveddeleted-warn": "'''Sè-jī: Lí taⁿ chún-pī beh khui ê ia̍h, chêng bat hō͘ lâng thâi tiāu koè.''' Lí tio̍h chim-chiok soà-chiap pian-chi̍p chit ia̍h ê pit-iàu-sèng. Chia ū chit ia̍h ê san-tû kì-lo̍k (deletion log) hō͘ lí chham-khó:",
        "edit-conflict": "Siu-kái sio-chhiong",
+       "postedit-confirmation-saved": "Lí ê siu-kái í-keng pó-chûn.",
        "defaultmessagetext": "Siat piān ê bûn-jī",
        "content-model-wikitext": "wikitext",
        "content-model-text": "sûn bûn-pún",
index 116b216..911fc80 100644 (file)
@@ -41,7 +41,7 @@
        "tog-enotifminoredits": "Famme na masciata mail pure quanno se fanno cagnamiente piccerille 'e paggene e files",
        "tog-enotifrevealaddr": "Fa' vedé 'o ndirizzo mail ncopp'e mmasciate 'e notifica",
        "tog-shownumberswatching": "Fa' vedé 'o nummero d'utente che teneno 'a paggena cuntrullata",
-       "tog-oldsig": "Firma 'e mmo:",
+       "tog-oldsig": "'A firma vosta (mo' mo'):",
        "tog-fancysig": "Piglia 'a firma comme fosse nu wikitesto (senza fà link automatico)",
        "tog-uselivepreview": "Abbìa 'o \"Live preview\"",
        "tog-forceeditsummary": "Chiere a mme quanno se sta azzeccanno nu campo oggetto abbacante",
@@ -58,7 +58,7 @@
        "tog-showhiddencats": "Fa' vedé 'e categurie annascunnute",
        "tog-norollbackdiff": "Nun fà vedé 'o cunfronto nfra verziune quanno se fà nu rollback ('o torna arreto)",
        "tog-useeditwarning": "Famme sapé quanno lasso na paggena 'e mudifeca senza sarvà 'e cagnamiente",
-       "tog-prefershttps": "Usa sempe na connessione sicura quanno s'accummincia sessione",
+       "tog-prefershttps": "Usa sempe na connessione sicura pe' tramente ca s'accummincia sessione",
        "underline-always": "Sèmpe",
        "underline-never": "Màje",
        "underline-default": "Tiene sempe le mpostazzione d' 'o navigatóre",
        "newwindow": "(s'arape n'ata fenèsta)",
        "cancel": "Scancèlla",
        "moredotdotdot": "Cchiù...",
-       "morenotlisted": "Chisto elenco nun è cumpreto.",
+       "morenotlisted": "Chisto elenco putesse nun essere cumpleto sano sano.",
        "mypage": "Paggena",
        "mytalk": "'E chiàcchieriate mmie",
        "anontalk": "Chiacchierate",
        "navigation": "Navigazzione",
        "and": "&#32;e",
-       "qbfind": "Truòva",
-       "qbbrowse": "Sfoglia",
-       "qbedit": "Càgna",
-       "qbpageoptions": "Chesta paggena",
-       "qbmyoptions": "'E ppaggene mie",
        "faq": "FAQ",
-       "faqpage": "Project:Domanne frequente",
        "actions": "Azione",
        "namespaces": "Namespace",
        "variants": "Variante",
        "searcharticle": "Vàje",
        "history": "Verziune 'e primma",
        "history_short": "Cronologgia",
+       "history_small": "cronologgia",
        "updatedmarker": "cagnamiénte 'e ll'urdema visita d' 'a mia",
        "printableversion": "Verzione pe' stampa",
        "permalink": "Jonta permanente",
        "edit-local": "Càgna descrizione lucale",
        "create": "Crèa",
        "create-local": "Azzecca descrizione lucale",
-       "editthispage": "Càgna chesta paggena",
-       "create-this-page": "Crèa sta paggena",
        "delete": "Scancèlla",
-       "deletethispage": "Scancèlla chésta paggena",
-       "undeletethispage": "Arrepiglia chista paggena",
        "undelete_short": "Arremedia {{PLURAL:$1|na verziona|$1 vverziune}}",
        "viewdeleted_short": "Vide {{PLURAL:$1|nu cagnamiénto scancellato|$1 cagnamiénte scancellate}}",
        "protect": "Prutegge",
        "protect_change": "càgna",
-       "protectthispage": "Ferma chesta paggena",
        "unprotect": "Càgna prutezzione",
-       "unprotectthispage": "Càgna prutezzione 'e chesta paggena",
        "newpage": "Paggena nòva",
-       "talkpage": "Paggena 'e chiàcchiera",
        "talkpagelinktext": "Chiàcchiera",
        "specialpage": "Paggena speciàle",
        "personaltools": "Strumiente perzonale",
-       "articlepage": "Vere a paggena e contenuto",
        "talk": "Chiàcchiera",
        "views": "Visite",
        "toolbox": "Strumiente",
-       "userpage": "Vere a paggena utente",
-       "projectpage": "Vere a paggena 'e servizio",
+       "tool-link-userrights": "Càgna gruppe {{GENDER:$1|utente}}",
+       "tool-link-userrights-readonly": "Vire gruppe {{GENDER:$1|utente}}",
+       "tool-link-emailuser": "Manna na masciata email a st'{{GENDER:$1|utente}}",
        "imagepage": "Vere a paggena d' 'o file",
        "mediawikipage": "Vere 'a mmasciata",
        "templatepage": "Vere 'o template",
        "redirectedfrom": "(Redirect 'a $1)",
        "redirectpagesub": "Paggena 'e redirect",
        "redirectto": "Reindirizza a:",
-       "lastmodifiedat": "Urdemo cagnamiénto pe' a paggena: $2, $1.",
+       "lastmodifiedat": "Sta paggena fuje, n'urdema vota, cagnàta 'o $1, 'e $2.",
        "viewcount": "Chesta paggena è stata liggiùta {{PLURAL:$1|una vòta|$1 vòte}}.",
        "protectedpage": "Paggena prutetta",
        "jumpto": "Vaje a:",
        "createacct-another-username-ph": "'Nserisce 'o nomme utente",
        "yourpassword": "Password:",
        "userlogin-yourpassword": "Password",
-       "userlogin-yourpassword-ph": "'Nserisce 'a toja password",
+       "userlogin-yourpassword-ph": "Nzertàte 'a password vuosta",
        "createacct-yourpassword-ph": "'Nserisce 'na password",
        "yourpasswordagain": "Ripete 'a password:",
        "createacct-yourpasswordagain": "Cunferma password",
        "eauthentsent": "Na mmasciata 'e conferma t'è stata mannata a l'indirizzo e-mail nzignàto.\nApprimm' 'e te mannà n'atu mail, hè 'a stà 'a ffà 'e struzione dint'a l'e-mail, pe' cunfermà ca 'o cunto fosse d' 'o tujo overo.",
        "throttled-mailpassword": "S'è mannata na mail pe te' riabbià 'a password 'a meno 'e {{PLURAL:$1|n'ora|$1 ore}}.\nPe' ce sparagnà abbuse, 'a funzione 'e riabbiamento d' 'a password se può usa sulamente na vota ogne {{PLURAL:$1|ora|$1 ore}}.",
        "mailerror": "Errore pe' tramente ca se mannava na mmasciata: $1",
-       "acct_creation_throttle_hit": "{{PLURAL:$1|1 registrazzione è già stata effettuata|$1 registrazzione song già state effettuate}} 'e qualcuno cu 'o tujo stisso innerezzo IP dint'ô urdemo juorno: è 'o massimo cunsentito 'n chisto periodo 'e tiempo.\nPerciò, 'e utente ca ausano chisto innerezzo IP nun possono registrarse ppe 'o mumiento.",
+       "acct_creation_throttle_hit": "'E vvisite a sta wiki ausanno l'IP tuoja se so' mise a crià {{PLURAL:$1|1 registrazzione|$1 registrazzione}} int'a ll'urdeme juorne ($2), chesto fosse 'o massimo premmesso pe' stu periodo 'e tiempo.\nPerciò, 'e utente ca ausano chisto innerezzo IP nun se ponno riggistrà ancora mò mò.",
        "emailauthenticated": "'O ndirizzo email è stato cunfermato 'o $2 a 'e $3.",
        "emailnotauthenticated": "'O ndirizzo 'e posta elettronica nun è stat'ancora cunfermato.\nNun se mannarranno mmasciate e-mail p' ' funzione ccà abbascio.",
        "noemailprefs": "Avite 'a specificà nu ndirizzo e-mail pe ll'attivà sti funzione.",
        "botpasswords-label-delete": "Scancèlla",
        "botpasswords-label-resetpassword": "Riabbìa 'a password",
        "botpasswords-label-grants": "Assegnaziune apprecabbele:",
-       "botpasswords-help-grants": "Ogne assegnazione dà acciesso a 'e deritte utente elencate ca n'utenza avesse già. Vedite 'a [[Special:ListGrants|tabbella 'e ll'assegnaziune]] pe' ne mòvere cchiù nfurmaziune.",
+       "botpasswords-help-grants": "L'assegnazione premmettessero ausà deritte utente elencate ca n'utenza avesse già. Premmettenno st'assegnazione ccà nun è ca ve facesse trasì int'a sti deritte, pecché 'o cunto vuosto nun 'e tenisse pe' n'atu mezzo. Vedite 'a [[Special:ListGrants|tabbella 'e ll'assegnaziune]] pe' ne mòvere cchiù nfurmaziune.",
        "botpasswords-label-grants-column": "Assegnaziune date",
        "botpasswords-bad-appid": "'O nomme bot \"$1\" nun è bbuono.",
        "botpasswords-insert-failed": "Nun se pò azzeccà 'o nomme bot \"$1\". Fosse stato già azzeccato?",
        "botpasswords-updated-body": "'A password bot \"$1\" 'a ll'utente \"$2\" fuje agghiurnata.",
        "botpasswords-deleted-title": "Password bot scancellata",
        "botpasswords-deleted-body": "'A password bot \"$1\" 'a ll'utente \"$2\" è stata scancellata.",
-       "botpasswords-newpassword": "'A password nòva pe' puté trasì cu <strong>$1</strong> è <strong>$2</strong>. <em>Pe' piacere signatevello chesto pe' ve ffà conzurtaziune future.</em>",
+       "botpasswords-newpassword": "'A password nòva pe' puté trasì cu <strong>$1</strong> è <strong>$2</strong>. <em>Pe' piacere signatevello chesto pe' ve ffà conzurtaziune future.</em> <br>('E bott viecchie addò servisse nu nomme utente comm'a chell' 'e l'utente, putite ancora ausà <strong>$3</strong> comm' 'o nomm' 'utente e <strong>$4</strong> comm' 'a password.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider nun è disponibbele.",
        "botpasswords-restriction-failed": "'E restriziune 'e password bot nun ve permettessero st'acciesso.",
        "botpasswords-invalid-name": "'O nomme utente nnecato nun cuntenesse nu spartetóre 'e bot password (\"$1\").",
        "blockedtitle": "Utente bloccato.",
        "blockedtext": "<strong>'O nomme utente o ll'IP vuosto è stato bloccato.</strong>\n\n'O blocco è stato mpustato 'a $1. 'O mutivo d' 'o blocco è chesto: ''$2''\n\n* Abbiàta d' 'o blocco: $8\n* Ammaturità d' 'o blocco: $6\n* Tiempo 'e blocco: $7\n\nPutite cuntattà $1 o n'atu [[{{MediaWiki:Grouppage-sysop}}|ammenistratore]] pe' discutere 'o blocco.\n\nVedite c' 'a funzione 'Scrivete a ll'utente' nun è attiva si nun s'è riggistrato 'o ndirizzo e-mail buono dint' 'e [[Special:Preferences|preferenze]] o pùre si ll'uso 'e tale funzione è stato bloccato.\n\n'O ndirizzo IP attuale è $3, 'o nummero ID d' 'o blocco è #$5.\nPe' piacere avite 'e specificà tutte sti dettaglie ccà ncoppa quanno facite cocche dumanna.",
        "autoblockedtext": "Ll'IP vuosto è stato bloccato pecché 'o steva piglianno n'atu utente, ch'è stato bloccato pe' $1.\n\n'O mutivo d' 'o blocco è chesto:\n\n:''$2''\n\n* Abbiàta d' 'o blocco: $8\n* Ammaturità d' 'o blocco: $6\n* Tiempo 'e blocco: $7\n\nPutite cuntattà $1 o n'atu [[{{MediaWiki:Grouppage-sysop}}|ammenistratore]] pe' discutere 'o blocco.\n\nVedite c' 'a funzione 'Scrivete a ll'utente' nun è attiva si nun s'è riggistrato 'o ndirizzo e-mail buono dint' 'e [[Special:Preferences|preferenze]] o pùre si ll'uso 'e tale funzione è stato bloccato.\n\n'O ndirizzo IP attuale è $3, 'o nummero ID d' 'o blocco è #$5.\nPe' piacere avite 'e specificà tutte sti dettaglie ccà ncoppa quanno facite cocche dumanna.",
+       "systemblockedtext": "'O nomme utente d' 'o vuosto o ll'IP address fosse stata automaticamente bluccata 'a MediaWiki.\n'O mutivo fosse chesto:\n\n:<em>$2</em>\n\n* Inizio d' 'o blocco: $8\n* Ammatura 'o blocco: $6\n* Intervall' 'e blocco: $7\n\n'O indirizzo IP 'e mò fosse $3.\nPe' piacere, facite specifice tuttuquante 'e ddettaglie ccà quanno iate a ghienchere na richiesta 'e chiarimiente.",
        "blockednoreason": "nisciuna ragione è stata indicata",
        "whitelistedittext": "Pe' cagnà 'e ppaggene è necessario $1.",
        "confirmedittext": "Pe puté cagnà paggene avite 'a cunfermà l'indirizzo e-mail.\nPe' piacere abbiate e ffà 'a validazione d' 'o ndirizzo e-mail pe' bbìa d' 'e [[Special:Preferences|preferenze d'utente]].",
        "readonlywarning": "<strong>Attenziò</strong>: 'o database è bloccato pe se ffà 'a manutenzione. P' 'o mumento nun se ponno sarvà 'e cagnamiente fatte.\nPe' nun 'e sperdere, copia sti cuntenute dint'a nu file 'e testo e sarvatillo pe' tramente c'aspiette 'o sblocco d' 'o database.\n\nL'ammenistratore 'e sistema ca mpustaje 'o blocco ave scritto sta spiegazione: $1.",
        "protectedpagewarning": "'''Attenziò: sta paggena è stata bloccata 'n modo tale ca sulamente l'utente ch' 'e privilegge d'ammenistratore 'a ponno cagnà.'''\nL'urdemo elemento d' 'o riggistro è scritto ccà abbascio pe' n'avé riferimento:",
        "semiprotectedpagewarning": "'''Nota:''' Sta paggena è stata bloccata 'n modo ca sulamente l'utente riggistrate 'a ponno cagnà.\nL'urdemo elemento d' 'o riggistro è scritto ccà abbascio pe n'avé nfurmazione:",
-       "cascadeprotectedwarning": "'''Attenziò:''' Sta paggena è stata bloccata 'n modo ca sulamente l'utente ch' 'e privilegge d'ammenistratore 'a ponno cagnà. Chesto succiere pecché 'a paggena è appennuta dint'a {{PLURAL:$1|la paggena innecata ccà abbascio, ch'è stata prutetta|'e paggene innecate ccà abbascio, che so' state prutette}} sciglienno 'a prutezione \"ricurziva\":",
+       "cascadeprotectedwarning": "<strong>Attenziò:</strong> Sta paggena è stata bloccata 'n modo ca sulamente l'utente ch' 'e [[Special:ListGroupRights|privilegge specifiche]] 'a ponno cagnà. Chesto succiere pecché 'a paggena è appennuta dint'a {{PLURAL:$1|la paggena innecata ccà abbascio, ch'è stata prutetta|'e paggene innecate ccà abbascio, che so' state prutette}} sciglienno 'a prutezione \"ricurziva\":",
        "titleprotectedwarning": "'''Attenziò: sta paggena è stata bloccata 'n modo ca fossero necessarie [[Special:ListGroupRights|deritte specifici]] p' 'a crià.'''\nL'urdemo elemento d' 'o riggistro è riportato ccà abbascio pe nfurmazione:",
        "templatesused": "{{PLURAL:$1|Template|Templates}} ausate 'a chesta paggena:",
        "templatesusedpreview": "{{PLURAL:$1|Template|Templates}} ausate dint'a st'anteprimma:",
        "invalid-content-data": "Date cuntenute nun buone",
        "content-not-allowed-here": "'O cuntenuto \"$1\" nun è permesso dint'a paggena [[$2]]",
        "editwarning-warning": "Ascenno 'e sta paggena putisse ffà sperdere 'e cagnamiente fatte.\nSi sì trasuto, allora può stutà st'avviso dint'a sezziona \"{{int:prefs-editing}}\" d' 'e preferenze.",
+       "editpage-invalidcontentmodel-title": "Mudell' 'e cuntenute nun suppurtato",
+       "editpage-invalidcontentmodel-text": "'O mudell' 'e cuntenute \"$1\" nun è suppurtato.",
        "editpage-notsupportedcontentformat-title": "Furmato d' 'o cuntenuto nun suppurtato",
        "editpage-notsupportedcontentformat-text": "'O furmato d' 'o cuntenuto $1 nun è suppurtato d' 'o mudello 'e cuntenuto $2.",
        "content-model-wikitext": "wikitesto",
        "post-expand-template-argument-warning": "'''Attenziò:''' sta paggena cuntene uno o cchiù argumente 'e template troppo gruosse pe' 'a spannere. Sti argumente se lassarranno fore.",
        "post-expand-template-argument-category": "Paggene ca cunteneno argumente nun cunziderate",
        "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.",
        "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",
        "page_first": "primma",
        "page_last": "úrdema",
        "histlegend": "Confronto nfra verziune: sciglite 'e casciulelle c'attoccassero a 'e verziune che vulite cunfruntà e spremmite Invio o pure 'o buttóne ccà abbascio.\n\nLiggenda: '''({{int:cur}})''' = differenze c' 'a verzione 'e mmò, '''({{int:last}})''' = differenze c' 'a verzione 'e primma, '''{{int:minoreditletter}}''' = cagnamiento minore",
-       "history-fieldset-title": "Naviga dint' 'a cronologgia",
-       "history-show-deleted": "Solo chille canciellate",
+       "history-fieldset-title": "Circa pe' verziune",
+       "history-show-deleted": "Sulo 'e verziune scancellate",
        "histfirst": "primma",
        "histlast": "urdema",
        "historysize": "({{PLURAL:$1|1 byte|$1 byte}})",
        "searchprofile-advanced-tooltip": "Circa dint'e namespace perzonalizzate",
        "search-result-size": "$1 ({{PLURAL:$2|'na parola|$2 parole}})",
        "search-result-category-size": "{{PLURAL:$1|1 utente|$1 utente}} ({{PLURAL:$2|1 sottocategurìa|$2 sottocategurìe}}, {{PLURAL:$3|1 file|$3 files}})",
-       "search-redirect": "(redirect $1)",
+       "search-redirect": "(redirect 'a $1)",
        "search-section": "(sezzione $1)",
        "search-category": "(categurìa $1)",
        "search-file-match": "(currispunnenza dint' 'e cuntenute d' 'o file)",
        "search-suggest": "Prova chisto: $1",
        "search-rewritten": "Mmustann' 'e risultate pe' $1. Circa mmece pe' $2.",
-       "search-interwiki-caption": "Prugiette frate",
+       "search-interwiki-caption": "Risultate 'a prugiette frate",
        "search-interwiki-default": "Risultate 'a $1:",
        "search-interwiki-more": "(cchiù)",
+       "search-interwiki-more-results": "cchiù risultate",
        "search-relatedarticle": "Azzeccato",
        "searchrelated": "azzeccato",
        "searchall": "Tutte",
        "search-external": "Ricerca 'a fore",
        "searchdisabled": "'A ricerca dint'a {{SITENAME}} nun è attiva; pe' tramente se putesse ausà nu mutore 'e cerca sterno comm'a Google. (Avite 'e sapé però, ca sti cuntenute d' 'o {{SITENAME}} dint' 'e mutore, può darse ca nun stanno agghiurnate.)",
        "search-error": "È succiesso n'errore pe' tramente ca se faceva 'a ricerca: $1",
+       "search-warning": "È succiesso n'avvertimento pe' tramente ca se vaceva 'a ricerca: $1",
        "preferences": "Preferenze d''e mmeje",
        "mypreferences": "Preferenze d''e mmeje",
        "prefs-edits": "Cagnamiente affettuate:",
        "prefs-help-recentchangescount": "Chesto ntenne ll'urdeme cagnamiente, 'e cronologgie 'e paggena, e riggistre.",
        "prefs-help-watchlist-token2": "Chest'è 'a chiave segreta pe se ffà 'o feed web 'e l'elenco 'e cuntrolo d' 'o vuosto.\nSi coccheruno 'a cunoscesse, allora putesse vedé l'elenco 'e cuntrollo, picciò nun 'a spartite. [[Special:ResetTokens|Cliccate ccà se tenite necessità d' 'a rimpizzà]].",
        "savedprefs": "'E preferenze songo state sarvate.",
-       "savedrights": "'E dritte 'e l'utente {{GENDER:$1|$1}} sto state sarvate.",
+       "savedrights": "'E dritte 'e gruppe 'utente {{GENDER:$1|$1}} sto state sarvate.",
        "timezonelegend": "Fuso orario:",
        "localtime": "Ora lucale:",
        "timezoneuseserverdefault": "Aúsa ora predefinita d' 'o wiki ($1)",
        "youremail": "E-mail:",
        "username": "{{GENDER:$1|Nomme utente}}:",
        "prefs-memberingroups": "{{GENDER:$2|Membro}} {{PLURAL:$1|d' 'o gruppo|d' 'e gruppe}}:",
+       "group-membership-link-with-expiry": "$1 (nzin' 'a $2)",
        "prefs-registration": "Data 'e riggistrazione:",
        "yourrealname": "Nomme vero",
        "yourlanguage": "Lengua:",
        "prefs-help-prefershttps": "Sta preferenza averrà affetto 'a 'o prossimo acciesso vuosto.",
        "prefswarning-warning": "Avite fatto cagnamiente a 'e preferenze d' 'e vuoste ca nun so' stat'ancora sarvate.\nSi ascite 'a sta paggena senza clickà \"$1\" 'e preferenze d' 'e vuoste nun sarranno agghiurnate.",
        "prefs-tabs-navigation-hint": "Suggerimento: se ponno ausà 'e buttòne 'e freccia a manca e a dritta pe' ve muovere nfra 'e schede dint'a l'elenco d' 'e schede.",
-       "userrights": "Gestione d' 'e permesse 'e l'utente",
-       "userrights-lookup-user": "Gestione 'e gruppe d'utenza",
+       "userrights": "Deritte utente",
+       "userrights-lookup-user": "Sciglie n'utente",
        "userrights-user-editname": "Nzertàte nu nomme utente:",
-       "editusergroup": "Cagnate 'e gruppe d'{{GENDER:$1|utenze}}",
+       "editusergroup": "Càrreca gruppe 'utente",
        "editinguser": "Cagnamiento d' 'e deritte d'{{GENDER:$1|utente}} '''[[User:$1|$1]]''' $2",
-       "userrights-editusergroup": "Cagnate 'e gruppe d'utenze",
+       "viewinguserrights": "Verenn' 'e deritte '{{GENDER:$1|utente}} <strong>[[User:$1|$1]]</strong> $2",
+       "userrights-editusergroup": "Cagna 'e gruppe '{{GENDER:$1|utente}}",
+       "userrights-viewusergroup": "Vire gruppe {{GENDER:$1|utente}}",
        "saveusergroups": "Sarvate 'e gruppe d'{{GENDER:$1|utenza}}",
        "userrights-groupsmember": "Ffà parte {{PLURAL:$1|d' 'o gruppo|d' 'e gruppe}}:",
        "userrights-groupsmember-auto": "Membro mplicito 'e:",
-       "userrights-groups-help": "Putite cagnà 'e gruppe assegnate a l'utente:\n* Na cascia 'e spunta scigliuta significasse ca appartenenza 'e l'utente a 'o gruppo\n* Na cascia 'e spunta nun scigliuta significasse 'a nun appartenenza a 'o gruppo.\n* 'O simmolo * significasse ca nun se può scancellà l'appartenenza a 'o gruppo aropp'a ll'avé miso (o viceversa).",
+       "userrights-groups-help": "Putite cagnà 'e gruppe assegnate a l'utente:\n* Na cascia 'e spunta scigliuta significasse ca appartenenza 'e l'utente a 'o gruppo\n* Na cascia 'e spunta nun scigliuta significasse 'a nun appartenenza a 'o gruppo.\n* 'O simmolo * significasse ca nun se può scancellà l'appartenenza a 'o gruppo aropp'a ll'avé miso (o viceversa).\n* 'O # significasse ca vuje putite surtanto tirà arreto nu tiempo 'e ammatura 'e stu gruppo utente; nun 'o putite fà annanze.",
        "userrights-reason": "Mutivo:",
        "userrights-no-interwiki": "Nun tenite permesse pe' cagnà 'e deritte 'e l'utente ncopp'a l'ati wiki.",
        "userrights-nodatabase": "'O database $1 nun esiste o nun è nu database lucale.",
        "userrights-changeable-col": "Gruppe ca putite cagnà",
        "userrights-unchangeable-col": "Gruppe ca nun putite cagnà",
+       "userrights-expiry-current": "Ammatura 'o $1",
+       "userrights-expiry-none": "Nun ammaturasse",
+       "userrights-expiry": "Ammatura:",
+       "userrights-expiry-existing": "'O tiempo d'ammaturamiento esistente: $3, $2",
+       "userrights-expiry-othertime": "N'ata durata:",
+       "userrights-expiry-options": "1 juorne:1 day,1 semmana:1 week,1 mese:1 month,3 mise:3 mise,6 mesi:6 months,1 anno:1 year",
+       "userrights-invalid-expiry": "'O tiempo pe' quanno ammatura 'o gruppo \"$1\" nun è buono.",
+       "userrights-expiry-in-past": "'O tiempo pe' quanno ammatura 'o gruppo \"$1\" fosse int' 'o passato.",
+       "userrights-cannot-shorten-expiry": "Nun putite turnà arreto 'o tiempo ammatura int' 'o gruppo \"$1\". Surtanto ll'utente cu nu permesso pe' puté azzeccà o luvà stu gruppo ponno ffà annanze 'e tiempe ammaturamiento.",
        "userrights-conflict": "Conflitto 'e cagnamiento 'e deritte utente! Cuntrullate e cunfermate 'e cagnamiente vuoste.",
        "group": "Gruppo:",
        "group-user": "Utente",
        "grant-basic": "Deritte 'e base",
        "grant-viewdeleted": "Vide 'e file e paggene scancellate",
        "grant-viewmywatchlist": "Vide l'elenco 'e cuntrullate",
+       "grant-viewrestrictedlogs": "Vide 'e valure private d' 'o riggistro",
        "newuserlogpage": "Riggistro 'e nuove utente",
        "newuserlogpagetext": "Chest'è nu riggistro 'e criazione d'utenze.",
        "rightslog": "Deritte 'e ll'utente",
        "action-upload_by_url": "carreca stu file 'a n'indirizzo URL",
        "action-writeapi": "usa l'API 'n scrittura",
        "action-delete": "scancèlla chista paggena",
-       "action-deleterevision": "scancellà sta verziona",
+       "action-deleterevision": "scancellà 'e verziune",
        "action-deletedhistory": "vide 'a cronologgia scancellata 'e sta paggena",
        "action-browsearchive": "ascìa dint' 'e paggene scancellate",
        "action-undelete": "arripiglia chista paggena",
        "fileduplicatesearch-noresults": "Nisciuno file chiamato \"$1\" è stato accucchiato.",
        "specialpages": "Paggene speciale",
        "specialpages-note-top": "Liggenda",
-       "specialpages-note": "* Paggene speciale normale.\n* <span class=\"mw-specialpagerestricted\">Paggene speciale ch' 'e restriziune.</span>",
        "specialpages-group-maintenance": "Report 'e manutenzione",
        "specialpages-group-other": "Ati paggene speciale",
        "specialpages-group-login": "Trasite o criate n'acciesso nuovo",
index edaebe7..2763df2 100644 (file)
        "fileduplicatesearch-noresults": "Ingen ved navn «$1» funnet.",
        "specialpages": "Spesialsider",
        "specialpages-note-top": "Tegnforklaring",
-       "specialpages-note": "* Normale spesialsider.\n* <span class=\"mw-specialpagerestricted\">Spesialsider med begrenset tilgang.</span>",
        "specialpages-group-maintenance": "Vedlikeholdsrapporter",
        "specialpages-group-other": "Andre spesialsider",
        "specialpages-group-login": "Innlogging / opprette bruker",
index de9207a..99041d4 100644 (file)
        "right-autocreateaccount": "Automatisch aanmelden met een extern gebruikersaccount",
        "right-minoredit": "Bewerkingen als klein markeren",
        "right-move": "Pagina's hernoemen",
-       "right-move-subpages": "Pagina's inclusief subpagina's verplaatsen",
+       "right-move-subpages": "Pagina's inclusief deelpagina's verplaatsen",
        "right-move-rootuserpages": "Gebruikerspagina's van het hoogste niveau hernoemen",
        "right-move-categorypages": "Categoriepagina's hernoemen",
        "right-movefile": "Bestanden hernoemen",
        "action-history": "de geschiedenis van deze pagina te bekijken",
        "action-minoredit": "deze bewerking als klein te markeren",
        "action-move": "deze pagina te hernoemen",
-       "action-move-subpages": "deze pagina en bijbehorende subpagina's te hernoemen",
+       "action-move-subpages": "deze pagina en bijbehorende deelpagina's te hernoemen",
        "action-move-rootuserpages": "gebruikerspagina's van het hoogste niveau te hernoemen",
        "action-move-categorypages": "categoriepagina's te hernoemen",
        "action-movefile": "dit bestand te hernoemen",
        "rcfilters-legend-heading": "<strong>Lijst met afkortingen:</strong>",
        "rcfilters-activefilters": "Actieve filters",
        "rcfilters-advancedfilters": "Geavanceerde filters",
+       "rcfilters-limit-title": "Wijzigingen om te tonen",
+       "rcfilters-limit-shownum": "Toon laatste $1 wijzigingen",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|dag|dagen}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|uur|uren}}",
        "rcfilters-quickfilters": "Opgeslagen filters",
        "rcfilters-quickfilters-placeholder-title": "Nog geen koppelingen opgeslagen",
        "rcfilters-quickfilters-placeholder-description": "Om uw filterinstellingen op te slaan en later te kunnen hergebruiken, klik op het bladwijzer pictogram in het Actieve Filter gebied beneden.",
        "rcfilters-savedqueries-unsetdefault": "Als standaard verwijderen",
        "rcfilters-savedqueries-remove": "Verwijderen",
        "rcfilters-savedqueries-new-name-label": "Naam",
+       "rcfilters-savedqueries-new-name-placeholder": "Beschrijf het doel van het filter",
        "rcfilters-savedqueries-apply-label": "Filter aanmaken",
        "rcfilters-savedqueries-cancel-label": "Annuleren",
        "rcfilters-savedqueries-add-new-title": "Huidige filter instellingen opslaan",
        "rcfilters-invalid-filter": "Ongeldig filter",
        "rcfilters-empty-filter": "Geen actieve filters. Alle bijdragen worden weergeven.",
        "rcfilters-filterlist-title": "Filters",
-       "rcfilters-filterlist-whatsthis": "Wat is dit?",
+       "rcfilters-filterlist-whatsthis": "Hoe werkt dit?",
        "rcfilters-filterlist-feedbacklink": "Geef terugkoppeling op de nieuwe (beta)filters",
        "rcfilters-highlightbutton-title": "Resultaten markeren",
        "rcfilters-highlightmenu-title": "Kies een kleur",
        "rcfilters-filter-editsbyself-description": "Uw eigen bijdragen.",
        "rcfilters-filter-editsbyother-label": "Wijzigingen door anderen",
        "rcfilters-filter-editsbyother-description": "Alle wijzigingen behalve die door u gemaakt zijn.",
-       "rcfilters-filtergroup-userExpLevel": "Ervaringsniveau (alleen voor geregistreerde gebruikers)",
+       "rcfilters-filtergroup-userExpLevel": "Gebruikersregistratie en ervaring",
        "rcfilters-filter-user-experience-level-registered-label": "Geregistreerd",
-       "rcfilters-filter-user-experience-level-registered-description": "Ingelogde gebruikers.",
+       "rcfilters-filter-user-experience-level-registered-description": "Aangemelde bewerkers.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Niet-geregistreerd",
-       "rcfilters-filter-user-experience-level-unregistered-description": "Gebruikers die niet zijn ingelogd.",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Bewerkers die niet zijn aangemeld.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Nieuwkomers",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Minder dan 10 bewerkingen en 4 dagen van activiteit.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Geregistreerde bewerkers met minder dan 10 bewerkingen en 4 dagen van activiteit.",
        "rcfilters-filter-user-experience-level-learner-label": "Leerlingen",
-       "rcfilters-filter-user-experience-level-learner-description": "Meer ervaring dan \"nieuwkomers\", maar minder dan \"ervaren gebruikers\".",
+       "rcfilters-filter-user-experience-level-learner-description": "Geregistreerde bewerkers met meer ervaring dan \"nieuwkomers\", maar minder dan \"ervaren gebruikers\".",
        "rcfilters-filter-user-experience-level-experienced-label": "Ervaren gebruikers",
-       "rcfilters-filter-user-experience-level-experienced-description": "Meer dan 30 dagen van activiteit en 500 bewerkingen.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Geregistreerde bewerkers met meer dan 500 bewerkingen en 30 dagen van activiteit.",
        "rcfilters-filtergroup-automated": "Automatische bijdragen",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-bots-description": "De wijzigingen van geautomatiseerde hulpmiddelen.",
        "rcfilters-filter-logactions-label": "Geregistreerde acties",
        "rcfilters-filter-logactions-description": "Administratieve handelingen, account creaties, pagina verwijderingen, uploads…",
        "rcfilters-hideminor-conflicts-typeofchange": "Bepaalde soorten wijzigingen kunnen niet worden aangemerkt als \"klein\", dus dit filter is in conflict met de volgende soorten wijzigingenfilters: $1",
-       "rcfilters-filtergroup-lastRevision": "Laatste versie",
+       "rcfilters-filtergroup-lastRevision": "Laatste versies",
        "rcfilters-filter-lastrevision-label": "Laatste versie",
-       "rcfilters-filter-lastrevision-description": "De meest recente wijziging aan de pagina.",
-       "rcfilters-filter-previousrevision-label": "Eerdere versies",
-       "rcfilters-filter-previousrevision-description": "Alle wijzigingen die niet de meest recente wijziging op de pagina zijn.",
+       "rcfilters-filter-lastrevision-description": "Alleen de meest recente wijziging aan de pagina.",
+       "rcfilters-filter-previousrevision-label": "Niet de laatste versie",
+       "rcfilters-filter-previousrevision-description": "Alle wijzigingen die niet de \"laatste versie\" zijn.",
        "rcfilters-filter-excluded": "Uitgesloten",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:niet</strong> $1",
+       "rcfilters-exclude-button-off": "Geselecteerde uitsluiten",
        "rcfilters-view-tags": "Gelabelde bewerkingen",
        "rcfilters-view-namespaces-tooltip": "Filter resultaten op naamruimte",
        "rcfilters-view-tags-tooltip": "Filter resultaten door middel van bewerkingslabels",
        "delete-warning-toobig": "Deze pagina heeft een lange bewerkingsgeschiedenis, meer dan $1 {{PLURAL:$1|versie|versies}}.\nHet verwijderen van deze pagina kan de werking van de database van {{SITENAME}} verstoren.\nWees voorzichtig.",
        "deleteprotected": "U kunt deze pagina niet verwijderen omdat hij is beveiligd.",
        "deleting-backlinks-warning": "<strong>Waarschuwing:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|andere pagina's]] gebruiken of verwijzen naar de pagina die u wilt verwijderen.",
+       "deleting-subpages-warning": "<strong>Waarschuwing:</strong>De pagina die u wilt verwijderen heeft [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|een deelpagina|$1 deelpagina's|51=meer dan 50 deelpagina's}}]].",
        "rollback": "Wijzigingen ongedaan maken",
        "rollbacklink": "terugdraaien",
        "rollbacklinkcount": "{{PLURAL:$1|één bewerking|$1 bewerkingen}} terugdraaien",
        "articleexists": "De pagina bestaat al of de paginanaam is ongeldig.\nKies een andere paginanaam.",
        "cantmove-titleprotected": "U kunt geen pagina naar deze naam hernoemen, omdat deze naam beveiligd is tegen het aanmaken ervan.",
        "movetalk": "Bijbehorende overlegpagina hernoemen",
-       "move-subpages": "Subpagina's hernoemen (maximaal $1)",
-       "move-talk-subpages": "Subpagina's van overlegpagina's hernoemen (maximaal $1)",
+       "move-subpages": "Deelpagina's hernoemen (maximaal $1)",
+       "move-talk-subpages": "Deelpagina's van overlegpagina's hernoemen (maximaal $1)",
        "movepage-page-exists": "De pagina $1 bestaat al en kan niet automatisch verwijderd worden.",
        "movepage-page-moved": "De pagina $1 is hernoemd naar $2.",
        "movepage-page-unmoved": "De pagina $1 kon niet hernoemd worden naar $2.",
        "movepage-max-pages": "Het maximale aantal automatisch te hernoemen pagina's is bereikt ({{PLURAL:$1|$1|$1}}).\nDe overige pagina's worden niet automatisch hernoemd.",
        "movelogpage": "Hernoemingslogboek",
        "movelogpagetext": "Hieronder staan hernoemde pagina's.",
-       "movesubpage": "{{PLURAL:$1|Subpagina|Subpagina's}}",
-       "movesubpagetext": "De {{PLURAL:$1|subpagina|$1 subpagina's}} van deze pagina {{PLURAL:$1|wordt|worden}} hieronder weergegeven.",
+       "movesubpage": "{{PLURAL:$1|Deelpagina|Deelpagina's}}",
+       "movesubpagetext": "De {{PLURAL:$1|deelpagina|$1 deelpagina's}} van deze pagina {{PLURAL:$1|wordt|worden}} hieronder weergegeven.",
        "movesubpagetalktext": "De bijbehorende overlegpagina heeft $1 {{PLURAL:$1|deelpagina|deelpagina's}} hierbeneden getoond.",
-       "movenosubpage": "Deze pagina heeft geen subpagina's.",
+       "movenosubpage": "Deze pagina heeft geen deelpagina's.",
        "movereason": "Reden:",
        "revertmove": "terugdraaien",
        "delete_and_move_text": "Onder de naam \"[[:$1]]\" bestaat al een pagina.\nWilt u deze verwijderen om plaats te maken voor de te hernoemen pagina?",
        "import-interwiki-submit": "Importeren",
        "import-mapping-default": "Importeren naar standaardplaatsen",
        "import-mapping-namespace": "Importeren naar een naamruimte:",
-       "import-mapping-subpage": "Importeren als subpagina's van de volgende pagina:",
+       "import-mapping-subpage": "Importeren als deelpagina's van de volgende pagina:",
        "import-upload-filename": "Bestandsnaam:",
        "import-comment": "Opmerking:",
        "importtext": "Gebruik de [[Special:Export|exportfunctie]] in de wiki waar de informatie vandaan komt.\nSla de uitvoer op uw eigen computer op, en voeg die daarna hier toe.",
        "import-error-bad-location": "Versie $2 met behulp van model $3 kan niet worden opgeslagen als \"$1\" op deze wiki, aangezien dat model niet ondersteund wordt op die pagina.",
        "import-options-wrong": "Verkeerde {{PLURAL:$2|optie|opties}}: <nowiki>$1</nowiki>",
        "import-rootpage-invalid": "De opgegeven basispagina is ongeldig.",
-       "import-rootpage-nosubpage": "In de naamruimte \"$1\" van de basispagina is het aanmaken van subpagina's niet mogelijk.",
+       "import-rootpage-nosubpage": "In de naamruimte \"$1\" van de basispagina is het aanmaken van deelpagina's niet mogelijk.",
        "importlogpage": "Importlogboek",
        "importlogpagetext": "Administratieve import van pagina's met geschiedenis van andere wiki's.",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|versie|versies}} geïmporteerd",
        "pageinfo-few-watchers": "Minder dan  {{PLURAL:$1|één volger|$1 volgers}}",
        "pageinfo-few-visiting-watchers": "Er kan wel of niet een volger zijn die de laatste bewerkingen hier bezoekt",
        "pageinfo-redirects-name": "Aantal doorverwijzingen naar deze pagina",
-       "pageinfo-subpages-name": "Subpagina's van deze pagina",
+       "pageinfo-subpages-name": "Aantal deelpagina's van deze pagina",
        "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|doorverwijzing|doorverwijzingen}}; $3 {{PLURAL:$3|niet-doorverwijzing|niet-doorverwijzingen}})",
        "pageinfo-firstuser": "Gebruiker die de pagina heeft aangemaakt",
        "pageinfo-firsttime": "Datum waarop de pagina is aangemaakt",
        "fileduplicatesearch-noresults": "Er is geen bestand met de naam \"$1\" gevonden.",
        "specialpages": "Speciale pagina's",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Normale speciale pagina's\n* <span class=\"mw-specialpagerestricted\">Beperkt toegankelijke speciale pagina's</span>",
+       "specialpages-note-restricted": "* Normale speciale pagina's.\n* <span class=\"mw-specialpagerestricted\">Beperkt toegankelijke speciale pagina's.</span>",
        "specialpages-group-maintenance": "Onderhoudsrapporten",
        "specialpages-group-other": "Overige speciale pagina's",
        "specialpages-group-login": "Aanmelden / registreren",
index 21533aa..c6c66f0 100644 (file)
        "preview": "Førehandsvising",
        "showpreview": "Førehandsvis",
        "showdiff": "Sjå skilnader",
+       "blankarticle": "<strong>Åtvaring:</strong> Sida du er i ferd med å oppretta er tom.\nKlikkar du på «$1» ein gong til vil sida opprettast utan innhald.",
        "anoneditwarning": "'''Åtvaring:''' Du er ikkje innlogga.\nIP-adressa di vil verta lagra i den offentlege endringshistorikken til sida. Om du <strong>[$1 loggar inn]</strong> eller <strong>[$2 lagar ein konto]</strong>, vil endringane dine knytast til brukarnamnet ditt, saman med andre fordelar.",
        "anonpreviewwarning": "''Du er ikkje innlogga. Lagrar du vil IP-adressa di verta ført opp i endringshistorikken til denne sida.''",
        "missingsummary": "'''Påminning:''' Du har ikkje skrive noko endringssamandrag. Dersom du trykkjer «Lagre» ein gong til, vert endringa di lagra utan.",
        "post-expand-template-inclusion-category": "Sider som inneheld for store malar",
        "post-expand-template-argument-warning": "Åtvaring: Sida inneheld ein eller fleire malparameterar som vert for lange når dei utvidast.\nDesse parameterane har vorte utelatne.",
        "post-expand-template-argument-category": "Sider med utelatne malparameterar",
-       "parser-template-loop-warning": "Malløkka oppdaga: [[$1]]",
+       "parser-template-loop-warning": "Mallykkje oppdaga: [[$1]]",
        "parser-template-recursion-depth-warning": "Malen er inkludert for mange gonger ($1)",
        "language-converter-depth-warning": "Språkomformaren si djubdegrense vart overstege ($1)",
        "node-count-exceeded-category": "Sider der talet på knutepunkt er overskride",
        "rcfilters-highlightbutton-title": "Uthev resultat",
        "rcfilters-highlightmenu-title": "Vel ein farge",
        "rcfilters-filterlist-noresults": "Fann ingen filter",
-       "rcfilters-filtergroup-registration": "Brukarregistrering",
-       "rcfilters-filter-registered-label": "Registrerte",
-       "rcfilters-filter-registered-description": "Innlogga brukarar.",
-       "rcfilters-filter-unregistered-label": "Uregistrerte",
-       "rcfilters-filter-unregistered-description": "Brukarar som ikkje er innlogga.",
        "rcfilters-filtergroup-authorship": "Forfattar",
        "rcfilters-filter-editsbyself-label": "Endringar av deg",
        "rcfilters-filter-editsbyself-description": "Dine eigne bidrag.",
        "rcfilters-filter-editsbyother-label": "Endringar av andre",
        "rcfilters-filter-editsbyother-description": "Alle endringar utanom dine eigne.",
        "rcfilters-filtergroup-userExpLevel": "Røynslenivå (berre for registrerte brukarar)",
+       "rcfilters-filter-user-experience-level-registered-label": "Registrerte",
+       "rcfilters-filter-user-experience-level-registered-description": "Innlogga brukarar.",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Uregistrerte",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Brukarar som ikkje er innlogga.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Nykomarar",
        "rcfilters-filter-user-experience-level-newcomer-description": "Færre enn 10 endringar og 4 dagar med aktivitet.",
        "rcfilters-filter-user-experience-level-learner-label": "Nybyrjarar",
        "fileduplicatesearch-noresults": "Fann inga fil med namnet «$1».",
        "specialpages": "Spesialsider",
        "specialpages-note-top": "Tyding",
-       "specialpages-note": "* Vanlege spesialsider.\n* <span class=\"mw-specialpagerestricted\">Spesialsider med avgrensa tilgang.</span>",
        "specialpages-group-maintenance": "Vedlikehaldsrapportar",
        "specialpages-group-other": "Andre spesialsider",
        "specialpages-group-login": "Logga inn / oppretta brukarkonto",
        "logentry-move-move_redir": "$1 {{GENDER:$2|flytte}} sida $3 til $4 over ei omdirigering",
        "logentry-move-move_redir-noredirect": "$1 {{GENDER:$2|flytte}} sida $3 til $4 over ei omdirigering utan å lata etter ei omdirigering",
        "logentry-patrol-patrol": "$1 {{GENDER:$2|merkte}} versjon $4 av sida $3 som patruljert",
-       "logentry-patrol-patrol-auto": "$1{{GENDER:$2| merkte}} automatisk versjon $4 av sida $3 som patruljert",
+       "logentry-patrol-patrol-auto": "$1 {{GENDER:$2|merkte}} automatisk versjon $4 av sida $3 som patruljert",
        "logentry-newusers-newusers": "Brukarkontoen $1 vart {{GENDER:$2|oppretta}}",
        "logentry-newusers-create": "Brukarkontoen $1 vart {{GENDER:$2|oppretta}}",
        "logentry-newusers-create2": "Brukarkontoen $3 vart {{GENDER:$2|oppretta}} av $1",
index 9d1a356..61a40b3 100644 (file)
        "rcfilters-legend-heading": "<strong>Wykaz skrótów:</strong>",
        "rcfilters-activefilters": "Aktywne filtry",
        "rcfilters-advancedfilters": "Zaawansowane filtry",
+       "rcfilters-limit-title": "Zmian do pokazania",
+       "rcfilters-limit-shownum": "Pokaż ostatnie $1 zmian",
+       "rcfilters-days-title": "Ostatnich dni",
+       "rcfilters-hours-title": "Ostatnich godzin",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|dzień|dni}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|godzina|godziny|godzin}}",
        "rcfilters-quickfilters": "Zapisane filtry",
        "rcfilters-quickfilters-placeholder-title": "Nie masz jeszcze zapisanych linków",
        "rcfilters-quickfilters-placeholder-description": "Aby zapisać ustawienia filtrów i używać ich później, kliknij ikonkę zakładki w polu aktywnych filtrów znajdującym się niżej.",
        "rcfilters-invalid-filter": "Nieprawidłowy filtr",
        "rcfilters-empty-filter": "Brak aktywnych filtrów. Wyświetlane są wszystkie zmiany.",
        "rcfilters-filterlist-title": "Filtry",
-       "rcfilters-filterlist-whatsthis": "Co to jest?",
+       "rcfilters-filterlist-whatsthis": "Jak działają?",
        "rcfilters-filterlist-feedbacklink": "Podziel się swoją opinią na temat tych nowych (beta) filtrów",
        "rcfilters-highlightbutton-title": "Podświetl wyniki",
        "rcfilters-highlightmenu-title": "Wybierz kolor",
        "rcfilters-filter-editsbyself-description": "Czynności dokonane przez Ciebie.",
        "rcfilters-filter-editsbyother-label": "Zmiany dokonane przez innych",
        "rcfilters-filter-editsbyother-description": "Wszystkie zmiany oprócz Twoich.",
-       "rcfilters-filtergroup-userExpLevel": "Poziom doświadczenia (tylko o zarejestrowanych użytkownikach)",
+       "rcfilters-filtergroup-userExpLevel": "Zarejestrowanie użytkownika i doświadczenie",
        "rcfilters-filter-user-experience-level-registered-label": "Zarejestrowani",
        "rcfilters-filter-user-experience-level-registered-description": "Zalogowani edytorzy.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Niezarejestrowani",
        "rcfilters-filter-user-experience-level-unregistered-description": "Niezalogowani",
        "rcfilters-filter-user-experience-level-newcomer-label": "Początkujący",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Mniej niż 10 edycji i 4 dni aktywności.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Zarejestrowani edytorzy z mniej niż 10 edycji i 4 dni aktywności.",
        "rcfilters-filter-user-experience-level-learner-label": "Uczący się",
-       "rcfilters-filter-user-experience-level-learner-description": "Większe doświadczenie niż „Nowicjusze”, ale mniejsze niż „Doświadczeni użytkownicy”.",
+       "rcfilters-filter-user-experience-level-learner-description": "Zarejestrowani edytujący, których doświadczenie plasuje się między „Nowicjuszami”, a „Doświadczonymi użytkownikami”.",
        "rcfilters-filter-user-experience-level-experienced-label": "Doświadczeni użytkownicy",
-       "rcfilters-filter-user-experience-level-experienced-description": "Ponad 30 dni aktywności i 500 edycji.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Zarejestrowani edytujący z ponad 500 edycji i 30 dni aktywności.",
        "rcfilters-filtergroup-automated": "Zmiany zautomatyzowane",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-bots-description": "Zmiany wykonane z użyciem zautomatyzowanych narzędzi.",
        "rcfilters-hideminor-conflicts-typeofchange-global": "Filtr „Drobne zmiany” koliduje z jednym lub wieloma filtrami Rodzaju zmian, ponieważ niektóre rodzaje zmian nie mogą być uznawane za  „drobne”. Kolidujące filtry zostały powyżej odpowiednio zaznaczone na pasku aktywnych filtrów.",
        "rcfilters-hideminor-conflicts-typeofchange": "Niektóre rodzaje zmian nie mogą być uznawane za „drobne”, dlatego ten filtr koliduje z następującymi filtrami Rodzaju zmian: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Ten filtr Rodzaju zmian koliduje z filtrem „Drobne zmiany”. Nie wszystkie zmiany mogą być uznawane za „drobne”.",
-       "rcfilters-filtergroup-lastRevision": "Ostatnia wersja",
-       "rcfilters-filter-lastrevision-label": "Ostatnie wersje",
+       "rcfilters-filtergroup-lastRevision": "Ostatnie wersje",
+       "rcfilters-filter-lastrevision-label": "Najnowsza wersja",
        "rcfilters-filter-lastrevision-description": "Tylko najnowsze zmiany dla każdej ze stron.",
-       "rcfilters-filter-previousrevision-label": "Wcześniejsze wersje",
-       "rcfilters-filter-previousrevision-description": "Wszystkie edycje, które nie są najnowszą zmianą strony.",
+       "rcfilters-filter-previousrevision-label": "Wersje inne niż najnowsza",
+       "rcfilters-filter-previousrevision-description": "Wszystkie edycje, które nie są najnowszą wersją strony.",
        "rcfilters-filter-excluded": "Wykluczono",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:nie z</strong> $1",
+       "rcfilters-exclude-button-off": "Wyklucz zaznaczone",
+       "rcfilters-exclude-button-on": "Zaznaczone są wykluczone",
        "rcfilters-view-tags": "Edycje ze znacznikami zmian",
        "rcfilters-view-namespaces-tooltip": "Przefiltruj wyniki według przestrzeni nazw",
        "rcfilters-view-tags-tooltip": "Przefiltruj wyniki według znaczników zmian",
        "delete-warning-toobig": "Ta strona ma bardzo długą historię edycji – ponad $1 {{PLURAL:$1|zmianę|zmiany|zmian}}.<br />\nBądź ostrożny, ponieważ usunięcie jej może spowodować zakłócenia w pracy {{GRAMMAR:D.lp|{{SITENAME}}}}.",
        "deleteprotected": "Nie możesz usunąć tej strony, ponieważ została zabezpieczona.",
        "deleting-backlinks-warning": "<strong>Uwaga:</strong> Do strony, którą masz zamiar usunąć, odwołują się [[Special:WhatLinksHere/{{FULLPAGENAME}}|inne strony]].",
+       "deleting-subpages-warning": "<strong>Ostrzeżenie:</strong> Strona którą chcesz usunąć ma [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|jedną podstronę|$1 podstrony|$1 podstron|51=ponad 50 podstron}}]].",
        "rollback": "Cofnij edycję",
        "rollbacklink": "cofnij",
        "rollbacklinkcount": "cofnij $1 {{PLURAL:$1|edycję|edycje|edycji}}",
        "fileduplicatesearch-noresults": "Brak pliku o nazwie „$1”.",
        "specialpages": "Strony specjalne",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Normalne strony specjalne.\n* <span class=\"mw-specialpagerestricted\">Zastrzeżone strony specjalne.</span>",
+       "specialpages-note-restricted": "* Normalne strony specjalne.\n* <span class=\"mw-specialpagerestricted\">Zastrzeżone strony specjalne.</span>",
        "specialpages-group-maintenance": "Raporty konserwacyjne",
        "specialpages-group-other": "Inne strony specjalne",
        "specialpages-group-login": "Logowanie / rejestracja",
index 81b6f72..eda97ca 100644 (file)
@@ -14,7 +14,8 @@
                        "Matma Rex",
                        "Saanvel",
                        "Satdeep gill",
-                       "Abbas dhothar"
+                       "Abbas dhothar",
+                       "Saraiki"
                ]
        },
        "tog-underline": "جوڑ تھلے لین:",
        "category-file-count-limited": "اس گٹھ چ اے {{PLURAL:$1|فائل اے|$1 فائلاں نیں}}۔",
        "listingcontinuesabbrev": "جاری",
        "index-category": "انڈیکسڈ صفے",
-       "noindex-category": "نان انڈیکسڈ صفے",
+       "noindex-category": "نان انڈیکسڈ ورقے",
        "broken-file-category": "ٹٹے ہوۓ جوڑاں آلے صفحے",
        "about": "بارے چ",
        "article": "آرٹیکل والا صفہ",
        "anontalk": "گل",
        "navigation": "کھوج",
        "and": "&#32;تے",
-       "qbfind": "کھوج",
-       "qbbrowse": "لبو",
-       "qbedit": "لکھو",
-       "qbpageoptions": "اے صفہ",
-       "qbmyoptions": "میرے صفے",
        "faq": "FAQ",
-       "faqpage": "Project:FAQ",
        "actions": "کم",
        "namespaces": "ناں تھانواں:",
        "variants": "قسماں",
        "view": "وکھالہ",
        "view-foreign": "$1 تے ویکھو",
        "edit": "لکھو",
+       "edit-local": "مقامی تفصیل درج کرو",
        "create": "بناؤ",
        "create-local": "آپنی لکھت رلاؤ",
-       "editthispage": "اس صفحہ تے لکھو",
-       "create-this-page": "اے صفحہ بناؤ",
        "delete": "مٹاؤ",
-       "deletethispage": "اے صفحہ مٹاؤ",
-       "undeletethispage": "اس صفحے نوں واپس لیاؤ",
        "undelete_short": "مٹانا واپس {{PLURAL:$1|اکتبدیلی|$1 تبدیلی}}",
        "viewdeleted_short": "ویکھو {{PLURAL:$1|اک مٹائی گئی تبدیلی|$1 مٹائیاں گئیاں تبدیلیاں}}",
        "protect": "بچاؤ",
        "protect_change": "تبدیل کرو",
-       "protectthispage": "اے صفحہ بچاؤ",
        "unprotect": "اینا بچاؤ",
-       "unprotectthispage": "اے صفحہ اینا بچاؤ",
        "newpage": "نواں صفہ",
-       "talkpage": "اس صفحے دے بارے چ گل بات کرو",
        "talkpagelinktext": "گل بات",
        "specialpage": "خاص صفحہ",
        "personaltools": "ذاتی اوزار",
-       "articlepage": "مضمون آلا صفحہ",
        "talk": "گل بات",
        "views": "وکھالے",
        "toolbox": "سَند",
-       "userpage": "ورتن آلے دا صفہ ویکھو",
-       "projectpage": "ویونت والا صفہ ویکھو",
        "imagepage": "فائل آلا صفہ ویکھو",
        "mediawikipage": "سنیعا آلا صفحہ ویکھو",
        "templatepage": "سچے آلا صفحہ ویکھو",
        "fileduplicatesearch-result-n": "فائل ''$1'' چ {{PLURAL:$2|1 رلدی نقل|$2 رلدیاں نقلں}} نیں۔",
        "fileduplicatesearch-noresults": "\"$1\" ناں دی کوئی فائل نئیں لبی۔",
        "specialpages": "خاص صفے",
-       "specialpages-note": "* نارمل خاص صفے.\n* <span class=\"mw-specialpagerestricted\">روکے گۓ خاص صفے.</span>\n* <span class=\"mw-specialpagecached\">کاشے خاص صفے (پرانے ہوگۓ ہون).</span>",
        "specialpages-group-maintenance": "مرمت رپورٹ",
        "specialpages-group-other": "ہور خاص صفے",
        "specialpages-group-login": "لاگان / کھاتہ کھولو",
index c95d6f8..292fe61 100644 (file)
        "rcfilters-legend-heading": "<strong>Lista de abreviaturas:</strong>",
        "rcfilters-activefilters": "Filtros ativos",
        "rcfilters-advancedfilters": "Filtros avançados",
+       "rcfilters-limit-title": "Mudanças para mostrar",
+       "rcfilters-limit-shownum": "Mostrar as últimas $1 modificações",
+       "rcfilters-days-title": "Dias recentes",
+       "rcfilters-hours-title": "Horas recentes",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|dia|dias}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|hora|horas}}",
        "rcfilters-quickfilters": "Filtros salvos",
        "rcfilters-quickfilters-placeholder-title": "Ainda não foi gravado nenhum link",
        "rcfilters-quickfilters-placeholder-description": "Para gravar as suas configurações dos filtros e reutilizá-las mais tarde, clique o ícone do marcador de página, na área Filtro Ativo abaixo.",
        "rcfilters-invalid-filter": "Filtro inválido",
        "rcfilters-empty-filter": "Nenhum filtro ativo. Todas as contribuições são mostradas.",
        "rcfilters-filterlist-title": "Filtros",
-       "rcfilters-filterlist-whatsthis": "O que é isso?",
+       "rcfilters-filterlist-whatsthis": "Como funcionam estes?",
        "rcfilters-filterlist-feedbacklink": "Forneça feedback sobre os novos filtros (beta)",
        "rcfilters-highlightbutton-title": "Realçar os resultados",
        "rcfilters-highlightmenu-title": "Selecione uma cor",
        "rcfilters-filter-editsbyself-description": "Suas proprias contribuições.",
        "rcfilters-filter-editsbyother-label": "Mudanças de outros",
        "rcfilters-filter-editsbyother-description": "Todas as mudanças, exceto a sua.",
-       "rcfilters-filtergroup-userExpLevel": "Nível de experiência (apenas para usuário registados)",
+       "rcfilters-filtergroup-userExpLevel": "Registro e experiência do usuário",
        "rcfilters-filter-user-experience-level-registered-label": "Registrado",
-       "rcfilters-filter-user-experience-level-registered-description": "Editores conectados.",
-       "rcfilters-filter-user-experience-level-unregistered-label": "Não registrado",
-       "rcfilters-filter-user-experience-level-unregistered-description": "Editores que não estão conectados.",
+       "rcfilters-filter-user-experience-level-registered-description": "Editores registrados.",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Não registados",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Editores que não estão autenticados.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Recém-chegados",
        "rcfilters-filter-user-experience-level-newcomer-description": "Menos de 10 edições e 4 dias de atividade.",
        "rcfilters-filter-user-experience-level-learner-label": "Aprendizes",
        "rcfilters-hideminor-conflicts-typeofchange-global": "O filtro \"Edições menores\" conflita com um ou mais filtros de Tipo de Alteração, porque certos tipos de alteração não podem ser designadas como \"menores\". Os filtros em conflito estão marcados na área Filtros Ativos, acima.",
        "rcfilters-hideminor-conflicts-typeofchange": "Determinados tipos de alteração não podem ser designados como \"menor\", portanto, este filtro entra em conflito com os seguintes filtros de Tipo de Alteração: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Este filtro de Tipo de Alteração entra em conflito com o filtro \"Edições menores\". Certos tipos de mudança não podem ser designadas como \"menores\".",
-       "rcfilters-filtergroup-lastRevision": "Última revisão",
-       "rcfilters-filter-lastrevision-label": "Última revisão",
-       "rcfilters-filter-lastrevision-description": "A alteração mais recente para uma página.",
-       "rcfilters-filter-previousrevision-label": "Revisões anteriores",
-       "rcfilters-filter-previousrevision-description": "Todas as alterações que não são a alteração mais recente para uma página.",
+       "rcfilters-filtergroup-lastRevision": "Últimas revisões",
+       "rcfilters-filter-lastrevision-label": "Revisão atual",
+       "rcfilters-filter-lastrevision-description": "Somente a mudança mais recente para uma página.",
+       "rcfilters-filter-previousrevision-label": "Não é a última revisão",
+       "rcfilters-filter-previousrevision-description": "Todas as mudanças que não são as \"ultimas revisões\".",
        "rcfilters-filter-excluded": "Excluído",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:não</strong> $1",
+       "rcfilters-exclude-button-off": "Excluir selecionado",
+       "rcfilters-exclude-button-on": "Excluindo selecionados",
        "rcfilters-view-tags": "Edições marcadas",
        "rcfilters-view-namespaces-tooltip": "Filtrar resultados por namespace",
        "rcfilters-view-tags-tooltip": "Filtre os resultados usando edit tags",
        "delete-warning-toobig": "Esta página possui um longo histórico de edições, com mais de $1 {{PLURAL:$1|edição|edições}}.\nEliminá-la poderá causar problemas na base de dados de {{SITENAME}};\nprossiga com cuidado.",
        "deleteprotected": "Não é possível eliminar esta página porque foi protegida.",
        "deleting-backlinks-warning": "'''Cuidado:''' [[Special:WhatLinksHere/{{FULLPAGENAME}}|outras páginas]] ligam ou redirecionam para a página que você está prestes a eliminar.",
+       "deleting-subpages-warning": "<strong>Aviso:</strong> A página que você está prestes a excluir tem [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|uma subpágina|$1 subpáginas|51=mais de 50 subpáginas}}]].",
        "rollback": "Reverter edições",
        "rollbacklink": "reverter",
        "rollbacklinkcount": "reverter $1 {{PLURAL:$1|edição|edições}}",
        "fileduplicatesearch-noresults": "Não foi encontrado nenhum arquivo com o nome \"$1\".",
        "specialpages": "Páginas especiais",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Páginas especiais normais.\n* <span class=\"mw-specialpagerestricted\">Páginas especiais restritas.</span>",
        "specialpages-group-maintenance": "Relatórios de manutenção",
        "specialpages-group-other": "Outras páginas especiais",
        "specialpages-group-login": "Entrar / Criar conta",
index 0b3888d..23f56c0 100644 (file)
        "rcfilters-filter-editsbyself-description": "As suas edições.",
        "rcfilters-filter-editsbyother-label": "Modificações de outros",
        "rcfilters-filter-editsbyother-description": "Todas as modificações, exceto as suas.",
-       "rcfilters-filtergroup-userExpLevel": "Nível de experiência (apenas para utilizadores registados)",
+       "rcfilters-filtergroup-userExpLevel": "Registo de utilizadores e experiência",
        "rcfilters-filter-user-experience-level-registered-label": "Registados",
        "rcfilters-filter-user-experience-level-registered-description": "Editores autenticados.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Não registados",
        "rcfilters-filter-user-experience-level-unregistered-description": "Editores que não estão autenticados.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Novatos",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Menos de 10 edições e de 4 dias de atividade.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Editores registados, com menos de 10 edições e de 4 dias de atividade.",
        "rcfilters-filter-user-experience-level-learner-label": "Aprendizes",
-       "rcfilters-filter-user-experience-level-learner-description": "Mais experiência do que \"Novatos\", mas menos do que \"Utilizadores experientes\".",
+       "rcfilters-filter-user-experience-level-learner-description": "Editores registados, com mais experiência do que \"Novatos\", mas menos do que \"Utilizadores experientes\".",
        "rcfilters-filter-user-experience-level-experienced-label": "Utilizadores experientes",
-       "rcfilters-filter-user-experience-level-experienced-description": "Mais de 30 dias de atividade e de 500 edições.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Editores registados, com mais de 500 edições e de 30 dias de atividade.",
        "rcfilters-filtergroup-automated": "Contribuições automatizadas",
        "rcfilters-filter-bots-label": "Robô",
        "rcfilters-filter-bots-description": "Edições efetuadas por ferramentas automatizadas.",
        "rcfilters-filter-humans-label": "Ser humano (não robô)",
-       "rcfilters-filter-humans-description": "Edições efetuadas por editores humanos.",
+       "rcfilters-filter-humans-description": "Edições efetuadas por pessoas.",
        "rcfilters-filtergroup-reviewstatus": "Estado da revisão",
        "rcfilters-filter-patrolled-label": "Patrulhadas",
        "rcfilters-filter-patrolled-description": "Edições marcadas como patrulhadas.",
        "rcfilters-hideminor-conflicts-typeofchange-global": "O filtro \"Edições menores\" entra em conflito com um ou mais filtros de Tipo de Modificação, porque certos tipos de modificações não podem ser classificados como \"menores\". Os filtros em conflito estão marcados na área Filtros Ativos, acima.",
        "rcfilters-hideminor-conflicts-typeofchange": "Certos tipos de modificações não podem ser classificados como \"menores\", portanto este filtro entra em conflito com os seguintes filtros de Tipo de Modificação: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Este filtro de Tipo de Modificação entra em conflito com o filtro \"Edições menores\". Certos tipos de modificações não podem ser classificados como \"menores\".",
-       "rcfilters-filtergroup-lastRevision": "Última revisão",
+       "rcfilters-filtergroup-lastRevision": "Últimas revisões",
        "rcfilters-filter-lastrevision-label": "Última revisão",
-       "rcfilters-filter-lastrevision-description": "A modificação mais recente de uma página.",
-       "rcfilters-filter-previousrevision-label": "Revisões anteriores",
-       "rcfilters-filter-previousrevision-description": "Todas as modificações que não sejam a modificação mais recente de uma página.",
+       "rcfilters-filter-lastrevision-description": "Só a modificação mais recente de uma página.",
+       "rcfilters-filter-previousrevision-label": "Revisões menos a mais recente",
+       "rcfilters-filter-previousrevision-description": "Todas as modificações que não são a \"última revisão\".",
        "rcfilters-filter-excluded": "Excluído",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:não</strong> $1",
        "rcfilters-exclude-button-off": "Excluir os selecionados",
        "delete-warning-toobig": "Esta página tem um histórico de edições longo, com mais de $1 {{PLURAL:$1|edição|edições}}.\nEliminá-la poderá causar problemas na base de dados da wiki {{SITENAME}};\nprossiga com precaução.",
        "deleteprotected": "Não é possível eliminar esta página porque foi protegida.",
        "deleting-backlinks-warning": "<strong>Aviso:</strong> Existem [[Special:WhatLinksHere/{{FULLPAGENAME}}|páginas]] que contêm ligações para a página que está prestes a eliminar ou que a transcluem.",
+       "deleting-subpages-warning": "<strong>Aviso:</strong> A página que está prestes a eliminar tem [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|uma subpágina|$1 subpáginas|51=mais de 50 subpáginas}}]].",
        "rollback": "Reverter edições",
        "rollbacklink": "reverter",
        "rollbacklinkcount": "reverter $1 {{PLURAL:$1|edição|edições}}",
        "fileduplicatesearch-noresults": "Não foi encontrado nenhum ficheiro com o nome \"$1\".",
        "specialpages": "Páginas especiais",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Páginas especiais normais.\n* <span class=\"mw-specialpagerestricted\">Páginas especiais restritas.</span>",
+       "specialpages-note-restricted": "* Páginas especiais normais.\n* <span class=\"mw-specialpagerestricted\">Páginas especiais restritas.</span>",
        "specialpages-group-maintenance": "Relatórios de manutenção",
        "specialpages-group-other": "Outras páginas especiais",
        "specialpages-group-login": "Entrar / criar conta",
index 719f413..699ac96 100644 (file)
        "specialpages": "{{doc-special|SpecialPages|unlisted=1}}\nDisplay name of link to [[Special:SpecialPages]] shown on all pages in the toolbox.\n\nSee also:\n* {{msg-mw|Specialpages}}\n* {{msg-mw|Accesskey-t-specialpages}}\n* {{msg-mw|Tooltip-t-specialpages}}\n{{Identical|Special page}}",
        "specialpages-summary": "{{doc-specialpagesummary|specialpages}}",
        "specialpages-note-top": "Heading for {{msg-mw|specialpages-note}}.\n{{Identical|Legend}}",
-       "specialpages-note": "Footer note for the [[Special:SpecialPages]] page",
+       "specialpages-note-restricted": "Footer note for the [[Special:SpecialPages]] page",
+       "specialpages-note-cached": "{{ignore}}\nFooter note for the [[Special:SpecialPages]] page",
        "specialpages-group-maintenance": "{{doc-special-group|like=[[Special:DoubleRedirects]], [[Special:LonelyPages]] and [[Special:WantedPages]]}}",
        "specialpages-group-other": "{{doc-special-group|like=[[Special:AdminLinks]] and [[Special:BookSources]]}}",
        "specialpages-group-login": "{{doc-special-group|like=[[Special:UserLogin]]}}",
index 9da374a..95ac6bd 100644 (file)
        "privacypage": "Project:Tasertit n tusligi",
        "retrievedfrom": "Itwarr-d zi \"$1\"",
        "youhavenewmessages": "Ghar-k / Ghar-m $1 ($2).",
-       "editsection": "Ẓṛeg",
-       "editold": "ẓṛeg",
+       "editsection": "ⵙⵏⴼⵍ",
+       "editold": "ⵙⵏⴼⵍ",
        "viewsourceold": "ẓeṛ aɣbalu",
-       "editlink": "ẓṛg",
+       "editlink": "ⵙⵏⴼⵍ",
        "viewsourcelink": "ẓṛ aghbalu",
        "editsectionhint": "Ẓṛeg tigezmi: $1",
        "toc": "ⵜⵓⵎⴰⵢⵉⵏ",
        "yourpassword": "Tawalt n wadaf:",
        "login": "ⴰⴷⴼ",
        "nav-login-createaccount": "Adef / egg amiḍan",
-       "logout": "Ufugh",
-       "userlogout": "Ufugh",
+       "logout": "ⴼⴼⵖ",
+       "userlogout": "ⴼⴼⵖ",
        "createaccount": "Egg amiḍan",
        "createacct-benefit-body2": "{{PLURAL:$1|ⵜⴰⵙⵏⴰ|ⵜⴰⵙⵏⵉⵡⵉⵏ}}",
        "loginsuccesstitle": "Adaf icna",
        "newarticletext": "Tdefar-d tazdayt n Tasna εad war telli .\nbac ad tegged , arri di taflwit a swadday (xemm i [$1  Tasna n Tallalt] i ineɣmisen ifruryen).\nmala qacek da s ɣalaṭ waha, tecca di tbutunt n '''deffar''' di (browser) inec .",
        "noarticletext": "Rxxu ur din llint ca tira di tasna ya.\nTzmmard [[Special:Search/{{PAGENAME}}|rzu xf yizwl n tasna ya]] di tasniwin nnḍni,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs],\nnigh [{{fullurl:{{FULLPAGENAME}}|action=edit}} edit this page]</span>.",
        "previewnote": "'''Wa d Azar-ascan waha;\ntiẓṛigin εad war twaḥḍent!'''",
-       "editing": "Aẓṛag di $1",
-       "editingsection": "Aẓrag  di $1 (tigezmi)",
+       "editing": "ⴰⵙⵏⴼⵍ ⵏ $1",
+       "editingsection": "ⴰⵙⵏⴼⵍ ⵏ $1 (ⵜⵉⴳⵣⵎⵉ)",
        "copyrightwarning": "Maṛṛa tirra di {{SITENAME}} twaggent swadday i $2 (ẓar da $1).\nmala war texsed tirra inac ad twaẓṛegent , ad twamsebḍant .\nUr ten-teg ca da.<br />\ntjadjid-anɣ Ɛawt ila qa d cekk ig yuran manaya, niɣ tesneɣlet-id zi ca n uɣbal nniḍn d alelli.\n'''UR SADDAF CA TIRRA ƔARSENT COPYRIGHTE BLA MA AD-IXES BAB-INES !'''",
        "templatesused": "Timudmiwin itwaggen di Tasna ya:",
        "templatesusedpreview": "Timudmiwin igg itwasxdemen dg uzar-ascan a :",
        "action-read": "ⵖⵔ ⵜⴰⵙⵏⴰ ⴰ",
        "action-edit": "ⵙⵏⴼⵍ ⵜⴰⵙⵏⴰ ⴰ",
        "action-delete": "ⴽⴽⵙ ⵜⴰⵙⵏⴰ ⴰ",
-       "nchanges": "$1 {{PLURAL:$1|tiẓṛegt|tiẓṛigin}}",
+       "nchanges": "$1 {{PLURAL:$1|ⵓⵙⵏⴼⵍ|ⵉⵙⵏⴼⵉⵍⵏ}}",
        "enhancedrc-history": "ⴰⵎⵣⵔⵓⵢ",
        "recentchanges": "Tiẓṛigin tineggura",
        "recentchanges-feed-description": "Bbar tiẓṛigin timayutin n wiki deg usudem(feed) a .",
        "rcshowhideliu": "$1 users ig yudeffen",
        "rcshowhideanons": "$1 users war twasnen",
        "rcshowhidepatr": "Tiẓṛigin ig itwaẓrent di $1",
-       "rcshowhidemine": "$1 tiẓṛigin inu",
+       "rcshowhidemine": "$1 ⵉⵙⵏⴼⵉⵍⵏ ⵉⵏⵓ",
        "rclinks": "Ẓar $1 tiẓṛigin tinggura di $2 n ussan inggura",
        "diff": "imṣebḍan",
        "hist": "ⴰⵎⵣⵔⵓⵢ",
        "filehist": "Amzruy n usatul",
        "filehist-help": "Tka di date/time bac ad tẓerd afaylu mamec ja d-itban di Lwaqt a .",
        "filehist-deleteall": "ⴽⴽⵙ ⵎⴰⵔⵔⴰ",
-       "filehist-deleteone": "sfaḍ",
+       "filehist-deleteone": "ⴽⴽⵙ",
        "filehist-current": "ⴰⵎⵉⵔⴰⵏ",
        "filehist-datetime": "ⴰⵙⴰⴽⵓⴷ/ⴰⴽⵓⴷ",
        "filehist-user": "Aseqdac",
        "filehist-dimensions": "Tisektiwin",
-       "filehist-filesize": "Tiddi n ufaylu",
+       "filehist-filesize": "ⵜⵉⴷⴷⵉ ⵏ ⵓⴼⴰⵢⵍⵓ",
        "filehist-comment": "ⴰⵅⴼⴰⵡⴰⵍ",
        "imagelinks": "Aseqdec usatul",
        "linkstoimage": "{{PLURAL:$1|Tasna ya teqn-ad|$1 Tasniwin a qnent-id}} ɣa ufaylu ya :",
        "statistics-pages": "ⵜⴰⵙⵏⵉⵡⵉⵏ",
        "doubleredirects": "(redirects) ɛɛawdent",
        "brokenredirects": "(redirects) arẓent",
-       "brokenredirects-edit": "arri",
+       "brokenredirects-edit": "ⵙⵏⴼⵍ",
        "brokenredirects-delete": "ⴽⴽⵙ",
        "withoutinterwiki": "Tasna bla tiẓdayin n tutlayt",
        "withoutinterwiki-submit": "Smmrad",
        "actioncomplete": "Tiggawt tsala",
        "deletedtext": "\"$1\" Twakkes.\nXemm $2 i tikkas timaynutin.",
        "dellogpage": "Aɣmis n uṣfaḍ",
-       "deletecomment": "Ssebba:",
+       "deletecomment": "ⵜⴰⵎⵏⵜⵉⵍⵜ:",
        "deleteotherreason": "Ca n ssebba nniḍn:",
        "deletereasonotherlist": "Ssebba nniḍn",
        "rollbacklink": "Sdwl ghar dffar",
        "whatlinkshere-hidelinks": "$1 timqqan",
        "blockip": "Sbdd asqdac a",
        "ipbreason": "ⵜⴰⵎⵏⵜⵉⵍⵜ:",
-       "ipboptions": "2 n timirin:2 hours,1 n wass:1 day,3 n wussan:3 days,1 imalass:1 week,2 imallassn:2 weeks,1 wayur:1 month,3 wayurn:3 months,6 wayurn:6 months,1 asggwas:1 year,tartalla:infinite",
+       "ipboptions": "2 ⵜⵙⵔⴰⴳⵉⵏ:2 hours,1 ⵡⴰⵙⵙ:1 day,3 ⵡⵓⵙⵙⴰⵏ:3 days,1 ⵉⵎⴰⵍⴰⵙⵙ:1 week,2 ⵉⵎⴰⵍⴰⵙⵙⵏ:2 weeks,1 ⵡⴰⵢⵢⵓⵓⵔ:1 month,3 ⵡⴰⵢⵢⵓⵔⵏ:3 months,6 ⵡⴰⵢⵢⵓⵔⵏ:6 months,1 ⵓⵙⴳⴳⵯⴰⵙ:1 year,ⵍⴱⴷⴰ:infinite",
        "autoblocklist-submit": "ⵔⵣⵓ",
        "ipblocklist": "Tabdart n tansiwin IP d isemawen n iseqdacen ig iteblukan",
        "blocklist-reason": "ⵜⴰⵎⵏⵜⵉⵍⵜ",
index 46196c1..1161326 100644 (file)
        "fileduplicatesearch-noresults": "Betg chattà ina datoteca cun il num \"$1\".",
        "specialpages": "Paginas spezialas",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Paginas spezialas normalas.\n* <span class=\"mw-specialpagerestricted\">Paginas spezialas restrenschidas.</span>",
        "specialpages-group-maintenance": "Rapports da mantegnamant",
        "specialpages-group-other": "Autras paginas spezialas",
        "specialpages-group-login": "S'annunziar / crear in conto",
index 55d3bdb..a24c6c2 100644 (file)
        "rcfilters-noresults-conflict": "Nu s-au găsit rezultate deoarece criteriile de căutare sunt în conflict",
        "rcfilters-state-message-subset": "Acest filtru nu are efecte deoarece rezultatele sale sunt incluse în filtrele cu selectie mai largă {{PLURAL:$2|filtru|filtre}} (încercați să subliniați pentru a o deosebi): $1",
        "rcfilters-state-message-fullcoverage": "Selectarea tuturor filtrelor dintr-un grup este aceeași cu cea selectată, astfel încât acest filtru nu are efect. Grupul include: $1",
-       "rcfilters-filtergroup-registration": "Înregistrare utilizator",
-       "rcfilters-filter-registered-label": "Înregistrat",
-       "rcfilters-filter-registered-description": "Editorii conectați.",
-       "rcfilters-filter-unregistered-label": "Neînregistrat",
-       "rcfilters-filter-unregistered-description": "Editorii care nu sunt conectați.",
-       "rcfilters-filter-unregistered-conflicts-user-experience-level": "Acest filtru contravine {{PLURAL:$2|filtru|filtre}} de Experiență, care {{PLURAL:$2|găsesc|gasește}} doar userii inreistrați: $1",
        "rcfilters-filtergroup-authorship": "Contribuția autorului",
        "rcfilters-filter-editsbyself-label": "Modificările tale",
        "rcfilters-filter-editsbyself-description": "Contribuțiile tale.",
        "rcfilters-filter-editsbyother-label": "Contribuțiile altora",
        "rcfilters-filter-editsbyother-description": "Toate modificările mai puțin ale tale.",
        "rcfilters-filtergroup-userExpLevel": "Nivel de experiență (numai pentru utilizatorii înregistrați)",
-       "rcfilters-filtergroup-user-experience-level-conflicts-unregistered": "Filtrele Experiență găsesc numai utilizatori înregistrați, deci acest filtru este în conflict cu filtrul \"Înregistrat\".",
-       "rcfilters-filtergroup-user-experience-level-conflicts-unregistered-global": "Filtrul \"Înregistrat\" ​​este în conflict cu unul sau mai multe filtre Experiență, care găsește numai utilizatorii înregistrați. Filtrele conflictuale sunt marcate în zona Filtre active, de mai sus.",
+       "rcfilters-filter-user-experience-level-registered-label": "Înregistrat",
+       "rcfilters-filter-user-experience-level-registered-description": "Editorii conectați.",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Neînregistrat",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Editorii care nu sunt conectați.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Nou veniți",
        "rcfilters-filter-user-experience-level-newcomer-description": "Mai puțin de 10 editări și 4 zile de activitate.",
        "rcfilters-filter-user-experience-level-learner-label": "Cursanți",
        "fileduplicatesearch-noresults": "Nu s-a găsit niciun fișier cu numele „$1”.",
        "specialpages": "Pagini speciale",
        "specialpages-note-top": "Legendă",
-       "specialpages-note": "* Pagini speciale normale.\n* <span class=\"mw-specialpagerestricted\">Pagini speciale restricționate.</span>",
        "specialpages-group-maintenance": "Întreținere",
        "specialpages-group-other": "Alte pagini speciale",
        "specialpages-group-login": "Autentificare / creare cont",
        "logentry-delete-delete": "$1 {{GENDER:$2|a șters}} pagina $3",
        "logentry-delete-delete_redir": "$1 {{GENDER:$2|a șters}} pagina de redirecționare $3 prin suprascriere",
        "logentry-delete-restore": "$1 {{GENDER:$2|a restaurat}} pagina $3 ($4)",
-       "restore-count-files": "{{PLURAL:$1|1 fișier|$1 fișiere}}",
+       "restore-count-revisions": "{{PLURAL:$1|1 versiune|$1 versiuni|$1 de versiuni}}",
+       "restore-count-files": "{{PLURAL:$1|1 fișier|$1 fișiere|$1 de fișiere}}",
        "logentry-delete-event": "$1 {{GENDER:$2|a schimbat}} vizibilitatea {{PLURAL:$5|unui eveniment din jurnal|a $5 evenimente din jurnal|a $5 de evenimente din jurnal}} pentru $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|a schimbat}} vizibilitatea {{PLURAL:$5|unei versiuni|a $5 versiuni|a $5 de versiuni}} pentru pagina $3: $4",
        "logentry-delete-event-legacy": "$1 {{GENDER:$2|a modificat}} vizibilitatea evenimentelor din jurnal pentru $3",
index fe2444a..41c9360 100644 (file)
        "changepassword-success": "'A password toje ha state cangiate!",
        "changepassword-throttled": "Tu è pruvate 'nu sacche de vote a trasè.\nPe piacere aspitte $1 apprime de pruvà arrete.",
        "botpasswords": "Password d'u bot",
+       "botpasswords-existing": "Passuord de le bot esistende",
+       "botpasswords-createnew": "Ccreje 'na passuord nove pu bot",
+       "botpasswords-editexisting": "Cange 'na passuord d'u bot ca esiste ggià",
        "botpasswords-label-appid": "Nome d'u bot:",
        "botpasswords-label-create": "Ccreje",
        "botpasswords-label-update": "Aggiorne",
        "botpasswords-updated-title": "Passuord d'u bot cangiate",
        "botpasswords-deleted-title": "Passuord d'u bot scangellate",
        "resetpass_forbidden": "Le Password non ge ponne cangià",
+       "resetpass_forbidden-reason": "Le passuord non ge ponne essere cangiate: $1",
        "resetpass-no-info": "Tu a essere colleghete pe accedere a sta pàgene direttamende.",
        "resetpass-submit-loggedin": "Cange 'a password",
        "resetpass-submit-cancel": "Annulle",
        "minoredit": "Cangiaminde stuèdeche",
        "watchthis": "Condrolle sta pàgene",
        "savearticle": "Registre 'a vôsce",
+       "savechanges": "Reggistre le cangiaminde",
+       "publishpage": "Pubbleche 'a pàgene",
+       "publishchanges": "Pubbleche le cangiaminde",
        "preview": "Andeprime",
        "showpreview": "Vide l'andeprime",
        "showdiff": "Fa vedè le cangiaminde",
        "invalid-content-data": "Condenute d'u date invalide",
        "content-not-allowed-here": "\"$1\" condenute non g'è permesse sus 'a pàgene [[$2]]",
        "editwarning-warning": "Assenne da sta pàgene tu puè perdè tutte le date ca è cangiate.\nCe tu è trasute, tu puè disabbilità st'avvertimende jndr'à sezione \"{{int:prefs-editing}}\" de le preferenze tune.",
+       "editpage-invalidcontentmodel-title": "'U Modelle d'u condenute non gè supportate",
+       "editpage-invalidcontentmodel-text": "'U modelle d'u condenute \"$1\" non g'è supportate.",
        "editpage-notsupportedcontentformat-title": "'U formate d'u condenute non gè supportate",
        "editpage-notsupportedcontentformat-text": "'U formate d'u condenute $1 non g'è supportate da 'u modelle de condenute $2.",
        "content-model-wikitext": "Uicchiteste",
        "grant-group-file-interaction": "Inderaggisce cu le media",
        "grant-group-watchlist-interaction": "Inderaggisce cu le pàggene condrollate",
        "grant-group-email": "Manne 'n'e-mail",
+       "grant-createaccount": "Ccreje le cunde utinde",
+       "grant-createeditmovepage": "Ccreje, cange e spueste le pàggene",
+       "grant-delete": "Scangille pàggene, revisiune e vôsce de l'archivije",
        "newuserlogpage": "Archivije de ccreazione de le utinde",
        "newuserlogpagetext": "Quiste ète l'archivije de le creazziune de l'utinde.",
        "rightslog": "Archivie de le diritte de l'utende",
        "recentchanges-submit": "Fà 'ndrucà",
        "rcfilters-activefilters": "Filtre attive",
        "rcfilters-advancedfilters": "Filtre avanzate",
+       "rcfilters-limit-title": "Cangiaminde da 'ndrucà",
+       "rcfilters-limit-shownum": "Fà 'ndrucà le urteme $1 cangiaminde",
+       "rcfilters-days-title": "Urteme sciurne",
+       "rcfilters-hours-title": "Urteme ore",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|sciurne}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|ore}}",
        "rcfilters-quickfilters": "Filtre reggistrate",
        "rcfilters-quickfilters-placeholder-title": "Nisciune collegamende reggistrate",
+       "rcfilters-savedqueries-defaultlabel": "Filtre reggistrate",
+       "rcfilters-savedqueries-rename": "Renomene",
+       "rcfilters-savedqueries-setdefault": "'Mboste cumme predefinite",
+       "rcfilters-savedqueries-unsetdefault": "Live cumme predefinite",
+       "rcfilters-savedqueries-remove": "Live",
+       "rcfilters-savedqueries-new-name-label": "Nome",
+       "rcfilters-savedqueries-new-name-placeholder": "Dì a ce serve 'u filtre",
        "rcfilters-savedqueries-apply-label": "Ccrèje 'nu filtre",
+       "rcfilters-savedqueries-cancel-label": "Annulle",
+       "rcfilters-filterlist-title": "Filtre",
+       "rcfilters-filterlist-whatsthis": "Cumme funzionane?",
+       "rcfilters-highlightmenu-title": "Scacchie 'nu culore",
+       "rcfilters-highlightmenu-help": "Scacchie 'nu culore pe evidenzià sta probbietà",
+       "rcfilters-filterlist-noresults": "Nisciune filtre acchiate",
+       "rcfilters-filter-bots-label": "Bot",
+       "rcfilters-filter-patrolled-label": "Condrollate",
+       "rcfilters-filter-patrolled-description": "Cangiaminde signate cumme condrollate.",
+       "rcfilters-filter-unpatrolled-label": "Non condrollate",
+       "rcfilters-filter-unpatrolled-description": "Cangiaminde non signate cumme condrollate.",
+       "rcfilters-filtergroup-significance": "Significate",
+       "rcfilters-filter-minor-label": "Cangiaminde stuèdeche",
        "rcnotefrom": "Sotte {{PLURAL:$5|ste 'u cangiamende|stonne le cangiaminde}} da <strong>$3, $4</strong> ('nzigne a <strong>$1</strong> fatte vedè).",
        "rclistfrom": "Fà vedè le urteme cangiaminde partenne da $3 $2",
        "rcshowhideminor": "$1 cangiaminde stuèdeche",
        "rcshowhidemine": "$1 cangiaminde mie",
        "rcshowhidemine-show": "Fà vedè",
        "rcshowhidemine-hide": "Scunne",
+       "rcshowhidecategorization-show": "Fà 'ndrucà",
+       "rcshowhidecategorization-hide": "Scunne",
        "rclinks": "Vide l'urteme $1 cangiaminde jndr'à l'urteme $2 sciurne",
        "diff": "diff",
        "hist": "cunde",
        "mostrevisions": "Pàggene cchiù cangete",
        "prefixindex": "Tutte le pàggene cu 'u prefisse",
        "prefixindex-namespace": "Tutte le pàggene cu 'u prefisse ($1 namespace)",
+       "prefixindex-submit": "Fà 'ndrucà",
        "prefixindex-strip": "Strisce d'u prefisse jndr'à l'elenghe",
        "shortpages": "Pàggene corte",
        "longpages": "Pàggene longhe",
        "protectedpages-performer": "Stoche a protegge l'utende",
        "protectedpages-params": "Parametre de protezzione",
        "protectedpages-reason": "Mutive",
+       "protectedpages-submit": "Fà 'ndrucà le pàggene",
        "protectedpages-unknown-timestamp": "Scanusciute",
        "protectedpages-unknown-performer": "Utende scanusciute",
        "protectedtitles": "Titele prutette",
        "protectedtitles-summary": "Sta pàgene elenghe le titole ca so prutette da 'a ccrejazzione. Pe 'n'elenghe de le pàggene ca sò prutette, 'ndruche [[{{#special:ProtectedPages}}|{{int:protectedpages}}]].",
        "protectedtitlesempty": "Nisciune titele jè pe mò prutette cu ste parametre.",
+       "protectedtitles-submit": "Fà 'ndrucà le titole",
        "listusers": "Liste de l'utende",
        "listusers-editsonly": "Fà vedè sulamende l'utinde cu cangiaminde fatte",
        "listusers-creationsort": "Arrenghete pe date de ccreazione",
        "usereditcount": "$1 {{PLURAL:$1|cangiamende|cangiaminde}}",
        "usercreated": "{{GENDER:$3|Ccrejate}} 'u $1 a le ore $2",
        "newpages": "Pàggene nuève",
+       "newpages-submit": "Fà 'ndrucà",
        "newpages-username": "Nome de l'utende:",
        "ancientpages": "Pàggene vìcchje",
        "move": "Spuèste",
        "apisandbox-intro": "Ause sta pàgene pe sperimendà cu le <strong>API de le web service pe MediaUicchi</strong>.\nFà referimende a [[mw:API:Main page| 'a documendazione de l'API]] pe cchiù dettaglie de l'ause de l'API.\nEsembie: [https://www.mediawiki.org/wiki/API#A_simple_example pigghie 'u condenute d'a Pàgene Prengepàle]. Scacchie 'n'azione pe 'ndrucà otre esembie.\n\nVide ca, pure ca queste jè 'na buatte de sabbie tu puè carrescià le cangiaminde de sta pàgene sus 'a uicchi.",
        "apisandbox-submit": "Fà 'na richieste",
        "apisandbox-reset": "Pulizze",
+       "apisandbox-retry": "Pruève arrete",
        "apisandbox-examples": "Esembie",
        "apisandbox-results": "Resultate",
        "apisandbox-request-url-label": "URL richieste:",
        "apisandbox-request-time": "Tiembe cercate: {{PLURAL:$1|$1 ms}}",
+       "apisandbox-continue": "Condinue",
+       "apisandbox-continue-clear": "Pulizze",
        "booksources": "Sorgende de le libbre",
        "booksources-search-legend": "Cirche pe le fonde de le libbre",
        "booksources-isbn": "ISBN:",
        "fileduplicatesearch-noresults": "Nisciune file chiamate \"$1\" ha state acchiate.",
        "specialpages": "Pàggene speciele",
        "specialpages-note-top": "Leggende",
-       "specialpages-note": "* Pàggene speciale normale.\n* <span class=\"mw-specialpagerestricted\">Pàggene speciale cu le restriziune.</span>",
        "specialpages-group-maintenance": "Report d'a manutenzione",
        "specialpages-group-other": "Otre pàggene speciele",
        "specialpages-group-login": "Tràse / Reggistrate",
index 6606815..f712a14 100644 (file)
        "rcfilters-legend-heading": "<strong>Список сокращений:</strong>",
        "rcfilters-activefilters": "Активные фильтры",
        "rcfilters-advancedfilters": "Расширенные фильтры",
+       "rcfilters-limit-title": "Изменения для показа",
+       "rcfilters-days-title": "Последние дни",
+       "rcfilters-hours-title": "Последние часы",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|день|дня|дней}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|час|часа|часов}}",
        "rcfilters-quickfilters": "Сохранённые фильтры",
        "rcfilters-quickfilters-placeholder-title": "Сохраненных ссылок еще нет",
        "rcfilters-quickfilters-placeholder-description": "Чтобы сохранить настройки фильтра и повторно использовать их позже, щелкните значок закладки в области «Активный фильтр» ниже.",
        "rcfilters-invalid-filter": "Недопустимый фильтр",
        "rcfilters-empty-filter": "Нет активных фильтров. Показываются все правки.",
        "rcfilters-filterlist-title": "Фильтры",
-       "rcfilters-filterlist-whatsthis": "ЧÑ\82о Ñ\8dÑ\82о?",
+       "rcfilters-filterlist-whatsthis": "Ð\9aак Ñ\8dÑ\82о Ñ\80абоÑ\82аеÑ\82?",
        "rcfilters-filterlist-feedbacklink": "Оставить отзыв о новых (бета) фильтрах",
        "rcfilters-highlightbutton-title": "Выделить результаты",
        "rcfilters-highlightmenu-title": "Выберите цвет",
        "rcfilters-filter-editsbyself-description": "Ваш вклад.",
        "rcfilters-filter-editsbyother-label": "Изменения, внесённые другими участниками",
        "rcfilters-filter-editsbyother-description": "Все правки, кроме ваших собственных.",
-       "rcfilters-filtergroup-userExpLevel": "УÑ\80овнÑ\8f Ð¾Ð¿Ñ\8bÑ\82а (Ñ\82олÑ\8cко Ð´Ð»Ñ\8f Ð·Ð°Ñ\80егиÑ\81Ñ\82Ñ\80иÑ\80ованнÑ\8bÑ\85 Ñ\83Ñ\87аÑ\81Ñ\82ников)",
+       "rcfilters-filtergroup-userExpLevel": "РегиÑ\81Ñ\82Ñ\80аÑ\86иÑ\8f Ñ\83Ñ\87аÑ\81Ñ\82ника Ð¸ ÐµÐ³Ð¾ Ð¾Ð¿Ñ\8bÑ\82",
        "rcfilters-filter-user-experience-level-registered-label": "Зарегистрированные",
        "rcfilters-filter-user-experience-level-registered-description": "Вошедшие редакторы.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Незарегистрированные",
        "rcfilters-filter-user-experience-level-unregistered-description": "Редакторы, которые не вошли в систему.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Новички",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Ð\9cенее 10 Ð¿Ñ\80авок Ð¸ 4 Ð´Ð½ÐµÐ¹ работы.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Ð\97аÑ\80егиÑ\81Ñ\82Ñ\80иÑ\80ованнÑ\8bе Ñ\80едакÑ\82оÑ\80Ñ\8b Ñ\81 Ð¼ÐµÐ½ÐµÐµ Ñ\87ем 10 Ð¿Ñ\80авками Ð¸ 4 Ð´Ð½Ñ\8fми работы.",
        "rcfilters-filter-user-experience-level-learner-label": "Учащиеся",
-       "rcfilters-filter-user-experience-level-learner-description": "Ð\91олÑ\8cÑ\88е Ð¾Ð¿Ñ\8bÑ\82а, Ñ\87ем Ñ\83 Â«Ð\9dовиÑ\87ков», Ð½Ð¾ Ð¼ÐµÐ½Ñ\8cÑ\88е, Ñ\87ем Ñ\83 Â«Ð\9eпÑ\8bÑ\82нÑ\8bÑ\85 Ð¿Ð¾Ð»Ñ\8cзоваÑ\82елей».",
+       "rcfilters-filter-user-experience-level-learner-description": "Ð\97аÑ\80егиÑ\81Ñ\82Ñ\80иÑ\80ованнÑ\8bе Ñ\80едакÑ\82оÑ\80Ñ\8b, Ñ\87ей Ð¾Ð¿Ñ\8bÑ\82 Ð½Ð°Ñ\85одиÑ\82Ñ\81Ñ\8f Ð³Ð´Ðµ-Ñ\82о Ð¼ÐµÐ¶Ð´Ñ\83 Ñ\83Ñ\80овнÑ\8fми Â«Ð\9dовиÑ\87ок» Ð¸ Â«Ð\9eпÑ\8bÑ\82нÑ\8bе Ð¿Ð¾Ð»Ñ\8cзоваÑ\82ели».",
        "rcfilters-filter-user-experience-level-experienced-label": "Опытные пользователи",
-       "rcfilters-filter-user-experience-level-experienced-description": "Ð\91олее 30 Ð´Ð½ÐµÐ¹ Ð°ÐºÑ\82ивноÑ\81Ñ\82и Ð¸ 500 Ð¿Ñ\80авок.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Ð\97аÑ\80егиÑ\81Ñ\82Ñ\80иÑ\80ованнÑ\8bе Ñ\80едакÑ\82оÑ\80Ñ\8b Ñ\81 Ð±Ð¾Ð»ÐµÐµ Ñ\87ем 500 Ð¿Ñ\80авок Ð¸ 30 Ð´Ð½Ñ\8fми Ð°ÐºÑ\82ивноÑ\81Ñ\82и.",
        "rcfilters-filtergroup-automated": "Автоматизированные вклады",
        "rcfilters-filter-bots-label": "Бот",
        "rcfilters-filter-bots-description": "Правки, сделанные с помощью автоматизированных инструментов.",
        "rcfilters-hideminor-conflicts-typeofchange-global": "Фильтр \"малые правки\" конфликтует с одним или несколькими фильтрами, поскольку некоторые типы правок не могут быть названы малыми. Конфликтные фильтры отмечены вверху, в области Активных фильтров.",
        "rcfilters-hideminor-conflicts-typeofchange": "Определённые типы правок не могут быть названы «малыми», поэтому этот фильтр конфликтует со следующим фильтром типа правок: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Этот фильтр типа правок конфликтует с фильтром малых правок. Определённые типы правок не могут быть отмечены «малыми».",
-       "rcfilters-filtergroup-lastRevision": "ТекÑ\83Ñ\89аÑ\8f Ð²ÐµÑ\80Ñ\81иÑ\8f",
+       "rcfilters-filtergroup-lastRevision": "Ð\9fоÑ\81ледние Ð²ÐµÑ\80Ñ\81ии",
        "rcfilters-filter-lastrevision-label": "Текущая версия",
-       "rcfilters-filter-lastrevision-description": "Самое последнее изменение на странице.",
-       "rcfilters-filter-previousrevision-label": "Ð\91олее Ñ\80анние Ð²ÐµÑ\80Ñ\81ии",
-       "rcfilters-filter-previousrevision-description": "Все правки, не являющиеся самыми последними на странице.",
+       "rcfilters-filter-lastrevision-description": "ТолÑ\8cко Ñ\81амое последнее изменение на странице.",
+       "rcfilters-filter-previousrevision-label": "Ð\9dе Ð¿Ð¾Ñ\81леднÑ\8fÑ\8f Ð²ÐµÑ\80Ñ\81иÑ\8f",
+       "rcfilters-filter-previousrevision-description": "Все правки, не являющиеся «последней версией».",
        "rcfilters-filter-excluded": "Исключено",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:not</strong> $1",
+       "rcfilters-exclude-button-off": "Исключить выбранное",
+       "rcfilters-exclude-button-on": "Исключение выбранного",
        "rcfilters-view-tags": "Тегированные правки",
        "rcfilters-view-namespaces-tooltip": "Результаты фильтра по пространствам имён",
        "rcfilters-view-tags-tooltip": "Результаты фильтра, использующего метки правок",
        "fileduplicatesearch-noresults": "Не найден файл с именем «$1».",
        "specialpages": "Спецстраницы",
        "specialpages-note-top": "Легенда",
-       "specialpages-note": "* Обычные служебные страницы.\n* <span class=\"mw-specialpagerestricted\">Служебные страницы с ограниченным доступом.</span>",
        "specialpages-group-maintenance": "Отчёты технического обслуживания",
        "specialpages-group-other": "Другие служебные страницы",
        "specialpages-group-login": "Представиться / Зарегистрироваться",
index 010223b..907e6c7 100644 (file)
        "anontalk": "بحث",
        "navigation": "رھنمائي",
        "and": "&#32؛۽",
-       "qbfind": "ڳوليو",
-       "qbbrowse": "جھانگيو",
-       "qbedit": "سنواريو",
-       "qbpageoptions": "هيءُ صفحو",
-       "qbmyoptions": "منهنجا صفحا",
        "faq": "ڪپس",
-       "faqpage": "Project:ڪپس",
        "actions": "ڪارگذاريون",
        "namespaces": "نانءُپولارَ",
        "variants": "بَدَلَ",
        "edit-local": "مقامي تشريح کي ترميميو",
        "create": "سرجيو",
        "create-local": "مقامي تشريح ڏيو",
-       "editthispage": "هيءُ صفحو سنواريو",
-       "create-this-page": "اهو صفحو نئين سر جوڙيو",
        "delete": "ڊاھيو",
-       "deletethispage": "هيءُ صفحو ڊاهيو",
-       "undeletethispage": "هيءُ صفحو اڻ ڊاهيو",
        "undelete_short": "اڻڊاهيو {{PLURAL:$1|هڪ ترميم|$1 ترميمون}}",
        "viewdeleted_short": "ڏسو {{PLURAL:$1|هڪ ڊاٺل ترميم|$1 ڊاٺل ترميمون}}",
        "protect": "تحفظيو",
        "protect_change": "تبديل ڪريو",
-       "protectthispage": "هيءُ صفحو تحفظيو",
        "unprotect": "تحفظ بدلايو",
-       "unprotectthispage": "هن صفحي جو تحفظ بدلايو",
        "newpage": "نئون صفحو",
-       "talkpage": "هن صفحي تي بحث ڪريو",
        "talkpagelinktext": "بحث",
        "specialpage": "خاص صفحو",
        "personaltools": "ذاتي اوزار",
-       "articlepage": "مسودو ڏسو",
        "talk": "بحث",
        "views": "ڏيٺون",
        "toolbox": "اوزارَ",
        "tool-link-userrights": "{{GENDER:$1|يوزر}} گروھ تبديل ڪريو",
        "tool-link-userrights-readonly": "{{GENDER:$1|يوزر}} گروھ ڏسو",
        "tool-link-emailuser": "ھن {{GENDER:$1|يوزر}} ڏانھن برقٽپال موڪليو",
-       "userpage": "يوزر صفحو ڏسو",
-       "projectpage": "رٿائي صفحو ڏسو",
        "imagepage": "ذريعاتي صفحو ڏسو",
        "mediawikipage": "نياپي جو صفحو ڏسو",
        "templatepage": "سانچي جو صفحو ڏسو",
        "rcfilters-search-placeholder": "تازيون تبديليون ڇاڻيو (جھانگيو يا لکڻ شروع ڪريو)",
        "rcfilters-empty-filter": "ڪي بہ سرگرم ڇاڻيون ناھن. سڀ ڀاڱيداريون ڏيکاريل آھن.",
        "rcfilters-filterlist-title": "ڇاڻيون",
-       "rcfilters-filterlist-whatsthis": "Ù\87Ù\8a Ú\87ا Ø¢Ù\87Ù\8a؟",
+       "rcfilters-filterlist-whatsthis": "Ù\87Ù\8a ÚªÙ\8aئÙ\86 ÚªÙ\85 ÚªÙ\86 Ù¿Ø§؟",
        "rcfilters-highlightbutton-title": "نتيجن کي نمايان (هاءِ لائيٽ) ڪيو",
        "rcfilters-highlightmenu-title": "رنگ چونڊيو",
-       "rcfilters-filter-registered-label": "رجسٽر ٿيل",
-       "rcfilters-filter-unregistered-label": "اڻ رجسٽر ٿيل",
        "rcfilters-filter-editsbyself-label": "مون پاران تبديليون",
        "rcfilters-filter-editsbyother-label": "ٻين پاران تبديليون",
+       "rcfilters-filter-user-experience-level-registered-label": "رجسٽر ٿيل",
+       "rcfilters-filter-user-experience-level-unregistered-label": "اڻرجسٽر ٿيل",
        "rcfilters-filter-user-experience-level-newcomer-label": "نوان ايندڙ",
        "rcfilters-filter-user-experience-level-learner-label": "سکندڙ",
        "rcfilters-filter-user-experience-level-experienced-label": "تجربيڪار واھپ",
        "tooltip-compareselectedversions": "هن صفحي جن ٻن چونڊيل پرتن درميان تفاوت ڏسو.",
        "tooltip-watch": "هيءُ صفحو پنهنجي نظر ۾ فھرست ۾ شامل ڪريو",
        "tooltip-rollback": "\"واپس ورايو\" ھن صفحي ۾ پوئين ڀاڱيدار جي ڪيل ترميم(ن) کي ھڪ ٽڙڪ سان اڻڪري ٿو",
+       "tooltip-preferences-save": "ترجيحون سانڍيو",
        "tooltip-summary": "ننڍو خلاصو ڏيو",
        "anonymous": "گمنام {{PLURAL:$1|يوزر|يوزرس}} جو {{SITENAME}}",
        "simpleantispam-label": "اينٽي-اسپام روڪ.\nھن کي <strong>نہ</strong> ڀريو!",
index 368e648..824851e 100644 (file)
        "retrievedfrom": "Yurrid z \"$1\"",
        "youhavenewmessages": "{{PLURAL:$3|{{GENDER:$3|ⴷⴰⵔⴽ|ⴷⴰⵔⵎ}}}} $1 ($2).",
        "newmessageslinkplural": "{{PLURAL:$1|ⵜⵓⵣⵉⵏⵜ ⵜⴰⵎⴰⵢⵏⵓⵜ|ⵜⵓⵣⵉⵏⵉⵏ ⵜⵉⵎⴰⵢⵏⵓⵜⵉⵏ}}",
-       "newmessagesdifflinkplural": "{{PLURAL:$1|â´°âµ\99âµ\8fâ´¼âµ\8d âµ\89ⴳⴳⵯâµ\94â´°âµ\8f|âµ\89âµ\99âµ\8fâ´¼ⵍⵏ ⴳⴳⵯⵔⴰⵏⵉⵏ}}",
+       "newmessagesdifflinkplural": "{{PLURAL:$1|âµ\93âµ\99âµ\8fâ´¼âµ\8d âµ\89ⴳⴳⵯâµ\94â´°âµ\8f|âµ\89âµ\99âµ\8fâ´¼âµ\89ⵍⵏ ⴳⴳⵯⵔⴰⵏⵉⵏ}}",
        "youhavenewmessagesmulti": "{{GENDER:|ⴷⴰⵔⴽ|ⴷⴰⵔⵎ}} ⵜⵓⵣⵉⵏⵉⵏ ⵜⵉⵎⴰⵢⵏⵓⵜⵉⵏ ⴳ $1",
        "editsection": "ⵙⵏⴼⵍ",
        "editold": "ⵙⵏⴼⵍ",
        "notloggedin": "Ur tmlit mat git",
        "createaccount": "Murzm amidan nek (lkunt)..",
        "createaccountmail": "S tirawt taliktunant",
-       "createacct-benefit-body1": "{{PLURAL:$1|ⴰⵙⵏⴼⵍ|ⵉⵙⵏⴼⵍⵏ}}",
+       "createacct-benefit-body1": "{{PLURAL:$1|â´°âµ\99âµ\8fâ´¼âµ\8d|âµ\89âµ\99âµ\8fâ´¼âµ\89âµ\8dâµ\8f}}",
        "createacct-benefit-body2": "{{PLURAL:$1|ⵜⴰⵙⵏⴰ|ⵜⴰⵙⵏⵉⵡⵉⵏ}}",
        "createacct-benefit-body3": "{{PLURAL:$1|ⴰⵏⴰⵎⵓ ⵉⴳⴳⵯⵔⴰⵏ|ⵉⵏⴰⵎⵓⵜⵏ ⴳⴳⵯⵔⴰⵏⵉⵏ}}",
        "badretype": "Tasarut lin tgit ur dis tucka.",
        "action-delete": "ⴽⴽⵙ ⵜⴰⵙⵏⴰ ⴰⴷ",
        "nchanges": "$1 {{PLURAL:$1|ⵓⵙⵏⴼⵍ|ⵉⵙⵏⴼⵍⵏ}}",
        "enhancedrc-history": "ⴰⵎⵣⵔⵓⵢ",
-       "recentchanges": "ⵉⵙⵏⴼⵍⵏ ⴳⴳⵯⵔⴰⵏⵉⵏ",
+       "recentchanges": "âµ\89âµ\99âµ\8fâ´¼âµ\89âµ\8dâµ\8f â´³â´³âµ¯âµ\94â´°âµ\8fâµ\89âµ\8f",
        "recentchanges-legend": "Tixtiɣitin (options) n imbddl imaynutn",
        "recentchanges-summary": "Ml imbddln imaynutn  n wiki ɣ tasna yad",
        "recentchanges-feed-description": "ⴹⴼⵓⵔ ⵉⵙⵏⴼⵍⵏ ⴰⴽⴽⵯ ⵉⴳⴳⵯⵔⴰⵏ ⵏ ⵓⵡⵉⴽⵉ ⴳ ⵉⴼⵉⵍⵉ ⴰⴷ.",
        "recentchanges-label-minor": "ⵡⴰⴷ ⵉⴳⴰ ⴰⵙⵏⴼⵍ ⵓⵎⵥⵉⵢ",
        "recentchanges-label-bot": "ⴰⵙⵏⴼⵍ ⴰⴷ ⵉⵙⴽⵔ ⵜ ⵢⴰⵏ ⵓⵔⵓⴱⵓ",
        "recentchanges-label-unpatrolled": "Ambddl ad ura jju ittmẓra",
+       "recentchanges-label-plusminus": "ⵜⵏⴼⵍ ⵜⵉⴷⴷⵉ ⵏ ⵜⴰⵙⵏⴰ ⵙ ⵡⵓⵟⵟⵓⵏ ⴰⴷ ⵏ ⵉⴷ ⴱⴰⵢⵜ",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (ⵥⵔ ⵓⵍⴰ [[Special:NewPages|ⵜⴰⵍⴳⴰⵎⵜ ⵏ ⵜⴰⵙⵏⵉⵡⵉⵏ ⵜⵉⵎⴰⵢⵏⵓⵜⵉⵏ]])",
        "rcfilters-savedqueries-new-name-label": "ⵉⵙⵎ",
        "rcfilters-filterlist-whatsthis": "ⵎⴰⵜⵜⴰ ⵓⵢⴰ?",
        "rcfilters-filter-bots-label": "ⴱⵓⵜ",
        "rcnotefrom": "Had imbddln lli ittuyskarn z '''$2''' ('''$1''' ɣ uggar).",
        "rclistfrom": "Mel imbdeltn imaynutn z $3 $2",
-       "rcshowhideminor": "$1 ⵉⵙⵏⴼⵍⵏ ⵓⵎⵥⵉⵢⵏ",
+       "rcshowhideminor": "$1 âµ\89âµ\99âµ\8fâ´¼âµ\89âµ\8dâµ\8f âµ\93âµ\8eâµ¥âµ\89âµ¢âµ\8f",
        "rcshowhideminor-hide": "ⵙⵙⵏⵜⵍ",
        "rcshowhidebots": "$1 ⵉⴷ ⴱⵓⵜ",
        "rcshowhidebots-hide": "ⵙⵙⵏⵜⵍ",
        "recentchangeslinked": "Imbddel zun ɣwid",
        "recentchangeslinked-feed": "Imbddeln zund ɣwid",
        "recentchangeslinked-toolbox": "Imbddeln zund ɣwid",
-       "recentchangeslinked-title": "ⵉⵙⵏⴼⵍⵏ ⵇⵇⵏⵏⵉⵏ ⵙ \"$1\"",
+       "recentchangeslinked-title": "âµ\89âµ\99âµ\8fâ´¼âµ\89âµ\8dâµ\8f âµ\87âµ\87âµ\8fâµ\8fâµ\89âµ\8f âµ\99 \"$1\"",
        "recentchangeslinked-summary": "Ɣid umuɣ iymbddeln li ittyskarnin tigira yad ɣ tisniwin li ittuyzdayn d kra n tasna (ulla i igmamn n kra taggayt ittuyzlayn). Tisniwin  ɣ [[Special:Watchlist|Umuɣ n tisniwin li ttsaggat]].",
        "recentchangeslinked-page": "ⵉⵙⵎ ⵏ ⵜⴰⵙⵏⴰ:",
        "recentchangeslinked-to": "Afficher les changements vers les pages liées au lieu de la page donnée\nMel imbddeln z tisniwin li ittuyzdayni bla tasna li trit.",
        "listusers": "ⵜⴰⵍⴳⴰⵎⵜ ⵏ ⵉⵙⵎⵔⴰⵙⵏ",
        "usercreated": "{{GENDER:$3|ⵉⵙⵏⵓⵍⴼⴰ|ⵜⵙⵏⵓⵍⴼⴰ}} ⴳ $1 ⴳ $2",
        "newpages": "ⵜⴰⵙⵏⵉⵡⵉⵏ ⵜⵉⵎⴰⵢⵏⵓⵜⵉⵏ",
-       "move": "âµ\99âµ\8eâ´°ⵜⵜⵉ",
+       "move": "âµ\99âµ\8eâµ\93ⵜⵜⵉ",
        "movethispage": "ⵙⵎⴰⵜⵜⵉ ⵜⴰⵙⵏⴰ ⴰⴷ",
        "unusedcategoriestext": "Taggayin ad llant waxxa gis nt ur tlli kra n tasna wala kra n taggayin yaḍnin",
        "notargettitle": "F walu",
        "restriction-type": "ⵜⵓⵔⴰⴳⵜ:",
        "restriction-level": "Restriction level:",
        "restriction-edit": "ⵙⵏⴼⵍ",
-       "restriction-move": "âµ\99âµ\8eâ´°ⵜⵜⵉ",
+       "restriction-move": "âµ\99âµ\8eâµ\93ⵜⵜⵉ",
        "undeletelink": "mel/rard",
        "undeleteviewlink": "Ẓṛ",
        "undelete-search-submit": "ⵙⵉⴳⴳⵍ",
        "tooltip-ca-unprotect": "ⵙⵏⴼⵍ ⴰⴼⵔⴰⴳ ⵏ ⵜⴰⵙⵏⴰ ⴰⴷ",
        "tooltip-ca-delete": "ⴽⴽⵙ ⵜⴰⵙⵏⴰ ⴰⴷ",
        "tooltip-ca-undelete": "Rard imbddeln imzwura li ittyskarnin ɣ tasna yad",
-       "tooltip-ca-move": "âµ\99âµ\8eâ´°ⵜⵜⵉ ⵜⴰⵙⵏⴰ ⴰⴷ",
+       "tooltip-ca-move": "âµ\99âµ\8eâµ\93ⵜⵜⵉ ⵜⴰⵙⵏⴰ ⴰⴷ",
        "tooltip-ca-watch": "ⵔⵏⵓ ⵜⴰⵙⵏⴰ ⴰⴷ ⵉ ⵜⵍⴳⴰⵎⵜ {{GENDER:|ⵏⵏⴽ|ⵏⵏⵎ}} ⵏ ⵓⴹⴼⴼⵓⵔ",
        "tooltip-ca-unwatch": "ⵙⵉⵜⵜⵉ ⵜⴰⵙⵏⴰ ⴰⴷ ⵣⴳ ⵜⵍⴳⴰⵎⵜ {{GENDER:|ⵏⵏⴽ|ⵏⵏⵎ}} ⵏ ⵓⴹⴼⴼⵓⵔ",
        "tooltip-search": "ⵙⵉⴳⴳⵍ ⴳ {{SITENAME}}",
        "tooltip-n-mainpage-description": "Kid tasna tamuqrant",
        "tooltip-n-portal": "f' usenfar, matzdart atitskrt, maniɣrattaft ɣayli trit",
        "tooltip-n-currentevents": "Tiɣri izrbn i kullu maɣid immusn",
-       "tooltip-n-recentchanges": "ⵜⴰⵍⴳⴰⵎⵜ ⵏ ⵉⵙⵏⴼⵍⵏ ⴳⴳⵯⵔⴰⵏⵉⵏ ⴳ ⵓⵡⵉⴽⵉ",
+       "tooltip-n-recentchanges": "âµ\9câ´°âµ\8dⴳⴰâµ\8eâµ\9c âµ\8f âµ\89âµ\99âµ\8fâ´¼âµ\89âµ\8dâµ\8f â´³â´³âµ¯âµ\94â´°âµ\8fâµ\89âµ\8f â´³ âµ\93ⵡâµ\89â´½âµ\89",
        "tooltip-n-randompage": "Srbu yat tasna ɣik nna ka tga",
        "tooltip-n-help": "Adɣar n w-aws",
        "tooltip-t-whatlinkshere": "Umuɣ n kullu tisnatin n Wiki lid ilkkmn ɣid",
        "tags-active-no": "ⵓⵀⵓ",
        "tags-edit": "ⵙⵏⴼⵍ",
        "tags-delete": "ⴽⴽⵙ",
-       "tags-hitcount": "$1 {{PLURAL:$1|ⵓⵙⵏⴼⵍ|ⵉⵙⵏⴼⵍⵏ}}",
+       "tags-hitcount": "$1 {{PLURAL:$1|âµ\93âµ\99âµ\8fâ´¼âµ\8d|âµ\89âµ\99âµ\8fâ´¼âµ\89âµ\8dâµ\8f}}",
        "tags-create-submit": "ⵙⵏⵓⵍⴼⵓ",
        "comparepages": "ⵙⵎⵣⴰⵣⴰⵍ ⵜⴰⵙⵏⵉⵡⵉⵏ",
        "compare-page1": "ⵜⴰⵙⵏⴰ 1",
diff --git a/languages/i18n/skr-arab.json b/languages/i18n/skr-arab.json
new file mode 100644 (file)
index 0000000..575c764
--- /dev/null
@@ -0,0 +1,651 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Saraiki"
+               ]
+       },
+       "tog-underline": "لنک  ہیٹھ لکیر",
+       "tog-hideminor": "چھوٹیاں تبدیلیاں لُکاؤ",
+       "tog-numberheadings": "سرخیاں کوں خود کار نمبر ݙیوو",
+       "tog-showtoolbar": "آلات ترمیم ݙکھاؤ",
+       "underline-always": "ہمیشہ",
+       "underline-never": "کݙاہیں وی کائناں",
+       "underline-default": "سکن یا براؤزر دا طے شدہ",
+       "editfont-style": "خانہ ترمیم دا فونٹ",
+       "editfont-default": "براؤزر دا طے شدہ",
+       "editfont-monospace": "مونوسپیسڈ فونٹ",
+       "editfont-sansserif": "سنس سیرف فونٹ",
+       "editfont-serif": "سیرف فونٹ",
+       "sunday": "اتوار",
+       "monday": "سونوار",
+       "tuesday": "منگل",
+       "wednesday": "بدھ",
+       "thursday": "خمیس",
+       "friday": "جمعہ",
+       "saturday": "چھݨ چھݨ",
+       "sun": "اتوار",
+       "mon": "سونوار",
+       "tue": "منگل",
+       "wed": "بدھ",
+       "thu": "خمیس",
+       "fri": "جمعہ",
+       "sat": "چھݨ چھݨ",
+       "january": "جنوری",
+       "february": "فروری",
+       "march": "مارچ",
+       "april": "اپريل",
+       "may_long": "مئی",
+       "june": "جون",
+       "july": "جولائی",
+       "august": "اگست",
+       "september": "ستمبر",
+       "october": "اکتوبر",
+       "november": "نومبر",
+       "december": "دسمبر",
+       "january-gen": "جنوری",
+       "february-gen": "فروری",
+       "march-gen": "مارچ",
+       "april-gen": "اپريل",
+       "may-gen": "مئی",
+       "june-gen": "جون",
+       "july-gen": "جولائی",
+       "august-gen": "اگست",
+       "september-gen": "ستمبر",
+       "october-gen": "اکتوبر",
+       "november-gen": "نومبر",
+       "december-gen": "دسمبر",
+       "jan": "جنوری",
+       "feb": "فروری",
+       "mar": "مارچ",
+       "apr": "اپریل",
+       "may": "مئی",
+       "jun": "جون",
+       "jul": "جولائی",
+       "aug": "اگست",
+       "sep": "ستمبر",
+       "oct": "اکتوبر",
+       "nov": "نومبر",
+       "dec": "دسمبر",
+       "january-date": "$1 جنوری",
+       "february-date": "$1 فروری",
+       "march-date": "$1 مارچ",
+       "april-date": "$1 اپریل",
+       "may-date": "$1 مئی",
+       "june-date": "$1 جون",
+       "july-date": "$1 جولائی",
+       "august-date": "$1 اگست",
+       "september-date": "$1 ستمبر",
+       "october-date": "$1 اکتوبر",
+       "november-date": "$1 نومبر",
+       "december-date": "$1 دسمبر",
+       "period-am": "سویر",
+       "period-pm": "شام",
+       "pagecategories": "{{PLURAL:$1|زمرہ|زمرہ جات}}",
+       "category_header": "زمرہ \"$1\" وچ ورقے",
+       "subcategories": "ذیلی زمرہ جات",
+       "category-media-header": "زمرہ \"$1\" وچ میڈیا",
+       "hidden-categories": "{{PLURAL:$1|پوشیدہ زمرہ|پوشیدہ زمرہ جات}}",
+       "listingcontinuesabbrev": "جاری۔",
+       "noindex-category": "غیر فہرست شدہ صفحات",
+       "broken-file-category": "ٹٹے ہوۓ جوڑاں آلے صفحے",
+       "about": "تعارف",
+       "article": "مواد آلا ورقہ",
+       "newwindow": "(نویں ونڈو وچ کھولو)",
+       "cancel": "مکاؤ",
+       "moredotdotdot": "ٻئے",
+       "mypage": "ورقہ",
+       "mytalk": "ڳالھ مہاڑ",
+       "anontalk": "ڳالھ مہاڑ",
+       "navigation": "رہنمائی",
+       "and": "&#32;تے",
+       "faq": "عام طور تے پچھے ونڄݨ آلے سوال",
+       "actions": "کم",
+       "namespaces": "ناں دیاں جہاواں",
+       "variants": "قسماں",
+       "navigation-heading": "فہرست رہنمائی",
+       "errorpagetitle": "نقص",
+       "returnto": "واپس $1 چلو",
+       "tagline": " {{SITENAME}} توں",
+       "help": "مدد",
+       "search": "کھوج",
+       "searchbutton": "کھوج",
+       "go": "ڄلو",
+       "searcharticle": "ڄلو",
+       "history": "پچھلے کم",
+       "history_short": "تاریخچہ",
+       "history_small": "تاریخچہ",
+       "printableversion": "چھپݨ جوگا ورقہ",
+       "permalink": "پکا جوڑ",
+       "print": "چھاپو",
+       "view": "ݙکھالے",
+       "view-foreign": "$1 تے ݙیکھو",
+       "edit": "لکھو",
+       "create": "بݨاؤ",
+       "create-local": "آپنی لکھت رلاؤ",
+       "delete": "مٹاؤ",
+       "protect_change": "تبدیل کرو",
+       "newpage": "نواں ورقہ",
+       "talkpagelinktext": "ڳالھ مہاڑ",
+       "specialpage": "خاص ورقہ",
+       "personaltools": "ذاتی آوزار",
+       "talk": "ڳالھ مہاڑ",
+       "views": "ݙکھالے",
+       "toolbox": "آوزار",
+       "otherlanguages": "ٻنھاں زباناں وچ",
+       "redirectedfrom": "($1 کنوں ولدا رجوع )",
+       "redirectpagesub": "صفحہ ریڈائریکٹ کرو",
+       "redirectto": "اڳے کرو:",
+       "lastmodifiedat": "ایہ ورقہ چھیکڑی واری  $1 کوں $2 تے تبدیل تھیا ہائی۔",
+       "jumpto": "ٹپ مارو",
+       "jumptonavigation": "رہنمائی",
+       "jumptosearch": "ڳولو",
+       "aboutsite": "{{SITENAME}} دا تعارف",
+       "aboutpage": "Project:تعارف",
+       "copyrightpage": "{{ns:project}}:حقوق تصانیف",
+       "currentevents": "حالیہ واقعات",
+       "currentevents-url": "Project:حالیہ واقعات",
+       "disclaimers": "لاتعلقی اظہار",
+       "disclaimerpage": "Project:عام لاتعلقی اظہار",
+       "edithelp": "لکھݨ وچ مدد",
+       "helppage-top-gethelp": "مدد",
+       "mainpage": "وݙا ورقہ",
+       "mainpage-description": "پہلا ورقہ",
+       "portal": "بیٹھک",
+       "portal-url": "Project:دیوان عام",
+       "privacy": "پرائیویسی پالیسی",
+       "privacypage": "Project:پرائیویسی پالیسی",
+       "ok": "ٹھیک ہے",
+       "retrievedfrom": "\"$1\" توں گھدا",
+       "youhavenewmessages": "{{PLURAL:$3| تہاݙے کیتے}} $1 ($2).",
+       "newmessagesdifflinkplural": "چھیکڑی {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
+       "editsection": "لکھو",
+       "editold": "لکھو",
+       "viewsourceold": "ماخذ ݙیکھو",
+       "editlink": "لکھو",
+       "viewsourcelink": "ماخذ ݙیکھو",
+       "editsectionhint": "حصہ لکھو: $1",
+       "toc": "حصے",
+       "showtoc": "ݙیکھاؤ",
+       "hidetoc": "لُکاؤ",
+       "collapsible-collapse": "لکاؤ",
+       "collapsible-expand": "ودھاؤ",
+       "confirmable-yes": "ڄیا",
+       "confirmable-no": "کو",
+       "site-atom-feed": "$1 اٹوم فیڈ",
+       "page-atom-feed": "$1 اٹوم فیڈ",
+       "red-link-title": "$1 (ایہ ورقہ اڄݨ تائیں کائنی بݨیا)",
+       "nstab-main": "ورقہ",
+       "nstab-user": "صفحۂ صارف",
+       "nstab-special": "خاص ورقہ",
+       "nstab-project": "پروجیکٹ ورقہ",
+       "nstab-image": "فائل",
+       "nstab-mediawiki": "سنیہہ",
+       "nstab-template": "سانچہ",
+       "nstab-category": "زمرہ",
+       "mainpage-nstab": "وݙا ورقہ",
+       "nosuchspecialpage": "اینجھا کوئی خاص ورقہ کائنی",
+       "error": "نقص",
+       "databaseerror": "ڈیٹابیس دی غلطی",
+       "databaseerror-error": "نقص: $1",
+       "badtitle": "بھیڑا عنوان",
+       "viewsource": "ماخذ ݙیکھو",
+       "viewsource-title": "$1 دا مسودہ ݙیکھو",
+       "yourname": "صارف دا ناں",
+       "userlogin-yourname": "صارف ناں",
+       "userlogin-yourname-ph": "آپݨا ورتݨ ناں صارف درج کرو",
+       "createacct-another-username-ph": "آپݨا ورتݨ ناں صارف درج کرو",
+       "yourpassword": "پاس ورڈ",
+       "userlogin-yourpassword": "پاس ورڈ",
+       "userlogin-yourpassword-ph": "پاس ورڈ درج کرو",
+       "createacct-yourpassword-ph": "پاس ورڈ درج کرو",
+       "yourpasswordagain": "پاس ورڈ ولدا لکھو",
+       "createacct-yourpasswordagain": "پاس ورڈ دی تصدیق کرو",
+       "createacct-yourpasswordagain-ph": "پاس ورڈ ولدا درج کرو",
+       "userlogin-remembermypassword": "میکوں لاگ ان رکھو",
+       "userlogin-signwithsecure": "محفوظ رابطہ (کنکشن) استعمال کرو",
+       "cannotlogin-title": "لاگ ان نی تھی سڳدے",
+       "cannotlogin-text": "لاگ ان تھیوݨ ناممکن ہے",
+       "cannotloginnow-title": "ہݨ لاگ ان نہوے تھی سڳدے",
+       "login": "لاگ ان تھیوو",
+       "logout": "لاگ آؤٹ",
+       "userlogout": "لاگ آؤٹ",
+       "notloggedin": "لاگ ان نہوے تھئے",
+       "userlogin-noaccount": "تہاݙا کھاتہ کائنی؟",
+       "userlogin-joinproject": "جُڑ ونڄو {{SITENAME}} نال",
+       "createaccount": "کھاتہ کھولو",
+       "userlogin-resetpassword-link": "پاسورڈ بھل ڳئے ہو؟",
+       "userlogin-helplink2": "لاگ ان تھیوݨ کیتے مدد دی لوڑ ہے؟",
+       "createacct-emailrequired": "ای میل پتہ",
+       "createacct-emailoptional": "ای-میل پتہ، آپشنل",
+       "createacct-email-ph": "اپنا ای-میل پتہ لکھو",
+       "createacct-another-email-ph": "اپنا ای-میل پتہ لکھو",
+       "createacct-reason": "سبب",
+       "createacct-submit": "اپݨاں کھاتا کھولو",
+       "createacct-another-submit": "کھاتہ کھولو",
+       "createacct-benefit-heading": "{{SITENAME}} تہاݙے وانگوں علم دوست افراد دا مرہون منت ہے۔",
+       "createacct-benefit-body1": "$1 {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
+       "createacct-benefit-body2": "\n$1 {{PLURAL:$1|ورقہ|ورقے}}",
+       "createacct-benefit-body3": "ہݨ دے {{PLURAL:$1|کم|کماں}}",
+       "loginerror": "لاگ ان وچ غلطی",
+       "createacct-error": "کھاتہ بݨاوݨ وچ غلطی",
+       "loginlanguagelabel": "زبان: $1",
+       "pt-login": "لاگ ان تھیوو",
+       "pt-login-button": "لاگ ان تھیوو",
+       "pt-createaccount": "کھاتہ کھولو",
+       "pt-userlogout": "لاگ آؤٹ",
+       "changepassword": "پاس ورڈ تبدیل کرو",
+       "oldpassword": "پراݨا پاس ورڈ",
+       "retypenew": "نواں پاس ورڈ ولدا لکھو",
+       "botpasswords-label-create": "بݨاؤ",
+       "botpasswords-label-update": "اپ ݙیٹ",
+       "botpasswords-label-cancel": "منسوخ",
+       "botpasswords-label-delete": "مٹاؤ",
+       "botpasswords-label-resetpassword": "پاس ورڈ تبدیل کرو",
+       "passwordreset": "نواں پاس ورڈ بݨاؤ",
+       "passwordreset-username": "صارف دا ناں",
+       "resettokens-tokens": "ٹوکن",
+       "resettokens-token-label": "$1 (موجودہ قدر: $2)",
+       "bold_sample": "موٹی لکھائی",
+       "bold_tip": "موٹی لکھائی",
+       "italic_sample": "ترچھا متن",
+       "italic_tip": "ترچھی لکھائی",
+       "link_sample": "جوڑ",
+       "link_tip": "اندرونی جوڑ",
+       "extlink_sample": "http://www.example.com جوڑ دا ناں",
+       "extlink_tip": "باہرلے جوڑ (remember http:// prefix)",
+       "headline_sample": "شہ سرخی",
+       "headline_tip": "ݙوجھے درجے دی سرخی",
+       "nowiki_sample": "فارمیٹ نہ تھئی ہوئی لکھائی اتھ درج کرو",
+       "nowiki_tip": "ویکی فارمیٹ کوں نظرانداز کرو",
+       "image_tip": "پیوستہ فائل",
+       "media_tip": "فائل دا جوڑ",
+       "sig_tip": "تہاݙے دستخط ویلے دے نال",
+       "hr_tip": "اُفقی لکیر (زیادہ استعمال نہ کریں)",
+       "summary": "خلاصہ",
+       "subject": "عنوان:",
+       "minoredit": "ایہ ہک چھوٹی تبدیلی ہے",
+       "watchthis": "ایں ورقے تے اکھ رکھو",
+       "savearticle": "محفوظ",
+       "savechanges": "تبدیلیاں محفوظ کرو",
+       "publishpage": "ورقہ شائع کرو",
+       "publishchanges": "تبدیلیاں شائع کرو",
+       "preview": "نمائش",
+       "showpreview": "نمائش",
+       "showdiff": "تبدیلیاں ݙکھاؤ",
+       "loginreqlink": "لاگ ان",
+       "userpage-userdoesnotexist-view": "صارف کھاتہ \"$1\" رجسٹرڈ کائنی۔",
+       "continue-editing": "خانہ ترمیم وچ ونڄو",
+       "editing": "تساں \"$1\" لکھدے پئے ہو",
+       "creating": "زیر تخلیق $1",
+       "editingsection": "«$1» دے قطعہ دی ترمیم",
+       "templatesused": "ایں ورقے تے  ورتے ڳئے {{PLURAL:$1|سانچے|سانچہ}}:",
+       "template-protected": "(بچایا گیا)",
+       "template-semiprotected": "(نیم محفوظ)",
+       "hiddencategories": "ایہ ورقہ {{PLURAL:$1|1 لُکے زمریاں|$1 لکا زمرہ }} وچ شامل ہے:",
+       "permissionserrors": "خطائے اجازت",
+       "moveddeleted-notice": "ایہ ورقہ مٹایا ڳیا ہے۔ مٹاوݨ دا لاگ ہیٹھاں ݙتا ہویا ہے",
+       "content-model-wikitext": "ویکی متن",
+       "undo-failure": "متنازع تبدیلیاں پاروں ایہ تبدیلی واپس نی تھی سڳدی۔",
+       "viewpagelogs": "صفحے دے لاگ ݙیکھو",
+       "currentrev-asof": "حالیہ نسخہ بمطابق $1",
+       "revisionasof": "دی تبدیلیاں $1",
+       "previousrevision": "→ پراݨا نسخہ",
+       "nextrevision": "نویں تبدیلی →",
+       "currentrevisionlink": "موجودہ حالت",
+       "cur": " رائج",
+       "last": "پچھلا",
+       "history-fieldset-title": "دہرائی کیتے لبھت",
+       "histfirst": "قدیم ترین",
+       "histlast": "تازہ ترین",
+       "history-feed-title": "ریویژن رکارڈ",
+       "history-feed-item-nocomment": "$2 کوں $1",
+       "rev-delundel": "ݙکھاؤ/لکاؤ",
+       "mergelog": "لاگ رلاؤ",
+       "history-title": "\"$1\" دا ریکارڈ",
+       "difference-title": "\"$1\" دے نسخیاں دے درمیان فرق",
+       "lineno": "سطر $1:",
+       "compareselectedversions": "منتخب متـن دا موازنہ",
+       "editundo": "واپس",
+       "diff-empty": "(کوئی فرق کائنی)",
+       "searchresults": "کھوج دا نتارا",
+       "searchresults-title": "\"$1\" دے کھوج نتارے",
+       "prevn": "پچھلے {{PLURAL:$1|$1}}",
+       "nextn": "اگلے {{PLURAL:$1|$1}}",
+       "prevn-title": "پہلے $1 {{PLURAL:$1|نتیجے}}",
+       "nextn-title": "اگلے $1 {{PLURAL:$1|نتیجے}}",
+       "shown-title": "وکھاؤ $1 {{PLURAL:$1|نتیجے}}",
+       "viewprevnext": "($1 {{int:pipe-separator}} $2) ݙیکھو ($3)",
+       "searchprofile-articles": "لسٹ ورقے",
+       "searchprofile-images": "ملٹی میڈیا",
+       "searchprofile-everything": "سب کجھ",
+       "searchprofile-advanced": "اگلا",
+       "searchprofile-articles-tooltip": "$1 وچ ڳولو",
+       "searchprofile-images-tooltip": "فائلاں ڳولو",
+       "searchprofile-everything-tooltip": " سارا مواد ڳولو",
+       "searchprofile-advanced-tooltip": "کسٹم نانواں وچ ڳولو",
+       "search-result-size": "$1 ({{PLURAL:$2|1 لفظ|$2 الفاظ}})",
+       "search-redirect": "($1 کنوں ولدا رجوع )",
+       "search-section": "(قطعہ $1)",
+       "search-file-match": "فائل مواد نال ملدا ہے",
+       "search-suggest": "بھلا تہاݙا مطلب ہائی: $1",
+       "searchall": "یکے",
+       "search-nonefound": "سوال دے نال رلدے ملدے نتارے کائنی۔",
+       "mypreferences": "ترجیحات",
+       "group-bot": "بوٹ",
+       "group-sysop": "منتظمین",
+       "grouppage-bot": "{{ns:project}}:بوٹ",
+       "grouppage-sysop": "{{ns:project}}:ایڈمنسٹریٹر",
+       "right-writeapi": "اے پی آئی تحریر دا استعمال",
+       "newuserlogpage": "یوزر بنݨاوݨ آلی لاگ",
+       "rightslog": "ورتݨ والے دے حقاں دی لاگ",
+       "action-edit": "ایں ورقے تے لکھو",
+       "action-createaccount": "ایہ ورتݨ آلا کھاتہ کھولو",
+       "enhancedrc-history": "پچھلا کم",
+       "recentchanges": "نویاں تبدیلیاں",
+       "recentchanges-legend": "اِختیاراتِ حالیہ تبدیلیاں",
+       "recentchanges-feed-description": "ایں فیڈ وچ وکی تے تھیوݨ آلیاں نویاں نکور تبدیلیاں ݙیکھو۔",
+       "recentchanges-label-newpage": "ایں تبدیلی نواں ورقہ بݨایا ہے",
+       "recentchanges-label-minor": "ایہ ہک چھوٹی تبدیلی ہے",
+       "recentchanges-label-bot": "ایہ تبدیلی  بوٹ نے کیتی ہے۔",
+       "recentchanges-label-unpatrolled": "ایہ تبدیلی اڄݨ تائیں واپس کائنی ولی۔",
+       "recentchanges-label-plusminus": "ورقے دا تبدیل شدہ حجم بلحاظ تعداد بائٹ",
+       "recentchanges-legend-heading": "<strong>اختصارات:</strong>",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (ایہ وی ݙیکھو [[Special:NewPages|نویں ورقیاں دی لسٹ]])",
+       "rclistfrom": "$3 $2 توں ہونے آلیاں نویاں تبدیلیاں ݙکھاؤ",
+       "rcshowhideminor": "$1 معمولی تبدیلیاں",
+       "rcshowhideminor-show": "ݙیکھاؤ",
+       "rcshowhideminor-hide": "لُکاؤ",
+       "rcshowhidebots": "$1 بوٹ",
+       "rcshowhidebots-show": "ݙیکھاؤ",
+       "rcshowhidebots-hide": "لُکاؤ",
+       "rcshowhideliu": "مندرج صارفین $1",
+       "rcshowhideliu-show": "ݙیکھاؤ",
+       "rcshowhideliu-hide": "لُکاؤ",
+       "rcshowhideanons": "گمنام صارف $1",
+       "rcshowhideanons-show": "ݙیکھاؤ",
+       "rcshowhideanons-hide": "لُکاؤ",
+       "rcshowhidepatr": "$1 مراجعت شدہ ترامیم",
+       "rcshowhidemine": "ذاتی ترامیم میݙے کم $1",
+       "rcshowhidemine-show": "ݙیکھاؤ",
+       "rcshowhidemine-hide": "لُکاؤ",
+       "rclinks": "آخری $2 ݙینہ دیاں $1 تبدیلیاں ݙکھاؤ",
+       "diff": "فرق",
+       "hist": "پچھلا کم",
+       "hide": "لُکاؤ",
+       "show": "ݙیکھاؤ",
+       "minoreditletter": "چھوٹا کم",
+       "newpageletter": "نواں",
+       "boteditletter": " خودکار",
+       "rc-change-size-new": "تبدیلی دے بعد $1 {{PLURAL:$1|بائٹ}}",
+       "rc-old-title": "اصلاً «$1» دے عنوان نال تخلیق شدہ",
+       "recentchangeslinked": "رلدیاں ملدیاں تبدیلیاں",
+       "recentchangeslinked-feed": "رلدیاں ملدیاں تبدیلیاں",
+       "recentchangeslinked-toolbox": "رلدیاں ملدیاں تبدیلیاں",
+       "recentchangeslinked-title": "\"$1\" دے متعلقہ تبدیلیاں",
+       "recentchangeslinked-page": "ورقے دا ناں",
+       "recentchangeslinked-to": "کھلے ہوئے ورقے دی بجائے ایندے نال جُڑے ہوئے ورقے دیاں تبدیلیاں ݙکھاؤ",
+       "upload": "فائل چڑھاؤ",
+       "uploadlogpage": "اپلوڈ لاگ",
+       "filedesc": "خلاصہ",
+       "license": "اجازت نامہ:",
+       "license-header": "اجازہ کاری",
+       "imgfile": "فائل",
+       "listfiles": "فائل لسٹ",
+       "file-anchor-link": "فائل",
+       "filehist": "فائل دا تاریخچہ",
+       "filehist-help": "کہیں خاص ویلے تے تاریخ کوں فائل کینویں  نظردی ہائی، ݙیکݨ کیتے اوں ویلے تے کلک کرو۔",
+       "filehist-revert": "واپس",
+       "filehist-current": "موجودہ",
+       "filehist-datetime": "تریخ/ویلہ",
+       "filehist-thumb": "تھمب نیل",
+       "filehist-thumbtext": "مورخہ $1 دا تھمب نیل",
+       "filehist-nothumb": "کوئی تھمبنیل کائنی۔",
+       "filehist-user": "ورتݨ والا",
+       "filehist-dimensions": "پاسے",
+       "filehist-comment": "رائے",
+       "imagelinks": "فائل ورتݨ",
+       "linkstoimage": "اِیں فائل نال ہیٹھاں درج  {{PLURAL:$1|ورقہ مربوط ہے|$1 صفحات مربوط ہن}}:",
+       "nolinkstoimage": "ایں فائل نال کوئی ورقہ کائنی ڄُڑیا ہویا۔",
+       "linkstoimage-redirect": "$1 (فائل وت رجوع) $2",
+       "sharedupload-desc-here": "ایہ فائل $1 توں ہے تے ݙوجھیاں منصوبیاں تے وی ورتی ویسی۔\nایندی وضاحت [$2 فائل دی وضاحت دا ورقہ]  تے تھلے ݙتی ڳئی۔",
+       "filepage-nofile": "ایں ناں دی کوئی فائل کائنی۔",
+       "upload-disallowed-here": "تساں ایں فائل تے لکھ نی سڳدے۔",
+       "randompage": "رلے ملے ورقے",
+       "statistics": "شماريات",
+       "double-redirect-fixer": "ریڈائرکٹ فکسر",
+       "nbytes": "$1 {{PLURAL:$1|بائٹ}}",
+       "nmembers": "{{PLURAL:$1|رکن|اراکین}}",
+       "prefixindex": "سارے ورقے بمع سابقہ",
+       "listusers": "ورتݨ والیاں دے ناں",
+       "newpages": "نویں ورقے",
+       "move": "ٹرو",
+       "pager-newer-n": "{{PLURAL:$1|newer 1|زیادہ نواں $1}}",
+       "pager-older-n": "{{PLURAL:$1|قدیم}} $1",
+       "booksources": "کتابی وسائل",
+       "booksources-search-legend": "ایں مضمون تے کتاباں لبھو",
+       "booksources-search": "ڳولو",
+       "specialloguserlabel": "کرݨ آلا :",
+       "log": "لاگز",
+       "all-logs-page": "سارےعوامی لاگ",
+       "logempty": "لاگ وچ رلدیاں ملدیاں چیزاں کائنی۔",
+       "allpages": "سارے مقالے",
+       "allarticles": "سارے مقالے",
+       "allpagessubmit": "ڄلو",
+       "allpages-hide-redirects": "رجوع مکررات لکاؤ",
+       "categories": "زمرہ",
+       "listgrouprights-members": "(رکناں دی لسٹ)",
+       "emailuser": "ایں ورتݨ والے کوں ای میل کرو",
+       "usermessage-editor": "نظامی پیغام رساں",
+       "watchlist": "زیرنظر فہرست",
+       "mywatchlist": "زیرنظر فہرست",
+       "watchlistfor2": "$1 تے $2 کیتے",
+       "watch": "اکھ تلے رکھو",
+       "unwatch": "اکھ ہیٹھوں ہٹاؤ",
+       "wlshowlast": "ݙیکھاؤ چھیکڑی $1 گھنٹے $2 ݙینہ",
+       "watchlist-options": "نظر تھلے رکھݨ دیاں راہواں",
+       "enotif_reset": "سارے ورقے ڈیکھ گھدن",
+       "dellogpage": "مٹاوݨ آلی لاگ",
+       "rollbacklink": "واپس",
+       "protectlogpage": "بچت لاگ",
+       "protectedarticle": "\"[[$1]]\" بچایا گیا اے",
+       "modifiedarticleprotection": "«[[$1]]» دا درجہ حفاظت تبدیل کیتا",
+       "protect-default": "تمام صارفین کوں اجازت ہے",
+       "restriction-edit": "لکھو",
+       "restriction-move": "ٹرو",
+       "namespace": "ناں دی جگہ:",
+       "invert": "انتخاب معکوس",
+       "namespace_association": "رلدے ناں دی تھاں",
+       "blanknamespace": "(مکھ)",
+       "contributions": " $1 ورتن آلے دا حصہ",
+       "contributions-title": "صارف $1 دی شراکتاں",
+       "mycontris": "شراکتاں",
+       "anoncontribs": "شراکتاں",
+       "contribsub2": "{{GENDER:$3|$1}} ($2)",
+       "nocontribs": "ایں معیار دے مطابق کوئی تبدیلی نی لبھی۔",
+       "uctop": "(موجودہ)",
+       "month": "مہینے توں (تے پہلاں):",
+       "year": "سال توں (تے پہلاں):",
+       "sp-contributions-newbies": "صرف نویں ورتݨ آلیاں دے کم ݙکھاؤ",
+       "sp-contributions-blocklog": "لاگ روکو",
+       "sp-contributions-uploads": "اپلوڈ کردہ",
+       "sp-contributions-logs": "لاگز",
+       "sp-contributions-talk": "ڳالھ مہاڑ",
+       "sp-contributions-search": "حصے پاؤݨ آلیاں دی تلاش",
+       "sp-contributions-username": "آئی پی پتہ یا ورتݨ آلا ناں:",
+       "sp-contributions-toponly": "صرف اوہ تبدیلیاں ݙکھاؤ جیہڑیاں ہُݨے ہُݨے تھیاں ہن۔",
+       "sp-contributions-newonly": "صرف نویں ورقیاں بݨݨ آلیاں لکھتاں ݙیکھاؤ",
+       "sp-contributions-submit": "ڳولو",
+       "whatlinkshere": "مربوط ورقے",
+       "whatlinkshere-title": "«$1» دے نال جُڑے ہوے ورقے",
+       "whatlinkshere-page": "ورقہ",
+       "linkshere": "<strong>[[:$1]]</strong> نال درج ذیل ورقے مربوط ہن:",
+       "nolinkshere": "<strong>[[:$1]]</strong> نال کوئی ورقہ مربوط کائنی۔",
+       "isredirect": "صفحہ ریڈائریکٹ کرو",
+       "istemplate": "شامل شدہ",
+       "isimage": "فائل دا ربط",
+       "whatlinkshere-prev": "{{PLURAL:$1|پچھلا|پچھلے $1}}",
+       "whatlinkshere-next": "{{PLURAL:$1|اگلا|اگلے $1}}",
+       "whatlinkshere-links": "→ روابط",
+       "whatlinkshere-hideredirs": "رجوع مکررات $1",
+       "whatlinkshere-hidetrans": "استعمالات $1",
+       "whatlinkshere-hidelinks": "روابط $1",
+       "whatlinkshere-hideimages": "تصویر دے روابط $1",
+       "whatlinkshere-filters": "نتارے",
+       "infiniteblock": "بے انت",
+       "blocklink": "پابندی لاؤ",
+       "contribslink": "حصے داری",
+       "blocklogpage": "لاگ روکو",
+       "blocklogentry": "«[[$1]]» تے $2 کیتے پابندی عائد کی ڳئی ہے $3",
+       "reblock-logentry": "[[$1]] دی ترتیبات پابندی کوں تبدیل کیتاڳئے، ہݨ میعاد $2 $3 تے مُکسی",
+       "block-log-flags-nocreate": "کھاتا کھولݨ تے پابندی ہے",
+       "proxyblocker": "پراکسی روکݨ آلا",
+       "movelogpage": "ناں تبدیل کرݨ دا لاگ",
+       "export": "ورقے ٻاہر بھیجو",
+       "thumbnail-more": "وݙا کرو",
+       "importlogpage": "لاگ گھن آؤ",
+       "tooltip-pt-userpage": "تہاݙا صارف ورقہ",
+       "tooltip-pt-mytalk": "{{GENDER:|Your}} گالھ مہاڑ",
+       "tooltip-pt-preferences": "تہاݙیاں ترجیحاں",
+       "tooltip-pt-watchlist": " انہاں ورقیاں دی لسٹ جنہاں وچ تساں تبدیلیاں کرݨ کیتے ݙیہدے پئے ہو۔",
+       "tooltip-pt-mycontris": "میݙے کم",
+       "tooltip-pt-login": "لاگ ان تھیوو تاں چنگا ہے، ضروری کائنی۔",
+       "tooltip-pt-logout": "لاگ آؤٹ",
+       "tooltip-pt-createaccount": "ایہ تہاݙے کیتے چنگا ہے جو کھاتہ کھولو تے لاگ ان تھیوو، پر ایہ لازمی کائنی۔",
+       "tooltip-ca-talk": "مضمون بارے بحث",
+       "tooltip-ca-edit": "ایں ورقے تے لکھو",
+       "tooltip-ca-addsection": "نواں حصہ شروع کرو",
+       "tooltip-ca-viewsource": "ایہ ورقہ محفوظ تھیا ہویا ہے۔ \nتساں صرف ایندا ماخذ ݙیکھ سڳدے ہو۔",
+       "tooltip-ca-history": "ایں ورقے دا پراݨا روپ۔",
+       "tooltip-ca-protect": "ایہ ورقہ محفوظ کرو",
+       "tooltip-ca-delete": "ایں ورقے کوں مٹاؤ",
+       "tooltip-ca-move": "ایں ورقے کوں گھن ڄلو",
+       "tooltip-ca-watch": "ایں ورقے کوں آپݨی دید آلے ورقیاں وچ رکھو",
+       "tooltip-ca-unwatch": "ایں ورقے کوں آپݨی دید آلے ورقیاں وچ رکھو",
+       "tooltip-search": "ڳولو {{SITENAME}}",
+       "tooltip-search-go": "جے ایں عنوان دا ورقہ ہے تاں اتھ ونڄو",
+       "tooltip-search-fulltext": "ایں عبارت کوں ورقیاں وچ ڳولو",
+       "tooltip-p-logo": "پہلا ورقہ ݙیکھو",
+       "tooltip-n-mainpage": "پہلا ورقہ ݙیکھو",
+       "tooltip-n-mainpage-description": "پہلے ورقے تے ونڄو",
+       "tooltip-n-portal": "ایں مںصوبے بارے، تساں کیا کر سڳدو، ، چیزاں کتھوں ڳولوں",
+       "tooltip-n-currentevents": "موجودہ حالات وچ پچھلیاں معلومات ݙیکھو",
+       "tooltip-n-recentchanges": "وکی تے نویاں تبدیلیاں۔",
+       "tooltip-n-randompage": "کوئی صفہ کھولو۔",
+       "tooltip-n-help": "لبھݨ دی جاہ",
+       "tooltip-t-whatlinkshere": "ایں نال جڑے سارے وکی ورقے۔",
+       "tooltip-t-recentchangeslinked": "ایں ورقے توں جڑے ورقیاں وچ نویاں تبدیلیاں",
+       "tooltip-feed-atom": "اِیں ورقے دا اٹوم فیڈ",
+       "tooltip-t-upload": "فائل چڑھاؤ",
+       "tooltip-t-specialpages": "سارے خاص ورقیاں دی تندیر",
+       "tooltip-t-print": "ایں ورقے دا چھپݨ آلا انگ ݙیکھو",
+       "tooltip-t-permalink": "اس صفے دے ایں روپ نال پکا جوڑ",
+       "tooltip-ca-nstab-main": "مواد آلا صفہ ݙیکھو",
+       "tooltip-ca-nstab-user": "صارف دا ورقہ ݙیکھو",
+       "tooltip-ca-nstab-special": "ایہ ہک خاص ورقہ ہے، اینکوں تبدیل نسے کرسڳدے",
+       "tooltip-ca-nstab-project": "منصبے آلا ورقہ ݙیکھو",
+       "tooltip-ca-nstab-image": "فائل دا ورقہ ݙیکھو",
+       "tooltip-ca-nstab-mediawiki": "نظامی سنیہہ ݙیکھو",
+       "tooltip-ca-nstab-template": "سانچہ ݙیکھو",
+       "tooltip-ca-nstab-category": "کیٹاگری آلا ورقہ ݙیکھو",
+       "tooltip-minoredit": "ایں کوں نکی ترممیم وچ ڳݨو",
+       "tooltip-save": "تبدیلیاں محفوظ کرو",
+       "tooltip-preview": "محفوظ کرݨ کنے پہلے تبدیلیاں ݙیکھو، مہربانی ہوسی۔",
+       "tooltip-diff": "ایں لکھت وچ کیتیاں ڳیاں تبدیلیاں ݙیکھاؤ",
+       "tooltip-watch": "ایں ورقے کوں آپݨی دید آلے ورقیاں وچ رکھو",
+       "tooltip-rollback": "رول بیک\" ہک کلک وچ ورقے کوں پچھلی حالت وچ گھن ویسی\"",
+       "tooltip-undo": "واپس تے کلک کرݨ نال  پچھلی ترمیم تے پُڄ ویسو، نمائشی انداز وچ ترمیم دا خانہ کھلسی۔ تساں مختصر سسب وی بیان کر سڳدے ہو۔",
+       "tooltip-summary": "مختصر خلاصہ درج کرو",
+       "simpleantispam-label": "سپام روک پھاٹک\nاینکوں <strong>نہ</strong>  بھرو!",
+       "pageinfo-title": "«$1» دی معلومات",
+       "pageinfo-header-basic": "بنیادی معلومات",
+       "pageinfo-header-edits": "تاریخچۂ ترمیم",
+       "pageinfo-header-restrictions": "ورقے دی حفاظت",
+       "pageinfo-header-properties": "صفحہ دی خاصیتاں",
+       "pageinfo-display-title": "عنوان",
+       "pageinfo-default-sort": "کلید برائے ابتدائی ترتیب",
+       "pageinfo-length": "ورقے دی لمناݨ (بائٹ وچ)",
+       "pageinfo-article-id": "ورقے دی شناخت",
+       "pageinfo-language": "زبان",
+       "pageinfo-content-model": "انداز متن",
+       "pageinfo-robot-policy": "روبوٹ دی فہرست سازی",
+       "pageinfo-robot-index": "مجاز",
+       "pageinfo-robot-noindex": "ممنوع",
+       "pageinfo-watchers": "تعداد ناظرین",
+       "pageinfo-few-watchers": "$1 کنوں گھٹ {{PLURAL:$1|ناظر|ناظرین}}",
+       "pageinfo-redirects-name": "رجوعاں  دی تعداد",
+       "pageinfo-subpages-name": "ایں ورقے دے ذیلی ورقیاں دی تعداد",
+       "pageinfo-firstuser": "صفحہ ساز",
+       "pageinfo-firsttime": "صفحہ سازی دی تاریخ",
+       "pageinfo-lastuser": "چھیکڑی ترمیم کنندہ",
+       "pageinfo-lasttime": "چھیکڑی ترمیم دی تاریخ",
+       "pageinfo-edits": "ترامیم دی مجموعی تعداد",
+       "pageinfo-authors": "مختلف مصنفین دی  تعداد",
+       "pageinfo-recent-authors": "مختلف مصنفین دی حالیہ تعداد",
+       "pageinfo-magic-words": "جادوئی {{PLURAL:$1|لفظ|الفاظ}} ($1)",
+       "pageinfo-hidden-categories": "پوشیدہ {{PLURAL:$1|زمرہ|زمرہ جات}} ($1)",
+       "pageinfo-templates": "زیر استعمال {{PLURAL:$1|سانچہ|سانچے}} ($1)",
+       "pageinfo-toolboxlink": "معلومات صفحہ",
+       "pageinfo-contentpage": "شمار بطور ورقہ",
+       "pageinfo-contentpage-yes": "ڄیا",
+       "patrol-log-page": "گشت لاگ",
+       "previousdiff": "← پرانی لکھائی",
+       "nextdiff": "نویں لکھائی →",
+       "widthheightpage": "$1×$2، $3 {{PLURAL:$3|ورقہ|ورقے}}",
+       "file-info-size": "\n$1 × $2 پکسل، فائل دا حجم: $3، MIME قسم: $4",
+       "file-info-size-pages": "$1 × $2 پکسل، فائل دا حجم: $3، MIME قسم: $4، $5 {{PLURAL:$5|ورقہ|ورقے}}",
+       "file-nohires": "ایں توں زیادہ ریزولیوشن دستیاب کائنی۔",
+       "svg-long-desc": "ایس وی جی فائل، ابعاد $1 × $2 پکسل، فائل دا حجم: $3",
+       "show-big-image": "اصل فائل",
+       "show-big-image-preview": "ایں نمائش دا حجم:$1",
+       "show-big-image-other": "ٻیاں {{PLURAL:$2|قرارداد|قراردادیں}}: $1۔",
+       "show-big-image-size": "$1 × $2 پکسلز",
+       "metadata": "میٹا ڈیٹا",
+       "metadata-help": "ایں فائل وچ ٻیاں معلومات وی ہن۔ شاید او تہاݙے کیمرے یا سیکنر توں آیاں ہن، جیندے نال تساں ایہ فائل بݨائی ہائی۔\nجے ایہ فائل آپݨی اصل حالت وچ نہ ہووے تاں کجھ معلومات تبدیل تھئی ہوئی فائل دی پوری پوری عکاسی کائناں کریسی۔",
+       "metadata-fields": "تصویر دے میٹاڈیٹا دے او خانے جہڑے پیغام میں درج ہن او تصویر دے صفحے تے شامل ہوندے ہن۔ ایہ ااوں ویلے ظاہر تھیندن جڈݨ میٹاڈیٹا کوں ودھایا ونڄے۔\nٻئے خانے شروع وچ لُڳے ہوندن۔\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "exif-orientation": "اورینٹیشن",
+       "exif-xresolution": "افقی ریزولوشن",
+       "exif-yresolution": "عمودی ریزولیشن",
+       "exif-datetime": "فائل بدلݨ دی تاریخ تے ویلا",
+       "exif-make": "کیمرہ ساز کمپنی",
+       "exif-model": "کیمرے دا ماڈل",
+       "exif-software": "مستعمل سافٹ ویئر",
+       "exif-exifversion": "اکزیف ورژن",
+       "exif-colorspace": "رنگ فضا",
+       "exif-datetimeoriginal": "ڈیٹا بݨاوݨ دی تاریخ تے ویلا",
+       "exif-datetimedigitized": "ڈجیٹائزنگ دا ویلہ تے تریخ",
+       "exif-orientation-1": "عام",
+       "namespacesall": "یکے",
+       "monthsall": "یکے",
+       "imgmultipagenext": "اگلا →",
+       "imgmultigo": "ونڄو!",
+       "imgmultigoto": "$1 تے ونڄو",
+       "watchlisttools-clear": "زیرنظر فہرست دی صفائی",
+       "watchlisttools-view": "متعلقہ تبدیلیاں ݙیکو",
+       "watchlisttools-edit": "زیرنظر فہرست  کوں ݙیکھو تے تبدیلی کرو",
+       "watchlisttools-raw": "کچی زیرِنظرفہرست وچ تبدیلی کرو",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|تبادلۂ خیال]])",
+       "redirect": "فائل، صارف، ورقہ،دہرائی یا آئی ڈی لاگ دے ذریعے ولدا واپس",
+       "redirect-submit": "ڄلو",
+       "redirect-lookup": "تلاش:",
+       "redirect-value": "قدر:",
+       "redirect-user": "صارف دی شناخت",
+       "redirect-page": "ورقے دی شناخت",
+       "redirect-revision": "ورقے دا رویژن",
+       "redirect-file": "فائل دا ناں",
+       "specialpages": "خاص ورقے",
+       "tag-filter": "[[Special:Tags|Tag]] نتارا:",
+       "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ٹیگ|ٹیگز}}]]: $2)",
+       "tags-active-yes": "ڄیا",
+       "tags-active-no": "کو",
+       "tags-hitcount": "$1 {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
+       "logentry-delete-delete": "$1 {{GENDER:$2|مٹایا ڳیا}} ورقہ $3",
+       "logentry-delete-restore": "$1 {{GENDER:$2|بحال تھی ڳیوہے}} page $3 ($4)",
+       "revdelete-content-hid": "مواد لکیا",
+       "logentry-move-move": "$1 {{جنس:$2|پلٹی}} صفہ $3 توں $4",
+       "logentry-newusers-create": "صارف کھاتہ $1 {{GENDER:$2|بݨایا ڳیا}}",
+       "logentry-newusers-autocreate": "صارف کھاتہ $1 خودکار طور  {{GENDER:$2|تخلیق تھیا}}",
+       "logentry-upload-upload": "$1 {{GENDER:$2|اپلوڈ}} $3",
+       "searchsuggest-search": "ڳولو",
+       "duration-days": "$1 {{PLURAL:$1|ݙینہ}}",
+       "randomrootpage": "بے ترتيب بنیادی صفحہ"
+}
index a20c372..b60523e 100644 (file)
        "rcfilters-legend-heading": "<strong>Seznam okrajšav:</strong>",
        "rcfilters-activefilters": "Dejavni filtri",
        "rcfilters-advancedfilters": "Napredni filtri",
+       "rcfilters-limit-title": "Spremembe za prikaz",
+       "rcfilters-limit-shownum": "Prikaži zadnjih $1 sprememb",
+       "rcfilters-days-title": "Pretekli dnevi",
+       "rcfilters-hours-title": "Pretekle ure",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|dan|dneva|dnevi|dni}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|ura|uri|ure|ur}}",
        "rcfilters-quickfilters": "Shranjeni filtri",
        "rcfilters-quickfilters-placeholder-title": "Shranjena ni še nobena povezava",
        "rcfilters-quickfilters-placeholder-description": "Da shranite svoje nastavitve filtrov in jih ponovno uporabite pozneje, kliknite na ikono za zaznamek v območju Dejavni filtri spodaj.",
        "rcfilters-invalid-filter": "Neveljaven filter",
        "rcfilters-empty-filter": "Ni dejavnih filtrov. Prikazani so vsi prispevki.",
        "rcfilters-filterlist-title": "Filtri",
-       "rcfilters-filterlist-whatsthis": "Kaj je to?",
+       "rcfilters-filterlist-whatsthis": "Kako to deluje?",
        "rcfilters-filterlist-feedbacklink": "Podajte povratne informacije o novih (preizkusnih) filtrih",
        "rcfilters-highlightbutton-title": "Označi rezultate",
        "rcfilters-highlightmenu-title": "Izberite barvo",
        "rcfilters-filter-editsbyself-description": "Vaša lastna urejanja.",
        "rcfilters-filter-editsbyother-label": "Spremembe drugih",
        "rcfilters-filter-editsbyother-description": "Vse spremembe razen vaše.",
-       "rcfilters-filtergroup-userExpLevel": "Stopnja izkušenosti (samo za registrirane uporabnike)",
+       "rcfilters-filtergroup-userExpLevel": "Registriranost in izkušenost uporabnika",
        "rcfilters-filter-user-experience-level-registered-label": "Registriran",
        "rcfilters-filter-user-experience-level-registered-description": "Prijavljeni uredniki.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Neregistriran",
        "rcfilters-filter-user-experience-level-unregistered-description": "Uredniki, ki niso prijavljeni.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Novinci",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Manj kot 10 urejanj in 4 dni dejavnosti.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Registrirani uredniki z manj kot 10 urejanji in 4 dnevi dejavnosti.",
        "rcfilters-filter-user-experience-level-learner-label": "Učenci",
-       "rcfilters-filter-user-experience-level-learner-description": "Več izkušenj kot »Novinci«, vendar manj kot »Izkušeni uporabniki«.",
+       "rcfilters-filter-user-experience-level-learner-description": "Registrirani uredniki, katerih izkušenost se uvršča med »Novince« in »Izkušene uporabnike«.",
        "rcfilters-filter-user-experience-level-experienced-label": "Izkušeni uporabniki",
-       "rcfilters-filter-user-experience-level-experienced-description": "Več kot 30 dni dejavnosti in 500 urejanj.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Registrirani uredniki z več kot 500 urejanji in 30 dnevi dejavnosti.",
        "rcfilters-filtergroup-automated": "Samodejni prispevki",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-bots-description": "Urejanja, narejena s samodejnimi orodji.",
        "rcfilters-hideminor-conflicts-typeofchange-global": "Filter »Manjša urejanja« je v sporu z enim ali več filtri Vrsta spremembe, ker nekaterih vrst urejanj ni možno označiti kot »manjša«. Filtri v sporu so označeni v območju Dejavni filtri zgoraj.",
        "rcfilters-hideminor-conflicts-typeofchange": "Nekaterih vrst sprememb ni možno označiti kot »manjše«, zato je ta filter v sporu z naslednjimi filtri Vrsta spremembe: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Ta filter Vrsta spremembe je v sporu s filtrom »Manjše urejanje«. Nekaterih vrst sprememb ni možno označiti kot »manjše«.",
-       "rcfilters-filtergroup-lastRevision": "Zadnja redakcija",
-       "rcfilters-filter-lastrevision-label": "Zadnja redakcija",
-       "rcfilters-filter-lastrevision-description": "Najnovejša sprememba strani.",
-       "rcfilters-filter-previousrevision-label": "Zgodnejše redakcije",
-       "rcfilters-filter-previousrevision-description": "Vse spremembe, ki niso najnovejša sprememba strani.",
+       "rcfilters-filtergroup-lastRevision": "Najnovejše redakcije",
+       "rcfilters-filter-lastrevision-label": "Najnovejša redakcija",
+       "rcfilters-filter-lastrevision-description": "Samo najnovejša sprememba strani.",
+       "rcfilters-filter-previousrevision-label": "Nenajnovejša redakcija",
+       "rcfilters-filter-previousrevision-description": "Vse spremembe, ki niso »najnovejša redakcija«.",
        "rcfilters-filter-excluded": "Izključeno",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:ne</strong> $1",
+       "rcfilters-exclude-button-off": "Izključi izbrane",
+       "rcfilters-exclude-button-on": "Izključitev izbranih",
        "rcfilters-view-tags": "Označena urejanja",
        "rcfilters-view-namespaces-tooltip": "Filtriraj rezultate po imenskem prostoru",
        "rcfilters-view-tags-tooltip": "Filtriraj rezultate z uporabo oznak urejanj",
        "delete-warning-toobig": "Ta stran ima obsežno zgodovino urejanja, tj. čez $1 {{PLURAL:$1|redakcijo|redakciji|redakcije|redakcij}}.\nNjeno brisanje lahko zmoti obratovanje zbirke podatkov {{GRAMMAR:dative|{{SITENAME}}}};\nnadaljujte s previdnostjo.",
        "deleteprotected": "Strani ne morete izbrisati, ker jo je nekdo zaščitil.",
        "deleting-backlinks-warning": "<strong>Opozorilo:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Druge strani]] se povezujejo na ali vključujejo stran, ki jo nameravate izbrisati.",
+       "deleting-subpages-warning": "<strong>Opozorilo:</strong> Stran, ki jo nameravate izbrisati, ima [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|podstran|$1 podstrani|51=več kot 50 podstrani}}]].",
        "rollback": "Vrni spremembe",
        "rollbacklink": "vrni",
        "rollbacklinkcount": "vrni $1 {{PLURAL:$1|urejanje|urejanji|urejanja|urejanj}}",
        "fileduplicatesearch-noresults": "Datoteke imenovane »$1« ni mogoče najti.",
        "specialpages": "Posebne strani",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Navadne posebne strani.\n* <span class=\"mw-specialpagerestricted\">Omejene posebne strani.</span>",
+       "specialpages-note-restricted": "* Navadne posebne strani.\n* <span class=\"mw-specialpagerestricted\">Omejene posebne strani.</span>",
        "specialpages-group-maintenance": "Vzdrževalna poročila",
        "specialpages-group-other": "Ostale posebne strani",
        "specialpages-group-login": "Prijavite se / ustvarite račun",
index 1f4b643..1ee5c3b 100644 (file)
        "fileduplicatesearch-noresults": "Датотека под називом „$1“ није пронађена.",
        "specialpages": "Посебне странице",
        "specialpages-note-top": "Легенда",
-       "specialpages-note": "* Нормалне посебне странице\n* <span class=\"mw-specialpagerestricted\">Ограничене посебне странице</span>",
        "specialpages-group-maintenance": "Извештаји одржавања",
        "specialpages-group-other": "Остале посебне странице",
        "specialpages-group-login": "Пријава / регистрација",
index ac3c379..7bf39b3 100644 (file)
        "fileduplicatesearch-noresults": "Datoteka pod nazivom „$1“ nije pronađena.",
        "specialpages": "Posebne stranice",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Normalne posebne stranice\n* <span class=\"mw-specialpagerestricted\">Ograničene posebne stranice</span>",
        "specialpages-group-maintenance": "Izveštaji održavanja",
        "specialpages-group-other": "Ostale posebne stranice",
        "specialpages-group-login": "Prijava / registracija",
index 1303239..961ba5a 100644 (file)
        "rcfilters-legend-heading": "<strong>Lista över förkortningar:</strong>",
        "rcfilters-activefilters": "Aktiva filter",
        "rcfilters-advancedfilters": "Avancerade filter",
+       "rcfilters-limit-title": "Ändringar att visa",
+       "rcfilters-limit-shownum": "Visa de senaste $1 ändringarna",
+       "rcfilters-days-title": "Senaste dagarna",
+       "rcfilters-hours-title": "Senaste timmarna",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|dag|dagar}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|timme|timmar}}",
        "rcfilters-quickfilters": "Sparade filter",
        "rcfilters-quickfilters-placeholder-title": "Inga länkar har sparats ännu",
        "rcfilters-quickfilters-placeholder-description": "För att spara dina filterinställningar och återanvända dem senare, klicka på bokmärkesikonen under \"Aktiva filter\" nedan.",
        "rcfilters-invalid-filter": "Ogiltigt filter",
        "rcfilters-empty-filter": "Inga aktiva filter. Alla bidrag visas.",
        "rcfilters-filterlist-title": "Filter",
-       "rcfilters-filterlist-whatsthis": "Vad är detta?",
+       "rcfilters-filterlist-whatsthis": "Hur fungerar desse?",
        "rcfilters-filterlist-feedbacklink": "Ge återkoppling på nya (beta)filter",
        "rcfilters-highlightbutton-title": "Markera resultat",
        "rcfilters-highlightmenu-title": "Välj en färg",
        "rcfilters-filter-editsbyself-description": "Dina egna bidrag.",
        "rcfilters-filter-editsbyother-label": "Ändringar av andra",
        "rcfilters-filter-editsbyother-description": "Alla ändringar förutom dina egna.",
-       "rcfilters-filtergroup-userExpLevel": "Erfarenhetsnivå (endast för registrerade användare)",
+       "rcfilters-filtergroup-userExpLevel": "Användarregistrering och -erfarenhet",
        "rcfilters-filter-user-experience-level-registered-label": "Registrerade",
        "rcfilters-filter-user-experience-level-registered-description": "Inloggade redigerare.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Oregistrerade",
        "rcfilters-filter-user-experience-level-unregistered-description": "Redigerare som inte är inloggade.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Nykomlingar",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Färre än 10 redigeringar och 4 dagars aktivitet.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Registrerade redigerare med färre än 10 redigeringar och 4 dagars aktivitet.",
        "rcfilters-filter-user-experience-level-learner-label": "Nybörjare",
-       "rcfilters-filter-user-experience-level-learner-description": "Mer erfarenhet än \"Nybörjare\" men mindre än \"Erfarna användare\".",
+       "rcfilters-filter-user-experience-level-learner-description": "Registrerade redigerare vars erfarenhet hamnar mellan \"Nybörjare\" och \"Erfarna användare\".",
        "rcfilters-filter-user-experience-level-experienced-label": "Erfarna användare",
-       "rcfilters-filter-user-experience-level-experienced-description": "Fler än 30 dagars aktivitet och 500 redigeringar.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Registrerade redigerare med fler än 500 redigeringar och 30 dagars aktivitet.",
        "rcfilters-filtergroup-automated": "Automatiserade bidrag",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-bots-description": "Redigeringar gjorda av automatiserade verktyg.",
        "rcfilters-hideminor-conflicts-typeofchange-global": "Filtret \"Mindre redigering\" är i konflikt med en eller flera ändringstypfilter, eftersom vissa ändringstyper inte kan betecknas som \"mindre\". Filtren som är i konflikt är markerade i området med aktiva filter ovan.",
        "rcfilters-hideminor-conflicts-typeofchange": "Vissa ändringstyper kan inte betecknas som \"mindre\", så detta filter är i konflikt med följande ändringstypfilter: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Detta ändringstypfilter är i konflikt med filtret \"Mindre ändringar\". Vissa ändringstyper kan inte betecknas som \"mindre\".",
-       "rcfilters-filtergroup-lastRevision": "Senaste version",
+       "rcfilters-filtergroup-lastRevision": "Senaste versioner",
        "rcfilters-filter-lastrevision-label": "Senaste version",
-       "rcfilters-filter-lastrevision-description": "Den senaste ändringen av en sida.",
-       "rcfilters-filter-previousrevision-label": "Tidigare versioner",
-       "rcfilters-filter-previousrevision-description": "Alla ändringar som inte är den senaste ändringen av en sida.",
+       "rcfilters-filter-lastrevision-description": "Endast senaste ändringen av en sida.",
+       "rcfilters-filter-previousrevision-label": "Inte den senaste versionen",
+       "rcfilters-filter-previousrevision-description": "Alla ändringar som inte är den \"senaste versionen\".",
        "rcfilters-filter-excluded": "Exkluderad",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:not</strong> $1",
+       "rcfilters-exclude-button-off": "Exkludera markerade",
+       "rcfilters-exclude-button-on": "Exkluderar markerade",
        "rcfilters-view-tags": "Märkta redigeringar",
        "rcfilters-view-namespaces-tooltip": "Filtrera resultat efter namnrymder",
        "rcfilters-view-tags-tooltip": "Filtrera resultat med redigeringsmärken",
        "delete-warning-toobig": "Denna sida har en lång redigeringshistorik med mer än $1 {{PLURAL:$1|sidversion|sidversioner}}. Att radera sidan kan skapa problem med hanteringen av databasen på {{SITENAME}}; var försiktig.",
        "deleteprotected": "Du kan inte radera denna sida eftersom den är skyddad.",
        "deleting-backlinks-warning": "<strong>Varning:</strong>\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|Andra sidor]] länkar till eller inkluderar sidan som du är på väg att radera.",
+       "deleting-subpages-warning": "<strong>Varning:</strong> Sidan du håller på att radera har [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|en undersida|$1 undersidor|51=över 50 undersidor}}]].",
        "rollback": "Rulla tillbaka ändringar",
        "rollbacklink": "rulla tillbaka",
        "rollbacklinkcount": "rulla tillbaka $1 {{PLURAL:$1|redigering|redigeringar}}",
        "fileduplicatesearch-noresults": "Ingen fil med namnet \"$1\" hittades.",
        "specialpages": "Specialsidor",
        "specialpages-note-top": "Teckenförklaring",
-       "specialpages-note": "* Normala specialsidor.\n* <span class=\"mw-specialpagerestricted\">Specialsidor med begränsad åtkomst.</span>",
        "specialpages-group-maintenance": "Underhållsrapporter",
        "specialpages-group-other": "Övriga specialsidor",
        "specialpages-group-login": "Logga in / skapa konto",
index fd2ac88..493d2e6 100644 (file)
        "recentchanges-legend-heading": "<strong>సూచిక :</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|కొత్త పేజీల జాబితా]]ను కూడా చూడండి)",
        "recentchanges-submit": "చూపించు",
+       "rcfilters-savedqueries-remove": "తొలగించు",
+       "rcfilters-savedqueries-new-name-label": "పేరు",
        "rcfilters-filterlist-title": "వడపోతలు",
        "rcfilters-highlightmenu-title": "ఒక రంగును ఎంచుకోండి",
        "rcfilters-filter-editsbyself-label": "మీ దిద్దుబాట్లు",
        "rcfilters-filter-editsbyother-label": "ఇతరుల దిద్దుబాట్లు",
        "rcfilters-filter-editsbyother-description": "మీరు చేసినవి కాకుండా మిగిలిన దిద్దుబాట్లన్నీ.",
        "rcfilters-filtergroup-userExpLevel": "అనుభవ స్థాయి (నమోదైన వాడుకరులకు మాత్రమే)",
+       "rcfilters-filter-user-experience-level-registered-label": "నమోదైనది",
        "rcfilters-filter-user-experience-level-newcomer-label": "కొత్తవారు",
        "rcfilters-filter-user-experience-level-newcomer-description": "10 కంటే తక్కువ దిద్దుబాట్లు, 4 రోజుల కంటే తక్కువ పని.",
        "rcfilters-filter-user-experience-level-learner-label": "నేర్చుకుంటున్నవారు",
        "fileduplicatesearch-noresults": "\"$1\" అనే పేరుగల దస్త్రమేమీ కనబడలేదు.",
        "specialpages": "ప్రత్యేక పేజీలు",
        "specialpages-note-top": "సూచిక",
-       "specialpages-note": "* మామూలు ప్రత్యేక పుటలు.\n* <span class=\"mw-specialpagerestricted\">నియంత్రిత ప్రత్యేక పుటలు.</span>",
        "specialpages-group-maintenance": "నిర్వహణా నివేదికలు",
        "specialpages-group-other": "ఇతర ప్రత్యేక పేజీలు",
        "specialpages-group-login": "ప్రవేశించండి / ఖాతాను సృష్టించుకోండి",
index 05cf315..3d72f11 100644 (file)
        "page_first": "uluk",
        "page_last": "ikus",
        "histfirst": "sedu liu hotu",
-       "histlast": "Foun liu hotu",
+       "histlast": "foun liu hotu",
        "historyempty": "(mamuk)",
        "history-feed-item-nocomment": "$1 iha $2",
        "rev-delundel": "hatudu/subar",
index 1619bb5..25c0457 100644 (file)
@@ -13,7 +13,8 @@
                        "아라",
                        "Macofe",
                        "AryanSogd",
-                       "ToJack"
+                       "ToJack",
+                       "Vashgird"
                ]
        },
        "tog-underline": "Пайвандҳо хаткашида:",
        "tog-watchlisthideliu": "Пинҳон кардани вироишоти корбарони вурудшуда аз феҳристи пайгириҳо",
        "tog-watchlisthideanons": "Пинҳон кардани вироишоти корбарони гумном аз феҳристи пайгириҳо",
        "tog-watchlisthidepatrolled": "Пинҳони вироишҳои гаштхӯрда аз феҳристи пайгириҳо",
+       "tog-watchlisthidecategorization": "Пинҳон кардани гурӯҳбандии саҳифаҳо",
        "tog-ccmeonemails": "Нусхаҳои хатҳоро ба ман рои кунед, ман онҳоро ба корбарон рои мекунам",
        "tog-diffonly": "Муҳтавиёти саҳифаи зерин намоиш дода нашавад",
        "tog-showhiddencats": "Гурӯҳҳои пинҳонро намоиш бидеҳ",
-       "tog-norollbackdiff": "Баъд аз вогардони тафовутро нишон надеҳ",
+       "tog-norollbackdiff": "Баъд аз вогардонӣ тафовутро нишон надеҳ",
        "tog-useeditwarning": "Дар ҳолати тарки саҳифа вироиши тағйироти захиранашуда манро огаҳ кун",
-       "tog-prefershttps": "ҲамеÑ\88а Ð¿Ð°Ð¹Ð²Ð°Ñ\81Ñ\82Ñ\88авии Ð°Ð¼Ð½Ñ\80о Ð´Ð°Ñ\80 Ò³Ð¾Ð»Ð¸ Ð²Ñ\83Ñ\80Ñ\83д Ð¸Ñ\81Ñ\82иÑ\84ода Ð±Ð°Ñ\80",
+       "tog-prefershttps": "ҲамеÑ\88а Ð¸Ñ\81Ñ\82иÑ\84ода Ð±Ñ\83Ñ\80дани Ð¿Ð°Ð¹Ð²Ð°Ñ\81Ñ\82Ñ\88авии Ð°Ð¼Ð½ Ð´Ð°Ñ\80 Ò³Ð¾Ð»Ð¸ Ð²Ñ\83Ñ\80Ñ\83д",
        "underline-always": "Доимо",
        "underline-never": "Ҳеҷгоҳ",
        "underline-default": "Пӯст ё мурургари пешфарз",
        "october-date": "$1 октябр",
        "november-date": "$1 ноябр",
        "december-date": "$1 декабр",
+       "period-am": "АМ",
+       "period-pm": "РМ",
        "pagecategories": "{{PLURAL:$1|Гурӯҳ|Гурӯҳҳо}}",
        "category_header": "Мақолаҳо дар гурӯҳи \"$1\"",
        "subcategories": "Зергурӯҳҳо",
        "morenotlisted": "Ин феҳрист комил нест.",
        "mypage": "Саҳифа",
        "mytalk": "Баҳс",
-       "anontalk": "Баҳс бо ин IP",
+       "anontalk": "Баҳс",
        "navigation": "Гаштан",
        "and": "&#32;ва",
        "faq": "Саволҳои тез-тез пурсидашуда",
        "license": "Иҷозатнома:",
        "license-header": "Иҷозатнома",
        "nolicense": "Ҳеҷ яке интихоб нашудааст",
+       "licenses-edit": "Тағйири имконоти иҷозатнома",
        "license-nopreview": "(Пешнамоиш вуҷуд надорад)",
        "upload_source_url": "(як нишони интернетии мӯътабар ва оммавӣ)",
        "upload_source_file": " (парвандае дар компютери шумо)",
index 92f157d..146647e 100644 (file)
@@ -70,7 +70,8 @@
                        "Олександр",
                        "Similartothissimilartothat",
                        "Bunyk",
-                       "Choomaq"
+                       "Choomaq",
+                       "SimondR"
                ]
        },
        "tog-underline": "Підкреслювання посилань:",
        "rcfilters-invalid-filter": "Недійсний фільтр",
        "rcfilters-empty-filter": "Без фільтрів. Показано всі зміни.",
        "rcfilters-filterlist-title": "Фільтри",
-       "rcfilters-filterlist-whatsthis": "Що Ñ\86е?",
+       "rcfilters-filterlist-whatsthis": "Як Ñ\86е Ð¿Ñ\80аÑ\86Ñ\8eÑ\94?",
        "rcfilters-filterlist-feedbacklink": "Надайте відгук про нові (бета) фільтри",
        "rcfilters-highlightbutton-title": "Виділити результати",
        "rcfilters-highlightmenu-title": "Вибрати колір",
        "rcfilters-noresults-conflict": "Результатів не знайдено через конфлікт у пошукових критеріях",
        "rcfilters-state-message-subset": "Цей фільтр не має впливу, оскільки його результати включені в результати {{PLURAL:$2|цього, ширшого, фільтра|цих, ширших, фільтрів}} (спробуйте увімкнути виділення, щоб вирізнити їх): $1",
        "rcfilters-state-message-fullcoverage": "Вибір усіх фільтрів у групі — це все одно, що не вибирати жодного з них, тобто таке фільтрування не має впливу. Гупа містить: $1",
-       "rcfilters-filtergroup-registration": "Реєстрація користувача",
-       "rcfilters-filter-registered-label": "Зареєстровані",
-       "rcfilters-filter-registered-description": "Користувачі, що увійшли в систему.",
-       "rcfilters-filter-unregistered-label": "Незареєстровані",
-       "rcfilters-filter-unregistered-description": "Користувачі, які не ввійшли в систему.",
-       "rcfilters-filter-unregistered-conflicts-user-experience-level": "Цей фільтр конфліктує з {{PLURAL:$2|таким фільтром|такими фільтрами}} досвіду, {{PLURAL:$2|який знаходить|які знаходять}} лише зареєстрованих користувачів: $1",
        "rcfilters-filtergroup-authorship": "Авторство внеску",
        "rcfilters-filter-editsbyself-label": "Зміни, здійснені Вами",
        "rcfilters-filter-editsbyself-description": "Ваш власний внесок.",
        "rcfilters-filter-editsbyother-label": "Зміни, здійснені іншими",
        "rcfilters-filter-editsbyother-description": "Усі зміни, за винятком Ваших власних.",
        "rcfilters-filtergroup-userExpLevel": "Рівень досвіду (тільки для зареєстрованих користувачів)",
-       "rcfilters-filtergroup-user-experience-level-conflicts-unregistered": "Фільтри досвіду знаходять лише зареєстрованих користувачів, тож цей фільтр конфліктує з фільтром «Незареєстровані».",
-       "rcfilters-filtergroup-user-experience-level-conflicts-unregistered-global": "Фільтр «Незареєстровані» конфліктує з одним або більше фільтрами досвіду, які знаходять лише зареєстрованих користувачів. Конфліктні фільтри позначені вище в ділянці активних фільтрів.",
+       "rcfilters-filter-user-experience-level-registered-label": "Зареєстровані",
+       "rcfilters-filter-user-experience-level-registered-description": "Користувачі, що увійшли в систему.",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Незареєстровані",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Користувачі, які не ввійшли в систему.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Новачки",
        "rcfilters-filter-user-experience-level-newcomer-description": "Менше ніж 10 редагувань і 4 дні активності.",
        "rcfilters-filter-user-experience-level-learner-label": "Учні",
        "fileduplicatesearch-noresults": "Файл з назвою «$1» не знайдено.",
        "specialpages": "Спеціальні сторінки",
        "specialpages-note-top": "Легенда",
-       "specialpages-note": "* Звичайні службові сторінки\n* <span class=\"mw-specialpagerestricted\">Сторінки з обмеженим доступом.</span>",
        "specialpages-group-maintenance": "Технічні звіти",
        "specialpages-group-other": "Інші",
        "specialpages-group-login": "Вхід до системи / реєстрація",
index d434d40..8dd0c53 100644 (file)
@@ -36,7 +36,8 @@
                        "Xð",
                        "Nguyên Lê",
                        "Asmen",
-                       "Stephanecbisson"
+                       "Stephanecbisson",
+                       "Quoclinh94"
                ]
        },
        "tog-underline": "Gạch chân liên kết:",
        "recentchanges-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "Xem",
        "rcfilters-activefilters": "Bộ lọc hiện hành",
+       "rcfilters-days-title": "Những ngày gần đây",
        "rcfilters-savedqueries-setdefault": "Đặt làm mặc định",
        "rcfilters-savedqueries-unsetdefault": "Loại bỏ mặc định",
        "rcfilters-restore-default-filters": "Mặc định lại các bộ lọc",
        "rcfilters-highlightmenu-help": "Chọn màu để làm nổi bật thuộc tính này",
        "rcfilters-filterlist-noresults": "Không tìm thấy bộ lọc",
        "rcfilters-noresults-conflict": "Không tìm thấy kết quả nào do tiêu chí tìm kiếm đang bị mâu thuẫn",
-       "rcfilters-filtergroup-registration": "Trạng thái đăng ký thành viên",
-       "rcfilters-filter-registered-label": "Đã đăng ký",
-       "rcfilters-filter-registered-description": "Người dùng đã đăng nhập.",
-       "rcfilters-filter-unregistered-label": "Vô danh",
-       "rcfilters-filter-unregistered-description": "Người dùng chưa đăng nhập.",
        "rcfilters-filtergroup-authorship": "Người sửa đổi",
        "rcfilters-filter-editsbyself-label": "Sửa đổi của bạn",
        "rcfilters-filter-editsbyself-description": "Các sửa đổi do bạn tạo ra.",
        "rcfilters-filter-editsbyother-label": "Sửa đổi của người khác",
        "rcfilters-filter-editsbyother-description": "Các sửa đổi của người khác.",
        "rcfilters-filtergroup-userExpLevel": "Trình độ (chỉ người dùng đã đăng ký)",
+       "rcfilters-filter-user-experience-level-registered-label": "Đã đăng ký",
+       "rcfilters-filter-user-experience-level-registered-description": "Người dùng đã đăng nhập.",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Vô danh",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Người dùng chưa đăng nhập.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Người mới đến",
        "rcfilters-filter-user-experience-level-newcomer-description": "Chưa tới 10 sửa đổi và 4 ngày hoạt động.",
        "rcfilters-filter-user-experience-level-learner-label": "Người đang tập",
        "fileduplicatesearch-noresults": "Không tìm thấy tập tin nào tên “$1”.",
        "specialpages": "Các trang đặc biệt",
        "specialpages-note-top": "Chú giải",
-       "specialpages-note": "* Trang đặc biệt thông thường.\n* <strong class=\"mw-specialpagerestricted\">Trang đặc biệt được hạn chế.</strong>",
        "specialpages-group-maintenance": "Báo cáo bảo quản",
        "specialpages-group-other": "Trang đặc biệt khác",
        "specialpages-group-login": "Đăng nhập / Mở tài khoản",
index 4001765..da6046e 100644 (file)
        "recentchanges-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "ווייזן",
        "rcfilters-activefilters": "אַקטיווע פילטערס",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|טאג|טעג}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|שעה|שעה'ן}}",
        "rcfilters-quickfilters": "אויפֿגעהיטענע פֿילטערס",
        "rcfilters-quickfilters-placeholder-title": "קיין לינקען נאך נישט אויפֿגעהיטן",
        "rcfilters-savedqueries-defaultlabel": "אױפֿגעהיטענע פֿילטערס",
        "rcfilters-invalid-filter": "אומגילטיגער פֿילטער",
        "rcfilters-empty-filter": "קיין אַקטיווע פילטערס. אלע ביישטייערונגען געוויזן.",
        "rcfilters-filterlist-title": "פֿילטערס",
-       "rcfilters-filterlist-whatsthis": "וואס איז דאס?",
+       "rcfilters-filterlist-whatsthis": "ווי ארבעט דאס?",
+       "rcfilters-highlightbutton-title": "ארויסשטאַרצן רעזולטאַטן",
        "rcfilters-highlightmenu-title": "אויסקלויבן א קאליר",
        "rcfilters-filterlist-noresults": "קיין פֿילטערס נישט געטראפֿן",
-       "rcfilters-filtergroup-registration": "באניצער איינשרייבונג",
-       "rcfilters-filter-registered-label": "אײַנגעשריבן",
        "rcfilters-filter-editsbyself-label": "ענדערונגען פון אייך",
        "rcfilters-filter-editsbyself-description": "אייערע אייגענע בײשטײערונגען.",
        "rcfilters-filter-editsbyother-label": "ענדערונגען פֿון אנדערע",
        "rcfilters-filter-editsbyother-description": "אלע ענדערונגען אחוץ אייערע אייגענע.",
+       "rcfilters-filter-user-experience-level-registered-label": "אײַנגעשריבן",
        "rcfilters-filter-user-experience-level-learner-label": "לערנער",
        "rcfilters-filter-bots-label": "באט",
        "rcfilters-filter-humans-label": "מענטש (נישט קיין באט)",
        "rcfilters-filter-minor-label": "מינערדיקע רעדאַקטירונגען",
        "rcfilters-filter-pageedits-label": "בלאט רעדאקטירונגען",
        "rcfilters-filter-newpages-label": "בלאַט־שאַפֿונגען",
-       "rcfilters-filtergroup-lastRevision": "לעצטע ווערסיע",
+       "rcfilters-filtergroup-lastRevision": "לעצטע ווערסיעס",
        "rcfilters-filter-lastrevision-label": "לעצטע ווערסיע",
-       "rcfilters-filter-previousrevision-label": "פֿר×\99ער×\93×\99קע ווערסיעס",
+       "rcfilters-filter-previousrevision-label": "× ×\99ש×\98 ×\93×\99 ×\9cעצ×\98ע ווערסיעס",
        "rcfilters-filter-excluded": "אויסגעשלאסן",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:נישט</strong> $1",
        "rcnotefrom": "פֿאלגנד {{PLURAL:$5|איז די ענדערונג| זענען די ענדערונגען}} זײַט <strong>$3, $4</strong> (ביז <strong>$1</strong>).",
        "undeletecomment": "אורזאַך:",
        "cannotundelete": "טייל אדער גארע צוריקשטעלונג איז דורכגעפאלן: $1",
        "undeletedpage": "'''דער בלאט $1 איז געווארן צוריקגעשטעלט.'''\n\nזעט דעם [[Special:Log/delete| אויסמעקן לאג]] פֿאר א ליסטע פון די לעצטע אויסגעמעקטע און צוריקגעשטעלטע בלעטער.",
-       "undelete-header": "זעט [[Special:Log/delete|דעם אויסמעקונג זשורנאַל]] פֿאַר בלעטער וואָס זענען לעצטנס געווארן אויסגעמעקט recently deleted pages.",
+       "undelete-header": "זעט [[Special:Log/delete|דעם אויסמעקונג זשורנאַל]] פֿאַר בלעטער וואָס זענען לעצטנס געווארן אויסגעמעקט.",
        "undelete-search-title": "זוכן אויסגעמעקטע בלעטער",
        "undelete-search-box": "זוכן אויסגעמעקטע בלעטער",
        "undelete-search-prefix": "ווײַז בלעטער וואס הייבן אן מיט:",
        "version-entrypoints-header-url": "URL",
        "version-libraries-library": "ביבליאטעק",
        "version-libraries-version": "ווערסיע",
+       "redirect": "ווייטערפֿירן לויט טעקע, באַניצער, בלאַט, ווערסיע אדער לאגבוך אידענטיפֿצירער",
        "redirect-submit": "גייט",
        "redirect-lookup": "זוכן:",
        "redirect-value": "ווערט:",
        "fileduplicatesearch-noresults": "קיין טעקע מיטן נאמען \"$1\" נישט געטראפֿן.",
        "specialpages": "ספעציעלע בלעטער",
        "specialpages-note-top": "לעגענדע",
-       "specialpages-note": "* נארמאַלע באַזונדערע בלעטער.\n* <span class=\"mw-specialpagerestricted\">באַגרענעצטע באַזונדערע בלעטער.</span>",
        "specialpages-group-maintenance": "אויפֿהאַלטונג באַריכטן",
        "specialpages-group-other": "אַנדערע ספעציעלע בלעטער",
        "specialpages-group-login": "ארײַנלאגירן / שאַפֿן קאנטע",
index 8902585..2f02916 100644 (file)
        "grant-editmywatchlist": "改你嘅監視清單",
        "grant-editpage": "改已經有嘅版",
        "grant-editprotected": "改保護咗嘅版",
+       "grant-viewmywatchlist": "睇你嘅監視清單",
        "newuserlogpage": "使用者開戶記錄",
        "newuserlogpagetext": "呢個係一個使用者開戶嘅日誌",
        "rightslog": "用戶權限日誌",
        "recentchanges-legend-heading": "<strong>標記:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (睇埋[[Special:NewPages|新開版]])",
        "recentchanges-submit": "顯示",
-       "rcfilters-filter-editsbyself-description": "你改嘅嘢。",
+       "rcfilters-filter-editsbyself-description": "你嘅貢獻。",
        "rcfilters-filter-editsbyother-label": "其他人改嘅嘢",
-       "rcfilters-filter-editsbyother-description": "其他人(唔係你)改嘅嘢",
+       "rcfilters-filter-editsbyother-description": "所有改過嘅嘢(除咗你自己)",
        "rcfilters-filtergroup-userExpLevel": "經驗級別(只限簽咗到嘅用戶)",
        "rcfilters-filter-user-experience-level-newcomer-label": "新手",
-       "rcfilters-filter-user-experience-level-newcomer-description": "少過4日、10次編輯",
+       "rcfilters-filter-user-experience-level-newcomer-description": "少過4日、10次編輯嘅用戶",
        "rcfilters-filter-user-experience-level-learner-label": "學徒",
        "rcfilters-filter-user-experience-level-learner-description": "編輯數同經驗多過「新手」但少過「老手」。",
        "rcfilters-filter-user-experience-level-experienced-label": "老手",
-       "rcfilters-filter-user-experience-level-experienced-description": "超過30日同埋500次編輯",
+       "rcfilters-filter-user-experience-level-experienced-description": "超過30日同埋500次編輯嘅用戶",
        "rcfilters-filtergroup-automated": "自動貢獻",
        "rcfilters-filter-bots-label": "機械人",
        "rcfilters-filter-bots-description": "用自動工具做嘅貢獻",
        "fileduplicatesearch-result-n": "個檔案 \"$1\" 有$2項完全相同嘅重覆。",
        "fileduplicatesearch-noresults": "檔案名\"$1\"找不到",
        "specialpages": "特別頁",
-       "specialpages-note": "* 標準特別頁。\n* <span class=\"mw-specialpagerestricted\">有限制嘅特別頁。</span>",
        "specialpages-group-maintenance": "維護報告",
        "specialpages-group-other": "其它特別頁",
        "specialpages-group-login": "簽到/開新戶口",
index 9a579ca..c46ad3a 100644 (file)
@@ -96,7 +96,8 @@
                        "D41D8CD98F",
                        "Wmr",
                        "逆襲的天邪鬼",
-                       "WhitePhosphorus"
+                       "WhitePhosphorus",
+                       "A2093064"
                ]
        },
        "tog-underline": "链接下划线:",
        "rcfilters-filter-editsbyself-description": "您自己的贡献。",
        "rcfilters-filter-editsbyother-label": "他人更改",
        "rcfilters-filter-editsbyother-description": "除了您的更改以外的所有更改。",
-       "rcfilters-filtergroup-userExpLevel": "体验水平(仅限注册用户)",
+       "rcfilters-filtergroup-userExpLevel": "用户注册及体验",
        "rcfilters-filter-user-experience-level-registered-label": "已注册",
        "rcfilters-filter-user-experience-level-registered-description": "登录编辑者。",
        "rcfilters-filter-user-experience-level-unregistered-label": "未注册",
        "rcfilters-hideminor-conflicts-typeofchange-global": "“小编辑”过滤器与一个或多个更改类型过滤器冲突,因为其中某种更改类型不可指定为“小编辑”。冲突过滤器已在上方活跃过滤器中被标记。",
        "rcfilters-hideminor-conflicts-typeofchange": "某种更改类型不可指定为“小编辑”,因此该过滤器与以下更改类型过滤器相冲突:$1",
        "rcfilters-typeofchange-conflicts-hideminor": "这种更改类型过滤器与“小编辑”过滤器相冲突。某种更改类型不可指定为“小编辑”。",
-       "rcfilters-filtergroup-lastRevision": "最新版本",
-       "rcfilters-filter-lastrevision-label": "最新版本",
-       "rcfilters-filter-lastrevision-description": "对页面的最近更改。",
-       "rcfilters-filter-previousrevision-label": "早期版本",
-       "rcfilters-filter-previousrevision-description": "除最近更改外,所有对某一页面的更改。",
+       "rcfilters-filtergroup-lastRevision": "最新修订版本",
+       "rcfilters-filter-lastrevision-label": "最新修订版本",
+       "rcfilters-filter-lastrevision-description": "å\8fªå\8c\85æ\8b¬å¯¹é¡µé\9d¢ç\9a\84æ\9c\80è¿\91æ\9b´æ\94¹ã\80\82",
+       "rcfilters-filter-previousrevision-label": "不是最新修订版本",
+       "rcfilters-filter-previousrevision-description": "所有不是“最新修订版本”的更改。",
        "rcfilters-filter-excluded": "已排除",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:不是</strong>$1",
        "rcfilters-exclude-button-off": "排除选项",
        "rcfilters-view-namespaces-tooltip": "按名字空间过滤结果",
        "rcfilters-view-tags-tooltip": "按编辑标签过滤结果",
        "rcfilters-view-return-to-default-tooltip": "返回主过滤菜单",
-       "rcfilters-liveupdates-button": "å\9c¨çº¿更新",
+       "rcfilters-liveupdates-button": "å®\9eæ\97更新",
        "rcnotefrom": "下面{{PLURAL:$5|是}}<strong>$3 $4</strong>之后的更改(最多显示<strong>$1</strong>个)。",
        "rclistfromreset": "重置时间选择",
        "rclistfrom": "显示$3 $2之后的新更改",
        "delete-warning-toobig": "此页面有大量的编辑历史,超过$1个版本。删除它可能会破坏{{SITENAME}}的数据库操作;请谨慎考虑是否执行。",
        "deleteprotected": "您不能删除此页面因为它被保护。",
        "deleting-backlinks-warning": "<strong>警告:</strong>有[[Special:WhatLinksHere/{{FULLPAGENAME}}|其他页面]]链接至或包含您要删除的页面。",
+       "deleting-subpages-warning": "<strong>警告:</strong>您要删除的页面有[[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|$1个子页面|51=超过50个子页面}}]]。",
        "rollback": "回退编辑",
        "rollbacklink": "回退",
        "rollbacklinkcount": "回退$1次编辑",
        "fileduplicatesearch-noresults": "没有文件命名为\"$1\"发现。",
        "specialpages": "特殊页面",
        "specialpages-note-top": "说明",
-       "specialpages-note": "*普通特殊页面。\n*<span class=\"mw-specialpagerestricted\">受限特殊页面。</span>",
+       "specialpages-note-restricted": "* 普通特殊页面。\n* <span class=\"mw-specialpagerestricted\">受限特殊页面。</span>",
        "specialpages-group-maintenance": "维护报告",
        "specialpages-group-other": "其它特殊页面",
        "specialpages-group-login": "登录/创建账户",
index 16a8aaf..933567b 100644 (file)
        "rcfilters-filter-excluded": "已排除",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:不是</strong>$1",
        "rcfilters-view-tags": "標記的編輯",
+       "rcfilters-liveupdates-button": "實時更新",
        "rcnotefrom": "以下{{PLURAL:$5|為}}自 <strong>$3 $4</strong> 以來的變更 (最多顯示 <strong>$1</strong> 筆)。",
        "rclistfromreset": "重設日期選擇",
        "rclistfrom": "顯示自 $3 $2 以來的新變更",
        "fileduplicatesearch-noresults": "查無名稱為 \"$1\" 的檔案。",
        "specialpages": "特殊頁面",
        "specialpages-note-top": "說明",
-       "specialpages-note": "* 一般特殊頁面。\n* <span class=\"mw-specialpagerestricted\">受限制的特殊頁面。</span>",
        "specialpages-group-maintenance": "維護報表",
        "specialpages-group-other": "其它特殊頁面",
        "specialpages-group-login": "登入 / 建立帳號",
        "mw-widgets-usersmultiselect-placeholder": "加入更多...",
        "date-range-from": "開始日期:",
        "date-range-to": "結束日期:",
-       "sessionmanager-tie": "ç\84¡æ³\95å\90\88ä½µå¤\9aå\80\8bè«\8bæ±\82èª\8d証類型:$1。",
+       "sessionmanager-tie": "ç\84¡æ³\95å\90\88ä½µå¤\9aå\80\8bè«\8bæ±\82èª\8dè­\89類型:$1。",
        "sessionprovider-generic": "$1 連線階段",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "以 cookie 為基礎的連線階段",
        "sessionprovider-nocookies": "Cookie 功能可能已被關閉,請確認您改開啟 Cookie 功能並重新啟動。",
        "log-action-filter-suppress-reblock": "由重新封鎖禁止顯示使用者",
        "log-action-filter-upload-upload": "新上傳",
        "log-action-filter-upload-overwrite": "重新上傳",
-       "authmanager-authn-not-in-progress": "èª\8d証尚未進行或連線階段資料已遺失,請重頭再開始。",
+       "authmanager-authn-not-in-progress": "èª\8dè­\89尚未進行或連線階段資料已遺失,請重頭再開始。",
        "authmanager-authn-no-primary": "提供的憑證無法用來認証。",
        "authmanager-authn-no-local-user": "提供的憑證沒有與任何在此 wiki 上的使用者相關聯。",
        "authmanager-authn-no-local-user-link": "提供的憑證有效但沒有與任何在此 wiki 上的使用者相關聯。請採用其他方式登入,或建立新使用者,您將會有選項可以連結您先前的憑證到新帳號。",
        "authprovider-confirmlink-ok-help": "顯示連結失敗訊息後繼續。",
        "authprovider-resetpass-skip-label": "略過",
        "authprovider-resetpass-skip-help": "略過重設密碼。",
-       "authform-nosession-login": "å·²æ\88\90å\8a\9fèª\8d証,但您的瀏覽器無法 \"記住\" 登入資訊。\n\n$1",
+       "authform-nosession-login": "å·²æ\88\90å\8a\9fèª\8dè­\89,但您的瀏覽器無法 \"記住\" 登入資訊。\n\n$1",
        "authform-nosession-signup": "已建立帳號,但您的瀏覽器無法 \"記住\" 登入資訊。\n\n$1",
        "authform-newtoken": "缺少密鑰。$1",
        "authform-notoken": "缺少密鑰",
        "linkaccounts-submit": "連結帳號",
        "unlinkaccounts": "取消連結帳號",
        "unlinkaccounts-success": "已取消連結帳號。",
-       "authenticationdatachange-ignored": "èª\8d証資料變更未被處理,可能未設定提供者?",
+       "authenticationdatachange-ignored": "èª\8dè­\89資料變更未被處理,可能未設定提供者?",
        "userjsispublic": "請注意:JavaScript 子頁面可被其他使用者檢視,不應包含機密資料。",
        "usercssispublic": "請注意:CSS 子頁面可被其他使用者檢視,不應包含機密資料。",
        "restrictionsfield-badip": "無效的 IP 位址或範圍:$1",
diff --git a/languages/messages/MessagesSkr.php b/languages/messages/MessagesSkr.php
new file mode 100644 (file)
index 0000000..2072b8d
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+/** Saraiki (multiple scripts)
+ *
+ * To improve a translation please visit https://translatewiki.net
+ *
+ * @ingroup Language
+ * @file
+ *
+ */
+
+$fallback = 'skr-arab';
diff --git a/languages/messages/MessagesSkr_arab.php b/languages/messages/MessagesSkr_arab.php
new file mode 100644 (file)
index 0000000..901e2aa
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+/** Saraiki (Arabic script) (سرائیکی)
+ *
+ * To improve a translation please visit https://translatewiki.net
+ *
+ * @ingroup Language
+ * @file
+ *
+ */
+
+$fallback = 'ur, pnb';
+
+$rtl = true;
index 478a0c4..04565f2 100644 (file)
@@ -344,7 +344,7 @@ abstract class Maintenance {
         * @return mixed
         */
        protected function getStdin( $len = null ) {
-               if ( $len == Maintenance::STDIN_ALL ) {
+               if ( $len == self::STDIN_ALL ) {
                        return file_get_contents( 'php://stdin' );
                }
                $f = fopen( 'php://stdin', 'rt' );
@@ -457,7 +457,7 @@ abstract class Maintenance {
         * @return int
         */
        public function getDbType() {
-               return Maintenance::DB_STD;
+               return self::DB_STD;
        }
 
        /**
index 2262338..b21bfbb 100644 (file)
@@ -39,7 +39,7 @@ class AddRFCAndPMIDInterwiki extends LoggedUpdateMaintenance {
        }
 
        protected function updateSkippedMessage() {
-               return 'RFC and PMID already added to interwiki database table';
+               return 'RFC and PMID already added to interwiki database table.';
        }
 
        protected function doDBUpdates() {
index 8a837d2..d273a6a 100644 (file)
@@ -140,9 +140,6 @@ class GenerateSitemap extends Maintenance {
         */
        private $identifier;
 
-       /**
-        * Constructor
-        */
        public function __construct() {
                parent::__construct();
                $this->addDescription( 'Creates a sitemap for the site' );
index cf0acde..9e9fd3e 100644 (file)
@@ -40,7 +40,6 @@ class CheckLanguageCLI {
        private $includeExif = false;
 
        /**
-        * Constructor.
         * @param array $options Options for script.
         */
        public function __construct( array $options ) {
@@ -557,7 +556,6 @@ class CheckExtensionsCLI extends CheckLanguageCLI {
        private $extensions;
 
        /**
-        * Constructor.
         * @param array $options Options for script.
         * @param string $extension The extension name (or names).
         */
diff --git a/maintenance/populatePPSortKey.php b/maintenance/populatePPSortKey.php
new file mode 100644 (file)
index 0000000..fd7974d
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Populate the pp_sortkey fields in the page_props table
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+require_once __DIR__ . '/Maintenance.php';
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Usage:
+ *  populatePPSortKey.php
+ */
+class PopulatePPSortKey extends LoggedUpdateMaintenance {
+       public function __construct() {
+               parent::__construct();
+               $this->addDescription( 'Populate the pp_sortkey field' );
+               $this->setBatchSize( 100 );
+       }
+
+       protected function doDBUpdates() {
+               $dbw = $this->getDB( DB_MASTER );
+
+               $lastProp = null;
+               $lastPageValue = 0;
+               $editedRowCount = 0;
+
+               while ( true ) {
+                       $conditions = [ 'pp_sortkey IS NULL' ];
+                       if ( $lastPageValue !== 0 ) {
+                               $conditions[] = 'pp_page > ' . $dbw->addQuotes( $lastPageValue ) . ' OR ' .
+                                       '( pp_page = ' . $dbw->addQuotes( $lastPageValue ) .
+                                       ' AND pp_propname > ' . $dbw->addQuotes( $lastProp ) . ' )';
+                       }
+
+                       $res = $dbw->select(
+                               'page_props',
+                               [ 'pp_propname', 'pp_page', 'pp_sortkey', 'pp_value' ],
+                               $conditions,
+                               __METHOD__,
+                               [
+                                       'ORDER BY' => 'pp_page, pp_propname',
+                                       'LIMIT' => $this->mBatchSize
+                               ]
+                       );
+
+                       if ( $res->numRows() === 0 ) {
+                               break;
+                       }
+
+                       $this->beginTransaction( $dbw, __METHOD__ );
+
+                       foreach ( $res as $row ) {
+                               if ( !is_numeric( $row->pp_value ) ) {
+                                       continue;
+                               }
+                               $dbw->update(
+                                       'page_props',
+                                       [ 'pp_sortkey' => $row->pp_value ],
+                                       [
+                                               'pp_page' => $row->pp_page,
+                                               'pp_propname' => $row->pp_propname
+                                       ],
+                                       __METHOD__
+                               );
+                               $editedRowCount++;
+                       }
+
+                       $this->output( "Updated " . $editedRowCount . " rows\n" );
+                       $this->commitTransaction( $dbw, __METHOD__ );
+
+                       // We need to get the last element's page ID
+                       $lastPageValue = $row->pp_page;
+                       // And the propname...
+                       $lastProp = $row->pp_propname;
+               }
+
+               $this->output( "Done!\n" );
+       }
+
+       protected function getUpdateKey() {
+               return 'populate pp_sortkey';
+       }
+}
+
+$maintClass = 'PopulatePPSortKey';
+require_once RUN_MAINTENANCE_IF_MAIN;
index 117e9cc..f14856a 100644 (file)
@@ -48,7 +48,7 @@ class Sqlite {
         * @return bool True if no error or error string in case of errors
         */
        public static function checkSqlSyntax( $files ) {
-               if ( !Sqlite::isPresent() ) {
+               if ( !self::isPresent() ) {
                        throw new MWException( "Can't check SQL syntax: SQLite not found" );
                }
                if ( !is_array( $files ) ) {
index 5d773d1..01cf3c3 100644 (file)
@@ -47,7 +47,7 @@ class UserOptions {
         */
        function __construct( $opts, $args ) {
                if ( !$this->checkOpts( $opts, $args ) ) {
-                       UserOptions::showUsageAndExit();
+                       self::showUsageAndExit();
                } else {
                        $this->mReady = $this->initializeOpts( $opts, $args );
                }
index 92a218a..d2e4fcc 100644 (file)
--- a/phpcs.xml
+++ b/phpcs.xml
        <arg name="bootstrap" value="vendor/mediawiki/mediawiki-codesniffer/utils/bootstrap-ci.php"/>
        <arg name="encoding" value="UTF-8"/>
        <arg name="extensions" value="php,php5,inc,sample"/>
-       <exclude-pattern>node_modules/</exclude-pattern>
-       <exclude-pattern>vendor/</exclude-pattern>
        <exclude-pattern type="relative">^extensions/</exclude-pattern>
        <exclude-pattern type="relative">^skins/</exclude-pattern>
-       <exclude-pattern>.git</exclude-pattern>
-       <exclude-pattern>AdminSettings.php</exclude-pattern>
-       <exclude-pattern>LocalSettings.php</exclude-pattern>
-       <exclude-pattern>StartProfiler.php</exclude-pattern>
+       <exclude-pattern>AdminSettings\.php</exclude-pattern>
+       <exclude-pattern>LocalSettings\.php</exclude-pattern>
+       <exclude-pattern>StartProfiler\.php</exclude-pattern>
 </ruleset>
index 33fb8f1..f725efe 100644 (file)
@@ -1060,6 +1060,9 @@ return [
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.hlist' => [
+               'styles' => [
+                       'resources/src/mediawiki/mediawiki.hlist-allskins.less',
+               ],
                'skinStyles' => [
                        'default' => 'resources/src/mediawiki/mediawiki.hlist.css',
                ],
index ae68fc4..4749222 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * QUnit 1.23.1
+ * QUnit 2.4.0
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2016-04-12T17:29Z
+ * Date: 2017-07-08T15:20Z
  */
 
 /** Font Family and Sizes */
@@ -27,7 +27,7 @@
 }
 
 
-/** Header */
+/** Header (excluding toolbar) */
 
 #qunit-header {
        padding: 0.5em 0 0.5em 1em;
        color: #FFF;
 }
 
-#qunit-testrunner-toolbar label {
-       display: inline-block;
-       padding: 0 0.5em 0 0.1em;
-}
-
 #qunit-banner {
        height: 5px;
 }
 
-#qunit-testrunner-toolbar {
-       padding: 0.5em 1em 0.5em 1em;
-       color: #5E740B;
-       background-color: #EEE;
-       overflow: hidden;
-}
-
 #qunit-filteredTest {
        padding: 0.5em 1em 0.5em 1em;
-       background-color: #F4FF77;
        color: #366097;
+       background-color: #F4FF77;
 }
 
 #qunit-userAgent {
        padding: 0.5em 1em 0.5em 1em;
-       background-color: #2B81AF;
        color: #FFF;
+       background-color: #2B81AF;
        text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
 }
 
-#qunit-modulefilter-container {
-       float: right;
-       padding: 0.2em;
+
+/** Toolbar */
+
+#qunit-testrunner-toolbar {
+       padding: 0.5em 1em 0.5em 1em;
+       color: #5E740B;
+       background-color: #EEE;
+}
+
+#qunit-testrunner-toolbar .clearfix {
+       height: 0;
+       clear: both;
 }
 
-.qunit-url-config {
+#qunit-testrunner-toolbar label {
        display: inline-block;
-       padding: 0.1em;
 }
 
-.qunit-filter {
-       display: block;
+#qunit-testrunner-toolbar input[type=checkbox],
+#qunit-testrunner-toolbar input[type=radio] {
+       margin: 3px;
+       vertical-align: -2px;
+}
+
+#qunit-testrunner-toolbar input[type=text] {
+       box-sizing: border-box;
+       height: 1.6em;
+}
+
+.qunit-url-config,
+.qunit-filter,
+#qunit-modulefilter {
+       display: inline-block;
+       line-height: 2.1em;
+}
+
+.qunit-filter,
+#qunit-modulefilter {
        float: right;
+       position: relative;
        margin-left: 1em;
 }
 
+.qunit-url-config label {
+       margin-right: 0.5em;
+}
+
+#qunit-modulefilter-search {
+       box-sizing: border-box;
+       width: 400px;
+}
+
+#qunit-modulefilter-search-container:after {
+       position: absolute;
+       right: 0.3em;
+       content: "\25bc";
+       color: black;
+}
+
+#qunit-modulefilter-dropdown {
+       /* align with #qunit-modulefilter-search */
+       box-sizing: border-box;
+       width: 400px;
+       position: absolute;
+       right: 0;
+       top: 50%;
+       margin-top: 0.8em;
+
+       border: 1px solid #D3D3D3;
+       border-top: none;
+       border-radius: 0 0 .25em .25em;
+       color: #000;
+       background-color: #F5F5F5;
+       z-index: 99;
+}
+
+#qunit-modulefilter-dropdown a {
+       color: inherit;
+       text-decoration: none;
+}
+
+#qunit-modulefilter-dropdown .clickable.checked {
+       font-weight: bold;
+       color: #000;
+       background-color: #D2E0E6;
+}
+
+#qunit-modulefilter-dropdown .clickable:hover {
+       color: #FFF;
+       background-color: #0D3349;
+}
+
+#qunit-modulefilter-actions {
+       display: block;
+       overflow: auto;
+
+       /* align with #qunit-modulefilter-dropdown-list */
+       font: smaller/1.5em sans-serif;
+}
+
+#qunit-modulefilter-dropdown #qunit-modulefilter-actions > * {
+       box-sizing: border-box;
+       max-height: 2.8em;
+       display: block;
+       padding: 0.4em;
+}
+
+#qunit-modulefilter-dropdown #qunit-modulefilter-actions > button {
+       float: right;
+       font: inherit;
+}
+
+#qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child {
+       /* insert padding to align with checkbox margins */
+       padding-left: 3px;
+}
+
+#qunit-modulefilter-dropdown-list {
+       max-height: 200px;
+       overflow-y: auto;
+       margin: 0;
+       border-top: 2px groove threedhighlight;
+       padding: 0.4em 0 0;
+       font: smaller/1.5em sans-serif;
+}
+
+#qunit-modulefilter-dropdown-list li {
+       white-space: nowrap;
+       overflow: hidden;
+       text-overflow: ellipsis;
+}
+
+#qunit-modulefilter-dropdown-list .clickable {
+       display: block;
+       padding-left: 0.15em;
+}
+
+
 /** Tests: Pass/Fail */
 
 #qunit-tests {
 #qunit-tests li.running,
 #qunit-tests li.pass,
 #qunit-tests li.fail,
-#qunit-tests li.skipped {
+#qunit-tests li.skipped,
+#qunit-tests li.aborted {
        display: list-item;
 }
 
 }
 
 #qunit-tests.hidepass li.running,
-#qunit-tests.hidepass li.pass {
+#qunit-tests.hidepass li.pass:not(.todo) {
        visibility: hidden;
        position: absolute;
        width:   0;
 }
 
 #qunit-tests del {
-       background-color: #E0F2BE;
        color: #374E0C;
+       background-color: #E0F2BE;
        text-decoration: none;
 }
 
 #qunit-tests ins {
-       background-color: #FFCACA;
        color: #500;
+       background-color: #FFCACA;
        text-decoration: none;
 }
 
 
 #qunit-banner.qunit-fail                    { background-color: #EE5757; }
 
+
+/*** Aborted tests */
+#qunit-tests .aborted { color: #000; background-color: orange; }
 /*** Skipped tests */
 
 #qunit-tests .skipped {
        background-color: #EBECE9;
 }
 
+#qunit-tests .qunit-todo-label,
 #qunit-tests .qunit-skipped-label {
        background-color: #F4FF77;
        display: inline-block;
        margin: -0.4em 0.4em -0.4em 0;
 }
 
+#qunit-tests .qunit-todo-label {
+       background-color: #EEE;
+}
+
 /** Result */
 
 #qunit-testresult {
-       padding: 0.5em 1em 0.5em 1em;
-
        color: #2B81AF;
        background-color: #D2E0E6;
 
        border-bottom: 1px solid #FFF;
 }
+#qunit-testresult .clearfix {
+       height: 0;
+       clear: both;
+}
 #qunit-testresult .module-name {
        font-weight: 700;
 }
+#qunit-testresult-display {
+       padding: 0.5em 1em 0.5em 1em;
+       width: 85%;
+       float:left;
+}
+#qunit-testresult-controls {
+       padding: 0.5em 1em 0.5em 1em;
+  width: 10%;
+       float:left;
+}
 
 /** Fixture */
 
index 5df0822..bb8f31d 100644 (file)
 /*!
- * QUnit 1.23.1
+ * QUnit 2.4.0
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2016-04-12T17:29Z
+ * Date: 2017-07-08T15:20Z
  */
+(function (global$1) {
+  'use strict';
 
-( function( global ) {
-
-var QUnit = {};
-
-var Date = global.Date;
-var now = Date.now || function() {
-       return new Date().getTime();
-};
-
-var setTimeout = global.setTimeout;
-var clearTimeout = global.clearTimeout;
-
-// Store a local window from the global to allow direct references.
-var window = global.window;
-
-var defined = {
-       document: window && window.document !== undefined,
-       setTimeout: setTimeout !== undefined,
-       sessionStorage: ( function() {
-               var x = "qunit-test-string";
-               try {
-                       sessionStorage.setItem( x, x );
-                       sessionStorage.removeItem( x );
-                       return true;
-               } catch ( e ) {
-                       return false;
-               }
-       }() )
-};
-
-var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
-var globalStartCalled = false;
-var runStarted = false;
-
-var toString = Object.prototype.toString,
-       hasOwn = Object.prototype.hasOwnProperty;
-
-// Returns a new Array with the elements that are in a but not in b
-function diff( a, b ) {
-       var i, j,
-               result = a.slice();
-
-       for ( i = 0; i < result.length; i++ ) {
-               for ( j = 0; j < b.length; j++ ) {
-                       if ( result[ i ] === b[ j ] ) {
-                               result.splice( i, 1 );
-                               i--;
-                               break;
-                       }
-               }
-       }
-       return result;
-}
-
-// From jquery.js
-function inArray( elem, array ) {
-       if ( array.indexOf ) {
-               return array.indexOf( elem );
-       }
-
-       for ( var i = 0, length = array.length; i < length; i++ ) {
-               if ( array[ i ] === elem ) {
-                       return i;
-               }
-       }
-
-       return -1;
-}
-
-/**
- * Makes a clone of an object using only Array or Object as base,
- * and copies over the own enumerable properties.
- *
- * @param {Object} obj
- * @return {Object} New object with only the own properties (recursively).
- */
-function objectValues ( obj ) {
-       var key, val,
-               vals = QUnit.is( "array", obj ) ? [] : {};
-       for ( key in obj ) {
-               if ( hasOwn.call( obj, key ) ) {
-                       val = obj[ key ];
-                       vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
-               }
-       }
-       return vals;
-}
-
-function extend( a, b, undefOnly ) {
-       for ( var prop in b ) {
-               if ( hasOwn.call( b, prop ) ) {
-
-                       // Avoid "Member not found" error in IE8 caused by messing with window.constructor
-                       // This block runs on every environment, so `global` is being used instead of `window`
-                       // to avoid errors on node.
-                       if ( prop !== "constructor" || a !== global ) {
-                               if ( b[ prop ] === undefined ) {
-                                       delete a[ prop ];
-                               } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
-                                       a[ prop ] = b[ prop ];
-                               }
-                       }
-               }
-       }
-
-       return a;
-}
-
-function objectType( obj ) {
-       if ( typeof obj === "undefined" ) {
-               return "undefined";
-       }
-
-       // Consider: typeof null === object
-       if ( obj === null ) {
-               return "null";
-       }
-
-       var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
-               type = match && match[ 1 ];
-
-       switch ( type ) {
-               case "Number":
-                       if ( isNaN( obj ) ) {
-                               return "nan";
-                       }
-                       return "number";
-               case "String":
-               case "Boolean":
-               case "Array":
-               case "Set":
-               case "Map":
-               case "Date":
-               case "RegExp":
-               case "Function":
-               case "Symbol":
-                       return type.toLowerCase();
-       }
-       if ( typeof obj === "object" ) {
-               return "object";
-       }
-}
-
-// Safe object type checking
-function is( type, obj ) {
-       return QUnit.objectType( obj ) === type;
-}
-
-// Doesn't support IE6 to IE9, it will return undefined on these browsers
-// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
-function extractStacktrace( e, offset ) {
-       offset = offset === undefined ? 4 : offset;
-
-       var stack, include, i;
-
-       if ( e.stack ) {
-               stack = e.stack.split( "\n" );
-               if ( /^error$/i.test( stack[ 0 ] ) ) {
-                       stack.shift();
-               }
-               if ( fileName ) {
-                       include = [];
-                       for ( i = offset; i < stack.length; i++ ) {
-                               if ( stack[ i ].indexOf( fileName ) !== -1 ) {
-                                       break;
-                               }
-                               include.push( stack[ i ] );
-                       }
-                       if ( include.length ) {
-                               return include.join( "\n" );
-                       }
-               }
-               return stack[ offset ];
-
-       // Support: Safari <=6 only
-       } else if ( e.sourceURL ) {
-
-               // Exclude useless self-reference for generated Error objects
-               if ( /qunit.js$/.test( e.sourceURL ) ) {
-                       return;
-               }
-
-               // For actual exceptions, this is useful
-               return e.sourceURL + ":" + e.line;
-       }
-}
-
-function sourceFromStacktrace( offset ) {
-       var error = new Error();
-
-       // Support: Safari <=7 only, IE <=10 - 11 only
-       // Not all browsers generate the `stack` property for `new Error()`, see also #636
-       if ( !error.stack ) {
-               try {
-                       throw error;
-               } catch ( err ) {
-                       error = err;
-               }
-       }
-
-       return extractStacktrace( error, offset );
-}
-
-/**
- * Config object: Maintain internal state
- * Later exposed as QUnit.config
- * `config` initialized at top of scope
- */
-var config = {
+  global$1 = global$1 && 'default' in global$1 ? global$1['default'] : global$1;
 
-       // The queue of tests to run
-       queue: [],
+  var window = global$1.window;
+  var self$1 = global$1.self;
+  var console = global$1.console;
+  var setTimeout = global$1.setTimeout;
+  var clearTimeout = global$1.clearTimeout;
 
-       // Block until document ready
-       blocking: true,
+  var document = window && window.document;
+  var navigator = window && window.navigator;
 
-       // By default, run previously failed tests first
-       // very useful in combination with "Hide passed tests" checked
-       reorder: true,
+  var localSessionStorage = function () {
+       var x = "qunit-test-string";
+       try {
+               global$1.sessionStorage.setItem(x, x);
+               global$1.sessionStorage.removeItem(x);
+               return global$1.sessionStorage;
+       } catch (e) {
+               return undefined;
+       }
+  }();
 
-       // By default, modify document.title when suite is done
-       altertitle: true,
+  var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
+    return typeof obj;
+  } : function (obj) {
+    return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+  };
 
-       // HTML Reporter: collapse every test except the first failing test
-       // If false, all failing tests will be expanded
-       collapse: true,
 
-       // By default, scroll to top of the page when suite is done
-       scrolltop: true,
 
-       // Depth up-to which object will be dumped
-       maxDepth: 5,
 
-       // When enabled, all tests must call expect()
-       requireExpects: false,
 
-       // Placeholder for user-configurable form-exposed URL parameters
-       urlConfig: [],
 
-       // Set of all modules.
-       modules: [],
 
-       // Stack of nested modules
-       moduleStack: [],
 
-       // The first unnamed module
-       currentModule: {
-               name: "",
-               tests: []
-       },
 
-       callbacks: {}
-};
-
-// Push a loose unnamed module to the modules collection
-config.modules.push( config.currentModule );
-
-var loggingCallbacks = {};
-
-// Register logging callbacks
-function registerLoggingCallbacks( obj ) {
-       var i, l, key,
-               callbackNames = [ "begin", "done", "log", "testStart", "testDone",
-                       "moduleStart", "moduleDone" ];
-
-       function registerLoggingCallback( key ) {
-               var loggingCallback = function( callback ) {
-                       if ( objectType( callback ) !== "function" ) {
-                               throw new Error(
-                                       "QUnit logging methods require a callback function as their first parameters."
-                               );
-                       }
-
-                       config.callbacks[ key ].push( callback );
-               };
-
-               // DEPRECATED: This will be removed on QUnit 2.0.0+
-               // Stores the registered functions allowing restoring
-               // at verifyLoggingCallbacks() if modified
-               loggingCallbacks[ key ] = loggingCallback;
-
-               return loggingCallback;
-       }
-
-       for ( i = 0, l = callbackNames.length; i < l; i++ ) {
-               key = callbackNames[ i ];
-
-               // Initialize key collection of logging callback
-               if ( objectType( config.callbacks[ key ] ) === "undefined" ) {
-                       config.callbacks[ key ] = [];
-               }
-
-               obj[ key ] = registerLoggingCallback( key );
-       }
-}
-
-function runLoggingCallbacks( key, args ) {
-       var i, l, callbacks;
-
-       callbacks = config.callbacks[ key ];
-       for ( i = 0, l = callbacks.length; i < l; i++ ) {
-               callbacks[ i ]( args );
-       }
-}
-
-// DEPRECATED: This will be removed on 2.0.0+
-// This function verifies if the loggingCallbacks were modified by the user
-// If so, it will restore it, assign the given callback and print a console warning
-function verifyLoggingCallbacks() {
-       var loggingCallback, userCallback;
-
-       for ( loggingCallback in loggingCallbacks ) {
-               if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
-
-                       userCallback = QUnit[ loggingCallback ];
-
-                       // Restore the callback function
-                       QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
-
-                       // Assign the deprecated given callback
-                       QUnit[ loggingCallback ]( userCallback );
-
-                       if ( global.console && global.console.warn ) {
-                               global.console.warn(
-                                       "QUnit." + loggingCallback + " was replaced with a new value.\n" +
-                                       "Please, check out the documentation on how to apply logging callbacks.\n" +
-                                       "Reference: https://api.qunitjs.com/category/callbacks/"
-                               );
-                       }
-               }
-       }
-}
-
-( function() {
-       if ( !defined.document ) {
-               return;
-       }
-
-       // `onErrorFnPrev` initialized at top of scope
-       // Preserve other handlers
-       var onErrorFnPrev = window.onerror;
-
-       // Cover uncaught exceptions
-       // Returning true will suppress the default browser handler,
-       // returning false will let it run.
-       window.onerror = function( error, filePath, linerNr ) {
-               var ret = false;
-               if ( onErrorFnPrev ) {
-                       ret = onErrorFnPrev( error, filePath, linerNr );
-               }
-
-               // Treat return value as window.onerror itself does,
-               // Only do our handling if not suppressed.
-               if ( ret !== true ) {
-                       if ( QUnit.config.current ) {
-                               if ( QUnit.config.current.ignoreGlobalErrors ) {
-                                       return true;
-                               }
-                               QUnit.pushFailure( error, filePath + ":" + linerNr );
-                       } else {
-                               QUnit.test( "global failure", extend( function() {
-                                       QUnit.pushFailure( error, filePath + ":" + linerNr );
-                               }, { validTest: true } ) );
-                       }
-                       return false;
-               }
-
-               return ret;
-       };
-}() );
-
-// Figure out if we're running the tests from a server or not
-QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
-
-// Expose the current QUnit version
-QUnit.version = "1.23.1";
-
-extend( QUnit, {
-
-       // Call on start of module test to prepend name to all tests
-       module: function( name, testEnvironment, executeNow ) {
-               var module, moduleFns;
-               var currentModule = config.currentModule;
-
-               if ( arguments.length === 2 ) {
-                       if ( objectType( testEnvironment ) === "function" ) {
-                               executeNow = testEnvironment;
-                               testEnvironment = undefined;
-                       }
-               }
-
-               // DEPRECATED: handles setup/teardown functions,
-               // beforeEach and afterEach should be used instead
-               if ( testEnvironment && testEnvironment.setup ) {
-                       testEnvironment.beforeEach = testEnvironment.setup;
-                       delete testEnvironment.setup;
-               }
-               if ( testEnvironment && testEnvironment.teardown ) {
-                       testEnvironment.afterEach = testEnvironment.teardown;
-                       delete testEnvironment.teardown;
-               }
-
-               module = createModule();
-
-               moduleFns = {
-                       beforeEach: setHook( module, "beforeEach" ),
-                       afterEach: setHook( module, "afterEach" )
-               };
-
-               if ( objectType( executeNow ) === "function" ) {
-                       config.moduleStack.push( module );
-                       setCurrentModule( module );
-                       executeNow.call( module.testEnvironment, moduleFns );
-                       config.moduleStack.pop();
-                       module = module.parentModule || currentModule;
-               }
-
-               setCurrentModule( module );
-
-               function createModule() {
-                       var parentModule = config.moduleStack.length ?
-                               config.moduleStack.slice( -1 )[ 0 ] : null;
-                       var moduleName = parentModule !== null ?
-                               [ parentModule.name, name ].join( " > " ) : name;
-                       var module = {
-                               name: moduleName,
-                               parentModule: parentModule,
-                               tests: [],
-                               moduleId: generateHash( moduleName )
-                       };
-
-                       var env = {};
-                       if ( parentModule ) {
-                               extend( env, parentModule.testEnvironment );
-                               delete env.beforeEach;
-                               delete env.afterEach;
-                       }
-                       extend( env, testEnvironment );
-                       module.testEnvironment = env;
-
-                       config.modules.push( module );
-                       return module;
-               }
-
-               function setCurrentModule( module ) {
-                       config.currentModule = module;
-               }
-
-       },
-
-       // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
-       asyncTest: asyncTest,
-
-       test: test,
-
-       skip: skip,
-
-       only: only,
-
-       // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
-       // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
-       start: function( count ) {
-               var globalStartAlreadyCalled = globalStartCalled;
-
-               if ( !config.current ) {
-                       globalStartCalled = true;
-
-                       if ( runStarted ) {
-                               throw new Error( "Called start() outside of a test context while already started" );
-                       } else if ( globalStartAlreadyCalled || count > 1 ) {
-                               throw new Error( "Called start() outside of a test context too many times" );
-                       } else if ( config.autostart ) {
-                               throw new Error( "Called start() outside of a test context when " +
-                                       "QUnit.config.autostart was true" );
-                       } else if ( !config.pageLoaded ) {
-
-                               // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
-                               config.autostart = true;
-                               return;
-                       }
-               } else {
-
-                       // If a test is running, adjust its semaphore
-                       config.current.semaphore -= count || 1;
-
-                       // If semaphore is non-numeric, throw error
-                       if ( isNaN( config.current.semaphore ) ) {
-                               config.current.semaphore = 0;
-
-                               QUnit.pushFailure(
-                                       "Called start() with a non-numeric decrement.",
-                                       sourceFromStacktrace( 2 )
-                               );
-                               return;
-                       }
-
-                       // Don't start until equal number of stop-calls
-                       if ( config.current.semaphore > 0 ) {
-                               return;
-                       }
-
-                       // Throw an Error if start is called more often than stop
-                       if ( config.current.semaphore < 0 ) {
-                               config.current.semaphore = 0;
-
-                               QUnit.pushFailure(
-                                       "Called start() while already started (test's semaphore was 0 already)",
-                                       sourceFromStacktrace( 2 )
-                               );
-                               return;
-                       }
-               }
-
-               resumeProcessing();
-       },
-
-       // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
-       stop: function( count ) {
-
-               // If there isn't a test running, don't allow QUnit.stop() to be called
-               if ( !config.current ) {
-                       throw new Error( "Called stop() outside of a test context" );
-               }
-
-               // If a test is running, adjust its semaphore
-               config.current.semaphore += count || 1;
-
-               pauseProcessing();
-       },
-
-       config: config,
-
-       is: is,
-
-       objectType: objectType,
-
-       extend: extend,
-
-       load: function() {
-               config.pageLoaded = true;
-
-               // Initialize the configuration options
-               extend( config, {
-                       stats: { all: 0, bad: 0 },
-                       moduleStats: { all: 0, bad: 0 },
-                       started: 0,
-                       updateRate: 1000,
-                       autostart: true,
-                       filter: ""
-               }, true );
-
-               config.blocking = false;
-
-               if ( config.autostart ) {
-                       resumeProcessing();
-               }
-       },
-
-       stack: function( offset ) {
-               offset = ( offset || 0 ) + 2;
-               return sourceFromStacktrace( offset );
-       }
-} );
-
-registerLoggingCallbacks( QUnit );
-
-function begin() {
-       var i, l,
-               modulesLog = [];
-
-       // If the test run hasn't officially begun yet
-       if ( !config.started ) {
-
-               // Record the time of the test run's beginning
-               config.started = now();
-
-               verifyLoggingCallbacks();
-
-               // Delete the loose unnamed module if unused.
-               if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
-                       config.modules.shift();
-               }
-
-               // Avoid unnecessary information by not logging modules' test environments
-               for ( i = 0, l = config.modules.length; i < l; i++ ) {
-                       modulesLog.push( {
-                               name: config.modules[ i ].name,
-                               tests: config.modules[ i ].tests
-                       } );
-               }
-
-               // The test run is officially beginning now
-               runLoggingCallbacks( "begin", {
-                       totalTests: Test.count,
-                       modules: modulesLog
-               } );
-       }
-
-       config.blocking = false;
-       process( true );
-}
-
-function process( last ) {
-       function next() {
-               process( last );
-       }
-       var start = now();
-       config.depth = ( config.depth || 0 ) + 1;
-
-       while ( config.queue.length && !config.blocking ) {
-               if ( !defined.setTimeout || config.updateRate <= 0 ||
-                               ( ( now() - start ) < config.updateRate ) ) {
-                       if ( config.current ) {
-
-                               // Reset async tracking for each phase of the Test lifecycle
-                               config.current.usedAsync = false;
-                       }
-                       config.queue.shift()();
-               } else {
-                       setTimeout( next, 13 );
-                       break;
-               }
-       }
-       config.depth--;
-       if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
-               done();
-       }
-}
-
-function pauseProcessing() {
-       config.blocking = true;
-
-       if ( config.testTimeout && defined.setTimeout ) {
-               clearTimeout( config.timeout );
-               config.timeout = setTimeout( function() {
-                       if ( config.current ) {
-                               config.current.semaphore = 0;
-                               QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
-                       } else {
-                               throw new Error( "Test timed out" );
-                       }
-                       resumeProcessing();
-               }, config.testTimeout );
-       }
-}
-
-function resumeProcessing() {
-       runStarted = true;
-
-       // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
-       if ( defined.setTimeout ) {
-               setTimeout( function() {
-                       if ( config.current && config.current.semaphore > 0 ) {
-                               return;
-                       }
-                       if ( config.timeout ) {
-                               clearTimeout( config.timeout );
-                       }
-
-                       begin();
-               }, 13 );
-       } else {
-               begin();
-       }
-}
-
-function done() {
-       var runtime, passed;
-
-       config.autorun = true;
-
-       // Log the last module results
-       if ( config.previousModule ) {
-               runLoggingCallbacks( "moduleDone", {
-                       name: config.previousModule.name,
-                       tests: config.previousModule.tests,
-                       failed: config.moduleStats.bad,
-                       passed: config.moduleStats.all - config.moduleStats.bad,
-                       total: config.moduleStats.all,
-                       runtime: now() - config.moduleStats.started
-               } );
-       }
-       delete config.previousModule;
-
-       runtime = now() - config.started;
-       passed = config.stats.all - config.stats.bad;
-
-       runLoggingCallbacks( "done", {
-               failed: config.stats.bad,
-               passed: passed,
-               total: config.stats.all,
-               runtime: runtime
-       } );
-}
-
-function setHook( module, hookName ) {
-       if ( module.testEnvironment === undefined ) {
-               module.testEnvironment = {};
-       }
-
-       return function( callback ) {
-               module.testEnvironment[ hookName ] = callback;
-       };
-}
-
-var focused = false;
-var priorityCount = 0;
-var unitSampler;
-
-function Test( settings ) {
-       var i, l;
-
-       ++Test.count;
-
-       extend( this, settings );
-       this.assertions = [];
-       this.semaphore = 0;
-       this.usedAsync = false;
-       this.module = config.currentModule;
-       this.stack = sourceFromStacktrace( 3 );
-
-       // Register unique strings
-       for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
-               if ( this.module.tests[ i ].name === this.testName ) {
-                       this.testName += " ";
-               }
-       }
-
-       this.testId = generateHash( this.module.name, this.testName );
-
-       this.module.tests.push( {
-               name: this.testName,
-               testId: this.testId
-       } );
-
-       if ( settings.skip ) {
-
-               // Skipped tests will fully ignore any sent callback
-               this.callback = function() {};
-               this.async = false;
-               this.expected = 0;
-       } else {
-               this.assert = new Assert( this );
-       }
-}
-
-Test.count = 0;
-
-Test.prototype = {
-       before: function() {
-               if (
-
-                       // Emit moduleStart when we're switching from one module to another
-                       this.module !== config.previousModule ||
-
-                               // They could be equal (both undefined) but if the previousModule property doesn't
-                               // yet exist it means this is the first test in a suite that isn't wrapped in a
-                               // module, in which case we'll just emit a moduleStart event for 'undefined'.
-                               // Without this, reporters can get testStart before moduleStart  which is a problem.
-                               !hasOwn.call( config, "previousModule" )
-               ) {
-                       if ( hasOwn.call( config, "previousModule" ) ) {
-                               runLoggingCallbacks( "moduleDone", {
-                                       name: config.previousModule.name,
-                                       tests: config.previousModule.tests,
-                                       failed: config.moduleStats.bad,
-                                       passed: config.moduleStats.all - config.moduleStats.bad,
-                                       total: config.moduleStats.all,
-                                       runtime: now() - config.moduleStats.started
-                               } );
-                       }
-                       config.previousModule = this.module;
-                       config.moduleStats = { all: 0, bad: 0, started: now() };
-                       runLoggingCallbacks( "moduleStart", {
-                               name: this.module.name,
-                               tests: this.module.tests
-                       } );
-               }
-
-               config.current = this;
-
-               if ( this.module.testEnvironment ) {
-                       delete this.module.testEnvironment.beforeEach;
-                       delete this.module.testEnvironment.afterEach;
-               }
-               this.testEnvironment = extend( {}, this.module.testEnvironment );
-
-               this.started = now();
-               runLoggingCallbacks( "testStart", {
-                       name: this.testName,
-                       module: this.module.name,
-                       testId: this.testId
-               } );
-
-               if ( !config.pollution ) {
-                       saveGlobal();
-               }
-       },
-
-       run: function() {
-               var promise;
-
-               config.current = this;
-
-               if ( this.async ) {
-                       QUnit.stop();
-               }
-
-               this.callbackStarted = now();
-
-               if ( config.notrycatch ) {
-                       runTest( this );
-                       return;
-               }
-
-               try {
-                       runTest( this );
-               } catch ( e ) {
-                       this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
-                               this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
-
-                       // Else next test will carry the responsibility
-                       saveGlobal();
-
-                       // Restart the tests if they're blocking
-                       if ( config.blocking ) {
-                               QUnit.start();
-                       }
-               }
-
-               function runTest( test ) {
-                       promise = test.callback.call( test.testEnvironment, test.assert );
-                       test.resolvePromise( promise );
-               }
-       },
-
-       after: function() {
-               checkPollution();
-       },
-
-       queueHook: function( hook, hookName ) {
-               var promise,
-                       test = this;
-               return function runHook() {
-                       config.current = test;
-                       if ( config.notrycatch ) {
-                               callHook();
-                               return;
-                       }
-                       try {
-                               callHook();
-                       } catch ( error ) {
-                               test.pushFailure( hookName + " failed on " + test.testName + ": " +
-                               ( error.message || error ), extractStacktrace( error, 0 ) );
-                       }
-
-                       function callHook() {
-                               promise = hook.call( test.testEnvironment, test.assert );
-                               test.resolvePromise( promise, hookName );
-                       }
-               };
-       },
-
-       // Currently only used for module level hooks, can be used to add global level ones
-       hooks: function( handler ) {
-               var hooks = [];
-
-               function processHooks( test, module ) {
-                       if ( module.parentModule ) {
-                               processHooks( test, module.parentModule );
-                       }
-                       if ( module.testEnvironment &&
-                               QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
-                               hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) );
-                       }
-               }
-
-               // Hooks are ignored on skipped tests
-               if ( !this.skip ) {
-                       processHooks( this, this.module );
-               }
-               return hooks;
-       },
-
-       finish: function() {
-               config.current = this;
-               if ( config.requireExpects && this.expected === null ) {
-                       this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
-                               "not called.", this.stack );
-               } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
-                       this.pushFailure( "Expected " + this.expected + " assertions, but " +
-                               this.assertions.length + " were run", this.stack );
-               } else if ( this.expected === null && !this.assertions.length ) {
-                       this.pushFailure( "Expected at least one assertion, but none were run - call " +
-                               "expect(0) to accept zero assertions.", this.stack );
-               }
-
-               var i,
-                       bad = 0;
-
-               this.runtime = now() - this.started;
-               config.stats.all += this.assertions.length;
-               config.moduleStats.all += this.assertions.length;
-
-               for ( i = 0; i < this.assertions.length; i++ ) {
-                       if ( !this.assertions[ i ].result ) {
-                               bad++;
-                               config.stats.bad++;
-                               config.moduleStats.bad++;
-                       }
-               }
-
-               runLoggingCallbacks( "testDone", {
-                       name: this.testName,
-                       module: this.module.name,
-                       skipped: !!this.skip,
-                       failed: bad,
-                       passed: this.assertions.length - bad,
-                       total: this.assertions.length,
-                       runtime: this.runtime,
-
-                       // HTML Reporter use
-                       assertions: this.assertions,
-                       testId: this.testId,
-
-                       // Source of Test
-                       source: this.stack,
-
-                       // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
-                       duration: this.runtime
-               } );
-
-               // QUnit.reset() is deprecated and will be replaced for a new
-               // fixture reset function on QUnit 2.0/2.1.
-               // It's still called here for backwards compatibility handling
-               QUnit.reset();
-
-               config.current = undefined;
-       },
-
-       queue: function() {
-               var priority,
-                       test = this;
-
-               if ( !this.valid() ) {
-                       return;
-               }
-
-               function run() {
-
-                       // Each of these can by async
-                       synchronize( [
-                               function() {
-                                       test.before();
-                               },
-
-                               test.hooks( "beforeEach" ),
-                               function() {
-                                       test.run();
-                               },
-
-                               test.hooks( "afterEach" ).reverse(),
-
-                               function() {
-                                       test.after();
-                               },
-                               function() {
-                                       test.finish();
-                               }
-                       ] );
-               }
-
-               // Prioritize previously failed tests, detected from sessionStorage
-               priority = QUnit.config.reorder && defined.sessionStorage &&
-                               +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
-
-               return synchronize( run, priority, config.seed );
-       },
-
-       pushResult: function( resultInfo ) {
-
-               // Destructure of resultInfo = { result, actual, expected, message, negative }
-               var source,
-                       details = {
-                               module: this.module.name,
-                               name: this.testName,
-                               result: resultInfo.result,
-                               message: resultInfo.message,
-                               actual: resultInfo.actual,
-                               expected: resultInfo.expected,
-                               testId: this.testId,
-                               negative: resultInfo.negative || false,
-                               runtime: now() - this.started
-                       };
-
-               if ( !resultInfo.result ) {
-                       source = sourceFromStacktrace();
-
-                       if ( source ) {
-                               details.source = source;
-                       }
-               }
-
-               runLoggingCallbacks( "log", details );
-
-               this.assertions.push( {
-                       result: !!resultInfo.result,
-                       message: resultInfo.message
-               } );
-       },
-
-       pushFailure: function( message, source, actual ) {
-               if ( !( this instanceof Test ) ) {
-                       throw new Error( "pushFailure() assertion outside test context, was " +
-                               sourceFromStacktrace( 2 ) );
-               }
-
-               var details = {
-                               module: this.module.name,
-                               name: this.testName,
-                               result: false,
-                               message: message || "error",
-                               actual: actual || null,
-                               testId: this.testId,
-                               runtime: now() - this.started
-                       };
-
-               if ( source ) {
-                       details.source = source;
-               }
-
-               runLoggingCallbacks( "log", details );
-
-               this.assertions.push( {
-                       result: false,
-                       message: message
-               } );
-       },
-
-       resolvePromise: function( promise, phase ) {
-               var then, message,
-                       test = this;
-               if ( promise != null ) {
-                       then = promise.then;
-                       if ( QUnit.objectType( then ) === "function" ) {
-                               QUnit.stop();
-                               then.call(
-                                       promise,
-                                       function() { QUnit.start(); },
-                                       function( error ) {
-                                               message = "Promise rejected " +
-                                                       ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
-                                                       " " + test.testName + ": " + ( error.message || error );
-                                               test.pushFailure( message, extractStacktrace( error, 0 ) );
-
-                                               // Else next test will carry the responsibility
-                                               saveGlobal();
-
-                                               // Unblock
-                                               QUnit.start();
-                                       }
-                               );
-                       }
-               }
-       },
-
-       valid: function() {
-               var filter = config.filter,
-                       regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ),
-                       module = config.module && config.module.toLowerCase(),
-                       fullName = ( this.module.name + ": " + this.testName );
-
-               function moduleChainNameMatch( testModule ) {
-                       var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
-                       if ( testModuleName === module ) {
-                               return true;
-                       } else if ( testModule.parentModule ) {
-                               return moduleChainNameMatch( testModule.parentModule );
-                       } else {
-                               return false;
-                       }
-               }
-
-               function moduleChainIdMatch( testModule ) {
-                       return inArray( testModule.moduleId, config.moduleId ) > -1 ||
-                               testModule.parentModule && moduleChainIdMatch( testModule.parentModule );
-               }
-
-               // Internally-generated tests are always valid
-               if ( this.callback && this.callback.validTest ) {
-                       return true;
-               }
-
-               if ( config.moduleId && config.moduleId.length > 0 &&
-                       !moduleChainIdMatch( this.module ) ) {
-
-                       return false;
-               }
-
-               if ( config.testId && config.testId.length > 0 &&
-                       inArray( this.testId, config.testId ) < 0 ) {
-
-                       return false;
-               }
-
-               if ( module && !moduleChainNameMatch( this.module ) ) {
-                       return false;
-               }
-
-               if ( !filter ) {
-                       return true;
-               }
-
-               return regexFilter ?
-                       this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) :
-                       this.stringFilter( filter, fullName );
-       },
-
-       regexFilter: function( exclude, pattern, flags, fullName ) {
-               var regex = new RegExp( pattern, flags );
-               var match = regex.test( fullName );
-
-               return match !== exclude;
-       },
-
-       stringFilter: function( filter, fullName ) {
-               filter = filter.toLowerCase();
-               fullName = fullName.toLowerCase();
-
-               var include = filter.charAt( 0 ) !== "!";
-               if ( !include ) {
-                       filter = filter.slice( 1 );
-               }
-
-               // If the filter matches, we need to honour include
-               if ( fullName.indexOf( filter ) !== -1 ) {
-                       return include;
-               }
-
-               // Otherwise, do the opposite
-               return !include;
-       }
-};
-
-// Resets the test setup. Useful for tests that modify the DOM.
-/*
-DEPRECATED: Use multiple tests instead of resetting inside a test.
-Use testStart or testDone for custom cleanup.
-This method will throw an error in 2.0, and will be removed in 2.1
-*/
-QUnit.reset = function() {
-
-       // Return on non-browser environments
-       // This is necessary to not break on node tests
-       if ( !defined.document ) {
-               return;
-       }
-
-       var fixture = defined.document && document.getElementById &&
-                       document.getElementById( "qunit-fixture" );
-
-       if ( fixture ) {
-               fixture.innerHTML = config.fixture;
-       }
-};
-
-QUnit.pushFailure = function() {
-       if ( !QUnit.config.current ) {
-               throw new Error( "pushFailure() assertion outside test context, in " +
-                       sourceFromStacktrace( 2 ) );
-       }
-
-       // Gets current test obj
-       var currentTest = QUnit.config.current;
-
-       return currentTest.pushFailure.apply( currentTest, arguments );
-};
-
-// Based on Java's String.hashCode, a simple but not
-// rigorously collision resistant hashing function
-function generateHash( module, testName ) {
-       var hex,
-               i = 0,
-               hash = 0,
-               str = module + "\x1C" + testName,
-               len = str.length;
-
-       for ( ; i < len; i++ ) {
-               hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
-               hash |= 0;
-       }
-
-       // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
-       // strictly necessary but increases user understanding that the id is a SHA-like hash
-       hex = ( 0x100000000 + hash ).toString( 16 );
-       if ( hex.length < 8 ) {
-               hex = "0000000" + hex;
-       }
-
-       return hex.slice( -8 );
-}
-
-function synchronize( callback, priority, seed ) {
-       var last = !priority,
-               index;
-
-       if ( QUnit.objectType( callback ) === "array" ) {
-               while ( callback.length ) {
-                       synchronize( callback.shift() );
-               }
-               return;
-       }
-
-       if ( priority ) {
-               config.queue.splice( priorityCount++, 0, callback );
-       } else if ( seed ) {
-               if ( !unitSampler ) {
-                       unitSampler = unitSamplerGenerator( seed );
-               }
-
-               // Insert into a random position after all priority items
-               index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) );
-               config.queue.splice( priorityCount + index, 0, callback );
-       } else {
-               config.queue.push( callback );
-       }
-
-       if ( config.autorun && !config.blocking ) {
-               process( last );
-       }
-}
-
-function unitSamplerGenerator( seed ) {
-
-       // 32-bit xorshift, requires only a nonzero seed
-       // http://excamera.com/sphinx/article-xorshift.html
-       var sample = parseInt( generateHash( seed ), 16 ) || -1;
-       return function() {
-               sample ^= sample << 13;
-               sample ^= sample >>> 17;
-               sample ^= sample << 5;
-
-               // ECMAScript has no unsigned number type
-               if ( sample < 0 ) {
-                       sample += 0x100000000;
-               }
-
-               return sample / 0x100000000;
-       };
-}
-
-function saveGlobal() {
-       config.pollution = [];
-
-       if ( config.noglobals ) {
-               for ( var key in global ) {
-                       if ( hasOwn.call( global, key ) ) {
-
-                               // In Opera sometimes DOM element ids show up here, ignore them
-                               if ( /^qunit-test-output/.test( key ) ) {
-                                       continue;
-                               }
-                               config.pollution.push( key );
-                       }
-               }
-       }
-}
-
-function checkPollution() {
-       var newGlobals,
-               deletedGlobals,
-               old = config.pollution;
-
-       saveGlobal();
-
-       newGlobals = diff( config.pollution, old );
-       if ( newGlobals.length > 0 ) {
-               QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
-       }
-
-       deletedGlobals = diff( old, config.pollution );
-       if ( deletedGlobals.length > 0 ) {
-               QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
-       }
-}
-
-// Will be exposed as QUnit.asyncTest
-function asyncTest( testName, expected, callback ) {
-       if ( arguments.length === 2 ) {
-               callback = expected;
-               expected = null;
-       }
-
-       QUnit.test( testName, expected, callback, true );
-}
-
-// Will be exposed as QUnit.test
-function test( testName, expected, callback, async ) {
-       if ( focused )  { return; }
-
-       var newTest;
-
-       if ( arguments.length === 2 ) {
-               callback = expected;
-               expected = null;
-       }
-
-       newTest = new Test( {
-               testName: testName,
-               expected: expected,
-               async: async,
-               callback: callback
-       } );
-
-       newTest.queue();
-}
-
-// Will be exposed as QUnit.skip
-function skip( testName ) {
-       if ( focused )  { return; }
-
-       var test = new Test( {
-               testName: testName,
-               skip: true
-       } );
-
-       test.queue();
-}
-
-// Will be exposed as QUnit.only
-function only( testName, expected, callback, async ) {
-       var newTest;
-
-       if ( focused )  { return; }
-
-       QUnit.config.queue.length = 0;
-       focused = true;
-
-       if ( arguments.length === 2 ) {
-               callback = expected;
-               expected = null;
-       }
-
-       newTest = new Test( {
-               testName: testName,
-               expected: expected,
-               async: async,
-               callback: callback
-       } );
-
-       newTest.queue();
-}
-
-function Assert( testContext ) {
-       this.test = testContext;
-}
-
-// Assert helpers
-QUnit.assert = Assert.prototype = {
-
-       // Specify the number of expected assertions to guarantee that failed test
-       // (no assertions are run at all) don't slip through.
-       expect: function( asserts ) {
-               if ( arguments.length === 1 ) {
-                       this.test.expected = asserts;
-               } else {
-                       return this.test.expected;
-               }
-       },
-
-       // Increment this Test's semaphore counter, then return a function that
-       // decrements that counter a maximum of once.
-       async: function( count ) {
-               var test = this.test,
-                       popped = false,
-                       acceptCallCount = count;
-
-               if ( typeof acceptCallCount === "undefined" ) {
-                       acceptCallCount = 1;
-               }
-
-               test.semaphore += 1;
-               test.usedAsync = true;
-               pauseProcessing();
-
-               return function done() {
-
-                       if ( popped ) {
-                               test.pushFailure( "Too many calls to the `assert.async` callback",
-                                       sourceFromStacktrace( 2 ) );
-                               return;
-                       }
-                       acceptCallCount -= 1;
-                       if ( acceptCallCount > 0 ) {
-                               return;
-                       }
-
-                       test.semaphore -= 1;
-                       popped = true;
-                       resumeProcessing();
-               };
-       },
-
-       // Exports test.push() to the user API
-       // Alias of pushResult.
-       push: function( result, actual, expected, message, negative ) {
-               var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert;
-               return currentAssert.pushResult( {
-                       result: result,
-                       actual: actual,
-                       expected: expected,
-                       message: message,
-                       negative: negative
-               } );
-       },
-
-       pushResult: function( resultInfo ) {
-
-               // Destructure of resultInfo = { result, actual, expected, message, negative }
-               var assert = this,
-                       currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
-
-               // Backwards compatibility fix.
-               // Allows the direct use of global exported assertions and QUnit.assert.*
-               // Although, it's use is not recommended as it can leak assertions
-               // to other tests from async tests, because we only get a reference to the current test,
-               // not exactly the test where assertion were intended to be called.
-               if ( !currentTest ) {
-                       throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
-               }
-
-               if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
-                       currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
-                               sourceFromStacktrace( 2 ) );
-
-                       // Allow this assertion to continue running anyway...
-               }
-
-               if ( !( assert instanceof Assert ) ) {
-                       assert = currentTest.assert;
-               }
-
-               return assert.test.pushResult( resultInfo );
-       },
-
-       ok: function( result, message ) {
-               message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
-                       QUnit.dump.parse( result ) );
-               this.pushResult( {
-                       result: !!result,
-                       actual: result,
-                       expected: true,
-                       message: message
-               } );
-       },
-
-       notOk: function( result, message ) {
-               message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
-                       QUnit.dump.parse( result ) );
-               this.pushResult( {
-                       result: !result,
-                       actual: result,
-                       expected: false,
-                       message: message
-               } );
-       },
-
-       equal: function( actual, expected, message ) {
-               /*jshint eqeqeq:false */
-               this.pushResult( {
-                       result: expected == actual,
-                       actual: actual,
-                       expected: expected,
-                       message: message
-               } );
-       },
-
-       notEqual: function( actual, expected, message ) {
-               /*jshint eqeqeq:false */
-               this.pushResult( {
-                       result: expected != actual,
-                       actual: actual,
-                       expected: expected,
-                       message: message,
-                       negative: true
-               } );
-       },
-
-       propEqual: function( actual, expected, message ) {
-               actual = objectValues( actual );
-               expected = objectValues( expected );
-               this.pushResult( {
-                       result: QUnit.equiv( actual, expected ),
-                       actual: actual,
-                       expected: expected,
-                       message: message
-               } );
-       },
-
-       notPropEqual: function( actual, expected, message ) {
-               actual = objectValues( actual );
-               expected = objectValues( expected );
-               this.pushResult( {
-                       result: !QUnit.equiv( actual, expected ),
-                       actual: actual,
-                       expected: expected,
-                       message: message,
-                       negative: true
-               } );
-       },
-
-       deepEqual: function( actual, expected, message ) {
-               this.pushResult( {
-                       result: QUnit.equiv( actual, expected ),
-                       actual: actual,
-                       expected: expected,
-                       message: message
-               } );
-       },
-
-       notDeepEqual: function( actual, expected, message ) {
-               this.pushResult( {
-                       result: !QUnit.equiv( actual, expected ),
-                       actual: actual,
-                       expected: expected,
-                       message: message,
-                       negative: true
-               } );
-       },
-
-       strictEqual: function( actual, expected, message ) {
-               this.pushResult( {
-                       result: expected === actual,
-                       actual: actual,
-                       expected: expected,
-                       message: message
-               } );
-       },
-
-       notStrictEqual: function( actual, expected, message ) {
-               this.pushResult( {
-                       result: expected !== actual,
-                       actual: actual,
-                       expected: expected,
-                       message: message,
-                       negative: true
-               } );
-       },
-
-       "throws": function( block, expected, message ) {
-               var actual, expectedType,
-                       expectedOutput = expected,
-                       ok = false,
-                       currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
-
-               // 'expected' is optional unless doing string comparison
-               if ( message == null && typeof expected === "string" ) {
-                       message = expected;
-                       expected = null;
-               }
-
-               currentTest.ignoreGlobalErrors = true;
-               try {
-                       block.call( currentTest.testEnvironment );
-               } catch ( e ) {
-                       actual = e;
-               }
-               currentTest.ignoreGlobalErrors = false;
-
-               if ( actual ) {
-                       expectedType = QUnit.objectType( expected );
-
-                       // We don't want to validate thrown error
-                       if ( !expected ) {
-                               ok = true;
-                               expectedOutput = null;
-
-                       // Expected is a regexp
-                       } else if ( expectedType === "regexp" ) {
-                               ok = expected.test( errorString( actual ) );
-
-                       // Expected is a string
-                       } else if ( expectedType === "string" ) {
-                               ok = expected === errorString( actual );
-
-                       // Expected is a constructor, maybe an Error constructor
-                       } else if ( expectedType === "function" && actual instanceof expected ) {
-                               ok = true;
-
-                       // Expected is an Error object
-                       } else if ( expectedType === "object" ) {
-                               ok = actual instanceof expected.constructor &&
-                                       actual.name === expected.name &&
-                                       actual.message === expected.message;
-
-                       // Expected is a validation function which returns true if validation passed
-                       } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
-                               expectedOutput = null;
-                               ok = true;
-                       }
-               }
-
-               currentTest.assert.pushResult( {
-                       result: ok,
-                       actual: actual,
-                       expected: expectedOutput,
-                       message: message
-               } );
-       }
-};
-
-// Provide an alternative to assert.throws(), for environments that consider throws a reserved word
-// Known to us are: Closure Compiler, Narwhal
-( function() {
-       /*jshint sub:true */
-       Assert.prototype.raises = Assert.prototype [ "throws" ]; //jscs:ignore requireDotNotation
-}() );
-
-function errorString( error ) {
-       var name, message,
-               resultErrorString = error.toString();
-       if ( resultErrorString.substring( 0, 7 ) === "[object" ) {
-               name = error.name ? error.name.toString() : "Error";
-               message = error.message ? error.message.toString() : "";
-               if ( name && message ) {
-                       return name + ": " + message;
-               } else if ( name ) {
-                       return name;
-               } else if ( message ) {
-                       return message;
-               } else {
-                       return "Error";
-               }
-       } else {
-               return resultErrorString;
-       }
-}
-
-// Test for equality any JavaScript type.
-// Author: Philippe Rathé <prathe@gmail.com>
-QUnit.equiv = ( function() {
-
-       // Stack to decide between skip/abort functions
-       var callers = [];
-
-       // Stack to avoiding loops from circular referencing
-       var parents = [];
-       var parentsB = [];
-
-       var getProto = Object.getPrototypeOf || function( obj ) {
-
-               /*jshint proto: true */
-               return obj.__proto__;
-       };
-
-       function useStrictEquality( b, a ) {
-
-               // To catch short annotation VS 'new' annotation of a declaration. e.g.:
-               // `var i = 1;`
-               // `var j = new Number(1);`
-               if ( typeof a === "object" ) {
-                       a = a.valueOf();
-               }
-               if ( typeof b === "object" ) {
-                       b = b.valueOf();
-               }
-
-               return a === b;
-       }
-
-       function compareConstructors( a, b ) {
-               var protoA = getProto( a );
-               var protoB = getProto( b );
-
-               // Comparing constructors is more strict than using `instanceof`
-               if ( a.constructor === b.constructor ) {
-                       return true;
-               }
-
-               // Ref #851
-               // If the obj prototype descends from a null constructor, treat it
-               // as a null prototype.
-               if ( protoA && protoA.constructor === null ) {
-                       protoA = null;
-               }
-               if ( protoB && protoB.constructor === null ) {
-                       protoB = null;
-               }
-
-               // Allow objects with no prototype to be equivalent to
-               // objects with Object as their constructor.
-               if ( ( protoA === null && protoB === Object.prototype ) ||
-                               ( protoB === null && protoA === Object.prototype ) ) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       function getRegExpFlags( regexp ) {
-               return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ];
-       }
-
-       var callbacks = {
-               "string": useStrictEquality,
-               "boolean": useStrictEquality,
-               "number": useStrictEquality,
-               "null": useStrictEquality,
-               "undefined": useStrictEquality,
-               "symbol": useStrictEquality,
-               "date": useStrictEquality,
-
-               "nan": function() {
-                       return true;
-               },
-
-               "regexp": function( b, a ) {
-                       return a.source === b.source &&
-
-                               // Include flags in the comparison
-                               getRegExpFlags( a ) === getRegExpFlags( b );
-               },
-
-               // - skip when the property is a method of an instance (OOP)
-               // - abort otherwise,
-               // initial === would have catch identical references anyway
-               "function": function() {
-                       var caller = callers[ callers.length - 1 ];
-                       return caller !== Object && typeof caller !== "undefined";
-               },
-
-               "array": function( b, a ) {
-                       var i, j, len, loop, aCircular, bCircular;
-
-                       len = a.length;
-                       if ( len !== b.length ) {
-
-                               // Safe and faster
-                               return false;
-                       }
-
-                       // Track reference to avoid circular references
-                       parents.push( a );
-                       parentsB.push( b );
-                       for ( i = 0; i < len; i++ ) {
-                               loop = false;
-                               for ( j = 0; j < parents.length; j++ ) {
-                                       aCircular = parents[ j ] === a[ i ];
-                                       bCircular = parentsB[ j ] === b[ i ];
-                                       if ( aCircular || bCircular ) {
-                                               if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
-                                                       loop = true;
-                                               } else {
-                                                       parents.pop();
-                                                       parentsB.pop();
-                                                       return false;
-                                               }
-                                       }
-                               }
-                               if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
-                                       parents.pop();
-                                       parentsB.pop();
-                                       return false;
-                               }
-                       }
-                       parents.pop();
-                       parentsB.pop();
-                       return true;
-               },
-
-               "set": function( b, a ) {
-                       var innerEq,
-                               outerEq = true;
-
-                       if ( a.size !== b.size ) {
-                               return false;
-                       }
-
-                       a.forEach( function( aVal ) {
-                               innerEq = false;
-
-                               b.forEach( function( bVal ) {
-                                       if ( innerEquiv( bVal, aVal ) ) {
-                                               innerEq = true;
-                                       }
-                               } );
-
-                               if ( !innerEq ) {
-                                       outerEq = false;
-                               }
-                       } );
-
-                       return outerEq;
-               },
-
-               "map": function( b, a ) {
-                       var innerEq,
-                               outerEq = true;
-
-                       if ( a.size !== b.size ) {
-                               return false;
-                       }
-
-                       a.forEach( function( aVal, aKey ) {
-                               innerEq = false;
-
-                               b.forEach( function( bVal, bKey ) {
-                                       if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) {
-                                               innerEq = true;
-                                       }
-                               } );
-
-                               if ( !innerEq ) {
-                                       outerEq = false;
-                               }
-                       } );
-
-                       return outerEq;
-               },
-
-               "object": function( b, a ) {
-                       var i, j, loop, aCircular, bCircular;
-
-                       // Default to true
-                       var eq = true;
-                       var aProperties = [];
-                       var bProperties = [];
-
-                       if ( compareConstructors( a, b ) === false ) {
-                               return false;
-                       }
-
-                       // Stack constructor before traversing properties
-                       callers.push( a.constructor );
-
-                       // Track reference to avoid circular references
-                       parents.push( a );
-                       parentsB.push( b );
-
-                       // Be strict: don't ensure hasOwnProperty and go deep
-                       for ( i in a ) {
-                               loop = false;
-                               for ( j = 0; j < parents.length; j++ ) {
-                                       aCircular = parents[ j ] === a[ i ];
-                                       bCircular = parentsB[ j ] === b[ i ];
-                                       if ( aCircular || bCircular ) {
-                                               if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
-                                                       loop = true;
-                                               } else {
-                                                       eq = false;
-                                                       break;
-                                               }
-                                       }
-                               }
-                               aProperties.push( i );
-                               if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
-                                       eq = false;
-                                       break;
-                               }
-                       }
-
-                       parents.pop();
-                       parentsB.pop();
-
-                       // Unstack, we are done
-                       callers.pop();
-
-                       for ( i in b ) {
-
-                               // Collect b's properties
-                               bProperties.push( i );
-                       }
-
-                       // Ensures identical properties name
-                       return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
-               }
-       };
-
-       function typeEquiv( a, b ) {
-               var type = QUnit.objectType( a );
-               return QUnit.objectType( b ) === type && callbacks[ type ]( b, a );
-       }
-
-       // The real equiv function
-       function innerEquiv( a, b ) {
-
-               // We're done when there's nothing more to compare
-               if ( arguments.length < 2 ) {
-                       return true;
-               }
-
-               // Require type-specific equality
-               return ( a === b || typeEquiv( a, b ) ) &&
-
-                       // ...across all consecutive argument pairs
-                       ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) );
-       }
-
-       return innerEquiv;
-}() );
-
-// Based on jsDump by Ariel Flesler
-// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
-QUnit.dump = ( function() {
-       function quote( str ) {
-               return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
-       }
-       function literal( o ) {
-               return o + "";
-       }
-       function join( pre, arr, post ) {
-               var s = dump.separator(),
-                       base = dump.indent(),
-                       inner = dump.indent( 1 );
-               if ( arr.join ) {
-                       arr = arr.join( "," + s + inner );
-               }
-               if ( !arr ) {
-                       return pre + post;
-               }
-               return [ pre, inner + arr, base + post ].join( s );
-       }
-       function array( arr, stack ) {
-               var i = arr.length,
-                       ret = new Array( i );
-
-               if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
-                       return "[object Array]";
-               }
-
-               this.up();
-               while ( i-- ) {
-                       ret[ i ] = this.parse( arr[ i ], undefined, stack );
-               }
-               this.down();
-               return join( "[", ret, "]" );
-       }
-
-       var reName = /^function (\w+)/,
-               dump = {
-
-                       // The objType is used mostly internally, you can fix a (custom) type in advance
-                       parse: function( obj, objType, stack ) {
-                               stack = stack || [];
-                               var res, parser, parserType,
-                                       inStack = inArray( obj, stack );
-
-                               if ( inStack !== -1 ) {
-                                       return "recursion(" + ( inStack - stack.length ) + ")";
-                               }
-
-                               objType = objType || this.typeOf( obj  );
-                               parser = this.parsers[ objType ];
-                               parserType = typeof parser;
-
-                               if ( parserType === "function" ) {
-                                       stack.push( obj );
-                                       res = parser.call( this, obj, stack );
-                                       stack.pop();
-                                       return res;
-                               }
-                               return ( parserType === "string" ) ? parser : this.parsers.error;
-                       },
-                       typeOf: function( obj ) {
-                               var type;
-                               if ( obj === null ) {
-                                       type = "null";
-                               } else if ( typeof obj === "undefined" ) {
-                                       type = "undefined";
-                               } else if ( QUnit.is( "regexp", obj ) ) {
-                                       type = "regexp";
-                               } else if ( QUnit.is( "date", obj ) ) {
-                                       type = "date";
-                               } else if ( QUnit.is( "function", obj ) ) {
-                                       type = "function";
-                               } else if ( obj.setInterval !== undefined &&
-                                               obj.document !== undefined &&
-                                               obj.nodeType === undefined ) {
-                                       type = "window";
-                               } else if ( obj.nodeType === 9 ) {
-                                       type = "document";
-                               } else if ( obj.nodeType ) {
-                                       type = "node";
-                               } else if (
-
-                                       // Native arrays
-                                       toString.call( obj ) === "[object Array]" ||
-
-                                       // NodeList objects
-                                       ( typeof obj.length === "number" && obj.item !== undefined &&
-                                       ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
-                                       obj[ 0 ] === undefined ) ) )
-                               ) {
-                                       type = "array";
-                               } else if ( obj.constructor === Error.prototype.constructor ) {
-                                       type = "error";
-                               } else {
-                                       type = typeof obj;
-                               }
-                               return type;
-                       },
-
-                       separator: function() {
-                               return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
-                       },
-
-                       // Extra can be a number, shortcut for increasing-calling-decreasing
-                       indent: function( extra ) {
-                               if ( !this.multiline ) {
-                                       return "";
-                               }
-                               var chr = this.indentChar;
-                               if ( this.HTML ) {
-                                       chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
-                               }
-                               return new Array( this.depth + ( extra || 0 ) ).join( chr );
-                       },
-                       up: function( a ) {
-                               this.depth += a || 1;
-                       },
-                       down: function( a ) {
-                               this.depth -= a || 1;
-                       },
-                       setParser: function( name, parser ) {
-                               this.parsers[ name ] = parser;
-                       },
-
-                       // The next 3 are exposed so you can use them
-                       quote: quote,
-                       literal: literal,
-                       join: join,
-                       depth: 1,
-                       maxDepth: QUnit.config.maxDepth,
-
-                       // This is the list of parsers, to modify them, use dump.setParser
-                       parsers: {
-                               window: "[Window]",
-                               document: "[Document]",
-                               error: function( error ) {
-                                       return "Error(\"" + error.message + "\")";
-                               },
-                               unknown: "[Unknown]",
-                               "null": "null",
-                               "undefined": "undefined",
-                               "function": function( fn ) {
-                                       var ret = "function",
-
-                                               // Functions never have name in IE
-                                               name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
-
-                                       if ( name ) {
-                                               ret += " " + name;
-                                       }
-                                       ret += "(";
-
-                                       ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
-                                       return join( ret, dump.parse( fn, "functionCode" ), "}" );
-                               },
-                               array: array,
-                               nodelist: array,
-                               "arguments": array,
-                               object: function( map, stack ) {
-                                       var keys, key, val, i, nonEnumerableProperties,
-                                               ret = [];
-
-                                       if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
-                                               return "[object Object]";
-                                       }
-
-                                       dump.up();
-                                       keys = [];
-                                       for ( key in map ) {
-                                               keys.push( key );
-                                       }
-
-                                       // Some properties are not always enumerable on Error objects.
-                                       nonEnumerableProperties = [ "message", "name" ];
-                                       for ( i in nonEnumerableProperties ) {
-                                               key = nonEnumerableProperties[ i ];
-                                               if ( key in map && inArray( key, keys ) < 0 ) {
-                                                       keys.push( key );
-                                               }
-                                       }
-                                       keys.sort();
-                                       for ( i = 0; i < keys.length; i++ ) {
-                                               key = keys[ i ];
-                                               val = map[ key ];
-                                               ret.push( dump.parse( key, "key" ) + ": " +
-                                                       dump.parse( val, undefined, stack ) );
-                                       }
-                                       dump.down();
-                                       return join( "{", ret, "}" );
-                               },
-                               node: function( node ) {
-                                       var len, i, val,
-                                               open = dump.HTML ? "&lt;" : "<",
-                                               close = dump.HTML ? "&gt;" : ">",
-                                               tag = node.nodeName.toLowerCase(),
-                                               ret = open + tag,
-                                               attrs = node.attributes;
-
-                                       if ( attrs ) {
-                                               for ( i = 0, len = attrs.length; i < len; i++ ) {
-                                                       val = attrs[ i ].nodeValue;
-
-                                                       // IE6 includes all attributes in .attributes, even ones not explicitly
-                                                       // set. Those have values like undefined, null, 0, false, "" or
-                                                       // "inherit".
-                                                       if ( val && val !== "inherit" ) {
-                                                               ret += " " + attrs[ i ].nodeName + "=" +
-                                                                       dump.parse( val, "attribute" );
-                                                       }
-                                               }
-                                       }
-                                       ret += close;
-
-                                       // Show content of TextNode or CDATASection
-                                       if ( node.nodeType === 3 || node.nodeType === 4 ) {
-                                               ret += node.nodeValue;
-                                       }
-
-                                       return ret + open + "/" + tag + close;
-                               },
-
-                               // Function calls it internally, it's the arguments part of the function
-                               functionArgs: function( fn ) {
-                                       var args,
-                                               l = fn.length;
-
-                                       if ( !l ) {
-                                               return "";
-                                       }
-
-                                       args = new Array( l );
-                                       while ( l-- ) {
-
-                                               // 97 is 'a'
-                                               args[ l ] = String.fromCharCode( 97 + l );
-                                       }
-                                       return " " + args.join( ", " ) + " ";
-                               },
-
-                               // Object calls it internally, the key part of an item in a map
-                               key: quote,
-
-                               // Function calls it internally, it's the content of the function
-                               functionCode: "[code]",
-
-                               // Node calls it internally, it's a html attribute value
-                               attribute: quote,
-                               string: quote,
-                               date: quote,
-                               regexp: literal,
-                               number: literal,
-                               "boolean": literal
-                       },
-
-                       // If true, entities are escaped ( <, >, \t, space and \n )
-                       HTML: false,
-
-                       // Indentation unit
-                       indentChar: "  ",
-
-                       // If true, items in a collection, are separated by a \n, else just a space.
-                       multiline: true
-               };
-
-       return dump;
-}() );
-
-// Back compat
-QUnit.jsDump = QUnit.dump;
-
-// Deprecated
-// Extend assert methods to QUnit for Backwards compatibility
-( function() {
-       var i,
-               assertions = Assert.prototype;
-
-       function applyCurrent( current ) {
-               return function() {
-                       var assert = new Assert( QUnit.config.current );
-                       current.apply( assert, arguments );
-               };
-       }
-
-       for ( i in assertions ) {
-               QUnit[ i ] = applyCurrent( assertions[ i ] );
-       }
-}() );
-
-// For browser, export only select globals
-if ( defined.document ) {
-
-       ( function() {
-               var i, l,
-                       keys = [
-                               "test",
-                               "module",
-                               "expect",
-                               "asyncTest",
-                               "start",
-                               "stop",
-                               "ok",
-                               "notOk",
-                               "equal",
-                               "notEqual",
-                               "propEqual",
-                               "notPropEqual",
-                               "deepEqual",
-                               "notDeepEqual",
-                               "strictEqual",
-                               "notStrictEqual",
-                               "throws",
-                               "raises"
-                       ];
-
-               for ( i = 0, l = keys.length; i < l; i++ ) {
-                       window[ keys[ i ] ] = QUnit[ keys[ i ] ];
-               }
-       }() );
-
-       window.QUnit = QUnit;
-}
-
-// For nodejs
-if ( typeof module !== "undefined" && module && module.exports ) {
-       module.exports = QUnit;
-
-       // For consistency with CommonJS environments' exports
-       module.exports.QUnit = QUnit;
-}
-
-// For CommonJS with exports, but without module.exports, like Rhino
-if ( typeof exports !== "undefined" && exports ) {
-       exports.QUnit = QUnit;
-}
-
-if ( typeof define === "function" && define.amd ) {
-       define( function() {
-               return QUnit;
-       } );
-       QUnit.config.autostart = false;
-}
-
-// Get a reference to the global object, like window in browsers
-}( ( function() {
-       return this;
-}() ) ) );
-
-( function() {
-
-// Only interact with URLs via window.location
-var location = typeof window !== "undefined" && window.location;
-if ( !location ) {
-       return;
-}
-
-var urlParams = getUrlParams();
-
-QUnit.urlParams = urlParams;
-
-// Match module/test by inclusion in an array
-QUnit.config.moduleId = [].concat( urlParams.moduleId || [] );
-QUnit.config.testId = [].concat( urlParams.testId || [] );
-
-// Exact case-insensitive match of the module name
-QUnit.config.module = urlParams.module;
-
-// Regular expression or case-insenstive substring match against "moduleName: testName"
-QUnit.config.filter = urlParams.filter;
-
-// Test order randomization
-if ( urlParams.seed === true ) {
-
-       // Generate a random seed if the option is specified without a value
-       QUnit.config.seed = Math.random().toString( 36 ).slice( 2 );
-} else if ( urlParams.seed ) {
-       QUnit.config.seed = urlParams.seed;
-}
-
-// Add URL-parameter-mapped config values with UI form rendering data
-QUnit.config.urlConfig.push(
-       {
-               id: "hidepassed",
-               label: "Hide passed tests",
-               tooltip: "Only show tests and assertions that fail. Stored as query-strings."
-       },
-       {
-               id: "noglobals",
-               label: "Check for Globals",
-               tooltip: "Enabling this will test if any test introduces new properties on the " +
-                       "global object (`window` in Browsers). Stored as query-strings."
-       },
-       {
-               id: "notrycatch",
-               label: "No try-catch",
-               tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
-                       "exceptions in IE reasonable. Stored as query-strings."
-       }
-);
-
-QUnit.begin( function() {
-       var i, option,
-               urlConfig = QUnit.config.urlConfig;
-
-       for ( i = 0; i < urlConfig.length; i++ ) {
-
-               // Options can be either strings or objects with nonempty "id" properties
-               option = QUnit.config.urlConfig[ i ];
-               if ( typeof option !== "string" ) {
-                       option = option.id;
-               }
-
-               if ( QUnit.config[ option ] === undefined ) {
-                       QUnit.config[ option ] = urlParams[ option ];
-               }
-       }
-} );
-
-function getUrlParams() {
-       var i, param, name, value;
-       var urlParams = {};
-       var params = location.search.slice( 1 ).split( "&" );
-       var length = params.length;
-
-       for ( i = 0; i < length; i++ ) {
-               if ( params[ i ] ) {
-                       param = params[ i ].split( "=" );
-                       name = decodeURIComponent( param[ 0 ] );
-
-                       // Allow just a key to turn on a flag, e.g., test.html?noglobals
-                       value = param.length === 1 ||
-                               decodeURIComponent( param.slice( 1 ).join( "=" ) ) ;
-                       if ( urlParams[ name ] ) {
-                               urlParams[ name ] = [].concat( urlParams[ name ], value );
-                       } else {
-                               urlParams[ name ] = value;
-                       }
-               }
-       }
-
-       return urlParams;
-}
-
-// Don't load the HTML Reporter on non-browser environments
-if ( typeof window === "undefined" || !window.document ) {
-       return;
-}
-
-// Deprecated QUnit.init - Ref #530
-// Re-initialize the configuration options
-QUnit.init = function() {
-       var config = QUnit.config;
-
-       config.stats = { all: 0, bad: 0 };
-       config.moduleStats = { all: 0, bad: 0 };
-       config.started = 0;
-       config.updateRate = 1000;
-       config.blocking = false;
-       config.autostart = true;
-       config.autorun = false;
-       config.filter = "";
-       config.queue = [];
-
-       appendInterface();
-};
-
-var config = QUnit.config,
-       document = window.document,
-       collapseNext = false,
-       hasOwn = Object.prototype.hasOwnProperty,
-       unfilteredUrl = setUrl( { filter: undefined, module: undefined,
-               moduleId: undefined, testId: undefined } ),
-       defined = {
-               sessionStorage: ( function() {
-                       var x = "qunit-test-string";
-                       try {
-                               sessionStorage.setItem( x, x );
-                               sessionStorage.removeItem( x );
-                               return true;
-                       } catch ( e ) {
-                               return false;
-                       }
-               }() )
-       },
-       modulesList = [];
-
-/**
-* Escape text for attribute or text content.
-*/
-function escapeText( s ) {
-       if ( !s ) {
-               return "";
-       }
-       s = s + "";
-
-       // Both single quotes and double quotes (for attributes)
-       return s.replace( /['"<>&]/g, function( s ) {
-               switch ( s ) {
-               case "'":
-                       return "&#039;";
-               case "\"":
-                       return "&quot;";
-               case "<":
-                       return "&lt;";
-               case ">":
-                       return "&gt;";
-               case "&":
-                       return "&amp;";
-               }
-       } );
-}
-
-/**
- * @param {HTMLElement} elem
- * @param {string} type
- * @param {Function} fn
- */
-function addEvent( elem, type, fn ) {
-       if ( elem.addEventListener ) {
-
-               // Standards-based browsers
-               elem.addEventListener( type, fn, false );
-       } else if ( elem.attachEvent ) {
-
-               // Support: IE <9
-               elem.attachEvent( "on" + type, function() {
-                       var event = window.event;
-                       if ( !event.target ) {
-                               event.target = event.srcElement || document;
-                       }
-
-                       fn.call( elem, event );
-               } );
-       }
-}
-
-/**
- * @param {Array|NodeList} elems
- * @param {string} type
- * @param {Function} fn
- */
-function addEvents( elems, type, fn ) {
-       var i = elems.length;
-       while ( i-- ) {
-               addEvent( elems[ i ], type, fn );
-       }
-}
-
-function hasClass( elem, name ) {
-       return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
-}
-
-function addClass( elem, name ) {
-       if ( !hasClass( elem, name ) ) {
-               elem.className += ( elem.className ? " " : "" ) + name;
-       }
-}
-
-function toggleClass( elem, name, force ) {
-       if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) {
-               addClass( elem, name );
-       } else {
-               removeClass( elem, name );
-       }
-}
-
-function removeClass( elem, name ) {
-       var set = " " + elem.className + " ";
-
-       // Class name may appear multiple times
-       while ( set.indexOf( " " + name + " " ) >= 0 ) {
-               set = set.replace( " " + name + " ", " " );
-       }
-
-       // Trim for prettiness
-       elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
-}
-
-function id( name ) {
-       return document.getElementById && document.getElementById( name );
-}
-
-function getUrlConfigHtml() {
-       var i, j, val,
-               escaped, escapedTooltip,
-               selection = false,
-               urlConfig = config.urlConfig,
-               urlConfigHtml = "";
-
-       for ( i = 0; i < urlConfig.length; i++ ) {
-
-               // Options can be either strings or objects with nonempty "id" properties
-               val = config.urlConfig[ i ];
-               if ( typeof val === "string" ) {
-                       val = {
-                               id: val,
-                               label: val
-                       };
-               }
-
-               escaped = escapeText( val.id );
-               escapedTooltip = escapeText( val.tooltip );
-
-               if ( !val.value || typeof val.value === "string" ) {
-                       urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
-                               "' name='" + escaped + "' type='checkbox'" +
-                               ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
-                               ( config[ val.id ] ? " checked='checked'" : "" ) +
-                               " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
-                               "' title='" + escapedTooltip + "'>" + val.label + "</label>";
-               } else {
-                       urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
-                               "' title='" + escapedTooltip + "'>" + val.label +
-                               ": </label><select id='qunit-urlconfig-" + escaped +
-                               "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
-
-                       if ( QUnit.is( "array", val.value ) ) {
-                               for ( j = 0; j < val.value.length; j++ ) {
-                                       escaped = escapeText( val.value[ j ] );
-                                       urlConfigHtml += "<option value='" + escaped + "'" +
-                                               ( config[ val.id ] === val.value[ j ] ?
-                                                       ( selection = true ) && " selected='selected'" : "" ) +
-                                               ">" + escaped + "</option>";
-                               }
-                       } else {
-                               for ( j in val.value ) {
-                                       if ( hasOwn.call( val.value, j ) ) {
-                                               urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
-                                                       ( config[ val.id ] === j ?
-                                                               ( selection = true ) && " selected='selected'" : "" ) +
-                                                       ">" + escapeText( val.value[ j ] ) + "</option>";
-                                       }
-                               }
-                       }
-                       if ( config[ val.id ] && !selection ) {
-                               escaped = escapeText( config[ val.id ] );
-                               urlConfigHtml += "<option value='" + escaped +
-                                       "' selected='selected' disabled='disabled'>" + escaped + "</option>";
-                       }
-                       urlConfigHtml += "</select>";
-               }
-       }
-
-       return urlConfigHtml;
-}
-
-// Handle "click" events on toolbar checkboxes and "change" for select menus.
-// Updates the URL with the new state of `config.urlConfig` values.
-function toolbarChanged() {
-       var updatedUrl, value, tests,
-               field = this,
-               params = {};
-
-       // Detect if field is a select menu or a checkbox
-       if ( "selectedIndex" in field ) {
-               value = field.options[ field.selectedIndex ].value || undefined;
-       } else {
-               value = field.checked ? ( field.defaultValue || true ) : undefined;
-       }
-
-       params[ field.name ] = value;
-       updatedUrl = setUrl( params );
-
-       // Check if we can apply the change without a page refresh
-       if ( "hidepassed" === field.name && "replaceState" in window.history ) {
-               QUnit.urlParams[ field.name ] = value;
-               config[ field.name ] = value || false;
-               tests = id( "qunit-tests" );
-               if ( tests ) {
-                       toggleClass( tests, "hidepass", value || false );
-               }
-               window.history.replaceState( null, "", updatedUrl );
-       } else {
-               window.location = updatedUrl;
-       }
-}
-
-function setUrl( params ) {
-       var key, arrValue, i,
-               querystring = "?",
-               location = window.location;
-
-       params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
-
-       for ( key in params ) {
-
-               // Skip inherited or undefined properties
-               if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) {
-
-                       // Output a parameter for each value of this key (but usually just one)
-                       arrValue = [].concat( params[ key ] );
-                       for ( i = 0; i < arrValue.length; i++ ) {
-                               querystring += encodeURIComponent( key );
-                               if ( arrValue[ i ] !== true ) {
-                                       querystring += "=" + encodeURIComponent( arrValue[ i ] );
-                               }
-                               querystring += "&";
-                       }
-               }
-       }
-       return location.protocol + "//" + location.host +
-               location.pathname + querystring.slice( 0, -1 );
-}
-
-function applyUrlParams() {
-       var selectedModule,
-               modulesList = id( "qunit-modulefilter" ),
-               filter = id( "qunit-filter-input" ).value;
-
-       selectedModule = modulesList ?
-               decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
-               undefined;
-
-       window.location = setUrl( {
-               module: ( selectedModule === "" ) ? undefined : selectedModule,
-               filter: ( filter === "" ) ? undefined : filter,
-
-               // Remove moduleId and testId filters
-               moduleId: undefined,
-               testId: undefined
-       } );
-}
-
-function toolbarUrlConfigContainer() {
-       var urlConfigContainer = document.createElement( "span" );
-
-       urlConfigContainer.innerHTML = getUrlConfigHtml();
-       addClass( urlConfigContainer, "qunit-url-config" );
-
-       // For oldIE support:
-       // * Add handlers to the individual elements instead of the container
-       // * Use "click" instead of "change" for checkboxes
-       addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
-       addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
-
-       return urlConfigContainer;
-}
-
-function toolbarLooseFilter() {
-       var filter = document.createElement( "form" ),
-               label = document.createElement( "label" ),
-               input = document.createElement( "input" ),
-               button = document.createElement( "button" );
-
-       addClass( filter, "qunit-filter" );
-
-       label.innerHTML = "Filter: ";
-
-       input.type = "text";
-       input.value = config.filter || "";
-       input.name = "filter";
-       input.id = "qunit-filter-input";
-
-       button.innerHTML = "Go";
-
-       label.appendChild( input );
-
-       filter.appendChild( label );
-       filter.appendChild( button );
-       addEvent( filter, "submit", function( ev ) {
-               applyUrlParams();
-
-               if ( ev && ev.preventDefault ) {
-                       ev.preventDefault();
-               }
-
-               return false;
-       } );
-
-       return filter;
-}
-
-function toolbarModuleFilterHtml() {
-       var i,
-               moduleFilterHtml = "";
-
-       if ( !modulesList.length ) {
-               return false;
-       }
-
-       moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
-               "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
-               ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
-               ">< All Modules ></option>";
-
-       for ( i = 0; i < modulesList.length; i++ ) {
-               moduleFilterHtml += "<option value='" +
-                       escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
-                       ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
-                       ">" + escapeText( modulesList[ i ] ) + "</option>";
-       }
-       moduleFilterHtml += "</select>";
-
-       return moduleFilterHtml;
-}
-
-function toolbarModuleFilter() {
-       var toolbar = id( "qunit-testrunner-toolbar" ),
-               moduleFilter = document.createElement( "span" ),
-               moduleFilterHtml = toolbarModuleFilterHtml();
-
-       if ( !toolbar || !moduleFilterHtml ) {
-               return false;
-       }
-
-       moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
-       moduleFilter.innerHTML = moduleFilterHtml;
-
-       addEvent( moduleFilter.lastChild, "change", applyUrlParams );
-
-       toolbar.appendChild( moduleFilter );
-}
-
-function appendToolbar() {
-       var toolbar = id( "qunit-testrunner-toolbar" );
-
-       if ( toolbar ) {
-               toolbar.appendChild( toolbarUrlConfigContainer() );
-               toolbar.appendChild( toolbarLooseFilter() );
-               toolbarModuleFilter();
-       }
-}
-
-function appendHeader() {
-       var header = id( "qunit-header" );
-
-       if ( header ) {
-               header.innerHTML = "<a href='" + escapeText( unfilteredUrl ) + "'>" + header.innerHTML +
-                       "</a> ";
-       }
-}
-
-function appendBanner() {
-       var banner = id( "qunit-banner" );
-
-       if ( banner ) {
-               banner.className = "";
-       }
-}
-
-function appendTestResults() {
-       var tests = id( "qunit-tests" ),
-               result = id( "qunit-testresult" );
-
-       if ( result ) {
-               result.parentNode.removeChild( result );
-       }
-
-       if ( tests ) {
-               tests.innerHTML = "";
-               result = document.createElement( "p" );
-               result.id = "qunit-testresult";
-               result.className = "result";
-               tests.parentNode.insertBefore( result, tests );
-               result.innerHTML = "Running...<br />&#160;";
-       }
-}
-
-function storeFixture() {
-       var fixture = id( "qunit-fixture" );
-       if ( fixture ) {
-               config.fixture = fixture.innerHTML;
-       }
-}
-
-function appendFilteredTest() {
-       var testId = QUnit.config.testId;
-       if ( !testId || testId.length <= 0 ) {
-               return "";
-       }
-       return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
-               escapeText( testId.join( ", " ) ) +
-               " <a id='qunit-clearFilter' href='" +
-               escapeText( unfilteredUrl ) +
-               "'>Run all tests</a></div>";
-}
-
-function appendUserAgent() {
-       var userAgent = id( "qunit-userAgent" );
-
-       if ( userAgent ) {
-               userAgent.innerHTML = "";
-               userAgent.appendChild(
-                       document.createTextNode(
-                               "QUnit " + QUnit.version + "; " + navigator.userAgent
-                       )
-               );
-       }
-}
-
-function appendInterface() {
-       var qunit = id( "qunit" );
-
-       if ( qunit ) {
-               qunit.innerHTML =
-                       "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
-                       "<h2 id='qunit-banner'></h2>" +
-                       "<div id='qunit-testrunner-toolbar'></div>" +
-                       appendFilteredTest() +
-                       "<h2 id='qunit-userAgent'></h2>" +
-                       "<ol id='qunit-tests'></ol>";
-       }
-
-       appendHeader();
-       appendBanner();
-       appendTestResults();
-       appendUserAgent();
-       appendToolbar();
-}
-
-function appendTestsList( modules ) {
-       var i, l, x, z, test, moduleObj;
-
-       for ( i = 0, l = modules.length; i < l; i++ ) {
-               moduleObj = modules[ i ];
-
-               for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
-                       test = moduleObj.tests[ x ];
-
-                       appendTest( test.name, test.testId, moduleObj.name );
-               }
-       }
-}
-
-function appendTest( name, testId, moduleName ) {
-       var title, rerunTrigger, testBlock, assertList,
-               tests = id( "qunit-tests" );
-
-       if ( !tests ) {
-               return;
-       }
-
-       title = document.createElement( "strong" );
-       title.innerHTML = getNameHtml( name, moduleName );
-
-       rerunTrigger = document.createElement( "a" );
-       rerunTrigger.innerHTML = "Rerun";
-       rerunTrigger.href = setUrl( { testId: testId } );
-
-       testBlock = document.createElement( "li" );
-       testBlock.appendChild( title );
-       testBlock.appendChild( rerunTrigger );
-       testBlock.id = "qunit-test-output-" + testId;
-
-       assertList = document.createElement( "ol" );
-       assertList.className = "qunit-assert-list";
-
-       testBlock.appendChild( assertList );
-
-       tests.appendChild( testBlock );
-}
-
-// HTML Reporter initialization and load
-QUnit.begin( function( details ) {
-       var i, moduleObj, tests;
-
-       // Sort modules by name for the picker
-       for ( i = 0; i < details.modules.length; i++ ) {
-               moduleObj = details.modules[ i ];
-               if ( moduleObj.name ) {
-                       modulesList.push( moduleObj.name );
-               }
-       }
-       modulesList.sort( function( a, b ) {
-               return a.localeCompare( b );
-       } );
-
-       // Capture fixture HTML from the page
-       storeFixture();
-
-       // Initialize QUnit elements
-       appendInterface();
-       appendTestsList( details.modules );
-       tests = id( "qunit-tests" );
-       if ( tests && config.hidepassed ) {
-               addClass( tests, "hidepass" );
-       }
-} );
-
-QUnit.done( function( details ) {
-       var i, key,
-               banner = id( "qunit-banner" ),
-               tests = id( "qunit-tests" ),
-               html = [
-                       "Tests completed in ",
-                       details.runtime,
-                       " milliseconds.<br />",
-                       "<span class='passed'>",
-                       details.passed,
-                       "</span> assertions of <span class='total'>",
-                       details.total,
-                       "</span> passed, <span class='failed'>",
-                       details.failed,
-                       "</span> failed."
-               ].join( "" );
-
-       if ( banner ) {
-               banner.className = details.failed ? "qunit-fail" : "qunit-pass";
-       }
-
-       if ( tests ) {
-               id( "qunit-testresult" ).innerHTML = html;
-       }
-
-       if ( config.altertitle && document.title ) {
-
-               // Show ✖ for good, ✔ for bad suite result in title
-               // use escape sequences in case file gets loaded with non-utf-8-charset
-               document.title = [
-                       ( details.failed ? "\u2716" : "\u2714" ),
-                       document.title.replace( /^[\u2714\u2716] /i, "" )
-               ].join( " " );
-       }
-
-       // Clear own sessionStorage items if all tests passed
-       if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
-               for ( i = 0; i < sessionStorage.length; i++ ) {
-                       key = sessionStorage.key( i++ );
-                       if ( key.indexOf( "qunit-test-" ) === 0 ) {
-                               sessionStorage.removeItem( key );
-                       }
-               }
-       }
-
-       // Scroll back to top to show results
-       if ( config.scrolltop && window.scrollTo ) {
-               window.scrollTo( 0, 0 );
-       }
-} );
-
-function getNameHtml( name, module ) {
-       var nameHtml = "";
-
-       if ( module ) {
-               nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
-       }
-
-       nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
-
-       return nameHtml;
-}
-
-QUnit.testStart( function( details ) {
-       var running, testBlock, bad;
-
-       testBlock = id( "qunit-test-output-" + details.testId );
-       if ( testBlock ) {
-               testBlock.className = "running";
-       } else {
-
-               // Report later registered tests
-               appendTest( details.name, details.testId, details.module );
-       }
-
-       running = id( "qunit-testresult" );
-       if ( running ) {
-               bad = QUnit.config.reorder && defined.sessionStorage &&
-                       +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
-
-               running.innerHTML = ( bad ?
-                       "Rerunning previously failed test: <br />" :
-                       "Running: <br />" ) +
-                       getNameHtml( details.name, details.module );
-       }
-
-} );
-
-function stripHtml( string ) {
-
-       // Strip tags, html entity and whitespaces
-       return string.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\&quot;/g, "" ).replace( /\s+/g, "" );
-}
-
-QUnit.log( function( details ) {
-       var assertList, assertLi,
-               message, expected, actual, diff,
-               showDiff = false,
-               testItem = id( "qunit-test-output-" + details.testId );
-
-       if ( !testItem ) {
-               return;
-       }
-
-       message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
-       message = "<span class='test-message'>" + message + "</span>";
-       message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
-
-       // The pushFailure doesn't provide details.expected
-       // when it calls, it's implicit to also not show expected and diff stuff
-       // Also, we need to check details.expected existence, as it can exist and be undefined
-       if ( !details.result && hasOwn.call( details, "expected" ) ) {
-               if ( details.negative ) {
-                       expected = "NOT " + QUnit.dump.parse( details.expected );
-               } else {
-                       expected = QUnit.dump.parse( details.expected );
-               }
-
-               actual = QUnit.dump.parse( details.actual );
-               message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
-                       escapeText( expected ) +
-                       "</pre></td></tr>";
-
-               if ( actual !== expected ) {
-
-                       message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
-                               escapeText( actual ) + "</pre></td></tr>";
-
-                       // Don't show diff if actual or expected are booleans
-                       if ( !( /^(true|false)$/.test( actual ) ) &&
-                                       !( /^(true|false)$/.test( expected ) ) ) {
-                               diff = QUnit.diff( expected, actual );
-                               showDiff = stripHtml( diff ).length !==
-                                       stripHtml( expected ).length +
-                                       stripHtml( actual ).length;
-                       }
-
-                       // Don't show diff if expected and actual are totally different
-                       if ( showDiff ) {
-                               message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
-                                       diff + "</pre></td></tr>";
-                       }
-               } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
-                               expected.indexOf( "[object Object]" ) !== -1 ) {
-                       message += "<tr class='test-message'><th>Message: </th><td>" +
-                               "Diff suppressed as the depth of object is more than current max depth (" +
-                               QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
-                               " run with a higher max depth or <a href='" +
-                               escapeText( setUrl( { maxDepth: -1 } ) ) + "'>" +
-                               "Rerun</a> without max depth.</p></td></tr>";
-               } else {
-                       message += "<tr class='test-message'><th>Message: </th><td>" +
-                               "Diff suppressed as the expected and actual results have an equivalent" +
-                               " serialization</td></tr>";
-               }
-
-               if ( details.source ) {
-                       message += "<tr class='test-source'><th>Source: </th><td><pre>" +
-                               escapeText( details.source ) + "</pre></td></tr>";
-               }
-
-               message += "</table>";
-
-       // This occurs when pushFailure is set and we have an extracted stack trace
-       } else if ( !details.result && details.source ) {
-               message += "<table>" +
-                       "<tr class='test-source'><th>Source: </th><td><pre>" +
-                       escapeText( details.source ) + "</pre></td></tr>" +
-                       "</table>";
-       }
-
-       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
-
-       assertLi = document.createElement( "li" );
-       assertLi.className = details.result ? "pass" : "fail";
-       assertLi.innerHTML = message;
-       assertList.appendChild( assertLi );
-} );
-
-QUnit.testDone( function( details ) {
-       var testTitle, time, testItem, assertList,
-               good, bad, testCounts, skipped, sourceName,
-               tests = id( "qunit-tests" );
-
-       if ( !tests ) {
-               return;
-       }
-
-       testItem = id( "qunit-test-output-" + details.testId );
-
-       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
-
-       good = details.passed;
-       bad = details.failed;
-
-       // Store result when possible
-       if ( config.reorder && defined.sessionStorage ) {
-               if ( bad ) {
-                       sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
-               } else {
-                       sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
-               }
-       }
-
-       if ( bad === 0 ) {
-
-               // Collapse the passing tests
-               addClass( assertList, "qunit-collapsed" );
-       } else if ( bad && config.collapse && !collapseNext ) {
-
-               // Skip collapsing the first failing test
-               collapseNext = true;
-       } else {
-
-               // Collapse remaining tests
-               addClass( assertList, "qunit-collapsed" );
-       }
-
-       // The testItem.firstChild is the test name
-       testTitle = testItem.firstChild;
-
-       testCounts = bad ?
-               "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
-               "";
-
-       testTitle.innerHTML += " <b class='counts'>(" + testCounts +
-               details.assertions.length + ")</b>";
-
-       if ( details.skipped ) {
-               testItem.className = "skipped";
-               skipped = document.createElement( "em" );
-               skipped.className = "qunit-skipped-label";
-               skipped.innerHTML = "skipped";
-               testItem.insertBefore( skipped, testTitle );
-       } else {
-               addEvent( testTitle, "click", function() {
-                       toggleClass( assertList, "qunit-collapsed" );
-               } );
-
-               testItem.className = bad ? "fail" : "pass";
-
-               time = document.createElement( "span" );
-               time.className = "runtime";
-               time.innerHTML = details.runtime + " ms";
-               testItem.insertBefore( time, assertList );
-       }
-
-       // Show the source of the test when showing assertions
-       if ( details.source ) {
-               sourceName = document.createElement( "p" );
-               sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
-               addClass( sourceName, "qunit-source" );
-               if ( bad === 0 ) {
-                       addClass( sourceName, "qunit-collapsed" );
-               }
-               addEvent( testTitle, "click", function() {
-                       toggleClass( sourceName, "qunit-collapsed" );
-               } );
-               testItem.appendChild( sourceName );
-       }
-} );
-
-// Avoid readyState issue with phantomjs
-// Ref: #818
-var notPhantom = ( function( p ) {
-       return !( p && p.version && p.version.major > 0 );
-} )( window.phantom );
-
-if ( notPhantom && document.readyState === "complete" ) {
-       QUnit.load();
-} else {
-       addEvent( window, "load", QUnit.load );
-}
-
-/*
- * This file is a modified version of google-diff-match-patch's JavaScript implementation
- * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
- * modifications are licensed as more fully set forth in LICENSE.txt.
- *
- * The original source of google-diff-match-patch is attributable and licensed as follows:
- *
- * Copyright 2006 Google Inc.
- * https://code.google.com/p/google-diff-match-patch/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * More Info:
- *  https://code.google.com/p/google-diff-match-patch/
- *
- * Usage: QUnit.diff(expected, actual)
- *
- */
-QUnit.diff = ( function() {
-       function DiffMatchPatch() {
-       }
-
-       //  DIFF FUNCTIONS
-
-       /**
-        * The data structure representing a diff is an array of tuples:
-        * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
-        * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
-        */
-       var DIFF_DELETE = -1,
-               DIFF_INSERT = 1,
-               DIFF_EQUAL = 0;
-
-       /**
-        * Find the differences between two texts.  Simplifies the problem by stripping
-        * any common prefix or suffix off the texts before diffing.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {boolean=} optChecklines Optional speedup flag. If present and false,
-        *     then don't run a line-level diff first to identify the changed areas.
-        *     Defaults to true, which does a faster, slightly less optimal diff.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
-               var deadline, checklines, commonlength,
-                       commonprefix, commonsuffix, diffs;
-
-               // The diff must be complete in up to 1 second.
-               deadline = ( new Date() ).getTime() + 1000;
-
-               // Check for null inputs.
-               if ( text1 === null || text2 === null ) {
-                       throw new Error( "Null input. (DiffMain)" );
-               }
-
-               // Check for equality (speedup).
-               if ( text1 === text2 ) {
-                       if ( text1 ) {
-                               return [
-                                       [ DIFF_EQUAL, text1 ]
-                               ];
-                       }
-                       return [];
-               }
-
-               if ( typeof optChecklines === "undefined" ) {
-                       optChecklines = true;
-               }
-
-               checklines = optChecklines;
-
-               // Trim off common prefix (speedup).
-               commonlength = this.diffCommonPrefix( text1, text2 );
-               commonprefix = text1.substring( 0, commonlength );
-               text1 = text1.substring( commonlength );
-               text2 = text2.substring( commonlength );
-
-               // Trim off common suffix (speedup).
-               commonlength = this.diffCommonSuffix( text1, text2 );
-               commonsuffix = text1.substring( text1.length - commonlength );
-               text1 = text1.substring( 0, text1.length - commonlength );
-               text2 = text2.substring( 0, text2.length - commonlength );
-
-               // Compute the diff on the middle block.
-               diffs = this.diffCompute( text1, text2, checklines, deadline );
-
-               // Restore the prefix and suffix.
-               if ( commonprefix ) {
-                       diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
-               }
-               if ( commonsuffix ) {
-                       diffs.push( [ DIFF_EQUAL, commonsuffix ] );
-               }
-               this.diffCleanupMerge( diffs );
-               return diffs;
-       };
-
-       /**
-        * Reduce the number of edits by eliminating operationally trivial equalities.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
-               var changes, equalities, equalitiesLength, lastequality,
-                       pointer, preIns, preDel, postIns, postDel;
-               changes = false;
-               equalities = []; // Stack of indices where equalities are found.
-               equalitiesLength = 0; // Keeping our own length var is faster in JS.
-               /** @type {?string} */
-               lastequality = null;
-
-               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
-               pointer = 0; // Index of current position.
-
-               // Is there an insertion operation before the last equality.
-               preIns = false;
-
-               // Is there a deletion operation before the last equality.
-               preDel = false;
-
-               // Is there an insertion operation after the last equality.
-               postIns = false;
-
-               // Is there a deletion operation after the last equality.
-               postDel = false;
-               while ( pointer < diffs.length ) {
-
-                       // Equality found.
-                       if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
-                               if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
-
-                                       // Candidate found.
-                                       equalities[ equalitiesLength++ ] = pointer;
-                                       preIns = postIns;
-                                       preDel = postDel;
-                                       lastequality = diffs[ pointer ][ 1 ];
-                               } else {
-
-                                       // Not a candidate, and can never become one.
-                                       equalitiesLength = 0;
-                                       lastequality = null;
-                               }
-                               postIns = postDel = false;
-
-                       // An insertion or deletion.
-                       } else {
-
-                               if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
-                                       postDel = true;
-                               } else {
-                                       postIns = true;
-                               }
-
-                               /*
-                                * Five types to be split:
-                                * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
-                                * <ins>A</ins>X<ins>C</ins><del>D</del>
-                                * <ins>A</ins><del>B</del>X<ins>C</ins>
-                                * <ins>A</del>X<ins>C</ins><del>D</del>
-                                * <ins>A</ins><del>B</del>X<del>C</del>
-                                */
-                               if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
-                                               ( ( lastequality.length < 2 ) &&
-                                               ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
-
-                                       // Duplicate record.
-                                       diffs.splice(
-                                               equalities[ equalitiesLength - 1 ],
-                                               0,
-                                               [ DIFF_DELETE, lastequality ]
-                                       );
-
-                                       // Change second copy to insert.
-                                       diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
-                                       equalitiesLength--; // Throw away the equality we just deleted;
-                                       lastequality = null;
-                                       if ( preIns && preDel ) {
-
-                                               // No changes made which could affect previous entry, keep going.
-                                               postIns = postDel = true;
-                                               equalitiesLength = 0;
-                                       } else {
-                                               equalitiesLength--; // Throw away the previous equality.
-                                               pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
-                                               postIns = postDel = false;
-                                       }
-                                       changes = true;
-                               }
-                       }
-                       pointer++;
-               }
-
-               if ( changes ) {
-                       this.diffCleanupMerge( diffs );
-               }
-       };
-
-       /**
-        * Convert a diff array into a pretty HTML report.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        * @param {integer} string to be beautified.
-        * @return {string} HTML representation.
-        */
-       DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
-               var op, data, x,
-                       html = [];
-               for ( x = 0; x < diffs.length; x++ ) {
-                       op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
-                       data = diffs[ x ][ 1 ]; // Text of change.
-                       switch ( op ) {
-                       case DIFF_INSERT:
-                               html[ x ] = "<ins>" + escapeText( data ) + "</ins>";
-                               break;
-                       case DIFF_DELETE:
-                               html[ x ] = "<del>" + escapeText( data ) + "</del>";
-                               break;
-                       case DIFF_EQUAL:
-                               html[ x ] = "<span>" + escapeText( data ) + "</span>";
-                               break;
-                       }
-               }
-               return html.join( "" );
-       };
-
-       /**
-        * Determine the common prefix of two strings.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {number} The number of characters common to the start of each
-        *     string.
-        */
-       DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
-               var pointermid, pointermax, pointermin, pointerstart;
-
-               // Quick check for common null cases.
-               if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
-                       return 0;
-               }
-
-               // Binary search.
-               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
-               pointermin = 0;
-               pointermax = Math.min( text1.length, text2.length );
-               pointermid = pointermax;
-               pointerstart = 0;
-               while ( pointermin < pointermid ) {
-                       if ( text1.substring( pointerstart, pointermid ) ===
-                                       text2.substring( pointerstart, pointermid ) ) {
-                               pointermin = pointermid;
-                               pointerstart = pointermin;
-                       } else {
-                               pointermax = pointermid;
-                       }
-                       pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
-               }
-               return pointermid;
-       };
-
-       /**
-        * Determine the common suffix of two strings.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {number} The number of characters common to the end of each string.
-        */
-       DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
-               var pointermid, pointermax, pointermin, pointerend;
-
-               // Quick check for common null cases.
-               if ( !text1 ||
-                               !text2 ||
-                               text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
-                       return 0;
-               }
-
-               // Binary search.
-               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
-               pointermin = 0;
-               pointermax = Math.min( text1.length, text2.length );
-               pointermid = pointermax;
-               pointerend = 0;
-               while ( pointermin < pointermid ) {
-                       if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
-                                       text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
-                               pointermin = pointermid;
-                               pointerend = pointermin;
-                       } else {
-                               pointermax = pointermid;
-                       }
-                       pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
-               }
-               return pointermid;
-       };
-
-       /**
-        * Find the differences between two texts.  Assumes that the texts do not
-        * have any common prefix or suffix.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {boolean} checklines Speedup flag.  If false, then don't run a
-        *     line-level diff first to identify the changed areas.
-        *     If true, then run a faster, slightly less optimal diff.
-        * @param {number} deadline Time when the diff should be complete by.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
-               var diffs, longtext, shorttext, i, hm,
-                       text1A, text2A, text1B, text2B,
-                       midCommon, diffsA, diffsB;
-
-               if ( !text1 ) {
-
-                       // Just add some text (speedup).
-                       return [
-                               [ DIFF_INSERT, text2 ]
-                       ];
-               }
-
-               if ( !text2 ) {
-
-                       // Just delete some text (speedup).
-                       return [
-                               [ DIFF_DELETE, text1 ]
-                       ];
-               }
-
-               longtext = text1.length > text2.length ? text1 : text2;
-               shorttext = text1.length > text2.length ? text2 : text1;
-               i = longtext.indexOf( shorttext );
-               if ( i !== -1 ) {
-
-                       // Shorter text is inside the longer text (speedup).
-                       diffs = [
-                               [ DIFF_INSERT, longtext.substring( 0, i ) ],
-                               [ DIFF_EQUAL, shorttext ],
-                               [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
-                       ];
-
-                       // Swap insertions for deletions if diff is reversed.
-                       if ( text1.length > text2.length ) {
-                               diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
-                       }
-                       return diffs;
-               }
-
-               if ( shorttext.length === 1 ) {
-
-                       // Single character string.
-                       // After the previous speedup, the character can't be an equality.
-                       return [
-                               [ DIFF_DELETE, text1 ],
-                               [ DIFF_INSERT, text2 ]
-                       ];
-               }
-
-               // Check to see if the problem can be split in two.
-               hm = this.diffHalfMatch( text1, text2 );
-               if ( hm ) {
-
-                       // A half-match was found, sort out the return data.
-                       text1A = hm[ 0 ];
-                       text1B = hm[ 1 ];
-                       text2A = hm[ 2 ];
-                       text2B = hm[ 3 ];
-                       midCommon = hm[ 4 ];
-
-                       // Send both pairs off for separate processing.
-                       diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
-                       diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
-
-                       // Merge the results.
-                       return diffsA.concat( [
-                               [ DIFF_EQUAL, midCommon ]
-                       ], diffsB );
-               }
-
-               if ( checklines && text1.length > 100 && text2.length > 100 ) {
-                       return this.diffLineMode( text1, text2, deadline );
-               }
-
-               return this.diffBisect( text1, text2, deadline );
-       };
-
-       /**
-        * Do the two texts share a substring which is at least half the length of the
-        * longer text?
-        * This speedup can produce non-minimal diffs.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {Array.<string>} Five element Array, containing the prefix of
-        *     text1, the suffix of text1, the prefix of text2, the suffix of
-        *     text2 and the common middle.  Or null if there was no match.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
-               var longtext, shorttext, dmp,
-                       text1A, text2B, text2A, text1B, midCommon,
-                       hm1, hm2, hm;
-
-               longtext = text1.length > text2.length ? text1 : text2;
-               shorttext = text1.length > text2.length ? text2 : text1;
-               if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
-                       return null; // Pointless.
-               }
-               dmp = this; // 'this' becomes 'window' in a closure.
-
-               /**
-                * Does a substring of shorttext exist within longtext such that the substring
-                * is at least half the length of longtext?
-                * Closure, but does not reference any external variables.
-                * @param {string} longtext Longer string.
-                * @param {string} shorttext Shorter string.
-                * @param {number} i Start index of quarter length substring within longtext.
-                * @return {Array.<string>} Five element Array, containing the prefix of
-                *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
-                *     of shorttext and the common middle.  Or null if there was no match.
-                * @private
-                */
-               function diffHalfMatchI( longtext, shorttext, i ) {
-                       var seed, j, bestCommon, prefixLength, suffixLength,
-                               bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
-
-                       // Start with a 1/4 length substring at position i as a seed.
-                       seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
-                       j = -1;
-                       bestCommon = "";
-                       while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
-                               prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
-                                       shorttext.substring( j ) );
-                               suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
-                                       shorttext.substring( 0, j ) );
-                               if ( bestCommon.length < suffixLength + prefixLength ) {
-                                       bestCommon = shorttext.substring( j - suffixLength, j ) +
-                                               shorttext.substring( j, j + prefixLength );
-                                       bestLongtextA = longtext.substring( 0, i - suffixLength );
-                                       bestLongtextB = longtext.substring( i + prefixLength );
-                                       bestShorttextA = shorttext.substring( 0, j - suffixLength );
-                                       bestShorttextB = shorttext.substring( j + prefixLength );
-                               }
-                       }
-                       if ( bestCommon.length * 2 >= longtext.length ) {
-                               return [ bestLongtextA, bestLongtextB,
-                                       bestShorttextA, bestShorttextB, bestCommon
-                               ];
-                       } else {
-                               return null;
-                       }
-               }
-
-               // First check if the second quarter is the seed for a half-match.
-               hm1 = diffHalfMatchI( longtext, shorttext,
-                       Math.ceil( longtext.length / 4 ) );
-
-               // Check again based on the third quarter.
-               hm2 = diffHalfMatchI( longtext, shorttext,
-                       Math.ceil( longtext.length / 2 ) );
-               if ( !hm1 && !hm2 ) {
-                       return null;
-               } else if ( !hm2 ) {
-                       hm = hm1;
-               } else if ( !hm1 ) {
-                       hm = hm2;
-               } else {
-
-                       // Both matched.  Select the longest.
-                       hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
-               }
-
-               // A half-match was found, sort out the return data.
-               text1A, text1B, text2A, text2B;
-               if ( text1.length > text2.length ) {
-                       text1A = hm[ 0 ];
-                       text1B = hm[ 1 ];
-                       text2A = hm[ 2 ];
-                       text2B = hm[ 3 ];
-               } else {
-                       text2A = hm[ 0 ];
-                       text2B = hm[ 1 ];
-                       text1A = hm[ 2 ];
-                       text1B = hm[ 3 ];
-               }
-               midCommon = hm[ 4 ];
-               return [ text1A, text1B, text2A, text2B, midCommon ];
-       };
-
-       /**
-        * Do a quick line-level diff on both strings, then rediff the parts for
-        * greater accuracy.
-        * This speedup can produce non-minimal diffs.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {number} deadline Time when the diff should be complete by.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
-               var a, diffs, linearray, pointer, countInsert,
-                       countDelete, textInsert, textDelete, j;
-
-               // Scan the text on a line-by-line basis first.
-               a = this.diffLinesToChars( text1, text2 );
-               text1 = a.chars1;
-               text2 = a.chars2;
-               linearray = a.lineArray;
-
-               diffs = this.DiffMain( text1, text2, false, deadline );
-
-               // Convert the diff back to original text.
-               this.diffCharsToLines( diffs, linearray );
-
-               // Eliminate freak matches (e.g. blank lines)
-               this.diffCleanupSemantic( diffs );
-
-               // Rediff any replacement blocks, this time character-by-character.
-               // Add a dummy entry at the end.
-               diffs.push( [ DIFF_EQUAL, "" ] );
-               pointer = 0;
-               countDelete = 0;
-               countInsert = 0;
-               textDelete = "";
-               textInsert = "";
-               while ( pointer < diffs.length ) {
-                       switch ( diffs[ pointer ][ 0 ] ) {
-                       case DIFF_INSERT:
-                               countInsert++;
-                               textInsert += diffs[ pointer ][ 1 ];
-                               break;
-                       case DIFF_DELETE:
-                               countDelete++;
-                               textDelete += diffs[ pointer ][ 1 ];
-                               break;
-                       case DIFF_EQUAL:
-
-                               // Upon reaching an equality, check for prior redundancies.
-                               if ( countDelete >= 1 && countInsert >= 1 ) {
-
-                                       // Delete the offending records and add the merged ones.
-                                       diffs.splice( pointer - countDelete - countInsert,
-                                               countDelete + countInsert );
-                                       pointer = pointer - countDelete - countInsert;
-                                       a = this.DiffMain( textDelete, textInsert, false, deadline );
-                                       for ( j = a.length - 1; j >= 0; j-- ) {
-                                               diffs.splice( pointer, 0, a[ j ] );
-                                       }
-                                       pointer = pointer + a.length;
-                               }
-                               countInsert = 0;
-                               countDelete = 0;
-                               textDelete = "";
-                               textInsert = "";
-                               break;
-                       }
-                       pointer++;
-               }
-               diffs.pop(); // Remove the dummy entry at the end.
-
-               return diffs;
-       };
-
-       /**
-        * Find the 'middle snake' of a diff, split the problem in two
-        * and return the recursively constructed diff.
-        * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {number} deadline Time at which to bail if not yet complete.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
-               var text1Length, text2Length, maxD, vOffset, vLength,
-                       v1, v2, x, delta, front, k1start, k1end, k2start,
-                       k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
-
-               // Cache the text lengths to prevent multiple calls.
-               text1Length = text1.length;
-               text2Length = text2.length;
-               maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
-               vOffset = maxD;
-               vLength = 2 * maxD;
-               v1 = new Array( vLength );
-               v2 = new Array( vLength );
-
-               // Setting all elements to -1 is faster in Chrome & Firefox than mixing
-               // integers and undefined.
-               for ( x = 0; x < vLength; x++ ) {
-                       v1[ x ] = -1;
-                       v2[ x ] = -1;
-               }
-               v1[ vOffset + 1 ] = 0;
-               v2[ vOffset + 1 ] = 0;
-               delta = text1Length - text2Length;
-
-               // If the total number of characters is odd, then the front path will collide
-               // with the reverse path.
-               front = ( delta % 2 !== 0 );
-
-               // Offsets for start and end of k loop.
-               // Prevents mapping of space beyond the grid.
-               k1start = 0;
-               k1end = 0;
-               k2start = 0;
-               k2end = 0;
-               for ( d = 0; d < maxD; d++ ) {
-
-                       // Bail out if deadline is reached.
-                       if ( ( new Date() ).getTime() > deadline ) {
-                               break;
-                       }
-
-                       // Walk the front path one step.
-                       for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
-                               k1Offset = vOffset + k1;
-                               if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
-                                       x1 = v1[ k1Offset + 1 ];
-                               } else {
-                                       x1 = v1[ k1Offset - 1 ] + 1;
-                               }
-                               y1 = x1 - k1;
-                               while ( x1 < text1Length && y1 < text2Length &&
-                                       text1.charAt( x1 ) === text2.charAt( y1 ) ) {
-                                       x1++;
-                                       y1++;
-                               }
-                               v1[ k1Offset ] = x1;
-                               if ( x1 > text1Length ) {
-
-                                       // Ran off the right of the graph.
-                                       k1end += 2;
-                               } else if ( y1 > text2Length ) {
-
-                                       // Ran off the bottom of the graph.
-                                       k1start += 2;
-                               } else if ( front ) {
-                                       k2Offset = vOffset + delta - k1;
-                                       if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
-
-                                               // Mirror x2 onto top-left coordinate system.
-                                               x2 = text1Length - v2[ k2Offset ];
-                                               if ( x1 >= x2 ) {
-
-                                                       // Overlap detected.
-                                                       return this.diffBisectSplit( text1, text2, x1, y1, deadline );
-                                               }
-                                       }
-                               }
-                       }
-
-                       // Walk the reverse path one step.
-                       for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
-                               k2Offset = vOffset + k2;
-                               if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
-                                       x2 = v2[ k2Offset + 1 ];
-                               } else {
-                                       x2 = v2[ k2Offset - 1 ] + 1;
-                               }
-                               y2 = x2 - k2;
-                               while ( x2 < text1Length && y2 < text2Length &&
-                                       text1.charAt( text1Length - x2 - 1 ) ===
-                                       text2.charAt( text2Length - y2 - 1 ) ) {
-                                       x2++;
-                                       y2++;
-                               }
-                               v2[ k2Offset ] = x2;
-                               if ( x2 > text1Length ) {
-
-                                       // Ran off the left of the graph.
-                                       k2end += 2;
-                               } else if ( y2 > text2Length ) {
-
-                                       // Ran off the top of the graph.
-                                       k2start += 2;
-                               } else if ( !front ) {
-                                       k1Offset = vOffset + delta - k2;
-                                       if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
-                                               x1 = v1[ k1Offset ];
-                                               y1 = vOffset + x1 - k1Offset;
-
-                                               // Mirror x2 onto top-left coordinate system.
-                                               x2 = text1Length - x2;
-                                               if ( x1 >= x2 ) {
-
-                                                       // Overlap detected.
-                                                       return this.diffBisectSplit( text1, text2, x1, y1, deadline );
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               // Diff took too long and hit the deadline or
-               // number of diffs equals number of characters, no commonality at all.
-               return [
-                       [ DIFF_DELETE, text1 ],
-                       [ DIFF_INSERT, text2 ]
-               ];
-       };
-
-       /**
-        * Given the location of the 'middle snake', split the diff in two parts
-        * and recurse.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {number} x Index of split point in text1.
-        * @param {number} y Index of split point in text2.
-        * @param {number} deadline Time at which to bail if not yet complete.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
-               var text1a, text1b, text2a, text2b, diffs, diffsb;
-               text1a = text1.substring( 0, x );
-               text2a = text2.substring( 0, y );
-               text1b = text1.substring( x );
-               text2b = text2.substring( y );
-
-               // Compute both diffs serially.
-               diffs = this.DiffMain( text1a, text2a, false, deadline );
-               diffsb = this.DiffMain( text1b, text2b, false, deadline );
-
-               return diffs.concat( diffsb );
-       };
-
-       /**
-        * Reduce the number of edits by eliminating semantically trivial equalities.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
-               var changes, equalities, equalitiesLength, lastequality,
-                       pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
-                       lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
-               changes = false;
-               equalities = []; // Stack of indices where equalities are found.
-               equalitiesLength = 0; // Keeping our own length var is faster in JS.
-               /** @type {?string} */
-               lastequality = null;
-
-               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
-               pointer = 0; // Index of current position.
-
-               // Number of characters that changed prior to the equality.
-               lengthInsertions1 = 0;
-               lengthDeletions1 = 0;
-
-               // Number of characters that changed after the equality.
-               lengthInsertions2 = 0;
-               lengthDeletions2 = 0;
-               while ( pointer < diffs.length ) {
-                       if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
-                               equalities[ equalitiesLength++ ] = pointer;
-                               lengthInsertions1 = lengthInsertions2;
-                               lengthDeletions1 = lengthDeletions2;
-                               lengthInsertions2 = 0;
-                               lengthDeletions2 = 0;
-                               lastequality = diffs[ pointer ][ 1 ];
-                       } else { // An insertion or deletion.
-                               if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
-                                       lengthInsertions2 += diffs[ pointer ][ 1 ].length;
-                               } else {
-                                       lengthDeletions2 += diffs[ pointer ][ 1 ].length;
-                               }
-
-                               // Eliminate an equality that is smaller or equal to the edits on both
-                               // sides of it.
-                               if ( lastequality && ( lastequality.length <=
-                                               Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
-                                               ( lastequality.length <= Math.max( lengthInsertions2,
-                                                       lengthDeletions2 ) ) ) {
-
-                                       // Duplicate record.
-                                       diffs.splice(
-                                               equalities[ equalitiesLength - 1 ],
-                                               0,
-                                               [ DIFF_DELETE, lastequality ]
-                                       );
-
-                                       // Change second copy to insert.
-                                       diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
-
-                                       // Throw away the equality we just deleted.
-                                       equalitiesLength--;
-
-                                       // Throw away the previous equality (it needs to be reevaluated).
-                                       equalitiesLength--;
-                                       pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
-
-                                       // Reset the counters.
-                                       lengthInsertions1 = 0;
-                                       lengthDeletions1 = 0;
-                                       lengthInsertions2 = 0;
-                                       lengthDeletions2 = 0;
-                                       lastequality = null;
-                                       changes = true;
-                               }
-                       }
-                       pointer++;
-               }
-
-               // Normalize the diff.
-               if ( changes ) {
-                       this.diffCleanupMerge( diffs );
-               }
-
-               // Find any overlaps between deletions and insertions.
-               // e.g: <del>abcxxx</del><ins>xxxdef</ins>
-               //   -> <del>abc</del>xxx<ins>def</ins>
-               // e.g: <del>xxxabc</del><ins>defxxx</ins>
-               //   -> <ins>def</ins>xxx<del>abc</del>
-               // Only extract an overlap if it is as big as the edit ahead or behind it.
-               pointer = 1;
-               while ( pointer < diffs.length ) {
-                       if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
-                                       diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
-                               deletion = diffs[ pointer - 1 ][ 1 ];
-                               insertion = diffs[ pointer ][ 1 ];
-                               overlapLength1 = this.diffCommonOverlap( deletion, insertion );
-                               overlapLength2 = this.diffCommonOverlap( insertion, deletion );
-                               if ( overlapLength1 >= overlapLength2 ) {
-                                       if ( overlapLength1 >= deletion.length / 2 ||
-                                                       overlapLength1 >= insertion.length / 2 ) {
-
-                                               // Overlap found.  Insert an equality and trim the surrounding edits.
-                                               diffs.splice(
-                                                       pointer,
-                                                       0,
-                                                       [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
-                                               );
-                                               diffs[ pointer - 1 ][ 1 ] =
-                                                       deletion.substring( 0, deletion.length - overlapLength1 );
-                                               diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
-                                               pointer++;
-                                       }
-                               } else {
-                                       if ( overlapLength2 >= deletion.length / 2 ||
-                                                       overlapLength2 >= insertion.length / 2 ) {
-
-                                               // Reverse overlap found.
-                                               // Insert an equality and swap and trim the surrounding edits.
-                                               diffs.splice(
-                                                       pointer,
-                                                       0,
-                                                       [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
-                                               );
-
-                                               diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
-                                               diffs[ pointer - 1 ][ 1 ] =
-                                                       insertion.substring( 0, insertion.length - overlapLength2 );
-                                               diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
-                                               diffs[ pointer + 1 ][ 1 ] =
-                                                       deletion.substring( overlapLength2 );
-                                               pointer++;
-                                       }
-                               }
-                               pointer++;
-                       }
-                       pointer++;
-               }
-       };
-
-       /**
-        * Determine if the suffix of one string is the prefix of another.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {number} The number of characters common to the end of the first
-        *     string and the start of the second string.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
-               var text1Length, text2Length, textLength,
-                       best, length, pattern, found;
-
-               // Cache the text lengths to prevent multiple calls.
-               text1Length = text1.length;
-               text2Length = text2.length;
-
-               // Eliminate the null case.
-               if ( text1Length === 0 || text2Length === 0 ) {
-                       return 0;
-               }
-
-               // Truncate the longer string.
-               if ( text1Length > text2Length ) {
-                       text1 = text1.substring( text1Length - text2Length );
-               } else if ( text1Length < text2Length ) {
-                       text2 = text2.substring( 0, text1Length );
-               }
-               textLength = Math.min( text1Length, text2Length );
-
-               // Quick check for the worst case.
-               if ( text1 === text2 ) {
-                       return textLength;
-               }
-
-               // Start by looking for a single character match
-               // and increase length until no match is found.
-               // Performance analysis: https://neil.fraser.name/news/2010/11/04/
-               best = 0;
-               length = 1;
-               while ( true ) {
-                       pattern = text1.substring( textLength - length );
-                       found = text2.indexOf( pattern );
-                       if ( found === -1 ) {
-                               return best;
-                       }
-                       length += found;
-                       if ( found === 0 || text1.substring( textLength - length ) ===
-                                       text2.substring( 0, length ) ) {
-                               best = length;
-                               length++;
-                       }
-               }
-       };
-
-       /**
-        * Split two texts into an array of strings.  Reduce the texts to a string of
-        * hashes where each Unicode character represents one line.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
-        *     An object containing the encoded text1, the encoded text2 and
-        *     the array of unique strings.
-        *     The zeroth element of the array of unique strings is intentionally blank.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
-               var lineArray, lineHash, chars1, chars2;
-               lineArray = []; // E.g. lineArray[4] === 'Hello\n'
-               lineHash = {};  // E.g. lineHash['Hello\n'] === 4
-
-               // '\x00' is a valid character, but various debuggers don't like it.
-               // So we'll insert a junk entry to avoid generating a null character.
-               lineArray[ 0 ] = "";
-
-               /**
-                * Split a text into an array of strings.  Reduce the texts to a string of
-                * hashes where each Unicode character represents one line.
-                * Modifies linearray and linehash through being a closure.
-                * @param {string} text String to encode.
-                * @return {string} Encoded string.
-                * @private
-                */
-               function diffLinesToCharsMunge( text ) {
-                       var chars, lineStart, lineEnd, lineArrayLength, line;
-                       chars = "";
-
-                       // Walk the text, pulling out a substring for each line.
-                       // text.split('\n') would would temporarily double our memory footprint.
-                       // Modifying text would create many large strings to garbage collect.
-                       lineStart = 0;
-                       lineEnd = -1;
-
-                       // Keeping our own length variable is faster than looking it up.
-                       lineArrayLength = lineArray.length;
-                       while ( lineEnd < text.length - 1 ) {
-                               lineEnd = text.indexOf( "\n", lineStart );
-                               if ( lineEnd === -1 ) {
-                                       lineEnd = text.length - 1;
-                               }
-                               line = text.substring( lineStart, lineEnd + 1 );
-                               lineStart = lineEnd + 1;
-
-                               if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
-                                                       ( lineHash[ line ] !== undefined ) ) {
-                                       chars += String.fromCharCode( lineHash[ line ] );
-                               } else {
-                                       chars += String.fromCharCode( lineArrayLength );
-                                       lineHash[ line ] = lineArrayLength;
-                                       lineArray[ lineArrayLength++ ] = line;
-                               }
-                       }
-                       return chars;
-               }
-
-               chars1 = diffLinesToCharsMunge( text1 );
-               chars2 = diffLinesToCharsMunge( text2 );
-               return {
-                       chars1: chars1,
-                       chars2: chars2,
-                       lineArray: lineArray
-               };
-       };
-
-       /**
-        * Rehydrate the text in a diff from a string of line hashes to real lines of
-        * text.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        * @param {!Array.<string>} lineArray Array of unique strings.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
-               var x, chars, text, y;
-               for ( x = 0; x < diffs.length; x++ ) {
-                       chars = diffs[ x ][ 1 ];
-                       text = [];
-                       for ( y = 0; y < chars.length; y++ ) {
-                               text[ y ] = lineArray[ chars.charCodeAt( y ) ];
-                       }
-                       diffs[ x ][ 1 ] = text.join( "" );
-               }
-       };
-
-       /**
-        * Reorder and merge like edit sections.  Merge equalities.
-        * Any edit section can move as long as it doesn't cross an equality.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
-               var pointer, countDelete, countInsert, textInsert, textDelete,
-                       commonlength, changes, diffPointer, position;
-               diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
-               pointer = 0;
-               countDelete = 0;
-               countInsert = 0;
-               textDelete = "";
-               textInsert = "";
-               commonlength;
-               while ( pointer < diffs.length ) {
-                       switch ( diffs[ pointer ][ 0 ] ) {
-                       case DIFF_INSERT:
-                               countInsert++;
-                               textInsert += diffs[ pointer ][ 1 ];
-                               pointer++;
-                               break;
-                       case DIFF_DELETE:
-                               countDelete++;
-                               textDelete += diffs[ pointer ][ 1 ];
-                               pointer++;
-                               break;
-                       case DIFF_EQUAL:
-
-                               // Upon reaching an equality, check for prior redundancies.
-                               if ( countDelete + countInsert > 1 ) {
-                                       if ( countDelete !== 0 && countInsert !== 0 ) {
-
-                                               // Factor out any common prefixes.
-                                               commonlength = this.diffCommonPrefix( textInsert, textDelete );
-                                               if ( commonlength !== 0 ) {
-                                                       if ( ( pointer - countDelete - countInsert ) > 0 &&
-                                                                       diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
-                                                                       DIFF_EQUAL ) {
-                                                               diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
-                                                                       textInsert.substring( 0, commonlength );
-                                                       } else {
-                                                               diffs.splice( 0, 0, [ DIFF_EQUAL,
-                                                                       textInsert.substring( 0, commonlength )
-                                                               ] );
-                                                               pointer++;
-                                                       }
-                                                       textInsert = textInsert.substring( commonlength );
-                                                       textDelete = textDelete.substring( commonlength );
-                                               }
-
-                                               // Factor out any common suffixies.
-                                               commonlength = this.diffCommonSuffix( textInsert, textDelete );
-                                               if ( commonlength !== 0 ) {
-                                                       diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
-                                                                       commonlength ) + diffs[ pointer ][ 1 ];
-                                                       textInsert = textInsert.substring( 0, textInsert.length -
-                                                               commonlength );
-                                                       textDelete = textDelete.substring( 0, textDelete.length -
-                                                               commonlength );
-                                               }
-                                       }
-
-                                       // Delete the offending records and add the merged ones.
-                                       if ( countDelete === 0 ) {
-                                               diffs.splice( pointer - countInsert,
-                                                       countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
-                                       } else if ( countInsert === 0 ) {
-                                               diffs.splice( pointer - countDelete,
-                                                       countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
-                                       } else {
-                                               diffs.splice(
-                                                       pointer - countDelete - countInsert,
-                                                       countDelete + countInsert,
-                                                       [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
-                                               );
-                                       }
-                                       pointer = pointer - countDelete - countInsert +
-                                               ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
-                               } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
-
-                                       // Merge this equality with the previous one.
-                                       diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
-                                       diffs.splice( pointer, 1 );
-                               } else {
-                                       pointer++;
-                               }
-                               countInsert = 0;
-                               countDelete = 0;
-                               textDelete = "";
-                               textInsert = "";
-                               break;
-                       }
-               }
-               if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
-                       diffs.pop(); // Remove the dummy entry at the end.
-               }
-
-               // Second pass: look for single edits surrounded on both sides by equalities
-               // which can be shifted sideways to eliminate an equality.
-               // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
-               changes = false;
-               pointer = 1;
-
-               // Intentionally ignore the first and last element (don't need checking).
-               while ( pointer < diffs.length - 1 ) {
-                       if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
-                                       diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
-
-                               diffPointer = diffs[ pointer ][ 1 ];
-                               position = diffPointer.substring(
-                                       diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
-                               );
-
-                               // This is a single edit surrounded by equalities.
-                               if ( position === diffs[ pointer - 1 ][ 1 ] ) {
-
-                                       // Shift the edit over the previous equality.
-                                       diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
-                                               diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
-                                                       diffs[ pointer - 1 ][ 1 ].length );
-                                       diffs[ pointer + 1 ][ 1 ] =
-                                               diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
-                                       diffs.splice( pointer - 1, 1 );
-                                       changes = true;
-                               } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
-                                               diffs[ pointer + 1 ][ 1 ] ) {
-
-                                       // Shift the edit over the next equality.
-                                       diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
-                                       diffs[ pointer ][ 1 ] =
-                                               diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
-                                               diffs[ pointer + 1 ][ 1 ];
-                                       diffs.splice( pointer + 1, 1 );
-                                       changes = true;
-                               }
-                       }
-                       pointer++;
-               }
-
-               // If shifts were made, the diff needs reordering and another shift sweep.
-               if ( changes ) {
-                       this.diffCleanupMerge( diffs );
-               }
-       };
-
-       return function( o, n ) {
-               var diff, output, text;
-               diff = new DiffMatchPatch();
-               output = diff.DiffMain( o, n );
-               diff.diffCleanupEfficiency( output );
-               text = diff.diffPrettyHtml( output );
-
-               return text;
-       };
-}() );
-
-}() );
+
+
+  var classCallCheck = function (instance, Constructor) {
+    if (!(instance instanceof Constructor)) {
+      throw new TypeError("Cannot call a class as a function");
+    }
+  };
+
+  var createClass = function () {
+    function defineProperties(target, props) {
+      for (var i = 0; i < props.length; i++) {
+        var descriptor = props[i];
+        descriptor.enumerable = descriptor.enumerable || false;
+        descriptor.configurable = true;
+        if ("value" in descriptor) descriptor.writable = true;
+        Object.defineProperty(target, descriptor.key, descriptor);
+      }
+    }
+
+    return function (Constructor, protoProps, staticProps) {
+      if (protoProps) defineProperties(Constructor.prototype, protoProps);
+      if (staticProps) defineProperties(Constructor, staticProps);
+      return Constructor;
+    };
+  }();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  var toConsumableArray = function (arr) {
+    if (Array.isArray(arr)) {
+      for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
+
+      return arr2;
+    } else {
+      return Array.from(arr);
+    }
+  };
+
+  var toString = Object.prototype.toString;
+  var hasOwn = Object.prototype.hasOwnProperty;
+  var now = Date.now || function () {
+       return new Date().getTime();
+  };
+
+  var defined = {
+       document: window && window.document !== undefined,
+       setTimeout: setTimeout !== undefined
+  };
+
+  // Returns a new Array with the elements that are in a but not in b
+  function diff(a, b) {
+       var i,
+           j,
+           result = a.slice();
+
+       for (i = 0; i < result.length; i++) {
+               for (j = 0; j < b.length; j++) {
+                       if (result[i] === b[j]) {
+                               result.splice(i, 1);
+                               i--;
+                               break;
+                       }
+               }
+       }
+       return result;
+  }
+
+  /**
+   * Determines whether an element exists in a given array or not.
+   *
+   * @method inArray
+   * @param {Any} elem
+   * @param {Array} array
+   * @return {Boolean}
+   */
+  function inArray(elem, array) {
+       return array.indexOf(elem) !== -1;
+  }
+
+  /**
+   * Makes a clone of an object using only Array or Object as base,
+   * and copies over the own enumerable properties.
+   *
+   * @param {Object} obj
+   * @return {Object} New object with only the own properties (recursively).
+   */
+  function objectValues(obj) {
+       var key,
+           val,
+           vals = is("array", obj) ? [] : {};
+       for (key in obj) {
+               if (hasOwn.call(obj, key)) {
+                       val = obj[key];
+                       vals[key] = val === Object(val) ? objectValues(val) : val;
+               }
+       }
+       return vals;
+  }
+
+  function extend(a, b, undefOnly) {
+       for (var prop in b) {
+               if (hasOwn.call(b, prop)) {
+                       if (b[prop] === undefined) {
+                               delete a[prop];
+                       } else if (!(undefOnly && typeof a[prop] !== "undefined")) {
+                               a[prop] = b[prop];
+                       }
+               }
+       }
+
+       return a;
+  }
+
+  function objectType(obj) {
+       if (typeof obj === "undefined") {
+               return "undefined";
+       }
+
+       // Consider: typeof null === object
+       if (obj === null) {
+               return "null";
+       }
+
+       var match = toString.call(obj).match(/^\[object\s(.*)\]$/),
+           type = match && match[1];
+
+       switch (type) {
+               case "Number":
+                       if (isNaN(obj)) {
+                               return "nan";
+                       }
+                       return "number";
+               case "String":
+               case "Boolean":
+               case "Array":
+               case "Set":
+               case "Map":
+               case "Date":
+               case "RegExp":
+               case "Function":
+               case "Symbol":
+                       return type.toLowerCase();
+               default:
+                       return typeof obj === "undefined" ? "undefined" : _typeof(obj);
+       }
+  }
+
+  // Safe object type checking
+  function is(type, obj) {
+       return objectType(obj) === type;
+  }
+
+  // Based on Java's String.hashCode, a simple but not
+  // rigorously collision resistant hashing function
+  function generateHash(module, testName) {
+       var str = module + "\x1C" + testName;
+       var hash = 0;
+
+       for (var i = 0; i < str.length; i++) {
+               hash = (hash << 5) - hash + str.charCodeAt(i);
+               hash |= 0;
+       }
+
+       // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
+       // strictly necessary but increases user understanding that the id is a SHA-like hash
+       var hex = (0x100000000 + hash).toString(16);
+       if (hex.length < 8) {
+               hex = "0000000" + hex;
+       }
+
+       return hex.slice(-8);
+  }
+
+  // Test for equality any JavaScript type.
+  // Authors: Philippe Rathé <prathe@gmail.com>, David Chan <david@troi.org>
+  var equiv = (function () {
+
+       // Value pairs queued for comparison. Used for breadth-first processing order, recursion
+       // detection and avoiding repeated comparison (see below for details).
+       // Elements are { a: val, b: val }.
+       var pairs = [];
+
+       var getProto = Object.getPrototypeOf || function (obj) {
+               return obj.__proto__;
+       };
+
+       function useStrictEquality(a, b) {
+
+               // This only gets called if a and b are not strict equal, and is used to compare on
+               // the primitive values inside object wrappers. For example:
+               // `var i = 1;`
+               // `var j = new Number(1);`
+               // Neither a nor b can be null, as a !== b and they have the same type.
+               if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") {
+                       a = a.valueOf();
+               }
+               if ((typeof b === "undefined" ? "undefined" : _typeof(b)) === "object") {
+                       b = b.valueOf();
+               }
+
+               return a === b;
+       }
+
+       function compareConstructors(a, b) {
+               var protoA = getProto(a);
+               var protoB = getProto(b);
+
+               // Comparing constructors is more strict than using `instanceof`
+               if (a.constructor === b.constructor) {
+                       return true;
+               }
+
+               // Ref #851
+               // If the obj prototype descends from a null constructor, treat it
+               // as a null prototype.
+               if (protoA && protoA.constructor === null) {
+                       protoA = null;
+               }
+               if (protoB && protoB.constructor === null) {
+                       protoB = null;
+               }
+
+               // Allow objects with no prototype to be equivalent to
+               // objects with Object as their constructor.
+               if (protoA === null && protoB === Object.prototype || protoB === null && protoA === Object.prototype) {
+                       return true;
+               }
+
+               return false;
+       }
+
+       function getRegExpFlags(regexp) {
+               return "flags" in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0];
+       }
+
+       function isContainer(val) {
+               return ["object", "array", "map", "set"].indexOf(objectType(val)) !== -1;
+       }
+
+       function breadthFirstCompareChild(a, b) {
+
+               // If a is a container not reference-equal to b, postpone the comparison to the
+               // end of the pairs queue -- unless (a, b) has been seen before, in which case skip
+               // over the pair.
+               if (a === b) {
+                       return true;
+               }
+               if (!isContainer(a)) {
+                       return typeEquiv(a, b);
+               }
+               if (pairs.every(function (pair) {
+                       return pair.a !== a || pair.b !== b;
+               })) {
+
+                       // Not yet started comparing this pair
+                       pairs.push({ a: a, b: b });
+               }
+               return true;
+       }
+
+       var callbacks = {
+               "string": useStrictEquality,
+               "boolean": useStrictEquality,
+               "number": useStrictEquality,
+               "null": useStrictEquality,
+               "undefined": useStrictEquality,
+               "symbol": useStrictEquality,
+               "date": useStrictEquality,
+
+               "nan": function nan() {
+                       return true;
+               },
+
+               "regexp": function regexp(a, b) {
+                       return a.source === b.source &&
+
+                       // Include flags in the comparison
+                       getRegExpFlags(a) === getRegExpFlags(b);
+               },
+
+               // abort (identical references / instance methods were skipped earlier)
+               "function": function _function() {
+                       return false;
+               },
+
+               "array": function array(a, b) {
+                       var i, len;
+
+                       len = a.length;
+                       if (len !== b.length) {
+
+                               // Safe and faster
+                               return false;
+                       }
+
+                       for (i = 0; i < len; i++) {
+
+                               // Compare non-containers; queue non-reference-equal containers
+                               if (!breadthFirstCompareChild(a[i], b[i])) {
+                                       return false;
+                               }
+                       }
+                       return true;
+               },
+
+               // Define sets a and b to be equivalent if for each element aVal in a, there
+               // is some element bVal in b such that aVal and bVal are equivalent. Element
+               // repetitions are not counted, so these are equivalent:
+               // a = new Set( [ {}, [], [] ] );
+               // b = new Set( [ {}, {}, [] ] );
+               "set": function set$$1(a, b) {
+                       var innerEq,
+                           outerEq = true;
+
+                       if (a.size !== b.size) {
+
+                               // This optimization has certain quirks because of the lack of
+                               // repetition counting. For instance, adding the same
+                               // (reference-identical) element to two equivalent sets can
+                               // make them non-equivalent.
+                               return false;
+                       }
+
+                       a.forEach(function (aVal) {
+
+                               // Short-circuit if the result is already known. (Using for...of
+                               // with a break clause would be cleaner here, but it would cause
+                               // a syntax error on older Javascript implementations even if
+                               // Set is unused)
+                               if (!outerEq) {
+                                       return;
+                               }
+
+                               innerEq = false;
+
+                               b.forEach(function (bVal) {
+                                       var parentPairs;
+
+                                       // Likewise, short-circuit if the result is already known
+                                       if (innerEq) {
+                                               return;
+                                       }
+
+                                       // Swap out the global pairs list, as the nested call to
+                                       // innerEquiv will clobber its contents
+                                       parentPairs = pairs;
+                                       if (innerEquiv(bVal, aVal)) {
+                                               innerEq = true;
+                                       }
+
+                                       // Replace the global pairs list
+                                       pairs = parentPairs;
+                               });
+
+                               if (!innerEq) {
+                                       outerEq = false;
+                               }
+                       });
+
+                       return outerEq;
+               },
+
+               // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal)
+               // in a, there is some key-value pair (bKey, bVal) in b such that
+               // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not
+               // counted, so these are equivalent:
+               // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] );
+               // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] );
+               "map": function map(a, b) {
+                       var innerEq,
+                           outerEq = true;
+
+                       if (a.size !== b.size) {
+
+                               // This optimization has certain quirks because of the lack of
+                               // repetition counting. For instance, adding the same
+                               // (reference-identical) key-value pair to two equivalent maps
+                               // can make them non-equivalent.
+                               return false;
+                       }
+
+                       a.forEach(function (aVal, aKey) {
+
+                               // Short-circuit if the result is already known. (Using for...of
+                               // with a break clause would be cleaner here, but it would cause
+                               // a syntax error on older Javascript implementations even if
+                               // Map is unused)
+                               if (!outerEq) {
+                                       return;
+                               }
+
+                               innerEq = false;
+
+                               b.forEach(function (bVal, bKey) {
+                                       var parentPairs;
+
+                                       // Likewise, short-circuit if the result is already known
+                                       if (innerEq) {
+                                               return;
+                                       }
+
+                                       // Swap out the global pairs list, as the nested call to
+                                       // innerEquiv will clobber its contents
+                                       parentPairs = pairs;
+                                       if (innerEquiv([bVal, bKey], [aVal, aKey])) {
+                                               innerEq = true;
+                                       }
+
+                                       // Replace the global pairs list
+                                       pairs = parentPairs;
+                               });
+
+                               if (!innerEq) {
+                                       outerEq = false;
+                               }
+                       });
+
+                       return outerEq;
+               },
+
+               "object": function object(a, b) {
+                       var i,
+                           aProperties = [],
+                           bProperties = [];
+
+                       if (compareConstructors(a, b) === false) {
+                               return false;
+                       }
+
+                       // Be strict: don't ensure hasOwnProperty and go deep
+                       for (i in a) {
+
+                               // Collect a's properties
+                               aProperties.push(i);
+
+                               // Skip OOP methods that look the same
+                               if (a.constructor !== Object && typeof a.constructor !== "undefined" && typeof a[i] === "function" && typeof b[i] === "function" && a[i].toString() === b[i].toString()) {
+                                       continue;
+                               }
+
+                               // Compare non-containers; queue non-reference-equal containers
+                               if (!breadthFirstCompareChild(a[i], b[i])) {
+                                       return false;
+                               }
+                       }
+
+                       for (i in b) {
+
+                               // Collect b's properties
+                               bProperties.push(i);
+                       }
+
+                       // Ensures identical properties name
+                       return typeEquiv(aProperties.sort(), bProperties.sort());
+               }
+       };
+
+       function typeEquiv(a, b) {
+               var type = objectType(a);
+
+               // Callbacks for containers will append to the pairs queue to achieve breadth-first
+               // search order. The pairs queue is also used to avoid reprocessing any pair of
+               // containers that are reference-equal to a previously visited pair (a special case
+               // this being recursion detection).
+               //
+               // Because of this approach, once typeEquiv returns a false value, it should not be
+               // called again without clearing the pair queue else it may wrongly report a visited
+               // pair as being equivalent.
+               return objectType(b) === type && callbacks[type](a, b);
+       }
+
+       function innerEquiv(a, b) {
+               var i, pair;
+
+               // We're done when there's nothing more to compare
+               if (arguments.length < 2) {
+                       return true;
+               }
+
+               // Clear the global pair queue and add the top-level values being compared
+               pairs = [{ a: a, b: b }];
+
+               for (i = 0; i < pairs.length; i++) {
+                       pair = pairs[i];
+
+                       // Perform type-specific comparison on any pairs that are not strictly
+                       // equal. For container types, that comparison will postpone comparison
+                       // of any sub-container pair to the end of the pair queue. This gives
+                       // breadth-first search order. It also avoids the reprocessing of
+                       // reference-equal siblings, cousins etc, which can have a significant speed
+                       // impact when comparing a container of small objects each of which has a
+                       // reference to the same (singleton) large object.
+                       if (pair.a !== pair.b && !typeEquiv(pair.a, pair.b)) {
+                               return false;
+                       }
+               }
+
+               // ...across all consecutive argument pairs
+               return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1));
+       }
+
+       return function () {
+               var result = innerEquiv.apply(undefined, arguments);
+
+               // Release any retained objects
+               pairs.length = 0;
+               return result;
+       };
+  })();
+
+  /**
+   * Config object: Maintain internal state
+   * Later exposed as QUnit.config
+   * `config` initialized at top of scope
+   */
+  var config = {
+
+       // The queue of tests to run
+       queue: [],
+
+       // Block until document ready
+       blocking: true,
+
+       // By default, run previously failed tests first
+       // very useful in combination with "Hide passed tests" checked
+       reorder: true,
+
+       // By default, modify document.title when suite is done
+       altertitle: true,
+
+       // HTML Reporter: collapse every test except the first failing test
+       // If false, all failing tests will be expanded
+       collapse: true,
+
+       // By default, scroll to top of the page when suite is done
+       scrolltop: true,
+
+       // Depth up-to which object will be dumped
+       maxDepth: 5,
+
+       // When enabled, all tests must call expect()
+       requireExpects: false,
+
+       // Placeholder for user-configurable form-exposed URL parameters
+       urlConfig: [],
+
+       // Set of all modules.
+       modules: [],
+
+       // The first unnamed module
+       currentModule: {
+               name: "",
+               tests: [],
+               childModules: [],
+               testsRun: 0,
+               unskippedTestsRun: 0,
+               hooks: {
+                       before: [],
+                       beforeEach: [],
+                       afterEach: [],
+                       after: []
+               }
+       },
+
+       callbacks: {},
+
+       // The storage module to use for reordering tests
+       storage: localSessionStorage
+  };
+
+  // take a predefined QUnit.config and extend the defaults
+  var globalConfig = window && window.QUnit && window.QUnit.config;
+
+  // only extend the global config if there is no QUnit overload
+  if (window && window.QUnit && !window.QUnit.version) {
+       extend(config, globalConfig);
+  }
+
+  // Push a loose unnamed module to the modules collection
+  config.modules.push(config.currentModule);
+
+  // Based on jsDump by Ariel Flesler
+  // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
+  var dump = (function () {
+       function quote(str) {
+               return "\"" + str.toString().replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"";
+       }
+       function literal(o) {
+               return o + "";
+       }
+       function join(pre, arr, post) {
+               var s = dump.separator(),
+                   base = dump.indent(),
+                   inner = dump.indent(1);
+               if (arr.join) {
+                       arr = arr.join("," + s + inner);
+               }
+               if (!arr) {
+                       return pre + post;
+               }
+               return [pre, inner + arr, base + post].join(s);
+       }
+       function array(arr, stack) {
+               var i = arr.length,
+                   ret = new Array(i);
+
+               if (dump.maxDepth && dump.depth > dump.maxDepth) {
+                       return "[object Array]";
+               }
+
+               this.up();
+               while (i--) {
+                       ret[i] = this.parse(arr[i], undefined, stack);
+               }
+               this.down();
+               return join("[", ret, "]");
+       }
+
+       function isArray(obj) {
+               return (
+
+                       //Native Arrays
+                       toString.call(obj) === "[object Array]" ||
+
+                       // NodeList objects
+                       typeof obj.length === "number" && obj.item !== undefined && (obj.length ? obj.item(0) === obj[0] : obj.item(0) === null && obj[0] === undefined)
+               );
+       }
+
+       var reName = /^function (\w+)/,
+           dump = {
+
+               // The objType is used mostly internally, you can fix a (custom) type in advance
+               parse: function parse(obj, objType, stack) {
+                       stack = stack || [];
+                       var res,
+                           parser,
+                           parserType,
+                           objIndex = stack.indexOf(obj);
+
+                       if (objIndex !== -1) {
+                               return "recursion(" + (objIndex - stack.length) + ")";
+                       }
+
+                       objType = objType || this.typeOf(obj);
+                       parser = this.parsers[objType];
+                       parserType = typeof parser === "undefined" ? "undefined" : _typeof(parser);
+
+                       if (parserType === "function") {
+                               stack.push(obj);
+                               res = parser.call(this, obj, stack);
+                               stack.pop();
+                               return res;
+                       }
+                       return parserType === "string" ? parser : this.parsers.error;
+               },
+               typeOf: function typeOf(obj) {
+                       var type;
+
+                       if (obj === null) {
+                               type = "null";
+                       } else if (typeof obj === "undefined") {
+                               type = "undefined";
+                       } else if (is("regexp", obj)) {
+                               type = "regexp";
+                       } else if (is("date", obj)) {
+                               type = "date";
+                       } else if (is("function", obj)) {
+                               type = "function";
+                       } else if (obj.setInterval !== undefined && obj.document !== undefined && obj.nodeType === undefined) {
+                               type = "window";
+                       } else if (obj.nodeType === 9) {
+                               type = "document";
+                       } else if (obj.nodeType) {
+                               type = "node";
+                       } else if (isArray(obj)) {
+                               type = "array";
+                       } else if (obj.constructor === Error.prototype.constructor) {
+                               type = "error";
+                       } else {
+                               type = typeof obj === "undefined" ? "undefined" : _typeof(obj);
+                       }
+                       return type;
+               },
+
+               separator: function separator() {
+                       if (this.multiline) {
+                               return this.HTML ? "<br />" : "\n";
+                       } else {
+                               return this.HTML ? "&#160;" : " ";
+                       }
+               },
+
+               // Extra can be a number, shortcut for increasing-calling-decreasing
+               indent: function indent(extra) {
+                       if (!this.multiline) {
+                               return "";
+                       }
+                       var chr = this.indentChar;
+                       if (this.HTML) {
+                               chr = chr.replace(/\t/g, "   ").replace(/ /g, "&#160;");
+                       }
+                       return new Array(this.depth + (extra || 0)).join(chr);
+               },
+               up: function up(a) {
+                       this.depth += a || 1;
+               },
+               down: function down(a) {
+                       this.depth -= a || 1;
+               },
+               setParser: function setParser(name, parser) {
+                       this.parsers[name] = parser;
+               },
+
+               // The next 3 are exposed so you can use them
+               quote: quote,
+               literal: literal,
+               join: join,
+               depth: 1,
+               maxDepth: config.maxDepth,
+
+               // This is the list of parsers, to modify them, use dump.setParser
+               parsers: {
+                       window: "[Window]",
+                       document: "[Document]",
+                       error: function error(_error) {
+                               return "Error(\"" + _error.message + "\")";
+                       },
+                       unknown: "[Unknown]",
+                       "null": "null",
+                       "undefined": "undefined",
+                       "function": function _function(fn) {
+                               var ret = "function",
+
+
+                               // Functions never have name in IE
+                               name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
+
+                               if (name) {
+                                       ret += " " + name;
+                               }
+                               ret += "(";
+
+                               ret = [ret, dump.parse(fn, "functionArgs"), "){"].join("");
+                               return join(ret, dump.parse(fn, "functionCode"), "}");
+                       },
+                       array: array,
+                       nodelist: array,
+                       "arguments": array,
+                       object: function object(map, stack) {
+                               var keys,
+                                   key,
+                                   val,
+                                   i,
+                                   nonEnumerableProperties,
+                                   ret = [];
+
+                               if (dump.maxDepth && dump.depth > dump.maxDepth) {
+                                       return "[object Object]";
+                               }
+
+                               dump.up();
+                               keys = [];
+                               for (key in map) {
+                                       keys.push(key);
+                               }
+
+                               // Some properties are not always enumerable on Error objects.
+                               nonEnumerableProperties = ["message", "name"];
+                               for (i in nonEnumerableProperties) {
+                                       key = nonEnumerableProperties[i];
+                                       if (key in map && !inArray(key, keys)) {
+                                               keys.push(key);
+                                       }
+                               }
+                               keys.sort();
+                               for (i = 0; i < keys.length; i++) {
+                                       key = keys[i];
+                                       val = map[key];
+                                       ret.push(dump.parse(key, "key") + ": " + dump.parse(val, undefined, stack));
+                               }
+                               dump.down();
+                               return join("{", ret, "}");
+                       },
+                       node: function node(_node) {
+                               var len,
+                                   i,
+                                   val,
+                                   open = dump.HTML ? "&lt;" : "<",
+                                   close = dump.HTML ? "&gt;" : ">",
+                                   tag = _node.nodeName.toLowerCase(),
+                                   ret = open + tag,
+                                   attrs = _node.attributes;
+
+                               if (attrs) {
+                                       for (i = 0, len = attrs.length; i < len; i++) {
+                                               val = attrs[i].nodeValue;
+
+                                               // IE6 includes all attributes in .attributes, even ones not explicitly
+                                               // set. Those have values like undefined, null, 0, false, "" or
+                                               // "inherit".
+                                               if (val && val !== "inherit") {
+                                                       ret += " " + attrs[i].nodeName + "=" + dump.parse(val, "attribute");
+                                               }
+                                       }
+                               }
+                               ret += close;
+
+                               // Show content of TextNode or CDATASection
+                               if (_node.nodeType === 3 || _node.nodeType === 4) {
+                                       ret += _node.nodeValue;
+                               }
+
+                               return ret + open + "/" + tag + close;
+                       },
+
+                       // Function calls it internally, it's the arguments part of the function
+                       functionArgs: function functionArgs(fn) {
+                               var args,
+                                   l = fn.length;
+
+                               if (!l) {
+                                       return "";
+                               }
+
+                               args = new Array(l);
+                               while (l--) {
+
+                                       // 97 is 'a'
+                                       args[l] = String.fromCharCode(97 + l);
+                               }
+                               return " " + args.join(", ") + " ";
+                       },
+
+                       // Object calls it internally, the key part of an item in a map
+                       key: quote,
+
+                       // Function calls it internally, it's the content of the function
+                       functionCode: "[code]",
+
+                       // Node calls it internally, it's a html attribute value
+                       attribute: quote,
+                       string: quote,
+                       date: quote,
+                       regexp: literal,
+                       number: literal,
+                       "boolean": literal,
+                       symbol: function symbol(sym) {
+                               return sym.toString();
+                       }
+               },
+
+               // If true, entities are escaped ( <, >, \t, space and \n )
+               HTML: false,
+
+               // Indentation unit
+               indentChar: "  ",
+
+               // If true, items in a collection, are separated by a \n, else just a space.
+               multiline: true
+       };
+
+       return dump;
+  })();
+
+  var LISTENERS = Object.create(null);
+  var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"];
+
+  /**
+   * Emits an event with the specified data to all currently registered listeners.
+   * Callbacks will fire in the order in which they are registered (FIFO). This
+   * function is not exposed publicly; it is used by QUnit internals to emit
+   * logging events.
+   *
+   * @private
+   * @method emit
+   * @param {String} eventName
+   * @param {Object} data
+   * @return {Void}
+   */
+  function emit(eventName, data) {
+       if (objectType(eventName) !== "string") {
+               throw new TypeError("eventName must be a string when emitting an event");
+       }
+
+       // Clone the callbacks in case one of them registers a new callback
+       var originalCallbacks = LISTENERS[eventName];
+       var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : [];
+
+       for (var i = 0; i < callbacks.length; i++) {
+               callbacks[i](data);
+       }
+  }
+
+  /**
+   * Registers a callback as a listener to the specified event.
+   *
+   * @public
+   * @method on
+   * @param {String} eventName
+   * @param {Function} callback
+   * @return {Void}
+   */
+  function on(eventName, callback) {
+       if (objectType(eventName) !== "string") {
+               throw new TypeError("eventName must be a string when registering a listener");
+       } else if (!inArray(eventName, SUPPORTED_EVENTS)) {
+               var events = SUPPORTED_EVENTS.join(", ");
+               throw new Error("\"" + eventName + "\" is not a valid event; must be one of: " + events + ".");
+       } else if (objectType(callback) !== "function") {
+               throw new TypeError("callback must be a function when registering a listener");
+       }
+
+       if (!LISTENERS[eventName]) {
+               LISTENERS[eventName] = [];
+       }
+
+       // Don't register the same callback more than once
+       if (!inArray(callback, LISTENERS[eventName])) {
+               LISTENERS[eventName].push(callback);
+       }
+  }
+
+  // Register logging callbacks
+  function registerLoggingCallbacks(obj) {
+       var i,
+           l,
+           key,
+           callbackNames = ["begin", "done", "log", "testStart", "testDone", "moduleStart", "moduleDone"];
+
+       function registerLoggingCallback(key) {
+               var loggingCallback = function loggingCallback(callback) {
+                       if (objectType(callback) !== "function") {
+                               throw new Error("QUnit logging methods require a callback function as their first parameters.");
+                       }
+
+                       config.callbacks[key].push(callback);
+               };
+
+               return loggingCallback;
+       }
+
+       for (i = 0, l = callbackNames.length; i < l; i++) {
+               key = callbackNames[i];
+
+               // Initialize key collection of logging callback
+               if (objectType(config.callbacks[key]) === "undefined") {
+                       config.callbacks[key] = [];
+               }
+
+               obj[key] = registerLoggingCallback(key);
+       }
+  }
+
+  function runLoggingCallbacks(key, args) {
+       var i, l, callbacks;
+
+       callbacks = config.callbacks[key];
+       for (i = 0, l = callbacks.length; i < l; i++) {
+               callbacks[i](args);
+       }
+  }
+
+  // Doesn't support IE9, it will return undefined on these browsers
+  // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+  var fileName = (sourceFromStacktrace(0) || "").replace(/(:\d+)+\)?/, "").replace(/.+\//, "");
+
+  function extractStacktrace(e, offset) {
+       offset = offset === undefined ? 4 : offset;
+
+       var stack, include, i;
+
+       if (e && e.stack) {
+               stack = e.stack.split("\n");
+               if (/^error$/i.test(stack[0])) {
+                       stack.shift();
+               }
+               if (fileName) {
+                       include = [];
+                       for (i = offset; i < stack.length; i++) {
+                               if (stack[i].indexOf(fileName) !== -1) {
+                                       break;
+                               }
+                               include.push(stack[i]);
+                       }
+                       if (include.length) {
+                               return include.join("\n");
+                       }
+               }
+               return stack[offset];
+       }
+  }
+
+  function sourceFromStacktrace(offset) {
+       var error = new Error();
+
+       // Support: Safari <=7 only, IE <=10 - 11 only
+       // Not all browsers generate the `stack` property for `new Error()`, see also #636
+       if (!error.stack) {
+               try {
+                       throw error;
+               } catch (err) {
+                       error = err;
+               }
+       }
+
+       return extractStacktrace(error, offset);
+  }
+
+  var priorityCount = 0;
+  var unitSampler = void 0;
+
+  /**
+   * Advances the ProcessingQueue to the next item if it is ready.
+   * @param {Boolean} last
+   */
+  function advance() {
+       var start = now();
+       config.depth = (config.depth || 0) + 1;
+
+       while (config.queue.length && !config.blocking) {
+               var elapsedTime = now() - start;
+
+               if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
+                       if (priorityCount > 0) {
+                               priorityCount--;
+                       }
+
+                       config.queue.shift()();
+               } else {
+                       setTimeout(advance, 13);
+                       break;
+               }
+       }
+
+       config.depth--;
+
+       if (!config.blocking && !config.queue.length && config.depth === 0) {
+               done();
+       }
+  }
+
+  function addToQueueImmediate(callback) {
+       if (objectType(callback) === "array") {
+               while (callback.length) {
+                       addToQueueImmediate(callback.pop());
+               }
+
+               return;
+       }
+
+       config.queue.unshift(callback);
+       priorityCount++;
+  }
+
+  /**
+   * Adds a function to the ProcessingQueue for execution.
+   * @param {Function|Array} callback
+   * @param {Boolean} priority
+   * @param {String} seed
+   */
+  function addToQueue(callback, prioritize, seed) {
+       if (prioritize) {
+               config.queue.splice(priorityCount++, 0, callback);
+       } else if (seed) {
+               if (!unitSampler) {
+                       unitSampler = unitSamplerGenerator(seed);
+               }
+
+               // Insert into a random position after all prioritized items
+               var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
+               config.queue.splice(priorityCount + index, 0, callback);
+       } else {
+               config.queue.push(callback);
+       }
+  }
+
+  /**
+   * Creates a seeded "sample" generator which is used for randomizing tests.
+   */
+  function unitSamplerGenerator(seed) {
+
+       // 32-bit xorshift, requires only a nonzero seed
+       // http://excamera.com/sphinx/article-xorshift.html
+       var sample = parseInt(generateHash(seed), 16) || -1;
+       return function () {
+               sample ^= sample << 13;
+               sample ^= sample >>> 17;
+               sample ^= sample << 5;
+
+               // ECMAScript has no unsigned number type
+               if (sample < 0) {
+                       sample += 0x100000000;
+               }
+
+               return sample / 0x100000000;
+       };
+  }
+
+  /**
+   * This function is called when the ProcessingQueue is done processing all
+   * items. It handles emitting the final run events.
+   */
+  function done() {
+       var storage = config.storage;
+
+       ProcessingQueue.finished = true;
+
+       var runtime = now() - config.started;
+       var passed = config.stats.all - config.stats.bad;
+
+       emit("runEnd", globalSuite.end(true));
+       runLoggingCallbacks("done", {
+               passed: passed,
+               failed: config.stats.bad,
+               total: config.stats.all,
+               runtime: runtime
+       });
+
+       // Clear own storage items if all tests passed
+       if (storage && config.stats.bad === 0) {
+               for (var i = storage.length - 1; i >= 0; i--) {
+                       var key = storage.key(i);
+
+                       if (key.indexOf("qunit-test-") === 0) {
+                               storage.removeItem(key);
+                       }
+               }
+       }
+  }
+
+  var ProcessingQueue = {
+       finished: false,
+       add: addToQueue,
+       addImmediate: addToQueueImmediate,
+       advance: advance
+  };
+
+  var TestReport = function () {
+       function TestReport(name, suite, options) {
+               classCallCheck(this, TestReport);
+
+               this.name = name;
+               this.suiteName = suite.name;
+               this.fullName = suite.fullName.concat(name);
+               this.runtime = 0;
+               this.assertions = [];
+
+               this.skipped = !!options.skip;
+               this.todo = !!options.todo;
+
+               this.valid = options.valid;
+
+               this._startTime = 0;
+               this._endTime = 0;
+
+               suite.pushTest(this);
+       }
+
+       createClass(TestReport, [{
+               key: "start",
+               value: function start(recordTime) {
+                       if (recordTime) {
+                               this._startTime = Date.now();
+                       }
+
+                       return {
+                               name: this.name,
+                               suiteName: this.suiteName,
+                               fullName: this.fullName.slice()
+                       };
+               }
+       }, {
+               key: "end",
+               value: function end(recordTime) {
+                       if (recordTime) {
+                               this._endTime = Date.now();
+                       }
+
+                       return extend(this.start(), {
+                               runtime: this.getRuntime(),
+                               status: this.getStatus(),
+                               errors: this.getFailedAssertions(),
+                               assertions: this.getAssertions()
+                       });
+               }
+       }, {
+               key: "pushAssertion",
+               value: function pushAssertion(assertion) {
+                       this.assertions.push(assertion);
+               }
+       }, {
+               key: "getRuntime",
+               value: function getRuntime() {
+                       return this._endTime - this._startTime;
+               }
+       }, {
+               key: "getStatus",
+               value: function getStatus() {
+                       if (this.skipped) {
+                               return "skipped";
+                       }
+
+                       var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
+
+                       if (!testPassed) {
+                               return "failed";
+                       } else if (this.todo) {
+                               return "todo";
+                       } else {
+                               return "passed";
+                       }
+               }
+       }, {
+               key: "getFailedAssertions",
+               value: function getFailedAssertions() {
+                       return this.assertions.filter(function (assertion) {
+                               return !assertion.passed;
+                       });
+               }
+       }, {
+               key: "getAssertions",
+               value: function getAssertions() {
+                       return this.assertions.slice();
+               }
+
+               // Remove actual and expected values from assertions. This is to prevent
+               // leaking memory throughout a test suite.
+
+       }, {
+               key: "slimAssertions",
+               value: function slimAssertions() {
+                       this.assertions = this.assertions.map(function (assertion) {
+                               delete assertion.actual;
+                               delete assertion.expected;
+                               return assertion;
+                       });
+               }
+       }]);
+       return TestReport;
+  }();
+
+  var focused$1 = false;
+
+  function Test(settings) {
+       var i, l;
+
+       ++Test.count;
+
+       this.expected = null;
+       this.assertions = [];
+       this.semaphore = 0;
+       this.module = config.currentModule;
+       this.stack = sourceFromStacktrace(3);
+       this.steps = [];
+       this.timeout = undefined;
+
+       // If a module is skipped, all its tests and the tests of the child suites
+       // should be treated as skipped even if they are defined as `only` or `todo`.
+       // As for `todo` module, all its tests will be treated as `todo` except for
+       // tests defined as `skip` which will be left intact.
+       //
+       // So, if a test is defined as `todo` and is inside a skipped module, we should
+       // then treat that test as if was defined as `skip`.
+       if (this.module.skip) {
+               settings.skip = true;
+               settings.todo = false;
+
+               // Skipped tests should be left intact
+       } else if (this.module.todo && !settings.skip) {
+               settings.todo = true;
+       }
+
+       extend(this, settings);
+
+       this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
+               todo: settings.todo,
+               skip: settings.skip,
+               valid: this.valid()
+       });
+
+       // Register unique strings
+       for (i = 0, l = this.module.tests; i < l.length; i++) {
+               if (this.module.tests[i].name === this.testName) {
+                       this.testName += " ";
+               }
+       }
+
+       this.testId = generateHash(this.module.name, this.testName);
+
+       this.module.tests.push({
+               name: this.testName,
+               testId: this.testId,
+               skip: !!settings.skip
+       });
+
+       if (settings.skip) {
+
+               // Skipped tests will fully ignore any sent callback
+               this.callback = function () {};
+               this.async = false;
+               this.expected = 0;
+       } else {
+               this.assert = new Assert(this);
+       }
+  }
+
+  Test.count = 0;
+
+  function getNotStartedModules(startModule) {
+       var module = startModule,
+           modules = [];
+
+       while (module && module.testsRun === 0) {
+               modules.push(module);
+               module = module.parentModule;
+       }
+
+       return modules;
+  }
+
+  Test.prototype = {
+       before: function before() {
+               var i,
+                   startModule,
+                   module = this.module,
+                   notStartedModules = getNotStartedModules(module);
+
+               for (i = notStartedModules.length - 1; i >= 0; i--) {
+                       startModule = notStartedModules[i];
+                       startModule.stats = { all: 0, bad: 0, started: now() };
+                       emit("suiteStart", startModule.suiteReport.start(true));
+                       runLoggingCallbacks("moduleStart", {
+                               name: startModule.name,
+                               tests: startModule.tests
+                       });
+               }
+
+               config.current = this;
+
+               this.testEnvironment = extend({}, module.testEnvironment);
+
+               this.started = now();
+               emit("testStart", this.testReport.start(true));
+               runLoggingCallbacks("testStart", {
+                       name: this.testName,
+                       module: module.name,
+                       testId: this.testId,
+                       previousFailure: this.previousFailure
+               });
+
+               if (!config.pollution) {
+                       saveGlobal();
+               }
+       },
+
+       run: function run() {
+               var promise;
+
+               config.current = this;
+
+               this.callbackStarted = now();
+
+               if (config.notrycatch) {
+                       runTest(this);
+                       return;
+               }
+
+               try {
+                       runTest(this);
+               } catch (e) {
+                       this.pushFailure("Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + (e.message || e), extractStacktrace(e, 0));
+
+                       // Else next test will carry the responsibility
+                       saveGlobal();
+
+                       // Restart the tests if they're blocking
+                       if (config.blocking) {
+                               internalRecover(this);
+                       }
+               }
+
+               function runTest(test) {
+                       promise = test.callback.call(test.testEnvironment, test.assert);
+                       test.resolvePromise(promise);
+
+                       // If the test has a "lock" on it, but the timeout is 0, then we push a
+                       // failure as the test should be synchronous.
+                       if (test.timeout === 0 && test.semaphore !== 0) {
+                               pushFailure("Test did not finish synchronously even though assert.timeout( 0 ) was used.", sourceFromStacktrace(2));
+                       }
+               }
+       },
+
+       after: function after() {
+               checkPollution();
+       },
+
+       queueHook: function queueHook(hook, hookName, hookOwner) {
+               var _this = this;
+
+               var callHook = function callHook() {
+                       var promise = hook.call(_this.testEnvironment, _this.assert);
+                       _this.resolvePromise(promise, hookName);
+               };
+
+               var runHook = function runHook() {
+                       if (hookName === "before") {
+                               if (hookOwner.unskippedTestsRun !== 0) {
+                                       return;
+                               }
+
+                               _this.preserveEnvironment = true;
+                       }
+
+                       if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && config.queue.length > 2) {
+                               return;
+                       }
+
+                       config.current = _this;
+                       if (config.notrycatch) {
+                               callHook();
+                               return;
+                       }
+                       try {
+                               callHook();
+                       } catch (error) {
+                               _this.pushFailure(hookName + " failed on " + _this.testName + ": " + (error.message || error), extractStacktrace(error, 0));
+                       }
+               };
+
+               return runHook;
+       },
+
+
+       // Currently only used for module level hooks, can be used to add global level ones
+       hooks: function hooks(handler) {
+               var hooks = [];
+
+               function processHooks(test, module) {
+                       if (module.parentModule) {
+                               processHooks(test, module.parentModule);
+                       }
+
+                       if (module.hooks[handler].length) {
+                               for (var i = 0; i < module.hooks[handler].length; i++) {
+                                       hooks.push(test.queueHook(module.hooks[handler][i], handler, module));
+                               }
+                       }
+               }
+
+               // Hooks are ignored on skipped tests
+               if (!this.skip) {
+                       processHooks(this, this.module);
+               }
+
+               return hooks;
+       },
+
+
+       finish: function finish() {
+               config.current = this;
+               if (config.requireExpects && this.expected === null) {
+                       this.pushFailure("Expected number of assertions to be defined, but expect() was " + "not called.", this.stack);
+               } else if (this.expected !== null && this.expected !== this.assertions.length) {
+                       this.pushFailure("Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack);
+               } else if (this.expected === null && !this.assertions.length) {
+                       this.pushFailure("Expected at least one assertion, but none were run - call " + "expect(0) to accept zero assertions.", this.stack);
+               }
+
+               var i,
+                   module = this.module,
+                   moduleName = module.name,
+                   testName = this.testName,
+                   skipped = !!this.skip,
+                   todo = !!this.todo,
+                   bad = 0,
+                   storage = config.storage;
+
+               this.runtime = now() - this.started;
+
+               config.stats.all += this.assertions.length;
+               module.stats.all += this.assertions.length;
+
+               for (i = 0; i < this.assertions.length; i++) {
+                       if (!this.assertions[i].result) {
+                               bad++;
+                               config.stats.bad++;
+                               module.stats.bad++;
+                       }
+               }
+
+               notifyTestsRan(module, skipped);
+
+               // Store result when possible
+               if (storage) {
+                       if (bad) {
+                               storage.setItem("qunit-test-" + moduleName + "-" + testName, bad);
+                       } else {
+                               storage.removeItem("qunit-test-" + moduleName + "-" + testName);
+                       }
+               }
+
+               // After emitting the js-reporters event we cleanup the assertion data to
+               // avoid leaking it. It is not used by the legacy testDone callbacks.
+               emit("testEnd", this.testReport.end(true));
+               this.testReport.slimAssertions();
+
+               runLoggingCallbacks("testDone", {
+                       name: testName,
+                       module: moduleName,
+                       skipped: skipped,
+                       todo: todo,
+                       failed: bad,
+                       passed: this.assertions.length - bad,
+                       total: this.assertions.length,
+                       runtime: skipped ? 0 : this.runtime,
+
+                       // HTML Reporter use
+                       assertions: this.assertions,
+                       testId: this.testId,
+
+                       // Source of Test
+                       source: this.stack
+               });
+
+               if (module.testsRun === numberOfTests(module)) {
+                       logSuiteEnd(module);
+
+                       // Check if the parent modules, iteratively, are done. If that the case,
+                       // we emit the `suiteEnd` event and trigger `moduleDone` callback.
+                       var parent = module.parentModule;
+                       while (parent && parent.testsRun === numberOfTests(parent)) {
+                               logSuiteEnd(parent);
+                               parent = parent.parentModule;
+                       }
+               }
+
+               config.current = undefined;
+
+               function logSuiteEnd(module) {
+                       emit("suiteEnd", module.suiteReport.end(true));
+                       runLoggingCallbacks("moduleDone", {
+                               name: module.name,
+                               tests: module.tests,
+                               failed: module.stats.bad,
+                               passed: module.stats.all - module.stats.bad,
+                               total: module.stats.all,
+                               runtime: now() - module.stats.started
+                       });
+               }
+       },
+
+       preserveTestEnvironment: function preserveTestEnvironment() {
+               if (this.preserveEnvironment) {
+                       this.module.testEnvironment = this.testEnvironment;
+                       this.testEnvironment = extend({}, this.module.testEnvironment);
+               }
+       },
+
+       queue: function queue() {
+               var test = this;
+
+               if (!this.valid()) {
+                       return;
+               }
+
+               function runTest() {
+
+                       // Each of these can by async
+                       ProcessingQueue.addImmediate([function () {
+                               test.before();
+                       }, test.hooks("before"), function () {
+                               test.preserveTestEnvironment();
+                       }, test.hooks("beforeEach"), function () {
+                               test.run();
+                       }, test.hooks("afterEach").reverse(), test.hooks("after").reverse(), function () {
+                               test.after();
+                       }, function () {
+                               test.finish();
+                       }]);
+               }
+
+               var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName);
+
+               // Prioritize previously failed tests, detected from storage
+               var prioritize = config.reorder && !!previousFailCount;
+
+               this.previousFailure = !!previousFailCount;
+
+               ProcessingQueue.add(runTest, prioritize, config.seed);
+
+               // If the queue has already finished, we manually process the new test
+               if (ProcessingQueue.finished) {
+                       ProcessingQueue.advance();
+               }
+       },
+
+
+       pushResult: function pushResult(resultInfo) {
+               if (this !== config.current) {
+                       throw new Error("Assertion occured after test had finished.");
+               }
+
+               // Destructure of resultInfo = { result, actual, expected, message, negative }
+               var source,
+                   details = {
+                       module: this.module.name,
+                       name: this.testName,
+                       result: resultInfo.result,
+                       message: resultInfo.message,
+                       actual: resultInfo.actual,
+                       expected: resultInfo.expected,
+                       testId: this.testId,
+                       negative: resultInfo.negative || false,
+                       runtime: now() - this.started,
+                       todo: !!this.todo
+               };
+
+               if (!resultInfo.result) {
+                       source = resultInfo.source || sourceFromStacktrace();
+
+                       if (source) {
+                               details.source = source;
+                       }
+               }
+
+               this.logAssertion(details);
+
+               this.assertions.push({
+                       result: !!resultInfo.result,
+                       message: resultInfo.message
+               });
+       },
+
+       pushFailure: function pushFailure(message, source, actual) {
+               if (!(this instanceof Test)) {
+                       throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2));
+               }
+
+               this.pushResult({
+                       result: false,
+                       message: message || "error",
+                       actual: actual || null,
+                       expected: null,
+                       source: source
+               });
+       },
+
+       /**
+    * Log assertion details using both the old QUnit.log interface and
+    * QUnit.on( "assertion" ) interface.
+    *
+    * @private
+    */
+       logAssertion: function logAssertion(details) {
+               runLoggingCallbacks("log", details);
+
+               var assertion = {
+                       passed: details.result,
+                       actual: details.actual,
+                       expected: details.expected,
+                       message: details.message,
+                       stack: details.source,
+                       todo: details.todo
+               };
+               this.testReport.pushAssertion(assertion);
+               emit("assertion", assertion);
+       },
+
+
+       resolvePromise: function resolvePromise(promise, phase) {
+               var then,
+                   resume,
+                   message,
+                   test = this;
+               if (promise != null) {
+                       then = promise.then;
+                       if (objectType(then) === "function") {
+                               resume = internalStop(test);
+                               then.call(promise, function () {
+                                       resume();
+                               }, function (error) {
+                                       message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error);
+                                       test.pushFailure(message, extractStacktrace(error, 0));
+
+                                       // Else next test will carry the responsibility
+                                       saveGlobal();
+
+                                       // Unblock
+                                       resume();
+                               });
+                       }
+               }
+       },
+
+       valid: function valid() {
+               var filter = config.filter,
+                   regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter),
+                   module = config.module && config.module.toLowerCase(),
+                   fullName = this.module.name + ": " + this.testName;
+
+               function moduleChainNameMatch(testModule) {
+                       var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
+                       if (testModuleName === module) {
+                               return true;
+                       } else if (testModule.parentModule) {
+                               return moduleChainNameMatch(testModule.parentModule);
+                       } else {
+                               return false;
+                       }
+               }
+
+               function moduleChainIdMatch(testModule) {
+                       return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
+               }
+
+               // Internally-generated tests are always valid
+               if (this.callback && this.callback.validTest) {
+                       return true;
+               }
+
+               if (config.moduleId && config.moduleId.length > 0 && !moduleChainIdMatch(this.module)) {
+
+                       return false;
+               }
+
+               if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) {
+
+                       return false;
+               }
+
+               if (module && !moduleChainNameMatch(this.module)) {
+                       return false;
+               }
+
+               if (!filter) {
+                       return true;
+               }
+
+               return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName);
+       },
+
+       regexFilter: function regexFilter(exclude, pattern, flags, fullName) {
+               var regex = new RegExp(pattern, flags);
+               var match = regex.test(fullName);
+
+               return match !== exclude;
+       },
+
+       stringFilter: function stringFilter(filter, fullName) {
+               filter = filter.toLowerCase();
+               fullName = fullName.toLowerCase();
+
+               var include = filter.charAt(0) !== "!";
+               if (!include) {
+                       filter = filter.slice(1);
+               }
+
+               // If the filter matches, we need to honour include
+               if (fullName.indexOf(filter) !== -1) {
+                       return include;
+               }
+
+               // Otherwise, do the opposite
+               return !include;
+       }
+  };
+
+  function pushFailure() {
+       if (!config.current) {
+               throw new Error("pushFailure() assertion outside test context, in " + sourceFromStacktrace(2));
+       }
+
+       // Gets current test obj
+       var currentTest = config.current;
+
+       return currentTest.pushFailure.apply(currentTest, arguments);
+  }
+
+  function saveGlobal() {
+       config.pollution = [];
+
+       if (config.noglobals) {
+               for (var key in global$1) {
+                       if (hasOwn.call(global$1, key)) {
+
+                               // In Opera sometimes DOM element ids show up here, ignore them
+                               if (/^qunit-test-output/.test(key)) {
+                                       continue;
+                               }
+                               config.pollution.push(key);
+                       }
+               }
+       }
+  }
+
+  function checkPollution() {
+       var newGlobals,
+           deletedGlobals,
+           old = config.pollution;
+
+       saveGlobal();
+
+       newGlobals = diff(config.pollution, old);
+       if (newGlobals.length > 0) {
+               pushFailure("Introduced global variable(s): " + newGlobals.join(", "));
+       }
+
+       deletedGlobals = diff(old, config.pollution);
+       if (deletedGlobals.length > 0) {
+               pushFailure("Deleted global variable(s): " + deletedGlobals.join(", "));
+       }
+  }
+
+  // Will be exposed as QUnit.test
+  function test(testName, callback) {
+       if (focused$1) {
+               return;
+       }
+
+       var newTest = new Test({
+               testName: testName,
+               callback: callback
+       });
+
+       newTest.queue();
+  }
+
+  function todo(testName, callback) {
+       if (focused$1) {
+               return;
+       }
+
+       var newTest = new Test({
+               testName: testName,
+               callback: callback,
+               todo: true
+       });
+
+       newTest.queue();
+  }
+
+  // Will be exposed as QUnit.skip
+  function skip(testName) {
+       if (focused$1) {
+               return;
+       }
+
+       var test = new Test({
+               testName: testName,
+               skip: true
+       });
+
+       test.queue();
+  }
+
+  // Will be exposed as QUnit.only
+  function only(testName, callback) {
+       if (focused$1) {
+               return;
+       }
+
+       config.queue.length = 0;
+       focused$1 = true;
+
+       var newTest = new Test({
+               testName: testName,
+               callback: callback
+       });
+
+       newTest.queue();
+  }
+
+  // Put a hold on processing and return a function that will release it.
+  function internalStop(test) {
+       test.semaphore += 1;
+       config.blocking = true;
+
+       // Set a recovery timeout, if so configured.
+       if (defined.setTimeout) {
+               var timeoutDuration = void 0;
+
+               if (typeof test.timeout === "number") {
+                       timeoutDuration = test.timeout;
+               } else if (typeof config.testTimeout === "number") {
+                       timeoutDuration = config.testTimeout;
+               }
+
+               if (typeof timeoutDuration === "number" && timeoutDuration > 0) {
+                       clearTimeout(config.timeout);
+                       config.timeout = setTimeout(function () {
+                               pushFailure("Test took longer than " + timeoutDuration + "ms; test timed out.", sourceFromStacktrace(2));
+                               internalRecover(test);
+                       }, timeoutDuration);
+               }
+       }
+
+       var released = false;
+       return function resume() {
+               if (released) {
+                       return;
+               }
+
+               released = true;
+               test.semaphore -= 1;
+               internalStart(test);
+       };
+  }
+
+  // Forcefully release all processing holds.
+  function internalRecover(test) {
+       test.semaphore = 0;
+       internalStart(test);
+  }
+
+  // Release a processing hold, scheduling a resumption attempt if no holds remain.
+  function internalStart(test) {
+
+       // If semaphore is non-numeric, throw error
+       if (isNaN(test.semaphore)) {
+               test.semaphore = 0;
+
+               pushFailure("Invalid value on test.semaphore", sourceFromStacktrace(2));
+               return;
+       }
+
+       // Don't start until equal number of stop-calls
+       if (test.semaphore > 0) {
+               return;
+       }
+
+       // Throw an Error if start is called more often than stop
+       if (test.semaphore < 0) {
+               test.semaphore = 0;
+
+               pushFailure("Tried to restart test while already started (test's semaphore was 0 already)", sourceFromStacktrace(2));
+               return;
+       }
+
+       // Add a slight delay to allow more assertions etc.
+       if (defined.setTimeout) {
+               if (config.timeout) {
+                       clearTimeout(config.timeout);
+               }
+               config.timeout = setTimeout(function () {
+                       if (test.semaphore > 0) {
+                               return;
+                       }
+
+                       if (config.timeout) {
+                               clearTimeout(config.timeout);
+                       }
+
+                       begin();
+               }, 13);
+       } else {
+               begin();
+       }
+  }
+
+  function collectTests(module) {
+       var tests = [].concat(module.tests);
+       var modules = [].concat(toConsumableArray(module.childModules));
+
+       // Do a breadth-first traversal of the child modules
+       while (modules.length) {
+               var nextModule = modules.shift();
+               tests.push.apply(tests, nextModule.tests);
+               modules.push.apply(modules, toConsumableArray(nextModule.childModules));
+       }
+
+       return tests;
+  }
+
+  function numberOfTests(module) {
+       return collectTests(module).length;
+  }
+
+  function numberOfUnskippedTests(module) {
+       return collectTests(module).filter(function (test) {
+               return !test.skip;
+       }).length;
+  }
+
+  function notifyTestsRan(module, skipped) {
+       module.testsRun++;
+       if (!skipped) {
+               module.unskippedTestsRun++;
+       }
+       while (module = module.parentModule) {
+               module.testsRun++;
+               if (!skipped) {
+                       module.unskippedTestsRun++;
+               }
+       }
+  }
+
+  /**
+   * Returns a function that proxies to the given method name on the globals
+   * console object. The proxy will also detect if the console doesn't exist and
+   * will appropriately no-op. This allows support for IE9, which doesn't have a
+   * console if the developer tools are not open.
+   */
+  function consoleProxy(method) {
+       return function () {
+               if (console) {
+                       console[method].apply(console, arguments);
+               }
+       };
+  }
+
+  var Logger = {
+       warn: consoleProxy("warn")
+  };
+
+  var Assert = function () {
+       function Assert(testContext) {
+               classCallCheck(this, Assert);
+
+               this.test = testContext;
+       }
+
+       // Assert helpers
+
+       createClass(Assert, [{
+               key: "timeout",
+               value: function timeout(duration) {
+                       if (typeof duration !== "number") {
+                               throw new Error("You must pass a number as the duration to assert.timeout");
+                       }
+
+                       this.test.timeout = duration;
+               }
+
+               // Documents a "step", which is a string value, in a test as a passing assertion
+
+       }, {
+               key: "step",
+               value: function step(message) {
+                       var result = !!message;
+
+                       this.test.steps.push(message);
+
+                       return this.pushResult({
+                               result: result,
+                               message: message || "You must provide a message to assert.step"
+                       });
+               }
+
+               // Verifies the steps in a test match a given array of string values
+
+       }, {
+               key: "verifySteps",
+               value: function verifySteps(steps, message) {
+                       this.deepEqual(this.test.steps, steps, message);
+               }
+
+               // Specify the number of expected assertions to guarantee that failed test
+               // (no assertions are run at all) don't slip through.
+
+       }, {
+               key: "expect",
+               value: function expect(asserts) {
+                       if (arguments.length === 1) {
+                               this.test.expected = asserts;
+                       } else {
+                               return this.test.expected;
+                       }
+               }
+
+               // Put a hold on processing and return a function that will release it a maximum of once.
+
+       }, {
+               key: "async",
+               value: function async(count) {
+                       var test$$1 = this.test;
+
+                       var popped = false,
+                           acceptCallCount = count;
+
+                       if (typeof acceptCallCount === "undefined") {
+                               acceptCallCount = 1;
+                       }
+
+                       var resume = internalStop(test$$1);
+
+                       return function done() {
+                               if (config.current !== test$$1) {
+                                       throw Error("assert.async callback called after test finished.");
+                               }
+
+                               if (popped) {
+                                       test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2));
+                                       return;
+                               }
+
+                               acceptCallCount -= 1;
+                               if (acceptCallCount > 0) {
+                                       return;
+                               }
+
+                               popped = true;
+                               resume();
+                       };
+               }
+
+               // Exports test.push() to the user API
+               // Alias of pushResult.
+
+       }, {
+               key: "push",
+               value: function push(result, actual, expected, message, negative) {
+                       Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (https://api.qunitjs.com/assert/pushResult).");
+
+                       var currentAssert = this instanceof Assert ? this : config.current.assert;
+                       return currentAssert.pushResult({
+                               result: result,
+                               actual: actual,
+                               expected: expected,
+                               message: message,
+                               negative: negative
+                       });
+               }
+       }, {
+               key: "pushResult",
+               value: function pushResult(resultInfo) {
+
+                       // Destructure of resultInfo = { result, actual, expected, message, negative }
+                       var assert = this;
+                       var currentTest = assert instanceof Assert && assert.test || config.current;
+
+                       // Backwards compatibility fix.
+                       // Allows the direct use of global exported assertions and QUnit.assert.*
+                       // Although, it's use is not recommended as it can leak assertions
+                       // to other tests from async tests, because we only get a reference to the current test,
+                       // not exactly the test where assertion were intended to be called.
+                       if (!currentTest) {
+                               throw new Error("assertion outside test context, in " + sourceFromStacktrace(2));
+                       }
+
+                       if (!(assert instanceof Assert)) {
+                               assert = currentTest.assert;
+                       }
+
+                       return assert.test.pushResult(resultInfo);
+               }
+       }, {
+               key: "ok",
+               value: function ok(result, message) {
+                       if (!message) {
+                               message = result ? "okay" : "failed, expected argument to be truthy, was: " + dump.parse(result);
+                       }
+
+                       this.pushResult({
+                               result: !!result,
+                               actual: result,
+                               expected: true,
+                               message: message
+                       });
+               }
+       }, {
+               key: "notOk",
+               value: function notOk(result, message) {
+                       if (!message) {
+                               message = !result ? "okay" : "failed, expected argument to be falsy, was: " + dump.parse(result);
+                       }
+
+                       this.pushResult({
+                               result: !result,
+                               actual: result,
+                               expected: false,
+                               message: message
+                       });
+               }
+       }, {
+               key: "equal",
+               value: function equal(actual, expected, message) {
+
+                       // eslint-disable-next-line eqeqeq
+                       var result = expected == actual;
+
+                       this.pushResult({
+                               result: result,
+                               actual: actual,
+                               expected: expected,
+                               message: message
+                       });
+               }
+       }, {
+               key: "notEqual",
+               value: function notEqual(actual, expected, message) {
+
+                       // eslint-disable-next-line eqeqeq
+                       var result = expected != actual;
+
+                       this.pushResult({
+                               result: result,
+                               actual: actual,
+                               expected: expected,
+                               message: message,
+                               negative: true
+                       });
+               }
+       }, {
+               key: "propEqual",
+               value: function propEqual(actual, expected, message) {
+                       actual = objectValues(actual);
+                       expected = objectValues(expected);
+
+                       this.pushResult({
+                               result: equiv(actual, expected),
+                               actual: actual,
+                               expected: expected,
+                               message: message
+                       });
+               }
+       }, {
+               key: "notPropEqual",
+               value: function notPropEqual(actual, expected, message) {
+                       actual = objectValues(actual);
+                       expected = objectValues(expected);
+
+                       this.pushResult({
+                               result: !equiv(actual, expected),
+                               actual: actual,
+                               expected: expected,
+                               message: message,
+                               negative: true
+                       });
+               }
+       }, {
+               key: "deepEqual",
+               value: function deepEqual(actual, expected, message) {
+                       this.pushResult({
+                               result: equiv(actual, expected),
+                               actual: actual,
+                               expected: expected,
+                               message: message
+                       });
+               }
+       }, {
+               key: "notDeepEqual",
+               value: function notDeepEqual(actual, expected, message) {
+                       this.pushResult({
+                               result: !equiv(actual, expected),
+                               actual: actual,
+                               expected: expected,
+                               message: message,
+                               negative: true
+                       });
+               }
+       }, {
+               key: "strictEqual",
+               value: function strictEqual(actual, expected, message) {
+                       this.pushResult({
+                               result: expected === actual,
+                               actual: actual,
+                               expected: expected,
+                               message: message
+                       });
+               }
+       }, {
+               key: "notStrictEqual",
+               value: function notStrictEqual(actual, expected, message) {
+                       this.pushResult({
+                               result: expected !== actual,
+                               actual: actual,
+                               expected: expected,
+                               message: message,
+                               negative: true
+                       });
+               }
+       }, {
+               key: "throws",
+               value: function throws(block, expected, message) {
+                       var actual = void 0,
+                           result = false;
+
+                       var currentTest = this instanceof Assert && this.test || config.current;
+
+                       // 'expected' is optional unless doing string comparison
+                       if (objectType(expected) === "string") {
+                               if (message == null) {
+                                       message = expected;
+                                       expected = null;
+                               } else {
+                                       throw new Error("throws/raises does not accept a string value for the expected argument.\n" + "Use a non-string object value (e.g. regExp) instead if it's necessary.");
+                               }
+                       }
+
+                       currentTest.ignoreGlobalErrors = true;
+                       try {
+                               block.call(currentTest.testEnvironment);
+                       } catch (e) {
+                               actual = e;
+                       }
+                       currentTest.ignoreGlobalErrors = false;
+
+                       if (actual) {
+                               var expectedType = objectType(expected);
+
+                               // We don't want to validate thrown error
+                               if (!expected) {
+                                       result = true;
+                                       expected = null;
+
+                                       // Expected is a regexp
+                               } else if (expectedType === "regexp") {
+                                       result = expected.test(errorString(actual));
+
+                                       // Expected is a constructor, maybe an Error constructor
+                               } else if (expectedType === "function" && actual instanceof expected) {
+                                       result = true;
+
+                                       // Expected is an Error object
+                               } else if (expectedType === "object") {
+                                       result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
+
+                                       // Expected is a validation function which returns true if validation passed
+                               } else if (expectedType === "function" && expected.call({}, actual) === true) {
+                                       expected = null;
+                                       result = true;
+                               }
+                       }
+
+                       currentTest.assert.pushResult({
+                               result: result,
+                               actual: actual,
+                               expected: expected,
+                               message: message
+                       });
+               }
+       }]);
+       return Assert;
+  }();
+
+  // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
+  // Known to us are: Closure Compiler, Narwhal
+  // eslint-disable-next-line dot-notation
+
+
+  Assert.prototype.raises = Assert.prototype["throws"];
+
+  /**
+   * Converts an error into a simple string for comparisons.
+   *
+   * @param {Error} error
+   * @return {String}
+   */
+  function errorString(error) {
+       var resultErrorString = error.toString();
+
+       if (resultErrorString.substring(0, 7) === "[object") {
+               var name = error.name ? error.name.toString() : "Error";
+               var message = error.message ? error.message.toString() : "";
+
+               if (name && message) {
+                       return name + ": " + message;
+               } else if (name) {
+                       return name;
+               } else if (message) {
+                       return message;
+               } else {
+                       return "Error";
+               }
+       } else {
+               return resultErrorString;
+       }
+  }
+
+  /* global module, exports, define */
+  function exportQUnit(QUnit) {
+
+       if (defined.document) {
+
+               // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined.
+               if (window.QUnit && window.QUnit.version) {
+                       throw new Error("QUnit has already been defined.");
+               }
+
+               window.QUnit = QUnit;
+       }
+
+       // For nodejs
+       if (typeof module !== "undefined" && module && module.exports) {
+               module.exports = QUnit;
+
+               // For consistency with CommonJS environments' exports
+               module.exports.QUnit = QUnit;
+       }
+
+       // For CommonJS with exports, but without module.exports, like Rhino
+       if (typeof exports !== "undefined" && exports) {
+               exports.QUnit = QUnit;
+       }
+
+       if (typeof define === "function" && define.amd) {
+               define(function () {
+                       return QUnit;
+               });
+               QUnit.config.autostart = false;
+       }
+
+       // For Web/Service Workers
+       if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) {
+               self$1.QUnit = QUnit;
+       }
+  }
+
+  var SuiteReport = function () {
+       function SuiteReport(name, parentSuite) {
+               classCallCheck(this, SuiteReport);
+
+               this.name = name;
+               this.fullName = parentSuite ? parentSuite.fullName.concat(name) : [];
+
+               this.tests = [];
+               this.childSuites = [];
+
+               if (parentSuite) {
+                       parentSuite.pushChildSuite(this);
+               }
+       }
+
+       createClass(SuiteReport, [{
+               key: "start",
+               value: function start(recordTime) {
+                       if (recordTime) {
+                               this._startTime = Date.now();
+                       }
+
+                       return {
+                               name: this.name,
+                               fullName: this.fullName.slice(),
+                               tests: this.tests.map(function (test) {
+                                       return test.start();
+                               }),
+                               childSuites: this.childSuites.map(function (suite) {
+                                       return suite.start();
+                               }),
+                               testCounts: {
+                                       total: this.getTestCounts().total
+                               }
+                       };
+               }
+       }, {
+               key: "end",
+               value: function end(recordTime) {
+                       if (recordTime) {
+                               this._endTime = Date.now();
+                       }
+
+                       return {
+                               name: this.name,
+                               fullName: this.fullName.slice(),
+                               tests: this.tests.map(function (test) {
+                                       return test.end();
+                               }),
+                               childSuites: this.childSuites.map(function (suite) {
+                                       return suite.end();
+                               }),
+                               testCounts: this.getTestCounts(),
+                               runtime: this.getRuntime(),
+                               status: this.getStatus()
+                       };
+               }
+       }, {
+               key: "pushChildSuite",
+               value: function pushChildSuite(suite) {
+                       this.childSuites.push(suite);
+               }
+       }, {
+               key: "pushTest",
+               value: function pushTest(test) {
+                       this.tests.push(test);
+               }
+       }, {
+               key: "getRuntime",
+               value: function getRuntime() {
+                       return this._endTime - this._startTime;
+               }
+       }, {
+               key: "getTestCounts",
+               value: function getTestCounts() {
+                       var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
+
+                       counts = this.tests.reduce(function (counts, test) {
+                               if (test.valid) {
+                                       counts[test.getStatus()]++;
+                                       counts.total++;
+                               }
+
+                               return counts;
+                       }, counts);
+
+                       return this.childSuites.reduce(function (counts, suite) {
+                               return suite.getTestCounts(counts);
+                       }, counts);
+               }
+       }, {
+               key: "getStatus",
+               value: function getStatus() {
+                       var _getTestCounts = this.getTestCounts(),
+                           total = _getTestCounts.total,
+                           failed = _getTestCounts.failed,
+                           skipped = _getTestCounts.skipped,
+                           todo = _getTestCounts.todo;
+
+                       if (failed) {
+                               return "failed";
+                       } else {
+                               if (skipped === total) {
+                                       return "skipped";
+                               } else if (todo === total) {
+                                       return "todo";
+                               } else {
+                                       return "passed";
+                               }
+                       }
+               }
+       }]);
+       return SuiteReport;
+  }();
+
+  // Handle an unhandled exception. By convention, returns true if further
+  // error handling should be suppressed and false otherwise.
+  // In this case, we will only suppress further error handling if the
+  // "ignoreGlobalErrors" configuration option is enabled.
+  function onError(error) {
+       for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+               args[_key - 1] = arguments[_key];
+       }
+
+       if (config.current) {
+               if (config.current.ignoreGlobalErrors) {
+                       return true;
+               }
+               pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
+       } else {
+               test("global failure", extend(function () {
+                       pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
+               }, { validTest: true }));
+       }
+
+       return false;
+  }
+
+  var focused = false;
+  var QUnit = {};
+  var globalSuite = new SuiteReport();
+
+  // The initial "currentModule" represents the global (or top-level) module that
+  // is not explicitly defined by the user, therefore we add the "globalSuite" to
+  // it since each module has a suiteReport associated with it.
+  config.currentModule.suiteReport = globalSuite;
+
+  var moduleStack = [];
+  var globalStartCalled = false;
+  var runStarted = false;
+
+  // Figure out if we're running the tests from a server or not
+  QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
+
+  // Expose the current QUnit version
+  QUnit.version = "2.4.0";
+
+  function createModule(name, testEnvironment, modifiers) {
+       var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null;
+       var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
+       var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
+
+       var skip$$1 = parentModule !== null && parentModule.skip || modifiers.skip;
+       var todo$$1 = parentModule !== null && parentModule.todo || modifiers.todo;
+
+       var module = {
+               name: moduleName,
+               parentModule: parentModule,
+               tests: [],
+               moduleId: generateHash(moduleName),
+               testsRun: 0,
+               unskippedTestsRun: 0,
+               childModules: [],
+               suiteReport: new SuiteReport(name, parentSuite),
+
+               // Pass along `skip` and `todo` properties from parent module, in case
+               // there is one, to childs. And use own otherwise.
+               // This property will be used to mark own tests and tests of child suites
+               // as either `skipped` or `todo`.
+               skip: skip$$1,
+               todo: skip$$1 ? false : todo$$1
+       };
+
+       var env = {};
+       if (parentModule) {
+               parentModule.childModules.push(module);
+               extend(env, parentModule.testEnvironment);
+       }
+       extend(env, testEnvironment);
+       module.testEnvironment = env;
+
+       config.modules.push(module);
+       return module;
+  }
+
+  function processModule(name, options, executeNow) {
+       var modifiers = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
+
+       var module = createModule(name, options, modifiers);
+
+       // Move any hooks to a 'hooks' object
+       var testEnvironment = module.testEnvironment;
+       var hooks = module.hooks = {};
+
+       setHookFromEnvironment(hooks, testEnvironment, "before");
+       setHookFromEnvironment(hooks, testEnvironment, "beforeEach");
+       setHookFromEnvironment(hooks, testEnvironment, "afterEach");
+       setHookFromEnvironment(hooks, testEnvironment, "after");
+
+       function setHookFromEnvironment(hooks, environment, name) {
+               var potentialHook = environment[name];
+               hooks[name] = typeof potentialHook === "function" ? [potentialHook] : [];
+               delete environment[name];
+       }
+
+       var moduleFns = {
+               before: setHookFunction(module, "before"),
+               beforeEach: setHookFunction(module, "beforeEach"),
+               afterEach: setHookFunction(module, "afterEach"),
+               after: setHookFunction(module, "after")
+       };
+
+       var currentModule = config.currentModule;
+       if (objectType(executeNow) === "function") {
+               moduleStack.push(module);
+               config.currentModule = module;
+               executeNow.call(module.testEnvironment, moduleFns);
+               moduleStack.pop();
+               module = module.parentModule || currentModule;
+       }
+
+       config.currentModule = module;
+  }
+
+  // TODO: extract this to a new file alongside its related functions
+  function module$1(name, options, executeNow) {
+       if (focused) {
+               return;
+       }
+
+       if (arguments.length === 2) {
+               if (objectType(options) === "function") {
+                       executeNow = options;
+                       options = undefined;
+               }
+       }
+
+       processModule(name, options, executeNow);
+  }
+
+  module$1.only = function () {
+       if (focused) {
+               return;
+       }
+
+       config.modules.length = 0;
+       config.queue.length = 0;
+
+       module$1.apply(undefined, arguments);
+
+       focused = true;
+  };
+
+  module$1.skip = function (name, options, executeNow) {
+       if (focused) {
+               return;
+       }
+
+       if (arguments.length === 2) {
+               if (objectType(options) === "function") {
+                       executeNow = options;
+                       options = undefined;
+               }
+       }
+
+       processModule(name, options, executeNow, { skip: true });
+  };
+
+  module$1.todo = function (name, options, executeNow) {
+       if (focused) {
+               return;
+       }
+
+       if (arguments.length === 2) {
+               if (objectType(options) === "function") {
+                       executeNow = options;
+                       options = undefined;
+               }
+       }
+
+       processModule(name, options, executeNow, { todo: true });
+  };
+
+  extend(QUnit, {
+       on: on,
+
+       module: module$1,
+
+       test: test,
+
+       todo: todo,
+
+       skip: skip,
+
+       only: only,
+
+       start: function start(count) {
+               var globalStartAlreadyCalled = globalStartCalled;
+
+               if (!config.current) {
+                       globalStartCalled = true;
+
+                       if (runStarted) {
+                               throw new Error("Called start() while test already started running");
+                       } else if (globalStartAlreadyCalled || count > 1) {
+                               throw new Error("Called start() outside of a test context too many times");
+                       } else if (config.autostart) {
+                               throw new Error("Called start() outside of a test context when " + "QUnit.config.autostart was true");
+                       } else if (!config.pageLoaded) {
+
+                               // The page isn't completely loaded yet, so we set autostart and then
+                               // load if we're in Node or wait for the browser's load event.
+                               config.autostart = true;
+
+                               // Starts from Node even if .load was not previously called. We still return
+                               // early otherwise we'll wind up "beginning" twice.
+                               if (!defined.document) {
+                                       QUnit.load();
+                               }
+
+                               return;
+                       }
+               } else {
+                       throw new Error("QUnit.start cannot be called inside a test context.");
+               }
+
+               scheduleBegin();
+       },
+
+       config: config,
+
+       is: is,
+
+       objectType: objectType,
+
+       extend: extend,
+
+       load: function load() {
+               config.pageLoaded = true;
+
+               // Initialize the configuration options
+               extend(config, {
+                       stats: { all: 0, bad: 0 },
+                       started: 0,
+                       updateRate: 1000,
+                       autostart: true,
+                       filter: ""
+               }, true);
+
+               if (!runStarted) {
+                       config.blocking = false;
+
+                       if (config.autostart) {
+                               scheduleBegin();
+                       }
+               }
+       },
+
+       stack: function stack(offset) {
+               offset = (offset || 0) + 2;
+               return sourceFromStacktrace(offset);
+       },
+
+       onError: onError
+  });
+
+  QUnit.pushFailure = pushFailure;
+  QUnit.assert = Assert.prototype;
+  QUnit.equiv = equiv;
+  QUnit.dump = dump;
+
+  registerLoggingCallbacks(QUnit);
+
+  function scheduleBegin() {
+
+       runStarted = true;
+
+       // Add a slight delay to allow definition of more modules and tests.
+       if (defined.setTimeout) {
+               setTimeout(function () {
+                       begin();
+               }, 13);
+       } else {
+               begin();
+       }
+  }
+
+  function begin() {
+       var i,
+           l,
+           modulesLog = [];
+
+       // If the test run hasn't officially begun yet
+       if (!config.started) {
+
+               // Record the time of the test run's beginning
+               config.started = now();
+
+               // Delete the loose unnamed module if unused.
+               if (config.modules[0].name === "" && config.modules[0].tests.length === 0) {
+                       config.modules.shift();
+               }
+
+               // Avoid unnecessary information by not logging modules' test environments
+               for (i = 0, l = config.modules.length; i < l; i++) {
+                       modulesLog.push({
+                               name: config.modules[i].name,
+                               tests: config.modules[i].tests
+                       });
+               }
+
+               // The test run is officially beginning now
+               emit("runStart", globalSuite.start(true));
+               runLoggingCallbacks("begin", {
+                       totalTests: Test.count,
+                       modules: modulesLog
+               });
+       }
+
+       config.blocking = false;
+       ProcessingQueue.advance();
+  }
+
+  function setHookFunction(module, hookName) {
+       return function setHook(callback) {
+               module.hooks[hookName].push(callback);
+       };
+  }
+
+  exportQUnit(QUnit);
+
+  (function () {
+
+       if (typeof window === "undefined" || typeof document === "undefined") {
+               return;
+       }
+
+       var config = QUnit.config,
+           hasOwn = Object.prototype.hasOwnProperty;
+
+       // Stores fixture HTML for resetting later
+       function storeFixture() {
+
+               // Avoid overwriting user-defined values
+               if (hasOwn.call(config, "fixture")) {
+                       return;
+               }
+
+               var fixture = document.getElementById("qunit-fixture");
+               if (fixture) {
+                       config.fixture = fixture.innerHTML;
+               }
+       }
+
+       QUnit.begin(storeFixture);
+
+       // Resets the fixture DOM element if available.
+       function resetFixture() {
+               if (config.fixture == null) {
+                       return;
+               }
+
+               var fixture = document.getElementById("qunit-fixture");
+               if (fixture) {
+                       fixture.innerHTML = config.fixture;
+               }
+       }
+
+       QUnit.testStart(resetFixture);
+  })();
+
+  (function () {
+
+       // Only interact with URLs via window.location
+       var location = typeof window !== "undefined" && window.location;
+       if (!location) {
+               return;
+       }
+
+       var urlParams = getUrlParams();
+
+       QUnit.urlParams = urlParams;
+
+       // Match module/test by inclusion in an array
+       QUnit.config.moduleId = [].concat(urlParams.moduleId || []);
+       QUnit.config.testId = [].concat(urlParams.testId || []);
+
+       // Exact case-insensitive match of the module name
+       QUnit.config.module = urlParams.module;
+
+       // Regular expression or case-insenstive substring match against "moduleName: testName"
+       QUnit.config.filter = urlParams.filter;
+
+       // Test order randomization
+       if (urlParams.seed === true) {
+
+               // Generate a random seed if the option is specified without a value
+               QUnit.config.seed = Math.random().toString(36).slice(2);
+       } else if (urlParams.seed) {
+               QUnit.config.seed = urlParams.seed;
+       }
+
+       // Add URL-parameter-mapped config values with UI form rendering data
+       QUnit.config.urlConfig.push({
+               id: "hidepassed",
+               label: "Hide passed tests",
+               tooltip: "Only show tests and assertions that fail. Stored as query-strings."
+       }, {
+               id: "noglobals",
+               label: "Check for Globals",
+               tooltip: "Enabling this will test if any test introduces new properties on the " + "global object (`window` in Browsers). Stored as query-strings."
+       }, {
+               id: "notrycatch",
+               label: "No try-catch",
+               tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + "exceptions in IE reasonable. Stored as query-strings."
+       });
+
+       QUnit.begin(function () {
+               var i,
+                   option,
+                   urlConfig = QUnit.config.urlConfig;
+
+               for (i = 0; i < urlConfig.length; i++) {
+
+                       // Options can be either strings or objects with nonempty "id" properties
+                       option = QUnit.config.urlConfig[i];
+                       if (typeof option !== "string") {
+                               option = option.id;
+                       }
+
+                       if (QUnit.config[option] === undefined) {
+                               QUnit.config[option] = urlParams[option];
+                       }
+               }
+       });
+
+       function getUrlParams() {
+               var i, param, name, value;
+               var urlParams = Object.create(null);
+               var params = location.search.slice(1).split("&");
+               var length = params.length;
+
+               for (i = 0; i < length; i++) {
+                       if (params[i]) {
+                               param = params[i].split("=");
+                               name = decodeQueryParam(param[0]);
+
+                               // Allow just a key to turn on a flag, e.g., test.html?noglobals
+                               value = param.length === 1 || decodeQueryParam(param.slice(1).join("="));
+                               if (name in urlParams) {
+                                       urlParams[name] = [].concat(urlParams[name], value);
+                               } else {
+                                       urlParams[name] = value;
+                               }
+                       }
+               }
+
+               return urlParams;
+       }
+
+       function decodeQueryParam(param) {
+               return decodeURIComponent(param.replace(/\+/g, "%20"));
+       }
+  })();
+
+  var stats = {
+       passedTests: 0,
+       failedTests: 0,
+       skippedTests: 0,
+       todoTests: 0
+  };
+
+  // Escape text for attribute or text content.
+  function escapeText(s) {
+       if (!s) {
+               return "";
+       }
+       s = s + "";
+
+       // Both single quotes and double quotes (for attributes)
+       return s.replace(/['"<>&]/g, function (s) {
+               switch (s) {
+                       case "'":
+                               return "&#039;";
+                       case "\"":
+                               return "&quot;";
+                       case "<":
+                               return "&lt;";
+                       case ">":
+                               return "&gt;";
+                       case "&":
+                               return "&amp;";
+               }
+       });
+  }
+
+  (function () {
+
+       // Don't load the HTML Reporter on non-browser environments
+       if (typeof window === "undefined" || !window.document) {
+               return;
+       }
+
+       var config = QUnit.config,
+           document$$1 = window.document,
+           collapseNext = false,
+           hasOwn = Object.prototype.hasOwnProperty,
+           unfilteredUrl = setUrl({ filter: undefined, module: undefined,
+               moduleId: undefined, testId: undefined }),
+           modulesList = [];
+
+       function addEvent(elem, type, fn) {
+               elem.addEventListener(type, fn, false);
+       }
+
+       function removeEvent(elem, type, fn) {
+               elem.removeEventListener(type, fn, false);
+       }
+
+       function addEvents(elems, type, fn) {
+               var i = elems.length;
+               while (i--) {
+                       addEvent(elems[i], type, fn);
+               }
+       }
+
+       function hasClass(elem, name) {
+               return (" " + elem.className + " ").indexOf(" " + name + " ") >= 0;
+       }
+
+       function addClass(elem, name) {
+               if (!hasClass(elem, name)) {
+                       elem.className += (elem.className ? " " : "") + name;
+               }
+       }
+
+       function toggleClass(elem, name, force) {
+               if (force || typeof force === "undefined" && !hasClass(elem, name)) {
+                       addClass(elem, name);
+               } else {
+                       removeClass(elem, name);
+               }
+       }
+
+       function removeClass(elem, name) {
+               var set = " " + elem.className + " ";
+
+               // Class name may appear multiple times
+               while (set.indexOf(" " + name + " ") >= 0) {
+                       set = set.replace(" " + name + " ", " ");
+               }
+
+               // Trim for prettiness
+               elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
+       }
+
+       function id(name) {
+               return document$$1.getElementById && document$$1.getElementById(name);
+       }
+
+       function abortTests() {
+               var abortButton = id("qunit-abort-tests-button");
+               if (abortButton) {
+                       abortButton.disabled = true;
+                       abortButton.innerHTML = "Aborting...";
+               }
+               QUnit.config.queue.length = 0;
+               return false;
+       }
+
+       function interceptNavigation(ev) {
+               applyUrlParams();
+
+               if (ev && ev.preventDefault) {
+                       ev.preventDefault();
+               }
+
+               return false;
+       }
+
+       function getUrlConfigHtml() {
+               var i,
+                   j,
+                   val,
+                   escaped,
+                   escapedTooltip,
+                   selection = false,
+                   urlConfig = config.urlConfig,
+                   urlConfigHtml = "";
+
+               for (i = 0; i < urlConfig.length; i++) {
+
+                       // Options can be either strings or objects with nonempty "id" properties
+                       val = config.urlConfig[i];
+                       if (typeof val === "string") {
+                               val = {
+                                       id: val,
+                                       label: val
+                               };
+                       }
+
+                       escaped = escapeText(val.id);
+                       escapedTooltip = escapeText(val.tooltip);
+
+                       if (!val.value || typeof val.value === "string") {
+                               urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + "' title='" + escapedTooltip + "'><input id='qunit-urlconfig-" + escaped + "' name='" + escaped + "' type='checkbox'" + (val.value ? " value='" + escapeText(val.value) + "'" : "") + (config[val.id] ? " checked='checked'" : "") + " title='" + escapedTooltip + "' />" + escapeText(val.label) + "</label>";
+                       } else {
+                               urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + "' title='" + escapedTooltip + "'>" + val.label + ": </label><select id='qunit-urlconfig-" + escaped + "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
+
+                               if (QUnit.is("array", val.value)) {
+                                       for (j = 0; j < val.value.length; j++) {
+                                               escaped = escapeText(val.value[j]);
+                                               urlConfigHtml += "<option value='" + escaped + "'" + (config[val.id] === val.value[j] ? (selection = true) && " selected='selected'" : "") + ">" + escaped + "</option>";
+                                       }
+                               } else {
+                                       for (j in val.value) {
+                                               if (hasOwn.call(val.value, j)) {
+                                                       urlConfigHtml += "<option value='" + escapeText(j) + "'" + (config[val.id] === j ? (selection = true) && " selected='selected'" : "") + ">" + escapeText(val.value[j]) + "</option>";
+                                               }
+                                       }
+                               }
+                               if (config[val.id] && !selection) {
+                                       escaped = escapeText(config[val.id]);
+                                       urlConfigHtml += "<option value='" + escaped + "' selected='selected' disabled='disabled'>" + escaped + "</option>";
+                               }
+                               urlConfigHtml += "</select>";
+                       }
+               }
+
+               return urlConfigHtml;
+       }
+
+       // Handle "click" events on toolbar checkboxes and "change" for select menus.
+       // Updates the URL with the new state of `config.urlConfig` values.
+       function toolbarChanged() {
+               var updatedUrl,
+                   value,
+                   tests,
+                   field = this,
+                   params = {};
+
+               // Detect if field is a select menu or a checkbox
+               if ("selectedIndex" in field) {
+                       value = field.options[field.selectedIndex].value || undefined;
+               } else {
+                       value = field.checked ? field.defaultValue || true : undefined;
+               }
+
+               params[field.name] = value;
+               updatedUrl = setUrl(params);
+
+               // Check if we can apply the change without a page refresh
+               if ("hidepassed" === field.name && "replaceState" in window.history) {
+                       QUnit.urlParams[field.name] = value;
+                       config[field.name] = value || false;
+                       tests = id("qunit-tests");
+                       if (tests) {
+                               toggleClass(tests, "hidepass", value || false);
+                       }
+                       window.history.replaceState(null, "", updatedUrl);
+               } else {
+                       window.location = updatedUrl;
+               }
+       }
+
+       function setUrl(params) {
+               var key,
+                   arrValue,
+                   i,
+                   querystring = "?",
+                   location = window.location;
+
+               params = QUnit.extend(QUnit.extend({}, QUnit.urlParams), params);
+
+               for (key in params) {
+
+                       // Skip inherited or undefined properties
+                       if (hasOwn.call(params, key) && params[key] !== undefined) {
+
+                               // Output a parameter for each value of this key (but usually just one)
+                               arrValue = [].concat(params[key]);
+                               for (i = 0; i < arrValue.length; i++) {
+                                       querystring += encodeURIComponent(key);
+                                       if (arrValue[i] !== true) {
+                                               querystring += "=" + encodeURIComponent(arrValue[i]);
+                                       }
+                                       querystring += "&";
+                               }
+                       }
+               }
+               return location.protocol + "//" + location.host + location.pathname + querystring.slice(0, -1);
+       }
+
+       function applyUrlParams() {
+               var i,
+                   selectedModules = [],
+                   modulesList = id("qunit-modulefilter-dropdown-list").getElementsByTagName("input"),
+                   filter = id("qunit-filter-input").value;
+
+               for (i = 0; i < modulesList.length; i++) {
+                       if (modulesList[i].checked) {
+                               selectedModules.push(modulesList[i].value);
+                       }
+               }
+
+               window.location = setUrl({
+                       filter: filter === "" ? undefined : filter,
+                       moduleId: selectedModules.length === 0 ? undefined : selectedModules,
+
+                       // Remove module and testId filter
+                       module: undefined,
+                       testId: undefined
+               });
+       }
+
+       function toolbarUrlConfigContainer() {
+               var urlConfigContainer = document$$1.createElement("span");
+
+               urlConfigContainer.innerHTML = getUrlConfigHtml();
+               addClass(urlConfigContainer, "qunit-url-config");
+
+               addEvents(urlConfigContainer.getElementsByTagName("input"), "change", toolbarChanged);
+               addEvents(urlConfigContainer.getElementsByTagName("select"), "change", toolbarChanged);
+
+               return urlConfigContainer;
+       }
+
+       function abortTestsButton() {
+               var button = document$$1.createElement("button");
+               button.id = "qunit-abort-tests-button";
+               button.innerHTML = "Abort";
+               addEvent(button, "click", abortTests);
+               return button;
+       }
+
+       function toolbarLooseFilter() {
+               var filter = document$$1.createElement("form"),
+                   label = document$$1.createElement("label"),
+                   input = document$$1.createElement("input"),
+                   button = document$$1.createElement("button");
+
+               addClass(filter, "qunit-filter");
+
+               label.innerHTML = "Filter: ";
+
+               input.type = "text";
+               input.value = config.filter || "";
+               input.name = "filter";
+               input.id = "qunit-filter-input";
+
+               button.innerHTML = "Go";
+
+               label.appendChild(input);
+
+               filter.appendChild(label);
+               filter.appendChild(document$$1.createTextNode(" "));
+               filter.appendChild(button);
+               addEvent(filter, "submit", interceptNavigation);
+
+               return filter;
+       }
+
+       function moduleListHtml() {
+               var i,
+                   checked,
+                   html = "";
+
+               for (i = 0; i < config.modules.length; i++) {
+                       if (config.modules[i].name !== "") {
+                               checked = config.moduleId.indexOf(config.modules[i].moduleId) > -1;
+                               html += "<li><label class='clickable" + (checked ? " checked" : "") + "'><input type='checkbox' " + "value='" + config.modules[i].moduleId + "'" + (checked ? " checked='checked'" : "") + " />" + escapeText(config.modules[i].name) + "</label></li>";
+                       }
+               }
+
+               return html;
+       }
+
+       function toolbarModuleFilter() {
+               var allCheckbox,
+                   commit,
+                   reset,
+                   moduleFilter = document$$1.createElement("form"),
+                   label = document$$1.createElement("label"),
+                   moduleSearch = document$$1.createElement("input"),
+                   dropDown = document$$1.createElement("div"),
+                   actions = document$$1.createElement("span"),
+                   dropDownList = document$$1.createElement("ul"),
+                   dirty = false;
+
+               moduleSearch.id = "qunit-modulefilter-search";
+               addEvent(moduleSearch, "input", searchInput);
+               addEvent(moduleSearch, "input", searchFocus);
+               addEvent(moduleSearch, "focus", searchFocus);
+               addEvent(moduleSearch, "click", searchFocus);
+
+               label.id = "qunit-modulefilter-search-container";
+               label.innerHTML = "Module: ";
+               label.appendChild(moduleSearch);
+
+               actions.id = "qunit-modulefilter-actions";
+               actions.innerHTML = "<button style='display:none'>Apply</button>" + "<button type='reset' style='display:none'>Reset</button>" + "<label class='clickable" + (config.moduleId.length ? "" : " checked") + "'><input type='checkbox'" + (config.moduleId.length ? "" : " checked='checked'") + ">All modules</label>";
+               allCheckbox = actions.lastChild.firstChild;
+               commit = actions.firstChild;
+               reset = commit.nextSibling;
+               addEvent(commit, "click", applyUrlParams);
+
+               dropDownList.id = "qunit-modulefilter-dropdown-list";
+               dropDownList.innerHTML = moduleListHtml();
+
+               dropDown.id = "qunit-modulefilter-dropdown";
+               dropDown.style.display = "none";
+               dropDown.appendChild(actions);
+               dropDown.appendChild(dropDownList);
+               addEvent(dropDown, "change", selectionChange);
+               selectionChange();
+
+               moduleFilter.id = "qunit-modulefilter";
+               moduleFilter.appendChild(label);
+               moduleFilter.appendChild(dropDown);
+               addEvent(moduleFilter, "submit", interceptNavigation);
+               addEvent(moduleFilter, "reset", function () {
+
+                       // Let the reset happen, then update styles
+                       window.setTimeout(selectionChange);
+               });
+
+               // Enables show/hide for the dropdown
+               function searchFocus() {
+                       if (dropDown.style.display !== "none") {
+                               return;
+                       }
+
+                       dropDown.style.display = "block";
+                       addEvent(document$$1, "click", hideHandler);
+                       addEvent(document$$1, "keydown", hideHandler);
+
+                       // Hide on Escape keydown or outside-container click
+                       function hideHandler(e) {
+                               var inContainer = moduleFilter.contains(e.target);
+
+                               if (e.keyCode === 27 || !inContainer) {
+                                       if (e.keyCode === 27 && inContainer) {
+                                               moduleSearch.focus();
+                                       }
+                                       dropDown.style.display = "none";
+                                       removeEvent(document$$1, "click", hideHandler);
+                                       removeEvent(document$$1, "keydown", hideHandler);
+                                       moduleSearch.value = "";
+                                       searchInput();
+                               }
+                       }
+               }
+
+               // Processes module search box input
+               function searchInput() {
+                       var i,
+                           item,
+                           searchText = moduleSearch.value.toLowerCase(),
+                           listItems = dropDownList.children;
+
+                       for (i = 0; i < listItems.length; i++) {
+                               item = listItems[i];
+                               if (!searchText || item.textContent.toLowerCase().indexOf(searchText) > -1) {
+                                       item.style.display = "";
+                               } else {
+                                       item.style.display = "none";
+                               }
+                       }
+               }
+
+               // Processes selection changes
+               function selectionChange(evt) {
+                       var i,
+                           item,
+                           checkbox = evt && evt.target || allCheckbox,
+                           modulesList = dropDownList.getElementsByTagName("input"),
+                           selectedNames = [];
+
+                       toggleClass(checkbox.parentNode, "checked", checkbox.checked);
+
+                       dirty = false;
+                       if (checkbox.checked && checkbox !== allCheckbox) {
+                               allCheckbox.checked = false;
+                               removeClass(allCheckbox.parentNode, "checked");
+                       }
+                       for (i = 0; i < modulesList.length; i++) {
+                               item = modulesList[i];
+                               if (!evt) {
+                                       toggleClass(item.parentNode, "checked", item.checked);
+                               } else if (checkbox === allCheckbox && checkbox.checked) {
+                                       item.checked = false;
+                                       removeClass(item.parentNode, "checked");
+                               }
+                               dirty = dirty || item.checked !== item.defaultChecked;
+                               if (item.checked) {
+                                       selectedNames.push(item.parentNode.textContent);
+                               }
+                       }
+
+                       commit.style.display = reset.style.display = dirty ? "" : "none";
+                       moduleSearch.placeholder = selectedNames.join(", ") || allCheckbox.parentNode.textContent;
+                       moduleSearch.title = "Type to filter list. Current selection:\n" + (selectedNames.join("\n") || allCheckbox.parentNode.textContent);
+               }
+
+               return moduleFilter;
+       }
+
+       function appendToolbar() {
+               var toolbar = id("qunit-testrunner-toolbar");
+
+               if (toolbar) {
+                       toolbar.appendChild(toolbarUrlConfigContainer());
+                       toolbar.appendChild(toolbarModuleFilter());
+                       toolbar.appendChild(toolbarLooseFilter());
+                       toolbar.appendChild(document$$1.createElement("div")).className = "clearfix";
+               }
+       }
+
+       function appendHeader() {
+               var header = id("qunit-header");
+
+               if (header) {
+                       header.innerHTML = "<a href='" + escapeText(unfilteredUrl) + "'>" + header.innerHTML + "</a> ";
+               }
+       }
+
+       function appendBanner() {
+               var banner = id("qunit-banner");
+
+               if (banner) {
+                       banner.className = "";
+               }
+       }
+
+       function appendTestResults() {
+               var tests = id("qunit-tests"),
+                   result = id("qunit-testresult"),
+                   controls;
+
+               if (result) {
+                       result.parentNode.removeChild(result);
+               }
+
+               if (tests) {
+                       tests.innerHTML = "";
+                       result = document$$1.createElement("p");
+                       result.id = "qunit-testresult";
+                       result.className = "result";
+                       tests.parentNode.insertBefore(result, tests);
+                       result.innerHTML = "<div id=\"qunit-testresult-display\">Running...<br />&#160;</div>" + "<div id=\"qunit-testresult-controls\"></div>" + "<div class=\"clearfix\"></div>";
+                       controls = id("qunit-testresult-controls");
+               }
+
+               if (controls) {
+                       controls.appendChild(abortTestsButton());
+               }
+       }
+
+       function appendFilteredTest() {
+               var testId = QUnit.config.testId;
+               if (!testId || testId.length <= 0) {
+                       return "";
+               }
+               return "<div id='qunit-filteredTest'>Rerunning selected tests: " + escapeText(testId.join(", ")) + " <a id='qunit-clearFilter' href='" + escapeText(unfilteredUrl) + "'>Run all tests</a></div>";
+       }
+
+       function appendUserAgent() {
+               var userAgent = id("qunit-userAgent");
+
+               if (userAgent) {
+                       userAgent.innerHTML = "";
+                       userAgent.appendChild(document$$1.createTextNode("QUnit " + QUnit.version + "; " + navigator.userAgent));
+               }
+       }
+
+       function appendInterface() {
+               var qunit = id("qunit");
+
+               if (qunit) {
+                       qunit.innerHTML = "<h1 id='qunit-header'>" + escapeText(document$$1.title) + "</h1>" + "<h2 id='qunit-banner'></h2>" + "<div id='qunit-testrunner-toolbar'></div>" + appendFilteredTest() + "<h2 id='qunit-userAgent'></h2>" + "<ol id='qunit-tests'></ol>";
+               }
+
+               appendHeader();
+               appendBanner();
+               appendTestResults();
+               appendUserAgent();
+               appendToolbar();
+       }
+
+       function appendTestsList(modules) {
+               var i, l, x, z, test, moduleObj;
+
+               for (i = 0, l = modules.length; i < l; i++) {
+                       moduleObj = modules[i];
+
+                       for (x = 0, z = moduleObj.tests.length; x < z; x++) {
+                               test = moduleObj.tests[x];
+
+                               appendTest(test.name, test.testId, moduleObj.name);
+                       }
+               }
+       }
+
+       function appendTest(name, testId, moduleName) {
+               var title,
+                   rerunTrigger,
+                   testBlock,
+                   assertList,
+                   tests = id("qunit-tests");
+
+               if (!tests) {
+                       return;
+               }
+
+               title = document$$1.createElement("strong");
+               title.innerHTML = getNameHtml(name, moduleName);
+
+               rerunTrigger = document$$1.createElement("a");
+               rerunTrigger.innerHTML = "Rerun";
+               rerunTrigger.href = setUrl({ testId: testId });
+
+               testBlock = document$$1.createElement("li");
+               testBlock.appendChild(title);
+               testBlock.appendChild(rerunTrigger);
+               testBlock.id = "qunit-test-output-" + testId;
+
+               assertList = document$$1.createElement("ol");
+               assertList.className = "qunit-assert-list";
+
+               testBlock.appendChild(assertList);
+
+               tests.appendChild(testBlock);
+       }
+
+       // HTML Reporter initialization and load
+       QUnit.begin(function (details) {
+               var i, moduleObj, tests;
+
+               // Sort modules by name for the picker
+               for (i = 0; i < details.modules.length; i++) {
+                       moduleObj = details.modules[i];
+                       if (moduleObj.name) {
+                               modulesList.push(moduleObj.name);
+                       }
+               }
+               modulesList.sort(function (a, b) {
+                       return a.localeCompare(b);
+               });
+
+               // Initialize QUnit elements
+               appendInterface();
+               appendTestsList(details.modules);
+               tests = id("qunit-tests");
+               if (tests && config.hidepassed) {
+                       addClass(tests, "hidepass");
+               }
+       });
+
+       QUnit.done(function (details) {
+               var banner = id("qunit-banner"),
+                   tests = id("qunit-tests"),
+                   abortButton = id("qunit-abort-tests-button"),
+                   totalTests = stats.passedTests + stats.skippedTests + stats.todoTests + stats.failedTests,
+                   html = [totalTests, " tests completed in ", details.runtime, " milliseconds, with ", stats.failedTests, " failed, ", stats.skippedTests, " skipped, and ", stats.todoTests, " todo.<br />", "<span class='passed'>", details.passed, "</span> assertions of <span class='total'>", details.total, "</span> passed, <span class='failed'>", details.failed, "</span> failed."].join(""),
+                   test,
+                   assertLi,
+                   assertList;
+
+               // Update remaing tests to aborted
+               if (abortButton && abortButton.disabled) {
+                       html = "Tests aborted after " + details.runtime + " milliseconds.";
+
+                       for (var i = 0; i < tests.children.length; i++) {
+                               test = tests.children[i];
+                               if (test.className === "" || test.className === "running") {
+                                       test.className = "aborted";
+                                       assertList = test.getElementsByTagName("ol")[0];
+                                       assertLi = document$$1.createElement("li");
+                                       assertLi.className = "fail";
+                                       assertLi.innerHTML = "Test aborted.";
+                                       assertList.appendChild(assertLi);
+                               }
+                       }
+               }
+
+               if (banner && (!abortButton || abortButton.disabled === false)) {
+                       banner.className = stats.failedTests ? "qunit-fail" : "qunit-pass";
+               }
+
+               if (abortButton) {
+                       abortButton.parentNode.removeChild(abortButton);
+               }
+
+               if (tests) {
+                       id("qunit-testresult-display").innerHTML = html;
+               }
+
+               if (config.altertitle && document$$1.title) {
+
+                       // Show ✖ for good, ✔ for bad suite result in title
+                       // use escape sequences in case file gets loaded with non-utf-8-charset
+                       document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
+               }
+
+               // Scroll back to top to show results
+               if (config.scrolltop && window.scrollTo) {
+                       window.scrollTo(0, 0);
+               }
+       });
+
+       function getNameHtml(name, module) {
+               var nameHtml = "";
+
+               if (module) {
+                       nameHtml = "<span class='module-name'>" + escapeText(module) + "</span>: ";
+               }
+
+               nameHtml += "<span class='test-name'>" + escapeText(name) + "</span>";
+
+               return nameHtml;
+       }
+
+       QUnit.testStart(function (details) {
+               var running, testBlock, bad;
+
+               testBlock = id("qunit-test-output-" + details.testId);
+               if (testBlock) {
+                       testBlock.className = "running";
+               } else {
+
+                       // Report later registered tests
+                       appendTest(details.name, details.testId, details.module);
+               }
+
+               running = id("qunit-testresult-display");
+               if (running) {
+                       bad = QUnit.config.reorder && details.previousFailure;
+
+                       running.innerHTML = (bad ? "Rerunning previously failed test: <br />" : "Running: <br />") + getNameHtml(details.name, details.module);
+               }
+       });
+
+       function stripHtml(string) {
+
+               // Strip tags, html entity and whitespaces
+               return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\&quot;/g, "").replace(/\s+/g, "");
+       }
+
+       QUnit.log(function (details) {
+               var assertList,
+                   assertLi,
+                   message,
+                   expected,
+                   actual,
+                   diff,
+                   showDiff = false,
+                   testItem = id("qunit-test-output-" + details.testId);
+
+               if (!testItem) {
+                       return;
+               }
+
+               message = escapeText(details.message) || (details.result ? "okay" : "failed");
+               message = "<span class='test-message'>" + message + "</span>";
+               message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
+
+               // The pushFailure doesn't provide details.expected
+               // when it calls, it's implicit to also not show expected and diff stuff
+               // Also, we need to check details.expected existence, as it can exist and be undefined
+               if (!details.result && hasOwn.call(details, "expected")) {
+                       if (details.negative) {
+                               expected = "NOT " + QUnit.dump.parse(details.expected);
+                       } else {
+                               expected = QUnit.dump.parse(details.expected);
+                       }
+
+                       actual = QUnit.dump.parse(details.actual);
+                       message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + escapeText(expected) + "</pre></td></tr>";
+
+                       if (actual !== expected) {
+
+                               message += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText(actual) + "</pre></td></tr>";
+
+                               if (typeof details.actual === "number" && typeof details.expected === "number") {
+                                       if (!isNaN(details.actual) && !isNaN(details.expected)) {
+                                               showDiff = true;
+                                               diff = details.actual - details.expected;
+                                               diff = (diff > 0 ? "+" : "") + diff;
+                                       }
+                               } else if (typeof details.actual !== "boolean" && typeof details.expected !== "boolean") {
+                                       diff = QUnit.diff(expected, actual);
+
+                                       // don't show diff if there is zero overlap
+                                       showDiff = stripHtml(diff).length !== stripHtml(expected).length + stripHtml(actual).length;
+                               }
+
+                               if (showDiff) {
+                                       message += "<tr class='test-diff'><th>Diff: </th><td><pre>" + diff + "</pre></td></tr>";
+                               }
+                       } else if (expected.indexOf("[object Array]") !== -1 || expected.indexOf("[object Object]") !== -1) {
+                               message += "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the depth of object is more than current max depth (" + QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " + " run with a higher max depth or <a href='" + escapeText(setUrl({ maxDepth: -1 })) + "'>" + "Rerun</a> without max depth.</p></td></tr>";
+                       } else {
+                               message += "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the expected and actual results have an equivalent" + " serialization</td></tr>";
+                       }
+
+                       if (details.source) {
+                               message += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details.source) + "</pre></td></tr>";
+                       }
+
+                       message += "</table>";
+
+                       // This occurs when pushFailure is set and we have an extracted stack trace
+               } else if (!details.result && details.source) {
+                       message += "<table>" + "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details.source) + "</pre></td></tr>" + "</table>";
+               }
+
+               assertList = testItem.getElementsByTagName("ol")[0];
+
+               assertLi = document$$1.createElement("li");
+               assertLi.className = details.result ? "pass" : "fail";
+               assertLi.innerHTML = message;
+               assertList.appendChild(assertLi);
+       });
+
+       QUnit.testDone(function (details) {
+               var testTitle,
+                   time,
+                   testItem,
+                   assertList,
+                   good,
+                   bad,
+                   testCounts,
+                   skipped,
+                   sourceName,
+                   tests = id("qunit-tests");
+
+               if (!tests) {
+                       return;
+               }
+
+               testItem = id("qunit-test-output-" + details.testId);
+
+               assertList = testItem.getElementsByTagName("ol")[0];
+
+               good = details.passed;
+               bad = details.failed;
+
+               // This test passed if it has no unexpected failed assertions
+               var testPassed = details.failed > 0 ? details.todo : !details.todo;
+
+               if (testPassed) {
+
+                       // Collapse the passing tests
+                       addClass(assertList, "qunit-collapsed");
+               } else if (config.collapse) {
+                       if (!collapseNext) {
+
+                               // Skip collapsing the first failing test
+                               collapseNext = true;
+                       } else {
+
+                               // Collapse remaining tests
+                               addClass(assertList, "qunit-collapsed");
+                       }
+               }
+
+               // The testItem.firstChild is the test name
+               testTitle = testItem.firstChild;
+
+               testCounts = bad ? "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " : "";
+
+               testTitle.innerHTML += " <b class='counts'>(" + testCounts + details.assertions.length + ")</b>";
+
+               if (details.skipped) {
+                       stats.skippedTests++;
+
+                       testItem.className = "skipped";
+                       skipped = document$$1.createElement("em");
+                       skipped.className = "qunit-skipped-label";
+                       skipped.innerHTML = "skipped";
+                       testItem.insertBefore(skipped, testTitle);
+               } else {
+                       addEvent(testTitle, "click", function () {
+                               toggleClass(assertList, "qunit-collapsed");
+                       });
+
+                       testItem.className = testPassed ? "pass" : "fail";
+
+                       if (details.todo) {
+                               var todoLabel = document$$1.createElement("em");
+                               todoLabel.className = "qunit-todo-label";
+                               todoLabel.innerHTML = "todo";
+                               testItem.className += " todo";
+                               testItem.insertBefore(todoLabel, testTitle);
+                       }
+
+                       time = document$$1.createElement("span");
+                       time.className = "runtime";
+                       time.innerHTML = details.runtime + " ms";
+                       testItem.insertBefore(time, assertList);
+
+                       if (!testPassed) {
+                               stats.failedTests++;
+                       } else if (details.todo) {
+                               stats.todoTests++;
+                       } else {
+                               stats.passedTests++;
+                       }
+               }
+
+               // Show the source of the test when showing assertions
+               if (details.source) {
+                       sourceName = document$$1.createElement("p");
+                       sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
+                       addClass(sourceName, "qunit-source");
+                       if (testPassed) {
+                               addClass(sourceName, "qunit-collapsed");
+                       }
+                       addEvent(testTitle, "click", function () {
+                               toggleClass(sourceName, "qunit-collapsed");
+                       });
+                       testItem.appendChild(sourceName);
+               }
+       });
+
+       // Avoid readyState issue with phantomjs
+       // Ref: #818
+       var notPhantom = function (p) {
+               return !(p && p.version && p.version.major > 0);
+       }(window.phantom);
+
+       if (notPhantom && document$$1.readyState === "complete") {
+               QUnit.load();
+       } else {
+               addEvent(window, "load", QUnit.load);
+       }
+
+       // Wrap window.onerror. We will call the original window.onerror to see if
+       // the existing handler fully handles the error; if not, we will call the
+       // QUnit.onError function.
+       var originalWindowOnError = window.onerror;
+
+       // Cover uncaught exceptions
+       // Returning true will suppress the default browser handler,
+       // returning false will let it run.
+       window.onerror = function (message, fileName, lineNumber) {
+               var ret = false;
+               if (originalWindowOnError) {
+                       for (var _len = arguments.length, args = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
+                               args[_key - 3] = arguments[_key];
+                       }
+
+                       ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber].concat(args));
+               }
+
+               // Treat return value as window.onerror itself does,
+               // Only do our handling if not suppressed.
+               if (ret !== true) {
+                       var error = {
+                               message: message,
+                               fileName: fileName,
+                               lineNumber: lineNumber
+                       };
+
+                       ret = QUnit.onError(error);
+               }
+
+               return ret;
+       };
+  })();
+
+  /*
+   * This file is a modified version of google-diff-match-patch's JavaScript implementation
+   * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
+   * modifications are licensed as more fully set forth in LICENSE.txt.
+   *
+   * The original source of google-diff-match-patch is attributable and licensed as follows:
+   *
+   * Copyright 2006 Google Inc.
+   * https://code.google.com/p/google-diff-match-patch/
+   *
+   * Licensed under the Apache License, Version 2.0 (the "License");
+   * you may not use this file except in compliance with the License.
+   * You may obtain a copy of the License at
+   *
+   * https://www.apache.org/licenses/LICENSE-2.0
+   *
+   * Unless required by applicable law or agreed to in writing, software
+   * distributed under the License is distributed on an "AS IS" BASIS,
+   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   * See the License for the specific language governing permissions and
+   * limitations under the License.
+   *
+   * More Info:
+   *  https://code.google.com/p/google-diff-match-patch/
+   *
+   * Usage: QUnit.diff(expected, actual)
+   *
+   */
+  QUnit.diff = function () {
+       function DiffMatchPatch() {}
+
+       //  DIFF FUNCTIONS
+
+       /**
+    * The data structure representing a diff is an array of tuples:
+    * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
+    * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
+    */
+       var DIFF_DELETE = -1,
+           DIFF_INSERT = 1,
+           DIFF_EQUAL = 0;
+
+       /**
+    * Find the differences between two texts.  Simplifies the problem by stripping
+    * any common prefix or suffix off the texts before diffing.
+    * @param {string} text1 Old string to be diffed.
+    * @param {string} text2 New string to be diffed.
+    * @param {boolean=} optChecklines Optional speedup flag. If present and false,
+    *     then don't run a line-level diff first to identify the changed areas.
+    *     Defaults to true, which does a faster, slightly less optimal diff.
+    * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+    */
+       DiffMatchPatch.prototype.DiffMain = function (text1, text2, optChecklines) {
+               var deadline, checklines, commonlength, commonprefix, commonsuffix, diffs;
+
+               // The diff must be complete in up to 1 second.
+               deadline = new Date().getTime() + 1000;
+
+               // Check for null inputs.
+               if (text1 === null || text2 === null) {
+                       throw new Error("Null input. (DiffMain)");
+               }
+
+               // Check for equality (speedup).
+               if (text1 === text2) {
+                       if (text1) {
+                               return [[DIFF_EQUAL, text1]];
+                       }
+                       return [];
+               }
+
+               if (typeof optChecklines === "undefined") {
+                       optChecklines = true;
+               }
+
+               checklines = optChecklines;
+
+               // Trim off common prefix (speedup).
+               commonlength = this.diffCommonPrefix(text1, text2);
+               commonprefix = text1.substring(0, commonlength);
+               text1 = text1.substring(commonlength);
+               text2 = text2.substring(commonlength);
+
+               // Trim off common suffix (speedup).
+               commonlength = this.diffCommonSuffix(text1, text2);
+               commonsuffix = text1.substring(text1.length - commonlength);
+               text1 = text1.substring(0, text1.length - commonlength);
+               text2 = text2.substring(0, text2.length - commonlength);
+
+               // Compute the diff on the middle block.
+               diffs = this.diffCompute(text1, text2, checklines, deadline);
+
+               // Restore the prefix and suffix.
+               if (commonprefix) {
+                       diffs.unshift([DIFF_EQUAL, commonprefix]);
+               }
+               if (commonsuffix) {
+                       diffs.push([DIFF_EQUAL, commonsuffix]);
+               }
+               this.diffCleanupMerge(diffs);
+               return diffs;
+       };
+
+       /**
+    * Reduce the number of edits by eliminating operationally trivial equalities.
+    * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+    */
+       DiffMatchPatch.prototype.diffCleanupEfficiency = function (diffs) {
+               var changes, equalities, equalitiesLength, lastequality, pointer, preIns, preDel, postIns, postDel;
+               changes = false;
+               equalities = []; // Stack of indices where equalities are found.
+               equalitiesLength = 0; // Keeping our own length var is faster in JS.
+               /** @type {?string} */
+               lastequality = null;
+
+               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+               pointer = 0; // Index of current position.
+
+               // Is there an insertion operation before the last equality.
+               preIns = false;
+
+               // Is there a deletion operation before the last equality.
+               preDel = false;
+
+               // Is there an insertion operation after the last equality.
+               postIns = false;
+
+               // Is there a deletion operation after the last equality.
+               postDel = false;
+               while (pointer < diffs.length) {
+
+                       // Equality found.
+                       if (diffs[pointer][0] === DIFF_EQUAL) {
+                               if (diffs[pointer][1].length < 4 && (postIns || postDel)) {
+
+                                       // Candidate found.
+                                       equalities[equalitiesLength++] = pointer;
+                                       preIns = postIns;
+                                       preDel = postDel;
+                                       lastequality = diffs[pointer][1];
+                               } else {
+
+                                       // Not a candidate, and can never become one.
+                                       equalitiesLength = 0;
+                                       lastequality = null;
+                               }
+                               postIns = postDel = false;
+
+                               // An insertion or deletion.
+                       } else {
+
+                               if (diffs[pointer][0] === DIFF_DELETE) {
+                                       postDel = true;
+                               } else {
+                                       postIns = true;
+                               }
+
+                               /*
+       * Five types to be split:
+       * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
+       * <ins>A</ins>X<ins>C</ins><del>D</del>
+       * <ins>A</ins><del>B</del>X<ins>C</ins>
+       * <ins>A</del>X<ins>C</ins><del>D</del>
+       * <ins>A</ins><del>B</del>X<del>C</del>
+       */
+                               if (lastequality && (preIns && preDel && postIns && postDel || lastequality.length < 2 && preIns + preDel + postIns + postDel === 3)) {
+
+                                       // Duplicate record.
+                                       diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
+
+                                       // Change second copy to insert.
+                                       diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
+                                       equalitiesLength--; // Throw away the equality we just deleted;
+                                       lastequality = null;
+                                       if (preIns && preDel) {
+
+                                               // No changes made which could affect previous entry, keep going.
+                                               postIns = postDel = true;
+                                               equalitiesLength = 0;
+                                       } else {
+                                               equalitiesLength--; // Throw away the previous equality.
+                                               pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
+                                               postIns = postDel = false;
+                                       }
+                                       changes = true;
+                               }
+                       }
+                       pointer++;
+               }
+
+               if (changes) {
+                       this.diffCleanupMerge(diffs);
+               }
+       };
+
+       /**
+    * Convert a diff array into a pretty HTML report.
+    * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+    * @param {integer} string to be beautified.
+    * @return {string} HTML representation.
+    */
+       DiffMatchPatch.prototype.diffPrettyHtml = function (diffs) {
+               var op,
+                   data,
+                   x,
+                   html = [];
+               for (x = 0; x < diffs.length; x++) {
+                       op = diffs[x][0]; // Operation (insert, delete, equal)
+                       data = diffs[x][1]; // Text of change.
+                       switch (op) {
+                               case DIFF_INSERT:
+                                       html[x] = "<ins>" + escapeText(data) + "</ins>";
+                                       break;
+                               case DIFF_DELETE:
+                                       html[x] = "<del>" + escapeText(data) + "</del>";
+                                       break;
+                               case DIFF_EQUAL:
+                                       html[x] = "<span>" + escapeText(data) + "</span>";
+                                       break;
+                       }
+               }
+               return html.join("");
+       };
+
+       /**
+    * Determine the common prefix of two strings.
+    * @param {string} text1 First string.
+    * @param {string} text2 Second string.
+    * @return {number} The number of characters common to the start of each
+    *     string.
+    */
+       DiffMatchPatch.prototype.diffCommonPrefix = function (text1, text2) {
+               var pointermid, pointermax, pointermin, pointerstart;
+
+               // Quick check for common null cases.
+               if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) {
+                       return 0;
+               }
+
+               // Binary search.
+               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
+               pointermin = 0;
+               pointermax = Math.min(text1.length, text2.length);
+               pointermid = pointermax;
+               pointerstart = 0;
+               while (pointermin < pointermid) {
+                       if (text1.substring(pointerstart, pointermid) === text2.substring(pointerstart, pointermid)) {
+                               pointermin = pointermid;
+                               pointerstart = pointermin;
+                       } else {
+                               pointermax = pointermid;
+                       }
+                       pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
+               }
+               return pointermid;
+       };
+
+       /**
+    * Determine the common suffix of two strings.
+    * @param {string} text1 First string.
+    * @param {string} text2 Second string.
+    * @return {number} The number of characters common to the end of each string.
+    */
+       DiffMatchPatch.prototype.diffCommonSuffix = function (text1, text2) {
+               var pointermid, pointermax, pointermin, pointerend;
+
+               // Quick check for common null cases.
+               if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
+                       return 0;
+               }
+
+               // Binary search.
+               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
+               pointermin = 0;
+               pointermax = Math.min(text1.length, text2.length);
+               pointermid = pointermax;
+               pointerend = 0;
+               while (pointermin < pointermid) {
+                       if (text1.substring(text1.length - pointermid, text1.length - pointerend) === text2.substring(text2.length - pointermid, text2.length - pointerend)) {
+                               pointermin = pointermid;
+                               pointerend = pointermin;
+                       } else {
+                               pointermax = pointermid;
+                       }
+                       pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
+               }
+               return pointermid;
+       };
+
+       /**
+    * Find the differences between two texts.  Assumes that the texts do not
+    * have any common prefix or suffix.
+    * @param {string} text1 Old string to be diffed.
+    * @param {string} text2 New string to be diffed.
+    * @param {boolean} checklines Speedup flag.  If false, then don't run a
+    *     line-level diff first to identify the changed areas.
+    *     If true, then run a faster, slightly less optimal diff.
+    * @param {number} deadline Time when the diff should be complete by.
+    * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+    * @private
+    */
+       DiffMatchPatch.prototype.diffCompute = function (text1, text2, checklines, deadline) {
+               var diffs, longtext, shorttext, i, hm, text1A, text2A, text1B, text2B, midCommon, diffsA, diffsB;
+
+               if (!text1) {
+
+                       // Just add some text (speedup).
+                       return [[DIFF_INSERT, text2]];
+               }
+
+               if (!text2) {
+
+                       // Just delete some text (speedup).
+                       return [[DIFF_DELETE, text1]];
+               }
+
+               longtext = text1.length > text2.length ? text1 : text2;
+               shorttext = text1.length > text2.length ? text2 : text1;
+               i = longtext.indexOf(shorttext);
+               if (i !== -1) {
+
+                       // Shorter text is inside the longer text (speedup).
+                       diffs = [[DIFF_INSERT, longtext.substring(0, i)], [DIFF_EQUAL, shorttext], [DIFF_INSERT, longtext.substring(i + shorttext.length)]];
+
+                       // Swap insertions for deletions if diff is reversed.
+                       if (text1.length > text2.length) {
+                               diffs[0][0] = diffs[2][0] = DIFF_DELETE;
+                       }
+                       return diffs;
+               }
+
+               if (shorttext.length === 1) {
+
+                       // Single character string.
+                       // After the previous speedup, the character can't be an equality.
+                       return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
+               }
+
+               // Check to see if the problem can be split in two.
+               hm = this.diffHalfMatch(text1, text2);
+               if (hm) {
+
+                       // A half-match was found, sort out the return data.
+                       text1A = hm[0];
+                       text1B = hm[1];
+                       text2A = hm[2];
+                       text2B = hm[3];
+                       midCommon = hm[4];
+
+                       // Send both pairs off for separate processing.
+                       diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
+                       diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
+
+                       // Merge the results.
+                       return diffsA.concat([[DIFF_EQUAL, midCommon]], diffsB);
+               }
+
+               if (checklines && text1.length > 100 && text2.length > 100) {
+                       return this.diffLineMode(text1, text2, deadline);
+               }
+
+               return this.diffBisect(text1, text2, deadline);
+       };
+
+       /**
+    * Do the two texts share a substring which is at least half the length of the
+    * longer text?
+    * This speedup can produce non-minimal diffs.
+    * @param {string} text1 First string.
+    * @param {string} text2 Second string.
+    * @return {Array.<string>} Five element Array, containing the prefix of
+    *     text1, the suffix of text1, the prefix of text2, the suffix of
+    *     text2 and the common middle.  Or null if there was no match.
+    * @private
+    */
+       DiffMatchPatch.prototype.diffHalfMatch = function (text1, text2) {
+               var longtext, shorttext, dmp, text1A, text2B, text2A, text1B, midCommon, hm1, hm2, hm;
+
+               longtext = text1.length > text2.length ? text1 : text2;
+               shorttext = text1.length > text2.length ? text2 : text1;
+               if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
+                       return null; // Pointless.
+               }
+               dmp = this; // 'this' becomes 'window' in a closure.
+
+               /**
+     * Does a substring of shorttext exist within longtext such that the substring
+     * is at least half the length of longtext?
+     * Closure, but does not reference any external variables.
+     * @param {string} longtext Longer string.
+     * @param {string} shorttext Shorter string.
+     * @param {number} i Start index of quarter length substring within longtext.
+     * @return {Array.<string>} Five element Array, containing the prefix of
+     *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
+     *     of shorttext and the common middle.  Or null if there was no match.
+     * @private
+     */
+               function diffHalfMatchI(longtext, shorttext, i) {
+                       var seed, j, bestCommon, prefixLength, suffixLength, bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
+
+                       // Start with a 1/4 length substring at position i as a seed.
+                       seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
+                       j = -1;
+                       bestCommon = "";
+                       while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
+                               prefixLength = dmp.diffCommonPrefix(longtext.substring(i), shorttext.substring(j));
+                               suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i), shorttext.substring(0, j));
+                               if (bestCommon.length < suffixLength + prefixLength) {
+                                       bestCommon = shorttext.substring(j - suffixLength, j) + shorttext.substring(j, j + prefixLength);
+                                       bestLongtextA = longtext.substring(0, i - suffixLength);
+                                       bestLongtextB = longtext.substring(i + prefixLength);
+                                       bestShorttextA = shorttext.substring(0, j - suffixLength);
+                                       bestShorttextB = shorttext.substring(j + prefixLength);
+                               }
+                       }
+                       if (bestCommon.length * 2 >= longtext.length) {
+                               return [bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB, bestCommon];
+                       } else {
+                               return null;
+                       }
+               }
+
+               // First check if the second quarter is the seed for a half-match.
+               hm1 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4));
+
+               // Check again based on the third quarter.
+               hm2 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2));
+               if (!hm1 && !hm2) {
+                       return null;
+               } else if (!hm2) {
+                       hm = hm1;
+               } else if (!hm1) {
+                       hm = hm2;
+               } else {
+
+                       // Both matched.  Select the longest.
+                       hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
+               }
+
+               // A half-match was found, sort out the return data.
+               if (text1.length > text2.length) {
+                       text1A = hm[0];
+                       text1B = hm[1];
+                       text2A = hm[2];
+                       text2B = hm[3];
+               } else {
+                       text2A = hm[0];
+                       text2B = hm[1];
+                       text1A = hm[2];
+                       text1B = hm[3];
+               }
+               midCommon = hm[4];
+               return [text1A, text1B, text2A, text2B, midCommon];
+       };
+
+       /**
+    * Do a quick line-level diff on both strings, then rediff the parts for
+    * greater accuracy.
+    * This speedup can produce non-minimal diffs.
+    * @param {string} text1 Old string to be diffed.
+    * @param {string} text2 New string to be diffed.
+    * @param {number} deadline Time when the diff should be complete by.
+    * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+    * @private
+    */
+       DiffMatchPatch.prototype.diffLineMode = function (text1, text2, deadline) {
+               var a, diffs, linearray, pointer, countInsert, countDelete, textInsert, textDelete, j;
+
+               // Scan the text on a line-by-line basis first.
+               a = this.diffLinesToChars(text1, text2);
+               text1 = a.chars1;
+               text2 = a.chars2;
+               linearray = a.lineArray;
+
+               diffs = this.DiffMain(text1, text2, false, deadline);
+
+               // Convert the diff back to original text.
+               this.diffCharsToLines(diffs, linearray);
+
+               // Eliminate freak matches (e.g. blank lines)
+               this.diffCleanupSemantic(diffs);
+
+               // Rediff any replacement blocks, this time character-by-character.
+               // Add a dummy entry at the end.
+               diffs.push([DIFF_EQUAL, ""]);
+               pointer = 0;
+               countDelete = 0;
+               countInsert = 0;
+               textDelete = "";
+               textInsert = "";
+               while (pointer < diffs.length) {
+                       switch (diffs[pointer][0]) {
+                               case DIFF_INSERT:
+                                       countInsert++;
+                                       textInsert += diffs[pointer][1];
+                                       break;
+                               case DIFF_DELETE:
+                                       countDelete++;
+                                       textDelete += diffs[pointer][1];
+                                       break;
+                               case DIFF_EQUAL:
+
+                                       // Upon reaching an equality, check for prior redundancies.
+                                       if (countDelete >= 1 && countInsert >= 1) {
+
+                                               // Delete the offending records and add the merged ones.
+                                               diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert);
+                                               pointer = pointer - countDelete - countInsert;
+                                               a = this.DiffMain(textDelete, textInsert, false, deadline);
+                                               for (j = a.length - 1; j >= 0; j--) {
+                                                       diffs.splice(pointer, 0, a[j]);
+                                               }
+                                               pointer = pointer + a.length;
+                                       }
+                                       countInsert = 0;
+                                       countDelete = 0;
+                                       textDelete = "";
+                                       textInsert = "";
+                                       break;
+                       }
+                       pointer++;
+               }
+               diffs.pop(); // Remove the dummy entry at the end.
+
+               return diffs;
+       };
+
+       /**
+    * Find the 'middle snake' of a diff, split the problem in two
+    * and return the recursively constructed diff.
+    * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
+    * @param {string} text1 Old string to be diffed.
+    * @param {string} text2 New string to be diffed.
+    * @param {number} deadline Time at which to bail if not yet complete.
+    * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+    * @private
+    */
+       DiffMatchPatch.prototype.diffBisect = function (text1, text2, deadline) {
+               var text1Length, text2Length, maxD, vOffset, vLength, v1, v2, x, delta, front, k1start, k1end, k2start, k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
+
+               // Cache the text lengths to prevent multiple calls.
+               text1Length = text1.length;
+               text2Length = text2.length;
+               maxD = Math.ceil((text1Length + text2Length) / 2);
+               vOffset = maxD;
+               vLength = 2 * maxD;
+               v1 = new Array(vLength);
+               v2 = new Array(vLength);
+
+               // Setting all elements to -1 is faster in Chrome & Firefox than mixing
+               // integers and undefined.
+               for (x = 0; x < vLength; x++) {
+                       v1[x] = -1;
+                       v2[x] = -1;
+               }
+               v1[vOffset + 1] = 0;
+               v2[vOffset + 1] = 0;
+               delta = text1Length - text2Length;
+
+               // If the total number of characters is odd, then the front path will collide
+               // with the reverse path.
+               front = delta % 2 !== 0;
+
+               // Offsets for start and end of k loop.
+               // Prevents mapping of space beyond the grid.
+               k1start = 0;
+               k1end = 0;
+               k2start = 0;
+               k2end = 0;
+               for (d = 0; d < maxD; d++) {
+
+                       // Bail out if deadline is reached.
+                       if (new Date().getTime() > deadline) {
+                               break;
+                       }
+
+                       // Walk the front path one step.
+                       for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
+                               k1Offset = vOffset + k1;
+                               if (k1 === -d || k1 !== d && v1[k1Offset - 1] < v1[k1Offset + 1]) {
+                                       x1 = v1[k1Offset + 1];
+                               } else {
+                                       x1 = v1[k1Offset - 1] + 1;
+                               }
+                               y1 = x1 - k1;
+                               while (x1 < text1Length && y1 < text2Length && text1.charAt(x1) === text2.charAt(y1)) {
+                                       x1++;
+                                       y1++;
+                               }
+                               v1[k1Offset] = x1;
+                               if (x1 > text1Length) {
+
+                                       // Ran off the right of the graph.
+                                       k1end += 2;
+                               } else if (y1 > text2Length) {
+
+                                       // Ran off the bottom of the graph.
+                                       k1start += 2;
+                               } else if (front) {
+                                       k2Offset = vOffset + delta - k1;
+                                       if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
+
+                                               // Mirror x2 onto top-left coordinate system.
+                                               x2 = text1Length - v2[k2Offset];
+                                               if (x1 >= x2) {
+
+                                                       // Overlap detected.
+                                                       return this.diffBisectSplit(text1, text2, x1, y1, deadline);
+                                               }
+                                       }
+                               }
+                       }
+
+                       // Walk the reverse path one step.
+                       for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
+                               k2Offset = vOffset + k2;
+                               if (k2 === -d || k2 !== d && v2[k2Offset - 1] < v2[k2Offset + 1]) {
+                                       x2 = v2[k2Offset + 1];
+                               } else {
+                                       x2 = v2[k2Offset - 1] + 1;
+                               }
+                               y2 = x2 - k2;
+                               while (x2 < text1Length && y2 < text2Length && text1.charAt(text1Length - x2 - 1) === text2.charAt(text2Length - y2 - 1)) {
+                                       x2++;
+                                       y2++;
+                               }
+                               v2[k2Offset] = x2;
+                               if (x2 > text1Length) {
+
+                                       // Ran off the left of the graph.
+                                       k2end += 2;
+                               } else if (y2 > text2Length) {
+
+                                       // Ran off the top of the graph.
+                                       k2start += 2;
+                               } else if (!front) {
+                                       k1Offset = vOffset + delta - k2;
+                                       if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
+                                               x1 = v1[k1Offset];
+                                               y1 = vOffset + x1 - k1Offset;
+
+                                               // Mirror x2 onto top-left coordinate system.
+                                               x2 = text1Length - x2;
+                                               if (x1 >= x2) {
+
+                                                       // Overlap detected.
+                                                       return this.diffBisectSplit(text1, text2, x1, y1, deadline);
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               // Diff took too long and hit the deadline or
+               // number of diffs equals number of characters, no commonality at all.
+               return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
+       };
+
+       /**
+    * Given the location of the 'middle snake', split the diff in two parts
+    * and recurse.
+    * @param {string} text1 Old string to be diffed.
+    * @param {string} text2 New string to be diffed.
+    * @param {number} x Index of split point in text1.
+    * @param {number} y Index of split point in text2.
+    * @param {number} deadline Time at which to bail if not yet complete.
+    * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+    * @private
+    */
+       DiffMatchPatch.prototype.diffBisectSplit = function (text1, text2, x, y, deadline) {
+               var text1a, text1b, text2a, text2b, diffs, diffsb;
+               text1a = text1.substring(0, x);
+               text2a = text2.substring(0, y);
+               text1b = text1.substring(x);
+               text2b = text2.substring(y);
+
+               // Compute both diffs serially.
+               diffs = this.DiffMain(text1a, text2a, false, deadline);
+               diffsb = this.DiffMain(text1b, text2b, false, deadline);
+
+               return diffs.concat(diffsb);
+       };
+
+       /**
+    * Reduce the number of edits by eliminating semantically trivial equalities.
+    * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+    */
+       DiffMatchPatch.prototype.diffCleanupSemantic = function (diffs) {
+               var changes, equalities, equalitiesLength, lastequality, pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
+               changes = false;
+               equalities = []; // Stack of indices where equalities are found.
+               equalitiesLength = 0; // Keeping our own length var is faster in JS.
+               /** @type {?string} */
+               lastequality = null;
+
+               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+               pointer = 0; // Index of current position.
+
+               // Number of characters that changed prior to the equality.
+               lengthInsertions1 = 0;
+               lengthDeletions1 = 0;
+
+               // Number of characters that changed after the equality.
+               lengthInsertions2 = 0;
+               lengthDeletions2 = 0;
+               while (pointer < diffs.length) {
+                       if (diffs[pointer][0] === DIFF_EQUAL) {
+                               // Equality found.
+                               equalities[equalitiesLength++] = pointer;
+                               lengthInsertions1 = lengthInsertions2;
+                               lengthDeletions1 = lengthDeletions2;
+                               lengthInsertions2 = 0;
+                               lengthDeletions2 = 0;
+                               lastequality = diffs[pointer][1];
+                       } else {
+                               // An insertion or deletion.
+                               if (diffs[pointer][0] === DIFF_INSERT) {
+                                       lengthInsertions2 += diffs[pointer][1].length;
+                               } else {
+                                       lengthDeletions2 += diffs[pointer][1].length;
+                               }
+
+                               // Eliminate an equality that is smaller or equal to the edits on both
+                               // sides of it.
+                               if (lastequality && lastequality.length <= Math.max(lengthInsertions1, lengthDeletions1) && lastequality.length <= Math.max(lengthInsertions2, lengthDeletions2)) {
+
+                                       // Duplicate record.
+                                       diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
+
+                                       // Change second copy to insert.
+                                       diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
+
+                                       // Throw away the equality we just deleted.
+                                       equalitiesLength--;
+
+                                       // Throw away the previous equality (it needs to be reevaluated).
+                                       equalitiesLength--;
+                                       pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
+
+                                       // Reset the counters.
+                                       lengthInsertions1 = 0;
+                                       lengthDeletions1 = 0;
+                                       lengthInsertions2 = 0;
+                                       lengthDeletions2 = 0;
+                                       lastequality = null;
+                                       changes = true;
+                               }
+                       }
+                       pointer++;
+               }
+
+               // Normalize the diff.
+               if (changes) {
+                       this.diffCleanupMerge(diffs);
+               }
+
+               // Find any overlaps between deletions and insertions.
+               // e.g: <del>abcxxx</del><ins>xxxdef</ins>
+               //   -> <del>abc</del>xxx<ins>def</ins>
+               // e.g: <del>xxxabc</del><ins>defxxx</ins>
+               //   -> <ins>def</ins>xxx<del>abc</del>
+               // Only extract an overlap if it is as big as the edit ahead or behind it.
+               pointer = 1;
+               while (pointer < diffs.length) {
+                       if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) {
+                               deletion = diffs[pointer - 1][1];
+                               insertion = diffs[pointer][1];
+                               overlapLength1 = this.diffCommonOverlap(deletion, insertion);
+                               overlapLength2 = this.diffCommonOverlap(insertion, deletion);
+                               if (overlapLength1 >= overlapLength2) {
+                                       if (overlapLength1 >= deletion.length / 2 || overlapLength1 >= insertion.length / 2) {
+
+                                               // Overlap found.  Insert an equality and trim the surrounding edits.
+                                               diffs.splice(pointer, 0, [DIFF_EQUAL, insertion.substring(0, overlapLength1)]);
+                                               diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlapLength1);
+                                               diffs[pointer + 1][1] = insertion.substring(overlapLength1);
+                                               pointer++;
+                                       }
+                               } else {
+                                       if (overlapLength2 >= deletion.length / 2 || overlapLength2 >= insertion.length / 2) {
+
+                                               // Reverse overlap found.
+                                               // Insert an equality and swap and trim the surrounding edits.
+                                               diffs.splice(pointer, 0, [DIFF_EQUAL, deletion.substring(0, overlapLength2)]);
+
+                                               diffs[pointer - 1][0] = DIFF_INSERT;
+                                               diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlapLength2);
+                                               diffs[pointer + 1][0] = DIFF_DELETE;
+                                               diffs[pointer + 1][1] = deletion.substring(overlapLength2);
+                                               pointer++;
+                                       }
+                               }
+                               pointer++;
+                       }
+                       pointer++;
+               }
+       };
+
+       /**
+    * Determine if the suffix of one string is the prefix of another.
+    * @param {string} text1 First string.
+    * @param {string} text2 Second string.
+    * @return {number} The number of characters common to the end of the first
+    *     string and the start of the second string.
+    * @private
+    */
+       DiffMatchPatch.prototype.diffCommonOverlap = function (text1, text2) {
+               var text1Length, text2Length, textLength, best, length, pattern, found;
+
+               // Cache the text lengths to prevent multiple calls.
+               text1Length = text1.length;
+               text2Length = text2.length;
+
+               // Eliminate the null case.
+               if (text1Length === 0 || text2Length === 0) {
+                       return 0;
+               }
+
+               // Truncate the longer string.
+               if (text1Length > text2Length) {
+                       text1 = text1.substring(text1Length - text2Length);
+               } else if (text1Length < text2Length) {
+                       text2 = text2.substring(0, text1Length);
+               }
+               textLength = Math.min(text1Length, text2Length);
+
+               // Quick check for the worst case.
+               if (text1 === text2) {
+                       return textLength;
+               }
+
+               // Start by looking for a single character match
+               // and increase length until no match is found.
+               // Performance analysis: https://neil.fraser.name/news/2010/11/04/
+               best = 0;
+               length = 1;
+               while (true) {
+                       pattern = text1.substring(textLength - length);
+                       found = text2.indexOf(pattern);
+                       if (found === -1) {
+                               return best;
+                       }
+                       length += found;
+                       if (found === 0 || text1.substring(textLength - length) === text2.substring(0, length)) {
+                               best = length;
+                               length++;
+                       }
+               }
+       };
+
+       /**
+    * Split two texts into an array of strings.  Reduce the texts to a string of
+    * hashes where each Unicode character represents one line.
+    * @param {string} text1 First string.
+    * @param {string} text2 Second string.
+    * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
+    *     An object containing the encoded text1, the encoded text2 and
+    *     the array of unique strings.
+    *     The zeroth element of the array of unique strings is intentionally blank.
+    * @private
+    */
+       DiffMatchPatch.prototype.diffLinesToChars = function (text1, text2) {
+               var lineArray, lineHash, chars1, chars2;
+               lineArray = []; // E.g. lineArray[4] === 'Hello\n'
+               lineHash = {}; // E.g. lineHash['Hello\n'] === 4
+
+               // '\x00' is a valid character, but various debuggers don't like it.
+               // So we'll insert a junk entry to avoid generating a null character.
+               lineArray[0] = "";
+
+               /**
+     * Split a text into an array of strings.  Reduce the texts to a string of
+     * hashes where each Unicode character represents one line.
+     * Modifies linearray and linehash through being a closure.
+     * @param {string} text String to encode.
+     * @return {string} Encoded string.
+     * @private
+     */
+               function diffLinesToCharsMunge(text) {
+                       var chars, lineStart, lineEnd, lineArrayLength, line;
+                       chars = "";
+
+                       // Walk the text, pulling out a substring for each line.
+                       // text.split('\n') would would temporarily double our memory footprint.
+                       // Modifying text would create many large strings to garbage collect.
+                       lineStart = 0;
+                       lineEnd = -1;
+
+                       // Keeping our own length variable is faster than looking it up.
+                       lineArrayLength = lineArray.length;
+                       while (lineEnd < text.length - 1) {
+                               lineEnd = text.indexOf("\n", lineStart);
+                               if (lineEnd === -1) {
+                                       lineEnd = text.length - 1;
+                               }
+                               line = text.substring(lineStart, lineEnd + 1);
+                               lineStart = lineEnd + 1;
+
+                               if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined) {
+                                       chars += String.fromCharCode(lineHash[line]);
+                               } else {
+                                       chars += String.fromCharCode(lineArrayLength);
+                                       lineHash[line] = lineArrayLength;
+                                       lineArray[lineArrayLength++] = line;
+                               }
+                       }
+                       return chars;
+               }
+
+               chars1 = diffLinesToCharsMunge(text1);
+               chars2 = diffLinesToCharsMunge(text2);
+               return {
+                       chars1: chars1,
+                       chars2: chars2,
+                       lineArray: lineArray
+               };
+       };
+
+       /**
+    * Rehydrate the text in a diff from a string of line hashes to real lines of
+    * text.
+    * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+    * @param {!Array.<string>} lineArray Array of unique strings.
+    * @private
+    */
+       DiffMatchPatch.prototype.diffCharsToLines = function (diffs, lineArray) {
+               var x, chars, text, y;
+               for (x = 0; x < diffs.length; x++) {
+                       chars = diffs[x][1];
+                       text = [];
+                       for (y = 0; y < chars.length; y++) {
+                               text[y] = lineArray[chars.charCodeAt(y)];
+                       }
+                       diffs[x][1] = text.join("");
+               }
+       };
+
+       /**
+    * Reorder and merge like edit sections.  Merge equalities.
+    * Any edit section can move as long as it doesn't cross an equality.
+    * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+    */
+       DiffMatchPatch.prototype.diffCleanupMerge = function (diffs) {
+               var pointer, countDelete, countInsert, textInsert, textDelete, commonlength, changes, diffPointer, position;
+               diffs.push([DIFF_EQUAL, ""]); // Add a dummy entry at the end.
+               pointer = 0;
+               countDelete = 0;
+               countInsert = 0;
+               textDelete = "";
+               textInsert = "";
+
+               while (pointer < diffs.length) {
+                       switch (diffs[pointer][0]) {
+                               case DIFF_INSERT:
+                                       countInsert++;
+                                       textInsert += diffs[pointer][1];
+                                       pointer++;
+                                       break;
+                               case DIFF_DELETE:
+                                       countDelete++;
+                                       textDelete += diffs[pointer][1];
+                                       pointer++;
+                                       break;
+                               case DIFF_EQUAL:
+
+                                       // Upon reaching an equality, check for prior redundancies.
+                                       if (countDelete + countInsert > 1) {
+                                               if (countDelete !== 0 && countInsert !== 0) {
+
+                                                       // Factor out any common prefixes.
+                                                       commonlength = this.diffCommonPrefix(textInsert, textDelete);
+                                                       if (commonlength !== 0) {
+                                                               if (pointer - countDelete - countInsert > 0 && diffs[pointer - countDelete - countInsert - 1][0] === DIFF_EQUAL) {
+                                                                       diffs[pointer - countDelete - countInsert - 1][1] += textInsert.substring(0, commonlength);
+                                                               } else {
+                                                                       diffs.splice(0, 0, [DIFF_EQUAL, textInsert.substring(0, commonlength)]);
+                                                                       pointer++;
+                                                               }
+                                                               textInsert = textInsert.substring(commonlength);
+                                                               textDelete = textDelete.substring(commonlength);
+                                                       }
+
+                                                       // Factor out any common suffixies.
+                                                       commonlength = this.diffCommonSuffix(textInsert, textDelete);
+                                                       if (commonlength !== 0) {
+                                                               diffs[pointer][1] = textInsert.substring(textInsert.length - commonlength) + diffs[pointer][1];
+                                                               textInsert = textInsert.substring(0, textInsert.length - commonlength);
+                                                               textDelete = textDelete.substring(0, textDelete.length - commonlength);
+                                                       }
+                                               }
+
+                                               // Delete the offending records and add the merged ones.
+                                               if (countDelete === 0) {
+                                                       diffs.splice(pointer - countInsert, countDelete + countInsert, [DIFF_INSERT, textInsert]);
+                                               } else if (countInsert === 0) {
+                                                       diffs.splice(pointer - countDelete, countDelete + countInsert, [DIFF_DELETE, textDelete]);
+                                               } else {
+                                                       diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert, [DIFF_DELETE, textDelete], [DIFF_INSERT, textInsert]);
+                                               }
+                                               pointer = pointer - countDelete - countInsert + (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
+                                       } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
+
+                                               // Merge this equality with the previous one.
+                                               diffs[pointer - 1][1] += diffs[pointer][1];
+                                               diffs.splice(pointer, 1);
+                                       } else {
+                                               pointer++;
+                                       }
+                                       countInsert = 0;
+                                       countDelete = 0;
+                                       textDelete = "";
+                                       textInsert = "";
+                                       break;
+                       }
+               }
+               if (diffs[diffs.length - 1][1] === "") {
+                       diffs.pop(); // Remove the dummy entry at the end.
+               }
+
+               // Second pass: look for single edits surrounded on both sides by equalities
+               // which can be shifted sideways to eliminate an equality.
+               // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
+               changes = false;
+               pointer = 1;
+
+               // Intentionally ignore the first and last element (don't need checking).
+               while (pointer < diffs.length - 1) {
+                       if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) {
+
+                               diffPointer = diffs[pointer][1];
+                               position = diffPointer.substring(diffPointer.length - diffs[pointer - 1][1].length);
+
+                               // This is a single edit surrounded by equalities.
+                               if (position === diffs[pointer - 1][1]) {
+
+                                       // Shift the edit over the previous equality.
+                                       diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length);
+                                       diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
+                                       diffs.splice(pointer - 1, 1);
+                                       changes = true;
+                               } else if (diffPointer.substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) {
+
+                                       // Shift the edit over the next equality.
+                                       diffs[pointer - 1][1] += diffs[pointer + 1][1];
+                                       diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1];
+                                       diffs.splice(pointer + 1, 1);
+                                       changes = true;
+                               }
+                       }
+                       pointer++;
+               }
+
+               // If shifts were made, the diff needs reordering and another shift sweep.
+               if (changes) {
+                       this.diffCleanupMerge(diffs);
+               }
+       };
+
+       return function (o, n) {
+               var diff, output, text;
+               diff = new DiffMatchPatch();
+               output = diff.DiffMain(o, n);
+               diff.diffCleanupEfficiency(output);
+               text = diff.diffPrettyHtml(output);
+
+               return text;
+       };
+  }();
+
+}((function() { return this; }())));
index 602bdab..dec0fe8 100644 (file)
@@ -8,24 +8,24 @@
 
 /**
  * Hide all the elements irrelevant for printing
+ * Skins however can and should override.
  */
 /* General hide-in-print class, please only use sparely */
 .noprint,
 /* Various content classes, in alphabetical order */
+.catlinks,
 .magnify,
 .mw-cite-backlink,
 .mw-jump,
 .mw-editsection,
 .mw-editsection-like,
 .mw-hidden-catlinks,
-.noexcerpt,
-.ns-0 .mw-redirectedfrom,
+.mw-indicators,
+.mw-redirectedfrom,
 .patrollink,
-.play-btn-large,
 .usermessage,
 /* Various content ids, in alphabetical order */
 #column-one,
-#coordinates .image,
 #footer-places,
 #jump-to-nav,
 #mw-navigation,
index 9a85291..b6eda0f 100644 (file)
@@ -52,6 +52,7 @@
 
                this.conflicts = config.conflicts || {};
                this.defaultParams = {};
+               this.defaultFilters = {};
 
                this.aggregate( { update: 'filterItemUpdate' } );
                this.connect( this, { filterItemUpdate: 'onFilterItemUpdate' } );
@@ -89,6 +90,7 @@
                        var subsetNames = [],
                                filterItem = new mw.rcfilters.dm.FilterItem( filter.name, model, {
                                        group: model.getName(),
+                                       useDefaultAsBaseValue: !!filter.useDefaultAsBaseValue,
                                        label: filter.label || filter.name,
                                        description: filter.description || '',
                                        labelPrefixKey: model.labelPrefixKey,
                        items.push( filterItem );
 
                        // Store default parameter state; in this case, default is defined per filter
-                       if ( model.getType() === 'send_unselected_if_any' ) {
+                       if (
+                               model.getType() === 'send_unselected_if_any' ||
+                               model.getType() === 'boolean'
+                       ) {
                                // Store the default parameter state
                                // For this group type, parameter values are direct
                                // We need to convert from a boolean to a string ('1' and '0')
                        // or select the first option
                        this.selectItemByParamName( defaultParam );
                }
+
+               // Store default filter state based on default params
+               this.defaultFilters = this.getFilterRepresentation( this.getDefaultParams() );
+
+               // Check for filters that should be initially selected by their default value
+               this.getItems().forEach( function ( item ) {
+                       if (
+                               item.isUsingDefaultAsBaseValue() &&
+                               (
+                                       // This setting can only be applied to these groups
+                                       // the other groups are way too complex for that
+                                       model.getType() === 'single_option' ||
+                                       model.getType() === 'boolean'
+                               )
+                       ) {
+                               // Apply selection
+                               item.toggleSelected( !!model.defaultFilters[ item.getName() ] );
+                       }
+               } );
        };
 
        /**
         */
        mw.rcfilters.dm.FilterGroup.prototype.onFilterItemUpdate = function ( item ) {
                // Update state
-               var active = this.areAnySelected();
+               var changed = false,
+                       active = this.areAnySelected();
 
                if (
                        item.isSelected() &&
                        this.currSelected.toggleSelected( false );
                }
 
+               // For 'single_option' groups, check if we just unselected all
+               // items. This should never be the result. If we did unselect
+               // all (like resetting all filters to false) then this group
+               // must choose its default item or the first item in the group
                if (
+                       this.getType() === 'single_option' &&
+                       !this.getItems().some( function ( filterItem ) {
+                               return filterItem.isSelected();
+                       } )
+               ) {
+                       // Single option means there must be a single option
+                       // selected, so we have to either select the default
+                       // or select the first option
+                       this.currSelected = this.getItemByParamName( this.defaultParams[ this.getName() ] );
+                       this.currSelected.toggleSelected( true );
+                       changed = true;
+               }
+
+               if (
+                       changed ||
                        this.active !== active ||
                        this.currSelected !== item
                ) {
                return this.defaultParams;
        };
 
+       /**
+        * Get the default filter state of this group
+        *
+        * @return {Object} Default filter state
+        */
+       mw.rcfilters.dm.FilterGroup.prototype.getDefaultFilters = function () {
+               return this.defaultFilters;
+       };
+
        /**
         * This is for a single_option and string_options group types
         * it returns the value of the default
                var values,
                        areAnySelected = false,
                        buildFromCurrentState = !filterRepresentation,
+                       defaultFilters = this.getDefaultFilters(),
                        result = {},
                        model = this,
                        filterParamNames = {},
                        } else if ( !filterRepresentation[ item.getName() ] ) {
                                // We are given a filter representation, but we have to make
                                // sure that we fill in the missing filters if there are any
-                               // we will assume they are all falsey
-                               filterRepresentation[ item.getName() ] = false;
+                               // we will assume they are all falsey, unless they have
+                               // isUsingDefaultAsBaseValue, in which case they get their
+                               // default state
+                               if (
+                                       item.isUsingDefaultAsBaseValue() &&
+                                       (
+                                               // This setting can only be applied to these groups
+                                               // the other groups are way too complex for that
+                                               model.getType() === 'single_option' ||
+                                               model.getType() === 'boolean'
+                                       )
+                               ) {
+                                       filterRepresentation[ item.getName() ] = !!defaultFilters[ item.getName() ];
+                               } else {
+                                       filterRepresentation[ item.getName() ] = false;
+                               }
                        }
 
                        if ( filterRepresentation[ item.getName() ] ) {
                } );
 
                // Build result
-               if ( this.getType() === 'send_unselected_if_any' ) {
+               if (
+                       this.getType() === 'send_unselected_if_any' ||
+                       this.getType() === 'boolean'
+               ) {
                        // First, check if any of the items are selected at all.
                        // If none is selected, we're treating it as if they are
                        // all false
                        // Go over the items and define the correct values
                        $.each( filterRepresentation, function ( name, value ) {
                                // We must store all parameter values as strings '0' or '1'
-                               result[ filterParamNames[ name ] ] = areAnySelected ?
-                                       String( Number( !value ) ) :
-                                       '0';
+                               if ( model.getType() === 'send_unselected_if_any' ) {
+                                       result[ filterParamNames[ name ] ] = areAnySelected ?
+                                               String( Number( !value ) ) :
+                                               '0';
+                               } else if ( model.getType() === 'boolean' ) {
+                                       // Representation is straight-forward and direct from
+                                       // the parameter value to the filter state
+                                       result[ filterParamNames[ name ] ] = String( Number( !!value ) );
+                               }
                        } );
                } else if ( this.getType() === 'string_options' ) {
                        values = [];
         * Get the filter representation this group would provide
         * based on given parameter states.
         *
-        * @param {Object|string} [paramRepresentation] An object defining a parameter
+        * @param {Object} [paramRepresentation] An object defining a parameter
         *  state to translate the filter state from. If not given, an object
         *  representing all filters as falsey is returned; same as if the parameter
         *  given were an empty object, or had some of the filters missing.
         * @return {Object} Filter representation
         */
        mw.rcfilters.dm.FilterGroup.prototype.getFilterRepresentation = function ( paramRepresentation ) {
-               var areAnySelected, paramValues, defaultValue, item,
+               var areAnySelected, paramValues, defaultValue, item, currentValue,
                        oneWasSelected = false,
+                       defaultParams = this.getDefaultParams(),
+                       defaultFilters = this.getDefaultFilters(),
+                       expandedParams = $.extend( true, {}, paramRepresentation ),
                        model = this,
                        paramToFilterMap = {},
                        result = {};
 
-               if ( this.getType() === 'send_unselected_if_any' ) {
-                       paramRepresentation = paramRepresentation || {};
-                       // Expand param representation to include all filters in the group
+               paramRepresentation = paramRepresentation || {};
+               if (
+                       this.getType() === 'send_unselected_if_any' ||
+                       this.getType() === 'boolean'
+               ) {
+                       // Go over param representation; map and check for selections
                        this.getItems().forEach( function ( filterItem ) {
                                var paramName = filterItem.getParamName();
 
-                               paramRepresentation[ paramName ] = paramRepresentation[ paramName ] || '0';
+                               expandedParams[ paramName ] = paramRepresentation[ paramName ] || '0';
                                paramToFilterMap[ paramName ] = filterItem;
 
                                if ( Number( paramRepresentation[ filterItem.getParamName() ] ) ) {
                                }
                        } );
 
-                       $.each( paramRepresentation, function ( paramName, paramValue ) {
-                               var filterItem = paramToFilterMap[ paramName ];
-
-                               // Flip the definition between the parameter
-                               // state and the filter state
-                               // This is what the 'toggleSelected' value of the filter is
-                               result[ filterItem.getName() ] = areAnySelected ?
-                                       !Number( paramValue ) :
-                                       // Otherwise, there are no selected items in the
-                                       // group, which means the state is false
-                                       false;
+                       $.each( expandedParams, function ( paramName, paramValue ) {
+                               var value = paramValue,
+                                       filterItem = paramToFilterMap[ paramName ];
+
+                               if ( model.getType() === 'send_unselected_if_any' ) {
+                                       // Flip the definition between the parameter
+                                       // state and the filter state
+                                       // This is what the 'toggleSelected' value of the filter is
+                                       result[ filterItem.getName() ] = areAnySelected ?
+                                               !Number( paramValue ) :
+                                               // Otherwise, there are no selected items in the
+                                               // group, which means the state is false
+                                               false;
+                               } else if ( model.getType() === 'boolean' ) {
+                                       // Straight-forward definition of state
+                                       if (
+                                               filterItem.isUsingDefaultAsBaseValue() &&
+                                               paramRepresentation[ filterItem.getParamName() ] === undefined
+                                       ) {
+                                               value = defaultParams[ filterItem.getParamName() ];
+                                       }
+                                       result[ filterItem.getName() ] = !!Number( value );
+                               }
                        } );
                } else if ( this.getType() === 'string_options' ) {
-                       paramRepresentation = paramRepresentation || '';
+                       currentValue = paramRepresentation[ this.getName() ] || '';
 
                        // Normalize the given parameter values
                        paramValues = mw.rcfilters.utils.normalizeParamOptions(
                                // Given
-                               paramRepresentation.split(
+                               currentValue.split(
                                        this.getSeparator()
                                ),
                                // Allowed values
                } else if ( this.getType() === 'single_option' ) {
                        // There is parameter that fits a single filter and if not, get the default
                        this.getItems().forEach( function ( filterItem ) {
-                               result[ filterItem.getName() ] = filterItem.getParamName() === paramRepresentation;
-                               oneWasSelected = oneWasSelected || filterItem.getParamName() === paramRepresentation;
+                               var selected = false;
+
+                               if (
+                                       filterItem.isUsingDefaultAsBaseValue() &&
+                                       paramRepresentation[ model.getName() ] === undefined
+                               ) {
+                                       selected = !!Number( paramRepresentation[ model.getName() ] );
+                               } else {
+                                       selected = filterItem.getParamName() === paramRepresentation[ model.getName() ];
+                               }
+                               result[ filterItem.getName() ] = selected;
+                               oneWasSelected = oneWasSelected || selected;
                        } );
                }
 
                // Go over result and make sure all filters are represented.
                // If any filters are missing, they will get a falsey value
                this.getItems().forEach( function ( filterItem ) {
-                       result[ filterItem.getName() ] = !!result[ filterItem.getName() ];
+                       if (
+                               (
+                                       // This setting can only be applied to these groups
+                                       // the other groups are way too complex for that
+                                       model.getType() === 'single_option' ||
+                                       model.getType() === 'boolean'
+                               ) &&
+                               result[ filterItem.getName() ] === undefined &&
+                               filterItem.isUsingDefaultAsBaseValue()
+                       ) {
+                               result[ filterItem.getName() ] = !!defaultFilters[ filterItem.getName() ];
+                       }
                        oneWasSelected = oneWasSelected || !!result[ filterItem.getName() ];
                } );
 
index 06fa0aa..a602c32 100644 (file)
 
                // Create a map between known parameters and their models
                $.each( this.groups, function ( group, groupModel ) {
-                       if ( groupModel.getType() === 'send_unselected_if_any' ) {
+                       if (
+                               groupModel.getType() === 'send_unselected_if_any' ||
+                               groupModel.getType() === 'boolean'
+                       ) {
                                // Individual filters
                                groupModel.getItems().forEach( function ( filterItem ) {
                                        model.parameterMap[ filterItem.getParamName() ] = filterItem;
                //    group2: "param4|param5"
                // }
                $.each( params, function ( paramName, paramValue ) {
-                       var itemOrGroup = model.parameterMap[ paramName ];
-
-                       if ( itemOrGroup instanceof mw.rcfilters.dm.FilterItem ) {
-                               groupMap[ itemOrGroup.getGroupName() ] = groupMap[ itemOrGroup.getGroupName() ] || {};
-                               groupMap[ itemOrGroup.getGroupName() ][ itemOrGroup.getParamName() ] = paramValue;
-                       } else if ( itemOrGroup instanceof mw.rcfilters.dm.FilterGroup ) {
-                               // This parameter represents a group (values are the filters)
-                               // this is equivalent to checking if the group is 'string_options'
-                               groupMap[ itemOrGroup.getName() ] = groupMap[ itemOrGroup.getName() ] || {};
-                               groupMap[ itemOrGroup.getName() ] = paramValue;
+                       var groupName,
+                               itemOrGroup = model.parameterMap[ paramName ];
+
+                       if ( itemOrGroup ) {
+                               groupName = itemOrGroup instanceof mw.rcfilters.dm.FilterItem ?
+                                       itemOrGroup.getGroupName() : itemOrGroup.getName();
+
+                               groupMap[ groupName ] = groupMap[ groupName ] || {};
+                               groupMap[ groupName ][ paramName ] = paramValue;
                        }
                } );
 
                // Check if there are either any selected items or any items
                // that have highlight enabled
                return !this.getItems().some( function ( filterItem ) {
-                       return filterItem.isSelected() || filterItem.isHighlighted();
+                       return !filterItem.getGroupModel().isHidden() && ( filterItem.isSelected() || filterItem.isHighlighted() );
                } );
        };
 
index aa82e21..54a4dbe 100644 (file)
@@ -32,6 +32,7 @@
                this.namePrefix = config.namePrefix || 'item_';
                this.name = this.namePrefix + param;
 
+               this.useDefaultAsBaseValue = !!config.useDefaultAsBaseValue;
                this.label = config.label || this.name;
                this.labelPrefixKey = config.labelPrefixKey;
                this.description = config.description || '';
                return this.identifiers;
        };
 
+       /**
+        * Check whether the item uses its default state as a base value
+        *
+        * @return {boolean} Use default as base value
+        */
+       mw.rcfilters.dm.ItemModel.prototype.isUsingDefaultAsBaseValue = function () {
+               return this.useDefaultAsBaseValue;
+       };
+
        /**
         * Toggle the highlight feature on and off for this filter.
         * It only works if highlight is supported for this filter.
index 6cd2d0b..9da3f8c 100644 (file)
@@ -39,6 +39,7 @@
 
                        // Set as ready
                        $( '.rcfilters-head' ).addClass( 'mw-rcfilters-ui-ready' );
+                       $( '.rcfilters-spinner' ).detach();
 
                        $( 'a.mw-helplink' ).attr(
                                'href',
index 6277fd9..305f3f9 100644 (file)
@@ -1,5 +1,5 @@
 // Corrections for the standard special page
-.client-js{
+.client-js {
        .rcoptions {
                border: 0;
                border-bottom: 1px solid #a2a9b1;
 
        .rcfilters-head {
                min-height: 310px;
-               &:not( .mw-rcfilters-ui-ready ) {
-                       /* @embed */
-                       background-image: url( ../images/pending.gif );
-                       margin: 0;
 
-                       * {
-                               visibility: hidden;
-                       }
+               &:not( .mw-rcfilters-ui-ready ) {
+                       opacity: 0.5;
+                       pointer-events: none;
                }
        }
 
                // message of our own
                display: none;
        }
+
+       .rcfilters-spinner {
+               margin: -2em auto 0;
+               width: 70px;
+               opacity: 0.8;
+               display: block;
+               white-space: nowrap;
+
+               & .rcfilters-spinner-bounce,
+               &:before,
+               &:after {
+                       content: '';
+                       display: inline-block;
+                       width: 12px;
+                       height: 12px;
+                       background-color: #c8ccd1;
+                       border-radius: 100%;
+                       animation: rcfiltersBouncedelay 1.5s infinite ease-in-out;
+                       animation-fill-mode: both;
+                       animation-delay: -0.16s;
+               }
+
+               &:before {
+                       animation-delay: -0.33s;
+               }
+
+               &:after {
+                       animation-delay: 0s;
+               }
+
+       }
 }
 
 .mw-rcfilters-staticfilters-selected {
        font-weight: bold;
 }
+
+@keyframes rcfiltersBouncedelay {
+       0%,
+       100%,
+       80% {
+               transform: scale( 0.7 );
+       }
+       40% {
+               transform: scale( 1 );
+               background-color: #a2a9b1;
+       }
+}
index e8f504a..04f4174 100644 (file)
@@ -7,6 +7,7 @@
                &:after {
                        content: '';
                        mix-blend-mode: screen;
+                       pointer-events: none;
                        position: absolute;
                        width: 1.875em;
                        height: 1.875em;
index 6512f04..0e9e843 100644 (file)
                        this.$element.append( $message );
                } else {
                        this.$changesListContent = $changesListContent;
-                       this.$element.empty().append( this.$changesListContent );
+                       if ( !isInitialDOM ) {
+                               this.$element.empty().append( this.$changesListContent );
+                       }
                        // Set up highlight containers
                        this.setupHighlightContainers( this.$element );
 
index 5678a80..51b9815 100644 (file)
@@ -9,12 +9,15 @@
 mediaWiki.widgets.visibleByteLimit = function ( textInputWidget, limit ) {
        limit = limit || +textInputWidget.$input.attr( 'maxlength' );
 
+       // Temporarily disabled whilst upstream bug is fixed; T169982
+       /*
        function updateCount() {
                textInputWidget.setLabel( ( limit - $.byteLength( textInputWidget.getValue() ) ).toString() );
        }
        textInputWidget.on( 'change', updateCount );
        // Initialise value
        updateCount();
+       */
 
        // Actually enforce limit
        textInputWidget.$input.byteLimit( limit );
diff --git a/resources/src/mediawiki/mediawiki.hlist-allskins.less b/resources/src/mediawiki/mediawiki.hlist-allskins.less
new file mode 100644 (file)
index 0000000..d7071e4
--- /dev/null
@@ -0,0 +1,21 @@
+.hlist {
+       dl,
+       ol,
+       ul {
+               margin: 0;
+               padding: 0;
+
+               dl,
+               ol,
+               ul {
+                       display: inline;
+               }
+       }
+
+       dd,
+       dt,
+       li {
+               margin: 0;
+               display: inline;
+       }
+}
index c0788a4..2663d87 100644 (file)
@@ -2,31 +2,6 @@
  * Stylesheet for mediawiki.hlist module
  * @author [[User:Edokter]]
  */
-.hlist dl,
-.hlist ol,
-.hlist ul {
-       margin: 0;
-       padding: 0;
-}
-/* Display list items inline */
-.hlist dd,
-.hlist dt,
-.hlist li {
-       margin: 0;
-       display: inline;
-}
-/* Display nested lists inline */
-.hlist dl dl,
-.hlist dl ol,
-.hlist dl ul,
-.hlist ol dl,
-.hlist ol ol,
-.hlist ol ul,
-.hlist ul dl,
-.hlist ul ol,
-.hlist ul ul {
-       display: inline;
-}
 /* Generate interpuncts */
 .hlist dt:after {
        content: ':';
index 017d9fb..dffa863 100644 (file)
@@ -10,7 +10,7 @@
        };
        OO.ui.isMobile = function () {
                if ( isMobile === undefined ) {
-                       isMobile = mw.config.get( 'skin' ) === 'minerva';
+                       isMobile = !!mw.config.get( 'wgMFMode' );
                }
                return isMobile;
        };
index fff1eec..feed77f 100644 (file)
@@ -168,7 +168,7 @@ class ParserTestRunner {
                global $wgParserTestFiles;
 
                // Add core test files
-               $files = array_map( function( $item ) {
+               $files = array_map( function ( $item ) {
                        return __DIR__ . "/$item";
                }, self::$coreTestFiles );
 
index e8ccd9d..f8ba742 100644 (file)
@@ -28364,3 +28364,25 @@ wgRawHtml=1
 <style data-mw-foobar="baz">.foo::after { content: "<bar>"; }</style>
 </div>
 !! end
+
+!! test
+Decoding of HTML entities in headings and links for IDs and link fragments (T103714)
+!! wikitext
+== A&B&amp;C&amp;amp;D&amp;amp;amp;E ==
+[[#A&B&amp;C&amp;amp;D&amp;amp;amp;E]]
+!! html/php
+<h2><span class="mw-headline" id="A.26B.26C.26amp.3BD.26amp.3Bamp.3BE">A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p><a href="#A.26B.26C.26D.26amp.3BE">#A&amp;B&amp;C&amp;amp;D&amp;amp;amp;E</a>
+</p>
+!! end
+
+!! test
+Decoding of HTML entities in indicator names for IDs (T104196)
+!! options
+showindicators
+!! wikitext
+<indicator name="1&2&amp;3&amp;amp;4&amp;amp;amp;5">Indicator</indicator>
+!! html/php
+1&2&3&amp;4&amp;amp;5=Indicator
+
+!! end
index 31cfa70..215d292 100644 (file)
@@ -1087,10 +1087,15 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                        $page->doEditContent(
                                new WikitextContent( 'UTContent' ),
                                'UTPageSummary',
-                               EDIT_NEW,
+                               EDIT_NEW | EDIT_SUPPRESS_RC,
                                false,
                                $user
                        );
+                       // an edit always attempt to purge backlink links such as history
+                       // pages. That is unneccessary.
+                       JobQueueGroup::singleton()->get( 'htmlCacheUpdate' )->delete();
+                       // WikiPages::doEditUpdates randomly adds RC purges
+                       JobQueueGroup::singleton()->get( 'recentChangesUpdate' )->delete();
 
                        // doEditContent() probably started the session via
                        // User::loadFromSession(). Close it now.
index a8a8f4d..d8f89fb 100644 (file)
@@ -94,6 +94,7 @@ class ResourceLoaderTestModule extends ResourceLoaderModule {
        protected $isKnownEmpty = false;
        protected $type = ResourceLoaderModule::LOAD_GENERAL;
        protected $targets = [ 'phpunit' ];
+       protected $shouldEmbed = null;
 
        public function __construct( $options = [] ) {
                foreach ( $options as $key => $value ) {
@@ -143,6 +144,10 @@ class ResourceLoaderTestModule extends ResourceLoaderModule {
                return $this->isKnownEmpty;
        }
 
+       public function shouldEmbedModule( ResourceLoaderContext $context ) {
+               return $this->shouldEmbed !== null ? $this->shouldEmbed : parent::shouldEmbedModule( $context );
+       }
+
        public function enableModuleContentVersion() {
                return true;
        }
index 76a4f51..802f9b1 100644 (file)
@@ -39,7 +39,7 @@ class DeprecatedGlobalTest extends MediaWikiTestCase {
                global $wgDummyLazy;
 
                $called = false;
-               $factory = function() use ( &$called ) {
+               $factory = function () use ( &$called ) {
                        $called = true;
                        return new HashBagOStuff();
                };
index 89416f2..ae858f5 100644 (file)
@@ -23,7 +23,7 @@ class GitInfoTest extends MediaWikiTestCase {
        public function testValidJsonData() {
                global $IP;
 
-               $this->assertValidGitInfo( new GitInfo( "$IP/testValidJsonData") );
+               $this->assertValidGitInfo( new GitInfo( "$IP/testValidJsonData" ) );
                $this->assertValidGitInfo( new GitInfo( __DIR__ . "/../data/gitinfo/extension" ) );
        }
 
index ada516d..d78c1e7 100644 (file)
@@ -131,10 +131,9 @@ class PreferencesTest extends MediaWikiTestCase {
                        ->method( 'getConfig' )
                        ->willReturn( $configMock );
 
-               $this->setTemporaryHook( 'PreferencesFormPreSave', function(
+               $this->setTemporaryHook( 'PreferencesFormPreSave', function (
                        $formData, $form, $user, &$result, $oldUserOptions )
                        use ( $newOptions, $oldOptions, $userMock ) {
-
                        $this->assertSame( $userMock, $user );
                        foreach ( $newOptions as $option => $value ) {
                                $this->assertSame( $value, $formData[ $option ] );
index abcf1d4..6d093b0 100644 (file)
@@ -343,6 +343,41 @@ class SanitizerTest extends MediaWikiTestCase {
                ];
        }
 
+       /**
+        * Test Sanitizer::escapeId
+        *
+        * @dataProvider provideEscapeId
+        * @covers Sanitizer::escapeId
+        */
+       public function testEscapeId( $input, $output ) {
+               $this->assertEquals(
+                       $output,
+                       Sanitizer::escapeId( $input, [ 'noninitial', 'legacy' ] )
+               );
+       }
+
+       public static function provideEscapeId() {
+               return [
+                       [ '+', '.2B' ],
+                       [ '&', '.26' ],
+                       [ '=', '.3D' ],
+                       [ ':', ':' ],
+                       [ ';', '.3B' ],
+                       [ '@', '.40' ],
+                       [ '$', '.24' ],
+                       [ '-_.', '-_.' ],
+                       [ '!', '.21' ],
+                       [ '*', '.2A' ],
+                       [ '/', '.2F' ],
+                       [ '[]', '.5B.5D' ],
+                       [ '<>', '.3C.3E' ],
+                       [ '\'', '.27' ],
+                       [ '§', '.C2.A7' ],
+                       [ 'Test:A & B/Here', 'Test:A_.26_B.2FHere' ],
+                       [ 'A&B&amp;C&amp;amp;D&amp;amp;amp;E', 'A.26B.26C.26amp.3BD.26amp.3Bamp.3BE' ],
+               ];
+       }
+
        /**
         * Test escapeIdReferenceList for consistency with escapeId
         *
diff --git a/tests/phpunit/includes/SiteStatsTest.php b/tests/phpunit/includes/SiteStatsTest.php
new file mode 100644 (file)
index 0000000..ea476a7
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+class SiteStatsTest extends MediaWikiTestCase {
+
+       /**
+        * @covers SiteStats::jobs
+        */
+       function testJobsCountGetCached() {
+               $this->setService( 'MainWANObjectCache',
+                       new WANObjectCache( [ 'cache' => new HashBagOStuff() ] ) );
+               $cache = \MediaWiki\MediaWikiServices::getInstance()->getMainWANObjectCache();
+               $jobq = JobQueueGroup::singleton();
+
+               $jobq->push( new NullJob( Title::newMainPage(), [] ) );
+               $this->assertEquals( 1, SiteStats::jobs(),
+                        'A single job enqueued bumps jobscount stat to 1' );
+
+               $jobq->push( new NullJob( Title::newMainPage(), [] ) );
+               $this->assertEquals( 1, SiteStats::jobs(),
+                       'SiteStats::jobs() count does not reflect addition ' .
+                       'of a second job (cached)'
+               );
+
+               $jobq->get( 'null' )->delete();  // clear jobqueue
+               $this->assertEquals( 0, $jobq->get( 'null' )->getSize(),
+                       'Job queue for NullJob has been cleaned' );
+
+               $cache->delete( $cache->makeKey( 'SiteStats', 'jobscount' ) );
+               $this->assertEquals( 1, SiteStats::jobs(),
+                       'jobs count is kept in process cache' );
+
+               $cache->clearProcessCache();
+               $this->assertEquals( 0, SiteStats::jobs() );
+       }
+
+}
index a2c0d39..d47481c 100644 (file)
@@ -526,6 +526,10 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
         * @param array $expect
         */
        public function testGetMessageFromException( $exception, $options, $expect ) {
+               if ( $exception instanceof UsageException ) {
+                       $this->hideDeprecated( 'UsageException::getMessageArray' );
+               }
+
                $result = new ApiResult( 8388608 );
                $formatter = new ApiErrorFormatter( $result, Language::factory( 'en' ), 'html', false );
 
@@ -571,6 +575,12 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
        }
 
        public static function provideGetMessageFromException() {
+               MediaWiki\suppressWarnings();
+               $usageException = new UsageException(
+                       '<b>Something broke!</b>', 'ue-code', 0, [ 'xxx' => 'yyy', 'baz' => 23 ]
+               );
+               MediaWiki\restoreWarnings();
+
                return [
                        'Normal exception' => [
                                new RuntimeException( '<b>Something broke!</b>' ),
@@ -591,7 +601,7 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                                ]
                        ],
                        'UsageException' => [
-                               new UsageException( '<b>Something broke!</b>', 'ue-code', 0, [ 'xxx' => 'yyy', 'baz' => 23 ] ),
+                               $usageException,
                                [],
                                [
                                        'text' => '&#60;b&#62;Something broke!&#60;/b&#62;',
@@ -600,7 +610,7 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                                ]
                        ],
                        'UsageException, wrapped' => [
-                               new UsageException( '<b>Something broke!</b>', 'ue-code', 0, [ 'xxx' => 'yyy', 'baz' => 23 ] ),
+                               $usageException,
                                [ 'wrap' => 'parentheses', 'code' => 'some-code', 'data' => [ 'foo' => 'bar', 'baz' => 42 ] ],
                                [
                                        'text' => '(&#60;b&#62;Something broke!&#60;/b&#62;)',
index ea33a9e..ad334e9 100644 (file)
@@ -500,6 +500,10 @@ class ApiMainTest extends ApiTestCase {
                        MWExceptionHandler::getRedactedTraceAsString( $dbex )
                )->inLanguage( 'en' )->useDatabase( false )->text();
 
+               MediaWiki\suppressWarnings();
+               $usageEx = new UsageException( 'Usage exception!', 'ue', 0, [ 'foo' => 'bar' ] );
+               MediaWiki\restoreWarnings();
+
                $apiEx1 = new ApiUsageException( null,
                        StatusValue::newFatal( new ApiRawMessage( 'An error', 'sv-error1' ) ) );
                TestingAccessWrapper::newFromObject( $apiEx1 )->modulePath = 'foo+bar';
@@ -545,7 +549,7 @@ class ApiMainTest extends ApiTestCase {
                                ]
                        ],
                        [
-                               new UsageException( 'Usage exception!', 'ue', 0, [ 'foo' => 'bar' ] ),
+                               $usageEx,
                                [ 'existing-error', 'ue' ],
                                [
                                        'warnings' => [
index ba38128..608d8d9 100644 (file)
@@ -47,7 +47,7 @@ class ConfigFactoryTest extends MediaWikiTestCase {
        }
 
        /**
-        * @covers ConfigFactory::register
+        * @covers ConfigFactory::salvage
         */
        public function testSalvage() {
                $oldFactory = new ConfigFactory();
@@ -83,7 +83,7 @@ class ConfigFactoryTest extends MediaWikiTestCase {
        }
 
        /**
-        * @covers ConfigFactory::register
+        * @covers ConfigFactory::getConfigNames
         */
        public function testGetConfigNames() {
                $factory = new ConfigFactory();
@@ -96,7 +96,7 @@ class ConfigFactoryTest extends MediaWikiTestCase {
        /**
         * @covers ConfigFactory::makeConfig
         */
-       public function testMakeConfig() {
+       public function testMakeConfigWithCallback() {
                $factory = new ConfigFactory();
                $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
 
@@ -105,6 +105,16 @@ class ConfigFactoryTest extends MediaWikiTestCase {
                $this->assertSame( $conf, $factory->makeConfig( 'unittest' ) );
        }
 
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigWithObject() {
+               $factory = new ConfigFactory();
+               $conf = new HashConfig();
+               $factory->register( 'test', $conf );
+               $this->assertSame( $conf, $factory->makeConfig( 'test' ) );
+       }
+
        /**
         * @covers ConfigFactory::makeConfig
         */
index 763bfa8..19cffa2 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Wikimedia\TestingAccessWrapper;
+
 class EtcConfigTest extends PHPUnit_Framework_TestCase {
 
        private function createConfigMock( array $options = [] ) {
@@ -359,4 +361,155 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
 
                $this->assertSame( 'from-cache-expired', $mock->get( 'known' ) );
        }
+
+       public static function provideFetchFromServer() {
+               return [
+                       '200 OK - Success' => [
+                               'http' => [
+                                       'code' => 200,
+                                       'reason' => 'OK',
+                                       'headers' => [],
+                                       'body' => json_encode( [ 'node' => [ 'nodes' => [
+                                               [
+                                                       'key' => '/example/foo',
+                                                       'value' => json_encode( [ 'val' => true ] )
+                                               ],
+                                       ] ] ] ),
+                                       'error' => '',
+                               ],
+                               'expect' => [
+                                       [ 'foo' => true ], // data
+                                       null,
+                                       false // retry
+                               ],
+                       ],
+                       '200 OK - Skip dir' => [
+                               'http' => [
+                                       'code' => 200,
+                                       'reason' => 'OK',
+                                       'headers' => [],
+                                       'body' => json_encode( [ 'node' => [ 'nodes' => [
+                                               [
+                                                       'key' => '/example/foo',
+                                                       'value' => json_encode( [ 'val' => true ] )
+                                               ],
+                                               [
+                                                       'key' => '/example/sub',
+                                                       'dir' => true
+                                               ],
+                                               [
+                                                       'key' => '/example/bar',
+                                                       'value' => json_encode( [ 'val' => false ] )
+                                               ],
+                                       ] ] ] ),
+                                       'error' => '',
+                               ],
+                               'expect' => [
+                                       [ 'foo' => true, 'bar' => false ], // data
+                                       null,
+                                       false // retry
+                               ],
+                       ],
+                       '200 OK - Bad value' => [
+                               'http' => [
+                                       'code' => 200,
+                                       'reason' => 'OK',
+                                       'headers' => [],
+                                       'body' => json_encode( [ 'node' => [ 'nodes' => [
+                                               [
+                                                       'key' => '/example/foo',
+                                                       'value' => ';"broken{value'
+                                               ]
+                                       ] ] ] ),
+                                       'error' => '',
+                               ],
+                               'expect' => [
+                                       null, // data
+                                       "Failed to parse value for 'foo'.",
+                                       false // retry
+                               ],
+                       ],
+                       '200 OK - Empty node list' => [
+                               'http' => [
+                                       'code' => 200,
+                                       'reason' => 'OK',
+                                       'headers' => [],
+                                       'body' => '{"node":{"nodes":[]}}',
+                                       'error' => '',
+                               ],
+                               'expect' => [
+                                       [], // data
+                                       null,
+                                       false // retry
+                               ],
+                       ],
+                       '200 OK - Invalid JSON' => [
+                               'http' => [
+                                       'code' => 200,
+                                       'reason' => 'OK',
+                                       'headers' => [ 'content-length' => 0 ],
+                                       'body' => '',
+                                       'error' => '(curl error: no status set)',
+                               ],
+                               'expect' => [
+                                       null, // data
+                                       "Unexpected JSON response; missing 'nodes' list.",
+                                       false // retry
+                               ],
+                       ],
+                       '404 Not Found' => [
+                               'http' => [
+                                       'code' => 404,
+                                       'reason' => 'Not Found',
+                                       'headers' => [ 'content-length' => 0 ],
+                                       'body' => '',
+                                       'error' => '',
+                               ],
+                               'expect' => [
+                                       null, // data
+                                       'HTTP 404 (Not Found)',
+                                       false // retry
+                               ],
+                       ],
+                       '400 Bad Request - custom error' => [
+                               'http' => [
+                                       'code' => 400,
+                                       'reason' => 'Bad Request',
+                                       'headers' => [ 'content-length' => 0 ],
+                                       'body' => '',
+                                       'error' => 'No good reason',
+                               ],
+                               'expect' => [
+                                       null, // data
+                                       'No good reason',
+                                       true // retry
+                               ],
+                       ],
+               ];
+       }
+
+       /**
+        * @covers EtcdConfig::fetchAllFromEtcdServer
+        * @covers EtcdConfig::unserialize
+        * @dataProvider provideFetchFromServer
+        */
+       public function testFetchFromServer( array $httpResponse, array $expected ) {
+               $http = $this->getMockBuilder( MultiHttpClient::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $http->expects( $this->once() )->method( 'run' )
+                       ->willReturn( array_values( $httpResponse ) );
+
+               $conf = $this->getMockBuilder( EtcdConfig::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               // Access for protected member and method
+               $conf = TestingAccessWrapper::newFromObject( $conf );
+               $conf->http = $http;
+
+               $this->assertSame(
+                       $expected,
+                       $conf->fetchAllFromEtcdServer( 'etcd-tcp.example.net' )
+               );
+       }
 }
index b9ce997..d0996e3 100644 (file)
@@ -103,16 +103,16 @@ more stuff
 
        public static function dataGetSection() {
                return [
-                       [ WikitextContentTest::$sections,
+                       [ self::$sections,
                                "0",
                                "Intro"
                        ],
-                       [ WikitextContentTest::$sections,
+                       [ self::$sections,
                                "2",
                                "== test ==
 just a test"
                        ],
-                       [ WikitextContentTest::$sections,
+                       [ self::$sections,
                                "8",
                                false
                        ],
@@ -138,38 +138,38 @@ just a test"
 
        public static function dataReplaceSection() {
                return [
-                       [ WikitextContentTest::$sections,
+                       [ self::$sections,
                                "0",
                                "No more",
                                null,
-                               trim( preg_replace( '/^Intro/sm', 'No more', WikitextContentTest::$sections ) )
+                               trim( preg_replace( '/^Intro/sm', 'No more', self::$sections ) )
                        ],
-                       [ WikitextContentTest::$sections,
+                       [ self::$sections,
                                "",
                                "No more",
                                null,
                                "No more"
                        ],
-                       [ WikitextContentTest::$sections,
+                       [ self::$sections,
                                "2",
                                "== TEST ==\nmore fun",
                                null,
                                trim( preg_replace(
                                        '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==",
-                                       WikitextContentTest::$sections
+                                       self::$sections
                                ) )
                        ],
-                       [ WikitextContentTest::$sections,
+                       [ self::$sections,
                                "8",
                                "No more",
                                null,
-                               WikitextContentTest::$sections
+                               self::$sections
                        ],
-                       [ WikitextContentTest::$sections,
+                       [ self::$sections,
                                "new",
                                "No more",
                                "New",
-                               trim( WikitextContentTest::$sections ) . "\n\n\n== New ==\n\nNo more"
+                               trim( self::$sections ) . "\n\n\n== New ==\n\nNo more"
                        ],
                ];
        }
diff --git a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php
deleted file mode 100644 (file)
index 3dc810c..0000000
+++ /dev/null
@@ -1,366 +0,0 @@
-<?php
-/**
- * Holds tests for DatabaseMysqlBase MediaWiki class.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @author Antoine Musso
- * @copyright © 2013 Antoine Musso
- * @copyright © 2013 Wikimedia Foundation and contributors
- */
-
-use Wikimedia\Rdbms\TransactionProfiler;
-use Wikimedia\Rdbms\DatabaseDomain;
-use Wikimedia\Rdbms\MySQLMasterPos;
-use Wikimedia\Rdbms\DatabaseMysqlBase;
-
-/**
- * Fake class around abstract class so we can call concrete methods.
- */
-class FakeDatabaseMysqlBase extends DatabaseMysqlBase {
-       // From Database
-       function __construct() {
-               $this->profiler = new ProfilerStub( [] );
-               $this->trxProfiler = new TransactionProfiler();
-               $this->cliMode = true;
-               $this->connLogger = new \Psr\Log\NullLogger();
-               $this->queryLogger = new \Psr\Log\NullLogger();
-               $this->errorLogger = function ( Exception $e ) {
-                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
-               };
-               $this->currentDomain = DatabaseDomain::newUnspecified();
-       }
-
-       protected function closeConnection() {
-       }
-
-       protected function doQuery( $sql ) {
-       }
-
-       // From DatabaseMysql
-       protected function mysqlConnect( $realServer ) {
-       }
-
-       protected function mysqlSetCharset( $charset ) {
-       }
-
-       protected function mysqlFreeResult( $res ) {
-       }
-
-       protected function mysqlFetchObject( $res ) {
-       }
-
-       protected function mysqlFetchArray( $res ) {
-       }
-
-       protected function mysqlNumRows( $res ) {
-       }
-
-       protected function mysqlNumFields( $res ) {
-       }
-
-       protected function mysqlFieldName( $res, $n ) {
-       }
-
-       protected function mysqlFieldType( $res, $n ) {
-       }
-
-       protected function mysqlDataSeek( $res, $row ) {
-       }
-
-       protected function mysqlError( $conn = null ) {
-       }
-
-       protected function mysqlFetchField( $res, $n ) {
-       }
-
-       protected function mysqlRealEscapeString( $s ) {
-       }
-
-       function insertId() {
-       }
-
-       function lastErrno() {
-       }
-
-       function affectedRows() {
-       }
-
-       function getServerVersion() {
-       }
-}
-
-class DatabaseMysqlBaseTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideDiapers
-        * @covers DatabaseMysqlBase::addIdentifierQuotes
-        */
-       public function testAddIdentifierQuotes( $expected, $in ) {
-               $db = new FakeDatabaseMysqlBase();
-               $quoted = $db->addIdentifierQuotes( $in );
-               $this->assertEquals( $expected, $quoted );
-       }
-
-       /**
-        * Feeds testAddIdentifierQuotes
-        *
-        * Named per T22281 convention.
-        */
-       function provideDiapers() {
-               return [
-                       // Format: expected, input
-                       [ '``', '' ],
-
-                       // Yeah I really hate loosely typed PHP idiocies nowadays
-                       [ '``', null ],
-
-                       // Dear codereviewer, guess what addIdentifierQuotes()
-                       // will return with thoses:
-                       [ '``', false ],
-                       [ '`1`', true ],
-
-                       // We never know what could happen
-                       [ '`0`', 0 ],
-                       [ '`1`', 1 ],
-
-                       // Whatchout! Should probably use something more meaningful
-                       [ "`'`", "'" ],  # single quote
-                       [ '`"`', '"' ],  # double quote
-                       [ '````', '`' ], # backtick
-                       [ '`’`', '’' ],  # apostrophe (look at your encyclopedia)
-
-                       // sneaky NUL bytes are lurking everywhere
-                       [ '``', "\0" ],
-                       [ '`xyzzy`', "\0x\0y\0z\0z\0y\0" ],
-
-                       // unicode chars
-                       [
-                               self::createUnicodeString( '`\u0001a\uFFFFb`' ),
-                               self::createUnicodeString( '\u0001a\uFFFFb' )
-                       ],
-                       [
-                               self::createUnicodeString( '`\u0001\uFFFF`' ),
-                               self::createUnicodeString( '\u0001\u0000\uFFFF\u0000' )
-                       ],
-                       [ '`☃`', '☃' ],
-                       [ '`メインページ`', 'メインページ' ],
-                       [ '`Басты_бет`', 'Басты_бет' ],
-
-                       // Real world:
-                       [ '`Alix`', 'Alix' ],  # while( ! $recovered ) { sleep(); }
-                       [ '`Backtick: ```', 'Backtick: `' ],
-                       [ '`This is a test`', 'This is a test' ],
-               ];
-       }
-
-       private static function createUnicodeString( $str ) {
-               return json_decode( '"' . $str . '"' );
-       }
-
-       function getMockForViews() {
-               $db = $this->getMockBuilder( 'DatabaseMysqli' )
-                       ->disableOriginalConstructor()
-                       ->setMethods( [ 'fetchRow', 'query' ] )
-                       ->getMock();
-
-               $db->method( 'query' )
-                       ->with( $this->anything() )
-                       ->willReturn( new FakeResultWrapper( [
-                               (object)[ 'Tables_in_' => 'view1' ],
-                               (object)[ 'Tables_in_' => 'view2' ],
-                               (object)[ 'Tables_in_' => 'myview' ]
-                       ] ) );
-
-               return $db;
-       }
-       /**
-        * @covers DatabaseMysqlBase::listViews
-        */
-       function testListviews() {
-               $db = $this->getMockForViews();
-
-               $this->assertEquals( [ 'view1', 'view2', 'myview' ],
-                       $db->listViews() );
-
-               // Prefix filtering
-               $this->assertEquals( [ 'view1', 'view2' ],
-                       $db->listViews( 'view' ) );
-               $this->assertEquals( [ 'myview' ],
-                       $db->listViews( 'my' ) );
-               $this->assertEquals( [],
-                       $db->listViews( 'UNUSED_PREFIX' ) );
-               $this->assertEquals( [ 'view1', 'view2', 'myview' ],
-                       $db->listViews( '' ) );
-       }
-
-       /**
-        * @dataProvider provideComparePositions
-        */
-       function testHasReached( MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos, $match ) {
-               if ( $match ) {
-                       $this->assertTrue( $lowerPos->channelsMatch( $higherPos ) );
-
-                       $this->assertTrue( $higherPos->hasReached( $lowerPos ) );
-                       $this->assertTrue( $higherPos->hasReached( $higherPos ) );
-                       $this->assertTrue( $lowerPos->hasReached( $lowerPos ) );
-                       $this->assertFalse( $lowerPos->hasReached( $higherPos ) );
-               } else { // channels don't match
-                       $this->assertFalse( $lowerPos->channelsMatch( $higherPos ) );
-
-                       $this->assertFalse( $higherPos->hasReached( $lowerPos ) );
-                       $this->assertFalse( $lowerPos->hasReached( $higherPos ) );
-               }
-       }
-
-       function provideComparePositions() {
-               return [
-                       // Binlog style
-                       [
-                               new MySQLMasterPos( 'db1034-bin.000976', '843431247' ),
-                               new MySQLMasterPos( 'db1034-bin.000976', '843431248' ),
-                               true
-                       ],
-                       [
-                               new MySQLMasterPos( 'db1034-bin.000976', '999' ),
-                               new MySQLMasterPos( 'db1034-bin.000976', '1000' ),
-                               true
-                       ],
-                       [
-                               new MySQLMasterPos( 'db1034-bin.000976', '999' ),
-                               new MySQLMasterPos( 'db1035-bin.000976', '1000' ),
-                               false
-                       ],
-                       // MySQL GTID style
-                       [
-                               new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:23' ),
-                               new MySQLMasterPos( 'db1-bin.2', '2', '3E11FA47-71CA-11E1-9E33-C80AA9429562:24' ),
-                               true
-                       ],
-                       [
-                               new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:99' ),
-                               new MySQLMasterPos( 'db1-bin.2', '2', '3E11FA47-71CA-11E1-9E33-C80AA9429562:100' ),
-                               true
-                       ],
-                       [
-                               new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:99' ),
-                               new MySQLMasterPos( 'db1-bin.2', '2', '1E11FA47-71CA-11E1-9E33-C80AA9429562:100' ),
-                               false
-                       ],
-                       // MariaDB GTID style
-                       [
-                               new MySQLMasterPos( 'db1-bin.2', '1', '255-11-23' ),
-                               new MySQLMasterPos( 'db1-bin.2', '2', '255-11-24' ),
-                               true
-                       ],
-                       [
-                               new MySQLMasterPos( 'db1-bin.2', '1', '255-11-99' ),
-                               new MySQLMasterPos( 'db1-bin.2', '2', '255-11-100' ),
-                               true
-                       ],
-                       [
-                               new MySQLMasterPos( 'db1-bin.2', '1', '255-11-999' ),
-                               new MySQLMasterPos( 'db1-bin.2', '2', '254-11-1000' ),
-                               false
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideChannelPositions
-        */
-       function testChannelsMatch( MySQLMasterPos $pos1, MySQLMasterPos $pos2, $matches ) {
-               $this->assertEquals( $matches, $pos1->channelsMatch( $pos2 ) );
-               $this->assertEquals( $matches, $pos2->channelsMatch( $pos1 ) );
-       }
-
-       function provideChannelPositions() {
-               return [
-                       [
-                               new MySQLMasterPos( 'db1034-bin.000876', '44' ),
-                               new MySQLMasterPos( 'db1034-bin.000976', '74' ),
-                               true
-                       ],
-                       [
-                               new MySQLMasterPos( 'db1052-bin.000976', '999' ),
-                               new MySQLMasterPos( 'db1052-bin.000976', '1000' ),
-                               true
-                       ],
-                       [
-                               new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
-                               new MySQLMasterPos( 'db1035-bin.000976', '10000' ),
-                               false
-                       ],
-                       [
-                               new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
-                               new MySQLMasterPos( 'trump2016.000976', '10000' ),
-                               false
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideLagAmounts
-        */
-       function testPtHeartbeat( $lag ) {
-               $db = $this->getMockBuilder( 'DatabaseMysqli' )
-                       ->disableOriginalConstructor()
-                       ->setMethods( [
-                               'getLagDetectionMethod', 'getHeartbeatData', 'getMasterServerInfo' ] )
-                       ->getMock();
-
-               $db->method( 'getLagDetectionMethod' )
-                       ->willReturn( 'pt-heartbeat' );
-
-               $db->method( 'getMasterServerInfo' )
-                       ->willReturn( [ 'serverId' => 172, 'asOf' => time() ] );
-
-               // Fake the current time.
-               list( $nowSecFrac, $nowSec ) = explode( ' ', microtime() );
-               $now = (float)$nowSec + (float)$nowSecFrac;
-               // Fake the heartbeat time.
-               // Work arounds for weak DataTime microseconds support.
-               $ptTime = $now - $lag;
-               $ptSec = (int)$ptTime;
-               $ptSecFrac = ( $ptTime - $ptSec );
-               $ptDateTime = new DateTime( "@$ptSec" );
-               $ptTimeISO = $ptDateTime->format( 'Y-m-d\TH:i:s' );
-               $ptTimeISO .= ltrim( number_format( $ptSecFrac, 6 ), '0' );
-
-               $db->method( 'getHeartbeatData' )
-                       ->with( [ 'server_id' => 172 ] )
-                       ->willReturn( [ $ptTimeISO, $now ] );
-
-               $db->setLBInfo( 'clusterMasterHost', 'db1052' );
-               $lagEst = $db->getLag();
-
-               $this->assertGreaterThan( $lag - .010, $lagEst, "Correct heatbeat lag" );
-               $this->assertLessThan( $lag + .010, $lagEst, "Correct heatbeat lag" );
-       }
-
-       function provideLagAmounts() {
-               return [
-                       [ 0 ],
-                       [ 0.3 ],
-                       [ 6.5 ],
-                       [ 10.1 ],
-                       [ 200.2 ],
-                       [ 400.7 ],
-                       [ 600.22 ],
-                       [ 1000.77 ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/db/DatabaseSQLTest.php b/tests/phpunit/includes/db/DatabaseSQLTest.php
deleted file mode 100644 (file)
index 2b587db..0000000
+++ /dev/null
@@ -1,1075 +0,0 @@
-<?php
-
-use Wikimedia\Rdbms\LikeMatch;
-
-/**
- * Test the abstract database layer
- * This is a non DBMS depending test.
- */
-class DatabaseSQLTest extends MediaWikiTestCase {
-       /** @var DatabaseTestHelper */
-       private $database;
-
-       protected function setUp() {
-               parent::setUp();
-               $this->database = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => true ] );
-       }
-
-       protected function assertLastSql( $sqlText ) {
-               $this->assertEquals(
-                       $sqlText,
-                       $this->database->getLastSqls()
-               );
-       }
-
-       protected function assertLastSqlDb( $sqlText, $db ) {
-               $this->assertEquals( $sqlText, $db->getLastSqls() );
-       }
-
-       /**
-        * @dataProvider provideSelect
-        * @covers Database::select
-        */
-       public function testSelect( $sql, $sqlText ) {
-               $this->database->select(
-                       $sql['tables'],
-                       $sql['fields'],
-                       isset( $sql['conds'] ) ? $sql['conds'] : [],
-                       __METHOD__,
-                       isset( $sql['options'] ) ? $sql['options'] : [],
-                       isset( $sql['join_conds'] ) ? $sql['join_conds'] : []
-               );
-               $this->assertLastSql( $sqlText );
-       }
-
-       public static function provideSelect() {
-               return [
-                       [
-                               [
-                                       'tables' => 'table',
-                                       'fields' => [ 'field', 'alias' => 'field2' ],
-                                       'conds' => [ 'alias' => 'text' ],
-                               ],
-                               "SELECT field,field2 AS alias " .
-                                       "FROM table " .
-                                       "WHERE alias = 'text'"
-                       ],
-                       [
-                               [
-                                       'tables' => 'table',
-                                       'fields' => [ 'field', 'alias' => 'field2' ],
-                                       'conds' => [ 'alias' => 'text' ],
-                                       'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ],
-                               ],
-                               "SELECT field,field2 AS alias " .
-                                       "FROM table " .
-                                       "WHERE alias = 'text' " .
-                                       "ORDER BY field " .
-                                       "LIMIT 1"
-                       ],
-                       [
-                               [
-                                       'tables' => [ 'table', 't2' => 'table2' ],
-                                       'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
-                                       'conds' => [ 'alias' => 'text' ],
-                                       'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ],
-                                       'join_conds' => [ 't2' => [
-                                               'LEFT JOIN', 'tid = t2.id'
-                                       ] ],
-                               ],
-                               "SELECT tid,field,field2 AS alias,t2.id " .
-                                       "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
-                                       "WHERE alias = 'text' " .
-                                       "ORDER BY field " .
-                                       "LIMIT 1"
-                       ],
-                       [
-                               [
-                                       'tables' => [ 'table', 't2' => 'table2' ],
-                                       'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
-                                       'conds' => [ 'alias' => 'text' ],
-                                       'options' => [ 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ],
-                                       'join_conds' => [ 't2' => [
-                                               'LEFT JOIN', 'tid = t2.id'
-                                       ] ],
-                               ],
-                               "SELECT tid,field,field2 AS alias,t2.id " .
-                                       "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
-                                       "WHERE alias = 'text' " .
-                                       "GROUP BY field HAVING COUNT(*) > 1 " .
-                                       "LIMIT 1"
-                       ],
-                       [
-                               [
-                                       'tables' => [ 'table', 't2' => 'table2' ],
-                                       'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
-                                       'conds' => [ 'alias' => 'text' ],
-                                       'options' => [
-                                               'LIMIT' => 1,
-                                               'GROUP BY' => [ 'field', 'field2' ],
-                                               'HAVING' => [ 'COUNT(*) > 1', 'field' => 1 ]
-                                       ],
-                                       'join_conds' => [ 't2' => [
-                                               'LEFT JOIN', 'tid = t2.id'
-                                       ] ],
-                               ],
-                               "SELECT tid,field,field2 AS alias,t2.id " .
-                                       "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
-                                       "WHERE alias = 'text' " .
-                                       "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " .
-                                       "LIMIT 1"
-                       ],
-                       [
-                               [
-                                       'tables' => [ 'table' ],
-                                       'fields' => [ 'alias' => 'field' ],
-                                       'conds' => [ 'alias' => [ 1, 2, 3, 4 ] ],
-                               ],
-                               "SELECT field AS alias " .
-                                       "FROM table " .
-                                       "WHERE alias IN ('1','2','3','4')"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideUpdate
-        * @covers Database::update
-        */
-       public function testUpdate( $sql, $sqlText ) {
-               $this->database->update(
-                       $sql['table'],
-                       $sql['values'],
-                       $sql['conds'],
-                       __METHOD__,
-                       isset( $sql['options'] ) ? $sql['options'] : []
-               );
-               $this->assertLastSql( $sqlText );
-       }
-
-       public static function provideUpdate() {
-               return [
-                       [
-                               [
-                                       'table' => 'table',
-                                       'values' => [ 'field' => 'text', 'field2' => 'text2' ],
-                                       'conds' => [ 'alias' => 'text' ],
-                               ],
-                               "UPDATE table " .
-                                       "SET field = 'text'" .
-                                       ",field2 = 'text2' " .
-                                       "WHERE alias = 'text'"
-                       ],
-                       [
-                               [
-                                       'table' => 'table',
-                                       'values' => [ 'field = other', 'field2' => 'text2' ],
-                                       'conds' => [ 'id' => '1' ],
-                               ],
-                               "UPDATE table " .
-                                       "SET field = other" .
-                                       ",field2 = 'text2' " .
-                                       "WHERE id = '1'"
-                       ],
-                       [
-                               [
-                                       'table' => 'table',
-                                       'values' => [ 'field = other', 'field2' => 'text2' ],
-                                       'conds' => '*',
-                               ],
-                               "UPDATE table " .
-                                       "SET field = other" .
-                                       ",field2 = 'text2'"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideDelete
-        * @covers Database::delete
-        */
-       public function testDelete( $sql, $sqlText ) {
-               $this->database->delete(
-                       $sql['table'],
-                       $sql['conds'],
-                       __METHOD__
-               );
-               $this->assertLastSql( $sqlText );
-       }
-
-       public static function provideDelete() {
-               return [
-                       [
-                               [
-                                       'table' => 'table',
-                                       'conds' => [ 'alias' => 'text' ],
-                               ],
-                               "DELETE FROM table " .
-                                       "WHERE alias = 'text'"
-                       ],
-                       [
-                               [
-                                       'table' => 'table',
-                                       'conds' => '*',
-                               ],
-                               "DELETE FROM table"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideUpsert
-        * @covers Database::upsert
-        */
-       public function testUpsert( $sql, $sqlText ) {
-               $this->database->upsert(
-                       $sql['table'],
-                       $sql['rows'],
-                       $sql['uniqueIndexes'],
-                       $sql['set'],
-                       __METHOD__
-               );
-               $this->assertLastSql( $sqlText );
-       }
-
-       public static function provideUpsert() {
-               return [
-                       [
-                               [
-                                       'table' => 'upsert_table',
-                                       'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
-                                       'uniqueIndexes' => [ 'field' ],
-                                       'set' => [ 'field' => 'set' ],
-                               ],
-                               "BEGIN; " .
-                                       "UPDATE upsert_table " .
-                                       "SET field = 'set' " .
-                                       "WHERE ((field = 'text')); " .
-                                       "INSERT IGNORE INTO upsert_table " .
-                                       "(field,field2) " .
-                                       "VALUES ('text','text2'); " .
-                                       "COMMIT"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideDeleteJoin
-        * @covers Database::deleteJoin
-        */
-       public function testDeleteJoin( $sql, $sqlText ) {
-               $this->database->deleteJoin(
-                       $sql['delTable'],
-                       $sql['joinTable'],
-                       $sql['delVar'],
-                       $sql['joinVar'],
-                       $sql['conds'],
-                       __METHOD__
-               );
-               $this->assertLastSql( $sqlText );
-       }
-
-       public static function provideDeleteJoin() {
-               return [
-                       [
-                               [
-                                       'delTable' => 'table',
-                                       'joinTable' => 'table_join',
-                                       'delVar' => 'field',
-                                       'joinVar' => 'field_join',
-                                       'conds' => [ 'alias' => 'text' ],
-                               ],
-                               "DELETE FROM table " .
-                                       "WHERE field IN (" .
-                                       "SELECT field_join FROM table_join WHERE alias = 'text'" .
-                                       ")"
-                       ],
-                       [
-                               [
-                                       'delTable' => 'table',
-                                       'joinTable' => 'table_join',
-                                       'delVar' => 'field',
-                                       'joinVar' => 'field_join',
-                                       'conds' => '*',
-                               ],
-                               "DELETE FROM table " .
-                                       "WHERE field IN (" .
-                                       "SELECT field_join FROM table_join " .
-                                       ")"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideInsert
-        * @covers Database::insert
-        */
-       public function testInsert( $sql, $sqlText ) {
-               $this->database->insert(
-                       $sql['table'],
-                       $sql['rows'],
-                       __METHOD__,
-                       isset( $sql['options'] ) ? $sql['options'] : []
-               );
-               $this->assertLastSql( $sqlText );
-       }
-
-       public static function provideInsert() {
-               return [
-                       [
-                               [
-                                       'table' => 'table',
-                                       'rows' => [ 'field' => 'text', 'field2' => 2 ],
-                               ],
-                               "INSERT INTO table " .
-                                       "(field,field2) " .
-                                       "VALUES ('text','2')"
-                       ],
-                       [
-                               [
-                                       'table' => 'table',
-                                       'rows' => [ 'field' => 'text', 'field2' => 2 ],
-                                       'options' => 'IGNORE',
-                               ],
-                               "INSERT IGNORE INTO table " .
-                                       "(field,field2) " .
-                                       "VALUES ('text','2')"
-                       ],
-                       [
-                               [
-                                       'table' => 'table',
-                                       'rows' => [
-                                               [ 'field' => 'text', 'field2' => 2 ],
-                                               [ 'field' => 'multi', 'field2' => 3 ],
-                                       ],
-                                       'options' => 'IGNORE',
-                               ],
-                               "INSERT IGNORE INTO table " .
-                                       "(field,field2) " .
-                                       "VALUES " .
-                                       "('text','2')," .
-                                       "('multi','3')"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideInsertSelect
-        * @covers Database::insertSelect
-        */
-       public function testInsertSelect( $sql, $sqlTextNative, $sqlSelect, $sqlInsert ) {
-               $this->database->insertSelect(
-                       $sql['destTable'],
-                       $sql['srcTable'],
-                       $sql['varMap'],
-                       $sql['conds'],
-                       __METHOD__,
-                       isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [],
-                       isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [],
-                       isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : []
-               );
-               $this->assertLastSql( $sqlTextNative );
-
-               $dbWeb = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => false ] );
-               $dbWeb->forceNextResult( [
-                       array_flip( array_keys( $sql['varMap'] ) )
-               ] );
-               $dbWeb->insertSelect(
-                       $sql['destTable'],
-                       $sql['srcTable'],
-                       $sql['varMap'],
-                       $sql['conds'],
-                       __METHOD__,
-                       isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [],
-                       isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [],
-                       isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : []
-               );
-               $this->assertLastSqlDb( implode( '; ', [ $sqlSelect, $sqlInsert ] ), $dbWeb );
-       }
-
-       public static function provideInsertSelect() {
-               return [
-                       [
-                               [
-                                       'destTable' => 'insert_table',
-                                       'srcTable' => 'select_table',
-                                       'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
-                                       'conds' => '*',
-                               ],
-                               "INSERT INTO insert_table " .
-                                       "(field_insert,field) " .
-                                       "SELECT field_select,field2 " .
-                                       "FROM select_table WHERE *",
-                               "SELECT field_select AS field_insert,field2 AS field " .
-                               "FROM select_table WHERE *   FOR UPDATE",
-                               "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')"
-                       ],
-                       [
-                               [
-                                       'destTable' => 'insert_table',
-                                       'srcTable' => 'select_table',
-                                       'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
-                                       'conds' => [ 'field' => 2 ],
-                               ],
-                               "INSERT INTO insert_table " .
-                                       "(field_insert,field) " .
-                                       "SELECT field_select,field2 " .
-                                       "FROM select_table " .
-                                       "WHERE field = '2'",
-                               "SELECT field_select AS field_insert,field2 AS field FROM " .
-                               "select_table WHERE field = '2'   FOR UPDATE",
-                               "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')"
-                       ],
-                       [
-                               [
-                                       'destTable' => 'insert_table',
-                                       'srcTable' => 'select_table',
-                                       'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
-                                       'conds' => [ 'field' => 2 ],
-                                       'insertOptions' => 'IGNORE',
-                                       'selectOptions' => [ 'ORDER BY' => 'field' ],
-                               ],
-                               "INSERT IGNORE INTO insert_table " .
-                                       "(field_insert,field) " .
-                                       "SELECT field_select,field2 " .
-                                       "FROM select_table " .
-                                       "WHERE field = '2' " .
-                                       "ORDER BY field",
-                               "SELECT field_select AS field_insert,field2 AS field " .
-                               "FROM select_table WHERE field = '2' ORDER BY field  FOR UPDATE",
-                               "INSERT IGNORE INTO insert_table (field_insert,field) VALUES ('0','1')"
-                       ],
-                       [
-                               [
-                                       'destTable' => 'insert_table',
-                                       'srcTable' => [ 'select_table1', 'select_table2' ],
-                                       'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
-                                       'conds' => [ 'field' => 2 ],
-                                       'selectOptions' => [ 'ORDER BY' => 'field', 'FORCE INDEX' => [ 'select_table1' => 'index1' ] ],
-                                       'selectJoinConds' => [
-                                               'select_table2' => [ 'LEFT JOIN', [ 'select_table1.foo = select_table2.bar' ] ],
-                                       ],
-                               ],
-                               "INSERT INTO insert_table " .
-                                       "(field_insert,field) " .
-                                       "SELECT field_select,field2 " .
-                                       "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " .
-                                       "WHERE field = '2' " .
-                                       "ORDER BY field",
-                               "SELECT field_select AS field_insert,field2 AS field " .
-                               "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " .
-                               "WHERE field = '2' ORDER BY field  FOR UPDATE",
-                               "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideReplace
-        * @covers Database::replace
-        */
-       public function testReplace( $sql, $sqlText ) {
-               $this->database->replace(
-                       $sql['table'],
-                       $sql['uniqueIndexes'],
-                       $sql['rows'],
-                       __METHOD__
-               );
-               $this->assertLastSql( $sqlText );
-       }
-
-       public static function provideReplace() {
-               return [
-                       [
-                               [
-                                       'table' => 'replace_table',
-                                       'uniqueIndexes' => [ 'field' ],
-                                       'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
-                               ],
-                               "DELETE FROM replace_table " .
-                                       "WHERE ( field='text' ); " .
-                                       "INSERT INTO replace_table " .
-                                       "(field,field2) " .
-                                       "VALUES ('text','text2')"
-                       ],
-                       [
-                               [
-                                       'table' => 'module_deps',
-                                       'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ],
-                                       'rows' => [
-                                               'md_module' => 'module',
-                                               'md_skin' => 'skin',
-                                               'md_deps' => 'deps',
-                                       ],
-                               ],
-                               "DELETE FROM module_deps " .
-                                       "WHERE ( md_module='module' AND md_skin='skin' ); " .
-                                       "INSERT INTO module_deps " .
-                                       "(md_module,md_skin,md_deps) " .
-                                       "VALUES ('module','skin','deps')"
-                       ],
-                       [
-                               [
-                                       'table' => 'module_deps',
-                                       'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ],
-                                       'rows' => [
-                                               [
-                                                       'md_module' => 'module',
-                                                       'md_skin' => 'skin',
-                                                       'md_deps' => 'deps',
-                                               ], [
-                                                       'md_module' => 'module2',
-                                                       'md_skin' => 'skin2',
-                                                       'md_deps' => 'deps2',
-                                               ],
-                                       ],
-                               ],
-                               "DELETE FROM module_deps " .
-                                       "WHERE ( md_module='module' AND md_skin='skin' ); " .
-                                       "INSERT INTO module_deps " .
-                                       "(md_module,md_skin,md_deps) " .
-                                       "VALUES ('module','skin','deps'); " .
-                                       "DELETE FROM module_deps " .
-                                       "WHERE ( md_module='module2' AND md_skin='skin2' ); " .
-                                       "INSERT INTO module_deps " .
-                                       "(md_module,md_skin,md_deps) " .
-                                       "VALUES ('module2','skin2','deps2')"
-                       ],
-                       [
-                               [
-                                       'table' => 'module_deps',
-                                       'uniqueIndexes' => [ 'md_module', 'md_skin' ],
-                                       'rows' => [
-                                               [
-                                                       'md_module' => 'module',
-                                                       'md_skin' => 'skin',
-                                                       'md_deps' => 'deps',
-                                               ], [
-                                                       'md_module' => 'module2',
-                                                       'md_skin' => 'skin2',
-                                                       'md_deps' => 'deps2',
-                                               ],
-                                       ],
-                               ],
-                               "DELETE FROM module_deps " .
-                                       "WHERE ( md_module='module' ) OR ( md_skin='skin' ); " .
-                                       "INSERT INTO module_deps " .
-                                       "(md_module,md_skin,md_deps) " .
-                                       "VALUES ('module','skin','deps'); " .
-                                       "DELETE FROM module_deps " .
-                                       "WHERE ( md_module='module2' ) OR ( md_skin='skin2' ); " .
-                                       "INSERT INTO module_deps " .
-                                       "(md_module,md_skin,md_deps) " .
-                                       "VALUES ('module2','skin2','deps2')"
-                       ],
-                       [
-                               [
-                                       'table' => 'module_deps',
-                                       'uniqueIndexes' => [],
-                                       'rows' => [
-                                               'md_module' => 'module',
-                                               'md_skin' => 'skin',
-                                               'md_deps' => 'deps',
-                                       ],
-                               ],
-                               "INSERT INTO module_deps " .
-                                       "(md_module,md_skin,md_deps) " .
-                                       "VALUES ('module','skin','deps')"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideNativeReplace
-        * @covers Database::nativeReplace
-        */
-       public function testNativeReplace( $sql, $sqlText ) {
-               $this->database->nativeReplace(
-                       $sql['table'],
-                       $sql['rows'],
-                       __METHOD__
-               );
-               $this->assertLastSql( $sqlText );
-       }
-
-       public static function provideNativeReplace() {
-               return [
-                       [
-                               [
-                                       'table' => 'replace_table',
-                                       'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
-                               ],
-                               "REPLACE INTO replace_table " .
-                                       "(field,field2) " .
-                                       "VALUES ('text','text2')"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideConditional
-        * @covers Database::conditional
-        */
-       public function testConditional( $sql, $sqlText ) {
-               $this->assertEquals( trim( $this->database->conditional(
-                       $sql['conds'],
-                       $sql['true'],
-                       $sql['false']
-               ) ), $sqlText );
-       }
-
-       public static function provideConditional() {
-               return [
-                       [
-                               [
-                                       'conds' => [ 'field' => 'text' ],
-                                       'true' => 1,
-                                       'false' => 'NULL',
-                               ],
-                               "(CASE WHEN field = 'text' THEN 1 ELSE NULL END)"
-                       ],
-                       [
-                               [
-                                       'conds' => [ 'field' => 'text', 'field2' => 'anothertext' ],
-                                       'true' => 1,
-                                       'false' => 'NULL',
-                               ],
-                               "(CASE WHEN field = 'text' AND field2 = 'anothertext' THEN 1 ELSE NULL END)"
-                       ],
-                       [
-                               [
-                                       'conds' => 'field=1',
-                                       'true' => 1,
-                                       'false' => 'NULL',
-                               ],
-                               "(CASE WHEN field=1 THEN 1 ELSE NULL END)"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideBuildConcat
-        * @covers Database::buildConcat
-        */
-       public function testBuildConcat( $stringList, $sqlText ) {
-               $this->assertEquals( trim( $this->database->buildConcat(
-                       $stringList
-               ) ), $sqlText );
-       }
-
-       public static function provideBuildConcat() {
-               return [
-                       [
-                               [ 'field', 'field2' ],
-                               "CONCAT(field,field2)"
-                       ],
-                       [
-                               [ "'test'", 'field2' ],
-                               "CONCAT('test',field2)"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideBuildLike
-        * @covers Database::buildLike
-        */
-       public function testBuildLike( $array, $sqlText ) {
-               $this->assertEquals( trim( $this->database->buildLike(
-                       $array
-               ) ), $sqlText );
-       }
-
-       public static function provideBuildLike() {
-               return [
-                       [
-                               'text',
-                               "LIKE 'text' ESCAPE '`'"
-                       ],
-                       [
-                               [ 'text', new LikeMatch( '%' ) ],
-                               "LIKE 'text%' ESCAPE '`'"
-                       ],
-                       [
-                               [ 'text', new LikeMatch( '%' ), 'text2' ],
-                               "LIKE 'text%text2' ESCAPE '`'"
-                       ],
-                       [
-                               [ 'text', new LikeMatch( '_' ) ],
-                               "LIKE 'text_' ESCAPE '`'"
-                       ],
-                       [
-                               'more_text',
-                               "LIKE 'more`_text' ESCAPE '`'"
-                       ],
-                       [
-                               [ 'C:\\Windows\\', new LikeMatch( '%' ) ],
-                               "LIKE 'C:\\Windows\\%' ESCAPE '`'"
-                       ],
-                       [
-                               [ 'accent`_test`', new LikeMatch( '%' ) ],
-                               "LIKE 'accent```_test``%' ESCAPE '`'"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideUnionQueries
-        * @covers Database::unionQueries
-        */
-       public function testUnionQueries( $sql, $sqlText ) {
-               $this->assertEquals( trim( $this->database->unionQueries(
-                       $sql['sqls'],
-                       $sql['all']
-               ) ), $sqlText );
-       }
-
-       public static function provideUnionQueries() {
-               return [
-                       [
-                               [
-                                       'sqls' => [ 'RAW SQL', 'RAW2SQL' ],
-                                       'all' => true,
-                               ],
-                               "(RAW SQL) UNION ALL (RAW2SQL)"
-                       ],
-                       [
-                               [
-                                       'sqls' => [ 'RAW SQL', 'RAW2SQL' ],
-                                       'all' => false,
-                               ],
-                               "(RAW SQL) UNION (RAW2SQL)"
-                       ],
-                       [
-                               [
-                                       'sqls' => [ 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ],
-                                       'all' => false,
-                               ],
-                               "(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)"
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideUnionConditionPermutations
-        * @covers Database::unionConditionPermutations
-        */
-       public function testUnionConditionPermutations( $params, $expect ) {
-               if ( isset( $params['unionSupportsOrderAndLimit'] ) ) {
-                       $this->database->setUnionSupportsOrderAndLimit( $params['unionSupportsOrderAndLimit'] );
-               }
-
-               $sql = trim( $this->database->unionConditionPermutations(
-                       $params['table'],
-                       $params['vars'],
-                       $params['permute_conds'],
-                       isset( $params['extra_conds'] ) ? $params['extra_conds'] : '',
-                       'FNAME',
-                       isset( $params['options'] ) ? $params['options'] : [],
-                       isset( $params['join_conds'] ) ? $params['join_conds'] : []
-               ) );
-               $this->assertEquals( $expect, $sql );
-       }
-
-       public static function provideUnionConditionPermutations() {
-               return [
-                       // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
-                       [
-                               [
-                                       'table' => [ 'table1', 'table2' ],
-                                       'vars' => [ 'field1', 'alias' => 'field2' ],
-                                       'permute_conds' => [
-                                               'field3' => [ 1, 2, 3 ],
-                                               'duplicates' => [ 4, 5, 4 ],
-                                               'empty' => [],
-                                               'single' => [ 0 ],
-                                       ],
-                                       'extra_conds' => 'table2.bar > 23',
-                                       'options' => [
-                                               'ORDER BY' => [ 'field1', 'alias' ],
-                                               'INNER ORDER BY' => [ 'field1', 'field2' ],
-                                               'LIMIT' => 100,
-                                       ],
-                                       'join_conds' => [
-                                               'table2' => [ 'JOIN', 'table1.foo_id = table2.foo_id' ],
-                                       ],
-                               ],
-                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '1' AND duplicates = '4' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) UNION ALL " .
-                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '1' AND duplicates = '5' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) UNION ALL " .
-                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '2' AND duplicates = '4' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) UNION ALL " .
-                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '2' AND duplicates = '5' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) UNION ALL " .
-                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '3' AND duplicates = '4' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) UNION ALL " .
-                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '3' AND duplicates = '5' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) " .
-                               "ORDER BY field1,alias LIMIT 100"
-                       ],
-                       [
-                               [
-                                       'table' => 'foo',
-                                       'vars' => [ 'foo_id' ],
-                                       'permute_conds' => [
-                                               'bar' => [ 1, 2, 3 ],
-                                       ],
-                                       'extra_conds' => [ 'baz' => null ],
-                                       'options' => [
-                                               'NOTALL',
-                                               'ORDER BY' => [ 'foo_id' ],
-                                               'LIMIT' => 25,
-                                       ],
-                               ],
-                               "(SELECT  foo_id  FROM foo    WHERE bar = '1' AND baz IS NULL  ORDER BY foo_id LIMIT 25  ) UNION " .
-                               "(SELECT  foo_id  FROM foo    WHERE bar = '2' AND baz IS NULL  ORDER BY foo_id LIMIT 25  ) UNION " .
-                               "(SELECT  foo_id  FROM foo    WHERE bar = '3' AND baz IS NULL  ORDER BY foo_id LIMIT 25  ) " .
-                               "ORDER BY foo_id LIMIT 25"
-                       ],
-                       [
-                               [
-                                       'table' => 'foo',
-                                       'vars' => [ 'foo_id' ],
-                                       'permute_conds' => [
-                                               'bar' => [ 1, 2, 3 ],
-                                       ],
-                                       'extra_conds' => [ 'baz' => null ],
-                                       'options' => [
-                                               'NOTALL' => true,
-                                               'ORDER BY' => [ 'foo_id' ],
-                                               'LIMIT' => 25,
-                                       ],
-                                       'unionSupportsOrderAndLimit' => false,
-                               ],
-                               "(SELECT  foo_id  FROM foo    WHERE bar = '1' AND baz IS NULL  ) UNION " .
-                               "(SELECT  foo_id  FROM foo    WHERE bar = '2' AND baz IS NULL  ) UNION " .
-                               "(SELECT  foo_id  FROM foo    WHERE bar = '3' AND baz IS NULL  ) " .
-                               "ORDER BY foo_id LIMIT 25"
-                       ],
-                       [
-                               [
-                                       'table' => 'foo',
-                                       'vars' => [ 'foo_id' ],
-                                       'permute_conds' => [],
-                                       'extra_conds' => [ 'baz' => null ],
-                                       'options' => [
-                                               'ORDER BY' => [ 'foo_id' ],
-                                               'LIMIT' => 25,
-                                       ],
-                               ],
-                               "SELECT  foo_id  FROM foo    WHERE baz IS NULL  ORDER BY foo_id LIMIT 25"
-                       ],
-                       [
-                               [
-                                       'table' => 'foo',
-                                       'vars' => [ 'foo_id' ],
-                                       'permute_conds' => [
-                                               'bar' => [],
-                                       ],
-                                       'extra_conds' => [ 'baz' => null ],
-                                       'options' => [
-                                               'ORDER BY' => [ 'foo_id' ],
-                                               'LIMIT' => 25,
-                                       ],
-                               ],
-                               "SELECT  foo_id  FROM foo    WHERE baz IS NULL  ORDER BY foo_id LIMIT 25"
-                       ],
-                       [
-                               [
-                                       'table' => 'foo',
-                                       'vars' => [ 'foo_id' ],
-                                       'permute_conds' => [
-                                               'bar' => [ 1 ],
-                                       ],
-                                       'options' => [
-                                               'ORDER BY' => [ 'foo_id' ],
-                                               'LIMIT' => 25,
-                                               'OFFSET' => 150,
-                                       ],
-                               ],
-                               "SELECT  foo_id  FROM foo    WHERE bar = '1'  ORDER BY foo_id LIMIT 150,25"
-                       ],
-                       [
-                               [
-                                       'table' => 'foo',
-                                       'vars' => [ 'foo_id' ],
-                                       'permute_conds' => [],
-                                       'extra_conds' => [ 'baz' => null ],
-                                       'options' => [
-                                               'ORDER BY' => [ 'foo_id' ],
-                                               'LIMIT' => 25,
-                                               'OFFSET' => 150,
-                                               'INNER ORDER BY' => [ 'bar_id' ],
-                                       ],
-                               ],
-                               "(SELECT  foo_id  FROM foo    WHERE baz IS NULL  ORDER BY bar_id LIMIT 175  ) ORDER BY foo_id LIMIT 150,25"
-                       ],
-                       [
-                               [
-                                       'table' => 'foo',
-                                       'vars' => [ 'foo_id' ],
-                                       'permute_conds' => [],
-                                       'extra_conds' => [ 'baz' => null ],
-                                       'options' => [
-                                               'ORDER BY' => [ 'foo_id' ],
-                                               'LIMIT' => 25,
-                                               'OFFSET' => 150,
-                                               'INNER ORDER BY' => [ 'bar_id' ],
-                                       ],
-                                       'unionSupportsOrderAndLimit' => false,
-                               ],
-                               "SELECT  foo_id  FROM foo    WHERE baz IS NULL  ORDER BY foo_id LIMIT 150,25"
-                       ],
-                       // @codingStandardsIgnoreEnd
-               ];
-       }
-
-       /**
-        * @covers Database::commit
-        */
-       public function testTransactionCommit() {
-               $this->database->begin( __METHOD__ );
-               $this->database->commit( __METHOD__ );
-               $this->assertLastSql( 'BEGIN; COMMIT' );
-       }
-
-       /**
-        * @covers Database::rollback
-        */
-       public function testTransactionRollback() {
-               $this->database->begin( __METHOD__ );
-               $this->database->rollback( __METHOD__ );
-               $this->assertLastSql( 'BEGIN; ROLLBACK' );
-       }
-
-       /**
-        * @covers Database::dropTable
-        */
-       public function testDropTable() {
-               $this->database->setExistingTables( [ 'table' ] );
-               $this->database->dropTable( 'table', __METHOD__ );
-               $this->assertLastSql( 'DROP TABLE table CASCADE' );
-       }
-
-       /**
-        * @covers Database::dropTable
-        */
-       public function testDropNonExistingTable() {
-               $this->assertFalse(
-                       $this->database->dropTable( 'non_existing', __METHOD__ )
-               );
-       }
-
-       /**
-        * @dataProvider provideMakeList
-        * @covers Database::makeList
-        */
-       public function testMakeList( $list, $mode, $sqlText ) {
-               $this->assertEquals( trim( $this->database->makeList(
-                       $list, $mode
-               ) ), $sqlText );
-       }
-
-       public static function provideMakeList() {
-               return [
-                       [
-                               [ 'value', 'value2' ],
-                               LIST_COMMA,
-                               "'value','value2'"
-                       ],
-                       [
-                               [ 'field', 'field2' ],
-                               LIST_NAMES,
-                               "field,field2"
-                       ],
-                       [
-                               [ 'field' => 'value', 'field2' => 'value2' ],
-                               LIST_AND,
-                               "field = 'value' AND field2 = 'value2'"
-                       ],
-                       [
-                               [ 'field' => null, "field2 != 'value2'" ],
-                               LIST_AND,
-                               "field IS NULL AND (field2 != 'value2')"
-                       ],
-                       [
-                               [ 'field' => [ 'value', null, 'value2' ], 'field2' => 'value2' ],
-                               LIST_AND,
-                               "(field IN ('value','value2')  OR field IS NULL) AND field2 = 'value2'"
-                       ],
-                       [
-                               [ 'field' => [ null ], 'field2' => null ],
-                               LIST_AND,
-                               "field IS NULL AND field2 IS NULL"
-                       ],
-                       [
-                               [ 'field' => 'value', 'field2' => 'value2' ],
-                               LIST_OR,
-                               "field = 'value' OR field2 = 'value2'"
-                       ],
-                       [
-                               [ 'field' => 'value', 'field2' => null ],
-                               LIST_OR,
-                               "field = 'value' OR field2 IS NULL"
-                       ],
-                       [
-                               [ 'field' => [ 'value', 'value2' ], 'field2' => [ 'value' ] ],
-                               LIST_OR,
-                               "field IN ('value','value2')  OR field2 = 'value'"
-                       ],
-                       [
-                               [ 'field' => [ null, 'value', null, 'value2' ], "field2 != 'value2'" ],
-                               LIST_OR,
-                               "(field IN ('value','value2')  OR field IS NULL) OR (field2 != 'value2')"
-                       ],
-                       [
-                               [ 'field' => 'value', 'field2' => 'value2' ],
-                               LIST_SET,
-                               "field = 'value',field2 = 'value2'"
-                       ],
-                       [
-                               [ 'field' => 'value', 'field2' => null ],
-                               LIST_SET,
-                               "field = 'value',field2 = NULL"
-                       ],
-                       [
-                               [ 'field' => 'value', "field2 != 'value2'" ],
-                               LIST_SET,
-                               "field = 'value',field2 != 'value2'"
-                       ],
-               ];
-       }
-
-       public function testSessionTempTables() {
-               $temp1 = $this->database->tableName( 'tmp_table_1' );
-               $temp2 = $this->database->tableName( 'tmp_table_2' );
-               $temp3 = $this->database->tableName( 'tmp_table_3' );
-
-               $this->database->query( "CREATE TEMPORARY TABLE $temp1 LIKE orig_tbl", __METHOD__ );
-               $this->database->query( "CREATE TEMPORARY TABLE $temp2 LIKE orig_tbl", __METHOD__ );
-               $this->database->query( "CREATE TEMPORARY TABLE $temp3 LIKE orig_tbl", __METHOD__ );
-
-               $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
-               $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
-               $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
-
-               $this->database->dropTable( 'tmp_table_1', __METHOD__ );
-               $this->database->dropTable( 'tmp_table_2', __METHOD__ );
-               $this->database->dropTable( 'tmp_table_3', __METHOD__ );
-
-               $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
-               $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
-               $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
-
-               $this->database->query( "CREATE TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ );
-               $this->database->query( "CREATE TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ );
-               $this->database->query( "CREATE TEMPORARY TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ );
-
-               $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
-               $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
-               $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
-
-               $this->database->query( "DROP TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ );
-               $this->database->query( "DROP TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ );
-               $this->database->query( "DROP TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ );
-
-               $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
-               $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
-               $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
-       }
-}
index b90b1ad..ae61070 100644 (file)
@@ -87,6 +87,10 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
                                new Blob( "hello" ),
                                "x'68656c6c6f'",
                        ],
+                       [ // #5: null
+                               null,
+                               "''",
+                       ],
                ];
        }
 
diff --git a/tests/phpunit/includes/db/DatabaseTest.php b/tests/phpunit/includes/db/DatabaseTest.php
deleted file mode 100644 (file)
index 45791e2..0000000
+++ /dev/null
@@ -1,415 +0,0 @@
-<?php
-
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * @group Database
- * @group Database
- */
-class DatabaseTest extends MediaWikiTestCase {
-       /**
-        * @var Database
-        */
-       protected $db;
-
-       private $functionTest = false;
-
-       protected function setUp() {
-               parent::setUp();
-               $this->db = wfGetDB( DB_MASTER );
-       }
-
-       protected function tearDown() {
-               parent::tearDown();
-               if ( $this->functionTest ) {
-                       $this->dropFunctions();
-                       $this->functionTest = false;
-               }
-               $this->db->restoreFlags( IDatabase::RESTORE_INITIAL );
-       }
-
-       /**
-        * @covers Database::dropTable
-        */
-       public function testAddQuotesNull() {
-               $check = "NULL";
-               if ( $this->db->getType() === 'sqlite' || $this->db->getType() === 'oracle' ) {
-                       $check = "''";
-               }
-               $this->assertEquals( $check, $this->db->addQuotes( null ) );
-       }
-
-       public function testAddQuotesInt() {
-               # returning just "1234" should be ok too, though...
-               # maybe
-               $this->assertEquals(
-                       "'1234'",
-                       $this->db->addQuotes( 1234 ) );
-       }
-
-       public function testAddQuotesFloat() {
-               # returning just "1234.5678" would be ok too, though
-               $this->assertEquals(
-                       "'1234.5678'",
-                       $this->db->addQuotes( 1234.5678 ) );
-       }
-
-       public function testAddQuotesString() {
-               $this->assertEquals(
-                       "'string'",
-                       $this->db->addQuotes( 'string' ) );
-       }
-
-       public function testAddQuotesStringQuote() {
-               $check = "'string''s cause trouble'";
-               if ( $this->db->getType() === 'mysql' ) {
-                       $check = "'string\'s cause trouble'";
-               }
-               $this->assertEquals(
-                       $check,
-                       $this->db->addQuotes( "string's cause trouble" ) );
-       }
-
-       private function getSharedTableName( $table, $database, $prefix, $format = 'quoted' ) {
-               global $wgSharedDB, $wgSharedTables, $wgSharedPrefix, $wgSharedSchema;
-
-               $this->db->setTableAliases( [
-                       $table => [
-                               'dbname' => $database,
-                               'schema' => null,
-                               'prefix' => $prefix
-                       ]
-               ] );
-
-               $ret = $this->db->tableName( $table, $format );
-
-               $this->db->setTableAliases( array_fill_keys(
-                       $wgSharedDB ? $wgSharedTables : [],
-                       [
-                               'dbname' => $wgSharedDB,
-                               'schema' => $wgSharedSchema,
-                               'prefix' => $wgSharedPrefix
-                       ]
-               ) );
-
-               return $ret;
-       }
-
-       private function prefixAndQuote( $table, $database = null, $prefix = null, $format = 'quoted' ) {
-               if ( $this->db->getType() === 'sqlite' || $format !== 'quoted' ) {
-                       $quote = '';
-               } elseif ( $this->db->getType() === 'mysql' ) {
-                       $quote = '`';
-               } elseif ( $this->db->getType() === 'oracle' ) {
-                       $quote = '/*Q*/';
-               } else {
-                       $quote = '"';
-               }
-
-               if ( $database !== null ) {
-                       if ( $this->db->getType() === 'oracle' ) {
-                               $database = $quote . $database . '.';
-                       } else {
-                               $database = $quote . $database . $quote . '.';
-                       }
-               }
-
-               if ( $prefix === null ) {
-                       $prefix = $this->dbPrefix();
-               }
-
-               if ( $this->db->getType() === 'oracle' ) {
-                       return strtoupper( $database . $quote . $prefix . $table );
-               } else {
-                       return $database . $quote . $prefix . $table . $quote;
-               }
-       }
-
-       public function testTableNameLocal() {
-               $this->assertEquals(
-                       $this->prefixAndQuote( 'tablename' ),
-                       $this->db->tableName( 'tablename' )
-               );
-       }
-
-       public function testTableNameRawLocal() {
-               $this->assertEquals(
-                       $this->prefixAndQuote( 'tablename', null, null, 'raw' ),
-                       $this->db->tableName( 'tablename', 'raw' )
-               );
-       }
-
-       public function testTableNameShared() {
-               $this->assertEquals(
-                       $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_' ),
-                       $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_' )
-               );
-
-               $this->assertEquals(
-                       $this->prefixAndQuote( 'tablename', 'sharedatabase', null ),
-                       $this->getSharedTableName( 'tablename', 'sharedatabase', null )
-               );
-       }
-
-       public function testTableNameRawShared() {
-               $this->assertEquals(
-                       $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_', 'raw' ),
-                       $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_', 'raw' )
-               );
-
-               $this->assertEquals(
-                       $this->prefixAndQuote( 'tablename', 'sharedatabase', null, 'raw' ),
-                       $this->getSharedTableName( 'tablename', 'sharedatabase', null, 'raw' )
-               );
-       }
-
-       public function testTableNameForeign() {
-               $this->assertEquals(
-                       $this->prefixAndQuote( 'tablename', 'databasename', '' ),
-                       $this->db->tableName( 'databasename.tablename' )
-               );
-       }
-
-       public function testTableNameRawForeign() {
-               $this->assertEquals(
-                       $this->prefixAndQuote( 'tablename', 'databasename', '', 'raw' ),
-                       $this->db->tableName( 'databasename.tablename', 'raw' )
-               );
-       }
-
-       public function testStoredFunctions() {
-               if ( !in_array( wfGetDB( DB_MASTER )->getType(), [ 'mysql', 'postgres' ] ) ) {
-                       $this->markTestSkipped( 'MySQL or Postgres required' );
-               }
-               global $IP;
-               $this->dropFunctions();
-               $this->functionTest = true;
-               $this->assertTrue(
-                       $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" )
-               );
-               $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ );
-               $this->assertEquals( 42, $res->fetchObject()->test );
-       }
-
-       private function dropFunctions() {
-               $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function'
-                       . ( $this->db->getType() == 'postgres' ? '()' : '' )
-               );
-       }
-
-       public function testUnknownTableCorruptsResults() {
-               $res = $this->db->select( 'page', '*', [ 'page_id' => 1 ] );
-               $this->assertFalse( $this->db->tableExists( 'foobarbaz' ) );
-               $this->assertInternalType( 'int', $res->numRows() );
-       }
-
-       public function testTransactionIdle() {
-               $db = $this->db;
-
-               $db->setFlag( DBO_TRX );
-               $called = false;
-               $flagSet = null;
-               $db->onTransactionIdle(
-                       function () use ( $db, &$flagSet, &$called ) {
-                               $called = true;
-                               $flagSet = $db->getFlag( DBO_TRX );
-                       },
-                       __METHOD__
-               );
-               $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
-               $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
-               $this->assertTrue( $called, 'Callback reached' );
-
-               $db->clearFlag( DBO_TRX );
-               $flagSet = null;
-               $db->onTransactionIdle(
-                       function () use ( $db, &$flagSet ) {
-                               $flagSet = $db->getFlag( DBO_TRX );
-                       },
-                       __METHOD__
-               );
-               $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
-               $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
-
-               $db->clearFlag( DBO_TRX );
-               $db->onTransactionIdle(
-                       function () use ( $db ) {
-                               $db->setFlag( DBO_TRX );
-                       },
-                       __METHOD__
-               );
-               $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
-       }
-
-       public function testTransactionResolution() {
-               $db = $this->db;
-
-               $db->clearFlag( DBO_TRX );
-               $db->begin( __METHOD__ );
-               $called = false;
-               $db->onTransactionResolution( function () use ( $db, &$called ) {
-                       $called = true;
-                       $db->setFlag( DBO_TRX );
-               } );
-               $db->commit( __METHOD__ );
-               $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
-               $this->assertTrue( $called, 'Callback reached' );
-
-               $db->clearFlag( DBO_TRX );
-               $db->begin( __METHOD__ );
-               $called = false;
-               $db->onTransactionResolution( function () use ( $db, &$called ) {
-                       $called = true;
-                       $db->setFlag( DBO_TRX );
-               } );
-               $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
-               $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
-               $this->assertTrue( $called, 'Callback reached' );
-       }
-
-       /**
-        * @covers Database::setTransactionListener()
-        */
-       public function testTransactionListener() {
-               $db = $this->db;
-
-               $db->setTransactionListener( 'ping', function () use ( $db, &$called ) {
-                       $called = true;
-               } );
-
-               $called = false;
-               $db->begin( __METHOD__ );
-               $db->commit( __METHOD__ );
-               $this->assertTrue( $called, 'Callback reached' );
-
-               $called = false;
-               $db->begin( __METHOD__ );
-               $db->commit( __METHOD__ );
-               $this->assertTrue( $called, 'Callback still reached' );
-
-               $called = false;
-               $db->begin( __METHOD__ );
-               $db->rollback( __METHOD__ );
-               $this->assertTrue( $called, 'Callback reached' );
-
-               $db->setTransactionListener( 'ping', null );
-               $called = false;
-               $db->begin( __METHOD__ );
-               $db->commit( __METHOD__ );
-               $this->assertFalse( $called, 'Callback not reached' );
-       }
-
-       /**
-        * @covers Database::flushSnapshot()
-        */
-       public function testFlushSnapshot() {
-               $db = $this->db;
-
-               $db->flushSnapshot( __METHOD__ ); // ok
-               $db->flushSnapshot( __METHOD__ ); // ok
-
-               $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
-               $db->query( 'SELECT 1', __METHOD__ );
-               $this->assertTrue( (bool)$db->trxLevel(), "Transaction started." );
-               $db->flushSnapshot( __METHOD__ ); // ok
-               $db->restoreFlags( $db::RESTORE_PRIOR );
-
-               $this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." );
-       }
-
-       public function testGetScopedLock() {
-               $db = $this->db;
-
-               $db->setFlag( DBO_TRX );
-               try {
-                       $this->badLockingMethodImplicit( $db );
-               } catch ( RunTimeException $e ) {
-                       $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
-               }
-               $db->clearFlag( DBO_TRX );
-               $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
-               $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
-
-               try {
-                       $this->badLockingMethodExplicit( $db );
-               } catch ( RunTimeException $e ) {
-                       $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
-               }
-               $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
-               $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
-       }
-
-       private function badLockingMethodImplicit( IDatabase $db ) {
-               $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
-               $db->query( "SELECT 1" ); // trigger DBO_TRX
-               throw new RunTimeException( "Uh oh!" );
-       }
-
-       private function badLockingMethodExplicit( IDatabase $db ) {
-               $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
-               $db->begin( __METHOD__ );
-               throw new RunTimeException( "Uh oh!" );
-       }
-
-       /**
-        * @covers Database::getFlag(
-        * @covers Database::setFlag()
-        * @covers Database::restoreFlags()
-        */
-       public function testFlagSetting() {
-               $db = $this->db;
-               $origTrx = $db->getFlag( DBO_TRX );
-               $origSsl = $db->getFlag( DBO_SSL );
-
-               $origTrx
-                       ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
-                       : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
-               $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
-
-               $origSsl
-                       ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
-                       : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
-               $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) );
-
-               $db->restoreFlags( $db::RESTORE_INITIAL );
-               $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
-               $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
-
-               $origTrx
-                       ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
-                       : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
-               $origSsl
-                       ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
-                       : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
-
-               $db->restoreFlags();
-               $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
-               $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
-
-               $db->restoreFlags();
-               $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
-               $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
-       }
-
-       /**
-        * @covers Database::tablePrefix()
-        * @covers Database::dbSchema()
-        */
-       public function testMutators() {
-               $old = $this->db->tablePrefix();
-               $this->assertType( 'string', $old, 'Prefix is string' );
-               $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" );
-               $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) );
-               $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" );
-               $this->db->tablePrefix( $old );
-               $this->assertNotEquals( 'xxx', $this->db->tablePrefix() );
-
-               $old = $this->db->dbSchema();
-               $this->assertType( 'string', $old, 'Schema is string' );
-               $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" );
-               $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) );
-               $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" );
-               $this->db->dbSchema( $old );
-               $this->assertNotEquals( 'xxx', $this->db->dbSchema() );
-       }
-}
index d0121b1..1a15c26 100644 (file)
@@ -5,6 +5,10 @@
  * @author Timo Tijhof
  */
 
+/**
+ * @group ResourceLoader
+ * @group CSSMin
+ */
 class CSSMinTest extends MediaWikiTestCase {
 
        protected function setUp() {
@@ -233,6 +237,11 @@ class CSSMinTest extends MediaWikiTestCase {
                                [ 'foo { prop: url(/w/skin/images/bar.png); }', false, 'http://example.org/quux', false ],
                                'foo { prop: url(http://doc.example.org/w/skin/images/bar.png); }',
                        ],
+                       [
+                               "Don't barf at behavior: url(#default#behaviorName) - T162973",
+                               [ 'foo { behavior: url(#default#bar); }', false, '/w/', false ],
+                               'foo { behavior: url("#default#bar"); }',
+                       ],
                ];
        }
 
index 775709f..4a9f6cc 100644 (file)
@@ -81,22 +81,26 @@ class MultiWriteBagOStuffTest extends MediaWikiTestCase {
         */
        public function testSetDelayed() {
                $key = wfRandomString();
-               $value = wfRandomString();
+               $value = (object)[ 'v' => wfRandomString() ];
+               $expectValue = clone $value;
 
                // XXX: DeferredUpdates bound to transactions in CLI mode
                $dbw = wfGetDB( DB_MASTER );
                $dbw->begin();
                $this->cache->set( $key, $value );
 
+               // Test that later changes to $value don't affect the saved value (e.g. T168040)
+               $value->v = 'bogus';
+
                // Set in tier 1
-               $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' );
+               $this->assertEquals( $expectValue, $this->cache1->get( $key ), 'Written to tier 1' );
                // Not yet set in tier 2
                $this->assertEquals( false, $this->cache2->get( $key ), 'Not written to tier 2' );
 
                $dbw->commit();
 
                // Set in tier 2
-               $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' );
+               $this->assertEquals( $expectValue, $this->cache2->get( $key ), 'Written to tier 2' );
        }
 
        /**
index 3dc7e28..a8dbdd3 100644 (file)
@@ -8,16 +8,20 @@ use Wikimedia\Rdbms\DatabaseDomain;
 class DatabaseDomainTest extends PHPUnit_Framework_TestCase {
        public static function provideConstruct() {
                return [
-                       // All strings
-                       [ 'foo', 'bar', 'baz', 'foo-bar-baz' ],
-                       // Nothing
-                       [ null, null, '', '' ],
-                       // Invalid $database
-                       [ 0, 'bar', '', '', true ],
-                       // - in one of the fields
-                       [ 'foo-bar', 'baz', 'baa', 'foo?hbar-baz-baa' ],
-                       // ? in one of the fields
-                       [ 'foo?bar', 'baz', 'baa', 'foo??bar-baz-baa' ],
+                       'All strings' =>
+                               [ 'foo', 'bar', 'baz', 'foo-bar-baz' ],
+                       'Nothing' =>
+                               [ null, null, '', '' ],
+                       'Invalid $database' =>
+                               [ 0, 'bar', '', '', true ],
+                       'Invalid $schema' =>
+                               [ 'foo', 0, '', '', true ],
+                       'Invalid $prefix' =>
+                               [ 'foo', 'bar', 0, '', true ],
+                       'Dash' =>
+                               [ 'foo-bar', 'baz', 'baa', 'foo?hbar-baz-baa' ],
+                       'Question mark' =>
+                               [ 'foo?bar', 'baz', 'baa', 'foo??bar-baz-baa' ],
                ];
        }
 
@@ -27,6 +31,8 @@ class DatabaseDomainTest extends PHPUnit_Framework_TestCase {
        public function testConstruct( $db, $schema, $prefix, $id, $exception = false ) {
                if ( $exception ) {
                        $this->setExpectedException( InvalidArgumentException::class );
+                       new DatabaseDomain( $db, $schema, $prefix );
+                       return;
                }
 
                $domain = new DatabaseDomain( $db, $schema, $prefix );
@@ -35,23 +41,27 @@ class DatabaseDomainTest extends PHPUnit_Framework_TestCase {
                $this->assertEquals( $schema, $domain->getSchema() );
                $this->assertEquals( $prefix, $domain->getTablePrefix() );
                $this->assertEquals( $id, $domain->getId() );
+               $this->assertEquals( $id, strval( $domain ), 'toString' );
        }
 
        public static function provideNewFromId() {
                return [
-                       // basic
-                       [ 'foo', 'foo', null, '' ],
-                       // <database>-<prefix>
-                       [ 'foo-bar', 'foo', null, 'bar' ],
-                       [ 'foo-bar-baz', 'foo', 'bar', 'baz' ],
-                       // ?h -> -
-                       [ 'foo?hbar-baz-baa', 'foo-bar', 'baz', 'baa' ],
-                       // ?? -> ?
-                       [ 'foo??bar-baz-baa', 'foo?bar', 'baz', 'baa' ],
-                       // ? is left alone
-                       [ 'foo?bar-baz-baa', 'foo?bar', 'baz', 'baa' ],
-                       // too many parts
-                       [ 'foo-bar-baz-baa', '', '', '', true ],
+                       'Basic' =>
+                               [ 'foo', 'foo', null, '' ],
+                       'db+prefix' =>
+                               [ 'foo-bar', 'foo', null, 'bar' ],
+                       'db+schema+prefix' =>
+                               [ 'foo-bar-baz', 'foo', 'bar', 'baz' ],
+                       '?h -> -' =>
+                               [ 'foo?hbar-baz-baa', 'foo-bar', 'baz', 'baa' ],
+                       '?? -> ?' =>
+                               [ 'foo??bar-baz-baa', 'foo?bar', 'baz', 'baa' ],
+                       '? is left alone' =>
+                               [ 'foo?bar-baz-baa', 'foo?bar', 'baz', 'baa' ],
+                       'too many parts' =>
+                               [ 'foo-bar-baz-baa', '', '', '', true ],
+                       'from instance' =>
+                               [ DatabaseDomain::newUnspecified(), null, null, '' ],
                ];
        }
 
@@ -61,6 +71,8 @@ class DatabaseDomainTest extends PHPUnit_Framework_TestCase {
        public function testNewFromId( $id, $db, $schema, $prefix, $exception = false ) {
                if ( $exception ) {
                        $this->setExpectedException( InvalidArgumentException::class );
+                       DatabaseDomain::newFromId( $id );
+                       return;
                }
                $domain = DatabaseDomain::newFromId( $id );
                $this->assertInstanceOf( DatabaseDomain::class, $domain );
@@ -68,4 +80,50 @@ class DatabaseDomainTest extends PHPUnit_Framework_TestCase {
                $this->assertEquals( $schema, $domain->getSchema() );
                $this->assertEquals( $prefix, $domain->getTablePrefix() );
        }
+
+       public static function provideEquals() {
+               return [
+                       'Basic' =>
+                               [ 'foo', 'foo', null, '' ],
+                       'db+prefix' =>
+                               [ 'foo-bar', 'foo', null, 'bar' ],
+                       'db+schema+prefix' =>
+                               [ 'foo-bar-baz', 'foo', 'bar', 'baz' ],
+                       '?h -> -' =>
+                               [ 'foo?hbar-baz-baa', 'foo-bar', 'baz', 'baa' ],
+                       '?? -> ?' =>
+                               [ 'foo??bar-baz-baa', 'foo?bar', 'baz', 'baa' ],
+                       'Nothing' =>
+                               [ '', null, null, '' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideEquals
+        * @covers Wikimedia\Rdbms\DatabaseDomain::equals
+        */
+       public function testEquals( $id, $db, $schema, $prefix ) {
+               $fromId = DatabaseDomain::newFromId( $id );
+               $this->assertInstanceOf( DatabaseDomain::class, $fromId );
+
+               $constructed = new DatabaseDomain( $db, $schema, $prefix );
+
+               $this->assertTrue( $constructed->equals( $id ), 'constructed equals string' );
+               $this->assertTrue( $fromId->equals( $id ), 'fromId equals string' );
+
+               $this->assertTrue( $constructed->equals( $fromId ), 'compare constructed to newId' );
+               $this->assertTrue( $fromId->equals( $constructed ), 'compare newId to constructed' );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\DatabaseDomain::newUnspecified
+        */
+       public function testNewUnspecified() {
+               $domain = DatabaseDomain::newUnspecified();
+               $this->assertInstanceOf( DatabaseDomain::class, $domain );
+               $this->assertTrue( $domain->equals( '' ) );
+               $this->assertSame( null, $domain->getDatabase() );
+               $this->assertSame( null, $domain->getSchema() );
+               $this->assertSame( '', $domain->getTablePrefix() );
+       }
 }
diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php
new file mode 100644 (file)
index 0000000..b564310
--- /dev/null
@@ -0,0 +1,371 @@
+<?php
+/**
+ * Holds tests for DatabaseMysqlBase class.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Antoine Musso
+ * @copyright © 2013 Antoine Musso
+ * @copyright © 2013 Wikimedia Foundation and contributors
+ */
+
+use Wikimedia\Rdbms\TransactionProfiler;
+use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\MySQLMasterPos;
+use Wikimedia\Rdbms\DatabaseMysqlBase;
+
+/**
+ * Fake class around abstract class so we can call concrete methods.
+ */
+class FakeDatabaseMysqlBase extends DatabaseMysqlBase {
+       // From Database
+       function __construct() {
+               $this->profiler = new ProfilerStub( [] );
+               $this->trxProfiler = new TransactionProfiler();
+               $this->cliMode = true;
+               $this->connLogger = new \Psr\Log\NullLogger();
+               $this->queryLogger = new \Psr\Log\NullLogger();
+               $this->errorLogger = function ( Exception $e ) {
+                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
+               };
+               $this->currentDomain = DatabaseDomain::newUnspecified();
+       }
+
+       protected function closeConnection() {
+       }
+
+       protected function doQuery( $sql ) {
+       }
+
+       // From DatabaseMysql
+       protected function mysqlConnect( $realServer ) {
+       }
+
+       protected function mysqlSetCharset( $charset ) {
+       }
+
+       protected function mysqlFreeResult( $res ) {
+       }
+
+       protected function mysqlFetchObject( $res ) {
+       }
+
+       protected function mysqlFetchArray( $res ) {
+       }
+
+       protected function mysqlNumRows( $res ) {
+       }
+
+       protected function mysqlNumFields( $res ) {
+       }
+
+       protected function mysqlFieldName( $res, $n ) {
+       }
+
+       protected function mysqlFieldType( $res, $n ) {
+       }
+
+       protected function mysqlDataSeek( $res, $row ) {
+       }
+
+       protected function mysqlError( $conn = null ) {
+       }
+
+       protected function mysqlFetchField( $res, $n ) {
+       }
+
+       protected function mysqlRealEscapeString( $s ) {
+       }
+
+       function insertId() {
+       }
+
+       function lastErrno() {
+       }
+
+       function affectedRows() {
+       }
+
+       function getServerVersion() {
+       }
+}
+
+class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase {
+       /**
+        * @dataProvider provideDiapers
+        * @covers Wikimedia\Rdbms\DatabaseMysqlBase::addIdentifierQuotes
+        */
+       public function testAddIdentifierQuotes( $expected, $in ) {
+               $db = new FakeDatabaseMysqlBase();
+               $quoted = $db->addIdentifierQuotes( $in );
+               $this->assertEquals( $expected, $quoted );
+       }
+
+       /**
+        * Feeds testAddIdentifierQuotes
+        *
+        * Named per T22281 convention.
+        */
+       public static function provideDiapers() {
+               return [
+                       // Format: expected, input
+                       [ '``', '' ],
+
+                       // Yeah I really hate loosely typed PHP idiocies nowadays
+                       [ '``', null ],
+
+                       // Dear codereviewer, guess what addIdentifierQuotes()
+                       // will return with thoses:
+                       [ '``', false ],
+                       [ '`1`', true ],
+
+                       // We never know what could happen
+                       [ '`0`', 0 ],
+                       [ '`1`', 1 ],
+
+                       // Whatchout! Should probably use something more meaningful
+                       [ "`'`", "'" ],  # single quote
+                       [ '`"`', '"' ],  # double quote
+                       [ '````', '`' ], # backtick
+                       [ '`’`', '’' ],  # apostrophe (look at your encyclopedia)
+
+                       // sneaky NUL bytes are lurking everywhere
+                       [ '``', "\0" ],
+                       [ '`xyzzy`', "\0x\0y\0z\0z\0y\0" ],
+
+                       // unicode chars
+                       [
+                               self::createUnicodeString( '`\u0001a\uFFFFb`' ),
+                               self::createUnicodeString( '\u0001a\uFFFFb' )
+                       ],
+                       [
+                               self::createUnicodeString( '`\u0001\uFFFF`' ),
+                               self::createUnicodeString( '\u0001\u0000\uFFFF\u0000' )
+                       ],
+                       [ '`☃`', '☃' ],
+                       [ '`メインページ`', 'メインページ' ],
+                       [ '`Басты_бет`', 'Басты_бет' ],
+
+                       // Real world:
+                       [ '`Alix`', 'Alix' ],  # while( ! $recovered ) { sleep(); }
+                       [ '`Backtick: ```', 'Backtick: `' ],
+                       [ '`This is a test`', 'This is a test' ],
+               ];
+       }
+
+       private static function createUnicodeString( $str ) {
+               return json_decode( '"' . $str . '"' );
+       }
+
+       private function getMockForViews() {
+               $db = $this->getMockBuilder( 'DatabaseMysqli' )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [ 'fetchRow', 'query' ] )
+                       ->getMock();
+
+               $db->method( 'query' )
+                       ->with( $this->anything() )
+                       ->willReturn( new FakeResultWrapper( [
+                               (object)[ 'Tables_in_' => 'view1' ],
+                               (object)[ 'Tables_in_' => 'view2' ],
+                               (object)[ 'Tables_in_' => 'myview' ]
+                       ] ) );
+
+               return $db;
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\DatabaseMysqlBase::listViews
+        */
+       public function testListviews() {
+               $db = $this->getMockForViews();
+
+               $this->assertEquals( [ 'view1', 'view2', 'myview' ],
+                       $db->listViews() );
+
+               // Prefix filtering
+               $this->assertEquals( [ 'view1', 'view2' ],
+                       $db->listViews( 'view' ) );
+               $this->assertEquals( [ 'myview' ],
+                       $db->listViews( 'my' ) );
+               $this->assertEquals( [],
+                       $db->listViews( 'UNUSED_PREFIX' ) );
+               $this->assertEquals( [ 'view1', 'view2', 'myview' ],
+                       $db->listViews( '' ) );
+       }
+
+       /**
+        * @dataProvider provideComparePositions
+        * @covers Wikimedia\Rdbms\MySQLMasterPos
+        */
+       public function testHasReached( MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos, $match ) {
+               if ( $match ) {
+                       $this->assertTrue( $lowerPos->channelsMatch( $higherPos ) );
+
+                       $this->assertTrue( $higherPos->hasReached( $lowerPos ) );
+                       $this->assertTrue( $higherPos->hasReached( $higherPos ) );
+                       $this->assertTrue( $lowerPos->hasReached( $lowerPos ) );
+                       $this->assertFalse( $lowerPos->hasReached( $higherPos ) );
+               } else { // channels don't match
+                       $this->assertFalse( $lowerPos->channelsMatch( $higherPos ) );
+
+                       $this->assertFalse( $higherPos->hasReached( $lowerPos ) );
+                       $this->assertFalse( $lowerPos->hasReached( $higherPos ) );
+               }
+       }
+
+       public static function provideComparePositions() {
+               return [
+                       // Binlog style
+                       [
+                               new MySQLMasterPos( 'db1034-bin.000976', '843431247' ),
+                               new MySQLMasterPos( 'db1034-bin.000976', '843431248' ),
+                               true
+                       ],
+                       [
+                               new MySQLMasterPos( 'db1034-bin.000976', '999' ),
+                               new MySQLMasterPos( 'db1034-bin.000976', '1000' ),
+                               true
+                       ],
+                       [
+                               new MySQLMasterPos( 'db1034-bin.000976', '999' ),
+                               new MySQLMasterPos( 'db1035-bin.000976', '1000' ),
+                               false
+                       ],
+                       // MySQL GTID style
+                       [
+                               new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:23' ),
+                               new MySQLMasterPos( 'db1-bin.2', '2', '3E11FA47-71CA-11E1-9E33-C80AA9429562:24' ),
+                               true
+                       ],
+                       [
+                               new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:99' ),
+                               new MySQLMasterPos( 'db1-bin.2', '2', '3E11FA47-71CA-11E1-9E33-C80AA9429562:100' ),
+                               true
+                       ],
+                       [
+                               new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:99' ),
+                               new MySQLMasterPos( 'db1-bin.2', '2', '1E11FA47-71CA-11E1-9E33-C80AA9429562:100' ),
+                               false
+                       ],
+                       // MariaDB GTID style
+                       [
+                               new MySQLMasterPos( 'db1-bin.2', '1', '255-11-23' ),
+                               new MySQLMasterPos( 'db1-bin.2', '2', '255-11-24' ),
+                               true
+                       ],
+                       [
+                               new MySQLMasterPos( 'db1-bin.2', '1', '255-11-99' ),
+                               new MySQLMasterPos( 'db1-bin.2', '2', '255-11-100' ),
+                               true
+                       ],
+                       [
+                               new MySQLMasterPos( 'db1-bin.2', '1', '255-11-999' ),
+                               new MySQLMasterPos( 'db1-bin.2', '2', '254-11-1000' ),
+                               false
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideChannelPositions
+        * @covers Wikimedia\Rdbms\MySQLMasterPos
+        */
+       public function testChannelsMatch( MySQLMasterPos $pos1, MySQLMasterPos $pos2, $matches ) {
+               $this->assertEquals( $matches, $pos1->channelsMatch( $pos2 ) );
+               $this->assertEquals( $matches, $pos2->channelsMatch( $pos1 ) );
+       }
+
+       public static function provideChannelPositions() {
+               return [
+                       [
+                               new MySQLMasterPos( 'db1034-bin.000876', '44' ),
+                               new MySQLMasterPos( 'db1034-bin.000976', '74' ),
+                               true
+                       ],
+                       [
+                               new MySQLMasterPos( 'db1052-bin.000976', '999' ),
+                               new MySQLMasterPos( 'db1052-bin.000976', '1000' ),
+                               true
+                       ],
+                       [
+                               new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
+                               new MySQLMasterPos( 'db1035-bin.000976', '10000' ),
+                               false
+                       ],
+                       [
+                               new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
+                               new MySQLMasterPos( 'trump2016.000976', '10000' ),
+                               false
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideLagAmounts
+        * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLag
+        * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLagFromPtHeartbeat
+        */
+       public function testPtHeartbeat( $lag ) {
+               $db = $this->getMockBuilder( 'DatabaseMysqli' )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [
+                               'getLagDetectionMethod', 'getHeartbeatData', 'getMasterServerInfo' ] )
+                       ->getMock();
+
+               $db->method( 'getLagDetectionMethod' )
+                       ->willReturn( 'pt-heartbeat' );
+
+               $db->method( 'getMasterServerInfo' )
+                       ->willReturn( [ 'serverId' => 172, 'asOf' => time() ] );
+
+               // Fake the current time.
+               list( $nowSecFrac, $nowSec ) = explode( ' ', microtime() );
+               $now = (float)$nowSec + (float)$nowSecFrac;
+               // Fake the heartbeat time.
+               // Work arounds for weak DataTime microseconds support.
+               $ptTime = $now - $lag;
+               $ptSec = (int)$ptTime;
+               $ptSecFrac = ( $ptTime - $ptSec );
+               $ptDateTime = new DateTime( "@$ptSec" );
+               $ptTimeISO = $ptDateTime->format( 'Y-m-d\TH:i:s' );
+               $ptTimeISO .= ltrim( number_format( $ptSecFrac, 6 ), '0' );
+
+               $db->method( 'getHeartbeatData' )
+                       ->with( [ 'server_id' => 172 ] )
+                       ->willReturn( [ $ptTimeISO, $now ] );
+
+               $db->setLBInfo( 'clusterMasterHost', 'db1052' );
+               $lagEst = $db->getLag();
+
+               $this->assertGreaterThan( $lag - .010, $lagEst, "Correct heatbeat lag" );
+               $this->assertLessThan( $lag + .010, $lagEst, "Correct heatbeat lag" );
+       }
+
+       public static function provideLagAmounts() {
+               return [
+                       [ 0 ],
+                       [ 0.3 ],
+                       [ 6.5 ],
+                       [ 10.1 ],
+                       [ 200.2 ],
+                       [ 400.7 ],
+                       [ 600.22 ],
+                       [ 1000.77 ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php
new file mode 100644 (file)
index 0000000..f519772
--- /dev/null
@@ -0,0 +1,1131 @@
+<?php
+
+use Wikimedia\Rdbms\LikeMatch;
+
+/**
+ * Test the parts of the Database abstract class that deal
+ * with creating SQL text.
+ */
+class DatabaseSQLTest extends PHPUnit_Framework_TestCase {
+       /** @var DatabaseTestHelper */
+       private $database;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->database = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => true ] );
+       }
+
+       protected function assertLastSql( $sqlText ) {
+               $this->assertEquals(
+                       $sqlText,
+                       $this->database->getLastSqls()
+               );
+       }
+
+       protected function assertLastSqlDb( $sqlText, DatabaseTestHelper $db ) {
+               $this->assertEquals( $sqlText, $db->getLastSqls() );
+       }
+
+       /**
+        * @dataProvider provideSelect
+        * @covers Wikimedia\Rdbms\Database::select
+        * @covers Wikimedia\Rdbms\Database::selectSQLText
+        * @covers Wikimedia\Rdbms\Database::tableNamesWithIndexClauseOrJOIN
+        * @covers Wikimedia\Rdbms\Database::makeSelectOptions
+        * @covers Wikimedia\Rdbms\Database::makeOrderBy
+        * @covers Wikimedia\Rdbms\Database::makeGroupByWithHaving
+        */
+       public function testSelect( $sql, $sqlText ) {
+               $this->database->select(
+                       $sql['tables'],
+                       $sql['fields'],
+                       isset( $sql['conds'] ) ? $sql['conds'] : [],
+                       __METHOD__,
+                       isset( $sql['options'] ) ? $sql['options'] : [],
+                       isset( $sql['join_conds'] ) ? $sql['join_conds'] : []
+               );
+               $this->assertLastSql( $sqlText );
+       }
+
+       public static function provideSelect() {
+               return [
+                       [
+                               [
+                                       'tables' => 'table',
+                                       'fields' => [ 'field', 'alias' => 'field2' ],
+                                       'conds' => [ 'alias' => 'text' ],
+                               ],
+                               "SELECT field,field2 AS alias " .
+                                       "FROM table " .
+                                       "WHERE alias = 'text'"
+                       ],
+                       [
+                               [
+                                       // 'tables' with space prepended indicates pre-escaped table name
+                                       'tables' => ' table LEFT JOIN table2',
+                                       'fields' => [ 'field' ],
+                                       'conds' => [ 'field' => 'text' ],
+                               ],
+                               "SELECT field FROM  table LEFT JOIN table2 WHERE field = 'text'"
+                       ],
+                       [
+                               [
+                                       // Empty 'tables' is allowed
+                                       'tables' => '',
+                                       'fields' => [ 'SPECIAL_QUERY()' ],
+                               ],
+                               "SELECT SPECIAL_QUERY()"
+                       ],
+                       [
+                               [
+                                       'tables' => 'table',
+                                       'fields' => [ 'field', 'alias' => 'field2' ],
+                                       'conds' => [ 'alias' => 'text' ],
+                                       'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ],
+                               ],
+                               "SELECT field,field2 AS alias " .
+                                       "FROM table " .
+                                       "WHERE alias = 'text' " .
+                                       "ORDER BY field " .
+                                       "LIMIT 1"
+                       ],
+                       [
+                               [
+                                       'tables' => [ 'table', 't2' => 'table2' ],
+                                       'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
+                                       'conds' => [ 'alias' => 'text' ],
+                                       'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ],
+                                       'join_conds' => [ 't2' => [
+                                               'LEFT JOIN', 'tid = t2.id'
+                                       ] ],
+                               ],
+                               "SELECT tid,field,field2 AS alias,t2.id " .
+                                       "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
+                                       "WHERE alias = 'text' " .
+                                       "ORDER BY field " .
+                                       "LIMIT 1"
+                       ],
+                       [
+                               [
+                                       'tables' => [ 'table', 't2' => 'table2' ],
+                                       'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
+                                       'conds' => [ 'alias' => 'text' ],
+                                       'options' => [ 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ],
+                                       'join_conds' => [ 't2' => [
+                                               'LEFT JOIN', 'tid = t2.id'
+                                       ] ],
+                               ],
+                               "SELECT tid,field,field2 AS alias,t2.id " .
+                                       "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
+                                       "WHERE alias = 'text' " .
+                                       "GROUP BY field HAVING COUNT(*) > 1 " .
+                                       "LIMIT 1"
+                       ],
+                       [
+                               [
+                                       'tables' => [ 'table', 't2' => 'table2' ],
+                                       'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
+                                       'conds' => [ 'alias' => 'text' ],
+                                       'options' => [
+                                               'LIMIT' => 1,
+                                               'GROUP BY' => [ 'field', 'field2' ],
+                                               'HAVING' => [ 'COUNT(*) > 1', 'field' => 1 ]
+                                       ],
+                                       'join_conds' => [ 't2' => [
+                                               'LEFT JOIN', 'tid = t2.id'
+                                       ] ],
+                               ],
+                               "SELECT tid,field,field2 AS alias,t2.id " .
+                                       "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
+                                       "WHERE alias = 'text' " .
+                                       "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " .
+                                       "LIMIT 1"
+                       ],
+                       [
+                               [
+                                       'tables' => [ 'table' ],
+                                       'fields' => [ 'alias' => 'field' ],
+                                       'conds' => [ 'alias' => [ 1, 2, 3, 4 ] ],
+                               ],
+                               "SELECT field AS alias " .
+                                       "FROM table " .
+                                       "WHERE alias IN ('1','2','3','4')"
+                       ],
+                       [
+                               [
+                                       'tables' => 'table',
+                                       'fields' => [ 'field' ],
+                                       'options' => [ 'DISTINCT', 'LOCK IN SHARE MODE' ],
+                               ],
+                               "SELECT DISTINCT field FROM table      LOCK IN SHARE MODE"
+                       ],
+                       [
+                               [
+                                       'tables' => 'table',
+                                       'fields' => [ 'field' ],
+                                       'options' => [ 'EXPLAIN' => true ],
+                               ],
+                               'EXPLAIN SELECT field FROM table'
+                       ],
+                       [
+                               [
+                                       'tables' => 'table',
+                                       'fields' => [ 'field' ],
+                                       'options' => [ 'FOR UPDATE' ],
+                               ],
+                               "SELECT field FROM table      FOR UPDATE"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideUpdate
+        * @covers Wikimedia\Rdbms\Database::update
+        * @covers Wikimedia\Rdbms\Database::makeUpdateOptions
+        * @covers Wikimedia\Rdbms\Database::makeUpdateOptionsArray
+        */
+       public function testUpdate( $sql, $sqlText ) {
+               $this->database->update(
+                       $sql['table'],
+                       $sql['values'],
+                       $sql['conds'],
+                       __METHOD__,
+                       isset( $sql['options'] ) ? $sql['options'] : []
+               );
+               $this->assertLastSql( $sqlText );
+       }
+
+       public static function provideUpdate() {
+               return [
+                       [
+                               [
+                                       'table' => 'table',
+                                       'values' => [ 'field' => 'text', 'field2' => 'text2' ],
+                                       'conds' => [ 'alias' => 'text' ],
+                               ],
+                               "UPDATE table " .
+                                       "SET field = 'text'" .
+                                       ",field2 = 'text2' " .
+                                       "WHERE alias = 'text'"
+                       ],
+                       [
+                               [
+                                       'table' => 'table',
+                                       'values' => [ 'field = other', 'field2' => 'text2' ],
+                                       'conds' => [ 'id' => '1' ],
+                               ],
+                               "UPDATE table " .
+                                       "SET field = other" .
+                                       ",field2 = 'text2' " .
+                                       "WHERE id = '1'"
+                       ],
+                       [
+                               [
+                                       'table' => 'table',
+                                       'values' => [ 'field = other', 'field2' => 'text2' ],
+                                       'conds' => '*',
+                               ],
+                               "UPDATE table " .
+                                       "SET field = other" .
+                                       ",field2 = 'text2'"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideDelete
+        * @covers Wikimedia\Rdbms\Database::delete
+        */
+       public function testDelete( $sql, $sqlText ) {
+               $this->database->delete(
+                       $sql['table'],
+                       $sql['conds'],
+                       __METHOD__
+               );
+               $this->assertLastSql( $sqlText );
+       }
+
+       public static function provideDelete() {
+               return [
+                       [
+                               [
+                                       'table' => 'table',
+                                       'conds' => [ 'alias' => 'text' ],
+                               ],
+                               "DELETE FROM table " .
+                                       "WHERE alias = 'text'"
+                       ],
+                       [
+                               [
+                                       'table' => 'table',
+                                       'conds' => '*',
+                               ],
+                               "DELETE FROM table"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideUpsert
+        * @covers Wikimedia\Rdbms\Database::upsert
+        */
+       public function testUpsert( $sql, $sqlText ) {
+               $this->database->upsert(
+                       $sql['table'],
+                       $sql['rows'],
+                       $sql['uniqueIndexes'],
+                       $sql['set'],
+                       __METHOD__
+               );
+               $this->assertLastSql( $sqlText );
+       }
+
+       public static function provideUpsert() {
+               return [
+                       [
+                               [
+                                       'table' => 'upsert_table',
+                                       'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
+                                       'uniqueIndexes' => [ 'field' ],
+                                       'set' => [ 'field' => 'set' ],
+                               ],
+                               "BEGIN; " .
+                                       "UPDATE upsert_table " .
+                                       "SET field = 'set' " .
+                                       "WHERE ((field = 'text')); " .
+                                       "INSERT IGNORE INTO upsert_table " .
+                                       "(field,field2) " .
+                                       "VALUES ('text','text2'); " .
+                                       "COMMIT"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideDeleteJoin
+        * @covers Wikimedia\Rdbms\Database::deleteJoin
+        */
+       public function testDeleteJoin( $sql, $sqlText ) {
+               $this->database->deleteJoin(
+                       $sql['delTable'],
+                       $sql['joinTable'],
+                       $sql['delVar'],
+                       $sql['joinVar'],
+                       $sql['conds'],
+                       __METHOD__
+               );
+               $this->assertLastSql( $sqlText );
+       }
+
+       public static function provideDeleteJoin() {
+               return [
+                       [
+                               [
+                                       'delTable' => 'table',
+                                       'joinTable' => 'table_join',
+                                       'delVar' => 'field',
+                                       'joinVar' => 'field_join',
+                                       'conds' => [ 'alias' => 'text' ],
+                               ],
+                               "DELETE FROM table " .
+                                       "WHERE field IN (" .
+                                       "SELECT field_join FROM table_join WHERE alias = 'text'" .
+                                       ")"
+                       ],
+                       [
+                               [
+                                       'delTable' => 'table',
+                                       'joinTable' => 'table_join',
+                                       'delVar' => 'field',
+                                       'joinVar' => 'field_join',
+                                       'conds' => '*',
+                               ],
+                               "DELETE FROM table " .
+                                       "WHERE field IN (" .
+                                       "SELECT field_join FROM table_join " .
+                                       ")"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideInsert
+        * @covers Wikimedia\Rdbms\Database::insert
+        * @covers Wikimedia\Rdbms\Database::makeInsertOptions
+        */
+       public function testInsert( $sql, $sqlText ) {
+               $this->database->insert(
+                       $sql['table'],
+                       $sql['rows'],
+                       __METHOD__,
+                       isset( $sql['options'] ) ? $sql['options'] : []
+               );
+               $this->assertLastSql( $sqlText );
+       }
+
+       public static function provideInsert() {
+               return [
+                       [
+                               [
+                                       'table' => 'table',
+                                       'rows' => [ 'field' => 'text', 'field2' => 2 ],
+                               ],
+                               "INSERT INTO table " .
+                                       "(field,field2) " .
+                                       "VALUES ('text','2')"
+                       ],
+                       [
+                               [
+                                       'table' => 'table',
+                                       'rows' => [ 'field' => 'text', 'field2' => 2 ],
+                                       'options' => 'IGNORE',
+                               ],
+                               "INSERT IGNORE INTO table " .
+                                       "(field,field2) " .
+                                       "VALUES ('text','2')"
+                       ],
+                       [
+                               [
+                                       'table' => 'table',
+                                       'rows' => [
+                                               [ 'field' => 'text', 'field2' => 2 ],
+                                               [ 'field' => 'multi', 'field2' => 3 ],
+                                       ],
+                                       'options' => 'IGNORE',
+                               ],
+                               "INSERT IGNORE INTO table " .
+                                       "(field,field2) " .
+                                       "VALUES " .
+                                       "('text','2')," .
+                                       "('multi','3')"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideInsertSelect
+        * @covers Wikimedia\Rdbms\Database::insertSelect
+        * @covers Wikimedia\Rdbms\Database::nativeInsertSelect
+        */
+       public function testInsertSelect( $sql, $sqlTextNative, $sqlSelect, $sqlInsert ) {
+               $this->database->insertSelect(
+                       $sql['destTable'],
+                       $sql['srcTable'],
+                       $sql['varMap'],
+                       $sql['conds'],
+                       __METHOD__,
+                       isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [],
+                       isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [],
+                       isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : []
+               );
+               $this->assertLastSql( $sqlTextNative );
+
+               $dbWeb = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => false ] );
+               $dbWeb->forceNextResult( [
+                       array_flip( array_keys( $sql['varMap'] ) )
+               ] );
+               $dbWeb->insertSelect(
+                       $sql['destTable'],
+                       $sql['srcTable'],
+                       $sql['varMap'],
+                       $sql['conds'],
+                       __METHOD__,
+                       isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [],
+                       isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [],
+                       isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : []
+               );
+               $this->assertLastSqlDb( implode( '; ', [ $sqlSelect, $sqlInsert ] ), $dbWeb );
+       }
+
+       public static function provideInsertSelect() {
+               return [
+                       [
+                               [
+                                       'destTable' => 'insert_table',
+                                       'srcTable' => 'select_table',
+                                       'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
+                                       'conds' => '*',
+                               ],
+                               "INSERT INTO insert_table " .
+                                       "(field_insert,field) " .
+                                       "SELECT field_select,field2 " .
+                                       "FROM select_table WHERE *",
+                               "SELECT field_select AS field_insert,field2 AS field " .
+                               "FROM select_table WHERE *   FOR UPDATE",
+                               "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')"
+                       ],
+                       [
+                               [
+                                       'destTable' => 'insert_table',
+                                       'srcTable' => 'select_table',
+                                       'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
+                                       'conds' => [ 'field' => 2 ],
+                               ],
+                               "INSERT INTO insert_table " .
+                                       "(field_insert,field) " .
+                                       "SELECT field_select,field2 " .
+                                       "FROM select_table " .
+                                       "WHERE field = '2'",
+                               "SELECT field_select AS field_insert,field2 AS field FROM " .
+                               "select_table WHERE field = '2'   FOR UPDATE",
+                               "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')"
+                       ],
+                       [
+                               [
+                                       'destTable' => 'insert_table',
+                                       'srcTable' => 'select_table',
+                                       'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
+                                       'conds' => [ 'field' => 2 ],
+                                       'insertOptions' => 'IGNORE',
+                                       'selectOptions' => [ 'ORDER BY' => 'field' ],
+                               ],
+                               "INSERT IGNORE INTO insert_table " .
+                                       "(field_insert,field) " .
+                                       "SELECT field_select,field2 " .
+                                       "FROM select_table " .
+                                       "WHERE field = '2' " .
+                                       "ORDER BY field",
+                               "SELECT field_select AS field_insert,field2 AS field " .
+                               "FROM select_table WHERE field = '2' ORDER BY field  FOR UPDATE",
+                               "INSERT IGNORE INTO insert_table (field_insert,field) VALUES ('0','1')"
+                       ],
+                       [
+                               [
+                                       'destTable' => 'insert_table',
+                                       'srcTable' => [ 'select_table1', 'select_table2' ],
+                                       'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
+                                       'conds' => [ 'field' => 2 ],
+                                       'selectOptions' => [ 'ORDER BY' => 'field', 'FORCE INDEX' => [ 'select_table1' => 'index1' ] ],
+                                       'selectJoinConds' => [
+                                               'select_table2' => [ 'LEFT JOIN', [ 'select_table1.foo = select_table2.bar' ] ],
+                                       ],
+                               ],
+                               "INSERT INTO insert_table " .
+                                       "(field_insert,field) " .
+                                       "SELECT field_select,field2 " .
+                                       "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " .
+                                       "WHERE field = '2' " .
+                                       "ORDER BY field",
+                               "SELECT field_select AS field_insert,field2 AS field " .
+                               "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " .
+                               "WHERE field = '2' ORDER BY field  FOR UPDATE",
+                               "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideReplace
+        * @covers Wikimedia\Rdbms\Database::replace
+        */
+       public function testReplace( $sql, $sqlText ) {
+               $this->database->replace(
+                       $sql['table'],
+                       $sql['uniqueIndexes'],
+                       $sql['rows'],
+                       __METHOD__
+               );
+               $this->assertLastSql( $sqlText );
+       }
+
+       public static function provideReplace() {
+               return [
+                       [
+                               [
+                                       'table' => 'replace_table',
+                                       'uniqueIndexes' => [ 'field' ],
+                                       'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
+                               ],
+                               "DELETE FROM replace_table " .
+                                       "WHERE ( field='text' ); " .
+                                       "INSERT INTO replace_table " .
+                                       "(field,field2) " .
+                                       "VALUES ('text','text2')"
+                       ],
+                       [
+                               [
+                                       'table' => 'module_deps',
+                                       'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ],
+                                       'rows' => [
+                                               'md_module' => 'module',
+                                               'md_skin' => 'skin',
+                                               'md_deps' => 'deps',
+                                       ],
+                               ],
+                               "DELETE FROM module_deps " .
+                                       "WHERE ( md_module='module' AND md_skin='skin' ); " .
+                                       "INSERT INTO module_deps " .
+                                       "(md_module,md_skin,md_deps) " .
+                                       "VALUES ('module','skin','deps')"
+                       ],
+                       [
+                               [
+                                       'table' => 'module_deps',
+                                       'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ],
+                                       'rows' => [
+                                               [
+                                                       'md_module' => 'module',
+                                                       'md_skin' => 'skin',
+                                                       'md_deps' => 'deps',
+                                               ], [
+                                                       'md_module' => 'module2',
+                                                       'md_skin' => 'skin2',
+                                                       'md_deps' => 'deps2',
+                                               ],
+                                       ],
+                               ],
+                               "DELETE FROM module_deps " .
+                                       "WHERE ( md_module='module' AND md_skin='skin' ); " .
+                                       "INSERT INTO module_deps " .
+                                       "(md_module,md_skin,md_deps) " .
+                                       "VALUES ('module','skin','deps'); " .
+                                       "DELETE FROM module_deps " .
+                                       "WHERE ( md_module='module2' AND md_skin='skin2' ); " .
+                                       "INSERT INTO module_deps " .
+                                       "(md_module,md_skin,md_deps) " .
+                                       "VALUES ('module2','skin2','deps2')"
+                       ],
+                       [
+                               [
+                                       'table' => 'module_deps',
+                                       'uniqueIndexes' => [ 'md_module', 'md_skin' ],
+                                       'rows' => [
+                                               [
+                                                       'md_module' => 'module',
+                                                       'md_skin' => 'skin',
+                                                       'md_deps' => 'deps',
+                                               ], [
+                                                       'md_module' => 'module2',
+                                                       'md_skin' => 'skin2',
+                                                       'md_deps' => 'deps2',
+                                               ],
+                                       ],
+                               ],
+                               "DELETE FROM module_deps " .
+                                       "WHERE ( md_module='module' ) OR ( md_skin='skin' ); " .
+                                       "INSERT INTO module_deps " .
+                                       "(md_module,md_skin,md_deps) " .
+                                       "VALUES ('module','skin','deps'); " .
+                                       "DELETE FROM module_deps " .
+                                       "WHERE ( md_module='module2' ) OR ( md_skin='skin2' ); " .
+                                       "INSERT INTO module_deps " .
+                                       "(md_module,md_skin,md_deps) " .
+                                       "VALUES ('module2','skin2','deps2')"
+                       ],
+                       [
+                               [
+                                       'table' => 'module_deps',
+                                       'uniqueIndexes' => [],
+                                       'rows' => [
+                                               'md_module' => 'module',
+                                               'md_skin' => 'skin',
+                                               'md_deps' => 'deps',
+                                       ],
+                               ],
+                               "INSERT INTO module_deps " .
+                                       "(md_module,md_skin,md_deps) " .
+                                       "VALUES ('module','skin','deps')"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideNativeReplace
+        * @covers Wikimedia\Rdbms\Database::nativeReplace
+        */
+       public function testNativeReplace( $sql, $sqlText ) {
+               $this->database->nativeReplace(
+                       $sql['table'],
+                       $sql['rows'],
+                       __METHOD__
+               );
+               $this->assertLastSql( $sqlText );
+       }
+
+       public static function provideNativeReplace() {
+               return [
+                       [
+                               [
+                                       'table' => 'replace_table',
+                                       'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
+                               ],
+                               "REPLACE INTO replace_table " .
+                                       "(field,field2) " .
+                                       "VALUES ('text','text2')"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideConditional
+        * @covers Wikimedia\Rdbms\Database::conditional
+        */
+       public function testConditional( $sql, $sqlText ) {
+               $this->assertEquals( trim( $this->database->conditional(
+                       $sql['conds'],
+                       $sql['true'],
+                       $sql['false']
+               ) ), $sqlText );
+       }
+
+       public static function provideConditional() {
+               return [
+                       [
+                               [
+                                       'conds' => [ 'field' => 'text' ],
+                                       'true' => 1,
+                                       'false' => 'NULL',
+                               ],
+                               "(CASE WHEN field = 'text' THEN 1 ELSE NULL END)"
+                       ],
+                       [
+                               [
+                                       'conds' => [ 'field' => 'text', 'field2' => 'anothertext' ],
+                                       'true' => 1,
+                                       'false' => 'NULL',
+                               ],
+                               "(CASE WHEN field = 'text' AND field2 = 'anothertext' THEN 1 ELSE NULL END)"
+                       ],
+                       [
+                               [
+                                       'conds' => 'field=1',
+                                       'true' => 1,
+                                       'false' => 'NULL',
+                               ],
+                               "(CASE WHEN field=1 THEN 1 ELSE NULL END)"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideBuildConcat
+        * @covers Wikimedia\Rdbms\Database::buildConcat
+        */
+       public function testBuildConcat( $stringList, $sqlText ) {
+               $this->assertEquals( trim( $this->database->buildConcat(
+                       $stringList
+               ) ), $sqlText );
+       }
+
+       public static function provideBuildConcat() {
+               return [
+                       [
+                               [ 'field', 'field2' ],
+                               "CONCAT(field,field2)"
+                       ],
+                       [
+                               [ "'test'", 'field2' ],
+                               "CONCAT('test',field2)"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideBuildLike
+        * @covers Wikimedia\Rdbms\Database::buildLike
+        * @covers Wikimedia\Rdbms\Database::escapeLikeInternal
+        */
+       public function testBuildLike( $array, $sqlText ) {
+               $this->assertEquals( trim( $this->database->buildLike(
+                       $array
+               ) ), $sqlText );
+       }
+
+       public static function provideBuildLike() {
+               return [
+                       [
+                               'text',
+                               "LIKE 'text' ESCAPE '`'"
+                       ],
+                       [
+                               [ 'text', new LikeMatch( '%' ) ],
+                               "LIKE 'text%' ESCAPE '`'"
+                       ],
+                       [
+                               [ 'text', new LikeMatch( '%' ), 'text2' ],
+                               "LIKE 'text%text2' ESCAPE '`'"
+                       ],
+                       [
+                               [ 'text', new LikeMatch( '_' ) ],
+                               "LIKE 'text_' ESCAPE '`'"
+                       ],
+                       [
+                               'more_text',
+                               "LIKE 'more`_text' ESCAPE '`'"
+                       ],
+                       [
+                               [ 'C:\\Windows\\', new LikeMatch( '%' ) ],
+                               "LIKE 'C:\\Windows\\%' ESCAPE '`'"
+                       ],
+                       [
+                               [ 'accent`_test`', new LikeMatch( '%' ) ],
+                               "LIKE 'accent```_test``%' ESCAPE '`'"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideUnionQueries
+        * @covers Wikimedia\Rdbms\Database::unionQueries
+        */
+       public function testUnionQueries( $sql, $sqlText ) {
+               $this->assertEquals( trim( $this->database->unionQueries(
+                       $sql['sqls'],
+                       $sql['all']
+               ) ), $sqlText );
+       }
+
+       public static function provideUnionQueries() {
+               return [
+                       [
+                               [
+                                       'sqls' => [ 'RAW SQL', 'RAW2SQL' ],
+                                       'all' => true,
+                               ],
+                               "(RAW SQL) UNION ALL (RAW2SQL)"
+                       ],
+                       [
+                               [
+                                       'sqls' => [ 'RAW SQL', 'RAW2SQL' ],
+                                       'all' => false,
+                               ],
+                               "(RAW SQL) UNION (RAW2SQL)"
+                       ],
+                       [
+                               [
+                                       'sqls' => [ 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ],
+                                       'all' => false,
+                               ],
+                               "(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)"
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideUnionConditionPermutations
+        * @covers Wikimedia\Rdbms\Database::unionConditionPermutations
+        */
+       public function testUnionConditionPermutations( $params, $expect ) {
+               if ( isset( $params['unionSupportsOrderAndLimit'] ) ) {
+                       $this->database->setUnionSupportsOrderAndLimit( $params['unionSupportsOrderAndLimit'] );
+               }
+
+               $sql = trim( $this->database->unionConditionPermutations(
+                       $params['table'],
+                       $params['vars'],
+                       $params['permute_conds'],
+                       isset( $params['extra_conds'] ) ? $params['extra_conds'] : '',
+                       'FNAME',
+                       isset( $params['options'] ) ? $params['options'] : [],
+                       isset( $params['join_conds'] ) ? $params['join_conds'] : []
+               ) );
+               $this->assertEquals( $expect, $sql );
+       }
+
+       public static function provideUnionConditionPermutations() {
+               return [
+                       // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
+                       [
+                               [
+                                       'table' => [ 'table1', 'table2' ],
+                                       'vars' => [ 'field1', 'alias' => 'field2' ],
+                                       'permute_conds' => [
+                                               'field3' => [ 1, 2, 3 ],
+                                               'duplicates' => [ 4, 5, 4 ],
+                                               'empty' => [],
+                                               'single' => [ 0 ],
+                                       ],
+                                       'extra_conds' => 'table2.bar > 23',
+                                       'options' => [
+                                               'ORDER BY' => [ 'field1', 'alias' ],
+                                               'INNER ORDER BY' => [ 'field1', 'field2' ],
+                                               'LIMIT' => 100,
+                                       ],
+                                       'join_conds' => [
+                                               'table2' => [ 'JOIN', 'table1.foo_id = table2.foo_id' ],
+                                       ],
+                               ],
+                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '1' AND duplicates = '4' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) UNION ALL " .
+                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '1' AND duplicates = '5' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) UNION ALL " .
+                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '2' AND duplicates = '4' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) UNION ALL " .
+                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '2' AND duplicates = '5' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) UNION ALL " .
+                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '3' AND duplicates = '4' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) UNION ALL " .
+                               "(SELECT  field1,field2 AS alias  FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id))   WHERE field3 = '3' AND duplicates = '5' AND single = '0' AND (table2.bar > 23)  ORDER BY field1,field2 LIMIT 100  ) " .
+                               "ORDER BY field1,alias LIMIT 100"
+                       ],
+                       [
+                               [
+                                       'table' => 'foo',
+                                       'vars' => [ 'foo_id' ],
+                                       'permute_conds' => [
+                                               'bar' => [ 1, 2, 3 ],
+                                       ],
+                                       'extra_conds' => [ 'baz' => null ],
+                                       'options' => [
+                                               'NOTALL',
+                                               'ORDER BY' => [ 'foo_id' ],
+                                               'LIMIT' => 25,
+                                       ],
+                               ],
+                               "(SELECT  foo_id  FROM foo    WHERE bar = '1' AND baz IS NULL  ORDER BY foo_id LIMIT 25  ) UNION " .
+                               "(SELECT  foo_id  FROM foo    WHERE bar = '2' AND baz IS NULL  ORDER BY foo_id LIMIT 25  ) UNION " .
+                               "(SELECT  foo_id  FROM foo    WHERE bar = '3' AND baz IS NULL  ORDER BY foo_id LIMIT 25  ) " .
+                               "ORDER BY foo_id LIMIT 25"
+                       ],
+                       [
+                               [
+                                       'table' => 'foo',
+                                       'vars' => [ 'foo_id' ],
+                                       'permute_conds' => [
+                                               'bar' => [ 1, 2, 3 ],
+                                       ],
+                                       'extra_conds' => [ 'baz' => null ],
+                                       'options' => [
+                                               'NOTALL' => true,
+                                               'ORDER BY' => [ 'foo_id' ],
+                                               'LIMIT' => 25,
+                                       ],
+                                       'unionSupportsOrderAndLimit' => false,
+                               ],
+                               "(SELECT  foo_id  FROM foo    WHERE bar = '1' AND baz IS NULL  ) UNION " .
+                               "(SELECT  foo_id  FROM foo    WHERE bar = '2' AND baz IS NULL  ) UNION " .
+                               "(SELECT  foo_id  FROM foo    WHERE bar = '3' AND baz IS NULL  ) " .
+                               "ORDER BY foo_id LIMIT 25"
+                       ],
+                       [
+                               [
+                                       'table' => 'foo',
+                                       'vars' => [ 'foo_id' ],
+                                       'permute_conds' => [],
+                                       'extra_conds' => [ 'baz' => null ],
+                                       'options' => [
+                                               'ORDER BY' => [ 'foo_id' ],
+                                               'LIMIT' => 25,
+                                       ],
+                               ],
+                               "SELECT  foo_id  FROM foo    WHERE baz IS NULL  ORDER BY foo_id LIMIT 25"
+                       ],
+                       [
+                               [
+                                       'table' => 'foo',
+                                       'vars' => [ 'foo_id' ],
+                                       'permute_conds' => [
+                                               'bar' => [],
+                                       ],
+                                       'extra_conds' => [ 'baz' => null ],
+                                       'options' => [
+                                               'ORDER BY' => [ 'foo_id' ],
+                                               'LIMIT' => 25,
+                                       ],
+                               ],
+                               "SELECT  foo_id  FROM foo    WHERE baz IS NULL  ORDER BY foo_id LIMIT 25"
+                       ],
+                       [
+                               [
+                                       'table' => 'foo',
+                                       'vars' => [ 'foo_id' ],
+                                       'permute_conds' => [
+                                               'bar' => [ 1 ],
+                                       ],
+                                       'options' => [
+                                               'ORDER BY' => [ 'foo_id' ],
+                                               'LIMIT' => 25,
+                                               'OFFSET' => 150,
+                                       ],
+                               ],
+                               "SELECT  foo_id  FROM foo    WHERE bar = '1'  ORDER BY foo_id LIMIT 150,25"
+                       ],
+                       [
+                               [
+                                       'table' => 'foo',
+                                       'vars' => [ 'foo_id' ],
+                                       'permute_conds' => [],
+                                       'extra_conds' => [ 'baz' => null ],
+                                       'options' => [
+                                               'ORDER BY' => [ 'foo_id' ],
+                                               'LIMIT' => 25,
+                                               'OFFSET' => 150,
+                                               'INNER ORDER BY' => [ 'bar_id' ],
+                                       ],
+                               ],
+                               "(SELECT  foo_id  FROM foo    WHERE baz IS NULL  ORDER BY bar_id LIMIT 175  ) ORDER BY foo_id LIMIT 150,25"
+                       ],
+                       [
+                               [
+                                       'table' => 'foo',
+                                       'vars' => [ 'foo_id' ],
+                                       'permute_conds' => [],
+                                       'extra_conds' => [ 'baz' => null ],
+                                       'options' => [
+                                               'ORDER BY' => [ 'foo_id' ],
+                                               'LIMIT' => 25,
+                                               'OFFSET' => 150,
+                                               'INNER ORDER BY' => [ 'bar_id' ],
+                                       ],
+                                       'unionSupportsOrderAndLimit' => false,
+                               ],
+                               "SELECT  foo_id  FROM foo    WHERE baz IS NULL  ORDER BY foo_id LIMIT 150,25"
+                       ],
+                       // @codingStandardsIgnoreEnd
+               ];
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::commit
+        * @covers Wikimedia\Rdbms\Database::doCommit
+        */
+       public function testTransactionCommit() {
+               $this->database->begin( __METHOD__ );
+               $this->database->commit( __METHOD__ );
+               $this->assertLastSql( 'BEGIN; COMMIT' );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::rollback
+        * @covers Wikimedia\Rdbms\Database::doRollback
+        */
+       public function testTransactionRollback() {
+               $this->database->begin( __METHOD__ );
+               $this->database->rollback( __METHOD__ );
+               $this->assertLastSql( 'BEGIN; ROLLBACK' );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::dropTable
+        */
+       public function testDropTable() {
+               $this->database->setExistingTables( [ 'table' ] );
+               $this->database->dropTable( 'table', __METHOD__ );
+               $this->assertLastSql( 'DROP TABLE table CASCADE' );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::dropTable
+        */
+       public function testDropNonExistingTable() {
+               $this->assertFalse(
+                       $this->database->dropTable( 'non_existing', __METHOD__ )
+               );
+       }
+
+       /**
+        * @dataProvider provideMakeList
+        * @covers Wikimedia\Rdbms\Database::makeList
+        */
+       public function testMakeList( $list, $mode, $sqlText ) {
+               $this->assertEquals( trim( $this->database->makeList(
+                       $list, $mode
+               ) ), $sqlText );
+       }
+
+       public static function provideMakeList() {
+               return [
+                       [
+                               [ 'value', 'value2' ],
+                               LIST_COMMA,
+                               "'value','value2'"
+                       ],
+                       [
+                               [ 'field', 'field2' ],
+                               LIST_NAMES,
+                               "field,field2"
+                       ],
+                       [
+                               [ 'field' => 'value', 'field2' => 'value2' ],
+                               LIST_AND,
+                               "field = 'value' AND field2 = 'value2'"
+                       ],
+                       [
+                               [ 'field' => null, "field2 != 'value2'" ],
+                               LIST_AND,
+                               "field IS NULL AND (field2 != 'value2')"
+                       ],
+                       [
+                               [ 'field' => [ 'value', null, 'value2' ], 'field2' => 'value2' ],
+                               LIST_AND,
+                               "(field IN ('value','value2')  OR field IS NULL) AND field2 = 'value2'"
+                       ],
+                       [
+                               [ 'field' => [ null ], 'field2' => null ],
+                               LIST_AND,
+                               "field IS NULL AND field2 IS NULL"
+                       ],
+                       [
+                               [ 'field' => 'value', 'field2' => 'value2' ],
+                               LIST_OR,
+                               "field = 'value' OR field2 = 'value2'"
+                       ],
+                       [
+                               [ 'field' => 'value', 'field2' => null ],
+                               LIST_OR,
+                               "field = 'value' OR field2 IS NULL"
+                       ],
+                       [
+                               [ 'field' => [ 'value', 'value2' ], 'field2' => [ 'value' ] ],
+                               LIST_OR,
+                               "field IN ('value','value2')  OR field2 = 'value'"
+                       ],
+                       [
+                               [ 'field' => [ null, 'value', null, 'value2' ], "field2 != 'value2'" ],
+                               LIST_OR,
+                               "(field IN ('value','value2')  OR field IS NULL) OR (field2 != 'value2')"
+                       ],
+                       [
+                               [ 'field' => 'value', 'field2' => 'value2' ],
+                               LIST_SET,
+                               "field = 'value',field2 = 'value2'"
+                       ],
+                       [
+                               [ 'field' => 'value', 'field2' => null ],
+                               LIST_SET,
+                               "field = 'value',field2 = NULL"
+                       ],
+                       [
+                               [ 'field' => 'value', "field2 != 'value2'" ],
+                               LIST_SET,
+                               "field = 'value',field2 != 'value2'"
+                       ],
+               ];
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::registerTempTableOperation
+        */
+       public function testSessionTempTables() {
+               $temp1 = $this->database->tableName( 'tmp_table_1' );
+               $temp2 = $this->database->tableName( 'tmp_table_2' );
+               $temp3 = $this->database->tableName( 'tmp_table_3' );
+
+               $this->database->query( "CREATE TEMPORARY TABLE $temp1 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE $temp2 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE $temp3 LIKE orig_tbl", __METHOD__ );
+
+               $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+
+               $this->database->dropTable( 'tmp_table_1', __METHOD__ );
+               $this->database->dropTable( 'tmp_table_2', __METHOD__ );
+               $this->database->dropTable( 'tmp_table_3', __METHOD__ );
+
+               $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+
+               $this->database->query( "CREATE TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ );
+
+               $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+
+               $this->database->query( "DROP TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "DROP TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "DROP TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ );
+
+               $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+       }
+}
diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php
new file mode 100644 (file)
index 0000000..9bea7ff
--- /dev/null
@@ -0,0 +1,355 @@
+<?php
+
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\TransactionProfiler;
+use Wikimedia\TestingAccessWrapper;
+
+class DatabaseTest extends PHPUnit_Framework_TestCase {
+
+       protected function setUp() {
+               $this->db = new DatabaseTestHelper( __CLASS__ . '::' . $this->getName() );
+       }
+
+       public static function provideAddQuotes() {
+               return [
+                       [ null, 'NULL' ],
+                       [ 1234, "'1234'" ],
+                       [ 1234.5678, "'1234.5678'" ],
+                       [ 'string', "'string'" ],
+                       [ 'string\'s cause trouble', "'string\'s cause trouble'" ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideAddQuotes
+        * @covers Wikimedia\Rdbms\Database::addQuotes
+        */
+       public function testAddQuotes( $input, $expected ) {
+               $this->assertEquals( $expected, $this->db->addQuotes( $input ) );
+       }
+
+       public static function provideTableName() {
+               // Formatting is mostly ignored since addIdentifierQuotes is abstract.
+               // For testing of addIdentifierQuotes, see actual Database subclas tests.
+               return [
+                       'local' => [
+                               'tablename',
+                               'tablename',
+                               'quoted',
+                       ],
+                       'local-raw' => [
+                               'tablename',
+                               'tablename',
+                               'raw',
+                       ],
+                       'shared' => [
+                               'sharedb.tablename',
+                               'tablename',
+                               'quoted',
+                               [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ],
+                       ],
+                       'shared-raw' => [
+                               'sharedb.tablename',
+                               'tablename',
+                               'raw',
+                               [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ],
+                       ],
+                       'shared-prefix' => [
+                               'sharedb.sh_tablename',
+                               'tablename',
+                               'quoted',
+                               [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ],
+                       ],
+                       'shared-prefix-raw' => [
+                               'sharedb.sh_tablename',
+                               'tablename',
+                               'raw',
+                               [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ],
+                       ],
+                       'foreign' => [
+                               'databasename.tablename',
+                               'databasename.tablename',
+                               'quoted',
+                       ],
+                       'foreign-raw' => [
+                               'databasename.tablename',
+                               'databasename.tablename',
+                               'raw',
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideTableName
+        * @covers Wikimedia\Rdbms\Database::tableName
+        */
+       public function testTableName( $expected, $table, $format, array $alias = null ) {
+               if ( $alias ) {
+                       $this->db->setTableAliases( [ $table => $alias ] );
+               }
+               $this->assertEquals(
+                       $expected,
+                       $this->db->tableName( $table, $format ?: 'quoted' )
+               );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::onTransactionIdle
+        * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
+        */
+       public function testTransactionIdle() {
+               $db = $this->db;
+
+               $db->setFlag( DBO_TRX );
+               $called = false;
+               $flagSet = null;
+               $db->onTransactionIdle(
+                       function () use ( $db, &$flagSet, &$called ) {
+                               $called = true;
+                               $flagSet = $db->getFlag( DBO_TRX );
+                       },
+                       __METHOD__
+               );
+               $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
+               $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
+               $this->assertTrue( $called, 'Callback reached' );
+
+               $db->clearFlag( DBO_TRX );
+               $flagSet = null;
+               $db->onTransactionIdle(
+                       function () use ( $db, &$flagSet ) {
+                               $flagSet = $db->getFlag( DBO_TRX );
+                       },
+                       __METHOD__
+               );
+               $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
+               $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
+
+               $db->clearFlag( DBO_TRX );
+               $db->onTransactionIdle(
+                       function () use ( $db ) {
+                               $db->setFlag( DBO_TRX );
+                       },
+                       __METHOD__
+               );
+               $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::onTransactionResolution
+        * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
+        */
+       public function testTransactionResolution() {
+               $db = $this->db;
+
+               $db->clearFlag( DBO_TRX );
+               $db->begin( __METHOD__ );
+               $called = false;
+               $db->onTransactionResolution( function () use ( $db, &$called ) {
+                       $called = true;
+                       $db->setFlag( DBO_TRX );
+               } );
+               $db->commit( __METHOD__ );
+               $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
+               $this->assertTrue( $called, 'Callback reached' );
+
+               $db->clearFlag( DBO_TRX );
+               $db->begin( __METHOD__ );
+               $called = false;
+               $db->onTransactionResolution( function () use ( $db, &$called ) {
+                       $called = true;
+                       $db->setFlag( DBO_TRX );
+               } );
+               $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
+               $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
+               $this->assertTrue( $called, 'Callback reached' );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::setTransactionListener
+        */
+       public function testTransactionListener() {
+               $db = $this->db;
+
+               $db->setTransactionListener( 'ping', function () use ( $db, &$called ) {
+                       $called = true;
+               } );
+
+               $called = false;
+               $db->begin( __METHOD__ );
+               $db->commit( __METHOD__ );
+               $this->assertTrue( $called, 'Callback reached' );
+
+               $called = false;
+               $db->begin( __METHOD__ );
+               $db->commit( __METHOD__ );
+               $this->assertTrue( $called, 'Callback still reached' );
+
+               $called = false;
+               $db->begin( __METHOD__ );
+               $db->rollback( __METHOD__ );
+               $this->assertTrue( $called, 'Callback reached' );
+
+               $db->setTransactionListener( 'ping', null );
+               $called = false;
+               $db->begin( __METHOD__ );
+               $db->commit( __METHOD__ );
+               $this->assertFalse( $called, 'Callback not reached' );
+       }
+
+       /**
+        * Use this mock instead of DatabaseTestHelper for cases where
+        * DatabaseTestHelper is too inflexibile due to mocking too much
+        * or being too restrictive about fname matching (e.g. for tests
+        * that assert behaviour when the name is a mismatch, we need to
+        * catch the error here instead of there).
+        *
+        * @return Database
+        */
+       private function getMockDB( $methods = [] ) {
+               static $abstractMethods = [
+                       'affectedRows',
+                       'closeConnection',
+                       'dataSeek',
+                       'doQuery',
+                       'fetchObject', 'fetchRow',
+                       'fieldInfo', 'fieldName',
+                       'getSoftwareLink', 'getServerVersion',
+                       'getType',
+                       'indexInfo',
+                       'insertId',
+                       'lastError', 'lastErrno',
+                       'numFields', 'numRows',
+                       'open',
+                       'strencode',
+               ];
+               $db = $this->getMockBuilder( Database::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( array_values( array_unique( array_merge(
+                               $abstractMethods,
+                               $methods
+                       ) ) ) )
+                       ->getMock();
+               $wdb = TestingAccessWrapper::newFromObject( $db );
+               $wdb->trxProfiler = new TransactionProfiler();
+               $wdb->connLogger = new \Psr\Log\NullLogger();
+               $wdb->queryLogger = new \Psr\Log\NullLogger();
+               return $db;
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::flushSnapshot
+        */
+       public function testFlushSnapshot() {
+               $db = $this->getMockDB( [ 'isOpen' ] );
+               $db->method( 'isOpen' )->willReturn( true );
+
+               $db->flushSnapshot( __METHOD__ ); // ok
+               $db->flushSnapshot( __METHOD__ ); // ok
+
+               $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
+               $db->query( 'SELECT 1', __METHOD__ );
+               $this->assertTrue( (bool)$db->trxLevel(), "Transaction started." );
+               $db->flushSnapshot( __METHOD__ ); // ok
+               $db->restoreFlags( $db::RESTORE_PRIOR );
+
+               $this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." );
+       }
+
+       public function testGetScopedLock() {
+               $db = $this->getMockDB( [ 'isOpen' ] );
+               $db->method( 'isOpen' )->willReturn( true );
+
+               $db->setFlag( DBO_TRX );
+               try {
+                       $this->badLockingMethodImplicit( $db );
+               } catch ( RunTimeException $e ) {
+                       $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
+               }
+               $db->clearFlag( DBO_TRX );
+               $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
+               $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
+
+               try {
+                       $this->badLockingMethodExplicit( $db );
+               } catch ( RunTimeException $e ) {
+                       $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
+               }
+               $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
+               $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
+       }
+
+       private function badLockingMethodImplicit( IDatabase $db ) {
+               $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
+               $db->query( "SELECT 1" ); // trigger DBO_TRX
+               throw new RunTimeException( "Uh oh!" );
+       }
+
+       private function badLockingMethodExplicit( IDatabase $db ) {
+               $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
+               $db->begin( __METHOD__ );
+               throw new RunTimeException( "Uh oh!" );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::getFlag
+        * @covers Wikimedia\Rdbms\Database::setFlag
+        * @covers Wikimedia\Rdbms\Database::restoreFlags
+        */
+       public function testFlagSetting() {
+               $db = $this->db;
+               $origTrx = $db->getFlag( DBO_TRX );
+               $origSsl = $db->getFlag( DBO_SSL );
+
+               $origTrx
+                       ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
+                       : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
+               $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
+
+               $origSsl
+                       ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
+                       : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
+               $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) );
+
+               $db->restoreFlags( $db::RESTORE_INITIAL );
+               $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
+               $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
+
+               $origTrx
+                       ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
+                       : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
+               $origSsl
+                       ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
+                       : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
+
+               $db->restoreFlags();
+               $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
+               $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
+
+               $db->restoreFlags();
+               $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
+               $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::tablePrefix
+        * @covers Wikimedia\Rdbms\Database::dbSchema
+        */
+       public function testMutators() {
+               $old = $this->db->tablePrefix();
+               $this->assertInternalType( 'string', $old, 'Prefix is string' );
+               $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" );
+               $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) );
+               $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" );
+               $this->db->tablePrefix( $old );
+               $this->assertNotEquals( 'xxx', $this->db->tablePrefix() );
+
+               $old = $this->db->dbSchema();
+               $this->assertInternalType( 'string', $old, 'Schema is string' );
+               $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" );
+               $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) );
+               $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" );
+               $this->db->dbSchema( $old );
+               $this->assertNotEquals( 'xxx', $this->db->dbSchema() );
+       }
+}
index 556a348..3079d8f 100644 (file)
@@ -625,15 +625,15 @@ more stuff
                return [
                        [ 'Help:WikiPageTest_testReplaceSection',
                                CONTENT_MODEL_WIKITEXT,
-                               WikiPageTest::$sections,
+                               self::$sections,
                                "0",
                                "No more",
                                null,
-                               trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) )
+                               trim( preg_replace( '/^Intro/sm', 'No more', self::$sections ) )
                        ],
                        [ 'Help:WikiPageTest_testReplaceSection',
                                CONTENT_MODEL_WIKITEXT,
-                               WikiPageTest::$sections,
+                               self::$sections,
                                "",
                                "No more",
                                null,
@@ -641,29 +641,29 @@ more stuff
                        ],
                        [ 'Help:WikiPageTest_testReplaceSection',
                                CONTENT_MODEL_WIKITEXT,
-                               WikiPageTest::$sections,
+                               self::$sections,
                                "2",
                                "== TEST ==\nmore fun",
                                null,
                                trim( preg_replace( '/^== test ==.*== foo ==/sm',
                                        "== TEST ==\nmore fun\n\n== foo ==",
-                                       WikiPageTest::$sections ) )
+                                       self::$sections ) )
                        ],
                        [ 'Help:WikiPageTest_testReplaceSection',
                                CONTENT_MODEL_WIKITEXT,
-                               WikiPageTest::$sections,
+                               self::$sections,
                                "8",
                                "No more",
                                null,
-                               trim( WikiPageTest::$sections )
+                               trim( self::$sections )
                        ],
                        [ 'Help:WikiPageTest_testReplaceSection',
                                CONTENT_MODEL_WIKITEXT,
-                               WikiPageTest::$sections,
+                               self::$sections,
                                "new",
                                "No more",
                                "New",
-                               trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more"
+                               trim( self::$sections ) . "\n\n== New ==\n\nNo more"
                        ],
                ];
        }
index 3e0d883..3530d3c 100644 (file)
@@ -43,6 +43,7 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                        'test.top' => [ 'position' => 'top' ],
                        'test.private.top' => [ 'group' => 'private', 'position' => 'top' ],
                        'test.private.bottom' => [ 'group' => 'private', 'position' => 'bottom' ],
+                       'test.shouldembed' => [ 'shouldEmbed' => true ],
 
                        'test.styles.pure' => [ 'type' => ResourceLoaderModule::LOAD_STYLES ],
                        'test.styles.mixed' => [],
@@ -64,12 +65,24 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                                'group' => 'private',
                                'styles' => '.private{}',
                        ],
+                       'test.styles.shouldembed' => [
+                               'type' => ResourceLoaderModule::LOAD_STYLES,
+                               'shouldEmbed' => true,
+                               'styles' => '.shouldembed{}',
+                       ],
 
                        'test.scripts' => [],
                        'test.scripts.top' => [ 'position' => 'top' ],
                        'test.scripts.user' => [ 'group' => 'user' ],
                        'test.scripts.user.empty' => [ 'group' => 'user', 'isKnownEmpty' => true ],
                        'test.scripts.raw' => [ 'isRaw' => true ],
+                       'test.scripts.shouldembed' => [ 'shouldEmbed' => true ],
+
+                       'test.ordering.a' => [ 'shouldEmbed' => false ],
+                       'test.ordering.b' => [ 'shouldEmbed' => false ],
+                       'test.ordering.c' => [ 'shouldEmbed' => true, 'styles' => '.orderingC{}' ],
+                       'test.ordering.d' => [ 'shouldEmbed' => true, 'styles' => '.orderingD{}' ],
+                       'test.ordering.e' => [ 'shouldEmbed' => false ],
                ];
                return array_map( function ( $options ) {
                        return self::makeModule( $options );
@@ -102,6 +115,7 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                        'test.private.bottom',
                        'test.private.top',
                        'test.top',
+                       'test.shouldembed',
                        'test.unregistered',
                ] );
                $client->setModuleStyles( [
@@ -109,12 +123,14 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                        'test.styles.user.empty',
                        'test.styles.private',
                        'test.styles.pure',
+                       'test.styles.shouldembed',
                        'test.unregistered.styles',
                ] );
                $client->setModuleScripts( [
                        'test.scripts',
                        'test.scripts.user.empty',
                        'test.scripts.top',
+                       'test.scripts.shouldembed',
                        'test.unregistered.scripts',
                ] );
 
@@ -122,12 +138,15 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                        'states' => [
                                'test.private.top' => 'loading',
                                'test.private.bottom' => 'loading',
+                               'test.shouldembed' => 'loading',
                                'test.styles.pure' => 'ready',
                                'test.styles.user.empty' => 'ready',
                                'test.styles.private' => 'ready',
+                               'test.styles.shouldembed' => 'ready',
                                'test.scripts' => 'loading',
                                'test.scripts.top' => 'loading',
                                'test.scripts.user.empty' => 'ready',
+                               'test.scripts.shouldembed' => 'loading',
                        ],
                        'general' => [
                                'test',
@@ -139,12 +158,14 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                        'scripts' => [
                                'test.scripts',
                                'test.scripts.top',
+                               'test.scripts.shouldembed',
                        ],
                        'embed' => [
-                               'styles' => [ 'test.styles.private' ],
+                               'styles' => [ 'test.styles.private', 'test.styles.shouldembed' ],
                                'general' => [
                                        'test.private.bottom',
                                        'test.private.top',
+                                       'test.shouldembed',
                                ],
                        ],
                ];
@@ -276,6 +297,47 @@ class ResourceLoaderClientHtmlTest extends PHPUnit_Framework_TestCase {
                                'only' => ResourceLoaderModule::TYPE_STYLES,
                                'output' => '<noscript><link rel="stylesheet" href="/w/load.php?debug=false&amp;lang=nl&amp;modules=test.styles.noscript&amp;only=styles&amp;skin=fallback"/></noscript>',
                        ],
+                       [
+                               'context' => [],
+                               'modules' => [ 'test.shouldembed' ],
+                               'only' => ResourceLoaderModule::TYPE_COMBINED,
+                               'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.shouldembed@09p30q0",function($,jQuery,require,module){},{"css":[]});});</script>',
+                       ],
+                       [
+                               'context' => [],
+                               'modules' => [ 'test.styles.shouldembed' ],
+                               'only' => ResourceLoaderModule::TYPE_STYLES,
+                               'output' => '<style>.shouldembed{}</style>',
+                       ],
+                       [
+                               'context' => [],
+                               'modules' => [ 'test.scripts.shouldembed' ],
+                               'only' => ResourceLoaderModule::TYPE_SCRIPTS,
+                               'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.state({"test.scripts.shouldembed":"ready"});});</script>',
+                       ],
+                       [
+                               'context' => [],
+                               'modules' => [ 'test', 'test.shouldembed' ],
+                               'only' => ResourceLoaderModule::TYPE_COMBINED,
+                               'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?debug=false\u0026lang=nl\u0026modules=test\u0026skin=fallback");mw.loader.implement("test.shouldembed@09p30q0",function($,jQuery,require,module){},{"css":[]});});</script>',
+                       ],
+                       [
+                               'context' => [],
+                               'modules' => [ 'test.styles.pure', 'test.styles.shouldembed' ],
+                               'only' => ResourceLoaderModule::TYPE_STYLES,
+                               'output' =>
+                                       '<link rel="stylesheet" href="/w/load.php?debug=false&amp;lang=nl&amp;modules=test.styles.pure&amp;only=styles&amp;skin=fallback"/>' . "\n"
+                                       . '<style>.shouldembed{}</style>'
+                       ],
+                       [
+                               'context' => [],
+                               'modules' => [ 'test.ordering.a', 'test.ordering.e', 'test.ordering.b', 'test.ordering.d', 'test.ordering.c' ],
+                               'only' => ResourceLoaderModule::TYPE_STYLES,
+                               'output' =>
+                                       '<link rel="stylesheet" href="/w/load.php?debug=false&amp;lang=nl&amp;modules=test.ordering.a%2Cb&amp;only=styles&amp;skin=fallback"/>' . "\n"
+                                       . '<style>.orderingC{}.orderingD{}</style>' . "\n"
+                                       . '<link rel="stylesheet" href="/w/load.php?debug=false&amp;lang=nl&amp;modules=test.ordering.e&amp;only=styles&amp;skin=fallback"/>'
+                       ],
                        // @codingStandardsIgnoreEnd
                ];
        }
index 6597906..39c3421 100644 (file)
@@ -109,6 +109,6 @@ class TestSites {
        public static function insertIntoDb() {
                $sitesTable = \MediaWiki\MediaWikiServices::getInstance()->getSiteStore();
                $sitesTable->clear();
-               $sitesTable->saveSites( TestSites::getSites() );
+               $sitesTable->saveSites( self::getSites() );
        }
 }
index 85becff..a9a612d 100644 (file)
@@ -27,6 +27,8 @@ class SpecialRecentchangesTest extends AbstractChangesListSpecialPageTestCase {
 
                        [ 'days=3', [ 'days' => '3' ] ],
 
+                       [ 'days=0.25', [ 'days' => '0.25'] ],
+
                        [ 'namespace=5', [ 'namespace' => '5' ] ],
 
                        [ 'namespace=5|3', [ 'namespace' => '5|3' ] ],
diff --git a/tests/phpunit/structure/DatabaseIntegrationTest.php b/tests/phpunit/structure/DatabaseIntegrationTest.php
new file mode 100644 (file)
index 0000000..b0c1c8f
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\Database;
+
+/**
+ * @group Database
+ */
+class DatabaseIntegrationTest extends MediaWikiTestCase {
+       /**
+        * @var Database
+        */
+       protected $db;
+
+       private $functionTest = false;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->db = wfGetDB( DB_MASTER );
+       }
+
+       protected function tearDown() {
+               parent::tearDown();
+               if ( $this->functionTest ) {
+                       $this->dropFunctions();
+                       $this->functionTest = false;
+               }
+               $this->db->restoreFlags( IDatabase::RESTORE_INITIAL );
+       }
+
+       public function testStoredFunctions() {
+               if ( !in_array( wfGetDB( DB_MASTER )->getType(), [ 'mysql', 'postgres' ] ) ) {
+                       $this->markTestSkipped( 'MySQL or Postgres required' );
+               }
+               global $IP;
+               $this->dropFunctions();
+               $this->functionTest = true;
+               $this->assertTrue(
+                       $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" )
+               );
+               $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ );
+               $this->assertEquals( 42, $res->fetchObject()->test );
+       }
+
+       private function dropFunctions() {
+               $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function'
+                       . ( $this->db->getType() == 'postgres' ? '()' : '' )
+               );
+       }
+
+       public function testUnknownTableCorruptsResults() {
+               $res = $this->db->select( 'page', '*', [ 'page_id' => 1 ] );
+               $this->assertFalse( $this->db->tableExists( 'foobarbaz' ) );
+               $this->assertInternalType( 'int', $res->numRows() );
+       }
+}
index 2f19c81..251a4a2 100644 (file)
@@ -68,7 +68,6 @@
                        <directory suffix=".php">../../includes</directory>
                        <directory suffix=".php">../../languages</directory>
                        <directory suffix=".php">../../maintenance</directory>
-                       <directory suffix=".php">../../skins</directory>
                </whitelist>
        </filter>
 </phpunit>
index f023ddd..e944ef0 100644 (file)
@@ -2,7 +2,23 @@
 ( function ( $, mw, QUnit ) {
        'use strict';
 
-       var addons;
+       var addons, nested;
+
+       /**
+        * Make a safe copy of localEnv:
+        * - Creates a copy so that when the same object reference to module hooks is
+        *   used by multipe test hooks, our QUnit.module extension will not wrap the
+        *   callbacks multiple times. Instead, they wrap using a new object.
+        * - Normalise setup/teardown to avoid having to repeat this in each extension
+        *   (deprecated in QUnit 1.16, removed in QUnit 2).
+        * - Strip any other properties.
+        */
+       function makeSafeEnv( localEnv ) {
+               return {
+                       beforeEach: localEnv.setup || localEnv.beforeEach,
+                       afterEach: localEnv.teardown || localEnv.afterEach
+               };
+       }
 
        /**
         * Add bogus to url to prevent IE crazy caching
@@ -42,9 +58,6 @@
         *
         * Glue code for nicer integration with QUnit setup/teardown
         * Inspired by http://sinonjs.org/releases/sinon-qunit-1.0.0.js
-        * Fixes:
-        * - Work properly with asynchronous QUnit by using module setup/teardown
-        *   instead of synchronously wrapping QUnit.test.
         */
        sinon.assert.fail = function ( msg ) {
                QUnit.assert.ok( false, msg );
                useFakeTimers: false,
                useFakeServer: false
        };
+       // Extend QUnit.module to provide a Sinon sandbox.
        ( function () {
                var orgModule = QUnit.module;
-
                QUnit.module = function ( name, localEnv, executeNow ) {
-                       if ( QUnit.config.moduleStack.length ) {
-                               // When inside a nested module, don't add our Sinon
-                               // setup/teardown a second time.
+                       var orgBeforeEach, orgAfterEach, orgExecute;
+                       if ( nested ) {
+                               // In a nested module, don't re-run our handlers.
                                return orgModule.apply( this, arguments );
                        }
-
                        if ( arguments.length === 2 && typeof localEnv === 'function' ) {
                                executeNow = localEnv;
                                localEnv = undefined;
                        }
+                       if ( executeNow ) {
+                               // Wrap executeNow() so that we can detect nested modules
+                               orgExecute = executeNow;
+                               executeNow = function () {
+                                       var ret;
+                                       nested = true;
+                                       ret = orgExecute.apply( this, arguments );
+                                       nested = false;
+                                       return ret;
+                               };
+                       }
 
                        localEnv = localEnv || {};
-                       orgModule( name, {
-                               setup: function () {
-                                       var config = sinon.getConfig( sinon.config );
-                                       config.injectInto = this;
-                                       sinon.sandbox.create( config );
-
-                                       if ( localEnv.setup ) {
-                                               localEnv.setup.call( this );
-                                       }
-                               },
-                               teardown: function () {
-                                       if ( localEnv.teardown ) {
-                                               localEnv.teardown.call( this );
-                                       }
-
-                                       this.sandbox.verifyAndRestore();
+                       orgBeforeEach = localEnv.beforeEach;
+                       orgAfterEach = localEnv.afterEach;
+                       localEnv.beforeEach = function () {
+                               var config = sinon.getConfig( sinon.config );
+                               config.injectInto = this;
+                               sinon.sandbox.create( config );
+
+                               if ( orgBeforeEach ) {
+                                       return orgBeforeEach.apply( this, arguments );
                                }
-                       }, executeNow );
+                       };
+                       localEnv.afterEach = function () {
+                               var ret;
+                               if ( orgAfterEach ) {
+                                       ret = orgAfterEach.apply( this, arguments );
+                               }
+
+                               this.sandbox.verifyAndRestore();
+                               return ret;
+                       };
+                       return orgModule( name, localEnv, executeNow );
                };
        }() );
 
        // Extend QUnit.module to provide a fixture element.
        ( function () {
                var orgModule = QUnit.module;
-
                QUnit.module = function ( name, localEnv, executeNow ) {
-                       var fixture;
-
+                       var orgBeforeEach, orgAfterEach;
+                       if ( nested ) {
+                               // In a nested module, don't re-run our handlers.
+                               return orgModule.apply( this, arguments );
+                       }
                        if ( arguments.length === 2 && typeof localEnv === 'function' ) {
                                executeNow = localEnv;
                                localEnv = undefined;
                        }
 
                        localEnv = localEnv || {};
-                       orgModule( name, {
-                               setup: function () {
-                                       fixture = document.createElement( 'div' );
-                                       fixture.id = 'qunit-fixture';
-                                       document.body.appendChild( fixture );
-
-                                       if ( localEnv.setup ) {
-                                               localEnv.setup.call( this );
-                                       }
-                               },
-                               teardown: function () {
-                                       if ( localEnv.teardown ) {
-                                               localEnv.teardown.call( this );
-                                       }
-
-                                       fixture.parentNode.removeChild( fixture );
+                       orgBeforeEach = localEnv.beforeEach;
+                       orgAfterEach = localEnv.afterEach;
+                       localEnv.beforeEach = function () {
+                               this.fixture = document.createElement( 'div' );
+                               this.fixture.id = 'qunit-fixture';
+                               document.body.appendChild( this.fixture );
+
+                               if ( orgBeforeEach ) {
+                                       return orgBeforeEach.apply( this, arguments );
                                }
-                       }, executeNow );
+                       };
+                       localEnv.afterEach = function () {
+                               var ret;
+                               if ( orgAfterEach ) {
+                                       ret = orgAfterEach.apply( this, arguments );
+                               }
+
+                               this.fixture.parentNode.removeChild( this.fixture );
+                               return ret;
+                       };
+                       return orgModule( name, localEnv, executeNow );
+               };
+       }() );
+
+       // Extend QUnit.module to normalise localEnv.
+       // NOTE: This MUST be the last QUnit.module extension so that the above extensions
+       // may safely modify the object and assume beforeEach/afterEach.
+       ( function () {
+               var orgModule = QUnit.module;
+               QUnit.module = function ( name, localEnv, executeNow ) {
+                       if ( typeof localEnv === 'object' ) {
+                               localEnv = makeSafeEnv( localEnv );
+                       }
+                       return orgModule( name, localEnv, executeNow );
                };
        }() );
 
                        ajaxRequests.push( { xhr: jqXHR, options: ajaxOptions } );
                }
 
-               return function ( localEnv ) {
-                       localEnv = $.extend( {
-                               // QUnit
-                               setup: $.noop,
-                               teardown: $.noop,
-                               // MediaWiki
-                               config: {},
-                               messages: {}
-                       }, localEnv );
+               return function ( orgEnv ) {
+                       var localEnv = orgEnv ? makeSafeEnv( orgEnv ) : {};
+                       // MediaWiki env testing
+                       localEnv.config = orgEnv && orgEnv.config || {};
+                       localEnv.messages = orgEnv && orgEnv.messages || {};
 
                        return {
-                               setup: function () {
+                               beforeEach: function () {
                                        // Greetings, mock environment!
                                        mw.config = new MwMap();
                                        mw.config.set( freshConfigCopy( localEnv.config ) );
                                        // Start tracking ajax requests
                                        $( document ).on( 'ajaxSend', trackAjax );
 
-                                       localEnv.setup.call( this );
+                                       if ( localEnv.beforeEach ) {
+                                               return localEnv.beforeEach.apply( this, arguments );
+                                       }
                                },
 
-                               teardown: function () {
-                                       var timers, pending, $activeLen;
+                               afterEach: function () {
+                                       var timers, pending, $activeLen, ret;
 
-                                       localEnv.teardown.call( this );
+                                       if ( localEnv.afterEach ) {
+                                               ret = localEnv.afterEach.apply( this, arguments );
+                                       }
 
                                        // Stop tracking ajax requests
                                        $( document ).off( 'ajaxSend', trackAjax );
 
                                                throw new Error( 'Pending AJAX requests: ' + pending.length + ' (active: ' + $activeLen + ')' );
                                        }
+
+                                       return ret;
                                }
                        };
                };
 
                // Expect boolean true
                assertTrue: function ( actual, message ) {
-                       QUnit.push( actual === true, actual, true, message );
+                       this.pushResult( {
+                               result: actual === true,
+                               actual: actual,
+                               expected: true,
+                               message: message
+                       } );
                },
 
                // Expect boolean false
                assertFalse: function ( actual, message ) {
-                       QUnit.push( actual === false, actual, false, message );
+                       this.pushResult( {
+                               result: actual === false,
+                               actual: actual,
+                               expected: false,
+                               message: message
+                       } );
                },
 
                // Expect numerical value less than X
                lt: function ( actual, expected, message ) {
-                       QUnit.push( actual < expected, actual, 'less than ' + expected, message );
+                       this.pushResult( {
+                               result: actual < expected,
+                               actual: actual,
+                               expected: 'less than ' + expected,
+                               message: message
+                       } );
                },
 
                // Expect numerical value less than or equal to X
                ltOrEq: function ( actual, expected, message ) {
-                       QUnit.push( actual <= expected, actual, 'less than or equal to ' + expected, message );
+                       this.pushResult( {
+                               result: actual <= expected,
+                               actual: actual,
+                               expected: 'less than or equal to ' + expected,
+                               message: message
+                       } );
                },
 
                // Expect numerical value greater than X
                gt: function ( actual, expected, message ) {
-                       QUnit.push( actual > expected, actual, 'greater than ' + expected, message );
+                       this.pushResult( {
+                               result: actual > expected,
+                               actual: actual,
+                               expected: 'greater than ' + expected,
+                               message: message
+                       } );
                },
 
                // Expect numerical value greater than or equal to X
                gtOrEq: function ( actual, expected, message ) {
-                       QUnit.push( actual >= expected, actual, 'greater than or equal to ' + expected, message );
+                       this.pushResult( {
+                               result: actual >= true,
+                               actual: actual,
+                               expected: 'greater than or equal to ' + expected,
+                               message: message
+                       } );
                },
 
                /**
                htmlEqual: function ( actualHtml, expectedHtml, message ) {
                        var actual = getHtmlStructure( actualHtml ),
                                expected = getHtmlStructure( expectedHtml );
-
-                       QUnit.push(
-                               QUnit.equiv(
-                                       actual,
-                                       expected
-                               ),
-                               actual,
-                               expected,
-                               message
-                       );
+                       this.pushResult( {
+                               result: QUnit.equiv( actual, expected ),
+                               actual: actual,
+                               expected: expected,
+                               message: message
+                       } );
                },
 
                /**
                        var actual = getHtmlStructure( actualHtml ),
                                expected = getHtmlStructure( expectedHtml );
 
-                       QUnit.push(
-                               !QUnit.equiv(
-                                       actual,
-                                       expected
-                               ),
-                               actual,
-                               expected,
-                               message
-                       );
+                       this.pushResult( {
+                               result: !QUnit.equiv( actual, expected ),
+                               actual: actual,
+                               expected: expected,
+                               message: message,
+                               negative: true
+                       } );
                }
        };
 
         * Small test suite to confirm proper functionality of the utilities and
         * initializations defined above in this file.
         */
-       QUnit.module( 'test.mediawiki.qunit.testrunner', QUnit.newMwEnvironment( {
+       QUnit.module( 'testrunner', QUnit.newMwEnvironment( {
                setup: function () {
                        this.mwHtmlLive = mw.html;
                        mw.html = {
                assert.deepEqual( missing, [], 'Modules in missing state' );
        } );
 
-       QUnit.test( 'htmlEqual', function ( assert ) {
+       QUnit.test( 'assert.htmlEqual', function ( assert ) {
                assert.htmlEqual(
                        '<div><p class="some classes" data-length="10">Child paragraph with <a href="http://example.com">A link</a></p>Regular text<span>A span</span></div>',
                        '<div><p data-length=\'10\'  class=\'some classes\'>Child paragraph with <a href=\'http://example.com\' >A link</a></p>Regular text<span>A span</span></div>',
                        'foo<a href="http://example.com">example</a>quux',
                        'Outer text nodes are compared (last text node different)'
                );
-
        } );
 
-       QUnit.module( 'test.mediawiki.qunit.testrunner-after', QUnit.newMwEnvironment() );
+       QUnit.module( 'testrunner-after', QUnit.newMwEnvironment() );
 
        QUnit.test( 'Teardown', function ( assert ) {
                assert.equal( mw.html.escape( '<' ), '&lt;', 'teardown() callback was ran.' );
                assert.equal( mw.messages.get( 'testMsg' ), null, 'messages object restored to live in next module()' );
        } );
 
+       QUnit.module( 'testrunner-each', {
+               beforeEach: function () {
+                       this.mwHtmlLive = mw.html;
+               },
+               afterEach: function () {
+                       mw.html = this.mwHtmlLive;
+               }
+       } );
+       QUnit.test( 'beforeEach', function ( assert ) {
+               assert.ok( this.mwHtmlLive, 'setup() ran' );
+               mw.html = null;
+       } );
+       QUnit.test( 'afterEach', function ( assert ) {
+               assert.equal( mw.html.escape( '<' ), '&lt;', 'afterEach() ran' );
+       } );
+
+       QUnit.module( 'testrunner-each-compat', {
+               setup: function () {
+                       this.mwHtmlLive = mw.html;
+               },
+               teardown: function () {
+                       mw.html = this.mwHtmlLive;
+               }
+       } );
+       QUnit.test( 'setup', function ( assert ) {
+               assert.ok( this.mwHtmlLive, 'setup() ran' );
+               mw.html = null;
+       } );
+       QUnit.test( 'teardown', function ( assert ) {
+               assert.equal( mw.html.escape( '<' ), '&lt;', 'teardown() ran' );
+       } );
+
+       // Regression test for 'this.sandbox undefined' error, fixed by
+       // ensuring Sinon setup/teardown is not re-run on inner module.
+       QUnit.module( 'testrunner-nested', function () {
+               QUnit.module( 'testrunner-nested-inner', function () {
+                       QUnit.test( 'Dummy', function ( assert ) {
+                               assert.ok( true, 'Nested modules supported' );
+                       } );
+               } );
+       } );
+
 }( jQuery, mediaWiki, QUnit ) );
index edaef79..da7bafd 100644 (file)
                                { name: 'option2', label: 'group5option2-label', description: 'group5option2-desc' },
                                { name: 'option3', label: 'group5option3-label', description: 'group5option3-desc' }
                        ]
+               }, {
+                       name: 'group6',
+                       type: 'boolean',
+                       filters: [
+                               { name: 'group6option1', label: 'group6option1-label', description: 'group5option1-desc' },
+                               { name: 'group6option2', label: 'group6option2-label', description: 'group5option2-desc', default: true, useDefaultAsBaseValue: true },
+                               { name: 'group6option3', label: 'group6option3-label', description: 'group5option3-desc', default: true }
+                       ]
                } ],
                viewsDefinition = {
                        namespaces: {
                        group3: 'filter8',
                        group4: 'option2',
                        group5: 'option1',
+                       group6option1: '0',
+                       group6option2: '1',
+                       group6option3: '1',
                        namespace: ''
                },
                baseParamRepresentation = {
                        group3: '',
                        group4: 'option2',
                        group5: 'option1',
+                       group6option1: '0',
+                       group6option2: '1',
+                       group6option3: '0',
                        namespace: ''
                },
                baseFilterRepresentation = {
                        group5__option1: true, // No default set, first item is default value
                        group5__option2: false,
                        group5__option3: false,
+                       group6__group6option1: false,
+                       group6__group6option2: true,
+                       group6__group6option3: false,
                        namespace__0: false,
                        namespace__1: false,
                        namespace__2: false,
                        group5__option1: { selected: true, conflicted: false, included: false },
                        group5__option2: { selected: false, conflicted: false, included: false },
                        group5__option3: { selected: false, conflicted: false, included: false },
+                       group6__group6option1: { selected: false, conflicted: false, included: false },
+                       group6__group6option2: { selected: true, conflicted: false, included: false },
+                       group6__group6option3: { selected: false, conflicted: false, included: false },
                        namespace__0: { selected: false, conflicted: false, included: false },
                        namespace__1: { selected: false, conflicted: false, included: false },
                        namespace__2: { selected: false, conflicted: false, included: false },