Merge "Made User::touch no longer call load()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 3 Jun 2015 07:47:00 +0000 (07:47 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 3 Jun 2015 07:47:00 +0000 (07:47 +0000)
58 files changed:
RELEASE-NOTES-1.26
autoload.php
composer.json
docs/extension.schema.json
docs/hooks.txt
includes/Html.php
includes/User.php
includes/Xml.php
includes/XmlSelect.php [new file with mode: 0644]
includes/api/ApiStashEdit.php
includes/api/i18n/en.json
includes/cache/MessageBlobStore.php
includes/db/LoadBalancer.php
includes/exception/MWException.php
includes/exception/MWExceptionHandler.php
includes/installer/i18n/nl.json
includes/installer/i18n/yi.json
includes/libs/CSSMin.php
includes/mail/UserMailer.php
includes/media/XMP.php
includes/page/WikiPage.php
includes/parser/Parser.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderImageModule.php
includes/resourceloader/ResourceLoaderModule.php
includes/resourceloader/ResourceLoaderOOUIImageModule.php [new file with mode: 0644]
includes/specials/SpecialContributions.php
includes/specials/SpecialExport.php
includes/specials/SpecialImport.php
includes/specials/SpecialLinkSearch.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialRecentchangeslinked.php
includes/specials/SpecialWatchlist.php
includes/specials/SpecialWhatlinkshere.php
jsduck.json
languages/Language.php
languages/i18n/arq.json
languages/i18n/be-tarask.json
languages/i18n/br.json
languages/i18n/ce.json
languages/i18n/dty.json
languages/i18n/es.json
languages/i18n/fa.json
languages/i18n/ku-latn.json
languages/i18n/ru.json
languages/i18n/scn.json
languages/i18n/uk.json
languages/i18n/zh-hant.json
maintenance/deleteArchivedFiles.inc [deleted file]
maintenance/deleteArchivedFiles.php
maintenance/deleteArchivedRevisions.inc [deleted file]
maintenance/deleteArchivedRevisions.php
maintenance/jsduck/categories.json
resources/Resources.php
resources/ResourcesOOUI.php [new file with mode: 0644]
resources/src/mediawiki.ui/components/checkbox.less
resources/src/mediawiki/mediawiki.template.mustache.js
tests/phpunit/languages/LanguageTest.php

index af9e9d2..d1458bd 100644 (file)
@@ -15,6 +15,7 @@ production.
   "tag-<id>" interface message.
 * ':' (colon) is now invalid in usernames for new accounts. Existing accounts
   are not affected.
+* Added a new hook, 'LogException', to log exceptions in nonstandard ways.
 
 ==== External libraries ====
 
