Merge "Added limit to countRevisionsBetween() for sanity"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 8 Apr 2014 16:15:46 +0000 (16:15 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 8 Apr 2014 16:15:46 +0000 (16:15 +0000)
50 files changed:
includes/DefaultSettings.php
includes/GlobalFunctions.php
includes/SkinTemplate.php
includes/changes/RecentChange.php
includes/clientpool/RedisConnectionPool.php
includes/db/DatabaseMssql.php
includes/db/LBFactory.php
includes/installer/WebInstallerPage.php
includes/installer/i18n/en.json
includes/jobqueue/JobQueue.php
includes/jobqueue/JobQueueFederated.php
includes/libs/MultiHttpClient.php
includes/media/Exif.php
includes/resourceloader/ResourceLoader.php
includes/specials/SpecialAllpages.php
includes/specials/SpecialPrefixindex.php
includes/utils/UIDGenerator.php
languages/i18n/as.json
languages/i18n/ast.json
languages/i18n/azb.json
languages/i18n/ckb.json
languages/i18n/diq.json
languages/i18n/egl.json
languages/i18n/en.json
languages/i18n/es.json
languages/i18n/hr.json
languages/i18n/pl.json
languages/i18n/qqq.json
languages/i18n/sa.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/te.json
languages/i18n/th.json
languages/i18n/zh-hans.json
maintenance/resources/update-oojs.sh [new file with mode: 0755]
maintenance/runJobs.php
resources/lib/oojs-ui/oojs-ui-apex.css
resources/lib/oojs-ui/oojs-ui.js
resources/lib/oojs-ui/oojs-ui.svg.css
resources/lib/oojs-ui/update-oojs-ui.sh
resources/lib/oojs/oojs.js
resources/lib/oojs/update-oojs.sh [deleted file]
resources/src/jquery/jquery.byteLimit.js
resources/src/jquery/jquery.suggestions.js
skins/vector/variables.less
tests/phpunit/includes/jobqueue/JobQueueTest.php
tests/phpunit/includes/utils/UIDGeneratorTest.php
tests/qunit/data/testrunner.js
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.test.js

index eb8ba5c..a3c22b8 100644 (file)
@@ -5637,6 +5637,10 @@ $wgRC2UDPOmitBots = false;
  *   * 'formatter' -- the class name (implementing RCFeedFormatter) which will
  *     produce the text to send.
  *   * 'omit_bots' -- whether the bot edits should be in the feed
+ *   * 'omit_anon' -- whether anonymous edits should be in the feed
+ *   * 'omit_user' -- whether edits by registered users should be in the feed
+ *   * 'omit_minor' -- whether minor edits should be in the feed
+ *   * 'omit_patrolled' -- whether patrolled edits should be in the feed
  *  The IRC-specific options are:
  *   * 'add_interwiki_prefix' -- whether the titles should be prefixed with
  *     the first entry in the $wgLocalInterwikis array (or the value of
@@ -6207,6 +6211,7 @@ $wgJobTypesExcludedFromDefaultQueue = array( 'AssembleUploadChunks', 'PublishSta
  * on each job runner process. The meaning of "work items" varies per job,
  * but typically would be something like "pages to update". A single job
  * may have a variable number of work items, as is the case with batch jobs.
+ * This is used by runJobs.php and not jobs run via $wgJobRunRate.
  * These settings should be global to all wikis.
  */
 $wgJobBackoffThrottling = array();
index 5174b32..cef19e1 100644 (file)
@@ -1817,19 +1817,23 @@ function wfHostname() {
 }
 
 /**
- * Returns a HTML comment with the elapsed time since request.
- * This method has no side effects.
+ * Returns a script tag that stores the amount of time it took MediaWiki to
+ * handle the request in milliseconds as 'wgBackendResponseTime'.
+ *
+ * If $wgShowHostnames is true, the script will also set 'wgHostname' to the
+ * hostname of the server handling the request.
  *
  * @return string
  */
 function wfReportTime() {
        global $wgRequestTime, $wgShowHostnames;
 
-       $elapsed = microtime( true ) - $wgRequestTime;
-
-       return $wgShowHostnames
-               ? sprintf( '<!-- Served by %s in %01.3f secs. -->', wfHostname(), $elapsed )
-               : sprintf( '<!-- Served in %01.3f secs. -->', $elapsed );
+       $responseTime = round( ( microtime( true ) - $wgRequestTime ) * 1000 );
+       $reportVars = array( 'wgBackendResponseTime' => $responseTime );
+       if ( $wgShowHostnames ) {
+               $reportVars[ 'wgHostname' ] = wfHostname();
+       }
+       return Skin::makeVariablesScript( $reportVars );
 }
 
 /**
index 5073913..f359a1c 100644 (file)
@@ -944,8 +944,11 @@ class SkinTemplate extends Skin {
                        $content_navigation['namespaces'][$talkId]['context'] = 'talk';
 
                        if ( $userCanRead ) {
+                               $isForeignFile = $title->inNamespace( NS_FILE ) && $this->canUseWikiPage() &&
+                                       $this->getWikiPage() instanceof WikiFilePage && !$this->getWikiPage()->isLocal();
+
                                // Adds view view link
-                               if ( $title->exists() ) {
+                               if ( $title->exists() || $isForeignFile ) {
                                        $content_navigation['views']['view'] = $this->tabAction(
                                                $isTalk ? $talkPage : $subjectPage,
                                                array( "$skname-view-view", 'view' ),
@@ -955,6 +958,19 @@ class SkinTemplate extends Skin {
                                        $content_navigation['views']['view']['redundant'] = true;
                                }
 
+                               // If it is a non-local file, show a link to the file in its own repository
+                               if ( $isForeignFile ) {
+                                       $file = $this->getWikiPage()->getFile();
+                                       $content_navigation['views']['view-foreign'] = array(
+                                               'class' => '',
+                                               'text' => wfMessageFallback( "$skname-view-foreign", 'view-foreign' )->
+                                                       setContext( $this->getContext() )->
+                                                       params( $file->getRepo()->getDisplayName() )->text(),
+                                               'href' => $file->getDescriptionUrl(),
+                                               'primary' => false,
+                                       );
+                               }
+
                                wfProfileIn( __METHOD__ . '-edit' );
 
                                // Checks if user can edit the current page if it exists or create it otherwise
@@ -969,13 +985,16 @@ class SkinTemplate extends Skin {
                                                && ( ( $isTalk && $this->isRevisionCurrent() ) || $out->showNewSectionLink() );
                                        $section = $request->getVal( 'section' );
 
-                                       $msgKey = $title->exists() || ( $title->getNamespace() == NS_MEDIAWIKI && $title->getDefaultMessageText() !== false ) ?
-                                               'edit' : 'create';
+                                       if ( $title->exists() || ( $title->getNamespace() == NS_MEDIAWIKI && $title->getDefaultMessageText() !== false ) ) {
+                                               $msgKey = $isForeignFile ? 'edit-local' : 'edit';
+                                       } else {
+                                               $msgKey = $isForeignFile ? 'create-local' : 'create';
+                                       }
                                        $content_navigation['views']['edit'] = array(
                                                'class' => ( $isEditing && ( $section !== 'new' || !$showNewSection ) ? 'selected' : '' ) . $isTalkClass,
                                                'text' => wfMessageFallback( "$skname-view-$msgKey", $msgKey )->setContext( $this->getContext() )->text(),
                                                'href' => $title->getLocalURL( $this->editUrlOptions() ),
-                                               'primary' => true, // don't collapse this in vector
+                                               'primary' => !$isForeignFile, // don't collapse this in vector
                                        );
 
                                        // section link
index 072aa12..7705c10 100644 (file)
@@ -331,11 +331,23 @@ class RecentChange {
        public function notifyRCFeeds() {
                global $wgRCFeeds;
 
+               $performer = $this->getPerformer();
+
                foreach ( $wgRCFeeds as $feed ) {
-                       $omitBots = isset( $feed['omit_bots'] ) ? $feed['omit_bots'] : false;
+                       $feed += array(
+                               'omit_bots' => false,
+                               'omit_anon' => false,
+                               'omit_user' => false,
+                               'omit_minor' => false,
+                               'omit_patrolled' => false,
+                       );
 
                        if (
-                               ( $omitBots && $this->mAttribs['rc_bot'] ) ||
+                               ( $feed['omit_bots'] && $this->mAttribs['rc_bot'] ) ||
+                               ( $feed['omit_anon'] && $performer->isAnon() ) ||
+                               ( $feed['omit_user'] && !$performer->isAnon() ) ||
+                               ( $feed['omit_minor'] && $this->mAttribs['rc_minor'] ) ||
+                               ( $feed['omit_patrolled'] && $this->mAttribs['rc_patrolled'] ) ||
                                $this->mAttribs['rc_type'] == RC_EXTERNAL
                        ) {
                                continue;
index 5db814d..68a7f46 100644 (file)
@@ -421,7 +421,7 @@ class RedisConnRef {
                $conn->clearLastError();
                $res = $conn->evalSha( $sha1, $params, $numKeys );
                // If we got a permission error reply that means that (a) we are not in
-               // multi()/pipeline() and (b) some connection problem likely occured. If
+               // multi()/pipeline() and (b) some connection problem likely occurred. If
                // the password the client gave was just wrong, an exception should have
                // been thrown back in getConnection() previously.
                if ( preg_match( '/^ERR operation not permitted\b/', $conn->getLastError() ) ) {
index 50b7158..faed996 100644 (file)
@@ -164,8 +164,7 @@ class DatabaseMssql extends DatabaseBase {
         * @throws DBUnexpectedError
         */
        protected function doQuery( $sql ) {
-               global $wgDebugDumpSql;
-               if ( $wgDebugDumpSql ) {
+               if ( $this->debug() ) {
                        wfDebug( "SQL: [$sql]\n" );
                }
                $this->offset = 0;
index fcce870..eca9564 100644 (file)
@@ -243,7 +243,10 @@ class LBFactorySimple extends LBFactory {
                        global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
                        global $wgDBssl, $wgDBcompress;
 
-                       $flags = ( $wgDebugDumpSql ? DBO_DEBUG : 0 ) | DBO_DEFAULT;
+                       $flags = DBO_DEFAULT;
+                       if ( $wgDebugDumpSql ) {
+                               $flags |= DBO_DEBUG;
+                       }
                        if ( $wgDBssl ) {
                                $flags |= DBO_SSL;
                        }
index 4bc6cad..69a460a 100644 (file)
@@ -1144,8 +1144,7 @@ class WebInstaller_Options extends WebInstallerPage {
                        'wgEmailAuthentication', 'wgMainCacheType', '_MemCachedServers',
                        'wgUseInstantCommons' ) );
 
-               if ( !in_array( $this->getVar( '_RightsProfile' ),
-                       array_keys( $this->parent->rightsProfiles ) )
+               if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles )
                ) {
                        reset( $this->parent->rightsProfiles );
                        $this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
@@ -1158,7 +1157,7 @@ class WebInstaller_Options extends WebInstallerPage {
 
                                return false;
                        }
-               } elseif ( in_array( $code, array_keys( $this->parent->licenses ) ) ) {
+               } elseif ( array_key_exists( $code, $this->parent->licenses ) ) {
                        // Messages:
                        // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
                        // config-license-cc-0, config-license-pd, config-license-gfdl, config-license-none,
index 32a9912..0c6ff63 100644 (file)
@@ -76,7 +76,7 @@
     "config-gd": "Found GD graphics library built-in.\nImage thumbnailing will be enabled if you enable uploads.",
     "config-no-scaling": "Could not find GD library or ImageMagick.\nImage thumbnailing will be disabled.",
     "config-no-uri": "<strong>Error:</strong> Could not determine the current URI.\nInstallation aborted.",
-    "config-no-cli-uri": "<strong>Warning:</strong> No --scriptpath specified, using default: <code>$1</code>.",
+    "config-no-cli-uri": "<strong>Warning:</strong> No <code>--scriptpath</code> specified, using default: <code>$1</code>.",
     "config-using-server": "Using server name \"<nowiki>$1</nowiki>\".",
     "config-using-uri": "Using server URL \"<nowiki>$1$2</nowiki>\".",
     "config-uploads-not-safe": "<strong>Warning:</strong> Your default directory for uploads <code>$1</code> is vulnerable to arbitrary scripts execution.\nAlthough MediaWiki checks all uploaded files for security threats, it is highly recommended to [//www.mediawiki.org/wiki/Manual:Security#Upload_security close this security vulnerability] before enabling uploads.",
index 2d7103c..9b13aea 100644 (file)
@@ -304,11 +304,11 @@ abstract class JobQueue {
         *
         * @param Job|array $jobs A single job or an array of Jobs
         * @param int $flags Bitfield (supports JobQueue::QOS_ATOMIC)
-        * @return bool Returns false on failure
+        * @return void
         * @throws JobQueueError
         */
        final public function push( $jobs, $flags = 0 ) {
-               return $this->batchPush( is_array( $jobs ) ? $jobs : array( $jobs ), $flags );
+               $this->batchPush( is_array( $jobs ) ? $jobs : array( $jobs ), $flags );
        }
 
        /**
@@ -318,8 +318,8 @@ abstract class JobQueue {
         *
         * @param array $jobs List of Jobs
         * @param int $flags Bitfield (supports JobQueue::QOS_ATOMIC)
+        * @return void
         * @throws MWException
-        * @return bool Returns false on failure
         */
        final public function batchPush( array $jobs, $flags = 0 ) {
                if ( !count( $jobs ) ) {
@@ -337,17 +337,14 @@ abstract class JobQueue {
                }
 
                wfProfileIn( __METHOD__ );
-               $ok = $this->doBatchPush( $jobs, $flags );
+               $this->doBatchPush( $jobs, $flags );
                wfProfileOut( __METHOD__ );
-
-               return $ok;
        }
 
        /**
         * @see JobQueue::batchPush()
         * @param array $jobs
         * @param $flags
-        * @return bool
         */
        abstract protected function doBatchPush( array $jobs, $flags );
 
@@ -399,24 +396,21 @@ abstract class JobQueue {
         * Outside callers should use JobQueueGroup::ack() instead of this function.
         *
         * @param Job $job
+        * @return void
         * @throws MWException
-        * @return bool
         */
        final public function ack( Job $job ) {
                if ( $job->getType() !== $this->type ) {
                        throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
                }
                wfProfileIn( __METHOD__ );
-               $ok = $this->doAck( $job );
+               $this->doAck( $job );
                wfProfileOut( __METHOD__ );
-
-               return $ok;
        }
 
        /**
         * @see JobQueue::ack()
         * @param Job $job
-        * @return bool
         */
        abstract protected function doAck( Job $job );
 
@@ -539,22 +533,19 @@ abstract class JobQueue {
        /**
         * Deleted all unclaimed and delayed jobs from the queue
         *
-        * @return bool Success
         * @throws JobQueueError
         * @since 1.22
+        * @return void
         */
        final public function delete() {
                wfProfileIn( __METHOD__ );
-               $res = $this->doDelete();
+               $this->doDelete();
                wfProfileOut( __METHOD__ );
-
-               return $res;
        }
 
        /**
         * @see JobQueue::delete()
         * @throws MWException
-        * @return bool Success
         */
        protected function doDelete() {
                throw new MWException( "This method is not implemented." );
index 9502148..f2599ae 100644 (file)
@@ -265,7 +265,8 @@ class JobQueueFederated extends JobQueue {
                        /** @var JobQueue $queue */
                        $queue = $this->partitionQueues[$partition];
                        try {
-                               $ok = $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
+                               $ok = true;
+                               $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
                        } catch ( JobQueueError $e ) {
                                $ok = false;
                                MWExceptionHandler::logException( $e );
@@ -287,7 +288,8 @@ class JobQueueFederated extends JobQueue {
                        $partition = ArrayUtils::pickRandom( $partitionRing->getLocationWeights() );
                        $queue = $this->partitionQueues[$partition];
                        try {
-                               $ok = $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
+                               $ok = true;
+                               $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
                        } catch ( JobQueueError $e ) {
                                $ok = false;
                                MWExceptionHandler::logException( $e );
index 00cd257..340e7f4 100644 (file)
@@ -211,7 +211,8 @@ class MultiHttpClient {
                        $ch = $handles[$index];
                        curl_multi_remove_handle( $chm, $ch );
                        if ( curl_errno( $ch ) !== 0 ) {
-                               $req['error'] = "(curl error: " . curl_errno( $ch ) . ") " . curl_error( $ch );
+                               $req['response']['error'] = "(curl error: " .
+                                       curl_errno( $ch ) . ") " . curl_error( $ch );
                        }
                        // For convenience with the list() operator
                        $req['response'][0] = $req['response']['code'];
index 1cb5542..b39e042 100644 (file)
@@ -319,13 +319,13 @@ class Exif {
                $this->mFilteredExifData = array();
 
                foreach ( array_keys( $this->mRawExifData ) as $section ) {
-                       if ( !in_array( $section, array_keys( $this->mExifTags ) ) ) {
+                       if ( !array_key_exists( $section, $this->mExifTags ) ) {
                                $this->debug( $section, __FUNCTION__, "'$section' is not a valid Exif section" );
                                continue;
                        }
 
                        foreach ( array_keys( $this->mRawExifData[$section] ) as $tag ) {
-                               if ( !in_array( $tag, array_keys( $this->mExifTags[$section] ) ) ) {
+                               if ( !array_key_exists( $tag, $this->mExifTags[$section] ) ) {
                                        $this->debug( $tag, __FUNCTION__, "'$tag' is not a valid tag in '$section'" );
                                        continue;
                                }
index b2fb902..77659f6 100644 (file)
@@ -795,9 +795,11 @@ class ResourceLoader {
                                                $scripts = $module->getScriptURLsForDebug( $context );
                                        } else {
                                                $scripts = $module->getScript( $context );
-                                               if ( is_string( $scripts ) && strlen( $scripts ) && substr( $scripts, -1 ) !== ';' ) {
-                                                       // bug 27054: Append semicolon to prevent weird bugs
-                                                       // caused by files not terminating their statements right
+                                               // rtrim() because there are usually a few line breaks after the last ';'.
+                                               // A new line at EOF, a new line added by ResourceLoaderFileModule::readScriptFiles, etc.
+                                               if ( is_string( $scripts ) && strlen( $scripts ) && substr( rtrim( $scripts ), -1 ) !== ';' ) {
+                                                       // Append semicolon to prevent weird bugs caused by files not
+                                                       // terminating their statements right (bug 27054)
                                                        $scripts .= ";\n";
                                                }
                                        }
index 97e7238..70949d4 100644 (file)
@@ -97,7 +97,7 @@ class SpecialAllpages extends IncludableSpecialPage {
                $namespaces = $this->getContext()->getLanguage()->getNamespaces();
 
                $out->setPageTitle(
-                       ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) ?
+                       ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) ) ?
                                $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
                                $this->msg( 'allarticles' )
                );
@@ -375,7 +375,7 @@ class SpecialAllpages extends IncludableSpecialPage {
 
                if ( !$fromList || !$toList ) {
                        $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
-               } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
+               } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
                        // Show errormessage and reset to NS_MAIN
                        $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
                        $namespace = NS_MAIN;
index 8137651..f13fc59 100644 (file)
@@ -66,7 +66,7 @@ class SpecialPrefixindex extends SpecialAllpages {
 
                $namespaces = $wgContLang->getNamespaces();
                $out->setPageTitle(
-                       ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) )
+                       ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) )
                                ? $this->msg( 'prefixindex-namespace', str_replace( '_', ' ', $namespaces[$namespace] ) )
                                : $this->msg( 'prefixindex' )
                );
@@ -166,7 +166,7 @@ class SpecialPrefixindex extends SpecialAllpages {
 
                if ( !$prefixList || !$fromList ) {
                        $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
-               } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
+               } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
                        // Show errormessage and reset to NS_MAIN
                        $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
                        $namespace = NS_MAIN;
index e60293b..0eac06e 100644 (file)
@@ -30,6 +30,7 @@ class UIDGenerator {
        /** @var UIDGenerator */
        protected static $instance = null;
 
+       protected $nodeIdFile; // string; local file path
        protected $nodeId32; // string; node ID in binary (32 bits)
        protected $nodeId48; // string; node ID in binary (48 bits)
 
@@ -43,8 +44,11 @@ class UIDGenerator {
        const QUICK_VOLATILE = 2; // use an APC like in-memory counter if available
 
        protected function __construct() {
-               $idFile = wfTempDir() . '/mw-' . __CLASS__ . '-UID-nodeid';
-               $nodeId = is_file( $idFile ) ? file_get_contents( $idFile ) : '';
+               $this->nodeIdFile = wfTempDir() . '/mw-' . __CLASS__ . '-UID-nodeid';
+               $nodeId = '';
+               if ( is_file( $this->nodeIdFile ) ) {
+                       $nodeId = file_get_contents( $this->nodeIdFile );
+               }
                // Try to get some ID that uniquely identifies this machine (RFC 4122)...
                if ( !preg_match( '/^[0-9a-f]{12}$/i', $nodeId ) ) {
                        wfSuppressWarnings();
@@ -66,7 +70,7 @@ class UIDGenerator {
                                $nodeId = MWCryptRand::generateHex( 12, true );
                                $nodeId[1] = dechex( hexdec( $nodeId[1] ) | 0x1 ); // set multicast bit
                        }
-                       file_put_contents( $idFile, $nodeId ); // cache
+                       file_put_contents( $this->nodeIdFile, $nodeId ); // cache
                }
                $this->nodeId32 = wfBaseConvert( substr( sha1( $nodeId ), 0, 8 ), 16, 2, 32 );
                $this->nodeId48 = wfBaseConvert( $nodeId, 16, 2, 48 );
@@ -338,11 +342,12 @@ class UIDGenerator {
         */
        protected function getTimestampAndDelay( $lockFile, $clockSeqSize, $counterSize ) {
                // Get the UID lock file handle
-               if ( isset( $this->fileHandles[$lockFile] ) ) {
-                       $handle = $this->fileHandles[$lockFile];
+               $path = $this->$lockFile;
+               if ( isset( $this->fileHandles[$path] ) ) {
+                       $handle = $this->fileHandles[$path];
                } else {
-                       $handle = fopen( $this->$lockFile, 'cb+' );
-                       $this->fileHandles[$lockFile] = $handle ?: null; // cache
+                       $handle = fopen( $path, 'cb+' );
+                       $this->fileHandles[$path] = $handle ?: null; // cache
                }
                // Acquire the UID lock file
                if ( $handle === false ) {
@@ -450,6 +455,50 @@ class UIDGenerator {
                return array( (int)$sec, (int)( $msec * 1000 ) );
        }
 
+       /**
+        * Delete all cache files that have been created.
+        *
+        * This is a cleanup method primarily meant to be used from unit tests to
+        * avoid poluting the local filesystem. If used outside of a unit test
+        * environment it should be used with caution as it may destroy state saved
+        * in the files.
+        *
+        * @see unitTestTearDown
+        * @since 1.23
+        */
+       protected function deleteCacheFiles() {
+               // Bug: 44850
+               foreach ( $this->fileHandles as $path => $handle ) {
+                       if ( $handle !== null ) {
+                               fclose( $handle );
+                       }
+                       if ( is_file( $path ) ) {
+                               unlink( $path );
+                       }
+                       unset( $this->fileHandles[$path] );
+               }
+               if ( is_file( $this->nodeIdFile ) ) {
+                       unlink( $this->nodeIdFile );
+               }
+       }
+
+       /**
+        * Cleanup resources when tearing down after a unit test.
+        *
+        * This is a cleanup method primarily meant to be used from unit tests to
+        * avoid poluting the local filesystem. If used outside of a unit test
+        * environment it should be used with caution as it may destroy state saved
+        * in the files.
+        *
+        * @see deleteCacheFiles
+        * @since 1.23
+        */
+       public static function unitTestTearDown() {
+               // Bug: 44850
+               $gen = self::singleton();
+               $gen->deleteCacheFiles();
+       }
+
        function __destruct() {
                array_map( 'fclose', $this->fileHandles );
        }
index c0f7052..389d48f 100644 (file)
     "pool-timeout": "বন্ধ কৰাৰ বাবে অপেক্ষা কৰাৰ সময় উকলি গৈছে",
     "pool-queuefull": "পুল কিউ (pool queue) পূৰ্ণ",
     "pool-errorunknown": "অপৰিচিত ত্ৰুটি",
+    "pool-servererror": "পুল কাউণ্টাৰ সেৱা উপলব্ধ নহয় ($1)।",
     "aboutsite": "{{SITENAME}}ৰ বিষয়ে",
     "aboutpage": "Project:ইতিবৃত্ত",
     "copyright": "আন একো উল্লেখ নাথাকিলে এই বিষয়বস্তু $1 ৰ আওতাত উপলব্ধ।",
     "gotaccountlink": "প্ৰৱেশ",
     "userlogin-resetlink": "আপোনাৰ প্ৰৱেশ তথ্য পাহৰিছে?",
     "userlogin-resetpassword-link": "আপোনাৰ গুপ্তশব্দ পাহৰিছে?",
+    "userlogin-helplink2": "প্ৰৱেশ সংক্ৰান্তীয় সাহায্য",
     "userlogin-loggedin": "আপুনি ইতিমধ্যে {{GENDER:$1|$1}} হিচাপে প্ৰৱেশ কৰিছে। তলৰ আন সদস্যৰূপে প্ৰৱেশ কৰিবলৈ তলৰ প্ৰপত্ৰ ব্যৱহাৰ কৰক।",
     "userlogin-createanother": "আন এটা একাউণ্ট সৃষ্টি কৰক",
     "createacct-join": "আপোনাৰ তথ্যসমূহ তলত লিখক।",
     "loginlanguagelabel": "ভাষা: $1",
     "suspicious-userlogout": "আপোনাৰ প্ৰস্থানৰ অনুৰোধ বাতিল কৰা হৈছে কাৰণ হয়তো আপোনাৰ ব্ৰাউজাৰ অসম্পূৰ্ণ নতুবা পূৰ্বৱতী তথ্য পঠাইছে ।",
     "createacct-another-realname-tip": "প্ৰকৃত নাম দিয়াটো বৈকল্পিক।\nআপুনি নামটো দিলে সেইটো আপোনাৰ বৰঙণিসমূহৰ বাবে স্বীকৃতি প্ৰদানত ব্যৱহাৰ কৰা হ'ব।",
+    "pt-login": "প্ৰৱেশ",
     "pt-login-button": "প্ৰৱেশ",
+    "pt-createaccount": "একাউণ্ট সৃষ্টি কৰক",
+    "pt-userlogout": "প্ৰস্থান",
     "php-mail-error-unknown": "পি.এইছ.পি মেইল () কাৰ্যত অজ্ঞাত ত্ৰুটি ।",
     "user-mail-no-addy": "ই-মেইল ঠিকনা নোহোৱাকৈয়ে ই-মেইল পঠিওৱাৰ চেষ্টা কৰা হৈছে ।",
     "user-mail-no-body": "কোনো সমল নোহোৱাকৈ বা অতি সংক্ষিপ্ত কথাৰে ইমেইল পঠিয়াবলৈ চেষ্টা কৰিছিল।",
     "changepassword": "গুপ্তশব্দ সলনি কৰক",
-    "resetpass_announce": "à¦\86পà§\81নি à¦\87-মà§\87à¦\87লত à¦ªà§\8bৱা à¦\85সà§\8dথায়à§\80 à¦\97à§\81পà§\8dতশবà§\8dদৰà§\87 à¦ªà§\8dৰৱà§\87শ à¦\95ৰিà¦\9bà§\87।\nপà§\8dৰৱà§\87শ à¦¸à¦®à§\8dপà§\82ৰà§\8dণ à¦\95ৰিবলà§\88, à¦\86পà§\81নি à¦\8fà¦\9fা à¦¨à¦¤à§\81ন à¦\97à§\81পà§\8dতশবà§\8dদ à¦¦à¦¿à¦¬ à¦²à¦¾à¦\97িব:",
+    "resetpass_announce": "পà§\8dৰৱà§\87শ à¦¸à¦®à§\8dপà§\82ৰà§\8dণ à¦\95ৰিবলà§\88 à¦\86পà§\81নি à¦\8fà¦\9fা à¦¨à¦¤à§\81ন à¦\97à§\81পà§\8dতশবà§\8dদ à¦¦à¦¿à¦¬ à¦²à¦¾à¦\97িব।",
     "resetpass_header": "গুপ্তশব্দ সলনি কৰক",
     "oldpassword": "পুৰণি গুপ্তশব্দ:",
     "newpassword": "নতুন গুপ্তশব্দ:",
     "resetpass-submit-loggedin": "গুপ্তশব্দ সলনি কৰক",
     "resetpass-submit-cancel": "বাতিল কৰক",
     "resetpass-wrong-oldpass": "অস্থায়ী বা সাম্প্ৰতিক গুপ্তশব্দ গ্ৰহণযোগ্য নহয় ।\nহয়টো আপুনি ইতিমধ্যেই সফলভাবে আপুনাৰ গুপ্তশব্দ সলনি কৰিছিল বা এটা নতুন অস্থায়ী গুপ্তশব্দৰ বাবে অনুৰোধ কৰিছিল ।",
+    "resetpass-recycled": "অনুগ্ৰহ কৰি এতিয়াৰ গুপ্তশব্দটোৰ সলনি আন এটা গুপ্তশব্দ নিৰ্ধাৰণ কৰক।",
+    "resetpass-temp-emailed": "আপুনি এটা অস্থায়ী ইমেইল যোগে প্ৰাপ্ত সংকেতেৰে প্ৰৱেশ কৰিছে।\nপ্ৰৱেশ সম্পূৰ্ণ কৰিবলৈ আপুনি ইয়াত এটা নতুন গুপ্তশব্দ নিৰ্ধাৰণ কৰিব লাগিব:",
     "resetpass-temp-password": "অস্থায়ী গুপ্তশব্দ:",
     "resetpass-abort-generic": "এটা এক্সটেন্‌ছনৰদ্বাৰা গুপ্তশব্দ সলনি কাৰ্য বাতিল কৰা হৈছে।",
+    "resetpass-expired": "আপোনাৰ গুপ্তশব্দৰ ম্যাদ উকলি গৈছে। অনুগ্ৰহ কৰি প্ৰৱেশৰ বাবে এটা নতুন গুপ্তশব্দ নিৰ্ধাৰণ কৰক।",
+    "resetpass-expired-soft": "আপোনাৰ গুপ্তশব্দৰ ম্যাদ উকলি গৈছে আৰু নতুন এটা নিৰ্ধাৰণ কৰাৰ প্ৰয়োজন। অনুগ্ৰহ কৰি এতিয়া এটা নতুন গুপ্তশব্দ বাছনি কৰক বা পিছত নিৰ্ধাৰণ কৰিবলৈ \"{{int:resetpass-submit-cancel}}\" বুটামত ক্লিক কৰক।",
     "passwordreset": "গুপ্তশব্দ ন-কৈ বহুৱাওক",
     "passwordreset-text-one": "আপোনাৰ গুপ্তশব্দ ন-কৈ বহুৱাবলৈ এই প্ৰপত্ৰ পূৰণ কৰক।",
     "passwordreset-text-many": "{{PLURAL:$1|ইমেইলত এটা অস্থায়ী গুপ্তশব্দ পাবলৈ এই তথ্যসমূহৰ যিকোনো এটা দিয়ক।}}",
     "rcnotefrom": "তলত '''$2''' ৰ পৰা হোৱা ('''$1''' লৈকে) পৰিৱৰ্তন দেখুৱা হৈছে ।",
     "rclistfrom": "$1ৰ পৰা নতুন সালসলনি দেখুৱাওক",
     "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 নিৰীক্ষিত সম্পাদনা",
+    "rcshowhidepatr-show": "দেখুৱাওক",
+    "rcshowhidepatr-hide": "লুকুৱাওক",
     "rcshowhidemine": "মোৰ সম্পাদনা $1",
+    "rcshowhidemine-show": "দেখুৱাওক",
+    "rcshowhidemine-hide": "লুকুৱাওক",
     "rclinks": "যোৱা $2 দিনত হোৱা $1 টা সাল-সলনি চাওক ।<br />$3",
     "diff": "পাৰ্থক্য",
     "hist": "ইতিবৃত্ত",
     "protectedpages-cascade": "কেৱল প্ৰপাতাকাৰ সুৰক্ষা",
     "protectedpages-noredirect": "পুনঃনিৰ্দেশ লুকুৱাওক",
     "protectedpagesempty": "এই পাৰামিটাৰবোৰেৰে কোনো পৃষ্ঠা এতিয়া সুৰক্ষিত কৰা হোৱা নাই ।",
+    "protectedpages-page": "পৃষ্ঠা",
+    "protectedpages-expiry": "ম্যাদ উকলিব",
     "protectedtitles": "সুৰক্ষিত শিৰোনামাসমূহ",
     "protectedtitlesempty": "এই পাৰামিটাৰবোৰেৰে কোনো শিৰোনামা এতিয়া সুৰক্ষিত কৰা হোৱা নাই ।",
     "listusers": "সদস্য তালিকা",
index 8a8ff64..a4135fa 100644 (file)
     "pool-timeout": "Tiempu escosáu esperando pal bloquéu",
     "pool-queuefull": "La cola de trabayu ta enllena",
     "pool-errorunknown": "Fallu desconocíu",
+    "pool-servererror": "El serviciu de contador de recursos compartíos nun ta disponible ($1).",
     "aboutsite": "Tocante a {{SITENAME}}",
     "aboutpage": "Project:Tocante a",
     "copyright": "El conteníu ta disponible baxo los términos de la $1 si nun s'indica otra cosa.",
     "listgrouprights-removegroup-self": "Desaniciar {{PLURAL:$2|grupu|grupos}} de la cuenta propia: $1",
     "listgrouprights-addgroup-self-all": "Amestar tolos grupos a la cuenta propia",
     "listgrouprights-removegroup-self-all": "Desaniciar tolos grupos de la cuenta propia",
+    "trackingcategories": "Categoríes de siguimientu",
+    "trackingcategories-summary": "Esta páxina llista les categoríes de siguimientu que rellena automáticamente'l software de MediaWiki. Puen camudase los nomes alterando los mensaxes del sistema correspondientes nel espaciu de nomes {{ns:8}}.",
+    "trackingcategories-msg": "Categoría de siguimientu",
+    "trackingcategories-name": "Nome del mensaxe",
+    "trackingcategories-desc": "Criterios d'inclusión de categoría",
+    "noindex-category-desc": "La páxina contién una pallabra máxica <nowiki>__NOINDEX__</nowiki> (y ta nun espaciu de nomes nel que se permite esta marca) y, poro, los robots nun la indexarán.",
+    "index-category-desc": "La páxina contién una pallabra máxica <nowiki>__INDEX__</nowiki> (y ta nun espaciu de nomes nel que se permite esta marca) y, poro, los robots la indexarán anque normalmente nun lo faigan.",
+    "post-expand-template-inclusion-category-desc": "Después de espander toles plantíes, el tamañu de la páxina ye mayor que $wgMaxArticleSize; de mou qu'algunes plantíes nun s'espandieron.",
+    "post-expand-template-argument-category-desc": "Después d'espander l'argumentu d'una plantía (daqué ente llaves triples, como {{{Daqué}}}), la páxina ye mayor que $wgMaxArticleSize.",
+    "expensive-parserfunction-category-desc": "Hai demasiaes funciones analítiques costoses (como <code>#ifexist</code>) incluíes nuna páxina. Vea [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgExpensiveParserFunctionLimit Manual:$wgExpensiveParserFunctionLimit].",
+    "broken-file-category-desc": "Categoría amestada si la páxina contién un enllaz de ficheru frañáu (un enllaz pa incrustar un ficheru cuando'l ficheru nun esiste).",
+    "hidden-category-category-desc": "Esta ye una categoría que contién la pallabra máxica <nowiki>__HIDDENCAT__</nowiki>, que torga que s'amuese de mou predetermináu nel cuadru d'enllaces de categoría de les páxines.",
+    "trackingcategories-nodesc": "Nun hai una descripción disponible.",
+    "trackingcategories-disabled": "La categoría ta desactivada",
     "mailnologin": "Ensin direición d'unviu",
     "mailnologintext": "Has tener [[Special:UserLogin|sesión aniciada]] y una direición de corréu válida nes tos [[Special:Preferences|preferencies]] pa poder unviar correos a otros usuarios.",
     "emailuser": "Manda-y un corréu a esti usuariu",
index e6e6c41..3ed9ff5 100644 (file)
@@ -9,7 +9,8 @@
             "Ebraminio",
             "Erdemaslancan",
             "Mousa",
-            "Shirayuki"
+            "Shirayuki",
+            "Microchip08"
         ]
     },
     "tog-underline": "باغلانتی‌لارین آلتینی خطله:",
     "session_fail_preview_html": "'''اوزر ایستییریک! داخیل وئری‌سی‌نین ایتمه‌سین‌دن گؤره تنظیملمه‌نیزی اعمال کئچیره بیلمیجیی.' '\n\n' چونکی {{SITENAME}} سایتیندا raw هتمل تأثیرین‌دیر،تا جاوااسکریپت هوجوم‌لارینا تدبیر اولا‌راق گیزلنمیش‌دیر.'\n\n' 'گر بو حاق‌لی بیر تنظیملمه گیریش میسئ، لطفاً یئنی‌دن جهد ائدین. اگر هله چالیشمازسا، [[Special:UserLogout|خارج شوید]] یئنی‌دن ایجلاس آچماغی یوخلایین.' '",
     "token_suffix_mismatch": "'' ' ديَیشیکلیگی‌نین گئری چئوریلدی، چونکی آلیجی‌نین تنزیمله‌مه کوتوجوغونداکی دورغو ایشاره‌لرینی پوزدو. \nديَیشیکلیگی‌نین، صحیفه‌‌ متنینده پوزولماغی اؤنله‌مک اوچون گئری چئوریلدی. \nاگر پروبلئملی بیر wئب-باسئد آنونیم پروکسی خیدمتی ایستیفاده بو حادثه‌‌ بضا رئاللاشا بیلر.'' '",
     "edit_form_incomplete": "'''دییشیک‌لیک فورماسی اوچون بعضی سئروئرلره ایشلمه‌دی؛ ائتدیگینیز دییشیک‌لیک‌لر بوزولمامیشتیر، نظردن کئچیریب یئنی‌دن سینایین.'",
-    "editing": "<font style=\"color:red\">$1</font> دییشدیریلیر",
+    "editing": "$1 دییشدیریلیر",
     "creating": "$1 یارادیلیر",
     "editingsection": "$1 دَییشدیریلیر (بؤلوم)",
     "editingcomment": "$1 دَییشدیریلیر (یئنی بؤلوم)",
index 088d946..6c18875 100644 (file)
     "group-user": "بەکارهێنەران",
     "group-autoconfirmed": "بەکارھێنەرانی پەسندکراوی خۆگەڕ",
     "group-bot": "بۆتەکان",
-    "group-sysop": "بەڕێوبەران",
+    "group-sysop": "بەڕێوەبەران",
     "group-bureaucrat": "بیوروکراتەکان",
     "group-suppress": "چاودێرەکان",
     "group-all": "(ھەموو)",
     "statistics-header-hooks": "ئامارەکانی دیکە",
     "statistics-articles": "پەڕە بە ناوەڕۆکەکان",
     "statistics-pages": "پەڕەکان",
-    "statistics-pages-desc": "گشت Ù¾Û\95Ú\95Û\95کاÙ\86Û\8c Ù\88Û\8cÚ©Û\8cØ\8c Ø¨Û\95 Ù\84Û\95Ø®Û\86گرتÙ\86Û\8c Ù¾Û\95Ú\95Û\95کاÙ\86Û\8c Ù\88تÙ\88Ù\88Û\8eÚ\98Ø\8c Ú\95Û\95Ù\88اÙ\86Û\95کراÙ\88ەکان و ھتد.",
+    "statistics-pages-desc": "گشت Ù¾Û\95Ú\95Û\95کاÙ\86Û\8c Ù\88Û\8cÚ©Û\8cØ\8c Ø¨Û\95 Ù\84Û\95Ø®Û\86گرتÙ\86Û\8c Ù¾Û\95Ú\95Û\95کاÙ\86Û\8c Ù\84Û\8eدÙ\88اÙ\86Ø\8c Ú\95Û\95Ù\88اÙ\86Û\95Ú©Û\95رەکان و ھتد.",
     "statistics-files": "پەڕگە بارکراوەکان",
     "statistics-edits": "دەستکارییەکانی پەڕەکان لە کاتی دامەزراندنی {{SITENAME}}ەوە",
-    "statistics-edits-average": "نێونجی ژمارەی دەستکارییەکان لە پەڕەیەک دا",
+    "statistics-edits-average": "ناونجیی دەستکارییەکان بۆ ھەر پەڕە",
     "statistics-views-total": "دیتنی هەموو",
     "statistics-views-peredit": "دیتنی هەر دەستکارییەک",
     "statistics-users": "[[Special:ListUsers|بەکارھێنەر]]ە تۆمارکراوەکان",
     "statistics-users-active": "ئەندامە چالاکەکان",
-    "statistics-users-active-desc": "ئەو بەکارھێنەرانە کە لە {{PLURAL:$1|ڕۆژ|$1 ڕۆژ}}ی ڕابردوودا کارێکیان جێبەجێ کردبێت.",
+    "statistics-users-active-desc": "ئەو بەکارھێنەرانەی لە {{PLURAL:$1|ڕۆژ|$1 ڕۆژ}}ی ڕابردوودا کردەوەیەکیان بەڕێوە بردووە",
     "statistics-mostpopular": "زۆرترین لاپەڕە بینراوەکان",
     "pageswithprop": "پەڕەکان بە تایبەتمەندیی پەڕە",
     "pageswithprop-legend": "پەڕەکان بە تایبەتمەندیی پەڕە",
index 4d5f60e..32bd957 100644 (file)
@@ -16,7 +16,9 @@
             "Olvörg",
             "Reedy",
             "Sahim",
-            "Xoser"
+            "Xoser",
+            "Geitost",
+            "Microchip08"
         ]
     },
     "tog-underline": "Bınê gırey de xete bance:",
     "media_tip": "Gıreyê dosya",
     "sig_tip": "İmzay şıma be morê zemani",
     "hr_tip": "Xeta verardiye (teserrufın bıgureyne/bıxebetne)",
-    "summary": "<font style=\"color:Blue\">'''Xulasa:'''</font>",
+    "summary": "Xulasa:",
     "subject": "Mewzu/sernuşte:",
     "minoredit": "No vırnayışê do werdiyo",
     "watchthis": "'''Ena pele seyr ke'''",
     "session_fail_preview_html": "'''Ma meluli! Sebayê vindbiyayişê datasistemi ma vurnayişê şıma nêeşkeni qaydker.'''\n\n''Çunke keyepelê {{SITENAME}} de raw HTML aqtifo, seyrkerdışê verqayd semedê galayê (alızyayiş) JavaScript ri nımıyayo.''\n\n'''Eke no vurnayiş heqê şımayo, newe ra tesel bıker (bıcerebi). eke hona zi nêxebıtya, [[Special:UserLogout|vec]] newe ra hesabê xo aker.'''",
     "token_suffix_mismatch": "'''Vurnayişê şıma tepeya ameyo çunke qutiyê imla xerıbya.\nVurnayişê şıma qey nêxerepyayişê peli tepeya geyra a.\nEke şıma servisê proksi yo anonim şuxulneni sebebê ey noyo.'''",
     "edit_form_incomplete": "'''Qandê form dê vurnayışa tay wastera ma nêreşti; Vurnayışê ke şıma kerdê nêalızyayê, çım ra ravyarnê u fına bıcerbnê.'''",
-    "editing": "Şımayê <font style=\"color:red\">$1</font> vurnenê",
-    "creating": "Pela <font style=\"color:blue\">$1</font> vırazê",
+    "editing": "Şımayê $1 vurnenê",
+    "creating": "Pela $1 vırazê",
     "editingsection": "Per da $1 de şımaye kenê ke leti bıvurnê",
     "editingcomment": "$1 vuryeno (qısmo newe)",
     "editconflict": "Vurnayişê ke yewbini nêtepışeni: $1",
     "log-fulllog": "Temamê rocaneyi bıvine",
     "edit-hook-aborted": "Vurnayiş vınderiya.\nYew sebeb beyan nibı.",
     "edit-gone-missing": "Pel rocanebiyaye niyo.\nHewna kerde aseno.",
-    "edit-conflict": "Vurnayişê pêverdiyaye .",
+    "edit-conflict": "Vurnayişê pêverdiyaye.",
     "edit-no-change": "Vurnayişê şıma qebul nêbı, çunke nuşte de yew vurnayiş n3evıraziya.",
     "postedit-confirmation": "Vurnayışê to qeyd bi.",
     "edit-already-exists": "Pelo newe nêvıraziyeno.\nPel ca ra esto.",
index f16dcd8..d8c7bc5 100644 (file)
@@ -2,7 +2,8 @@
     "@metadata": {
         "authors": [
             "Lévi",
-            "Reder"
+            "Reder",
+            "Geitost"
         ]
     },
     "tog-underline": "Tîra 'na rîga sòta i colegamèint.",
     "log-fulllog": "Guêrda la stòria dal registrasiòun",
     "edit-hook-aborted": "La mudéfica l'é stêda scanşlêda da l' hook.\nAn n'é mìa stê dê nisóna spiegasiòun.",
     "edit-gone-missing": "Impusébil arnuvêr la pàgina. \nA sèmbra ch'la sìa stêda scanşlêda.",
-    "edit-conflict": "Cuntrâst 'd edisiòun",
+    "edit-conflict": "Cuntrâst 'd edisiòun.",
     "edit-no-change": "La mudéfica an n'é mìa stêda cunsidrêda perchè an n'é mìa stê fât di cambiamèint al tèst.",
     "postedit-confirmation": "La mudéfica l'é stêda salvêda.",
     "edit-already-exists": "L'é impusébil fêr 'na pàgina nōva.\nLa ghé bèle.",
index f8f3845..1a21f1f 100644 (file)
     "permalink": "Permanent link",
     "print": "Print",
     "view": "View",
+    "view-foreign": "View on $1",
     "edit": "Edit",
+    "edit-local": "Edit local description",
     "create": "Create",
+    "create-local": "Add local description",
     "editthispage": "Edit this page",
     "create-this-page": "Create this page",
     "delete": "Delete",
     "listgrouprights-removegroup-self-all": "Remove all groups from own account",
     "trackingcategories": "Tracking categories",
     "trackingcategories-summary": "This page lists tracking categories which are automatically populated by the MediaWiki software. Their names can be changed by altering the relevant system messages in the {{ns:8}} namespace.",
-    "trackingcategories-msg": "Tracking Category",
+    "trackingcategories-msg": "Tracking category",
     "trackingcategories-name": "Message name",
     "trackingcategories-desc": "Category inclusion criteria",
-    "noindex-category-desc": "The page has a <nowiki>__NOINDEX__</nowiki> magic word on it (and is in a namespace where that flag is allowed), and hence is not indexed by robots.",
+    "noindex-category-desc": "The page is not indexed by robots because it has the magic word <nowiki>__NOINDEX__</nowiki> on it and is in a namespace where that flag is allowed.",
     "index-category-desc": "The page has a <nowiki>__INDEX__</nowiki> on it (and is in a namespace where that flag is allowed), and hence is indexed by robots where it normally wouldn't be.",
     "post-expand-template-inclusion-category-desc": "After expanding all the templates, the page size is bigger than $wgMaxArticleSize, so some templates weren't expanded.",
     "post-expand-template-argument-category-desc": "After expanding a template argument (something in triple braces, like {{{Foo}}}), the page is bigger than $wgMaxArticleSize.",
index e503cb8..4c5a3c8 100644 (file)
     "unwatchedpages": "Páginas no vigiladas",
     "listredirects": "Lista de redirecciones",
     "listduplicatedfiles": "Lista de archivos con duplicados",
+    "listduplicatedfiles-entry": "[[:File:$1|$1]] tiene [[$3|{{PLURAL:$2|un duplicado|$2 duplicados}}]].",
     "unusedtemplates": "Plantillas sin uso",
     "unusedtemplatestext": "Aquí se enumeran todas las páginas en el espacio de nombres {{ns:template}} que no están incluidas en otras páginas. Recuerda mirar lo que enlaza a las plantillas antes de borrarlas.",
     "unusedtemplateswlh": "otros enlaces",
     "trackingcategories": "Categorías de seguimiento",
     "trackingcategories-msg": "Categoría de seguimiento",
     "trackingcategories-name": "Nombre del mensaje",
+    "trackingcategories-desc": "Criterios de inclusión de categoría",
+    "noindex-category-desc": "La página contiene la palabra mágica <nowiki>__NOINDEX__</nowiki> (y está en un espacio de nombres donde la función está activada); y por ello los robots no la indizan.",
     "trackingcategories-nodesc": "No hay descripción disponible.",
     "trackingcategories-disabled": "La categoría está desactivada",
     "mailnologin": "Ninguna dirección de envio",
     "limitreport-expensivefunctioncount": "Cuenta de la funcion expansiva del analizador",
     "expandtemplates": "Expandir plantillas",
     "expand_templates_intro": "Esta página especial toma un texto wiki y expande todas sus plantillas recursivamente.\nTambién expande las funciones sintácticas como <code><nowiki>{{</nowiki>#language:…}}</code>, y variables como\n<code><nowiki>{{</nowiki>CURRENTDAY}}</code>. De hecho, expande casi cualquier cosa que esté entre llaves dobles.",
-    "expand_templates_title": "Título de la página, útil para expandir <nowiki>{{PAGENAME}}</nowiki> o similares",
+    "expand_templates_title": "Título de la página, útil para expandir {{PAGENAME}} o similares",
     "expand_templates_input": "Texto a expandir:",
     "expand_templates_output": "Resultado:",
     "expand_templates_xml_output": "Salida XML",
index 7f4130f..19e360f 100644 (file)
     "undo-success": "Izmjena je uklonjena (tekst u okviru ispod ne sadrži posljednju izmjenu). Molim sačuvajte stranicu (provjerite sažetak).",
     "undo-failure": "Ova izmjena ne može biti uklonjena zbog postojanja međuinačica.",
     "undo-norev": "Izmjena nije mogla biti uklonjena jer ne postoji ili je obrisana.",
-    "undo-summary": "Uklanjanje izmjene $1 što ju je unio/unijela [[Special:Contributions/$2|$2]] ([[User talk:$2|razgovor]])",
+    "undo-summary": "uklanjanje izmjene $1 {{GENDER:$2|suradnika|suradnice}} [[Posebno:Doprinosi/$2|$2]] ([[Razgovor sa suradnikom:$2|razgovor]])",
     "cantcreateaccounttitle": "Nije moguće stvoriti suradnički račun",
     "cantcreateaccount-text": "Otvaranje suradničkog računa ove IP adrese ('''$1''') blokirao/la je [[User:$3|$3]].\n\nRazlog koji je dao/la $3 je ''$2''",
     "viewpagelogs": "Vidi evidencije za ovu stranicu",
     "cantrollback": "Ne mogu ukloniti posljednju promjenu, postoji samo jedna promjena.",
     "alreadyrolled": "Ne mogu ukloniti posljednju promjenu članka [[:$1]] koju je napravio  [[User:$2|$2]] ([[User talk:$2|Razgovor]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]); netko je već promijenio stranicu ili uklonio promjenu.\n\nPosljednju promjenu napravio je [[User:$3|$3]] ([[User talk:$3|Razgovor]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
     "editcomment": "Sažetak promjene je bio: \"''$1''\".",
-    "revertpage": "Uklonjena promjena suradnika [[Special:Contributions/$2|$2]] ([[User talk:$2|razgovor]]), vraćeno na posljednju inačicu suradnika [[User:$1|$1]]",
+    "revertpage": "uklonjena promjena {{GENDER:$2|suradnika|suradnice}} [[Special:Contributions/$2|$2]] ([[User talk:$2|razgovor]]), vraćeno na posljednju inačicu {{GENDER:$1|suradnika|suradnice}} [[User:$1|$1]]",
     "revertpage-nouser": "Vraćene izmjene suradnika (suradničko ime uklonjeno) na posljednju inačicu suradnika [[User:$1|$1]]",
-    "rollback-success": "Uklonjeno uređivanje {{GENDER:$1|suradnika|suradnice}} $1\nvraćeno na posljednju inačicu {{GENDER:$2|suradnika|suradnice}} $2.",
+    "rollback-success": "uklonjeno uređivanje {{GENDER:$1|suradnika|suradnice}} $1\nvraćeno na posljednju inačicu {{GENDER:$2|suradnika|suradnice}} $2.",
     "sessionfailure-title": "Prekid sesije",
     "sessionfailure": "Uočili smo problem s Vašom prijavom. Zadnja naredba nije izvršena kako bi se izbjegla zloupotreba. Molimo Vas da se u pregledniku vratite natrag na prethodnu stranicu, ponovno je učitate i zatim pokušate opet.",
     "protectlogpage": "Evidencija zaštićivanja",
index 19ac231..2d97cce 100644 (file)
@@ -61,7 +61,8 @@
             "Wpedzich",
             "Ymar",
             "Žekřil71pl",
-            "לערי ריינהארט"
+            "לערי ריינהארט",
+            "Pan Cube"
         ]
     },
     "tog-underline": "Podkreślenie linków:",
     "pool-timeout": "Zbyt długi czas oczekiwania na blokadę",
     "pool-queuefull": "Kolejka zadań jest pełna",
     "pool-errorunknown": "Błąd nieznany",
+    "pool-servererror": "Usługa licznika nie jest dostępna ($1).",
     "aboutsite": "O {{GRAMMAR:MS.lp|{{SITENAME}}}}",
     "aboutpage": "Project:O {{GRAMMAR:MS.lp|{{SITENAME}}}}",
     "copyright": "Treść udostępniana na licencji $1, jeśli nie podano inaczej.",
     "listgrouprights-removegroup-self": "Możliwość usunięcia własnego konta z {{PLURAL:$2|grupy|grup:}} $1",
     "listgrouprights-addgroup-self-all": "Może dodać własne konto do wszystkich grup",
     "listgrouprights-removegroup-self-all": "Może usunąć własne konto ze wszystkich grup",
+    "trackingcategories": "Śledzenie kategorii",
+    "trackingcategories-msg": "Śledzenie kategorii",
     "trackingcategories-name": "Nazwa komunikatu",
     "post-expand-template-inclusion-category-desc": "Po rozwinięciu wszystkich szablonów, rozmiar strony jest większe niż $wgMaxArticleSize, więc niektóre szablony nie zostały rozwinięte.",
     "expensive-parserfunction-category-desc": "Na stronie używanych jest zbyt wiele wymagających funkcji parsera (takich jak <code>#ifexist</code>). Więcej informacji na stronie [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgExpensiveParserFunctionLimit Manual:$wgExpensiveParserFunctionLimit].",
     "sp-contributions-newbies-sub": "Dla nowych użytkowników",
     "sp-contributions-newbies-title": "Wkład nowych użytkowników",
     "sp-contributions-blocklog": "blokady",
+    "sp-contributions-suppresslog": "stłumiony wkład użytkownika",
     "sp-contributions-deleted": "usunięty wkład użytkownika",
     "sp-contributions-uploads": "przesłane pliki",
     "sp-contributions-logs": "rejestry",
index f193599..939d011 100644 (file)
     "permalink": "Display name for a permanent link to the current revision of a page. When the page is edited, permalink will still link to this revision. Example: Last menu link on [[{{MediaWiki:Mainpage}}]]\n\nSee also:\n* {{msg-mw|Permalink}}\n* {{msg-mw|Accesskey-t-permalink}}\n* {{msg-mw|Tooltip-t-permalink}}\n{{Identical|Permalink}}",
     "print": "{{Identical|Print}}",
     "view": "The default text of the \"View\" or \"Read\" (Vector) views tab which represents the basic view for the page. Should be in the infinitive mood.\n\n{{Identical|View}}",
+    "view-foreign": "The text on the tab that sends people to the \"master copy\" of the file at the foreign file\nrepository (e.g. Wikimedia Commons). Should be in the infinitive mood.\n\nParameters:\n* $1 - the name of the shared repository. On Wikimedia sites, $1 is {{msg-mw|Shared-repo-name-shared}}. On wikis using [[mw:InstantCommons|InstantCommons]], $1 is {{msg-mw|Shared-repo-name-wikimediacommons}}. The default value for $1 is {{msg-mw|Shared-repo}}.",
     "edit": "The text of the tab going to the edit form. When the page is protected, you will see {{msg-mw|Viewsource}}. Should be in the infinitive mood.\n\nSee also:\n* {{msg-mw|Edit}}\n* {{msg-mw|Accesskey-ca-edit}}\n* {{msg-mw|Tooltip-ca-edit}}\n{{Identical|Edit}}",
-    "create": "The text on the tab of the edit form on unexisting pages starts editing them.\n\n{{Identical|Create}}",
+    "edit-local": "The text on the tab going to the edit form for the local description page of a file from a foreign file repository (e.g. Wikimedia Commons). Should be in the infinitive mood.\n\nSee also:\n* {{msg-mw|Edit}}\n* {{msg-mw|Create-local}}",
+    "create": "The text on the tab of the edit form on unexisting pages starts editing them. Should be in the infinitive mood.\n\n{{Identical|Create}}",
+    "create-local": "The text on the tab going to the creation form for the (not yet existing) local description page of a file from a foreign file repository (e.g. Wikimedia Commons). Should be in the infinitive mood.\n\nSee also:\n* {{msg-mw|Create}}\n* {{msg-mw|Edit-local}}",
     "editthispage": "This is the \"edit\" link as used in the Cologne Blue skin, at the bottom of the page.\n\nSee {{msg-mw|Create-this-page}} for when the page does not exist.\n{{Identical|Edit this page}}",
     "create-this-page": "In the Cologne Blue skin this is the text for the link leading to the edit form on pages that have not yet been created, at the bottom of the page. See {{msg-mw|editthispage}} for when the page already exists.\n{{Identical|Createpage}}",
     "delete": "Name of the Delete tab shown for admins. Should be in the infinitive mood.\n\nSee also:\n* {{msg-mw|Delete}}\n* {{msg-mw|Accesskey-ca-delete}}\n* {{msg-mw|Tooltip-ca-delete}}\n{{Identical|Delete}}",
index e68b7f0..45fea28 100644 (file)
     "accountcreated": "सदस्यता प्राप्ता",
     "accountcreatedtext": "[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|talk]]) कृते \"योजसम्भाषणम्\" इति पृष्ठं रचितम् ।",
     "createaccount-title": "{{SITENAME}} कृते सदस्यता प्राप्यताम्",
-    "createaccount-text": "भवतः विद्युत्संदेशसंकेतार्थं केनचित् $2 इति जनेन {{SITENAME}} ($4) इत्यत्र  $3 इति कूटशब्दं दत्वा लेखा सृष्टाऽस्ति।\nभवता अधुना प्रवेशं कृत्वा कूटशब्दः परिवर्तितव्यः।\n\nचेत् सा लेखा त्रुटिवशात् सृष्टा, तर्हि भवान् एतत्सन्देशम् उपेक्षितुं शक्नोति।\n\nTranslation in English: \nSomeone created an account for your e-mail address on {{SITENAME}} ($4) named \"$2\", with password \"$3\".\nYou should log in and change your password now.\n\nYou may ignore this message, if this account was created in error.",
-    "usernamehasherror": "प्रयोक्तृनाम्नि हेश् इत्यक्षरं (#) न अन्तर्भवितुं शक्नोति।",
-    "login-throttled": "भवता सद्य एव प्रभूततया प्रवेशप्रयासाः कृताः।\nकृपया पुनः प्रयासार्थं किंचित् प्रतीक्षताम्।",
-    "login-abort-generic": "भवतः प्रवेशप्रयासः विफलीभूतः - परित्यक्तः",
+    "createaccount-text": "{{SITENAME}} ($4) इत्यत्र, \"$2\" नाम्ना, \"$3\" कूटशब्देन, च कोऽपि भवतः/भवत्याः ई-पत्रसङ्केतस्य उपयोगं कृत्वा सदस्यतां प्रापत् ।\nअधुना भवान्/भवती प्रवेशं कृत्वा गुप्तसङ्ख्यां परिवर्तयितं शक्नोति ।\nएषा सदस्यताप्राप्तिः क्षत्या अभवत् चेत्, एनं सन्देशम् अवगणोतु ।",
+    "usernamehasherror": "# इत्यस्य सङ्केतस्य उपयोगः योजकनाम्नि कर्तुं न शक्यते ।",
+    "login-throttled": "भवता/भवत्या अत्यधिकाः प्रवेशप्रयासाः कृताः । \nकृपया $1 कालं यावत् प्रतिक्षां करोतु ।",
+    "login-abort-generic": "भवतः/भवत्याः प्रवेशप्रयासः विफलीभूतः - परित्यक्तः",
     "loginlanguagelabel": "भाषा : $1",
-    "suspicious-userlogout": "भवतः सत्राद् बहिर्गमनस्य अनुरोधः अस्वीकृतोऽस्ति, यस्मादेतत् भग्नादेकस्मात् ब्राउज़र्तः अथवा स्वल्पसञ्चयि-प्रॉक्सितः प्रेषित आसीत्।",
+    "suspicious-userlogout": "भवतः/भवत्याः \"निर्गमनम्\" इत्यस्य विनतिं स्वीकर्तुं न शक्यते । कारणं भवता/भवत्या एषा विनतिः तृटियुक्तगवेक्षणात् प्रतिनिधि(proxy)-तः वा कृता ।",
+    "createacct-another-realname-tip": "वास्तविकनाम ऐच्छकम् अस्ति । भवान्/भवती एनं विकल्पं समर्थयति चेत्, भवतः/भवत्याः योगदानश्रेयस्य उल्लेखसमये अस्य उपयोगः भविष्यति ।",
     "pt-login": "प्रविश्यताम्",
     "pt-login-button": "प्रविश्यताम्",
     "pt-createaccount": "सदस्यता प्राप्यताम्",
     "pt-userlogout": "निर्गमनम्",
-    "php-mail-error-unknown": "पीएच्पी इत्येतस्य mail() फलने अज्ञाता काऽपि त्रुटिर्जाता।",
-    "user-mail-no-addy": "ईपत्रसङ्केतं विना ईपत्रप्रेषणस्य प्रयासः कृतः ।",
-    "user-mail-no-body": "भवता खलु विद्युत्पत्रं रिक्ततया अथवा अतिलघुरूपेण प्रेषितुं चेष्टितम्।",
+    "php-mail-error-unknown": "PHP लिप्याः मुख्यनियोगे (in main()) अज्ञातत्रुटिः प्राप्ता ।",
+    "user-mail-no-addy": "ई-पत्रसङ्केतं विना ई-पत्रप्रेषणस्य प्रयासः कृतः ।",
+    "user-mail-no-body": "भवता/भवत्या रिक्तं लघुसन्देशयुक्तं वा ई-पत्रं प्रेषणस्य प्रयासः कृतः ।",
     "changepassword": "कूटशब्दः परिवर्त्यताम्",
-    "resetpass_announce": "भवानà¥\8d à¤¤à¤¾à¤¤à¥\8dà¤\95ालिà¤\95-à¤\88पतà¥\8dरदà¥\8dवारा à¤\85तà¥\8dर à¤ªà¥\8dरविषà¥\8dà¤\9fà¤\83 à¤\85सà¥\8dति à¥¤\nपà¥\8dरवà¥\87शनसà¥\8dय à¤¸à¤®à¤¾à¤ªà¤¨à¤¾à¤¯ à¤­à¤µà¤¤à¤¾ à¤\85तà¥\8dर à¤¨à¥\82तनà¤\83 à¤\95à¥\82à¤\9fशबà¥\8dदà¤\83 à¤¦à¤¾à¤¤à¤µà¥\8dयà¤\83:",
+    "resetpass_announce": "सदसà¥\8dयतापà¥\8dरà¤\95à¥\8dरियाà¤\82 à¤ªà¥\82रà¥\8dणà¤\82 à¤\95रà¥\8dतà¥\81à¤\82 à¤¨à¥\82तनà¤\83 à¤\95à¥\82à¤\9fशबà¥\8dदà¤\83 à¤²à¥\87à¤\96नà¥\80यà¤\83 à¤\8fव à¥¤",
     "resetpass_text": "<!-- पाठं अत्र लिखतु -->",
-    "resetpass_header": "सदस्यतायाः कूटशब्दः परिवर्त्यताम्",
+    "resetpass_header": "सदस्यतायाः कूटशब्दः परिवर्त्यताम्",
     "oldpassword": "पुरातनः कूटशब्दः",
     "newpassword": "नूतनः कूटशब्दः",
-    "retypenew": "नूतनः कूटशब्दः पुनः लिख्यताम्:",
-    "resetpass_submit": "कूटशब्दः योज्यतां प्रविश्यतां च",
-    "changepassword-success": "भवतः कूटशब्दः सफलतया परिवर्तितः!\nअधुना भवान् प्रवेश्यते ...",
+    "retypenew": "नूतनकूटशब्दः पुनः लिख्यताम् :",
+    "resetpass_submit": "कूटशब्दः योज्यतां, प्रविश्यतां च",
+    "changepassword-success": "भवतः/भवत्याः कूटशब्दः सफलतया परिवर्तितः ।",
+    "changepassword-throttled": "भवता/भवत्या अत्यधिकाः प्रवेशप्रयासाः कृताः । \nकृपया $1 कालं यावत् प्रतिक्षां करोतु ।",
     "resetpass_forbidden": "कूटशब्दाः परिवर्तयितुं न शक्यन्ते",
-    "resetpass-no-info": "भवता à¤\8fततà¥\8dपà¥\83षà¥\8dठà¤\82 à¤ªà¥\8dरतà¥\8dयà¤\95à¥\8dषतया à¤¸à¤®à¥\8dपà¥\8dरापà¥\8dतà¥\81à¤\82 à¤ªà¥\8dरवà¥\87शà¤\83 à¤\85वशà¥\8dयमà¥\87व à¤\95रà¥\8dतà¥\8dतवà¥\8dयà¤\83।",
+    "resetpass-no-info": "à¤\8fततà¥\8d à¤ªà¥\83षà¥\8dठà¤\82 à¤¸à¤®à¥\8dपादयितà¥\81à¤\82 à¤ªà¥\8dरवà¥\87शà¤\83 à¤\85निवारà¥\8dयà¤\83 ।",
     "resetpass-submit-loggedin": "कूटशब्दः परिवर्त्यताम्",
     "resetpass-submit-cancel": "निरस्यताम्",
-    "resetpass-wrong-oldpass": "अल्पकालीनः अथवा सद्यःकालीनः कूटशब्दः अमान्यः अस्ति।\nभवता पूर्वे एव सफलतया स्वकीयः कूटशब्दः परिवर्तितः स्यात्, अथवा एकः नूतनः अल्पकालीनः कूटशब्दः प्रार्थितः स्यात्।",
-    "resetpass-temp-password": "अस्थिर रहस्यवाक् :",
-    "passwordreset": "कूटशब्द पुनःस्थापनम्",
-    "passwordreset-legend": "कूटशब्द पुनःस्थापनम्",
-    "passwordreset-disabled": "अस्मिन् विक्यां कूटशब्द पुनःस्थापनं असमर्थीकृतमस्ति।",
-    "passwordreset-username": "योजकनामन्:",
+    "resetpass-wrong-oldpass": "अल्पकालीनः सद्यःकालीनः वा कूटशब्दः अमान्यः अस्ति ।\nभवता/भवत्या पूर्वम् एव सफलतया स्वकीयः कूटशब्दः परिवर्तितः स्यात्, एकः नूतनः अल्पकालीनः कूटशब्दः प्रार्थितः स्यात् वा ।",
+    "resetpass-recycled": "कूटशब्दं परिवर्तनावसरे नवीनकूटशब्दे पुरानतकूटशब्दस्य उपयोगं मा करोतु ।",
+    "resetpass-temp-emailed": "भवता/भवत्या अल्पकालीन(temporary)कूटशब्देन प्रवेशः प्राप्तः । \nसदस्यताप्रक्रियां पूर्णं कर्तुं नूतनः कूटशब्दः लेखनीयः एव ।",
+    "resetpass-temp-password": "अल्पकालीनकूटशब्दः :",
+    "resetpass-expired": "भवतः/भवत्याः कृटशब्दस्य अवधिः समाप्ता । प्रवेष्टुं नवीनकूटशब्दं निर्धारयतु ।",
+    "resetpass-expired-soft": "भवतः/भवत्याः कृटशब्दस्य अवधिः समाप्ता । कृपया नवीनकूटशब्दं निर्धारयतु । पश्चात् नवीनकूटशब्दं निर्धारयितुं \"{{int:resetpass-submit-cancel}}\" नुदतु ।",
+    "resetpass-validity-soft": "भवतः/भवत्याः कृटशब्दः अयोग्यः अस्ति । कृपया नवीनकूटशब्दं निर्धारयतु । पश्चात् नवीनकूटशब्दं निर्धारयितुं \"{{int:resetpass-submit-cancel}}\" नुदतु ।",
+    "passwordreset": "कूटशब्दः परिवर्त्यताम्",
+    "passwordreset-text-one": "ई-पत्रमाध्यमेन अल्पकालीनकूटशब्दं प्राप्तुम् अधस्तनं प्रपत्रं पूरयतु ।",
+    "passwordreset-text-many": "{{PLURAL:$1|ई-पत्रमाध्यमेन अल्पकालीनकूटशब्दं प्राप्तुम् अधस्तनां कामपि एकां पेटिकां पूरयतु ।}}",
+    "passwordreset-legend": "कूटशब्दः परिवर्त्यताम्",
+    "passwordreset-disabled": "अस्मिन् विकि-जालस्थाने कूटशब्दं परिर्तितुं निषेधः अस्ति ।",
+    "passwordreset-emaildisabled": "अस्मिन् विकि-जालस्थाने ई-पत्रसम्बद्धाः सेवाः असमर्थिताः सन्ति ।",
+    "passwordreset-username": "योजकनाम:",
     "passwordreset-domain": "क्षेत्रम्:",
-    "passwordreset-capture": "फलितरà¥\82पमà¥\8d à¤\88पतà¥\8dरà¤\82 à¤\95िà¤\82 à¤¦à¥\83शà¥\8dयतà¥\87 ?",
+    "passwordreset-capture": "परिणामसà¥\8dवरà¥\82पनिरà¥\8dमितानि à¤\88-पतà¥\8dराणि à¤¦à¥\8dरषà¥\8dà¤\9fà¥\81मà¥\8d à¤\87à¤\9aà¥\8dà¤\9bति ?",
     "passwordreset-capture-help": "अस्यां मञ्जूषायां यदि भवता अङ्क्यते तर्हि ईपत्रम् (अस्थायिकूटशब्देन सह) दर्श्यते प्रेष्यते च ।",
-    "passwordreset-email": "परमाणà¥\81पतà¥\8dरसà¤\99à¥\8dà¤\97à¥\87त:",
-    "passwordreset-emailtitle": "{{SITENAME}} à¤\87तà¥\8dयतà¥\8dर à¤²à¥\87à¤\96ा-विवरणमà¥\8d",
+    "passwordreset-email": "à¤\88-पतà¥\8dरसà¤\99à¥\8dà¤\95à¥\87तà¤\83",
+    "passwordreset-emailtitle": "{{SITENAME}} à¤\87तà¥\8dयतà¥\8dर à¤¯à¥\8bà¤\9cà¤\95विषयà¥\87",
     "passwordreset-emailtext-ip": "कश्चित् (भवान् अपि स्यात्, $1 इति ऐ. पि. सङ्केतात्) {{SITENAME}} ($4) इत्यस्य प्रवेशसम्बद्धं विवरणं प्रार्थितवान् । अधः सूचितस्य उपयोक्तुः {{PLURAL:$3 | प्रवेशविवरणं | प्रवेशविवरणानि}} \n$2\nइत्यनेन ईपत्रसङ्केतेन सम्बद्धम् अस्ति / सम्बद्धानि सन्ति ।\n{{PLURAL:$3|अयं तात्कालिकः कूटशब्दः | इमे तात्कालिकाः कूटशब्दाः}}  {{PLURAL:$5| एकं दिनं | $5 दिनानि}} यावत् सक्रियः भवति / सक्रियाः भवन्ति ।",
     "passwordreset-emailtext-user": "कश्चित् (भवान् अपि स्यात्, $1 इति ऐ. पि. सङ्केतात्) {{SITENAME}} ($4) इत्यस्य प्रवेशसम्बद्धं विवरणं प्रार्थितवान् । अधः सूचितस्य उपयोक्तुः {{PLURAL:$3 | प्रवेशविवरणं | प्रवेशविवरणानि}} \n$2\nइत्यनेन ईपत्रसङ्केतेन सम्बद्धम् अस्ति / सम्बद्धानि सन्ति ।\n{{PLURAL:$3|अयं तात्कालिकः कूटशब्दः | इमे तात्कालिकाः कूटशब्दाः}}  {{PLURAL:$5| एकं दिनं | $5 दिनानि}} यावत् सक्रियः भवति / सक्रियाः भवन्ति ।",
     "passwordreset-emailelement": "प्रयोक्तृनाम: $1\nअल्पकालिकः कूटशब्दः : $2",
index 4f1192d..63d4441 100644 (file)
     "user-mail-no-addy": "Покушали сте да пошаљете поруку без е-адресе.",
     "user-mail-no-body": "Покушано слање електронске поруке с празним или неразумно кратким садржајем.",
     "changepassword": "Промени лозинку",
-    "resetpass_announce": "Ð\9fÑ\80иÑ\98авÑ\99ени Ñ\81Ñ\82е Ñ\81 Ð¿Ñ\80ивÑ\80еменом Ð»Ð¾Ð·Ð¸Ð½ÐºÐ¾Ð¼.\nÐ\94а Ð±Ð¸Ñ\81Ñ\82е Ð·Ð°Ð²Ñ\80Ñ\88или Ð¿Ñ\80иÑ\98авÑ\83, Ð¿Ð¾Ð´ÐµÑ\81иÑ\82е Ð½Ð¾Ð²Ñ\83 Ð»Ð¾Ð·Ð¸Ð½ÐºÑ\83 Ð¾Ð²Ð´Ðµ:",
+    "resetpass_announce": "Ð\94а Ð±Ð¸Ñ\81Ñ\82е Ð·Ð°Ð²Ñ\80Ñ\88или Ð¿Ñ\80иÑ\98авÑ\83, Ð¿Ð¾Ð´ÐµÑ\81иÑ\82е Ð½Ð¾Ð²Ñ\83 Ð»Ð¾Ð·Ð¸Ð½ÐºÑ\83 Ð¾Ð²Ð´Ðµ.",
     "resetpass_text": "<!-- Овде унесите текст -->",
     "resetpass_header": "Промена лозинке налога",
     "oldpassword": "Стара лозинка:",
     "resetpass-temp-password": "Привремена лозинка:",
     "resetpass-abort-generic": "Промену лозинке је спречио додатак.",
     "resetpass-expired": "Ваша лозинка је истекла. Поставите нову лозинку да бисте се пријавили.",
-    "resetpass-expired-soft": "Ваша лозинка је истекла и морате поставити нову. Поставите нову лозинку или кликните откажи да је поставите касније.",
+    "resetpass-expired-soft": "Ваша лозинка је истекла и морате поставити нову. Поставите нову лозинку или кликните „{{int:resetpass-submit-cancel}}“ да је поставите касније.",
     "passwordreset": "Обнављање лозинке",
     "passwordreset-text-one": "Попуните овај образац да бисте ресетовали лозинку.",
     "passwordreset-text-many": "{{PLURAL:$1|Испуните једно од поља како би сте добили привремену лозинку на е-пошту.}}",
     "content-failed-to-parse": "Не могу да рашчланим садржај типа $2 за модел $1: $3",
     "invalid-content-data": "Неисправни подаци садржаја",
     "content-not-allowed-here": "Садржај модела „$1“ није дозвољен на страници [[$2]]",
-    "editwarning-warning": "Ако напустите ову страницу, изгубићете све измене које сте направили.\nАко сте пријављени, можете онемогућити ово упозорење у својим подешавањима, у одељку „Уређивање“.",
+    "editwarning-warning": "Ако напустите ову страницу, изгубићете све измене које сте направили.\nАко сте пријављени, можете онемогућити ово упозорење у својим подешавањима, у одељку „{{int:prefs-editing}}“.",
     "content-model-wikitext": "викитекст",
     "content-model-text": "чист текст",
     "content-model-javascript": "јаваскрипт",
     "page_last": "последња",
     "histlegend": "Избор разлика: изаберите кутијице измена за упоређивање и притисните ентер или дугме на дну.<br />\nОбјашњење: '''({{int:cur}})''' – разлика с тренутном изменом,\n'''({{int:last}})''' – разлика с претходном изменом, '''{{int:minoreditletter}}''' – мала измена",
     "history-fieldset-title": "Преглед историје",
-    "history-show-deleted": "само обрисано",
+    "history-show-deleted": "Само обрисано",
     "histfirst": "најстарије",
     "histlast": "најновије",
     "historysize": "({{PLURAL:$1|1 бајт|$1 бајта|$1 бајтова}})",
     "semicolon-separator": ";&#32;",
     "comma-separator": ",&#32;",
     "colon-separator": ":&#32;",
-    "pipe-separator": "&#32;&#32;",
+    "pipe-separator": "&#32;|&#32;",
     "word-separator": "&#32;",
     "ellipsis": "…",
     "percent": "$1%",
index b4e9186..24141d1 100644 (file)
     "page_last": "poslednja",
     "histlegend": "Izbor razlika: izaberite kutijice izmena za upoređivanje i pritisnite enter ili dugme na dnu.<br />\nObjašnjenje: '''({{int:cur}})''' – razlika s trenutnom izmenom,\n'''({{int:last}})''' – razlika s prethodnom izmenom, '''{{int:minoreditletter}}''' – mala izmena",
     "history-fieldset-title": "Pregled istorije",
-    "history-show-deleted": "samo obrisano",
+    "history-show-deleted": "Samo obrisane",
     "histfirst": "najstarije",
     "histlast": "najnovije",
     "historysize": "({{PLURAL:$1|1 bajt|$1 bajta|$1 bajtova}})",
     "semicolon-separator": ";&#32;",
     "comma-separator": ",&#32;",
     "colon-separator": ":&#32;",
-    "pipe-separator": "&#32;&#32;",
+    "pipe-separator": "&#32;|&#32;",
     "word-separator": "&#32;",
     "ellipsis": "…",
     "percent": "$1%",
index 73d3a77..0b5e91b 100644 (file)
     "pool-timeout": "తాళం కొరకు వేచివుండడానికి కాలపరిమితి అయిపోయింది",
     "pool-queuefull": "సమూహపు వరుస నిండుగా ఉంది",
     "pool-errorunknown": "తెలియని లోపం",
+    "pool-servererror": "పూల్ కౌంటర్ సేవ అందుబాటులో లేదు ($1).",
     "aboutsite": "{{SITENAME}} గురించి",
     "aboutpage": "Project:గురించి",
     "copyright": "విషయం $1 కి లోబడి లభ్యం, వేరుగా పేర్కొంటే తప్ప.",
     "unwatchedpages": "వీక్షణలో లేని పేజీలు",
     "listredirects": "దారిమార్పుల జాబితా",
     "listduplicatedfiles": "నకళ్ళు కలిగిన ఫైళ్ళ జాబితా",
+    "listduplicatedfiles-summary": "ఇది ఒక ఫైలు యొక్క సరికొత్త సంచిక మరొక ఫైలు యొక్క సరికొత్త సంచికతో సమానంగా ఉండే నకిలీ ఫైళ్ళ జాబితా. స్థానిక ఫైళ్ళు మాత్రమే పరిగణించబడ్డాయి.",
     "listduplicatedfiles-entry": "[[:File:$1|$1]] కు [[$3|{{PLURAL:$2|ఓ నకలు ఉంది|$2 నకళ్ళున్నాయి}}]].",
     "unusedtemplates": "వాడని మూసలు",
     "unusedtemplatestext": "వేరే ఇతర పేజీలలో చేర్చని {{ns:template}} పేరుబరిలోని పేజీలన్నింటినీ ఈ పేజీ చూపిస్తుంది.\nమూసలను తొలగించే ముందు వాటికి ఉన్న ఇతర లింకుల కోసం చూడడం గుర్తుంచుకోండి.",
     "listgrouprights-addgroup-self-all": "అన్ని సమూహాలని స్వంత ఖాతాకు చేర్చుకోలగడటం",
     "listgrouprights-removegroup-self-all": "స్వంత ఖాతా నుండి అన్ని సమూహాలనూ తొలగించుకోగలగడం",
     "trackingcategories-name": "సందేశం పేరు",
+    "trackingcategories-nodesc": "వివరణ లేదు.",
     "trackingcategories-disabled": "వర్గం అచేతనమై ఉంది",
     "mailnologin": "పంపించవలసిన చిరునామా లేదు",
     "mailnologintext": "ఇతరులకు ఈ-మెయిలు పంపించాలంటే, మీరు [[Special:UserLogin|లాగిన్‌]] అయి ఉండాలి, మరియు మీ [[Special:Preferences|అభిరుచుల]]లో సరైన ఈ-మెయిలు చిరునామా ఇచ్చి ఉండాలి.",
index ca4c244..74b6d8b 100644 (file)
     "watchthis": "เฝ้าดูหน้านี้",
     "savearticle": "บันทึก",
     "preview": "ตัวอย่าง",
-    "showpreview": "à¹\81สà¸\94à¸\87ตัวอย่าง",
+    "showpreview": "à¸\94ูตัวอย่าง",
     "showlivepreview": "แสดงตัวอย่างทันที",
     "showdiff": "แสดงความเปลี่ยนแปลง",
     "anoneditwarning": "'''คำเตือน:''' คุณมิได้ล็อกอิน เลขที่อยู่ไอพีของคุณจะถูกบันทึกไว้ในประวัติการแก้ไขของหน้านี้",
     "storedversion": "รุ่นที่เก็บไว้",
     "nonunicodebrowser": "'''คำเตือน: เว็บเบราว์เซอร์นี้ไม่สนับสนุนการใช้งานแบบยูนิโคด ตัวอักษรที่ไม่ใช่แบบแอสกีจะแสดงในกล่องการแก้ไขในลักษณะรหัสเลขฐานสิบหก'''",
     "editingold": "'''คำเตือน: ข้อมูลที่แก้ไขอยู่ไม่ใช่ข้อมูลใหม่ล่าสุดของหน้านี้ ถ้าทำการบันทึกไป การเปลี่ยนแปลงที่เกิดขึ้นระหว่างรุ่นนี้กับรุ่นใหม่จะสูญหาย'''",
-    "yourdiff": "à¸\82à¹\89อแตกต่าง",
+    "yourdiff": "à¸\84วามแตกต่าง",
     "copyrightwarning": "โปรดอย่าลืมว่างานเขียนทั้งหมดใน {{SITENAME}} ผู้เขียนทั้งหมดยินดีให้งานเก็บไว้ภายใต้สัญญาลิขสิทธิ์ $2 (ดู $1 สำหรับข้อมูลเพิ่มเติม)\nถ้าคุณไม่ต้องการให้งานของคุณถูกแก้ไข หรือไม่ต้องการให้งานเผยแพร่ตามที่ได้กล่าวไว้ อย่าส่งข้อความเข้ามาที่นี่<br />\nนอกจากนี้แน่ใจว่าข้อความที่ส่งเข้ามาได้เขียนด้วยตัวเอง ไม่ได้คัดลอก หรือทำซ้ำจากแหล่งอื่น\n'''อย่าส่งงานที่มีลิขสิทธิ์เข้ามาก่อนได้รับอนุญาตจากเจ้าของ!'''",
     "copyrightwarning2": "โปรดอย่าลืมว่างานเขียนทั้งหมดใน {{SITENAME}} อาจจะถูกแก้ไข ดัดแปลง หรือลบออกโดยผู้ร่วมเขียนคนอื่น\nถ้าคุณไม่ต้องการให้งานของคุณถูกแก้ไข หรือไม่ต้องการให้งานเผยแพร่ตามที่กล่าวไว้ อย่าส่งข้อความของคุณเข้ามาที่นี่<br />\nนอกจากนี้คุณแน่ใจว่าข้อความที่ส่งเข้ามาคุณได้เขียนด้วยตัวเอง ไม่ได้คัดลอก ทำซ้ำส่วนหนึ่งส่วนใดหรือทั้งหมดจากแหล่งอื่น (ดูรายละเอียดที่ $1)\n'''อย่าส่งงานที่มีลิขสิทธิ์เข้ามาก่อนได้รับอนุญาตจากเจ้าของ!'''",
     "longpageerror": "'''ข้อผิดพลาด: ข้อความที่คุณส่งเข้ามามีขนาด $1 กิโลไบต์\nซึ่งเกินกว่าขนาดสูงสุดซึ่งกำหนดไว้ที่ $2 กิโลไบต์ จึงไม่สามารถบันทึกได้'''",
     "revdelete-no-file": "ไม่มีไฟล์ที่ระบุ",
     "revdelete-show-file-confirm": "คุณแน่ใจที่จะดูรุ่นที่ถูกลบของไฟล์ \"<nowiki>$1</nowiki>\" เมื่อวันที่ $2 เวลา $3 หรือไม่",
     "revdelete-show-file-submit": "ใช่",
+    "revdelete-selected-text": "{{PLURAL:$1|รุ่นที่เลือก}}ของ [[:$2]]:",
+    "revdelete-selected-file": "{{PLURAL:$1|รุ่นไฟล์ที่เลือก}}ของ [[:$2]]:",
     "logdelete-selected": "{{PLURAL:$1|เหตุการณ์ปูมที่เลือก|เหตุการณ์ปูมที่เลือก}} :",
     "revdelete-text-text": "รุ่นที่ถูกลบจะยังปรากฏในประวัติหน้า แต่สาธารณะจะไม่สามารถเข้าถึงเนื้อหาบางส่วนได้",
     "revdelete-text-file": "รุ่นที่ถูกลบจะยังปรากฏในประวัติไฟล์ แต่สาธารณะจะไม่สามารถเข้าถึงเนื้อหาบางส่วนได้",
     "listgrouprights-removegroup-self": "ลบ{{PLURAL:$2|กลุ่ม|กลุ่ม}}ออกจากบัญชี: $1",
     "listgrouprights-addgroup-self-all": "เพิ่มทุกกลุ่มเข้าไปในบัญชีนี้",
     "listgrouprights-removegroup-self-all": "นำทุกกลุ่มออกจากบัญชีนี้",
+    "hidden-category-category-desc": "นี่คือหมวดหมู่ที่ติด <nowiki>__HIDDENCAT__</nowiki> ซึ่งป้องกันมิให้แสดงในกล่องลิงก์หมวดหมู่ในหน้าโดยปริยาย",
     "mailnologin": "ไม่มีที่อยู่ส่ง",
     "mailnologintext": "คุณต้อง[[Special:UserLogin|ล็อกอิน]]และมีที่อยู่อีเมลที่สมเหตุสมผลใน[[Special:Preferences|การตั้งค่า]]ของคุณ ในการส่งอีเมลหาผู้ใช้อื่น",
     "emailuser": "ส่งอีเมลหาผู้ใช้นี้",
     "sp-contributions-username": "เลขที่อยู่ไอพีหรือชื่อผู้ใช้:",
     "sp-contributions-toponly": "แสดงเฉพาะการแก้ไขรุ่นล่าสุด",
     "sp-contributions-newonly": "แสดงเฉพาะการแก้ไขที่เป็นการสร้างหน้า",
-    "sp-contributions-submit": "สืà¸\9aà¸\84à¹\89à¸\99",
+    "sp-contributions-submit": "à¸\84à¹\89à¸\99หา",
     "whatlinkshere": "หน้าที่ลิงก์มา",
     "whatlinkshere-title": "หน้าที่ลิงก์มายัง \"$1\"",
     "whatlinkshere-page": "หน้า:",
index 1f91624..645b6ae 100644 (file)
     "userlogin-resetlink": "忘记你的登录信息?",
     "userlogin-resetpassword-link": "忘记密码?",
     "userlogin-helplink2": "登录帮助",
-    "userlogin-loggedin": "你已经以{{GENDER:$1|$1}}的身份登录。使用下面的表格以其他用户的身份登录。",
+    "userlogin-loggedin": "您已经以{{GENDER:$1|$1}}的身份登录。使用以下表格以其他用户的身份登录。",
     "userlogin-createanother": "创建另一个账户",
     "createacct-join": "请在下面输入你的信息。",
     "createacct-another-join": "在下方输入新帐户信息。",
diff --git a/maintenance/resources/update-oojs.sh b/maintenance/resources/update-oojs.sh
new file mode 100755 (executable)
index 0000000..f26350f
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+if [ "$2" != "" ]
+then
+       echo >&2 "Usage: $0 [<version>]"
+       exit 1
+fi
+
+MW_DIR=$(cd $(dirname $0)/../..; pwd) # e.g. mediawiki-core/
+NPM_DIR=`mktemp -d 2>/dev/null || mktemp -d -t 'mw-update-oojs'` # e.g. /tmp/mw-update-oojs.rI0I5Vir
+
+# Prepare MediaWiki working copy
+cd $MW_DIR
+git reset resources/lib/oojs/ && git checkout resources/lib/oojs/ && git fetch origin || exit 1
+
+git checkout -B upstream-oojs origin/master || exit 1
+
+# Fetch upstream version
+cd $NPM_DIR
+if [ "$1" != "" ]
+then
+       npm install oojs@$1 || exit 1
+else
+       npm install oojs || exit 1
+fi
+
+OOJS_VERSION=$(node -e 'console.log(JSON.parse(require("fs").readFileSync("./node_modules/oojs/package.json")).version);')
+if [ "$OOJS_VERSION" == "" ]
+then
+       echo 'Could not find OOjs version'
+       exit 1
+fi
+
+# Copy file(s)
+mv ./node_modules/oojs/dist/* $MW_DIR/resources/lib/oojs/ || exit 1
+
+# Generate commit
+cd $MW_DIR || exit 1
+
+# Clean up temporary area
+rm -rf $NPM_DIR
+
+COMMITMSG=$(cat <<END
+Update OOjs to v$OOJS_VERSION
+
+Release notes:
+ https://git.wikimedia.org/blob/oojs%2Fcore.git/v$OOJS_VERSION/History.md
+END
+)
+
+git commit resources/lib/oojs/ -m "$COMMITMSG" || exit 1
index cd657ac..dc9c763 100644 (file)
@@ -36,6 +36,7 @@ class RunJobs extends Maintenance {
                $this->addOption( 'maxtime', 'Maximum amount of wall-clock time', false, true );
                $this->addOption( 'type', 'Type of job to run', false, true );
                $this->addOption( 'procs', 'Number of processes to use', false, true );
+               $this->addOption( 'nothrottle', 'Ignore job throttling configuration', false, false );
        }
 
        public function memoryLimit() {
@@ -66,6 +67,7 @@ class RunJobs extends Maintenance {
                $type = $this->getOption( 'type', false );
                $maxJobs = $this->getOption( 'maxjobs', false );
                $maxTime = $this->getOption( 'maxtime', false );
+               $noThrottle = $this->hasOption( 'nothrottle' );
                $startTime = time();
 
                $group = JobQueueGroup::singleton();
@@ -83,10 +85,12 @@ class RunJobs extends Maintenance {
                $flags = JobQueueGroup::USE_CACHE;
                $lastTime = time(); // time since last slave check
                do {
+                       $backoffs = array_filter( $backoffs, $backoffExpireFunc );
+                       $blacklist = $noThrottle ? array() : array_keys( $backoffs );
                        if ( $type === false ) {
-                               $backoffs = array_filter( $backoffs, $backoffExpireFunc );
-                               $blacklist = array_keys( $backoffs );
                                $job = $group->pop( JobQueueGroup::TYPE_DEFAULT, $flags, $blacklist );
+                       } elseif ( in_array( $type, $blacklist ) ) {
+                               $job = false; // requested queue in backoff state
                        } else {
                                $job = $group->pop( $type ); // job from a single queue
                        }
index 00cd433..3ddb5b3 100644 (file)
   opacity: 0.2;
 }
 
-.oo-ui-menuItemWidget.oo-ui-optionWidget-highlighted {
+.oo-ui-menuItemWidget.oo-ui-optionWidget-selected {
+  background-color: transparent;
+}
+
+.oo-ui-menuItemWidget.oo-ui-optionWidget-highlighted,
+.oo-ui-menuItemWidget.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected {
   background-color: #e1f3ff;
 }
 
index 1565cbb..cf838b9 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.1.0-pre (eaa1b7f06d)
+ * OOjs UI v0.1.0-pre (4975b8db90)
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2014 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: Thu Apr 03 2014 16:56:21 GMT-0700 (PDT)
+ * Date: Mon Apr 07 2014 15:17:10 GMT-0700 (PDT)
  */
 ( function ( OO ) {
 
@@ -193,9 +193,11 @@ OO.ui.Element = function OoUiElement( config ) {
        }
 };
 
-/* Static Properties */
+/* Setup */
+
+OO.initClass( OO.ui.Element );
 
-OO.ui.Element.static = {};
+/* Static Properties */
 
 /**
  * HTML tag name.
@@ -702,10 +704,9 @@ OO.ui.Frame = function OoUiFrame( config ) {
 
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.Frame, OO.ui.Element );
-
 OO.mixinClass( OO.ui.Frame, OO.EventEmitter );
 
 /* Static Properties */
@@ -968,10 +969,9 @@ OO.ui.Window = function OoUiWindow( config ) {
        this.frame.connect( this, { 'load': 'initialize' } );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.Window, OO.ui.Element );
-
 OO.mixinClass( OO.ui.Window, OO.EventEmitter );
 
 /* Events */
@@ -1339,10 +1339,9 @@ OO.ui.WindowSet = function OoUiWindowSet( factory, config ) {
        this.$element.addClass( 'oo-ui-windowSet' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.WindowSet, OO.ui.Element );
-
 OO.mixinClass( OO.ui.WindowSet, OO.EventEmitter );
 
 /* Events */
@@ -1516,7 +1515,7 @@ OO.ui.Dialog = function OoUiDialog( config ) {
        this.setSize( config.size );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.Dialog, OO.ui.Window );
 
@@ -1715,10 +1714,9 @@ OO.ui.Layout = function OoUiLayout( config ) {
        this.$element.addClass( 'oo-ui-layout' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.Layout, OO.ui.Element );
-
 OO.mixinClass( OO.ui.Layout, OO.EventEmitter );
 /**
  * User interface control.
@@ -1751,10 +1749,9 @@ OO.ui.Widget = function OoUiWidget( config ) {
        this.setDisabled( !!config.disabled );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.Widget, OO.ui.Element );
-
 OO.mixinClass( OO.ui.Widget, OO.EventEmitter );
 
 /* Events */
@@ -2274,9 +2271,11 @@ OO.ui.IconedElement = function OoUiIconedElement( $icon, config ) {
        this.setIcon( config.icon || this.constructor.static.icon );
 };
 
-/* Static Properties */
+/* Setup */
 
-OO.ui.IconedElement.static = {};
+OO.initClass( OO.ui.IconedElement );
+
+/* Static Properties */
 
 /**
  * Icon.
@@ -2362,9 +2361,11 @@ OO.ui.IndicatedElement = function OoUiIndicatedElement( $indicator, config ) {
        this.setIndicatorTitle( config.indicatorTitle  || this.constructor.static.indicatorTitle );
 };
 
-/* Static Properties */
+/* Setup */
+
+OO.initClass( OO.ui.IndicatedElement );
 
-OO.ui.IndicatedElement.static = {};
+/* Static Properties */
 
 /**
  * indicator.
@@ -2476,9 +2477,11 @@ OO.ui.LabeledElement = function OoUiLabeledElement( $label, config ) {
        this.autoFitLabel = config.autoFitLabel === undefined || !!config.autoFitLabel;
 };
 
-/* Static Properties */
+/* Setup */
 
-OO.ui.LabeledElement.static = {};
+OO.initClass( OO.ui.LabeledElement );
+
+/* Static Properties */
 
 /**
  * Label.
@@ -2627,9 +2630,11 @@ OO.ui.TitledElement = function OoUiTitledElement( $titled, config ) {
        this.setTitle( config.title || this.constructor.static.title );
 };
 
-/* Static Properties */
+/* Setup */
+
+OO.initClass( OO.ui.TitledElement );
 
-OO.ui.TitledElement.static = {};
+/* Static Properties */
 
 /**
  * Title.
@@ -2719,10 +2724,9 @@ OO.ui.Tool = function OoUiTool( toolGroup, config ) {
        this.setTitle( config.title || this.constructor.static.title );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.Tool, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.Tool, OO.ui.IconedElement );
 
 /* Events */
@@ -2977,10 +2981,9 @@ OO.ui.Toolbar = function OoUiToolbar( toolFactory, toolGroupFactory, config ) {
        this.$element.addClass( 'oo-ui-toolbar' ).append( this.$bar );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.Toolbar, OO.ui.Element );
-
 OO.mixinClass( OO.ui.Toolbar, OO.EventEmitter );
 OO.mixinClass( OO.ui.Toolbar, OO.ui.GroupElement );
 
@@ -3144,7 +3147,7 @@ OO.ui.ToolFactory = function OoUiToolFactory() {
        OO.ui.ToolFactory.super.call( this );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.ToolFactory, OO.Factory );
 
@@ -3307,10 +3310,9 @@ OO.ui.ToolGroup = function OoUiToolGroup( toolbar, config ) {
        this.populate();
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.ToolGroup, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.ToolGroup, OO.ui.GroupElement );
 
 /* Events */
@@ -3584,7 +3586,7 @@ OO.ui.ToolGroupFactory = function OoUiToolGroupFactory() {
        }
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.ToolGroupFactory, OO.Factory );
 
@@ -3638,10 +3640,9 @@ OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
        }
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.FieldsetLayout, OO.ui.Layout );
-
 OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.IconedElement );
 OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.LabeledElement );
 OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.GroupElement );
@@ -3699,10 +3700,9 @@ OO.ui.FieldLayout = function OoUiFieldLayout( field, config ) {
        this.setAlignment( config.align );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.FieldLayout, OO.ui.Layout );
-
 OO.mixinClass( OO.ui.FieldLayout, OO.ui.LabeledElement );
 
 /* Methods */
@@ -3799,7 +3799,7 @@ OO.ui.GridLayout = function OoUiGridLayout( panels, config ) {
        }
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.GridLayout, OO.ui.Layout );
 
@@ -3993,7 +3993,7 @@ OO.ui.BookletLayout = function OoUiBookletLayout( config ) {
        }
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.BookletLayout, OO.ui.Layout );
 
@@ -4383,7 +4383,7 @@ OO.ui.PanelLayout = function OoUiPanelLayout( config ) {
        this.$element.addClass( 'oo-ui-' + OO.ui.Element.getDir( this.$.context ) );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.PanelLayout, OO.ui.Layout );
 /**
@@ -4413,7 +4413,7 @@ OO.ui.PageLayout = function OoUiPageLayout( name, config ) {
        this.$element.addClass( 'oo-ui-pageLayout' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.PageLayout, OO.ui.PanelLayout );
 
@@ -4516,10 +4516,9 @@ OO.ui.StackLayout = function OoUiStackLayout( config ) {
        }
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.StackLayout, OO.ui.PanelLayout );
-
 OO.mixinClass( OO.ui.StackLayout, OO.ui.GroupElement );
 
 /* Events */
@@ -4633,7 +4632,7 @@ OO.ui.BarToolGroup = function OoUiBarToolGroup( toolbar, config ) {
        this.$element.addClass( 'oo-ui-barToolGroup' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.BarToolGroup, OO.ui.ToolGroup );
 
@@ -4695,10 +4694,9 @@ OO.ui.PopupToolGroup = function OoUiPopupToolGroup( toolbar, config ) {
                .prepend( this.$handle );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.PopupToolGroup, OO.ui.ToolGroup );
-
 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.IconedElement );
 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.IndicatedElement );
 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.LabeledElement );
@@ -4810,7 +4808,7 @@ OO.ui.ListToolGroup = function OoUiListToolGroup( toolbar, config ) {
        this.$element.addClass( 'oo-ui-listToolGroup' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.ListToolGroup, OO.ui.PopupToolGroup );
 
@@ -4844,7 +4842,7 @@ OO.ui.MenuToolGroup = function OoUiMenuToolGroup( toolbar, config ) {
        this.$element.addClass( 'oo-ui-menuToolGroup' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.MenuToolGroup, OO.ui.PopupToolGroup );
 
@@ -4901,10 +4899,9 @@ OO.ui.PopupTool = function OoUiPopupTool( toolbar, config ) {
                .append( this.popup.$element );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.PopupTool, OO.ui.Tool );
-
 OO.mixinClass( OO.ui.PopupTool, OO.ui.PopuppableElement );
 
 /* Methods */
@@ -4954,7 +4951,7 @@ OO.ui.GroupWidget = function OoUiGroupWidget( $element, config ) {
        OO.ui.GroupWidget.super.call( this, $element, config );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.GroupWidget, OO.ui.GroupElement );
 
@@ -5055,10 +5052,9 @@ OO.ui.IconWidget = function OoUiIconWidget( config ) {
        this.$element.addClass( 'oo-ui-iconWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.IconWidget, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.IconWidget, OO.ui.IconedElement );
 OO.mixinClass( OO.ui.IconWidget, OO.ui.TitledElement );
 
@@ -5091,10 +5087,9 @@ OO.ui.IndicatorWidget = function OoUiIndicatorWidget( config ) {
        this.$element.addClass( 'oo-ui-indicatorWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.IndicatorWidget, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.IndicatedElement );
 OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.TitledElement );
 
@@ -5126,10 +5121,9 @@ OO.ui.ButtonGroupWidget = function OoUiButtonGroupWidget( config ) {
        }
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.ButtonGroupWidget, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.ButtonGroupWidget, OO.ui.GroupElement );
 /**
  * Creates an OO.ui.ButtonWidget object.
@@ -5183,10 +5177,9 @@ OO.ui.ButtonWidget = function OoUiButtonWidget( config ) {
                .append( this.$button );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.ButtonWidget, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.ButtonWidget, OO.ui.ButtonedElement );
 OO.mixinClass( OO.ui.ButtonWidget, OO.ui.IconedElement );
 OO.mixinClass( OO.ui.ButtonWidget, OO.ui.IndicatedElement );
@@ -5274,7 +5267,7 @@ OO.ui.InputWidget = function OoUiInputWidget( config ) {
        this.setValue( config.value );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.InputWidget, OO.ui.Widget );
 
@@ -5447,7 +5440,7 @@ OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) {
        this.$element.addClass( 'oo-ui-checkboxInputWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.CheckboxInputWidget, OO.ui.InputWidget );
 
@@ -5528,10 +5521,9 @@ OO.ui.LabelWidget = function OoUiLabelWidget( config ) {
        this.$element.addClass( 'oo-ui-labelWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.LabelWidget, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.LabelWidget, OO.ui.LabeledElement );
 
 /* Static Properties */
@@ -5554,7 +5546,7 @@ OO.ui.LabelWidget.prototype.onClick = function () {
  * Lookup input widget.
  *
  * Mixin that adds a menu showing suggested values to a text input. Subclasses must handle `select`
- * events on #lookupMenu to make use of selections.
+ * and `choose` events on #lookupMenu to make use of selections.
  *
  * @class
  * @abstract
@@ -5699,7 +5691,7 @@ OO.ui.LookupInputWidget.prototype.populateLookupMenu = function () {
  */
 OO.ui.LookupInputWidget.prototype.initializeLookupMenuSelection = function () {
        if ( !this.lookupMenu.getSelectedItem() ) {
-               this.lookupMenu.intializeSelection( this.lookupMenu.getFirstSelectableItem() );
+               this.lookupMenu.selectItem( this.lookupMenu.getFirstSelectableItem() );
        }
        this.lookupMenu.highlightItem( this.lookupMenu.getSelectedItem() );
 };
@@ -5832,10 +5824,9 @@ OO.ui.OptionWidget = function OoUiOptionWidget( data, config ) {
                .append( this.$indicator );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.OptionWidget, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.OptionWidget, OO.ui.ItemWidget );
 OO.mixinClass( OO.ui.OptionWidget, OO.ui.IconedElement );
 OO.mixinClass( OO.ui.OptionWidget, OO.ui.LabeledElement );
@@ -6054,13 +6045,12 @@ OO.ui.SelectWidget = function OoUiSelectWidget( config ) {
        }
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.SelectWidget, OO.ui.Widget );
 
 // Need to mixin base class as well
 OO.mixinClass( OO.ui.SelectWidget, OO.ui.GroupElement );
-
 OO.mixinClass( OO.ui.SelectWidget, OO.ui.GroupWidget );
 
 /* Events */
@@ -6080,6 +6070,11 @@ OO.mixinClass( OO.ui.SelectWidget, OO.ui.GroupWidget );
  * @param {OO.ui.OptionWidget|null} item Selected item
  */
 
+/**
+ * @event choose
+ * @param {OO.ui.OptionWidget|null} item Chosen item
+ */
+
 /**
  * @event add
  * @param {OO.ui.OptionWidget[]} items Added items
@@ -6138,7 +6133,7 @@ OO.ui.SelectWidget.prototype.onMouseUp = function ( e ) {
        }
        if ( !this.disabled && e.which === 1 && this.selecting ) {
                this.pressItem( null );
-               this.selectItem( this.selecting );
+               this.chooseItem( this.selecting );
                this.selecting = null;
        }
 
@@ -6177,9 +6172,7 @@ OO.ui.SelectWidget.prototype.onMouseOver = function ( e ) {
 
        if ( !this.disabled ) {
                item = this.getTargetItem( e );
-               if ( item && item.isHighlightable() ) {
-                       this.highlightItem( item );
-               }
+               this.highlightItem( item && item.isHighlightable() ? item : null );
        }
        return false;
 };
@@ -6362,22 +6355,19 @@ OO.ui.SelectWidget.prototype.pressItem = function ( item ) {
 };
 
 /**
- * Setup selection and highlighting.
+ * Choose an item.
  *
- * This should be used to synchronize the UI with the model without emitting events that would in
- * turn update the model.
+ * Identical to #selectItem, but may vary in subclasses that want to take additional action when
+ * an item is selected using the keyboard or mouse.
  *
- * @param {OO.ui.OptionWidget} [item] Item to select
+ * @method
+ * @param {OO.ui.OptionWidget} item Item to choose
+ * @fires choose
  * @chainable
  */
-OO.ui.SelectWidget.prototype.intializeSelection = function ( item ) {
-       var i, len, selected;
-
-       for ( i = 0, len = this.items.length; i < len; i++ ) {
-               selected = this.items[i] === item;
-               this.items[i].setSelected( selected );
-               this.items[i].setHighlighted( selected );
-       }
+OO.ui.SelectWidget.prototype.chooseItem = function ( item ) {
+       this.selectItem( item );
+       this.emit( 'choose', item );
 
        return this;
 };
@@ -6545,7 +6535,7 @@ OO.ui.MenuItemWidget = function OoUiMenuItemWidget( data, config ) {
        this.$element.addClass( 'oo-ui-menuItemWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.MenuItemWidget, OO.ui.OptionWidget );
 /**
@@ -6575,16 +6565,16 @@ OO.ui.MenuWidget = function OoUiMenuWidget( config ) {
        this.$previousFocus = null;
        this.isolated = !config.input;
        this.visible = false;
+       this.flashing = false;
        this.onKeyDownHandler = OO.ui.bind( this.onKeyDown, this );
 
        // Initialization
        this.$element.hide().addClass( 'oo-ui-menuWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.MenuWidget, OO.ui.SelectWidget );
-
 OO.mixinClass( OO.ui.MenuWidget, OO.ui.ClippableElement );
 
 /* Methods */
@@ -6606,7 +6596,7 @@ OO.ui.MenuWidget.prototype.onKeyDown = function ( e ) {
                }
                switch ( e.keyCode ) {
                        case OO.ui.Keys.ENTER:
-                               this.selectItem( highlightItem );
+                               this.chooseItem( highlightItem );
                                handled = true;
                                break;
                        case OO.ui.Keys.UP:
@@ -6677,28 +6667,26 @@ OO.ui.MenuWidget.prototype.unbindKeyDownListener = function () {
 };
 
 /**
- * Select an item.
+ * Choose an item.
  *
- * The menu will stay open if an item is silently selected.
+ * This will close the menu when done, unlike selectItem which only changes selection.
  *
  * @method
- * @param {OO.ui.OptionWidget} [item] Item to select, omit to deselect all
+ * @param {OO.ui.OptionWidget} item Item to choose
  * @chainable
  */
-OO.ui.MenuWidget.prototype.selectItem = function ( item ) {
+OO.ui.MenuWidget.prototype.chooseItem = function ( item ) {
        // Parent method
-       OO.ui.SelectWidget.prototype.selectItem.call( this, item );
+       OO.ui.MenuWidget.super.prototype.chooseItem.call( this, item );
 
-       if ( !this.disabled ) {
-               if ( item ) {
-                       this.disabled = true;
-                       item.flash( OO.ui.bind( function () {
-                               this.hide();
-                               this.disabled = false;
-                       }, this ) );
-               } else {
+       if ( item && !this.flashing ) {
+               this.flashing = true;
+               item.flash( OO.ui.bind( function () {
                        this.hide();
-               }
+                       this.flashing = false;
+               }, this ) );
+       } else {
+               this.hide();
        }
 
        return this;
@@ -6834,10 +6822,9 @@ OO.ui.InlineMenuWidget = function OoUiInlineMenuWidget( config ) {
                .append( this.$handle, this.menu.$element );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.InlineMenuWidget, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.InlineMenuWidget, OO.ui.IconedElement );
 OO.mixinClass( OO.ui.InlineMenuWidget, OO.ui.IndicatedElement );
 OO.mixinClass( OO.ui.InlineMenuWidget, OO.ui.LabeledElement );
@@ -6910,10 +6897,12 @@ OO.ui.MenuSectionItemWidget = function OoUiMenuSectionItemWidget( data, config )
        this.$element.addClass( 'oo-ui-menuSectionItemWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.MenuSectionItemWidget, OO.ui.OptionWidget );
 
+/* Static Properties */
+
 OO.ui.MenuSectionItemWidget.static.selectable = false;
 
 OO.ui.MenuSectionItemWidget.static.highlightable = false;
@@ -6937,7 +6926,7 @@ OO.ui.OutlineWidget = function OoUiOutlineWidget( config ) {
        this.$element.addClass( 'oo-ui-outlineWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.OutlineWidget, OO.ui.SelectWidget );
 /**
@@ -7001,10 +6990,9 @@ OO.ui.OutlineControlsWidget = function OoUiOutlineControlsWidget( outline, confi
        this.$element.append( this.$icon, this.$group, this.$movers );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.OutlineControlsWidget, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.OutlineControlsWidget, OO.ui.GroupElement );
 OO.mixinClass( OO.ui.OutlineControlsWidget, OO.ui.IconedElement );
 
@@ -7083,7 +7071,7 @@ OO.ui.OutlineItemWidget = function OoUiOutlineItemWidget( data, config ) {
        this.setLevel( config.level );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.OutlineItemWidget, OO.ui.OptionWidget );
 
@@ -7205,10 +7193,9 @@ OO.ui.ButtonOptionWidget = function OoUiButtonOptionWidget( data, config ) {
        this.$element.append( this.$button );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.ButtonOptionWidget, OO.ui.OptionWidget );
-
 OO.mixinClass( OO.ui.ButtonOptionWidget, OO.ui.ButtonedElement );
 OO.mixinClass( OO.ui.ButtonOptionWidget, OO.ui.FlaggableElement );
 
@@ -7241,7 +7228,7 @@ OO.ui.ButtonSelectWidget = function OoUiButtonSelectWidget( config ) {
        this.$element.addClass( 'oo-ui-buttonSelectWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.ButtonSelectWidget, OO.ui.SelectWidget );
 /**
@@ -7307,12 +7294,10 @@ OO.ui.PopupWidget = function OoUiPopupWidget( config ) {
                .append( this.$popup, this.$tail );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.PopupWidget, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.PopupWidget, OO.ui.LabeledElement );
-
 OO.mixinClass( OO.ui.PopupWidget, OO.ui.ClippableElement );
 
 /* Events */
@@ -7527,10 +7512,9 @@ OO.ui.PopupButtonWidget = function OoUiPopupButtonWidget( config ) {
                .append( this.popup.$element );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.PopupButtonWidget, OO.ui.ButtonWidget );
-
 OO.mixinClass( OO.ui.PopupButtonWidget, OO.ui.PopuppableElement );
 
 /* Methods */
@@ -7609,7 +7593,7 @@ OO.ui.SearchWidget = function OoUiSearchWidget( config ) {
                .append( this.$results, this.$query );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.SearchWidget, OO.ui.Widget );
 
@@ -7770,7 +7754,7 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
        }
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.TextInputWidget, OO.ui.InputWidget );
 
@@ -7950,7 +7934,7 @@ OO.ui.TextInputMenuWidget = function OoUiTextInputMenuWidget( input, config ) {
        this.$element.addClass( 'oo-ui-textInputMenuWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.TextInputMenuWidget, OO.ui.MenuWidget );
 
@@ -8112,10 +8096,9 @@ OO.ui.ToggleButtonWidget = function OoUiToggleButtonWidget( config ) {
        this.$element.addClass( 'oo-ui-toggleButtonWidget' );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.ToggleButtonWidget, OO.ui.ButtonWidget );
-
 OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.ToggleWidget );
 
 /* Methods */
@@ -8181,10 +8164,9 @@ OO.ui.ToggleSwitchWidget = function OoUiToggleSwitchWidget( config ) {
                .append( this.$glow, this.$grip );
 };
 
-/* Inheritance */
+/* Setup */
 
 OO.inheritClass( OO.ui.ToggleSwitchWidget, OO.ui.Widget );
-
 OO.mixinClass( OO.ui.ToggleSwitchWidget, OO.ui.ToggleWidget );
 
 /* Methods */
index a7eecc1..873fd19 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.1.0-pre (eaa1b7f06d)
+ * OOjs UI v0.1.0-pre (4975b8db90)
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2014 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: Thu Apr 03 2014 16:56:21 GMT-0700 (PDT)
+ * Date: Mon Apr 07 2014 15:17:10 GMT-0700 (PDT)
  */
 
 /* Textures */
index edc7425..3c6dca6 100755 (executable)
@@ -6,7 +6,7 @@
 # ./bin/update-oojs-ui.sh path/to/repo/for/oojs-ui
 
 function oojsuihash() {
-       grep "OOjs UI v" resources/oojs-ui/oojs-ui.js \
+       grep "OOjs UI v" resources/lib/oojs-ui/oojs-ui.js \
                | head -n 1 \
                | grep -Eo '\([a-z0-9]+\)' \
                | sed 's/^(//' \
@@ -14,19 +14,19 @@ function oojsuihash() {
 }
 
 function oojsuitag() {
-       grep "OOjs UI v" resources/oojs-ui/oojs-ui.js \
+       grep "OOjs UI v" resources/lib/oojs-ui/oojs-ui.js \
                | head -n 1 \
                | grep -Eo '\bv[0-9a-z.-]+\b'
 }
 
 function oojsuiversion() {
-       grep "OOjs UI v" resources/oojs-ui/oojs-ui.js \
+       grep "OOjs UI v" resources/lib/oojs-ui/oojs-ui.js \
                | head -n 1 \
                | grep -Eo '\bv[0-9a-z.-]+\b.*$'
 }
 
-# cd to the VisualEditor directory
-cd $(cd $(dirname $0)/../..; pwd)
+# cd to the MW root directory
+cd $(cd $(dirname $0)/../../..; pwd)
 
 if [ "x$1" == "x" ]
 then
@@ -35,8 +35,8 @@ then
 fi
 
 # Undo any changes in the oojs-ui directory
-git reset resources/oojs-ui/
-git checkout resources/oojs-ui/
+git reset -- resources/lib/oojs-ui/
+git checkout -- resources/lib/oojs-ui/
 
 git fetch origin
 # Create a branch of MW if needed, and reset it to master
@@ -77,8 +77,8 @@ NEWCHANGESDISPLAY=$(git log $OLDVERSION.. --oneline --no-merges --reverse --colo
 # cd back to the VisualEditor directory
 cd -
 
-# Copy files from dist/ to resources/oojs-ui
-cp -a $1/dist/{oojs-ui.js,oojs-ui.svg.css,oojs-ui-apex.css,oojs-ui-agora.css,images,i18n} resources/oojs-ui/
+# Copy files from dist/ to resources/lib/oojs-ui
+cp -a $1/dist/{oojs-ui.js,oojs-ui.svg.css,oojs-ui-apex.css,oojs-ui-agora.css,images,i18n} resources/lib/oojs-ui/
 # Figure out what the new version is
 NEWVERSION=$(oojsuiversion)
 # Generate commit summary
@@ -90,7 +90,7 @@ $NEWCHANGES
 END
 )
 # Commit
-git commit resources/oojs-ui/ -m "$COMMITMSG"
+git commit resources/lib/oojs-ui/ -m "$COMMITMSG"
 cat >&2 <<END
 
 
index f953878..8ca3aed 100644 (file)
@@ -6,7 +6,7 @@
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: Wed Apr 02 2014 14:29:36 GMT-0700 (PDT)
+ * Date: Wed Apr 02 2014 14:20:50 GMT-0700 (PDT)
  */
 ( function ( global ) {
 
diff --git a/resources/lib/oojs/update-oojs.sh b/resources/lib/oojs/update-oojs.sh
deleted file mode 100755 (executable)
index 57c7625..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/env bash
-
-# FIXME this script is duplicated from update-oojs-ui.sh - factor this out
-
-# This script generates a commit that updates the oojs distribution
-# ./bin/update-oojs.sh path/to/repo/for/oojs
-
-function oojshash() {
-       grep "OOjs v" resources/oojs/oojs.js \
-               | head -n 1 \
-               | grep -Eo '\([a-z0-9]+\)' \
-               | sed 's/^(//' \
-               | sed 's/)$//'
-}
-
-function oojstag() {
-       grep "OOjs v" resources/oojs/oojs.js \
-               | head -n 1 \
-               | grep -Eo '\bv[0-9a-z.-]+\b'
-}
-
-function oojsversion() {
-       grep "OOjs v" resources/oojs/oojs.js \
-               | head -n 1 \
-               | grep -Eo '\bv[0-9a-z.-]+\b.*$'
-}
-
-# cd to the MW directory
-cd $(cd $(dirname $0)/../..; pwd)
-
-if [ "x$1" == "x" ]
-then
-       echo >&2 "Usage: update-oojs.sh path/to/repo/for/oojs"
-       exit 1
-fi
-
-# Undo any changes in the oojs directory
-git reset resources/oojs/
-git checkout resources/oojs/
-
-git fetch origin
-# Create a branch of MW if needed, and reset it to master
-git checkout -B update-oojs origin/master
-
-# Get the old oojs version
-OLDVERSION=$(oojshash)
-if [ "x$OLDVERSION" == "x" ]
-then
-       TAG=$(oojstag)
-fi
-
-# cd to the oojs directory
-cd $1 || exit 1
-if [ "x$OLDVERSION" == "x" ]
-then
-       # Try the tag
-       OLDVERSION=$(git rev-parse $TAG)
-       if [ $? != 0 ]
-       then
-               echo Could not find OOjs version
-               cd -
-               exit 1
-       fi
-fi
-if [ "$(git rev-parse $OLDVERSION)" == "$(git rev-parse HEAD)" ]
-then
-       echo "No changes (already at $OLDVERSION)"
-       cd -
-       exit 0
-fi
-# Build the distribution
-npm install || exit 1
-grunt || exit 1
-# Get the list of changes
-NEWCHANGES=$(git log $OLDVERSION.. --oneline --no-merges --reverse --color=never)
-NEWCHANGESDISPLAY=$(git log $OLDVERSION.. --oneline --no-merges --reverse --color=always)
-# cd back to the VisualEditor directory
-cd -
-
-# Copy files from dist/ to resources/oojs/
-cp -a $1/dist/* resources/oojs/
-# Figure out what the new version is
-NEWVERSION=$(oojsversion)
-# Generate commit summary
-COMMITMSG=$(cat <<END
-Update OOjs to $NEWVERSION
-
-New changes:
-$NEWCHANGES
-END
-)
-# Commit
-git commit resources/oojs/ -m "$COMMITMSG"
-cat >&2 <<END
-
-
-Created commit with changes:
-$NEWCHANGESDISPLAY
-END
index 17c1821..de05fdf 100644 (file)
@@ -8,7 +8,7 @@
         * and given a safe start position. It supports insertion anywhere
         * in the string, so "foo" to "fobaro" if limit is 4 will result in
         * "fobo", not "foba". Basically emulating the native maxlength by
-        * reconstructing where the insertion occured.
+        * reconstructing where the insertion occurred.
         *
         * @private
         * @param {string} safeVal Known value that was previously returned by this
index 29be239..0c89b73 100644 (file)
@@ -506,7 +506,7 @@ $.fn.suggestions = function () {
                                // Number of results visible without scrolling
                                visibleResults: 0,
 
-                               // Suggestion the last mousedown event occured on
+                               // Suggestion the last mousedown event occurred on
                                mouseDownOn: $( [] ),
                                $textbox: $(this),
                                selectedWithMouse: false
index ebfab42..c4578aa 100644 (file)
@@ -3,7 +3,8 @@
 // Page content
 // FIXME: Use global variable since Echo and CentralNotice use this variable
 @content-border-color: #a7d7f9;
-@content-font-family: Arimo, "Liberation Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+// FIXME: Find an open font that works with this stack and is readable by Windows users
+@content-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
 @content-font-color: #252525;
 @content-font-size: 0.875em;
 @content-line-height: 1.6;
index 3319490..70374dc 100644 (file)
@@ -108,8 +108,8 @@ class JobQueueTest extends MediaWikiTestCase {
                $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" );
                $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" );
 
-               $this->assertTrue( $queue->push( $this->newJob() ), "Push worked ($desc)" );
-               $this->assertTrue( $queue->batchPush( array( $this->newJob() ) ), "Push worked ($desc)" );
+               $this->assertNull( $queue->push( $this->newJob() ), "Push worked ($desc)" );
+               $this->assertNull( $queue->batchPush( array( $this->newJob() ) ), "Push worked ($desc)" );
 
                $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" );
 
@@ -157,7 +157,7 @@ class JobQueueTest extends MediaWikiTestCase {
                $queue->flushCaches();
                $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" );
 
-               $this->assertTrue( $queue->batchPush( array( $this->newJob(), $this->newJob() ) ),
+               $this->assertNull( $queue->batchPush( array( $this->newJob(), $this->newJob() ) ),
                        "Push worked ($desc)" );
                $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" );
 
@@ -183,7 +183,7 @@ class JobQueueTest extends MediaWikiTestCase {
                $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" );
                $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" );
 
-               $this->assertTrue(
+               $this->assertNull(
                        $queue->batchPush(
                                array( $this->newDedupedJob(), $this->newDedupedJob(), $this->newDedupedJob() )
                        ),
@@ -195,7 +195,7 @@ class JobQueueTest extends MediaWikiTestCase {
                $this->assertEquals( 1, $queue->getSize(), "Queue size is correct ($desc)" );
                $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" );
 
-               $this->assertTrue(
+               $this->assertNull(
                        $queue->batchPush(
                                array( $this->newDedupedJob(), $this->newDedupedJob(), $this->newDedupedJob() )
                        ),
@@ -244,7 +244,7 @@ class JobQueueTest extends MediaWikiTestCase {
                $id = wfRandomString( 32 );
                $root1 = Job::newRootJobParams( "nulljobspam:$id" ); // task ID/timestamp
                for ( $i = 0; $i < 5; ++$i ) {
-                       $this->assertTrue( $queue->push( $this->newJob( 0, $root1 ) ), "Push worked ($desc)" );
+                       $this->assertNull( $queue->push( $this->newJob( 0, $root1 ) ), "Push worked ($desc)" );
                }
                $queue->deduplicateRootJob( $this->newJob( 0, $root1 ) );
                sleep( 1 ); // roo job timestamp will increase
@@ -252,7 +252,7 @@ class JobQueueTest extends MediaWikiTestCase {
                $this->assertNotEquals( $root1['rootJobTimestamp'], $root2['rootJobTimestamp'],
                        "Root job signatures have different timestamps." );
                for ( $i = 0; $i < 5; ++$i ) {
-                       $this->assertTrue( $queue->push( $this->newJob( 0, $root2 ) ), "Push worked ($desc)" );
+                       $this->assertNull( $queue->push( $this->newJob( 0, $root2 ) ), "Push worked ($desc)" );
                }
                $queue->deduplicateRootJob( $this->newJob( 0, $root2 ) );
 
@@ -296,7 +296,7 @@ class JobQueueTest extends MediaWikiTestCase {
                $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" );
 
                for ( $i = 0; $i < 10; ++$i ) {
-                       $this->assertTrue( $queue->push( $this->newJob( $i ) ), "Push worked ($desc)" );
+                       $this->assertNull( $queue->push( $this->newJob( $i ) ), "Push worked ($desc)" );
                }
 
                for ( $i = 0; $i < 10; ++$i ) {
index 1eab5a3..50fa384 100644 (file)
@@ -2,6 +2,12 @@
 
 class UIDGeneratorTest extends MediaWikiTestCase {
 
+       protected function tearDown() {
+               // Bug: 44850
+               UIDGenerator::unitTestTearDown();
+               parent::tearDown();
+       }
+
        /**
         * @dataProvider provider_testTimestampedUID
         * @covers UIDGenerator::newTimestampedUID128
index 73ae0e6..7dff354 100644 (file)
         * </code>
         */
        QUnit.newMwEnvironment = ( function () {
-               var log, liveConfig, liveMessages;
+               var warn, log, liveConfig, liveMessages;
 
                liveConfig = mw.config.values;
                liveMessages = mw.messages.values;
 
+               function suppressWarnings() {
+                       warn = mw.log.warn;
+                       mw.log.warn = $.noop;
+               }
+
+               function restoreWarnings() {
+                       if ( warn !== undefined ) {
+                               mw.log.warn = warn;
+                               warn = undefined;
+                       }
+               }
+
                function freshConfigCopy( custom ) {
-                       var copy, warn;
+                       var copy;
                        // Tests should mock all factors that directly influence the tested code.
                        // For backwards compatibility though we set mw.config to a fresh copy of the live
                        // config. This way any modifications made to mw.config during the test will not
                        // affect other tests, nor the global scope outside the test runner.
                        // This is a shallow copy, since overriding an array or object value via "custom"
                        // should replace it. Setting a config property means you override it, not extend it.
-                       // NOTE: It is important that we temporarily disable mw.log#warn as extend() will
-                       // trigger MWDeprecationWarning for each of the deprecated properties.
-                       warn = mw.log.warn;
-                       mw.log.warn = $.noop;
-
+                       // NOTE: It is important that we suppress warnings because extend() will also access
+                       // deprecated properties and trigger deprecation warnings from mw.log#deprecate.
+                       suppressWarnings();
                        copy = $.extend( {}, liveConfig, custom );
-
-                       mw.log.warn = warn;
+                       restoreWarnings();
 
                        return copy;
                }
                                        // Greetings, mock environment!
                                        mw.config.values = freshConfigCopy( localEnv.config );
                                        mw.messages.values = freshMessagesCopy( localEnv.messages );
+                                       this.suppressWarnings = suppressWarnings;
+                                       this.restoreWarnings = restoreWarnings;
 
                                        localEnv.setup.call( this );
                                },
                                        // Farewell, mock environment!
                                        mw.config.values = liveConfig;
                                        mw.messages.values = liveMessages;
+
+                                       // As a convenience feature, automatically restore warnings if they're
+                                       // still suppressed by the end of the test.
+                                       restoreWarnings();
                                }
                        };
                };
index e5066e0..dab35f6 100644 (file)
@@ -51,6 +51,8 @@
 
                var api = new mw.Api();
 
+               this.suppressWarnings();
+
                api.get( {}, function () {
                        assert.ok( true, 'Function argument treated as success callback.' );
                } );
@@ -67,6 +69,8 @@
                        }
                } );
 
+               this.restoreWarnings();
+
                this.server.respondWith( /action=query/, function ( request ) {
                        request.respond( 200, { 'Content-Type': 'application/json' }, '[]' );
                } );
index 41b0cb7..e1fcb6a 100644 (file)
 
        QUnit.test( 'Initial check', 8, function ( assert ) {
                assert.ok( window.jQuery, 'jQuery defined' );
-               assert.ok( window.$, '$j defined' );
-               assert.ok( window.$j, '$j defined' );
+               assert.ok( window.$, '$ defined' );
                assert.strictEqual( window.$, window.jQuery, '$ alias to jQuery' );
+
+               this.suppressWarnings();
+               assert.ok( window.$j, '$j defined' );
                assert.strictEqual( window.$j, window.jQuery, '$j alias to jQuery' );
+               this.restoreWarnings();
 
                assert.ok( window.mediaWiki, 'mediaWiki defined' );
                assert.ok( window.mw, 'mw defined' );