index 612bc96..3a79cb7 100644 (file)
@@ -311,9 +311,7 @@ $wgAutoloadLocalClasses = array(
        'DeferredUpdates' => __DIR__ . '/includes/deferred/DeferredUpdates.php',
        'DeleteAction' => __DIR__ . '/includes/actions/DeleteAction.php',
        'DeleteArchivedFiles' => __DIR__ . '/maintenance/deleteArchivedFiles.php',
-       'DeleteArchivedFilesImplementation' => __DIR__ . '/maintenance/deleteArchivedFiles.inc',
        'DeleteArchivedRevisions' => __DIR__ . '/maintenance/deleteArchivedRevisions.php',
-       'DeleteArchivedRevisionsImplementation' => __DIR__ . '/maintenance/deleteArchivedRevisions.inc',
        'DeleteBatch' => __DIR__ . '/maintenance/deleteBatch.php',
        'DeleteDefaultMessages' => __DIR__ . '/maintenance/deleteDefaultMessages.php',
        'DeleteEqualMessages' => __DIR__ . '/maintenance/deleteEqualMessages.php',
@@ -996,6 +994,7 @@ $wgAutoloadLocalClasses = array(
        'ResourceLoaderImage' => __DIR__ . '/includes/resourceloader/ResourceLoaderImage.php',
        'ResourceLoaderImageModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderImageModule.php',
        'ResourceLoaderJqueryMsgDataModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderJqueryMsgDataModule.php',
+       'ResourceLoaderOOUIImageModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderOOUIImageModule.php',
        'ResourceLoaderLanguageDataModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLanguageDataModule.php',
        'ResourceLoaderLanguageNamesModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLanguageNamesModule.php',
        'ResourceLoaderModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderModule.php',
@@ -1366,7 +1365,7 @@ $wgAutoloadLocalClasses = array(
        'Xml' => __DIR__ . '/includes/Xml.php',
        'XmlDumpWriter' => __DIR__ . '/includes/Export.php',
        'XmlJsCode' => __DIR__ . '/includes/Xml.php',
-       'XmlSelect' => __DIR__ . '/includes/Xml.php',
+       'XmlSelect' => __DIR__ . '/includes/XmlSelect.php',
        'XmlTypeCheck' => __DIR__ . '/includes/libs/XmlTypeCheck.php',
        'ZhConverter' => __DIR__ . '/languages/classes/LanguageZh.php',
        'ZipDirectoryReader' => __DIR__ . '/includes/utils/ZipDirectoryReader.php',
index 8210236..9222aa4 100644 (file)
@@ -32,7 +32,7 @@
        "require-dev": {
                "jakub-onderka/php-parallel-lint": "~0.8",
                "justinrainbow/json-schema": "~1.3",
-               "phpunit/phpunit": "~4.5",
+               "phpunit/phpunit": "3.7.37",
                "mediawiki/mediawiki-codesniffer": "0.1.0"
        },
        "suggest": {
index 05f0c45..d7d9e47 100644 (file)
@@ -30,8 +30,7 @@
                        "description": "Extension's authors.",
                        "items": {
                                "type": "string"
-                       },
-                       "additionalItems": false
+                       }
                },
                "version": {
                        "type": "string",
                "ValidSkinNames": {
                        "type": "object"
                },
+               "SkinOOUIThemes": {
+                       "type": "object"
+               },
                "callback": {
                        "type": [
                                "array",
index 131986a..8e9223b 100644 (file)
@@ -1842,6 +1842,11 @@ $param: Associative Array with the following additional options:
     "&lt;div ...>$1&lt;/div>").
   - flags Integer display flags (NO_ACTION_LINK,NO_EXTRA_USER_LINKS)
 
+'LogException': Called before an exception (or PHP error) is logged. This is meant for integration
+with external error aggregation services; returning false will NOT prevent logging.
+$e: The exception (in case of a plain old PHP error, a wrapping ErrorException)
+$suppressed: true if the error was suppressed via error_reporting()/wfSuppressWarnings()
+
 'LoginAuthenticateAudit': A login attempt for a valid user account either
 succeeded or failed. No return data is accepted; this hook is for auditing only.
 $user: the User object being authenticated against
index 6907245..6da70b1 100644 (file)
@@ -104,8 +104,8 @@ class Html {
        /**
         * Modifies a set of attributes meant for button elements
         * and apply a set of default attributes when $wgUseMediaWikiUIEverywhere enabled.
-        * @param array $attrs
-        * @param string[] $modifiers to add to the button
+        * @param array $attrs HTML attributes in an associative array
+        * @param string[] $modifiers classes to add to the button
         * @see https://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers
         * @return array $attrs A modified attribute array
         */
@@ -115,16 +115,15 @@ class Html {
                        if ( isset( $attrs['class'] ) ) {
                                if ( is_array( $attrs['class'] ) ) {
                                        $attrs['class'][] = 'mw-ui-button';
-                                       $attrs = array_merge( $attrs, $modifiers );
+                                       $attrs['class'] = array_merge( $attrs['class'], $modifiers );
                                        // ensure compatibility with Xml
                                        $attrs['class'] = implode( ' ', $attrs['class'] );
                                } else {
                                        $attrs['class'] .= ' mw-ui-button ' . implode( ' ', $modifiers );
                                }
                        } else {
-                               $attrs['class'] = array( 'mw-ui-button' );
                                // ensure compatibility with Xml
-                               $attrs['class'] = implode( ' ', array_merge( $attrs['class'], $modifiers ) );
+                               $attrs['class'] = 'mw-ui-button ' . implode( ' ', $modifiers );
                        }
                }
                return $attrs;
@@ -162,7 +161,7 @@ class Html {
         * @param array $attrs Associative array of attributes, e.g., array(
         *   'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
         *   further documentation.
-        * @param string[] $modifiers to add to the button
+        * @param string[] $modifiers classes to add to the button
         * @see http://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers
         * @return string Raw HTML
         */
@@ -182,7 +181,7 @@ class Html {
         * @param array $attrs Associative array of attributes, e.g., array(
         *   'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
         *   further documentation.
-        * @param string[] $modifiers to add to the button
+        * @param string[] $modifiers classes to add to the button
         * @see http://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers
         * @return string Raw HTML
         */
index 6d3911b..bf0326a 100644 (file)
@@ -1229,7 +1229,7 @@ class User implements IDBAccessObject {
                        self::selectFields(),
                        array( 'user_id' => $this->mId ),
                        __METHOD__,
-                       ( $flags & self::READ_LOCKING == self::READ_LOCKING )
+                       ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
                                ? array( 'LOCK IN SHARE MODE' )
                                : array()
                );
@@ -2380,7 +2380,7 @@ class User implements IDBAccessObject {
         * @return string TS_MW Timestamp
         * @since 1.26
         */
-       protected function getDBTouched() {
+       public function getDBTouched() {
                $this->load();
 
                return $this->mTouched;
index f0bd70b..c356c6d 100644 (file)
@@ -871,112 +871,6 @@ class Xml {
        }
 }
 
-class XmlSelect {
-       protected $options = array();
-       protected $default = false;
-       protected $attributes = array();
-
-       public function __construct( $name = false, $id = false, $default = false ) {
-               if ( $name ) {
-                       $this->setAttribute( 'name', $name );
-               }
-
-               if ( $id ) {
-                       $this->setAttribute( 'id', $id );
-               }
-
-               if ( $default !== false ) {
-                       $this->default = $default;
-               }
-       }
-
-       /**
-        * @param string $default
-        */
-       public function setDefault( $default ) {
-               $this->default = $default;
-       }
-
-       /**
-        * @param string $name
-        * @param array $value
-        */
-       public function setAttribute( $name, $value ) {
-               $this->attributes[$name] = $value;
-       }
-
-       /**
-        * @param string $name
-        * @return array|null
-        */
-       public function getAttribute( $name ) {
-               if ( isset( $this->attributes[$name] ) ) {
-                       return $this->attributes[$name];
-               } else {
-                       return null;
-               }
-       }
-
-       /**
-        * @param string $name
-        * @param bool $value
-        */
-       public function addOption( $name, $value = false ) {
-               // Stab stab stab
-               $value = $value !== false ? $value : $name;
-
-               $this->options[] = array( $name => $value );
-       }
-
-       /**
-        * This accepts an array of form
-        * label => value
-        * label => ( label => value, label => value )
-        *
-        * @param array $options
-        */
-       public function addOptions( $options ) {
-               $this->options[] = $options;
-       }
-
-       /**
-        * This accepts an array of form
-        * label => value
-        * label => ( label => value, label => value )
-        *
-        * @param array $options
-        * @param bool $default
-        * @return string
-        */
-       static function formatOptions( $options, $default = false ) {
-               $data = '';
-
-               foreach ( $options as $label => $value ) {
-                       if ( is_array( $value ) ) {
-                               $contents = self::formatOptions( $value, $default );
-                               $data .= Html::rawElement( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
-                       } else {
-                               $data .= Xml::option( $label, $value, $value === $default ) . "\n";
-                       }
-               }
-
-               return $data;
-       }
-
-       /**
-        * @return string
-        */
-       public function getHTML() {
-               $contents = '';
-
-               foreach ( $this->options as $options ) {
-                       $contents .= self::formatOptions( $options, $this->default );
-               }
-
-               return Html::rawElement( 'select', $this->attributes, rtrim( $contents ) );
-       }
-}
-
 /**
  * A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to
  * interpret a given string as being a JavaScript expression, instead of string
diff --git a/includes/XmlSelect.php b/includes/XmlSelect.php
new file mode 100644 (file)
index 0000000..1cd04ae
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Class to generate XML <select>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Module of static functions for generating XML <select> elements
+ */
+class XmlSelect {
+       protected $options = array();
+       protected $default = false;
+       protected $attributes = array();
+
+       public function __construct( $name = false, $id = false, $default = false ) {
+               if ( $name ) {
+                       $this->setAttribute( 'name', $name );
+               }
+
+               if ( $id ) {
+                       $this->setAttribute( 'id', $id );
+               }
+
+               if ( $default !== false ) {
+                       $this->default = $default;
+               }
+       }
+
+       /**
+        * @param string $default
+        */
+       public function setDefault( $default ) {
+               $this->default = $default;
+       }
+
+       /**
+        * @param string $name
+        * @param array $value
+        */
+       public function setAttribute( $name, $value ) {
+               $this->attributes[$name] = $value;
+       }
+
+       /**
+        * @param string $name
+        * @return array|null
+        */
+       public function getAttribute( $name ) {
+               if ( isset( $this->attributes[$name] ) ) {
+                       return $this->attributes[$name];
+               } else {
+                       return null;
+               }
+       }
+
+       /**
+        * @param string $name
+        * @param bool $value
+        */
+       public function addOption( $name, $value = false ) {
+               // Stab stab stab
+               $value = $value !== false ? $value : $name;
+
+               $this->options[] = array( $name => $value );
+       }
+
+       /**
+        * This accepts an array of form
+        * label => value
+        * label => ( label => value, label => value )
+        *
+        * @param array $options
+        */
+       public function addOptions( $options ) {
+               $this->options[] = $options;
+       }
+
+       /**
+        * This accepts an array of form
+        * label => value
+        * label => ( label => value, label => value )
+        *
+        * @param array $options
+        * @param bool $default
+        * @return string
+        */
+       static function formatOptions( $options, $default = false ) {
+               $data = '';
+
+               foreach ( $options as $label => $value ) {
+                       if ( is_array( $value ) ) {
+                               $contents = self::formatOptions( $value, $default );
+                               $data .= Html::rawElement( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
+                       } else {
+                               $data .= Xml::option( $label, $value, $value === $default ) . "\n";
+                       }
+               }
+
+               return $data;
+       }
+
+       /**
+        * @return string
+        */
+       public function getHTML() {
+               $contents = '';
+
+               foreach ( $this->options as $options ) {
+                       $contents .= self::formatOptions( $options, $this->default );
+               }
+
+               return Html::rawElement( 'select', $this->attributes, rtrim( $contents ) );
+       }
+}
index 1b27f60..1f469b8 100644 (file)
@@ -331,7 +331,7 @@ class ApiStashEdit extends ApiBase {
                        $content->getDefaultFormat(),
                        sha1( $content->serialize( $content->getDefaultFormat() ) ),
                        $user->getId() ?: md5( $user->getName() ), // account for user parser options
-                       $user->getId() ? $user->getTouched() : '-' // handle preference change races
+                       $user->getId() ? $user->getDBTouched() : '-' // handle preference change races
                ) ) );
 
                return wfMemcKey( 'prepared-edit', md5( $title->getPrefixedDBkey() ), $hash );
index d905dea..f3faf48 100644 (file)
@@ -10,8 +10,8 @@
        "apihelp-main-param-action": "Which action to perform.",
        "apihelp-main-param-format": "The format of the output.",
        "apihelp-main-param-maxlag": "Maximum lag can be used when MediaWiki is installed on a database replicated cluster. To save actions causing any more site replication lag, this parameter can make the client wait until the replication lag is less than the specified value. In case of excessive lag, error code <samp>maxlag</samp> is returned with a message like <samp>Waiting for $host: $lag seconds lagged</samp>.<br />See [[mw:Manual:Maxlag_parameter|Manual: Maxlag parameter]] for more information.",
-       "apihelp-main-param-smaxage": "Set the <code>s-maxage</code> header to this many seconds. Errors are never cached.",
-       "apihelp-main-param-maxage": "Set the <code>max-age</code> header to this many seconds. Errors are never cached.",
+       "apihelp-main-param-smaxage": "Set the <code>s-maxage</code> HTTP cache control header to this many seconds. Errors are never cached.",
+       "apihelp-main-param-maxage": "Set the <code>max-age</code> HTTP cache control header to this many seconds. Errors are never cached.",
        "apihelp-main-param-assert": "Verify the user is logged in if set to <kbd>user</kbd>, or has the bot userright if <kbd>bot</kbd>.",
        "apihelp-main-param-requestid": "Any value given here will be included in the response. May be used to distinguish requests.",
        "apihelp-main-param-servedby": "Include the hostname that served the request in the results.",
index 011cae6..19349b2 100644 (file)
  * constituent messages or the resource itself is changed.
  */
 class MessageBlobStore {
+       /**
+        * In-process cache for message blobs.
+        *
+        * Keyed by language code, then module name.
+        *
+        * @var array
+        */
+       protected $blobCache = array();
+
        /**
         * Get the singleton instance
         *
@@ -56,18 +65,40 @@ class MessageBlobStore {
                if ( !count( $modules ) ) {
                        return array();
                }
-               // Try getting from the DB first
-               $blobs = $this->getFromDB( $resourceLoader, array_keys( $modules ), $lang );
 
-               // Generate blobs for any missing modules and store them in the DB
-               $missing = array_diff( array_keys( $modules ), array_keys( $blobs ) );
-               foreach ( $missing as $name ) {
+               $blobs = array();
+
+               // Try in-process cache
+               $missingFromCache = array();
+               foreach ( $modules as $name => $module ) {
+                       if ( isset( $this->blobCache[$lang][$name] ) ) {
+                               $blobs[$name] = $this->blobCache[$lang][$name];
+                       } else {
+                               $missingFromCache[] = $name;
+                       }
+               }
+
+               // Try DB cache
+               if ( $missingFromCache ) {
+                       $blobs += $this->getFromDB( $resourceLoader, $missingFromCache, $lang );
+               }
+
+               // Generate new blobs for any remaining modules and store in DB
+               $missingFromDb = array_diff( array_keys( $modules ), array_keys( $blobs ) );
+               foreach ( $missingFromDb as $name ) {
                        $blob = $this->insertMessageBlob( $name, $modules[$name], $lang );
                        if ( $blob ) {
                                $blobs[$name] = $blob;
                        }
                }
 
+               // Update in-process cache
+               if ( isset( $this->blobCache[$lang] ) ) {
+                       $this->blobCache[$lang] += $blobs;
+               } else {
+                       $this->blobCache[$lang] = $blobs;
+               }
+
                return $blobs;
        }
 
@@ -339,6 +370,10 @@ class MessageBlobStore {
         * @return array Array mapping module names to blobs
         */
        private function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
+               if ( !count( $modules ) ) {
+                       return array();
+               }
+
                $config = $resourceLoader->getConfig();
                $retval = array();
                $dbr = wfGetDB( DB_SLAVE );
index 2ea2487..99c9a14 100644 (file)
@@ -291,8 +291,8 @@ class LoadBalancer {
                                return false;
                        }
 
-                       wfDebugLog( 'connect', __METHOD__ .
-                               ": Using reader #$i: {$this->mServers[$i]['host']}..." );
+                       $serverName = $this->getServerName( $i );
+                       wfDebugLog( 'connect', __METHOD__ . ": Using reader #$i: $serverName..." );
 
                        $conn = $this->openConnection( $i, $wiki );
                        if ( !$conn ) {
@@ -462,7 +462,7 @@ class LoadBalancer {
 
                if ( $result == -1 || is_null( $result ) ) {
                        # Timed out waiting for slave, use master instead
-                       $server = $this->mServers[$index]['host'];
+                       $server = $server = $this->getServerName( $index );
                        $msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}";
                        wfDebug( "$msg\n" );
                        wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
@@ -660,11 +660,12 @@ class LoadBalancer {
                        $server = $this->mServers[$i];
                        $server['serverIndex'] = $i;
                        $conn = $this->reallyOpenConnection( $server, false );
+                       $serverName = $this->getServerName( $i );
                        if ( $conn->isOpen() ) {
-                               wfDebug( "Connected to database $i at {$this->mServers[$i]['host']}\n" );
+                               wfDebug( "Connected to database $i at $serverName\n" );
                                $this->mConns['local'][$i][0] = $conn;
                        } else {
-                               wfDebug( "Failed to connect to database $i at {$this->mServers[$i]['host']}\n" );
+                               wfDebug( "Failed to connect to database $i at $serverName\n" );
                                $this->mErrorConnection = $conn;
                                $conn = false;
                        }
@@ -888,12 +889,14 @@ class LoadBalancer {
         */
        public function getServerName( $i ) {
                if ( isset( $this->mServers[$i]['hostName'] ) ) {
-                       return $this->mServers[$i]['hostName'];
+                       $name = $this->mServers[$i]['hostName'];
                } elseif ( isset( $this->mServers[$i]['host'] ) ) {
-                       return $this->mServers[$i]['host'];
+                       $name = $this->mServers[$i]['host'];
                } else {
-                       return '';
+                       $name = '';
                }
+
+               return ( $name != '' ) ? $name : 'localhost';
        }
 
        /**
index 478fead..e54e568 100644 (file)
@@ -238,8 +238,8 @@ class MWException extends Exception {
                } elseif ( self::isCommandLine() ) {
                        MWExceptionHandler::printError( $this->getText() );
                } else {
-                       self::header( 'HTTP/1.1 500 MediaWiki exception' );
-                       self::header( 'Status: 500 MediaWiki exception' );
+                       self::header( 'HTTP/1.1 500 Internal Server Error' );
+                       self::header( 'Status: 500 Internal Server Error' );
                        self::header( "Content-Type: $wgMimeType; charset=utf-8" );
 
                        $this->reportHTML();
index c50b6c8..a58705f 100644 (file)
@@ -486,6 +486,8 @@ TXT;
                        if ( $json !== false ) {
                                wfDebugLog( 'exception-json', $json, 'private' );
                        }
+
+                       Hooks::run( 'LogException', array( $e, false ) );
                }
        }
 
@@ -501,7 +503,8 @@ TXT;
 
                // The set_error_handler callback is independent from error_reporting.
                // Filter out unwanted errors manually (e.g. when wfSuppressWarnings is active).
-               if ( ( error_reporting() & $e->getSeverity() ) !== 0 ) {
+               $suppressed = ( error_reporting() & $e->getSeverity() ) === 0;
+               if ( !$suppressed ) {
                        $log = self::getLogMessage( $e );
                        if ( $wgLogExceptionBacktrace ) {
                                wfDebugLog( $channel, $log . "\n" . $e->getTraceAsString() );
@@ -515,5 +518,7 @@ TXT;
                if ( $json !== false ) {
                        wfDebugLog( "$channel-json", $json, 'private' );
                }
+
+               Hooks::run( 'LogException', array( $e, $suppressed ) );
        }
 }
index a2ab5d2..1fd98ff 100644 (file)
@@ -13,7 +13,8 @@
                        "Southparkfan",
                        "Seb35",
                        "Mar(c)",
-                       "Sjoerddebruin"
+                       "Sjoerddebruin",
+                       "Esketti"
                ]
        },
        "config-desc": "Het installatieprogramma voor MediaWiki",
@@ -71,7 +72,7 @@
        "config-magic-quotes-sybase": "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] is actief!'''\nDeze instelling zorgt voor onvoorspelbare gegevenscorruptie.\nU kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
        "config-mbstring": "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is actief!'''\nDeze instelling zorgt voor onvoorspelbare gegevenscorruptie.\nU kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
        "config-safe-mode": "'''Waarschuwing:'''\n'''PHP's [http://www.php.net/features.safe-mode veilige modus] is actief.'''\nDit kan problemen veroorzaken, vooral bij het uploaden van bestanden en ondersteuning van <code>math</code>.",
-       "config-xml-bad": "De XML-module van PHP ontbreekt.\nMediaWiki heeft de functies van deze module nodig en werkt niet zonder deze module.\nAls u gebruik maakt van Mandrake, installeer dan het package php-xml.",
+       "config-xml-bad": "PHP-XML-module ontbreekt.\nMediaWiki is vereist functies in deze module en zal niet werken in deze configuratie.\nMoet u de php-xml-RPM pakket.",
        "config-pcre-old": "'''Onherstelbare fout:''' PCRE $1 of een latere versie is vereist.\nUw uitvoerbare versie van PHP is gekoppeld met PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Meer informatie].",
        "config-pcre-no-utf8": "'''Fataal:''' de module PRCE van PHP lijkt te zijn gecompileerd zonder ondersteuning voor PCRE_UTF8.\nMediaWiki heeft ondersteuning voor UTF-8 nodig om correct te kunnen werken.",
        "config-memory-raised": "PHP's <code>memory_limit</code> is $1 en is verhoogd tot $2.",
index e1d2226..300540f 100644 (file)
        "config-page-existingwiki": "עקזיסטירנדע וויקי",
        "config-help-restart": "צי ווילט איר אפראמען די גארע געשפייכלערטע דאטן וואס איר האט אײַנגעגעבן און ווידער אנהייבן דעם אינסטאלאציע־פראצעס?",
        "config-restart": "יא, ווידעראמאל אנהייבן",
+       "config-env-good": "מ'האט קאנטראלירט די סביבה.\nאיר קענט אינסטאלירן מעדיעוויקי.",
+       "config-env-bad": "מ'האט קאנטראלירט די סביבה.\nאיר קענט נישט אינסטאלירן מעדיעוויקי.",
        "config-env-php": "PHP $1 איז אינצטאלירט.",
+       "config-env-hhvm": "HHVM $1 איז אינסטאלירט.",
+       "config-xcache": "[http://xcache.lighttpd.net/ XCache] איז אינסטאלירט",
        "config-apc": "[http://www.php.net/apc APC] איז אינסטאלירט",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] איז אינסטאלירט",
        "config-diff3-bad": "GNU diff3 נישט געטראפן.",
        "config-db-type": "דאטנבאזע טיפ:",
        "config-db-host-oracle": "דאטנבאזע־TNS:",
+       "config-db-wiki-settings": "אידענטיפיצירן די דאזיקע וויקי",
        "config-db-name": "דאטנבאזע נאָמען:",
        "config-db-username": "דאטנבאזע באניצער־נאָמען:",
+       "config-db-password": "דאטנבאזע־פאסווארט:",
        "config-project-namespace": "פראיעקט נאָמענטייל:",
        "config-ns-generic": "פראיעקט",
        "config-admin-name": "אײַער באַניצער־נאָמען:",
index ffe26a9..f415c9b 100644 (file)
@@ -31,6 +31,9 @@ class CSSMin {
 
        /* Constants */
 
+       /** @var string Strip marker for comments. **/
+       const PLACEHOLDER = "\x7fPLACEHOLDER\x7f";
+
        /**
         * Internet Explorer data URI length limit. See encodeImageAsDataURI().
         */
@@ -232,19 +235,22 @@ class CSSMin {
                        $remote = substr( $remote, 0, -1 );
                }
 
+               // Disallow U+007F DELETE, which is illegal anyway, and which
+               // we use for comment placeholders.
+               $source = str_replace( "\x7f", "?", $source );
+
                // Replace all comments by a placeholder so they will not interfere with the remapping.
                // Warning: This will also catch on anything looking like the start of a comment between
                // quotation marks (e.g. "foo /* bar").
                $comments = array();
-               $placeholder = uniqid( '', true );
 
                $pattern = '/(?!' . CSSMin::EMBED_REGEX . ')(' . CSSMin::COMMENT_REGEX . ')/s';
 
                $source = preg_replace_callback(
                        $pattern,
-                       function ( $match ) use ( &$comments, $placeholder ) {
+                       function ( $match ) use ( &$comments ) {
                                $comments[] = $match[ 0 ];
-                               return $placeholder . ( count( $comments ) - 1 ) . 'x';
+                               return CSSMin::PLACEHOLDER . ( count( $comments ) - 1 ) . 'x';
                        },
                        $source
                );
@@ -257,13 +263,13 @@ class CSSMin {
 
                $source = preg_replace_callback(
                        $pattern,
-                       function ( $matchOuter ) use ( $local, $remote, $embedData, $placeholder ) {
+                       function ( $matchOuter ) use ( $local, $remote, $embedData ) {
                                $rule = $matchOuter[0];
 
                                // Check for global @embed comment and remove it. Allow other comments to be present
                                // before @embed (they have been replaced with placeholders at this point).
                                $embedAll = false;
-                               $rule = preg_replace( '/^((?:\s+|' . $placeholder . '(\d+)x)*)' . CSSMin::EMBED_REGEX . '\s*/', '$1', $rule, 1, $embedAll );
+                               $rule = preg_replace( '/^((?:\s+|' . CSSMin::PLACEHOLDER . '(\d+)x)*)' . CSSMin::EMBED_REGEX . '\s*/', '$1', $rule, 1, $embedAll );
 
                                // Build two versions of current rule: with remapped URLs
                                // and with embedded data: URIs (where possible).
@@ -328,7 +334,7 @@ class CSSMin {
                        }, $source );
 
                // Re-insert comments
-               $pattern = '/' . $placeholder . '(\d+)x/';
+               $pattern = '/' . CSSMin::PLACEHOLDER . '(\d+)x/';
                $source = preg_replace_callback( $pattern, function( $match ) use ( &$comments ) {
                        return $comments[ $match[1] ];
                }, $source );
index 3cabdae..8264673 100644 (file)
@@ -206,6 +206,8 @@ class UserMailer {
                $headers['Date'] = MWTimestamp::getLocalInstance()->format( 'r' );
                $headers['Message-ID'] = self::makeMsgId();
                $headers['X-Mailer'] = 'MediaWiki mailer';
+               $headers['List-Unsubscribe'] = '<' . SpecialPage::getTitleFor( 'Preferences' )
+                       ->getFullURL( '', false, PROTO_CANONICAL ) . '>';
 
                # Line endings need to be different on Unix and Windows due to
                # the bug described at http://trac.wordpress.org/ticket/2603
index 042f749..6b36e37 100644 (file)
@@ -155,16 +155,27 @@ class XMPReader implements LoggerAwareInterface {
                $this->logger = $logger;
        }
 
+       /**
+        * free the XML parser.
+        *
+        * @note It is unclear to me if we really need to do this ourselves
+        *  or if php garbage collection will automatically free the xmlParser
+        *  when it is no longer needed.
+        */
+       private function destroyXMLParser() {
+               if ( $this->xmlParser ) {
+                       xml_parser_free( $this->xmlParser );
+                       $this->xmlParser = null;
+               }
+       }
+
        /**
         * Main use is if a single item has multiple xmp documents describing it.
         * For example in jpeg's with extendedXMP
         */
        private function resetXMLParser() {
 
-               if ( $this->xmlParser ) {
-                       //is this needed?
-                       xml_parser_free( $this->xmlParser );
-               }
+               $this->destroyXMLParser();
 
                $this->xmlParser = xml_parser_create_ns( 'UTF-8', ' ' );
                xml_parser_set_option( $this->xmlParser, XML_OPTION_CASE_FOLDING, 0 );
@@ -180,15 +191,6 @@ class XMPReader implements LoggerAwareInterface {
                $this->xmlParsableBuffer = '';
        }
 
-       /** Destroy the xml parser
-        *
-        * Not sure if this is actually needed.
-        */
-       function __destruct() {
-               // not sure if this is needed.
-               xml_parser_free( $this->xmlParser );
-       }
-
        /**
         * Check if this instance supports using this class
         */
@@ -294,12 +296,11 @@ class XMPReader implements LoggerAwareInterface {
         *
         * @param string $content XMP data
         * @param bool $allOfIt If this is all the data (true) or if its split up (false). Default true
-        * @param bool $reset Does xml parser need to be reset. Default false
         * @throws RuntimeException
         * @return bool Success.
         */
-       public function parse( $content, $allOfIt = true, $reset = false ) {
-               if ( $reset ) {
+       public function parse( $content, $allOfIt = true ) {
+               if ( !$this->xmlParser ) {
                        $this->resetXMLParser();
                }
                try {
@@ -373,14 +374,21 @@ class XMPReader implements LoggerAwareInterface {
 
                                $this->logger->info( "XMPReader::parse : Error reading XMP content: $error ($where)" );
                                $this->results = array(); // blank if error.
+                               $this->destroyXMLParser();
                                return false;
                        }
                } catch ( Exception $e ) {
                        $this->logger->info( 'XMP parse error: ' . $e );
                        $this->results = array();
 
+                       if ( $allOfIt ) {
+                               $this->destroyXMLParser();
+                       }
                        return false;
                }
+               if ( $allOfIt ) {
+                       $this->destroyXMLParser();
+               }
 
                return true;
        }
index 8e2e8a5..1fd22cf 100644 (file)
@@ -2769,9 +2769,16 @@ class WikiPage implements Page, IDBAccessObject {
                $dbw->begin( __METHOD__ );
 
                if ( $id == 0 ) {
-                       $this->loadPageData( 'forupdate' );
+                       // T98706: lock the page from various other updates but avoid using
+                       // WikiPage::READ_LOCKING as that will carry over the FOR UPDATE to
+                       // the revisions queries (which also JOIN on user). Only lock the page
+                       // row and CAS check on page_latest to see if the trx snapshot matches.
+                       $latest = $this->lock();
+
+                       $this->loadPageData( WikiPage::READ_LATEST );
                        $id = $this->getID();
-                       if ( $id == 0 ) {
+                       if ( $id == 0 || $this->getLatest() != $latest ) {
+                               // Page not there or trx snapshot is stale
                                $dbw->rollback( __METHOD__ );
                                $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
                                return $status;
@@ -2878,6 +2885,24 @@ class WikiPage implements Page, IDBAccessObject {
                return $status;
        }
 
+       /**
+        * Lock the page row for this title and return page_latest (or 0)
+        *
+        * @return integer
+        */
+       protected function lock() {
+               return (int)wfGetDB( DB_MASTER )->selectField(
+                       'page',
+                       'page_latest',
+                       array(
+                               'page_namespace' => $this->getTitle()->getNamespace(),
+                               'page_title' => $this->getTitle()->getDBkey()
+                       ),
+                       __METHOD__,
+                       array( 'FOR UPDATE' )
+               );
+       }
+
        /**
         * Do some database updates after deletion
         *
index f86e731..928c3a8 100644 (file)
@@ -4449,7 +4449,9 @@ class Parser {
                $tocraw = array();
                $refers = array();
 
-               foreach ( $matches[3] as $headline ) {
+               $headlines = $numMatches !== false ? $matches[3] : array();
+
+               foreach ( $headlines as $headline ) {
                        $isTemplate = false;
                        $titleText = false;
                        $sectionIndex = false;
index b8a0acf..5a9465e 100644 (file)
@@ -247,6 +247,7 @@ class ResourceLoader {
 
                // Register core modules
                $this->register( include "$IP/resources/Resources.php" );
+               $this->register( include "$IP/resources/ResourcesOOUI.php" );
                // Register extension modules
                Hooks::run( 'ResourceLoaderRegisterModules', array( &$this ) );
                $this->register( $config->get( 'ResourceModules' ) );
index eaff4ab..f695b11 100644 (file)
@@ -28,7 +28,7 @@
  */
 class ResourceLoaderImageModule extends ResourceLoaderModule {
 
-       private $definition = null;
+       protected $definition = null;
 
        /**
         * Local base path, see __construct()
@@ -73,21 +73,27 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
         *         'selectorWithVariant' => [CSS selector template, variables: {prefix} {name} {variant}],
         *         // List of variants that may be used for the image files
         *         'variants' => array(
+        *             [theme name] => array(
         *                 [variant name] => array(
         *                     'color' => [color string, e.g. '#ffff00'],
         *                     'global' => [boolean, if true, this variant is available
         *                                  for all images of this type],
         *                 ),
+        *                 ...
+        *             ),
         *             ...
         *         ),
         *         // List of image files and their options
         *         'images' => array(
+        *             [theme name] => array(
         *                 [file path string],
         *                 [file path string] => array(
         *                     'name' => [image name string, defaults to file name],
         *                     'variants' => [array of variant name strings, variants
         *                                    available for this image],
         *                 ),
+        *                 ...
+        *             ),
         *             ...
         *         ),
         *     )
@@ -103,7 +109,7 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
        /**
         * Parse definition and external JSON data, if referenced.
         */
-       private function loadFromDefinition() {
+       protected function loadFromDefinition() {
                if ( $this->definition === null ) {
                        return;
                }
@@ -151,6 +157,17 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
                                                        "Invalid list error. '$option' given, array expected."
                                                );
                                        }
+                                       if ( !isset( $option['default'] ) ) {
+                                               // Backwards compatibility
+                                               $option = array( 'default' => $option );
+                                       }
+                                       foreach ( $option as $skin => $data ) {
+                                               if ( !is_array( $option ) ) {
+                                                       throw new InvalidArgumentException(
+                                                               "Invalid list error. '$option' given, array expected."
+                                                       );
+                                               }
+                                       }
                                        $this->{$member} = $option;
                                        break;
 
@@ -194,9 +211,9 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
         * @param string $name Image name
         * @return ResourceLoaderImage|null
         */
-       public function getImage( $name ) {
+       public function getImage( $name, ResourceLoaderContext $context ) {
                $this->loadFromDefinition();
-               $images = $this->getImages();
+               $images = $this->getImages( $context );
                return isset( $images[$name] ) ? $images[$name] : null;
        }
 
@@ -204,21 +221,29 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
         * Get ResourceLoaderImage objects for all images.
         * @return ResourceLoaderImage[] Array keyed by image name
         */
-       public function getImages() {
+       public function getImages( ResourceLoaderContext $context ) {
+               $skin = $context->getSkin();
                if ( !isset( $this->imageObjects ) ) {
                        $this->loadFromDefinition();
                        $this->imageObjects = array();
-
-                       foreach ( $this->images as $name => $options ) {
+               }
+               if ( !isset( $this->imageObjects[ $skin ] ) ) {
+                       $this->imageObjects[ $skin ] = array();
+                       if ( !isset( $this->images[ $skin ] ) ) {
+                               $this->images[ $skin ] = isset( $this->images[ 'default' ] ) ?
+                                       $this->images[ 'default' ] :
+                                       array();
+                       }
+                       foreach ( $this->images[ $skin ] as $name => $options ) {
                                $fileDescriptor = is_string( $options ) ? $options : $options['file'];
 
                                $allowedVariants = array_merge(
                                        is_array( $options ) && isset( $options['variants'] ) ? $options['variants'] : array(),
-                                       $this->getGlobalVariants()
+                                       $this->getGlobalVariants( $context )
                                );
-                               if ( isset( $this->variants ) ) {
+                               if ( isset( $this->variants[ $skin ] ) ) {
                                        $variantConfig = array_intersect_key(
-                                               $this->variants,
+                                               $this->variants[ $skin ],
                                                array_fill_keys( $allowedVariants, true )
                                        );
                                } else {
@@ -232,11 +257,11 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
                                        $this->localBasePath,
                                        $variantConfig
                                );
-                               $this->imageObjects[ $image->getName() ] = $image;
+                               $this->imageObjects[ $skin ][ $image->getName() ] = $image;
                        }
                }
 
-               return $this->imageObjects;
+               return $this->imageObjects[ $skin ];
        }
 
        /**
@@ -244,21 +269,27 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
         * for every image regardless of image options.
         * @return string[]
         */
-       public function getGlobalVariants() {
+       public function getGlobalVariants( ResourceLoaderContext $context ) {
+               $skin = $context->getSkin();
                if ( !isset( $this->globalVariants ) ) {
                        $this->loadFromDefinition();
                        $this->globalVariants = array();
-
-                       if ( isset( $this->variants ) ) {
-                               foreach ( $this->variants as $name => $config ) {
-                                       if ( isset( $config['global'] ) && $config['global'] ) {
-                                               $this->globalVariants[] = $name;
-                                       }
+               }
+               if ( !isset( $this->globalVariants[ $skin ] ) ) {
+                       $this->globalVariants[ $skin ] = array();
+                       if ( !isset( $this->variants[ $skin ] ) ) {
+                               $this->variants[ $skin ] = isset( $this->variants[ 'default' ] ) ?
+                                       $this->variants[ 'default' ] :
+                                       array();
+                       }
+                       foreach ( $this->variants[ $skin ] as $name => $config ) {
+                               if ( isset( $config['global'] ) && $config['global'] ) {
+                                       $this->globalVariants[ $skin ][] = $name;
                                }
                        }
                }
 
-               return $this->globalVariants;
+               return $this->globalVariants[ $skin ];
        }
 
        /**
@@ -273,7 +304,7 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
                $script = $context->getResourceLoader()->getLoadScript( $this->getSource() );
                $selectors = $this->getSelectors();
 
-               foreach ( $this->getImages() as $name => $image ) {
+               foreach ( $this->getImages( $context ) as $name => $image ) {
                        $declarations = $this->getCssDeclarations(
                                $image->getDataUri( $context, null, 'original' ),
                                $image->getUrl( $context, $script, null, 'rasterized' )
@@ -371,7 +402,7 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
        public function getModifiedTime( ResourceLoaderContext $context ) {
                $this->loadFromDefinition();
                $files = array();
-               foreach ( $this->getImages() as $name => $image ) {
+               foreach ( $this->getImages( $context ) as $name => $image ) {
                        $files[] = $image->getPath( $context );
                }
 
index 57634ab..958990c 100644 (file)
@@ -640,7 +640,7 @@ abstract class ResourceLoaderModule {
                        } catch ( Exception $e ) {
                                // We'll save this to cache to avoid having to validate broken JS over and over...
                                $err = $e->getMessage();
-                               $result = "throw new Error(" . Xml::encodeJsVar( "JavaScript parse error: $err" ) . ");";
+                               $result = "mw.log.error(" . Xml::encodeJsVar( "JavaScript parse error: $err" ) . ");";
                        }
 
                        $cache->set( $key, $result );
diff --git a/includes/resourceloader/ResourceLoaderOOUIImageModule.php b/includes/resourceloader/ResourceLoaderOOUIImageModule.php
new file mode 100644 (file)
index 0000000..6d76493
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Secret special sauce.
+ *
+ * @since 1.26
+ */
+class ResourceLoaderOOUIImageModule extends ResourceLoaderImageModule {
+       protected function loadFromDefinition() {
+               if ( $this->definition === null ) {
+                       return;
+               }
+
+               // Core default themes
+               $themes = array( 'default' => 'mediawiki' );
+               $themes += ExtensionRegistry::getInstance()->getAttribute( 'SkinOOUIThemes' );
+
+               $name = $this->definition['name'];
+               $rootPath = $this->definition['rootPath'];
+
+               $definition = array();
+               foreach ( $themes as $skin => $theme ) {
+                       // TODO Allow extensions to specify this path somehow
+                       $dataPath = $this->localBasePath . '/' . $rootPath . '/' . $theme . '/' . $name . '.json';
+
+                       if ( file_exists( $dataPath ) ) {
+                               $data = json_decode( file_get_contents( $dataPath ), true );
+                               array_walk_recursive( $data['images'], function ( &$path ) use ( $rootPath, $theme ) {
+                                       // TODO Allow extensions to specify this path somehow
+                                       $path = $rootPath . '/' . $theme . '/' . $path;
+                               } );
+                       } else {
+                               $data = array();
+                       }
+
+                       foreach ( $data as $key => $value ) {
+                               switch ( $key ) {
+                                       case 'images':
+                                       case 'variants':
+                                               $definition[$key][$skin] = $data[$key];
+                                               break;
+
+                                       default:
+                                               if ( !isset( $definition[$key] ) ) {
+                                                       $definition[$key] = $data[$key];
+                                               } elseif ( $definition[$key] !== $data[$key] ) {
+                                                       throw new Exception( "Mismatched OOUI theme definitions are not supported: trying to load $key of $theme theme" );
+                                               }
+                                               break;
+                               }
+                       }
+               }
+
+               // Fields from definition silently override keys from JSON files
+               $this->definition += $definition;
+
+               parent::loadFromDefinition();
+       }
+}
index 6b918e9..a2304e3 100644 (file)
@@ -38,6 +38,7 @@ class SpecialContributions extends IncludableSpecialPage {
                $this->outputHeader();
                $out = $this->getOutput();
                $out->addModuleStyles( 'mediawiki.special' );
+               $this->addHelpLink( 'Help:User contributions' );
 
                $this->opts = array();
                $request = $this->getRequest();
index c30d962..dfc7cdd 100644 (file)
@@ -295,6 +295,7 @@ class SpecialExport extends SpecialPage {
                $form .= Xml::closeElement( 'form' );
 
                $out->addHTML( $form );
+               $this->addHelpLink( 'Help:Export' );
        }
 
        /**
index 8124f10..f9b8ac3 100644 (file)
@@ -303,6 +303,7 @@ class SpecialImport extends SpecialPage {
                $action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) );
                $user = $this->getUser();
                $out = $this->getOutput();
+               $this->addHelpLink( '//meta.wikimedia.org/wiki/Special:MyLanguage/Help:Import', true );
                $importSources = $this->getConfig()->get( 'ImportSources' );
 
                if ( $user->isAllowed( 'importupload' ) ) {
index f5218ac..7ffaf48 100644 (file)
@@ -155,6 +155,7 @@ class LinkSearchPage extends QueryPage {
                $htmlForm->setAction( wfScript() );
                $htmlForm->setMethod( 'get' );
                $htmlForm->prepareForm()->displayForm( false );
+               $this->addHelpLink( 'Help:Linksearch' );
 
                if ( $target != '' ) {
                        $this->setParams( array(
index 7dc1158..96d512c 100644 (file)
@@ -57,6 +57,10 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                        return;
                }
 
+               $this->addHelpLink(
+                       '//meta.wikimedia.org/wiki/Special:MyLanguage/Help:Recent_changes',
+                       true
+               );
                parent::execute( $subpage );
        }
 
index 3ad9f0f..3c403fe 100644 (file)
@@ -244,6 +244,7 @@ class SpecialRecentChangesLinked extends SpecialRecentChanges {
                        Xml::check( 'showlinkedto', $opts['showlinkedto'], array( 'id' => 'showlinkedto' ) ) . ' ' .
                        Xml::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) );
 
+               $this->addHelpLink( 'Help:Related changes' );
                return $extraOpts;
        }
 
index df9d363..20f5776 100644 (file)
@@ -43,6 +43,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
 
                $output = $this->getOutput();
                $request = $this->getRequest();
+               $this->addHelpLink( 'Help:Watching pages' );
 
                $mode = SpecialEditWatchlist::getMode( $request, $subpage );
                if ( $mode !== false ) {
index 0b3175a..be77e62 100644 (file)
@@ -46,6 +46,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
 
                $this->setHeaders();
                $this->outputHeader();
+               $this->addHelpLink( 'Help:What links here' );
 
                $opts = new FormOptions();
 
index ae723e9..58d1ee6 100644 (file)
@@ -19,6 +19,7 @@
                "resources/src/mediawiki.page",
                "resources/src/mediawiki.special",
                "resources/src/mediawiki.toolbar",
+               "resources/src/mediawiki.widgets",
                "resources/src/jquery/jquery.accessKeyLabel.js",
                "resources/src/jquery/jquery.arrowSteps.js",
                "resources/src/jquery/jquery.autoEllipsis.js",
index 18f4594..d19dc25 100644 (file)
@@ -2034,7 +2034,18 @@ class Language {
                static $table = array(
                        array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
                        array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
-                       array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
+                       array( '',
+                               array( 'ק' ),
+                               array( 'ר' ),
+                               array( 'ש' ),
+                               array( 'ת' ),
+                               array( 'ת', 'ק' ),
+                               array( 'ת', 'ר' ),
+                               array( 'ת', 'ש' ),
+                               array( 'ת', 'ת' ),
+                               array( 'ת', 'ת', 'ק' ),
+                               array( 'ת', 'ת', 'ר' ),
+                       ),
                        array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
                );
 
@@ -2043,47 +2054,59 @@ class Language {
                        return $num;
                }
 
-               $s = '';
+               // Round thousands have special notations
+               if ( $num === 1000 ) {
+                       return "א' אלף";
+               } elseif ( $num % 1000 === 0 ) {
+                       return $table[0][$num / 1000] . "' אלפים";
+               }
+
+               $letters = array();
+
                for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
                        if ( $num >= $pow10 ) {
-                               if ( $num == 15 || $num == 16 ) {
-                                       $s .= $table[0][9] . $table[0][$num - 9];
+                               if ( $num === 15 || $num === 16 ) {
+                                       $letters[] = $table[0][9];
+                                       $letters[] = $table[0][$num - 9];
                                        $num = 0;
                                } else {
-                                       $s .= $table[$i][intval( ( $num / $pow10 ) )];
-                                       if ( $pow10 == 1000 ) {
-                                               $s .= "'";
+                                       $letters = array_merge(
+                                               $letters,
+                                               (array)$table[$i][intval( $num / $pow10 )]
+                                       );
+
+                                       if ( $pow10 === 1000 ) {
+                                               $letters[] = "'";
                                        }
                                }
                        }
+
                        $num = $num % $pow10;
                }
-               if ( strlen( $s ) == 2 ) {
-                       $str = $s . "'";
+
+               $preTransformLength = count( $letters );
+               if ( $preTransformLength === 1 ) {
+                       // Add geresh (single quote) to one-letter numbers
+                       $letters[] = "'";
                } else {
-                       $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
-                       $str .= substr( $s, strlen( $s ) - 2, 2 );
-               }
-               $start = substr( $str, 0, strlen( $str ) - 2 );
-               $end = substr( $str, strlen( $str ) - 2 );
-               switch ( $end ) {
-                       case 'כ':
-                               $str = $start . 'ך';
-                               break;
-                       case 'מ':
-                               $str = $start . 'ם';
-                               break;
-                       case 'נ':
-                               $str = $start . 'ן';
-                               break;
-                       case 'פ':
-                               $str = $start . 'ף';
-                               break;
-                       case 'צ':
-                               $str = $start . 'ץ';
-                               break;
+                       $lastIndex = $preTransformLength - 1;
+                       $letters[$lastIndex] = str_replace(
+                               array( 'כ', 'מ', 'נ', 'פ', 'צ' ),
+                               array( 'ך', 'ם', 'ן', 'ף', 'ץ' ),
+                               $letters[$lastIndex]
+                       );
+
+                       // Add gershayim (double quote) to multiple-letter numbers,
+                       // but exclude numbers with only one letter after the thousands
+                       // (1001-1009, 1020, 1030, 2001-2009, etc.)
+                       if ( $letters[1] === "'" && $preTransformLength === 3 ) {
+                               $letters[] = "'";
+                       } else {
+                               array_splice( $letters, -1, 0, '"' );
+                       }
                }
-               return $str;
+
+               return implode( $letters );
        }
 
        /**
index 8a50ec3..a15dabd 100644 (file)
        "loginreqlink": "اتكونيكتا",
        "loginreqpagetext": "الله يخلّيك لازم $1 باش تشوف الپاجات لُخرة.",
        "accmailtitle": "كلمت` السرّ راهي انبعتَت",
+       "accmailtext": "راهي انخلقت كلمت` سرّ مختارة على الزهَر لل مستعملي [[User talk:$1|$1]] و و انبعتت ل $2.\nتنجم تبدّلها فل پاجة<em>[[Special:ChangePassword|بدّل كلمت` السرّ]]</em> كي تتسجّل داخل.",
        "newarticle": "(جديد)",
        "newarticletext": "راك تبعت وصيلة لباجه لم ما تخدمتش بعد.\nباش تصنع هاذ الباجه ابدا الكتبه فالصندوق التحت (شوف في [$1  زياده باجه المساعده] لمعلومات).\nإذا كانت زيارتك لهاذ الباجه غلطه، ادرك على بوطون''ولى'' في نافيقاتور الإنترنت نتاعك.",
+       "anontalkpagetext": "----''هاذ الباجة نقاش لمستخدم مجهول ما قامش بإنشاء حساب بعد و الا ما يستعملش ذاك الحساب.\nلذا لازم علينا استعمال رقم الأيبي باش نتعرفو عليه/عليها.\nمثل هذا العنوان يمكنلو يشترك فيه مستخدمين بزاف.\nإذا كنت مستخدم مجهول وتشعر بلي التعليقات ما تخصكش وصلتلك ، من فضلك [[Special:UserLogin/signup|أصنع حساب]] ولا [[Special:UserLogin|سجل الدخول]] باش تتجنب الارتباك فالمستقبل مع مستخدمين مجهولين آخرين.''",
        "noarticletext": "حتا لضركا، ما كاين حتا نصّ ف هاذ الپاجه.\nتقدرو [[Special:Search/{{PAGENAME}}ترميو تفتيشه على هاذ العلوان]] فل پاجات لخرين,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} فتش فل عمليات المربوطة]\n ولا[{{fullurl:{{FULLPAGENAME}}|action=edit}} اصنع هاذ الپاجه]</span>.",
        "noarticletext-nopermission": "لحد الساعه ما كانش حتى نص في هاذ الباجه.\nتقدرو [[Special:Search/{{PAGENAME}}|ترميو تفتيشه على هاذ العنوان]] فالباجات لخرين,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} فتش فالعمليات المربوطه]\nو إلا[{{fullurl:{{FULLPAGENAME}}|action=edit}} أصنع هاذ الباجه]</span>.",
+       "missing-revision": "المراجعة #$1 من الباجة اللي سموها \"{{FULLPAGENAME}}\" ما هيش كاينة.\n\nهذا يصرا فالعادة منين نتبعو وصيلة تاريخها قديم لباجة تنحات.\nالتفاصيل يمكن نصيبوها في [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سجل المسح].",
+       "userpage-userdoesnotexist": "حساب المستخدم \"<nowiki>$1</nowiki>\" ماهوش مسجل.\nمن فضلك تأكد بلي راك باغي تصنع/تعدل هاذ الباجة.",
+       "userpage-userdoesnotexist-view": "حساب المستخدم \"$1\" ماهوش مسجل.",
+       "blocked-notice-logextract": "هذا المستخدم ممنوع حاليا.\nآخر دخلة في سجل المنع موفرة بالتحت كمرجع:",
+       "clearyourcache": "'''ملاحظة:''' بعد الحفظ، بلاك تحتاج باش تفرغ الكاش نتاع المتصفح و تشوف التبديلات.\n* '''فايرفوكس / سفاري:''' ادرك ''Shift'' منين تكون تدرك ''Reload''، ولاادرك ثاني إما  ''Ctrl-F5'' ولا''Ctrl-R'' (''⌘-R'' على ماك)\n* '''قوقل كروم:''' ادرك ''Ctrl-Shift-R'' (''⌘-Shift-R'' على ماك)\n* '''إنترنت إكسبلورر:''' ادرك ''Ctrl'' منين تدرك على ''Refresh''، ولا ادرك ''Ctrl-F5''\n* '''كنكرر:''' ادرك ''Reload'' ولا ادرك ''F5''\n* '''أوبرا:''' فرغ الكاش في ''Tools → Preferences''",
+       "usercssyoucanpreview": "'''ملاحظة:''' استعمل بوطون \"{{int:showpreview}}\" لاش تجرب CSS الجديد قبل ما تسجل الباجة.",
+       "userjsyoucanpreview": "'''ملاحظة:''' استعمل بوطون\"{{int:showpreview}}\" لتجربة جافاسكربت الجديدة قبل تسجل الباجة.",
+       "usercsspreview": "'''تفكر أنك راك تعرض الأنماط المتراصة (CSS) الخاصة بيك فقط\nو تمش حفظها بعد!'''",
+       "userjspreview": "'''تذكر أنك فقط تجرب/تعاين جافاسكربت.'''\n'''مازال ما  صراش التسجيل!'''",
+       "sitecsspreview": "'''تفكر أنك راك تعرض الأنماط المتراصة (CSS) الخاصة بيك فقط\nو تمش حفظها بعد!'''",
+       "sitejspreview": "'''تذكر أنك فقط تجرب/تعاين جافاسكربت.'''\n'''مازال ما  صراش التسجيل!'''",
+       "userinvalidcssjstitle": "'''تحذير:''' ما كانش تلبيسة\"$1\".\nتفكر بلي ملفات ال.css و ال.js تستعمل حروف صغيرة في العنوان ، كمثال {{ns:user}}:Foo/vector.css و ليس {{ns:user}}:Foo/Vector.css.",
+       "updated": "ميزاجور (تحديث)",
+       "note": "'''ملاحظة:'''",
        "previewnote": "'''ما تنساش هذي معاينه قبليه.'''\nلدوك التبديلات دياولك ما تسجلوش!",
+       "continue-editing": "روح لصندوق التحرير",
+       "previewconflict": "هاذ العرض يبين النص الموجود في صندوق التحرير الفوقاني اللي يبانلك إذا اختاريت النشر.",
+       "session_fail_preview": "'''مانقدروش نسجلو التبديلات نتاوعك علا خاطر كاين معلومات تودرو في ما يخص القعدة .'''\nمن فضلت حاول مرة اخرى.\nإذا ما نجحتش مرة جديدة, حاول باش [[Special:UserLogout|تخرج]], و تدخل مرة اخرى",
+       "session_fail_preview_html": "'''مانقدروش نسجلو التبديلات نتاوعك علا خاطر كاين معلومات تودرو في ما يخص القعدة .'''\n\n''على خاطر {{SITENAME}} فعل HTML brut, الإستضهار تخبا باش نتوخاو الهجمات بالجافاسكريبت.''\n\n'''إذا كانت محاولة التبديل صادقة, حاول مرة اخرى.'''\nالا ما نجحتش مرة اخرى, [[Special:UserLogout|اخرج]], و ادخل مرة اخرى",
        "editing": "تحرير $1",
        "creating": "خليق $1",
        "editingsection": "كتيبه $1 (قسم)",
index 495da1c..caa46a4 100644 (file)
        "upload-scripted-pi-callback": "Немагчыма загрузіць файл, які ўтрымлівае інструкцыі апрацоўкі табліцы стыляў XML.",
        "uploaded-script-svg": "У загружаным SVG-файле знойдзены небясьпечны элемэнт з падтрымкай сцэнароў «$1».",
        "uploaded-hostile-svg": "Знойдзены небясьпечны CSS у элемэнце стылю загружанага SVG-файла.",
+       "uploaded-event-handler-on-svg": "Усталёўваць атрыбуты апрацоўніка падзеяў <code>$1=\"$2\"</code> не дазволена для SVG-файлаў.",
        "uploadscriptednamespace": "Гэты SVG-файл утрымлівае няслушную прастору назваў «$1»",
        "uploadinvalidxml": "Не атрымалася прааналізаваць XML у загружаным файле.",
        "uploadvirus": "Файл утрымлівае вірус! Падрабязнасьці: $1",
index 5ff574a..98cd52e 100644 (file)
        "ipb-hardblock": "Mirout ouzh an implijerien kevreet da zegas kemmoù adalek ar chomlec'h IP-mañ",
        "ipbcreateaccount": "Mirout ouzh an implijer da grouiñ kontoù",
        "ipbemailban": "Mirout ouzh an implijer da gas posteloù",
-       "ipbenableautoblock": "Stankañ war-eeun ar chomlec'h IP diwezhañ implijet gant an den-mañ hag an holl chomlec'hioù a c'hallfe klask kemmañ traoù drezo drezo diwezhatoc'h",
+       "ipbenableautoblock": "Stankañ war-eeun ar chomlec'h IP diwezhañ implijet gant an den-mañ hag an holl chomlec'hioù a c'hallfe klask kemmañ traoù drezo diwezhatoc'h",
        "ipbsubmit": "Stankañ an implijer-mañ",
        "ipbother": "Prantad all",
        "ipboptions": "2 eurvezh:2 hours,1 devezh:1 day,3 devezh:3 days,1 sizhunvezh:1 week,2 sizhunvezh:2 weeks,1 mizvezh:1 month,3 mizvezh:3 months,6 mizvezh:6 months,1 bloaz:1 year,da viken:infinite",
index dc2cd0c..ebb66a1 100644 (file)
        "sp-contributions-blocked-notice-anon": "ХӀара IP-адрес хӀинца блоктоьхна ду.\nЛахахь гойту блоктохарийн тептар чура тӀаьххьарлера дӀаяздар:",
        "sp-contributions-search": "Къинхьегам лахар",
        "sp-contributions-username": "IP-адрес я декъашхочун цӀе:",
-       "sp-contributions-toponly": "ТÓ\80аÑ\8cÑ\85Ñ\8cаÑ\80а Ð±Ð¸Ð½Ð° Ñ\85ийÑ\86амаÑ\88 Ð³Ð°Ð¹Ñ\82а",
+       "sp-contributions-toponly": "Ð\93айÑ\82а Ñ\82Ó\80аÑ\8cÑ\85Ñ\85Ñ\8cаÑ\80леÑ\80а Ñ\85ийÑ\86амаÑ\88",
        "sp-contributions-newonly": "АгӀонаш кхоллар бен ма гайта",
        "sp-contributions-submit": "Лахар",
        "whatlinkshere": "Хьажоргаш кхузе",
index d4f707e..3574c29 100644 (file)
        "specialpage": "खास पानो",
        "personaltools": "व्यक्तिगत औजार",
        "articlepage": "कन्टेन्ट पानो हेर",
-       "talk": "बातà¤\9aित",
+       "talk": "à¤\95à¥\81रडà¥\80à¤\95ानà¥\80",
        "views": "अवलोकन गर",
        "toolbox": "औजारहरू",
        "userpage": "प्रयोगकर्ता पाना हेर्न्या",
        "lastmodifiedat": "यै पानालाई आन्तिम पटक $2, $1 मी परिवर्तन गरिया थ्यो।",
        "viewcount": "यो पाना हेरियाको थियो {{PLURAL:$1|एकपटक|$1 पटक}}",
        "protectedpage": "सुरक्षित गरिएका पानाहरु",
-       "jumpto": "यà¥\88मà¥\80 à¤\9cावà¤\93:",
+       "jumpto": "यैमी जाओ:",
        "jumptonavigation": "खोज",
        "jumptosearch": "खोज",
        "view-pool-error": "माफ गरया , ये समयमी सर्भरहरुमी कामको भार भौत रह्या छ।\nभौत भौत प्रयोगकर्ताहरु ये पाना हेद्या प्रयास गरी रहनु भएको छ।\nकृपया यो पाना पुन: हेर्नु अगाडि केही समय पर्खिदिनुहोस् ।\n\n$1",
        "timezoneregion-atlantic": "एट्लान्टिक महासागर",
        "timezoneregion-australia": "अष्ट्रेलिया",
        "timezoneregion-indian": "हिन्द महासागर",
+       "upload": "चित्र अपलोड गर",
+       "move": "नाम बदल",
        "movethispage": "पानाको नाम बदल्न्या",
        "mywatchlist": "मेरो ध्यान सूची",
+       "watch": "ध्यान राख",
        "watchthispage": "यै पानाको ध्यान राख",
+       "unwatch": "ध्यान हटाओ",
        "unwatchthispage": "ध्यान हटाओ",
        "notanarticle": "सामाग्री छैन",
-       "contributions": "{{GENDER:$1|प्रयोगकर्ता}}को योगदान"
+       "blanknamespace": "(मुख्य)",
+       "contributions": "{{GENDER:$1|प्रयोगकर्ता}}को योगदान",
+       "contribslink": "योगदानहरू",
+       "tooltip-search": "{{SITENAME}} मी खोज",
+       "tooltip-search-fulltext": "ये पाठका लागि पाना खोज",
+       "tooltip-t-recentchangeslinked": "ये पानामा जोडिईया पानानमा ऐलैको परिवर्तन",
+       "specialpages": "खास पानो",
+       "searchsuggest-search": "खोज"
 }
index b8fd4ab..83fe0b1 100644 (file)
        "passwordreset-emailtitle": "Detalles de la cuenta en {{SITENAME}}",
        "passwordreset-emailtext-ip": "Alguien (probablemente tú, desde la dirección IP $1) ha solicitado el restablecimiento de tu contraseña en {{SITENAME}} ($4). {{PLURAL:$3|La siguiente cuenta está asociada|Las siguientes cuentas están asociadas}}\na esta dirección de correo electrónico:\n\n$2\n\n{{PLURAL:$3|Esta contraseña temporal|Estas contraseñas temporales}} caducarán en {{PLURAL:$5|un día|$5 días}}.\nAhora puedes iniciar sesión y establecer una nueva contraseña. Si fue otra persona la que realizó esta solicitud, o si ya recuerdas tu contraseña original y, por tanto, no deseas cambiarla, puedes ignorar este mensaje y continuar usando tu contraseña anterior.",
        "passwordreset-emailtext-user": "El usuario $1 de {{SITENAME}} solicitó el restablecimiento de tu contraseña en {{SITENAME}}\n($4). {{PLURAL:$3|La siguiente cuenta está asociada|Las siguientes cuentas están asociadas}} a esta dirección de correo electrónico:\n\n$2\n\n{{PLURAL:$3|Esta contraseña temporal|Estas contraseñas temporales}} caducarán en {{PLURAL:$5|un día|$5 días}}.\nAhora puedes iniciar sesión y establecer una nueva contraseña. Si fue otra persona la que realizó esta solicitud, o si ya recuerdas tu contraseña original y, por tanto, no deseas cambiarla, puedes ignorar este mensaje y continuar usando tu contraseña anterior.",
-       "passwordreset-emailelement": "Nombre de usuario: $1\nContraseña temporal: $2",
+       "passwordreset-emailelement": "Nombre de {{GENDER:$1|usuario|usuaria}}: $1\nContraseña temporal: $2",
        "passwordreset-emailsent": "Se ha enviado un correo electrónico para el restablecimiento de tu contraseña.",
        "passwordreset-emailsent-capture": "Se ha enviado un correo para el restablecimiento de la contraseña, el cual se muestra a continuación.",
        "passwordreset-emailerror-capture": "Se ha generado un correo electrónico de restablecimiento de contraseña, que se muestra a continuación, pero ha fallado el envío {{GENDER:$2|al usuario|a la usuaria}}: $1",
        "duplicate-args-warning": "<strong>Aviso:</strong> [[:$1]] usa [[:$2]] con más de un valor para el parámetro «$3». Se usará solo el último valor proporcionado.",
        "duplicate-args-category": "Páginas que usan argumentos duplicados en invocaciones de plantillas",
        "duplicate-args-category-desc": "La página contiene invocaciones de plantillas que utilizan argumentos duplicados, como <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> o <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
-       "expensive-parserfunction-warning": "<strong>Advertencia:</strong> Esta página contiene demasiadas llamadas a funciones sintácticas costosas.\n\nTiene {{PLURAL:$1|una llamada|$1 llamadas}}, pero debería tener menos de {{PLURAL:$2|una|$2}}.",
+       "expensive-parserfunction-warning": "<strong>Advertencia:</strong> esta página contiene demasiadas llamadas a funciones sintácticas costosas.\n\nTiene {{PLURAL:$1|una llamada|$1 llamadas}}, pero debería tener menos de {{PLURAL:$2|una|$2}}.",
        "expensive-parserfunction-category": "Páginas con llamadas a funciones sintácticas demasiado costosas",
        "post-expand-template-inclusion-warning": "<strong>Aviso:</strong> El tamaño de las plantillas incluidas es muy grande.\nAlgunas de ellas no se incluirán.",
        "post-expand-template-inclusion-category": "Páginas con sobrecarga de plantillas",
        "diff-multi-sameuser": "(No se {{PLURAL:$1|muestra una edición intermedia|muestran $1 ediciones intermedias}} del mismo usuario)",
        "diff-multi-otherusers": "(No se {{PLURAL:$1|muestra una edición intermedia|muestran $1 ediciones intermedias}} de {{PLURAL:$2|otro usuario|$2 usuarios}})",
        "diff-multi-manyusers": "(No se {{PLURAL:$1|muestra una edición intermedia|muestran $1 ediciones intermedias}} de más de {{PLURAL:$2|un usuario|$2 usuarios}})",
-       "difference-missing-revision": "No {{PLURAL:$2|se ha encontrado|se han encontrado}} {{PLURAL:$2|una revisión|$2 revisiones}} de la comparación solicitada ($1).\n\nLa causa de esto suele ser un enlace obsoleto hacia una página que ya ha sido borrada.\nPara más información, consulta el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de borrados].",
+       "difference-missing-revision": "No se {{PLURAL:$2|ha encontrado una revisión|han encontrado $2 revisiones}} de la comparación solicitada ($1).\n\nLa causa de esto suele ser un enlace obsoleto hacia una página que ya ha sido borrada.\nPara más información, consulta el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de borrados].",
        "searchresults": "Resultados de la búsqueda",
        "searchresults-title": "Resultados de la búsqueda de «$1»",
        "titlematches": "Resultados por título de página",
        "searchprofile-everything-tooltip": "Buscar en todo el contenido (incluyendo páginas de discusión)",
        "searchprofile-advanced-tooltip": "Buscar en espacios de nombres personalizados",
        "search-result-size": "$1 ({{PLURAL:$2|1 palabra|$2 palabras}})",
-       "search-result-category-size": "{{PLURAL:$1|1 miembro|$1 miembros}} ({{PLURAL:$2|1 subcategoría|$2 subcategorías}}, {{PLURAL:$3|1 fichero|$3 ficheros}})",
+       "search-result-category-size": "{{PLURAL:$1|1 miembro|$1 miembros}} ({{PLURAL:$2|1 subcategoría|$2 subcategorías}}, {{PLURAL:$3|1 archivo|$3 archivos}})",
        "search-redirect": "(redirige desde $1)",
        "search-section": "(sección $1)",
        "search-category": "(categoría $1)",
        "uploaderror": "Error al intentar subir",
        "upload-recreate-warning": "<strong>Aviso: Un archivo con ese nombre ha sido eliminado o renombrado.</strong>\n\nA continuación se muestra el registro de borrados y traslados de esta página:",
        "uploadtext": "Utiliza el siguiente formulario para subir archivos.\nPara ver o buscar archivos subidos con anterioridad, ve a la [[Special:FileList|lista de archivos subidos]].\nLos archivos subidos también quedarán registrados en el [[Special:Log/upload|registro de archivos subidos]], y los borrados en el [[Special:Log/delete|registro de borrados]].\n\nPara incluir un archivo en una página, usa un enlace como los mostrados a continuación:\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code></strong> para usar la versión a tamaño completo del archivo\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|texto alternativo]]</nowiki></code></strong> para una versión de 200 píxeles de ancho situada en una caja en el margen izquierdo con \"texto alternativo\" como descripción\n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code></strong> para enlazar directamente al archivo sin mostrarlo.",
-       "upload-permitted": "{{PLURAL:$2|Tipo|Tipos}} de archivo permitidos: $1.",
-       "upload-preferred": "{{PLURAL:$2|Tipo|Tipos}} de archivo preferidos: $1.",
-       "upload-prohibited": "{{PLURAL:$2|Tipo|Tipos}} de archivo prohibidos: $1.",
+       "upload-permitted": "{{PLURAL:$2|Tipo de archivo permitido|Tipos de archivo permitidos}}: $1.",
+       "upload-preferred": "{{PLURAL:$2|Tipo de archivo preferido|Tipos de archivo preferidos}}: $1.",
+       "upload-prohibited": "{{PLURAL:$2|Tipo de archivo prohibido|Tipos de archivo prohibidos}}: $1.",
        "uploadlogpage": "Subidas de archivos",
        "uploadlogpagetext": "Debajo se muestra una lista de los últimos archivos subidos.\nVéase la [[Special:NewFiles|galería de archivos nuevos]] para una vista más estilizada",
        "filename": "Nombre del archivo",
        "upload-scripted-pi-callback": "No se pueden cargar archivos que contengan instrucciones de procesamiento en forma de hojas de estilo XML.",
        "uploaded-script-svg": "Se encontró el elemento habilitado para secuencias de órdenes «$1» en el archivo SVG cargado.",
        "uploaded-hostile-svg": "Se encontró código CSS no seguro en el elemento de estilo del archivo SVG cargado.",
+       "uploaded-event-handler-on-svg": "No está permitido configurar atributos controladores de eventos <code>$1=\"$2\"</code> en los archivos SVG.",
        "uploaded-href-attribute-svg": "No se permite que los archivos SVG contengan los atributos de «href» <code>&lt;$1 $2=\"$3\"&gt;</code> apuntando a recursos no locales (p. ej., http:// o javascript:).",
+       "uploaded-href-unsafe-target-svg": "Se encontró un \"href\" hacia un destino inseguro <code><$1 $2=\"$3\"></code> en el archivo SVG cargado.",
+       "uploaded-animate-svg": "Se encontró un etiqueta \"animate\" que puede estar cambiando \"href\", mediante el atributo \"from\" <code>&lt;$1 $2=\"$3\"&gt;</code> en el archivo SVG cargado.",
+       "uploaded-setting-event-handler-svg": "Está bloqueada la configuración de atributos controladores de eventos. Se encontró <code>&lt;$1 $2=\"$3\"&gt;</code> en el archivo SVG cargado.",
+       "uploaded-setting-href-svg": "Está bloqueado el uso de la etiqueta \"set\" para añadir un atributo \"href\" al elemento padre.",
+       "uploaded-wrong-setting-svg": "Está bloqueado el uso de la etiqueta \"set\" para añadir un destino remote/data/script a cualquier atributo. Se encontró <code>&lt;set to=\"$1\"&gt;</code> en el archivo SVG cargado.",
+       "uploaded-setting-handler-svg": "Están bloqueados los archivos SVG que configuran el atributo \"handler\" con remote/data/script. Se encontró <code>$1=\"$2\"</code> en el archivo SVG cargado.",
        "uploaded-remote-url-svg": "Se bloquean los SVG que contienen URL de estilo externas. El archivo SVG cargado contiene <code>$1=\"$2\"</code>.",
+       "uploaded-image-filter-svg": "Se encontró un filtro de imagen con URL: <code>&lt;$1 $2=\"$3\"&gt;</code> en el archivo SVG cargado.",
        "uploadscriptednamespace": "Este archivo SVG contiene un espacio de nombre no permitido '$1'",
        "uploadinvalidxml": "No se pudo analizar el XML del archivo cargado.",
        "uploadvirus": "¡El archivo contiene un virus!\nDetalles: $1",
        "backend-fail-closetemp": "No se pudo cerrar el archivo temporal.",
        "backend-fail-read": "No se pudo leer el archivo «$1».",
        "backend-fail-create": "No se pudo escribir el archivo $1.",
-       "backend-fail-maxsize": "No se pudo escribir el archivo $1 porque es mayor de {{PLURAL:$2|un byte|$2 bytes}}.",
+       "backend-fail-maxsize": "No se pudo escribir el archivo \"$1\" porque es mayor a {{PLURAL:$2|un byte|$2 bytes}}.",
        "backend-fail-readonly": "El servidor (back-end) de almacenamiento \"$1\" está actualmente en estado de sólo lectura. La razón aducida fue: \"$2\"",
        "backend-fail-synced": "El archivo \"$1\" se encuentra en un estado incoherente dentro de los servidores (backends) de almacenamiento interno",
        "backend-fail-connect": "No se pudo conectar al servidor (backend) de almacenamiento \"$1\".",
        "filehist-comment": "Comentario",
        "imagelinks": "Usos del archivo",
        "linkstoimage": "{{PLURAL:$1|La siguiente página enlaza|Las siguientes páginas enlazan}} a este archivo:",
-       "linkstoimage-more": "Hay más de {{PLURAL:$1|una página que enlaza|$1 páginas que enlazan}} con este archivo.\n\nLa lista siguiente sólo muestra {{PLURAL:$1|la primera página que enlaza|las primeras $1 páginas que enlazan}} con este archivo.\nTambién puedes consultar la [[Special:WhatLinksHere/$2|lista completa]].",
+       "linkstoimage-more": "Hay más de {{PLURAL:$1|una página que enlaza|$1 páginas que enlazan}} con este archivo.\nLa lista siguiente sólo muestra {{PLURAL:$1|la primera página que enlaza|las primeras $1 páginas que enlazan}} con este archivo.\nTambién puedes consultar la [[Special:WhatLinksHere/$2|lista completa]].",
        "nolinkstoimage": "No hay páginas que enlacen a esta imagen.",
        "morelinkstoimage": "Vea [[Special:WhatLinksHere/$1|más enlaces]] a este archivo.",
        "linkstoimage-redirect": "$1 (archivo de redirección) $2",
        "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",
        "randompage": "Página aleatoria",
-       "randompage-nopages": "No hay páginas en los siguientes {{PLURAL:$2|espacio de nombre|espacios de nombre}}: $1.",
+       "randompage-nopages": "No hay páginas en {{PLURAL:$2|el siguiente espacio de nombres|los siguientes espacios de nombres}}: $1.",
        "randomincategory": "Página aleatoria en categoría",
        "randomincategory-invalidcategory": "\"$1\" no es una categoría válida.",
        "randomincategory-nopages": "No hay páginas en la categoría [[:Category:$1|$1]].",
        "unusedimages": "Imágenes sin uso",
        "wantedcategories": "Categorías requeridas",
        "wantedpages": "Páginas requeridas",
-       "wantedpages-summary": "Lista de páginas inexistentes con el mayor número de enlaces a ellas, excluyendo páginas con únicamente redirecciones. Para una lista de páginas con redirecciones apuntando a ellas, consulte [[{{#special:BrokenRedirects}}]].",
+       "wantedpages-summary": "Lista de páginas inexistentes con el mayor número de enlaces a ellas, excluyendo páginas que son solo redirecciones a ellas. Para ver una lista de páginas inexistentes con redirecciones apuntando a ellas, consulta [[{{#special:BrokenRedirects}}|la lista de redirecciones rotas]].",
        "wantedpages-badtitle": "Título inválido en conjunto de resultados: $1",
        "wantedfiles": "Ficheros requeridos",
        "wantedfiletext-cat": "Los siguientes archivos están en uso, pero no existen. Es posible que algunos de ellos estén almacenados en repositorios externos y se hayan incluido aquí por error; dichas entradas aparecen <del>tachadas</del>. De igual manera, las páginas que incluyen archivos inexistentes se enumeran en [[:$1]].",
        "undeletepagetext": "{{PLURAL:$1|La siguiente página ha sido borrada, pero aún está en el archivo y puede ser restaurada.|Las siguientes $1 páginas han sido borradas, pero aún están en el archivo y pueden ser restauradas.}}\nPuede que el archivo se limpie periódicamente.",
        "undelete-fieldset-title": "Restaurar revisiones",
        "undeleteextrahelp": "Para restaurar todo el historial de la página, deja todas las casillas sin seleccionar y pulsa en <strong><em>{{int:undeletebtn}}</em></strong>. Para realizar una restauración selectiva, marca las revisiones que deban restaurarse y pulsa en <strong><em>{{int:undeletebtn}}</em></strong>.",
-       "undeleterevisions": "$1 {{PLURAL:$1|revisión|revisiones}} archivadas",
+       "undeleterevisions": "$1 {{PLURAL:$1|revisión archivada|revisiones archivadas}}",
        "undeletehistory": "Si restauras la página, todas sus revisiones se restaurarán en el historial.\nSi una nueva página con el mismo nombre se ha creado desde que se borró la original, las versiones restauradas aparecerán en el historial previo.",
        "undeleterevdel": "No se deshará el borrado si éste resulta en el borrado parcial de la última revisión de la página. En tal caso, desmarque o muestre las revisiones borradas más recientes. Las revisiones de archivos que no tiene permitido ver no se restaurarán.",
        "undeletehistorynoadmin": "El artículo ha sido borrado. La razón de su eliminación se indica abajo en el resumen, así como los detalles de las ediciones realizadas antes del borrado. El texto completo del artículo está disponible sólo para usuarios con permisos de administrador.",
        "undeleteviewlink": "ver",
        "undeleteinvert": "Invertir selección",
        "undeletecomment": "Razón:",
-       "undeletedrevisions": "{{PLURAL:$1|Una edición restaurada|$1 ediciones restauradas}}",
-       "undeletedrevisions-files": "$1 {{PLURAL:$1|ediciones restauradas y $2 archivo restaurado|ediciones y $2 archivos restaurados}}",
+       "undeletedrevisions": "{{PLURAL:$1|Una revisión restaurada|$1 revisiones restauradas}}",
+       "undeletedrevisions-files": "{{PLURAL:$1|1 revisión|$1 revisiones}} y {{PLURAL:$2|1 archivo|$2 archivos}} restaurados",
        "undeletedfiles": "$1 {{PLURAL:$1|archivo restaurado|archivos restaurados}}",
        "cannotundelete": "Hubo un error durante la restauración:\n$1",
        "undeletedpage": "'''Se ha restaurado $1'''\n\nConsulta el [[Special:Log/delete|registro de borrados]] para ver una lista de los últimos borrados y restauraciones.",
        "blocklist-reason": "Motivo",
        "ipblocklist-submit": "Buscar",
        "ipblocklist-localblock": "Bloqueo local",
-       "ipblocklist-otherblocks": "Otros {{PLURAL:$1|bloqueo| bloqueos}}",
+       "ipblocklist-otherblocks": "{{PLURAL:$1|Otro bloqueo|Otros bloqueos}}",
        "infiniteblock": "infinito",
        "expiringblock": "expira el $1 a las $2",
        "anononlyblock": "sólo anón.",
        "exif-compression-2": "CCITT Group 3 1-Dimensional Modified Huffman run length encoding",
        "exif-compression-3": "Codificación de fax CCITT grupo 3",
        "exif-compression-4": "Codificación de fax CCITT grupo 4",
+       "exif-compression-6": "JPEG (antiguo)",
        "exif-copyrighted-true": "Con derechos de autor",
        "exif-copyrighted-false": "No se ha definido el estado del copyright",
        "exif-unknowndate": "Fecha desconocida",
        "exif-orientation-8": "Rotada 90° a la derecha",
        "exif-planarconfiguration-1": "formato panorámico",
        "exif-planarconfiguration-2": "formato plano",
+       "exif-xyresolution-i": "$1 ppp",
        "exif-colorspace-65535": "Sin calibrar",
        "exif-componentsconfiguration-0": "no existe",
        "exif-exposureprogram-0": "No definido",
        "confirm-unwatch-button": "Aceptar",
        "confirm-unwatch-top": "¿Quitar esta página de tu lista de seguimiento?",
        "comma-separator": ",&#32;",
+       "percent": "$1 %",
        "quotation-marks": "«$1»",
        "imgmultipageprev": "← página anterior",
        "imgmultipagenext": "siguiente página →",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 ('''desactivado''')",
        "mediastatistics": "Estadísticas de multimedia",
        "mediastatistics-summary": "Estadísticas sobre los tipos de archivos cargados. Sólo se incluyen las versiones más recientes. Los archivos antiguos o eliminados están excluidos.",
+       "mediastatistics-nfiles": "$1 ($2 %)",
        "mediastatistics-nbytes": "{{PLURAL:$1|$1 ''byte''|$1 ''bytes''}} ($2; $3 %)",
        "mediastatistics-table-mimetype": "Tipo MIME",
        "mediastatistics-table-extensions": "Extensiones posibles",
index d29ac79..d68dc90 100644 (file)
        "expiringblock": "در $1 ساعت $2 به پایان می‌رسد",
        "anononlyblock": "فقط کاربران گمنام",
        "noautoblockblock": "بستن خودکار غیرفعال است",
-       "createaccountblock": "اÙ\85کاÙ\86 Ø§Û\8cجاد Ø­Ø³Ø§Ø¨ Ù\85سدÙ\88د Ø§Ø³Øª",
-       "emailblock": "ایمیل مسدود شد",
+       "createaccountblock": "اÙ\85کاÙ\86 Ø³Ø§Ø®Øª Ø­Ø³Ø§Ø¨ Ú¯Ø±Ù\81تÙ\87â\80\8cشدÙ\87",
+       "emailblock": "ایمیل بسته‌شده",
        "blocklist-nousertalk": "نمی تواند صفحهٔ بحث خود را ویرایش کند",
        "ipblocklist-empty": "فهرست بسته‌شدن‌ها خالی‌است.",
        "ipblocklist-no-results": "دسترسی حساب کاربری یا نشانی آی‌پی مورد نظر قطع نیست.",
        "block-log-flags-anononly": "فقط کاربران گمنام",
        "block-log-flags-nocreate": "قابلیت ایجاد حساب غیرفعال شد",
        "block-log-flags-noautoblock": "قطع دسترسی خودکار غیرفعال شد",
-       "block-log-flags-noemail": "ایمیل مسدود شد",
+       "block-log-flags-noemail": "ایمیل بسته‌شد",
        "block-log-flags-nousertalk": "صفحهٔ بحث خود را نمی‌تواند ویرایش کند",
        "block-log-flags-angry-autoblock": "قطع دسترسی خودکار پیشرفته فعال شد",
        "block-log-flags-hiddenname": "نام کاربری پنهان",
index 45cb44c..8270157 100644 (file)
        "permissionserrors": "Çewtiyê destûrê",
        "permissionserrorstext": "Tu nikanê vê tiştî bikê, ji bo {{PLURAL:$1|vê sedemê|van sedeman}}:",
        "permissionserrorstext-withaction": "Mafên te bo $2 tune ye ji bo {{PLURAL:$1|vê sedemê|van sedeman}}:",
-       "recreate-moveddeleted-warn": "'''Zanibe: Tu kê rûpelekê çêkê yê niha hate jêbirin!'''\n\nZanibe ku nuhçêkirinê vê rûpelê hêja ye ya na.\nÎnformasyon li ser jêbirinê vê rûpelê li vir e:",
+       "recreate-moveddeleted-warn": "<strong>Zanibe: Tu kê rûpelekê çêkê yê niha hate jêbirin!</strong>\n\nZanibe ku nuhçêkirinê vê rûpelê hêja ye ya na.\nÎnformasyon li ser jêbirinê vê rûpelê li vir e:",
        "moveddeleted-notice": "Ev rûpel hatiye jêbirin.\nSedema jêbirina rûpelê bi referansa li jêr hatiye piştrastkirin.",
        "log-fulllog": "Tevahiya wê bibîne",
        "edit-conflict": "Têkçûna guherandinan.",
index bd78aec..afeccc7 100644 (file)
        "uploaded-script-svg": "Найден небезопасный элемент с поддержкой сценариев «$1» в загруженном SVG-файле.",
        "uploaded-hostile-svg": "Найден небезопасный CSS-код в элементе стиля загруженного SVG-файла.",
        "uploaded-event-handler-on-svg": "Установка атрибутов обработчика событий <code>$1=\"$2\"</code> не разрешено для SVG-файлов.",
-       "uploaded-href-attribute-svg": "В SVG-файлах не допускаются href-атрибуты <code><$1 $2=\"$3\"></code> с нелокальной целью (т.е. http://, javascript:, и пр.).",
+       "uploaded-href-attribute-svg": "В SVG-файлах не допускаются href-атрибуты <code>&lt;$1 $2=\"$3\"&gt;</code> с нелокальной целью (т.е. http://, javascript:, и пр.).",
        "uploaded-href-unsafe-target-svg": "В загруженном SVG-файле найдена ссылка на небезопасную цель <code>&lt;$1 $2=\"$3\"&gt;</code>.",
-       "uploaded-animate-svg": "Найден тег «animate», который может изменять ссылку с помощью «from»-атрибута <code><$1 $2=\"$3\"></code> в загруженном SVG-файле.",
+       "uploaded-animate-svg": "Найден тег «animate», который может изменять ссылку с помощью «from»-атрибута <code>&lt;$1 $2=\"$3\"&gt;</code> в загруженном SVG-файле.",
+       "uploaded-setting-event-handler-svg": "Установка атрибутов обработчика событий заблокирована, в загруженном SVG-файле найден код <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-setting-href-svg": "Использование тега «set» для добавления атрибута «href» в родительский элемент заблокировано.",
+       "uploaded-wrong-setting-svg": "Использование тега «set» для добавления в качестве цели удалённого адреса/данных/сценария для любого атрибута заблокировано. В загруженном SVG-файле найдена конструкция <code>&lt;set to=\"$1\"&gt;</code>.",
+       "uploaded-setting-handler-svg": "SVG, который задаёт атрибут «handler» с помощью удалённого адреса/данных/скрипта, блокируется. Найдена конструкция <code>$1=\"$2\"</code> в загруженном SVG-файле.",
        "uploaded-remote-url-svg": "SVG, который задаёт любой атрибут стиля с помощью удалённого URL-адреса, блокируется. Найдена конструкция <code>$1=\"$2\"</code> в загруженном SVG-файле.",
        "uploaded-image-filter-svg": "В загруженном SVG-файле найден фильтр изображений с URL-адресом <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploadscriptednamespace": "Этот SVG-файл содержит некорректное пространство имён '$1'",
        "file-nohires": "Нет версии с бо́льшим разрешением.",
        "svg-long-desc": "SVG-файл, номинально $1 × $2 {{PLURAL:$2|пиксель|пикселя|пикселей}}, размер файла: $3",
        "svg-long-desc-animated": "Анимированный SVG-файл, номинально $1 × $2 {{PLURAL:$2|пиксель|пикселя|пикселей}}, размер файла: $3",
-       "svg-long-error": "неправильный SVG-файл: $1",
+       "svg-long-error": "Ð\9dеправильный SVG-файл: $1",
        "show-big-image": "Исходный файл",
        "show-big-image-preview": "Размер при предпросмотре: $1.",
        "show-big-image-other": "{{PLURAL:$2|1=Другое разрешение|Другие разрешения}}: $1.",
index fb8c1d3..9c79ad3 100644 (file)
        "badtitletext": "Lu tìtulu di pàggina addumannatu nun era vàlidu, era vacanti, o vinìa dûn culligamentu intir-linguìsticu o intir-wiki malu fattu.\nPutissi cuntèniri unu o cchiu' ssai caràttiri chi' nun su' cunsintuti ntê tìtula.",
        "title-invalid-empty": "Lu tìtulu addumannatu pâ pàggina è vacanti o puru cunteni sulu lu nomu dûn namespace.",
        "title-invalid-utf8": "Lu tìtulu addumannatu pâ pàggina cunteni na siguenza UTF-8 nun vàlida.",
-       "title-invalid-interwiki": "Lu tìtulu cunteni nu culligamentu interwiki",
+       "title-invalid-interwiki": "Lu tìtulu addumannatu pâ pàggina cunteni ligami interwiki, ca ntê tìtula nun si ponnu adupirari.",
        "title-invalid-talk-namespace": "Lu tìtulu addumannatu pâ pàggina si rifirisci a na pàggina di discussioni ca nun esisti.",
        "title-invalid-characters": "Lu tìtulu addumannatu pâ pàggina cunteni caràttiri nun vàlidi: \"$1\".",
        "title-invalid-relative": "Lu tìtulu havi un caminu rilativu. Li tìtuli di pàggina rilativi (./, ../) nun sunnu boni, picchì spissu nun si ponnu arruvari pi' menzu dî browser di l'utenti.",
        "title-invalid-magic-tilde": "Lu tìtulu addumannatu pâ pàggina cunteni na siguenza maggica di tildi nun vàlida(<nowiki>~~~</nowiki>).",
-       "title-invalid-too-long": "Lu tìtulu addumannatu pâ pàggina è troppu longu. Nun havi a' èssiri cchiu' longu di $1 byte sutta cudìfica UTF-8.",
+       "title-invalid-too-long": "Lu tìtulu addumannatu pâ pàggina è troppu longu. Nun havi a' èssiri cchiu' longu di {{PLURAL:$1|byte}} sutta cudìfica UTF-8.",
        "title-invalid-leading-colon": "Lu tìtulu addumannatu pâ pàggina cunteni nu signu di du punti ô principiu, chi' nun è vàlidu.",
        "perfcached": "Li dati ca sèquinu sunnu stratti di na ''cache'' e putissiru nun èssiri aggiurnati. Ntâ ''cache'' {{PLURAL:$1|capi un risultatu|càpunu $1 risultati}} massimu.",
        "perfcachedts": "Li dati ca sèquinu sunnu stratti di na ''cache'', e furu aggiurnati l'ultima vota ô $1. Ntâ ''cache'' {{PLURAL:$4|capi un risultatu|capunu $4 risultati}} massimu.",
        "content-model-javascript": "JavaScript",
        "content-json-empty-object": "Oggettu vacanti",
        "content-json-empty-array": "Vitturi vacanti",
+       "duplicate-args-warning": "<strong>Accura:</strong> [[:$1]] sta' chiamannu [[:$2]] cu' cchiu' ssai dûn valuri comu paràmitru \"$3\". Sarravi adupiratu sulu l'ùrtimu valuri spicificatu.",
        "duplicate-args-category": "Pàggini chi' cuntènunu chiamati a' template cu' argumenti duplicati",
        "duplicate-args-category-desc": "A pàggina cunteni chiamati a' template chi' adòpirunu argumenti duplicati, comu <code><nowiki>{{pippu|plutu=1|plutu=2}}</nowiki></code> o <code><nowiki>{{pippu|plutu|1=papirinu}}</nowiki></code>.",
        "expensive-parserfunction-warning": "Accura: Sta pàggina cunteni troppi chiamati ê parser functions.\n\nAvissi a èssiri menu di $2, ô mumentu ci {{PLURAL:$1|nn'è $1|nni sunnu $1}}.",
        "userrights-lookup-user": "Gistisci li gruppi di l'utenti",
        "userrights-user-editname": "Metti nu nomu utenti:",
        "editusergroup": "Cancia li gruppi di l'utenti",
-       "editinguser": "Canciamentu dî dritti di l'utenti <strong>[[User:$1|$1]]</strong> $2",
+       "editinguser": "Stai canciannu li dritti di {{GENDER:$1|l'utenti}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Cancia li gruppi di l'utenti",
        "saveusergroups": "Sarva li gruppi di l'utenti",
        "userrights-groupsmember": "Membru di:",
        "uploaddisabledtext": "Li carricamenti dî file sunnu disattivati.",
        "php-uploaddisabledtext": "Li carricamenti dî file sunnu disattivati ntô PHP.\nPi' favuri cuntrolla la mpustazzioni file_uploads.",
        "uploadscripted": "Stu file cunteni còdici HTML o di script, ca putissi èssiri ntirpitratu erruniamenti d'un browser web.",
+       "upload-scripted-pi-callback": "Nun si ponnu carricari file ca cuntènunu l'istruzzioni di prucessu xml-stylesheet.",
+       "uploaded-script-svg": "Fu' truvatu n'elimentu prugrammàbbili \"$1\" ntô file SVG carricatu.",
+       "uploaded-hostile-svg": "Fu' truvatu còdici CSS risicusu nta l'elimentu style dû file SVG carricatu.",
+       "uploaded-event-handler-on-svg": "Nun è cunsintutu di mpustari l'attribbuti gistura di eventi <code>$1=\"$2\"</code> ntê file SVG.",
+       "uploaded-href-attribute-svg": "Nun sunnu cunsintuti l'attribbuti href <code>&lt;$1 $2=\"$3\"&gt;</code> cu na distinazzioni ca nun è lucali (ad esempiu http://, javascript:, etc) ntê file SVG.",
+       "uploaded-href-unsafe-target-svg": "Fu' truvatu n'href cu na distinazzioni risicusa <code>&lt;$1 $2=\"$3\"&gt;</code> ntô file SVG carricatu.",
+       "uploaded-animate-svg": "Fu' truvata n'etichetta \"animate\" ca purrìa canciari href, adupirannu l'attribbutu \"from\" <code>&lt;$1 $2=\"$3\"&gt;</code>, ntô file SVG carricatu.",
+       "uploaded-setting-event-handler-svg": "Lu mpustari l'attribbuti dî gistura di l'eventi veni bluccatu; fu' truvatu <code>&lt;$1 $2=\"$3\"&gt;</code> ntô file SVG carricatu.",
+       "uploaded-setting-href-svg": "L'adupirari l'etichetta \"set\" p'agghiùnciri n'attribbutu \"href\" a' l'elimentu ginituri veni bluccatu.",
+       "uploaded-wrong-setting-svg": "L'adupirari l'etichetta \"set\" p'agghiùnciri na distinazzioni rimota/data/script a n'attribbutu quali è jè, è bluccatu. Fu' truvatu <code>&lt;set to=\"$1\"&gt;</code> ntô file SVG carricatu.",
+       "uploaded-setting-handler-svg": "Lu còdici SVG ca mposta l'attribbutu \"handler\" a na distinazzioni rimota/data/script veni bluccatu. Fu' truvatu <code>$1=\"$2\"</code> ntô file SVG carricatu.",
+       "uploaded-remote-url-svg": "Lu còdici SVG ca mposta n'attribbutu style quali è jè versu di n'URL rimota veni bluccatu. Fu' truvatu <code>$1=\"$2\"</code> ntô file SVG carricatu.",
+       "uploaded-image-filter-svg": "Fu' truvatu nu filtru di mmàggini cu' URL <code>&lt;$1 $2=\"$3\"&gt;</code> ntô file SVG carricatu.",
        "uploadscriptednamespace": "Stu file SVG cunteni nu namespace nun cunsintutu, \"$1\".",
        "uploadinvalidxml": "L'XML ntô file carricatu nun potti èssiri analizzatu sintatticamenti.",
        "uploadvirus": "Lu file cunteni un virus!\nDittagghî: $1",
        "unusedimages": "File nun usati",
        "wantedcategories": "Catigurìi addumannati",
        "wantedpages": "Pàggini addumannati",
-       "wantedpages-summary": "Elencu dî pàggini chi' nun esìstunu però hannu lu maggiuri nùmmiru di ligami versu d'iddi, lassannu stari li pàggini chi' comu ligami versu d'iddi hannu sulu rimanni. P'aviri n'elencu dî pàggini chi nun esìstunu però hannu rimanni versu d'iddi, talìa [[{{#special:BrokenRedirects}}]].",
+       "wantedpages-summary": "Elencu dî pàggini ca nun esìstunu però hannu lu maggiuri nùmmiru di ligami versu d'iddi, lassannu stari li pàggini chi' comu ligami versu d'iddi hannu sulu rimanni. P'aviri n'elencu dî pàggini ca nun esìstunu però hannu rimanni versu d'iddi, talìa [[{{#special:BrokenRedirects}}|l'elencu dî rimanni rutti]].",
        "wantedpages-badtitle": "Tìtulu nun vàlidu ntô gruppu di risultati: $1",
        "wantedfiles": "File addumannati",
        "wantedfiletext-cat": "Li file ccassutta su' richiamati però nun esìstunu. Ntâ lista cci ponnu èssiri macari li file chi' stannu nta dipòsiti esterni, cu' tuttu chi' esìstunu. Sti fausi pusitivi sarrannu <del>sbarrati</del>. Sparti, li pàggini chi' nclùdunu file chi' nun esìstunu su' elincati nta [[:$1]].",
        "tags-update-remove-not-allowed-multi": "Nun è cunsintutu di livari {{PLURAL:$2|st'etichetta|st'etichetti}} a' manu: $1",
        "tags-edit-title": "Canciamentu di l'etichetti",
        "tags-edit-manage-link": "Gistisci l'etichetti",
+       "tags-edit-revision-selected": "{{PLURAL:$1|Virsioni scigghiuta|Virsioni scigghiuti}} di [[:$2]]:",
+       "tags-edit-logentry-selected": "{{PLURAL:$1|Eventu dû riggistru scigghiutu|Eventi dû riggistru scigghiuti}}:",
        "tags-edit-revision-legend": "Agghiunci o leva etichetti di {{PLURAL:$1|sta virsioni|tutti li $1 virsioni}}",
+       "tags-edit-logentry-legend": "Agghiunci o leva etichetti di {{PLURAL:$1|sta vuci di riggistru|tutti li $1 vuci di riggistru}}",
        "tags-edit-existing-tags": "Etichetti esistenti:",
+       "tags-edit-existing-tags-none": "''Nudda''",
        "tags-edit-new-tags": "Etichetti novi:",
        "tags-edit-add": "Agghiunci st'etichetti:",
        "tags-edit-remove": "Leva st'etichetti:",
+       "tags-edit-remove-all-tags": "(leva tutti l'etichetti)",
        "tags-edit-chosen-placeholder": "Scegghî quarchi etichetta",
+       "tags-edit-chosen-no-results": "Nun si truvàu nudda etichetta ca currispunni",
+       "tags-edit-reason": "Mutivu:",
+       "tags-edit-revision-submit": "Àpplica li canciamenti a' {{PLURAL:$1|sta virsioni|$1 virsioni}}",
+       "tags-edit-logentry-submit": "Àpplica li canciamenti a' {{PLURAL:$1|sta vuci di riggistru|$1 vuci di riggistru}}",
+       "tags-edit-success": "Li canciamenti furu applicati boni.",
+       "tags-edit-failure": "Nun si pòttiru applicari li canciamenti:\n$1",
+       "tags-edit-nooldid-title": "Virsioni oggettu nun vàlida",
+       "tags-edit-nooldid-text": "O nun spicificasti nudda virsioni comu oggettu di sta funzioni, o puru la virsioni spicificata nun esisti.",
        "tags-edit-none-selected": "Pi' favuri scegghi ammenu una etichetta, di agghiùnciri o livari.",
        "comparepages": "Cunfrunta pàggini",
        "compare-page1": "Pàggina 1",
        "revdelete-uname-unhid": "nomu utenti ammustratu",
        "revdelete-restricted": "misi ristrizzioni pi' l'amministratura",
        "revdelete-unrestricted": "livati ristrizzioni pi' l'amministratura",
+       "logentry-block-block": "$1 {{GENDER:$2|bluccàu}} a' {{GENDER:$4|$3}} cûn tempu di scadenza di $5 $6",
+       "logentry-block-unblock": "$1 {{GENDER:$2|sbluccàu}} a' {{GENDER:$4|$3}}",
+       "logentry-block-reblock": "$1 {{GENDER:$2|canciàu}} li mpustazzioni dû bloccu di {{GENDER:$4|$3}} cûn tempu di scadenza di $5 $6",
+       "logentry-suppress-block": "$1 {{GENDER:$2|bluccàu}} a' {{GENDER:$4|$3}} cûn tempu di scadenza di $5 $6",
+       "logentry-suppress-reblock": "$1 {{GENDER:$2|canciàu}} li mpustazzioni dû bloccu di {{GENDER:$4|$3}} cûn tempu di scadenza di $5 $6",
+       "logentry-import-upload": "$1 {{GENDER:$2|impurtàu}} $3 pi' menzu dûn carricamentu di file",
+       "logentry-import-interwiki": "$1 {{GENDER:$2|impurtàu}} $3 di n'autra wiki",
        "logentry-merge-merge": "$1 {{GENDER:$2|juncìu}} $3 nta $4 (virsioni nfina ô $5)",
        "logentry-move-move": "$1 {{GENDER:$2|spustau}} la pàggina $3 nti $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|spustau}} a pàggina $3 nta $4 senza lassari nu rimannu",
        "logentry-upload-revert": "$1 {{GENDER:$2|carricau}} $3",
        "log-name-managetags": "Riggistru dâ gistioni di l'etichetti",
        "log-description-managetags": "Sta pàggina elenca l'opirazzioni amministrativi chi' riguàrdunu l'[[Special:Tags|etichetti]]. Lu riggìstru cunteni sulu l'azzioni chi' furu fatti a' manu di n'amministraturi; l'etichetti ponnu puru èssiri criati e cancillati dû software dâ wiki senza lassari vuci nta stu riggistru.",
+       "logentry-managetags-create": "$1 {{GENDER:$2|criàu}} l'etichetta «$4»",
        "logentry-managetags-delete": "$1 {{GENDER:$2|cancillàu}} l'etichetta \"$4\" (livata di $5 {{PLURAL:$5|virsioni o vuci di riggistru}})",
+       "logentry-managetags-activate": "$1 {{GENDER:$2|attivàu}} l'etichetta «$4» pi' l'usu di utenti e bot",
        "logentry-managetags-deactivate": "$1 {{GENDER:$2|disattivàu}} l'etichetta \"$4\" pi' l'usu di l'utenti e dî bot",
+       "log-name-tag": "Riggistru di l'etichetti",
        "log-description-tag": "Sta pàggina ammustra quannu l'utenti agghiunciunu o lèvunu [[Special:Tags|etichetti]] di sìnguli virsioni o vuci di riggistru. Stu riggistru nun rifirisci li variazzioni di l'etichetti chi' succèdunu ntô cuntestu di nu canciamentu, na cancillazzioni, o n'autra opirazzioni comu a' chisti.",
+       "logentry-tag-update-add-revision": "$1 {{GENDER:$2|agghiuncìu}} l'{{PLURAL:$7|etichetta|etichetti}} $6 â virsioni $4 dâ pàggina $3",
        "logentry-tag-update-add-logentry": "$1 {{GENDER:$2|agghiuncìu}} {{PLURAL:$7|l'etichetta|l'etichetti}} $6 â vuci di riggistru $5 dâ pàggina $3",
        "logentry-tag-update-remove-revision": "$1 {{GENDER:$2|livàu}} {{PLURAL:$9|l'etichetta|l'etichetti}} $8 dâ virsioni $4 dâ pàggina $3",
+       "logentry-tag-update-remove-logentry": "$1 {{GENDER:$2|livàu}} l'{{PLURAL:$9|etichetta|etichetti}} $8 dâ vuci di riggistru $5 dâ pàggina $3",
        "logentry-tag-update-revision": "$1 {{GENDER:$2|canciàu}} l'etichetti dâ virsioni $4 dâ pàggina $3 ({{PLURAL:$7|nn'agghiuncìu}} $6; {{PLURAL:$9|nni livàu}} $8)",
+       "logentry-tag-update-logentry": "$1 {{GENDER:$2|aggiurnàu}} l'etichetti ntâ vuci di riggistru $5 dâ pàggina $3 ({{PLURAL:$7|nn'agghiuncìu}} $6; {{PLURAL:$9|nni livàu}} $8)",
        "rightsnone": "(nuddu)",
        "revdelete-summary": "riassuntu dô canciamentu",
        "feedback-adding": "Agghiuncimentu dû cummentu â pàggina...",
+       "feedback-back": "Arreti",
        "feedback-bugcheck": "Bonu! Sulu cuntrolla chi' nun è unu dî [$1 bug già canusciuti].",
        "feedback-bugnew": "Cuntrullai. Signala nu bug novu",
        "feedback-bugornote": "Si' si' bonu a' discrìviri un prubblema tècnicu di manera dittagghiata, pi' favuri [$1 signala nu bug].\nSi' no, poi adupirari u mòdulu facilitatu ccassutta. U to cummentu sarravi agghiunciutu â pàggina \"[$3 $2]\", nsemmula ô to nomu utenti.",
        "feedback-cancel": "Annulla",
        "feedback-close": "Finutu",
+       "feedback-external-bug-report-button": "Signala nu prubblema tècnicu",
+       "feedback-dialog-title": "Lassa nu cummentu",
+       "feedback-dialog-intro": "Poi adupirari stu mòdulu facili ccassutta pi' lassari lu to cummentu. Stu cummentu sarravi agghiunciutu â pàggina «$1», nsemmula ô to nomu utenti.",
+       "feedback-error-title": "Erruri",
        "feedback-error1": "Erruri: Risultatu di l'API nun ricanusciutu",
        "feedback-error2": "Erruri: A mudìfica nun riniscìu",
        "feedback-error3": "Erruri: Nudda risposta di l'API",
+       "feedback-error4": "Erruri: Nun fu' pussìbbili pubblicari ntô tìtulu dû cummentu spicificatu",
        "feedback-message": "Missaggiu:",
        "feedback-subject": "Oggettu:",
        "feedback-submit": "Manna",
+       "feedback-terms": "Capisciu ca li nfurmazzioni dû me aggenti di l'utenti cuntènunu li virsioni pricisi dû me browser e dû me sistema opirativu, e ca sti nfurmazzioni sarrannu cunnivisi pubblicamenti a' latu dû me cummentu.",
+       "feedback-termsofuse": "Accunsentu a' lassari lu me cummentu in cuncurdanza cu' li Cunnizzioni d'Usu.",
        "feedback-thanks": "Grazzî! U to cummentu fu' affissu ntâ pàggina \"[$2 $1]\".",
        "feedback-thanks-title": "Ti ringrazziamu!",
        "feedback-useragent": "Aggenti di l'utenti:",
        "log-name-pagelang": "Riggistru dî canci di lingua",
        "log-description-pagelang": "Chistu è nu riggistru dî canciamenti â lingua dî pàggini.",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2|canciau}} a lingua dâ pàggina $3 di $4 a' $5.",
-       "default-skin-not-found": "Whoops! La peddi pridifinuta dâ to wiki, mpustata nta <code dir=\"ltr\">$wgDefaultSkin</code> comu <code>$1</code>, nun è dispunìbbili.\n\nA' quantu pari la to istallazzioni ncludi {{PLURAL:$4|la peddi|li peddi}} ccasutta. Talìa [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manuali: Cunfigurazzioni dî peddi] p'istruzzioni supra a' comu {{PLURAL:$4|s'attìva|s'attìvunu e comu si scegghî chidda pridifinuta}}.\n\n$2\n\n; Si' hai istallatu MediaWiki ora ora:\n: E' prubbàbbili chi' l'istallasti dû git, o direttamenti dû còdici surgenti nta quarchi' n'autra manera. Allura sta cosa è privista. Prova e istalla quarchi' peddi di [https://www.mediawiki.org/wiki/Category:All_skins l'archìviu dî peddi di mediawiki.org], a na manera di chisti:\n:* Scàrrica [https://www.mediawiki.org/wiki/Download lu prugramma d'istallazzioni in furmatu tar], chi' cunteni tanti peddi ed estinsioni. Poi cupiari e ncuddari la cartella <code>skins/</code> di ddadintra.\n:* Scàrrica a' una a' una quarchi' peddi in furmatu tar di [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* Clona via git unu dî dipòsiti <code>mediawiki/skins/*</code> ntâ cartella <code dir=\"ltr\">skins/</code> dâ to istallazzioni di MediaWiki.\n: Fari accussì' nun avissi a' ntirfirìri cû to dipòsitu git si' si' nu sviluppaturi di MediaWiki.\n\n; Si' hai aggiurnatu MediaWiki ora ora:\n: MediaWiki virsioni 1.24 e succissivi nun attìvunu cchiu' di manera autumàtica i peddi istallati (talìa [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manüali: Scuperta autumatica dî peddi]). Poi cupiari e ncuddari {{PLURAL:$5|sta riga|sti righi}} nta <code>LocalSettings.php</code> p'attivari {{PLURAL:$5|la peddi chi' pi' com'ora è istallata|tutti li peddi chi' pi' com'ora sunnu istallati}}:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Si' hai mudificatu <code>LocalSettings.php</code> ora ora:\n: Cuntrolla chi' nun sbagghiasti a' scrìviri li nomi dî peddi.",
-       "default-skin-not-found-no-skins": "Whoops! La peddi pridifinuta dâ to wiki, mpustata nta <code dir=\"ltr\">$wgDefaultSkin</code> comu <code>$1</code>, nun è dispunìbbili.\n\nNun hai nudda peddi installata.\n\n; Si' hai installatu o puru aggiurnatu MediaWiki ora ora:\n: E' prubbàbbili chi' l'installasti dû git, o direttamenti dû còdici surgenti nta quarchi' autra manera. Allura sta cosa è privista. MediaWiki virsioni 1.24 e succissivi nun cuntènunu nudda peddi ntô dipòsitu principali. Prova e installa quarchi' peddi di [https://www.mediawiki.org/wiki/Category:All_skins l'archìviu dî peddi di mediawiki.org], a na manera di chisti:\n:* Scàrrica [https://www.mediawiki.org/wiki/Download u prugramma d'installazzioni in furmatu tar], chi' cunteni tanti peddi ed estinsioni. Poi cupiari e ncuddari a cartella <code>skins/</code> di ddadintra.\n:* Scàrrica a' una a' una quarchi' peddi in furmatu tar di [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* Clona via git unu dî dipòsiti <code>mediawiki/skins/*</code> ntâ cartella <code dir=\"ltr\">skins/</code> dâ to installazzioni di MediaWiki.\n: Fari accussì' nun avissi a' ntirfirìri cû to dipòsitu git si' si' nu sviluppaturi di MediaWiki. Talìa [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manüali: Cunfigurazzioni dî peddi] p'istruzzioni supra a' comu s'attìvunu i peddi e comu si scegghî chidda pridifinuta.",
+       "default-skin-not-found": "Whoops! La peddi pridifinuta dâ to wiki, mpustata nta <code dir=\"ltr\">$wgDefaultSkin</code> comu <code>$1</code>, nun è dispunìbbili.\n\nA' quantu pari la to istallazzioni ncludi {{PLURAL:$4|la peddi|li peddi}} ccasutta. Talìa [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manuali: Cunfigurazzioni dî peddi] p'istruzzioni supra a' comu {{PLURAL:$4|s'attìva|s'attìvunu e comu si scegghî chidda pridifinuta}}.\n\n$2\n\n; Si' hai istallatu MediaWiki ora ora:\n: E' prubbàbbili ca l'istallasti dû git, o direttamenti dû còdici surgenti nta quarchi' n'autra manera. Allura sta cosa è privista. Prova e istalla quarchi' peddi di [https://www.mediawiki.org/wiki/Category:All_skins l'archìviu dî peddi di mediawiki.org], a na manera di chisti:\n:* Scàrrica [https://www.mediawiki.org/wiki/Download lu prugramma d'istallazzioni in furmatu tar], ca cunteni tanti peddi ed estinsioni. Poi cupiari e ncuddari la cartella <code>skins/</code> di ddadintra.\n:* Scàrrica a' una a' una quarchi' peddi ntô furmatu tar di [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Adòpira Git pi' scarricari peddi].\n: Lu fari accussi' nun avissi a' ntirfirìri cû to dipòsitu git si' si' nu sviluppaturi di MediaWiki.\n\n; Si' hai aggiurnatu MediaWiki ora ora:\n: MediaWiki virsioni 1.24 e succissivi nun attìvunu cchiu' di manera autumàtica li peddi istallati (talìa [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manüali: Scuperta autumatica dî peddi]). Poi cupiari e ncuddari {{PLURAL:$5|sta riga|sti righi}} nta <code>LocalSettings.php</code> p'attivari {{PLURAL:$5|la peddi ca è istallata pi' com'ora|tutti li peddi ca sunnu istallati pi' com'ora}}:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Si' hai mudificatu <code>LocalSettings.php</code> ora ora:\n: Cuntrolla ca nun sbagghiasti a' scrìviri li nomi dî peddi.",
+       "default-skin-not-found-no-skins": "Whoops! La peddi pridifinuta dâ to wiki, mpustata nta <code dir=\"ltr\">$wgDefaultSkin</code> comu <code>$1</code>, nun è dispunìbbili.\n\nNun hai nudda peddi installata.\n\n; Si' hai installatu o puru aggiurnatu MediaWiki ora ora:\n: E' prubbàbbili chi' l'installasti dû git, o direttamenti dû còdici surgenti nta quarchi' autra manera. Allura sta cosa è privista. MediaWiki virsioni 1.24 e succissivi nun cuntènunu nudda peddi ntô dipòsitu principali. Prova e installa quarchi' peddi di [https://www.mediawiki.org/wiki/Category:All_skins l'archìviu dî peddi di mediawiki.org], a na manera di chisti:\n:* Scàrrica [https://www.mediawiki.org/wiki/Download u prugramma d'installazzioni in furmatu tar], chi' cunteni tanti peddi ed estinsioni. Poi cupiari e ncuddari a cartella <code>skins/</code> di ddadintra.\n:* Scàrrica a' una a' una quarchi' peddi in furmatu tar di [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Adòpira Git pi' scarricari peddi].\n: Fari accussì' nun avissi a' ntirfirìri cû to dipòsitu git si' si' nu sviluppaturi di MediaWiki. Talìa [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manüali: Cunfigurazzioni dî peddi] p'istruzzioni supra a' comu s'attìvunu i peddi e comu si scegghî chidda pridifinuta.",
        "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (attivata)",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 ('''disattivata''')",
        "mediastatistics": "Statìstichi supra ê file multimidiali",
index 9d0daf9..02c6f54 100644 (file)
@@ -59,7 +59,8 @@
                        "Green Zero",
                        "Macofe",
                        "Alex Blokha",
-                       "Lxlalexlxl"
+                       "Lxlalexlxl",
+                       "Капитан Джон Шепард"
                ]
        },
        "tog-underline": "Підкреслювання посилань:",
        "content-model-css": "CSS",
        "content-json-empty-object": "Порожній об'єкт",
        "content-json-empty-array": "Порожній масив",
+       "duplicate-args-warning": "<strong>Увага:</strong> [[:$1]] викликає [[:$2]] з більш ніж одним значенням параметра «$3». Буде використано лише останнє вказане значення.",
        "duplicate-args-category": "Сторінки, що містять шаблон із кількома значеннями одного й того ж параметра",
        "duplicate-args-category-desc": "Тут перелічено сторінки, що містять дублікатне визначення аргументу при включенні шаблону, приміром, <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> або <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "expensive-parserfunction-warning": "'''Увага:''' Ця сторінка містить дуже багато викликів ресурсомістких функцій.\n\nКількість викликів не повинна перевищувати $2, зараз потрібно зробити $1 {{PLURAL:$1|виклик|виклики|викликів}}.",
        "uploaddisabledtext": "Можливість завантаження файлів відключена.",
        "php-uploaddisabledtext": "Завантаження файлів вимкнене у налаштуваннях PHP. Будь ласка, перевірте значення file_uploads.",
        "uploadscripted": "Файл містить HTML-код або скрипт, який може неправильно обробитися браузером.",
+       "upload-scripted-pi-callback": " \t\nНеможливо завантажити файл, що містить інструкції опрацювання таблиці стилів XML.",
+       "uploaded-script-svg": " \t\t\nЗнайдений небезпечний елемент з підтримкою сценаріїв «$1» в завантаженому файлі SVG.",
+       "uploaded-hostile-svg": " \t\nЗнайдений небезпечний CSS-код в елементі стилю завантаженого файлу SVG.",
+       "uploaded-event-handler-on-svg": " \t\nУстановка атрибутів обробника подій <code>$1=\"$2\"</code> не дозволено для SVG-файлів.",
+       "uploaded-href-attribute-svg": " \t\t\nУ SVG-файлів не допускаються href атрибути <code>&lt;$1 $2=\"$3\"&gt;</code> з засобів перехресного локального обміну повідомленнями з метою (т. е. http:// javascript:, тощо).",
        "uploadscriptednamespace": "Цей SVG-файл містить недопустимий простір імен \"$1\"",
        "uploadinvalidxml": "Не вдалося проаналізувати XML у завантаженому файлі.",
        "uploadvirus": "Файл містить вірус! Див. $1",
index 5703654..57c5c02 100644 (file)
        "content-model-css": "CSS",
        "content-json-empty-object": "空物件",
        "content-json-empty-array": "空陣列",
+       "duplicate-args-warning": "<strong>警告:</strong> [[:$1]] 呼叫 [[:$2]] 的 \"$3\" 參數使用了超過一次,僅會使用提供的最後一個參數值。",
        "duplicate-args-category": "模板呼叫時使用重複的參數的頁面",
        "duplicate-args-category-desc": "該頁面包含重複使用參數的模板呼叫,如 <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> 或 <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>。",
        "expensive-parserfunction-warning": "<strong>警告:</strong>此頁面使用了太多消耗系統資源的解析函數。\n\n使用次數應小於 $2 次,但目前使用了 $1 次。",
        "php-uploaddisabledtext": "PHP 已停用檔案上傳。\n請檢查 file_uploads 設定。",
        "uploadscripted": "此檔案包含可能會被網頁瀏覽器錯誤執行的 HTML 或 Script。",
        "upload-scripted-pi-callback": "無法上傳包含 xml-stylesheet 處理命令的檔案。",
-       "uploaded-script-svg": "於上傳的 SVG 檔案中找到可程式的腳本標籤 \"$1\"。",
-       "uploaded-hostile-svg": "於上傳的 SVG 檔案的樣式標籤中找到不安全的 CSS。",
+       "uploaded-script-svg": "於上傳的 SVG 檔案中找到可程式的腳本標籤 \"$1\"。",
+       "uploaded-hostile-svg": "於上傳的 SVG 檔案的樣式標籤中找到不安全的 CSS。",
        "uploaded-event-handler-on-svg": "不允許在 SVG 檔案設定 event-handler 屬性 <code>$1=\"$2\"</code>。",
        "uploaded-href-attribute-svg": "不允許在 SVG 檔案中的 Href 屬性 <code>&lt;$1 $2=\"$3\"&gt;</code> 使用非本地的目標  (例如 http://, javascript:, 等)。",
-       "uploaded-href-unsafe-target-svg": "於上傳的 SVG 檔案中找到 href 連結至不安全的目標 <code>&lt;$1 $2=\"$3\"&gt;</code>。",
-       "uploaded-animate-svg": "於上傳的 SVG 檔案中找到 \"animate\" 標籤可能會使用 \"from\" 屬性 <code>&lt;$1 $2=\"$3\"&gt;</code> 更改 href。",
+       "uploaded-href-unsafe-target-svg": "於已上傳的 SVG 檔案中找到 href 連結至不安全的目標 <code>&lt;$1 $2=\"$3\"&gt;</code>。",
+       "uploaded-animate-svg": "於已上傳的 SVG 檔案中找到 \"animate\" 標籤可能會使用 \"from\" 屬性 <code>&lt;$1 $2=\"$3\"&gt;</code> 更改 href。",
+       "uploaded-setting-event-handler-svg": "於已上傳的 SVG 檔案中找到 <code>&lt;$1 $2=\"$3\"&gt;</code>,已禁止設定 event-handler 屬性。",
+       "uploaded-setting-href-svg": "已禁止使用 \"set\" 標籤來加入 \"href\" 屬性至父元素。",
+       "uploaded-wrong-setting-svg": "於已上傳的 SVG 檔案中找到 <code>&lt;set to=\"$1\"&gt;</code>,已禁止使用 \"set\" 標籤加入 remote/data/script 目標至任何屬性。",
+       "uploaded-setting-handler-svg": "於已上傳的 SVG 檔案中找到 <code>$1=\"$2\"</code>,已禁止 SVG 使用 remote/data/script 設定 \"handler\" 屬性。",
+       "uploaded-remote-url-svg": "於已上傳的 SVG 檔案中找到 <code>$1=\"$2\"</code>,已禁止 SVG 使用任何遠端 URL 設定樣式。",
+       "uploaded-image-filter-svg": "於已上傳的 SVG 檔案中找到圖片過濾器使用 URL:<code>&lt;$1 $2=\"$3\"&gt;</code>。",
        "uploadscriptednamespace": "此 SVG 檔案使用了非法的命名空間 \"$1\"。",
        "uploadinvalidxml": "無法解析已上傳檔案中的 XML。",
        "uploadvirus": "該檔案含有病毒!\n詳細資訊:$1",
        "unusedimages": "未使用的檔案",
        "wantedcategories": "需要的分類",
        "wantedpages": "需要的頁面",
-       "wantedpages-summary": "以下為最多連結的不存在頁面,除只有重新導向連結的頁面外。 若要取得不存在的重新導向頁面,請至 [[{{#special:BrokenRedirects}}]]。",
+       "wantedpages-summary": "以下為最多連結的不存在頁面,除只有重新導向連結的頁面外。 若要取得不存在的重新導向頁面,請至 [[{{#special:BrokenRedirects}}|損壞的重新導向清單]]。",
        "wantedpages-badtitle": "結果集合中的標題無效:$1",
        "wantedfiles": "需要的檔案",
        "wantedfiletext-cat": "下列檔案被時用,但檔案不存在。 外部儲存庫的檔案儘管存在,但此清單仍會列出。 這類誤報的項目會以 <del>刪除線</del> 標示。 另外,頁面內嵌檔案不存在會於清單 [[:$1]] 中顯示。",
        "log-name-pagelang": "更改語言日誌",
        "log-description-pagelang": "此頁為頁面語言的變更日誌。",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2|已更改}}頁面 $3 的語言從 $4 到 $5。",
-       "default-skin-not-found": "哎呀!您於 <code dir=\"ltr\">$wgDefaultSkin</code> 設定的 Wiki 預設外觀 <code>$1</code> 無法使用。\n\n您的安裝程序應包含以下{{PLURAL:$4|外觀}}。請參考 [https://www.mediawiki.org/wiki/Manual:Skin_configuration 操作手冊:外觀設定] 以取得如何{{PLURAL:$4|開啟外觀並設為預設值}}的資訊。\n\n$2\n\n; 若您才剛安裝完 MediaWiki:\n: 您大概是使用 git 或直接透過原始碼使用其他方法安裝,這種情況是正常的。請嘗試安裝 [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org 的外觀目錄] 中的部份外觀使用以下方式:\n:* 下載 [https://www.mediawiki.org/wiki/Special:MyLanguage/Download tarball 安裝程式],該程式包含數個外觀與擴充套件。您可以複製並貼上至 <code>skins/</code> 目錄。\n:* 自 [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org] 下載個別外觀 tarball。\n:* 透過 git 複製 <code>mediawiki/skins/*</code> 儲存庫中其中一個外觀到您安裝的 MediaWiki <code dir=\"ltr\">skins/</code> 目錄中。\n: 若您是 MediaWiki 的開發人員,這麼做應該不會影響到您的 git 儲存庫。\n\n; 若您才剛升級 MediaWiki:\n: MediaWiki 1.24 與較新的版本不再自動開啟已安裝的外觀 (請參考 [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery 操作手冊:外觀自動搜尋])。您可以將下列{{PLURAL:$5|行}}貼上至 <code>LocalSettings.php</code> 來開啟{{PLURAL:$5|所有}}目前已經安裝的{{PLURAL:$5|外觀}}:\n\n<pre dir=\"ltr\">$3</pre>\n\n; 若您才剛修改 <code>LocalSettings.php</code>:\n: 請再次確認您輸入的外觀名稱是否有誤。",
-       "default-skin-not-found-no-skins": "哎呀!您於 <code>$wgDefaultSkin</code> 設定的 Wiki 預設外觀 <code>$1</code> 無法使用。\n\n您未安裝任何的外觀。\n\n; 若您才剛安裝完或升級完 MediaWiki:\n: 您大概是使用 git 或直接透過原始碼使用其他方法安裝,這種情況是正常的。 MediaWiki 1.24 或較新的版本在主要儲存庫中不再包含任何的外觀。 請嘗試安裝 [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org 的外觀目錄] 中的部份外觀使用以下方式:\n:* 下載 [https://www.mediawiki.org/wiki/Special:MyLanguage/Download tarball 安裝程式],該程式包含數個外觀與擴充套件。 您可以複製並貼上至 <code>skins/</code> 目錄。\n:* 自 [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org] 下載個別外觀 tarball。\n:* 透過 git 複製 <code>mediawiki/skins/*</code> 儲存庫中其中一個外觀到您安裝的 MediaWiki <code dir=\"ltr\">skins/</code> 目錄中。\n: 若您是 MediaWiki 的開發人員,這麼做應該不會影響到您的 git 儲存庫。 請參考 [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Skin_configuration 操作手冊:外觀設定] 以取得如何開啟外觀並設為預設值的資訊。",
+       "default-skin-not-found": "哎呀!您於 <code dir=\"ltr\">$wgDefaultSkin</code> 設定的 Wiki 預設外觀 <code>$1</code> 無法使用。\n\n您的安裝程序應包含以下{{PLURAL:$4|外觀}}。請參考 [https://www.mediawiki.org/wiki/Manual:Skin_configuration 操作手冊:外觀設定] 以取得如何{{PLURAL:$4|開啟外觀並設為預設值}}的資訊。\n\n$2\n\n; 若您才剛安裝完 MediaWiki:\n: 您大概是使用 git 或直接透過原始碼使用其他方法安裝,這種情況是正常的。請嘗試安裝 [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org 的外觀目錄] 中的部份外觀使用以下方式:\n:* 下載 [https://www.mediawiki.org/wiki/Special:MyLanguage/Download tarball 安裝程式],該程式包含數個外觀與擴充套件。您可以複製並貼上至 <code>skins/</code> 目錄。\n:* 自 [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org] 下載個別外觀 tarball。\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins 使用 Git 下載外觀]。\n: 若您是 MediaWiki 的開發人員,這麼做應該不會影響到您的 git 儲存庫。\n\n; 若您才剛升級 MediaWiki:\n: MediaWiki 1.24 與較新的版本不再自動開啟已安裝的外觀 (請參考 [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery 操作手冊:外觀自動搜尋])。您可以將下列{{PLURAL:$5|行}}貼上至 <code>LocalSettings.php</code> 來開啟{{PLURAL:$5|所有}}目前已經安裝的{{PLURAL:$5|外觀}}:\n\n<pre dir=\"ltr\">$3</pre>\n\n; 若您才剛修改 <code>LocalSettings.php</code>:\n: 請再次確認您輸入的外觀名稱是否有誤。",
+       "default-skin-not-found-no-skins": "哎呀!您於 <code>$wgDefaultSkin</code> 設定的 Wiki 預設外觀 <code>$1</code> 無法使用。\n\n您未安裝任何的外觀。\n\n; 若您才剛安裝完或升級完 MediaWiki:\n: 您大概是使用 git 或直接透過原始碼使用其他方法安裝,這種情況是正常的。 MediaWiki 1.24 或較新的版本在主要儲存庫中不再包含任何的外觀。 請嘗試安裝 [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org 的外觀目錄] 中的部份外觀使用以下方式:\n:* 下載 [https://www.mediawiki.org/wiki/Special:MyLanguage/Download tarball 安裝程式],該程式包含數個外觀與擴充套件。 您可以複製並貼上至 <code>skins/</code> 目錄。\n:* 自 [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org] 下載個別外觀 tarball。\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins 使用 Git 下載外觀]。\n: 若您是 MediaWiki 的開發人員,這麼做應該不會影響到您的 git 儲存庫。 請參考 [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Skin_configuration 操作手冊:外觀設定] 以取得如何開啟外觀並設為預設值的資訊。",
        "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (已開啟)",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 ('''已停用''')",
        "mediastatistics": "媒體統計資訊",
diff --git a/maintenance/deleteArchivedFiles.inc b/maintenance/deleteArchivedFiles.inc
deleted file mode 100644 (file)
index 0c0b34a..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-/**
- * Core functions for deleteArchivedFiles.php
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Maintenance
- */
-
-/**
- * Core functions for deleteArchivedFiles.php
- *
- * @ingroup Maintenance
- */
-class DeleteArchivedFilesImplementation {
-       public static function doDelete( $output, $force ) {
-               # Data should come off the master, wrapped in a transaction
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->begin( __METHOD__ );
-               $tbl_arch = $dbw->tableName( 'filearchive' );
-               $repo = RepoGroup::singleton()->getLocalRepo();
-               # Get "active" revisions from the filearchive table
-               $output->handleOutput( "Searching for and deleting archived files...\n" );
-               $res = $dbw->query( "SELECT fa_id,fa_storage_group,fa_storage_key,fa_sha1 FROM $tbl_arch" );
-               $count = 0;
-               foreach ( $res as $row ) {
-                       $key = $row->fa_storage_key;
-                       if ( !strlen( $key ) ) {
-                               $output->handleOutput( "Entry with ID {$row->fa_id} has empty key, skipping\n" );
-                               continue;
-                       }
-                       $group = $row->fa_storage_group;
-                       $id = $row->fa_id;
-                       $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
-                       if ( isset( $row->fa_sha1 ) ) {
-                               $sha1 = $row->fa_sha1;
-                       } else {
-                               // old row, populate from key
-                               $sha1 = LocalRepo::getHashFromKey( $key );
-                       }
-                       // Check if the file is used anywhere...
-                       $inuse = $dbw->selectField(
-                               'oldimage',
-                               '1',
-                               array(
-                                       'oi_sha1' => $sha1,
-                                       'oi_deleted & ' . File::DELETED_FILE => File::DELETED_FILE
-                               ),
-                               __METHOD__,
-                               array( 'FOR UPDATE' )
-                       );
-                       if ( $path && $repo->fileExists( $path ) && !$inuse ) {
-                               if ( $repo->quickPurge( $path ) ) {
-                                       $count++;
-                                       $dbw->query( "DELETE FROM $tbl_arch WHERE fa_id = $id" );
-                               } else {
-                                       $output->handleOutput( "Unable to remove file $path, skipping\n" );
-                               }
-                       } else {
-                               $output->handleOutput( "Notice - file '$key' not found in group '$group'\n" );
-                               if ( $force ) {
-                                       $output->handleOutput( "Got --force, deleting DB entry\n" );
-                                       $dbw->query( "DELETE FROM $tbl_arch WHERE fa_id = $id" );
-                               }
-                       }
-               }
-               $dbw->commit( __METHOD__ );
-               $output->handleOutput( "Done! [$count file(s)]\n" );
-       }
-}
index 286b1f2..bd8ca10 100644 (file)
@@ -25,7 +25,6 @@
  */
 
 require_once __DIR__ . '/Maintenance.php';
-require_once __DIR__ . '/deleteArchivedFiles.inc';
 
 /**
  * Maintenance script to delete archived (non-current) files from the database.
@@ -40,18 +39,82 @@ class DeleteArchivedFiles extends Maintenance {
                $this->addOption( 'force', 'Force deletion of rows from filearchive' );
        }
 
-       public function handleOutput( $str ) {
-               return $this->output( $str );
-       }
-
        public function execute() {
                if ( !$this->hasOption( 'delete' ) ) {
                        $this->output( "Use --delete to actually confirm this script\n" );
-
                        return;
                }
-               $force = $this->hasOption( 'force' );
-               DeleteArchivedFilesImplementation::doDelete( $this, $force );
+
+               # Data should come off the master, wrapped in a transaction
+               $dbw = $this->getDB( DB_MASTER );
+               $dbw->begin( __METHOD__ );
+               $repo = RepoGroup::singleton()->getLocalRepo();
+
+               # Get "active" revisions from the filearchive table
+               $this->output( "Searching for and deleting archived files...\n" );
+               $res = $dbw->select(
+                       'filearchive',
+                       array( 'fa_id', 'fa_storage_group', 'fa_storage_key', 'fa_sha1' ),
+                       '',
+                       __METHOD__
+               );
+
+               $count = 0;
+               foreach ( $res as $row ) {
+                       $key = $row->fa_storage_key;
+                       if ( !strlen( $key ) ) {
+                               $this->output( "Entry with ID {$row->fa_id} has empty key, skipping\n" );
+                               continue;
+                       }
+
+                       $group = $row->fa_storage_group;
+                       $id = $row->fa_id;
+                       $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
+                       if ( isset( $row->fa_sha1 ) ) {
+                               $sha1 = $row->fa_sha1;
+                       } else {
+                               // old row, populate from key
+                               $sha1 = LocalRepo::getHashFromKey( $key );
+                       }
+
+                       // Check if the file is used anywhere...
+                       $inuse = $dbw->selectField(
+                               'oldimage',
+                               '1',
+                               array(
+                                       'oi_sha1' => $sha1,
+                                       $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE
+                               ),
+                               __METHOD__,
+                               array( 'FOR UPDATE' )
+                       );
+
+                       $needForce = true;
+                       if ( !$repo->fileExists( $path ) ) {
+                               $this->output( "Notice - file '$key' not found in group '$group'\n" );
+                       } elseif ( $inuse ) {
+                               $this->output( "Notice - file '$key' is still in use\n" );
+                       } elseif ( !$repo->quickPurge( $path ) ) {
+                               $this->output( "Unable to remove file $path, skipping\n" );
+                               continue; // don't delete even with --force
+                       } else {
+                               $needForce = false;
+                       }
+
+                       if ( $needForce ) {
+                               if ( $this->hasOption( 'force' ) ) {
+                                       $this->output( "Got --force, deleting DB entry\n" );
+                               } else {
+                                       continue;
+                               }
+                       }
+
+                       $count++;
+                       $dbw->delete( 'filearchive', array( 'fa_id' => $id ), __METHOD__ );
+               }
+
+               $dbw->commit( __METHOD__ );
+               $this->output( "Done! [$count file(s)]\n" );
        }
 }
 
diff --git a/maintenance/deleteArchivedRevisions.inc b/maintenance/deleteArchivedRevisions.inc
deleted file mode 100644 (file)
index ed620ee..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-/**
- * Helper methods for the deleteArchivedRevisions.php maintenance script.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Maintenance
- */
-
-/**
- * Helper methods for the deleteArchivedRevisions.php maintenance script.
- *
- * @ingroup Maintenance
- */
-class DeleteArchivedRevisionsImplementation {
-
-       /**
-        * Perform the delete on archived revisions.
-        * @param object $maint An object (typically of class Maintenance)
-        * that implements two methods: handleOutput() and
-        * purgeRedundantText().  See Maintenance for a description of
-        * those methods.
-        */
-       public static function doDelete( $maint ) {
-               $dbw = wfGetDB( DB_MASTER );
-
-               $dbw->begin( __METHOD__ );
-
-               $tbl_arch = $dbw->tableName( 'archive' );
-
-               # Delete as appropriate
-               $maint->handleOutput( "Deleting archived revisions... " );
-               $dbw->query( "DELETE FROM $tbl_arch" );
-
-               $count = $dbw->affectedRows();
-               $deletedRows = $count != 0;
-
-               $maint->handleOutput( "done. $count revisions deleted.\n" );
-
-               # This bit's done
-               # Purge redundant text records
-               $dbw->commit( __METHOD__ );
-               if ( $deletedRows ) {
-                       $maint->purgeRedundantText( true );
-               }
-       }
-}
index 30883ba..9924eb0 100644 (file)
@@ -25,7 +25,6 @@
  */
 
 require_once __DIR__ . '/Maintenance.php';
-require_once __DIR__ . '/deleteArchivedRevisions.inc';
 
 /**
  * Maintenance script to delete archived (deleted from public) revisions
@@ -41,21 +40,24 @@ class DeleteArchivedRevisions extends Maintenance {
                $this->addOption( 'delete', 'Performs the deletion' );
        }
 
-       public function handleOutput( $str ) {
-               $this->output( $str );
-       }
-
        public function execute() {
-               $this->output( "Delete archived revisions\n\n" );
-               # Data should come off the master, wrapped in a transaction
-               if ( $this->hasOption( 'delete' ) ) {
-                       DeleteArchivedRevisionsImplementation::doDelete( $this );
-               } else {
-                       $dbw = wfGetDB( DB_MASTER );
-                       $res = $dbw->selectRow( 'archive', 'COUNT(*) as count', array(), __FUNCTION__ );
-                       $this->output( "Found {$res->count} revisions to delete.\n" );
+               $dbw = $this->getDB( DB_MASTER );
+
+               if ( !$this->hasOption( 'delete' ) ) {
+                       $count = $dbw->selectField( 'archive', 'COUNT(*)', '', __METHOD__ );
+                       $this->output( "Found $count revisions to delete.\n" );
                        $this->output( "Please run the script again with the --delete option "
                                . "to really delete the revisions.\n" );
+                       return;
+               }
+
+               $this->output( "Deleting archived revisions... " );
+               $dbw->delete( 'archive', '*', __METHOD__ );
+               $count = $dbw->affectedRows();
+               $this->output( "done. $count revisions deleted.\n" );
+
+               if ( $count ) {
+                       $this->purgeRedundantText( true );
                }
        }
 }
index 43e2d87..6fa27ef 100644 (file)
                                        "mw.Feedback.Dialog"
                                ]
                        },
+                       {
+                               "name": "Widgets",
+                               "classes": [
+                                       "mw.widgets*"
+                               ]
+                       },
                        {
                                "name": "Special",
                                "classes": [
index 3571a13..23be767 100644 (file)
@@ -1785,178 +1785,5 @@ return array(
        /* OOjs UI */
        // WARNING: OOjs-UI is NOT TESTED with older browsers and is likely to break
        // if loaded in browsers that don't support ES5
-       'oojs-ui' => array(
-               'scripts' => array(
-                       'resources/lib/oojs-ui/oojs-ui.js',
-               ),
-               'skinScripts' => array(
-                       'default' => 'resources/lib/oojs-ui/oojs-ui-mediawiki.js',
-               ),
-               'dependencies' => array(
-                       'es5-shim',
-                       'oojs',
-                       'oojs-ui.styles',
-                       'oojs-ui.styles.icons',
-                       'oojs-ui.styles.indicators',
-                       'oojs-ui.styles.textures',
-               ),
-               'messages' => array(
-                       'ooui-dialog-message-accept',
-                       'ooui-dialog-message-reject',
-                       'ooui-dialog-process-continue',
-                       'ooui-dialog-process-dismiss',
-                       'ooui-dialog-process-error',
-                       'ooui-dialog-process-retry',
-                       'ooui-outline-control-move-down',
-                       'ooui-outline-control-move-up',
-                       'ooui-outline-control-remove',
-                       'ooui-toolbar-more',
-                       'ooui-toolgroup-collapse',
-                       'ooui-toolgroup-expand',
-               ),
-               'targets' => array( 'desktop', 'mobile' ),
-       ),
-
-       'oojs-ui.styles' => array(
-               'position' => 'top',
-               'styles' => 'resources/src/oojs-ui-local.css', // HACK, see inside the file
-               'skinStyles' => array(
-                       'default' => 'resources/lib/oojs-ui/oojs-ui-mediawiki-noimages.css',
-               ),
-               'targets' => array( 'desktop', 'mobile' ),
-       ),
-
-       'oojs-ui.styles.icons' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.indicators' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'indicators.json',
-       ),
-       'oojs-ui.styles.textures' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'textures.json',
-       ),
-       'oojs-ui.styles.icons-alerts' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-alerts.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-content' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-content.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-editing-advanced' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-editing-advanced.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-editing-core' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-editing-core.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-editing-list' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-editing-list.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-editing-styling' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-editing-styling.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-interactions' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-interactions.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-layout' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-layout.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-location' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-location.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-media' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-media.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-moderation' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-moderation.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-movement' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-movement.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-user' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-user.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-       'oojs-ui.styles.icons-wikimedia' => array(
-               'position' => 'top',
-               'class' => 'ResourceLoaderImageModule',
-               'localBasePath' => "$IP/resources/lib/oojs-ui/themes/mediawiki",
-               'data' => 'icons-wikimedia.json',
-               'selectorWithoutVariant' => '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before',
-               'selectorWithVariant' => '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before',
-       ),
-
+       // @see ResourcesOOUI.php
 );
diff --git a/resources/ResourcesOOUI.php b/resources/ResourcesOOUI.php
new file mode 100644 (file)
index 0000000..37203fb
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+ * Definition of OOjs UI ResourceLoader modules.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+       die( 'Not an entry point.' );
+}
+
+// WARNING: OOjs-UI is NOT TESTED with older browsers and is likely to break
+// if loaded in browsers that don't support ES5
+return call_user_func( function () {
+       // Core default themes
+       $themes = array( 'default' => 'mediawiki' );
+       $themes += ExtensionRegistry::getInstance()->getAttribute( 'SkinOOUIThemes' );
+       $modules = array();
+
+       $modules['oojs-ui'] = array(
+               'scripts' => array(
+                       'resources/lib/oojs-ui/oojs-ui.js',
+               ),
+               'skinScripts' => array_combine(
+                       array_keys( $themes ),
+                       array_map( function ( $theme ) {
+                               // TODO Allow extensions to specify this path somehow
+                               return "resources/lib/oojs-ui/oojs-ui-$theme.js";
+                       }, array_values( $themes ) )
+               ),
+               'dependencies' => array(
+                       'es5-shim',
+                       'oojs',
+                       'oojs-ui.styles',
+                       'oojs-ui.styles.icons',
+                       'oojs-ui.styles.indicators',
+                       'oojs-ui.styles.textures',
+               ),
+               'messages' => array(
+                       'ooui-dialog-message-accept',
+                       'ooui-dialog-message-reject',
+                       'ooui-dialog-process-continue',
+                       'ooui-dialog-process-dismiss',
+                       'ooui-dialog-process-error',
+                       'ooui-dialog-process-retry',
+                       'ooui-outline-control-move-down',
+                       'ooui-outline-control-move-up',
+                       'ooui-outline-control-remove',
+                       'ooui-toolbar-more',
+                       'ooui-toolgroup-collapse',
+                       'ooui-toolgroup-expand',
+               ),
+               'targets' => array( 'desktop', 'mobile' ),
+       );
+       $modules['oojs-ui.styles'] = array(
+               'position' => 'top',
+               'styles' => 'resources/src/oojs-ui-local.css', // HACK, see inside the file
+               'skinStyles' => array_combine(
+                       array_keys( $themes ),
+                       array_map( function ( $theme ) {
+                               // TODO Allow extensions to specify this path somehow
+                               return "resources/lib/oojs-ui/oojs-ui-$theme-noimages.css";
+                       }, array_values( $themes ) )
+               ),
+               'targets' => array( 'desktop', 'mobile' ),
+       );
+
+       $imageSets = array(
+               // Comments for greppability
+               'icons', // oojs-ui.styles.icons
+               'indicators', // oojs-ui.styles.indicators
+               'textures', // oojs-ui.styles.textures
+               'icons-alerts', // oojs-ui.styles.icons-alerts
+               'icons-content', // oojs-ui.styles.icons-content
+               'icons-editing-advanced', // oojs-ui.styles.icons-editing-advanced
+               'icons-editing-core', // oojs-ui.styles.icons-editing-core
+               'icons-editing-list', // oojs-ui.styles.icons-editing-list
+               'icons-editing-styling', // oojs-ui.styles.icons-editing-styling
+               'icons-interactions', // oojs-ui.styles.icons-interactions
+               'icons-layout', // oojs-ui.styles.icons-layout
+               'icons-location', // oojs-ui.styles.icons-location
+               'icons-media', // oojs-ui.styles.icons-media
+               'icons-moderation', // oojs-ui.styles.icons-moderation
+               'icons-movement', // oojs-ui.styles.icons-movement
+               'icons-user', // oojs-ui.styles.icons-user
+               'icons-wikimedia', // oojs-ui.styles.icons-wikimedia
+       );
+       $rootPath = 'resources/lib/oojs-ui/themes';
+
+       foreach ( $imageSets as $name ) {
+               $module = array(
+                       'position' => 'top',
+                       'class' => 'ResourceLoaderOOUIImageModule',
+                       'name' => $name,
+                       'rootPath' => $rootPath,
+               );
+
+               if ( substr( $name, 0, 5 ) === 'icons' ) {
+                       $module['selectorWithoutVariant'] = '.oo-ui-icon-{name}, .mw-ui-icon-{name}:before';
+                       $module['selectorWithVariant'] = '.oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}, .mw-ui-icon-{name}-{variant}:before, .mw-ui-hovericon:hover .mw-ui-icon-{name}-{variant}-hover:before, .mw-ui-hovericon.mw-ui-icon-{name}-{variant}-hover:hover:before';
+               }
+
+               $modules[ "oojs-ui.styles.$name" ] = $module;
+       }
+
+       return $modules;
+} );
index 4829f5f..ac5becb 100644 (file)
@@ -54,6 +54,9 @@
                // we hide the input element as instead we will style the label that follows
                // we use opacity so that VoiceOver software can still identify it
                opacity: 0;
+               // Render "on top of" the label, so that it's still clickable (T98905)
+               z-index: 1;
+               position: relative;
                // ensure the invisible checkbox takes up the required width
                width: @checkboxSize;
                height: @checkboxSize;
index dcc3842..624986a 100644 (file)
@@ -5,7 +5,7 @@
                compile: function ( src ) {
                        return {
                                render: function ( data ) {
-                                       return $.parseHTML( Mustache.render( src, data ) );
+                                       return $( $.parseHTML( Mustache.render( src, data ) ) );
                                }
                        };
                }
index cff2e8f..faa30c3 100644 (file)
@@ -1412,6 +1412,77 @@ class LanguageTest extends LanguageClassesTestCase {
                );
        }
 
+       /**
+        * @dataProvider provideHebrewNumeralsData
+        * @covers Language::hebrewNumeral
+        */
+       public function testHebrewNumeral( $num, $numerals ) {
+               $this->assertEquals(
+                       $numerals,
+                       Language::hebrewNumeral( $num ),
+                       "hebrewNumeral('$num')"
+               );
+       }
+
+       public static function provideHebrewNumeralsData() {
+               return array(
+                       array( -1, -1 ),
+                       array( 0, 0 ),
+                       array( 1, "א'" ),
+                       array( 2, "ב'" ),
+                       array( 3, "ג'" ),
+                       array( 4, "ד'" ),
+                       array( 5, "ה'" ),
+                       array( 6, "ו'" ),
+                       array( 7, "ז'" ),
+                       array( 8, "ח'" ),
+                       array( 9, "ט'" ),
+                       array( 10, "י'" ),
+                       array( 11, 'י"א' ),
+                       array( 14, 'י"ד' ),
+                       array( 15, 'ט"ו' ),
+                       array( 16, 'ט"ז' ),
+                       array( 17, 'י"ז' ),
+                       array( 20, "כ'" ),
+                       array( 21, 'כ"א' ),
+                       array( 30, "ל'" ),
+                       array( 40, "מ'" ),
+                       array( 50, "נ'" ),
+                       array( 60, "ס'" ),
+                       array( 70, "ע'" ),
+                       array( 80, "פ'" ),
+                       array( 90, "צ'" ),
+                       array( 99, 'צ"ט' ),
+                       array( 100, "ק'" ),
+                       array( 101, 'ק"א' ),
+                       array( 110, 'ק"י' ),
+                       array( 200, "ר'" ),
+                       array( 300, "ש'" ),
+                       array( 400, "ת'" ),
+                       array( 500, 'ת"ק' ),
+                       array( 800, 'ת"ת' ),
+                       array( 1000, "א' אלף" ),
+                       array( 1001, "א'א'" ),
+                       array( 1012, "א'י\"ב" ),
+                       array( 1020, "א'ך'" ),
+                       array( 1030, "א'ל'" ),
+                       array( 1081, "א'פ\"א" ),
+                       array( 2000, "ב' אלפים" ),
+                       array( 2016, "ב'ט\"ז" ),
+                       array( 3000, "ג' אלפים" ),
+                       array( 4000, "ד' אלפים" ),
+                       array( 4904, "ד'תתק\"ד" ),
+                       array( 5000, "ה' אלפים" ),
+                       array( 5680, "ה'תר\"ף" ),
+                       array( 5690, "ה'תר\"ץ" ),
+                       array( 5708, "ה'תש\"ח" ),
+                       array( 5720, "ה'תש\"ך" ),
+                       array( 5740, "ה'תש\"ם" ),
+                       array( 5750, "ה'תש\"ן" ),
+                       array( 5775, "ה'תשע\"ה" ),
+               );
+       }
+
        /**
         * @dataProvider providePluralData
         * @covers Language::convertPlural