Merge "Make statsd counts for MWCallableUpdate actually useful"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 22 Jul 2016 23:30:27 +0000 (23:30 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 22 Jul 2016 23:30:27 +0000 (23:30 +0000)
38 files changed:
autoload.php
includes/FileDeleteForm.php
includes/Pingback.php
includes/api/i18n/ko.json
includes/db/Database.php
includes/diff/DifferenceEngine.php
includes/filerepo/file/LocalFile.php
includes/htmlform/HTMLCheckField.php
includes/htmlform/HTMLCheckMatrix.php
includes/htmlform/HTMLForm.php
includes/htmlform/HTMLFormField.php
includes/htmlform/HTMLMultiSelectField.php
includes/installer/DatabaseUpdater.php
includes/installer/WebInstallerName.php
includes/installer/i18n/be-tarask.json
includes/installer/i18n/de.json
includes/installer/i18n/en.json
includes/installer/i18n/fr.json
includes/installer/i18n/gl.json
includes/installer/i18n/he.json
includes/installer/i18n/ko.json
includes/installer/i18n/ksh.json
includes/installer/i18n/qqq.json
includes/installer/i18n/ru.json
includes/installer/i18n/zh-hans.json
includes/libs/objectcache/WANObjectCache.php
includes/parser/Preprocessor_Hash.php
includes/revisiondelete/RevDelList.php
includes/skins/BaseTemplate.php
languages/i18n/be-tarask.json
languages/i18n/cs.json
languages/i18n/dty.json
languages/i18n/hak.json
languages/i18n/ko.json
languages/i18n/lij.json
languages/i18n/pl.json
languages/i18n/ru.json
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php

index 09a2928..8c16adf 100644 (file)
@@ -988,7 +988,6 @@ $wgAutoloadLocalClasses = [
        'PNGMetadataExtractor' => __DIR__ . '/includes/media/PNGMetadataExtractor.php',
        'PPCustomFrame_DOM' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
        'PPCustomFrame_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
-       'PPDAccum_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
        'PPDPart' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
        'PPDPart_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
        'PPDStack' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
index 98f8283..361058b 100644 (file)
@@ -203,7 +203,7 @@ class FileDeleteForm {
                                                $dbw->endAtomic( __METHOD__ );
                                        } else {
                                                // Page deleted but file still there? rollback page delete
-                                               $dbw->rollback( __METHOD__ );
+                                               wfGetLBFactory()->rollbackMasterChanges( __METHOD__ );
                                        }
                                } else {
                                        // Done; nothing changed
index f633029..10d2904 100644 (file)
@@ -115,9 +115,11 @@ class Pingback {
         * as an associative array conforming to the Pingback schema on MetaWiki
         * (<https://meta.wikimedia.org/wiki/Schema:MediaWikiPingback>).
         *
+        * This is public so we can display it in the installer
+        *
         * @return array
         */
-       private function getData() {
+       public function getSystemInfo() {
                $event = [
                        'database'   => $this->config->get( 'DBtype' ),
                        'MediaWiki'  => $this->config->get( 'Version' ),
@@ -136,11 +138,20 @@ class Pingback {
                        $event['memoryLimit'] = $limit;
                }
 
+               return $event;
+       }
+
+       /**
+        * Get the EventLogging packet to be sent to the server
+        *
+        * @return array
+        */
+       private function getData() {
                return [
                        'schema'           => 'MediaWikiPingback',
                        'revision'         => self::SCHEMA_REV,
                        'wiki'             => $this->getOrCreatePingbackId(),
-                       'event'            => $event,
+                       'event'            => $this->getSystemInfo(),
                ];
        }
 
index a54c0b4..1ec5b50 100644 (file)
        "api-help-permissions-granted-to": "{{PLURAL:$1|다음 그룹에 부여됨}}: $2",
        "api-help-open-in-apisandbox": "<small>[연습장에서 열기]</small>",
        "api-help-authmanagerhelper-messageformat": "반환 메시지에 사용할 형식.",
-       "api-credits": "API 개발자:\n* Roan Kattouw (선임 개발자, 2007년 9월–2009년)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (초기 개발자, 선임 개발자 2006년 9월~2007년 9월)\n* Brad Jorsch (선임 개발자 2013년–현재)\n\n당신의 의견이나 제안, 질문은 mediawiki-api@lists.wikimedia.org 로 보내주시거나,\nhttps://phabricator.wikimedia.org/ 에 버그 신고를 해 주시기 바랍니다.."
+       "api-credits": "API 개발자:\n* Roan Kattouw (선임 개발자, 2007년 9월–2009년)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (초기 개발자, 선임 개발자 2006년 9월~2007년 9월)\n* Brad Jorsch (선임 개발자 2013년–현재)\n\n당신의 의견이나 제안, 질문은 mediawiki-api@lists.wikimedia.org 로 보내주시거나,\nhttps://phabricator.wikimedia.org/ 에 버그 보고를 해 주시기 바랍니다."
 }
index f23e24f..9b73584 100644 (file)
@@ -2564,12 +2564,12 @@ abstract class DatabaseBase implements IDatabase {
 
        final public function endAtomic( $fname = __METHOD__ ) {
                if ( !$this->mTrxLevel ) {
-                       throw new DBUnexpectedError( $this, 'No atomic transaction is open.' );
+                       throw new DBUnexpectedError( $this, "No atomic transaction is open (got $fname)." );
                }
                if ( !$this->mTrxAtomicLevels ||
                        array_pop( $this->mTrxAtomicLevels ) !== $fname
                ) {
-                       throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' );
+                       throw new DBUnexpectedError( $this, "Invalid atomic section ended (got $fname)." );
                }
 
                if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) {
index af5fbf3..bd7629a 100644 (file)
  * @ingroup DifferenceEngine
  */
 
-/**
- * Constant to indicate diff cache compatibility.
- * Bump this when changing the diff formatting in a way that
- * fixes important bugs or such to force cached diff views to
- * clear.
- */
+// Deprecated, use class constant instead
 define( 'MW_DIFF_VERSION', '1.11a' );
 
 /**
@@ -34,6 +29,13 @@ define( 'MW_DIFF_VERSION', '1.11a' );
  * @ingroup DifferenceEngine
  */
 class DifferenceEngine extends ContextSource {
+       /**
+        * Constant to indicate diff cache compatibility.
+        * Bump this when changing the diff formatting in a way that
+        * fixes important bugs or such to force cached diff views to
+        * clear.
+        */
+       const DIFF_VERSION = MW_DIFF_VERSION;
 
        /** @var int */
        public $mOldid;
@@ -777,7 +779,7 @@ class DifferenceEngine extends ContextSource {
                        throw new MWException( 'mOldid and mNewid must be set to get diff cache key.' );
                }
 
-               return wfMemcKey( 'diff', 'version', MW_DIFF_VERSION,
+               return wfMemcKey( 'diff', 'version', self::MW_DIFF_VERSION,
                        'oldid', $this->mOldid, 'newid', $this->mNewid );
        }
 
index 234dbac..e35af75 100644 (file)
@@ -2293,13 +2293,6 @@ class LocalFileDeleteBatch {
                        }
                }
 
-               // Lock the filearchive rows so that the files don't get deleted by a cleanup operation
-               // We acquire this lock by running the inserts now, before the file operations.
-               // This potentially has poor lock contention characteristics -- an alternative
-               // scheme would be to insert stub filearchive entries with no fa_name and commit
-               // them in a separate transaction, then run the file ops, then update the fa_name fields.
-               $this->doDBInserts();
-
                if ( !$repo->hasSha1Storage() ) {
                        // Removes non-existent file from the batch, so we don't get errors.
                        // This also handles files in the 'deleted' zone deleted via revision deletion.
@@ -2312,21 +2305,20 @@ class LocalFileDeleteBatch {
 
                        // Execute the file deletion batch
                        $status = $this->file->repo->deleteBatch( $this->deletionBatch );
-
                        if ( !$status->isGood() ) {
                                $this->status->merge( $status );
                        }
                }
 
                if ( !$this->status->isOK() ) {
-                       // Critical file deletion error
-                       // Roll back inserts, release lock and abort
-                       // TODO: delete the defunct filearchive rows if we are using a non-transactional DB
-                       $this->file->unlockAndRollback();
+                       // Critical file deletion error; abort
+                       $this->file->unlock();
 
                        return $this->status;
                }
 
+               // Copy the image/oldimage rows to filearchive
+               $this->doDBInserts();
                // Delete image/oldimage rows
                $this->doDBDeletes();
 
index 4a6b804..a553839 100644 (file)
@@ -118,9 +118,9 @@ class HTMLCheckField extends HTMLFormField {
 
                // GetCheck won't work like we want for checks.
                // Fetch the value in either one of the two following case:
-               // - we have a valid token (form got posted or GET forged by the user)
+               // - we have a valid submit attempt (form was just submitted, or a GET URL forged by the user)
                // - checkbox name has a value (false or true), ie is not null
-               if ( $request->getCheck( 'wpEditToken' ) || $request->getVal( $this->mName ) !== null ) {
+               if ( $this->isSubmitAttempt( $request ) || $request->getVal( $this->mName ) !== null ) {
                        return $invert
                                ? !$request->getBool( $this->mName )
                                : $request->getBool( $this->mName );
index 9f67233..b324fb6 100644 (file)
@@ -225,22 +225,13 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
         * @return array
         */
        function loadDataFromRequest( $request ) {
-               if ( $this->mParent->getMethod() == 'post' ) {
-                       if ( $request->wasPosted() ) {
-                               // Checkboxes are not added to the request arrays if they're not checked,
-                               // so it's perfectly possible for there not to be an entry at all
-                               return $request->getArray( $this->mName, [] );
-                       } else {
-                               // That's ok, the user has not yet submitted the form, so show the defaults
-                               return $this->getDefault();
-                       }
-               } else {
-                       // This is the impossible case: if we look at $_GET and see no data for our
-                       // field, is it because the user has not yet submitted the form, or that they
-                       // have submitted it with all the options unchecked. We will have to assume the
-                       // latter, which basically means that you can't specify 'positive' defaults
-                       // for GET forms.
+               if ( $this->isSubmitAttempt( $request ) ) {
+                       // Checkboxes are just not added to the request arrays if they're not checked,
+                       // so it's perfectly possible for there not to be an entry at all
                        return $request->getArray( $this->mName, [] );
+               } else {
+                       // That's ok, the user has not yet submitted the form, so show the defaults
+                       return $this->getDefault();
                }
        }
 
index 8ac4cf2..2b7417e 100644 (file)
@@ -190,6 +190,7 @@ class HTMLForm extends ContextSource {
        protected $mSubmitText;
        protected $mSubmitTooltip;
 
+       protected $mFormIdentifier;
        protected $mTitle;
        protected $mMethod = 'post';
        protected $mWasSubmitted = false;
@@ -480,7 +481,14 @@ class HTMLForm extends ContextSource {
                }
 
                # Load data from the request.
-               $this->loadData();
+               if (
+                       $this->mFormIdentifier === null ||
+                       $this->getRequest()->getVal( 'wpFormIdentifier' ) === $this->mFormIdentifier
+               ) {
+                       $this->loadData();
+               } else {
+                       $this->mFieldData = [];
+               }
 
                return $this;
        }
@@ -492,22 +500,29 @@ class HTMLForm extends ContextSource {
        public function tryAuthorizedSubmit() {
                $result = false;
 
-               $submit = false;
+               $identOkay = false;
+               if ( $this->mFormIdentifier === null ) {
+                       $identOkay = true;
+               } else {
+                       $identOkay = $this->getRequest()->getVal( 'wpFormIdentifier' ) === $this->mFormIdentifier;
+               }
+
+               $tokenOkay = false;
                if ( $this->getMethod() !== 'post' ) {
-                       $submit = true; // no session check needed
+                       $tokenOkay = true; // no session check needed
                } elseif ( $this->getRequest()->wasPosted() ) {
                        $editToken = $this->getRequest()->getVal( 'wpEditToken' );
                        if ( $this->getUser()->isLoggedIn() || $editToken !== null ) {
                                // Session tokens for logged-out users have no security value.
                                // However, if the user gave one, check it in order to give a nice
                                // "session expired" error instead of "permission denied" or such.
-                               $submit = $this->getUser()->matchEditToken( $editToken, $this->mTokenSalt );
+                               $tokenOkay = $this->getUser()->matchEditToken( $editToken, $this->mTokenSalt );
                        } else {
-                               $submit = true;
+                               $tokenOkay = true;
                        }
                }
 
-               if ( $submit ) {
+               if ( $tokenOkay && $identOkay ) {
                        $this->mWasSubmitted = true;
                        $result = $this->trySubmit();
                }
@@ -1042,6 +1057,12 @@ class HTMLForm extends ContextSource {
         */
        public function getHiddenFields() {
                $html = '';
+               if ( $this->mFormIdentifier !== null ) {
+                       $html .= Html::hidden(
+                               'wpFormIdentifier',
+                               $this->mFormIdentifier
+                       ) . "\n";
+               }
                if ( $this->getMethod() === 'post' ) {
                        $html .= Html::hidden(
                                'wpEditToken',
@@ -1327,6 +1348,27 @@ class HTMLForm extends ContextSource {
                return $this;
        }
 
+       /**
+        * Set an internal identifier for this form. It will be submitted as a hidden form field, allowing
+        * HTMLForm to determine whether the form was submitted (or merely viewed). Setting this serves
+        * two purposes:
+        *
+        * - If you use two or more forms on one page, it allows HTMLForm to identify which of the forms
+        *   was submitted, and not attempt to validate the other ones.
+        * - If you use checkbox or multiselect fields inside a form using the GET method, it allows
+        *   HTMLForm to distinguish between the initial page view and a form submission with all
+        *   checkboxes or select options unchecked.
+        *
+        * @since 1.28
+        * @param string $ident
+        * @return $this
+        */
+       public function setFormIdentifier( $ident ) {
+               $this->mFormIdentifier = $ident;
+
+               return $this;
+       }
+
        /**
         * Stop a default submit button being shown for this form. This implies that an
         * alternate submit method must be provided manually.
index 9f5e728..7cb497d 100644 (file)
@@ -349,6 +349,20 @@ abstract class HTMLFormField {
                $this->mShowEmptyLabels = $show;
        }
 
+       /**
+        * Can we assume that the request is an attempt to submit a HTMLForm, as opposed to an attempt to
+        * just view it? This can't normally be distinguished for e.g. checkboxes.
+        *
+        * Returns true if the request has a field for a CSRF token (wpEditToken) or a form identifier
+        * (wpFormIdentifier).
+        *
+        * @param WebRequest $request
+        * @return boolean
+        */
+       protected function isSubmitAttempt( WebRequest $request ) {
+               return $request->getCheck( 'wpEditToken' ) || $request->getCheck( 'wpFormIdentifier' );
+       }
+
        /**
         * Get the value that this input has been set to from a posted form,
         * or the input's default value if it has not been set.
index 23125bd..a231b2f 100644 (file)
@@ -123,23 +123,13 @@ class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable
         * @return string
         */
        function loadDataFromRequest( $request ) {
-               if ( $this->mParent->getMethod() == 'post' ) {
-                       if ( $request->wasPosted() ) {
-                               # Checkboxes are just not added to the request arrays if they're not checked,
-                               # so it's perfectly possible for there not to be an entry at all
-                               return $request->getArray( $this->mName, [] );
-                       } else {
-                               # That's ok, the user has not yet submitted the form, so show the defaults
-                               return $this->getDefault();
-                       }
-               } else {
-                       # This is the impossible case: if we look at $_GET and see no data for our
-                       # field, is it because the user has not yet submitted the form, or that they
-                       # have submitted it with all the options unchecked? We will have to assume the
-                       # latter, which basically means that you can't specify 'positive' defaults
-                       # for GET forms.
-                       # @todo FIXME...
+               if ( $this->isSubmitAttempt( $request ) ) {
+                       // Checkboxes are just not added to the request arrays if they're not checked,
+                       // so it's perfectly possible for there not to be an entry at all
                        return $request->getArray( $this->mName, [] );
+               } else {
+                       // That's ok, the user has not yet submitted the form, so show the defaults
+                       return $this->getDefault();
                }
        }
 
index 0d8137c..86b2f3b 100644 (file)
@@ -410,7 +410,6 @@ abstract class DatabaseUpdater {
        public function doUpdates( $what = [ 'core', 'extensions', 'stats' ] ) {
                global $wgVersion;
 
-               $this->db->begin( __METHOD__ );
                $what = array_flip( $what );
                $this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null;
                if ( isset( $what['core'] ) ) {
@@ -432,8 +431,6 @@ abstract class DatabaseUpdater {
                        $this->writeSchemaUpdateFile();
                        $this->setAppliedUpdates( "$wgVersion-schema", $this->updatesSkipped );
                }
-
-               $this->db->commit( __METHOD__ );
        }
 
        /**
index 2345d89..e6deed5 100644 (file)
@@ -50,6 +50,11 @@ class WebInstallerName extends WebInstallerPage {
                        wfMessage( 'config-ns-other-default' )->inContentLanguage()->text()
                );
 
+               $pingbackInfo = ( new Pingback() )->getSystemInfo();
+               // Database isn't available in config yet, so take it
+               // from the installer
+               $pingbackInfo['database'] = $this->getVar( 'wgDBtype' );
+
                $this->addHTML(
                        $this->parent->getTextBox( [
                                'var' => 'wgSitename',
@@ -103,7 +108,10 @@ class WebInstallerName extends WebInstallerPage {
                        $this->parent->getCheckBox( [
                                'var' => 'wgPingback',
                                'label' => 'config-pingback',
-                               'help' => $this->parent->getHelpBox( 'config-pingback-help' ),
+                               'help' => $this->parent->getHelpBox(
+                                       'config-pingback-help',
+                                       FormatJson::encode( $pingbackInfo, true )
+                               ),
                                'value' => true,
                        ] ) .
                        $this->getFieldsetEnd() .
index 30983e6..f0eb9f9 100644 (file)
        "config-subscribe": "Падпісацца на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce сьпіс распаўсюджаньня навінаў пра зьяўленьне новых вэрсіяў].",
        "config-subscribe-help": "Гэта ня вельмі актыўны сьпіс распаўсюджаньня навінаў пра зьяўленьне новых вэрсіяў, які ўключаючы важныя навіны пра бясьпеку.\nВам неабходна падпісацца на яго і абнавіць Вашае ўсталяваньне MediaWiki, калі зьявяцца новыя вэрсіі.",
        "config-subscribe-noemail": "Вы спрабавалі падпісацца на рассылку паведамленьняў пра выхад новых вэрсіяў, не пазначыўшы адрас электроннай пошты.\nКалі ласка, падайце слушны адрас, калі Вы жадаеце падпісацца на рассылку.",
+       "config-pingback": "Дзяліцца зьвесткамі пра гэтую ўсталёўку з распрацоўнікамі MediaWiki.",
        "config-almost-done": "Вы амаль што скончылі!\nАстатнія налады можна прапусьціць і пачаць усталяваньне вікі.",
        "config-optional-continue": "Задаць болей пытаньняў.",
        "config-optional-skip": "Хопіць, проста ўсталяваць вікі.",
index fd4456b..8de5c82 100644 (file)
        "config-subscribe": "Bitte die Mailingliste [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mitteilungen zu Versionsveröffentlichungen] abonnieren.",
        "config-subscribe-help": "Es handelt sich hierbei um eine Mailingliste mit wenigen Aussendungen, die für Mitteilungen zu Versionsveröffentlichungen, einschließlich wichtiger Sicherheitsveröffentlichungen, genutzt wird.\nDiese Mailingliste sollte abonniert werden. Zudem sollte die MediaWiki-Installation stets aktualisiert werden, sobald eine neue Programmversion veröffentlicht wurde.",
        "config-subscribe-noemail": "Beim Abonnieren der Mailingliste mit Mitteilungen zu Versionsveröffentlichungen wurde keine E-Mail-Adresse angegeben.\nBitte eine E-Mail-Adresse angeben, sofern die Mailingliste abonniert werden soll.",
+       "config-pingback": "Daten über diese Installation mit den MediaWiki-Entwicklern teilen.",
+       "config-pingback-help": "Wenn du diese Option auswählst, pingt MediaWiki regelmäßig https://www.mediawiki.org mit Basisdaten über diese MediaWiki-Instanz an. Diese Daten enthalten zum Beispiel den Systemtyp, die PHP-Version und das ausgewählte Datenbank-Backend. Die Wikimedia Foundation teilt diese Daten mit MediaWiki-Entwicklern, um dabei zu helfen, zukünftigen Entwicklungsaufwand zu leiten.",
        "config-almost-done": "Der Vorgang ist fast abgeschlossen!\nDie verbleibenden Konfigurationseinstellungen können übersprungen und das Wiki umgehend installiert werden.",
        "config-optional-continue": "Ja, es sollen weitere Konfigurationseinstellungen vorgenommen werden.",
        "config-optional-skip": "Nein, das Wiki soll nun installiert werden.",
index dbe4266..3f3032b 100644 (file)
        "config-subscribe-help": "This is a low-volume mailing list used for release announcements, including important security announcements.\nYou should subscribe to it and update your MediaWiki installation when new versions come out.",
        "config-subscribe-noemail": "You tried to subscribe to the release announcements mailing list without providing an email address.\nPlease provide an email address if you wish to subscribe to the mailing list.",
        "config-pingback": "Share data about this installation with MediaWiki developers.",
-       "config-pingback-help": "If you select this option, MediaWiki will periodically ping https://www.mediawiki.org with basic data about this MediaWiki instance. This data includes, for example, the type of system, PHP version, and chosen database backend. The Wikimedia Foundation shares this data with MediaWiki developers to help guide future development efforts.",
+       "config-pingback-help": "If you select this option, MediaWiki will periodically ping https://www.mediawiki.org with basic data about this MediaWiki instance. This data includes, for example, the type of system, PHP version, and chosen database backend. The Wikimedia Foundation shares this data with MediaWiki developers to help guide future development efforts. The following data will be sent for your system:\n<pre>$1</pre>",
        "config-almost-done": "You are almost done!\nYou can now skip the remaining configuration and install the wiki right now.",
        "config-optional-continue": "Ask me more questions.",
        "config-optional-skip": "I'm bored already, just install the wiki.",
index d73f61b..a12e6a0 100644 (file)
        "config-subscribe": "Abonnez-vous à la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce liste d'annonce des nouvelles versions]",
        "config-subscribe-help": "Il s'agit d'une liste de diffusion à faible volume utilisée servant à annoncer les nouvelles versions, y compris les versions améliorant la sécurité du logiciel.\nVous devriez y souscrire et mettre à jour votre version de MediaWiki lorsque de nouvelles versions sont publiées.",
        "config-subscribe-noemail": "Vous avez essayé de vous abonner à la liste de diffusion des communiqués, sans fournir une adresse courriel ! S'il vous plaît, fournir une adresse électronique si vous souhaitez vous abonner à la liste de diffusion.",
+       "config-pingback": "Partager des données au sujet de cette installation avec les développeurs de MediaWiki.",
+       "config-pingback-help": "Si vous sélectionnez cette option, MediaWiki fera périodiquement un ping https://www.mediawiki.org avec les données de base sur cette instance de MediaWiki. Ces données incluent, par exemple, le type de système, la version de PHP et de base de données arrière. La Fondation Wikimedia partage ces données avec les développeurs de MediaWiki pour aider à orienter les futurs efforts de développement.",
        "config-almost-done": "Vous avez presque fini !\nVous pouvez passer la configuration restante et installer immédiatement le wiki.",
        "config-optional-continue": "Me poser davantage de questions.",
        "config-optional-skip": "J’en ai assez, installer simplement le wiki.",
index d57e19f..326f947 100644 (file)
@@ -5,7 +5,8 @@
                        "Toliño",
                        "아라",
                        "Vivaelcelta",
-                       "Macofe"
+                       "Macofe",
+                       "Banjo"
                ]
        },
        "config-desc": "O programa de instalación de MediaWiki",
        "config-subscribe": "Subscríbase á [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de correo de anuncios sobre lanzamentos].",
        "config-subscribe-help": "Esta é unha lista de correos de baixo volume usada para anuncios sobre lanzamentos de novas versións, incluíndo avisos de seguridade importantes.\nDebería subscribirse a ela e actualizar a súa instalación MediaWiki cando saian as novas versións.",
        "config-subscribe-noemail": "Intentou subscribirse á lista de correo dos anuncios de novos lanzamentos sen proporcionar o enderezo de correo electrónico.\nDea un enderezo de correo electrónico se quere efectuar a subscrición á lista de correo.",
+       "config-pingback": "Compartir datos de esta instalación cos desenvolvedores de MediaWiki",
+       "config-pingback-help": "Se seleccionas esta opción, MediaWiki enviará periodicamente unha mensaxe a https://www.mediawiki.org con datos básicos sobre esta instancia Mediawiki. Estos datos inclúen, por exemplo, o tipo de sistema, versión de PHP e a base de datos escollida. A Fundación Wikimedia comparte estos datos cos desenvolvedores de MediaWiki para axudar a guiar o traballo futuro de desenvolvemento.",
        "config-almost-done": "Xa case rematou!\nNeste paso pode saltar o resto da configuración e instalar o wiki agora mesmo.",
        "config-optional-continue": "Facédeme máis preguntas.",
        "config-optional-skip": "Xa estou canso. Instalade o wiki.",
index 6f519ec..f156a00 100644 (file)
        "config-subscribe": "להירשם ל[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה עם הודעות על גרסאות חדשות].",
        "config-subscribe-help": "זוהי רשימת תפוצה עם הודעות מעטות שמשמשת להודעות על הוצאת גרסאות, כולל עדכוני אבטחה חשובים.\nמומלץ להירשם אליה ולעדכן את מדיה־ויקי כאשר יוצאות גרסאות חדשות.",
        "config-subscribe-noemail": "ניסית להירשם לרשימת תפוצה של הודעות בלי לתת כתובת דוא\"ל.\nנא לתת כתובת דוא\"ל אם ברצונך להירשם לרשימת התפוצה.",
+       "config-pingback": "לשתף נתונים אודות ההתקנה הזו עם מפתחי מדיה־ויקי.",
        "config-almost-done": "כמעט סיימת!\nאפשר לדלג על שאר ההגדרות ולהתקין את הוויקי כבר עכשיו.",
        "config-optional-continue": "הצגת שאלות נוספות.",
        "config-optional-skip": "משעמם לי, תתקינו לי כבר את הוויקי הזה.",
index 24f5fd7..d3405d2 100644 (file)
@@ -61,7 +61,7 @@
        "config-unicode-pure-php-warning": "<strong>경고:</strong> 유니코드 정규화를 처리할 [http://pecl.php.net/intl intl PECL 확장 기능]을 사용할 수 없기 때문에 느린 pure-PHP 구현을 대신 사용합니다.\n트래픽이 높은 사이트에서 실행하시려면 [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations 유니코드 정규화]를 읽어보셔야 합니다.",
        "config-unicode-update-warning": "<strong>경고:</strong> 유니코드 정규화 래퍼의 설치된 버전은 [http://site.icu-project.org/ ICU 프로젝트]의 라이브러리의 이전 버전을 사용합니다.\n만약 유니코드를 사용하는 것에 대해 우려가 된다면 [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations 업그레이드]해야합니다.",
        "config-no-db": "적절한 데이터베이스 드라이버를 찾을 수 없습니다! PHP용 데이터베이스 드라이버를 설치해야 합니다.\n다음 데이터베이스 {{PLURAL:$2|유형을 지원합니다}}: $1.\n\nPHP를 직접 컴파일했다면, 예를 들어 <code>./configure --with-mysql</code>을 사용하여, 데이터베이스 클라이언트를 활성화하도록 다시 설정하세요.\n데비안이나 우분투 패키지에서 PHP를 설치했다면 <code>php5-mysql</code> 모듈도 설치해야 합니다.",
-       "config-outdated-sqlite": "<strong>경고:</strong> 최소인 $2 버전보다 낮은 SQLite $1(이)가 있습니다. SQLite를 사용할 수 없습니다.",
+       "config-outdated-sqlite": "<strong>경고:</strong> 최소 요구 버전 $2 보다 낮은 SQLite $1이(가) 있습니다. SQLite를 사용할 수 없습니다.",
        "config-no-fts3": "<strong>경고:</strong> SQLite를 [//sqlite.org/fts3.html FTS3 모듈] 없이 컴파일하며, 검색 기능은 백엔드에 사용할 수 없습니다.",
        "config-pcre-old": "<strong>치명:</strong> PCRE $1 또는 그 이상이 필요합니다.\nPHP 바이너리는 PCRE $2에 연결되어 있습니다. [https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE 자세한 정보].",
        "config-pcre-no-utf8": "<strong>치명:</strong> PHP의 PCRE 모듈은 RCRE_UTF8 지원 없이 컴파일된 것 같습니다.\n미디어위키가 올바르게 작동하려면 UTF-8을 지원해야 합니다.",
        "config-db-wiki-help": "정상적인 위키 작업 동안 데이터베이스에 연결하는 데 사용할 사용자 이름과 비밀번호를 입력하세요.\n계정이 존재하지 않고 설치 계정에 충분한 권한이 있는 경우 이 사용자 계정은 위키를 작동하는 데 필요한 최소 권한으로 만들어집니다.",
        "config-db-prefix": "데이터베이스 테이블 접두어:",
        "config-db-prefix-help": "여러 위키 사이 또는 미디어위키와 다른 웹 애플리케이션 사이에 하나의 데이터베이스를 공유해야 하는 경우, 충돌을 피하기 위해 모든 테이블 이름에 접두어를 추가하도록 선택할 수 있습니다.\n공백을 사용하지 마세요.\n\n이 필드는 일반적으로 비어 있습니다.",
-       "config-mysql-old": "MySQL $1 ì\9d´ì\83\81ì\9d´ í\95\84ì\9a\94í\95\98ë\82\98 $2(ì\9d´)ê°\80 있습니다.",
+       "config-mysql-old": "MySQL $1 ì\9d´ì\83\81ì\9d´ í\95\84ì\9a\94í\95©ë\8b\88ë\8b¤. $2ì\9d´(ê°\80) 있습니다.",
        "config-db-port": "데이터베이스 포트:",
        "config-db-schema": "미디어위키에 대한 스키마:",
        "config-db-schema-help": "보통 이 스키마는 문제가 없습니다.\n필요한 경우에만 바꾸세요.",
        "config-invalid-schema": "미디어위키 \"$1\"에 대한 스키마가 잘못됐습니다.\nASCII 글자 (a-z, A-Z), 숫자 (0-9), 밑줄 (_)과 하이픈 (-)만 사용하세요.",
        "config-db-sys-create-oracle": "설치 관리자는 새 계정을 만들기 위한 SYSDBA 계정만을 지원합니다.",
        "config-db-sys-user-exists-oracle": "\"$1\" 사용자 계정이 이미 존재합니다. SYSDBA는 새 계정을 만드는 데에만 사용할 수 있습니다!",
-       "config-postgres-old": "PostgreSQL $1 ì\9d´ì\83\81ì\9d´ í\95\84ì\9a\94í\95\98ë\82\98 $2(ì\9d´)ê°\80 있습니다.",
+       "config-postgres-old": "PostgreSQL $1 ì\9d´ì\83\81ì\9d´ í\95\84ì\9a\94í\95©ë\8b\88ë\8b¤. $2ì\9d´(ê°\80) 있습니다.",
        "config-mssql-old": "Microsoft SQL 서버 $1 이상의 버전이 필요합니다. 현재 버전은 $2입니다.",
        "config-sqlite-name-help": "위키를 식별하기 위한 이름을 선택하세요.\n공백이나 하이픈을 사용하지 마십시오.\nSQLite 데이터 파일 이름에 사용됩니다.",
        "config-sqlite-parent-unwritable-group": "<code><nowiki>$1</nowiki></code> 데이터 디렉토리를 만들 수 없으며, 이는 웹 서버는 상위 디렉토리인 <code><nowiki>$2</nowiki></code>에 쓸 수 없기 때문입니다.\n\n설치 관리자는 웹 서버로 실행 중인 사용자를 지정할 수 없습니다.\n계속하려면 웹 서버가 쓸 수 있는 <code><nowiki>$3</nowiki></code> 디렉토리를 만드세요.\n유닉스/리눅스 시스템에서의 수행:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
        "config-subscribe": "[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 릴리스 발표 메일링 리스트]를 구독합니다.",
        "config-subscribe-help": "중요한 보안 발표를 포함한 배포판 발표에 사용되는 저용량 메일링 리스트입니다.\n이 리스트를 구독하고 새 버전이 나올 때 미디어위키 설치를 업데이트해야 합니다.",
        "config-subscribe-noemail": "이메일 주소를 입력하지 않고 릴리스 발표 메일링 리스트에 가입하려 합니다.\n메일링 리스트에 가입하고자 할 경우 이메일 주소를 입력하세요.",
+       "config-pingback": "본 설치에 관한 데이터를 미디어위키 개발자와 공유합니다.",
+       "config-pingback-help": "이 옵션을 선택하면 미디어위키는 주기적으로 이 미디어위키 인스턴스에 대한 기본 데이터를 가지고 https://www.mediawiki.org에 핑을 합니다. 이 데이터에는 이를테면 시스템의 종류, PHP 버전, 선택한 데이터베이스 백엔드를 포함합니다. 위키미디어 재단은 이 데이터를 미디어위키 개발자들과 공유하여 향후 개발 활동의 길잡이에 도움을 줍니다.",
        "config-almost-done": "거의 다 완료했습니다!\n이제 남은 설정을 생략하고 지금 바로 위키를 설치할 수 있습니다.",
        "config-optional-continue": "더 많은 질문을 물어보세요.",
        "config-optional-skip": "지겨워요, 그냥 위키를 설치할래요.",
index a39d3a1..3343db7 100644 (file)
        "config-subscribe": "Donn de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce \n<i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"„de eläktrohnesche Poß“\">e-mail</i>-Leß met de Aanköndijonge vum MehdijaWikki] abonnehre.",
        "config-subscribe-help": "Do kumme bloß winnish Meddeilunge un di jonn övver neu Versiohne vom MediaWiki un weeshtejje Saache vun däm sing Sesherheit.\nDo sullts se abbonneere, un Ding MediWiki_Projramme op der neue Shtand bränge, wann neu Version eruß kumme.",
        "config-subscribe-noemail": "Do has versöhk, der ohne en Addräß för Ding <i lang=\"en\">e-mail<i> aanzejävve, de Aanköndijonge för Aanköndijunge för neue Versione ze abboneere. Jivv en Addräß aan, wann De di Aanköndijonge hann wells.",
+       "config-pingback": "Jivv Dahte övver heh di Enschtallazjuhn vum Mehdijawikki aan de Äntwerkere.",
        "config-almost-done": "Do bes beinah dorsch!\nDo künnts jez der Räß vun de einzel Enschtällonge övverjonn, un et Wiki tiräktemang fähdesch opsäze.",
        "config-optional-continue": "De wells noch mih Frohre jeschtallt krijje un noch mih Enschtällonge maache?",
        "config-optional-skip": "Nä, lohß dä Ömshtand, donn eifarr_et Wiki opsäze.",
index 6a1dd08..833e7d6 100644 (file)
        "config-subscribe-help": "\"Low-volume\" in this context means that there will be few e-mails to that mailing list per time period.",
        "config-subscribe-noemail": "Error text in MediaWiki installer.",
        "config-pingback": "Option in the MediaWiki installer to submit data about this installation to MediaWiki.org.",
-       "config-pingback-help": "Explains what data will be shared if the user chooses to submit data to MediaWiki.org.",
+       "config-pingback-help": "Explains what data will be shared if the user chooses to submit data to MediaWiki.org. $1 is the JSON data that will be sent",
        "config-almost-done": "Status message in the MediaWiki installer.",
        "config-optional-continue": "Option in the MediaWiki installer to make a more fine-tuned installation.",
        "config-optional-skip": "Option in the MediaWiki installer to start executing the actual installation and stop asking questions.",
index 34ef3e8..418b4ff 100644 (file)
        "config-subscribe": "Подписаться на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce рассылку новостей о появлении новых версий MediaWiki].",
        "config-subscribe-help": "Это список рассылки с малым числом сообщений, используется для анонса новых выпусков и сообщений о проблемах с безопасностью.\nВам следует подписаться на него и обновлять движок MediaWiki, по мере выхода новых версий.",
        "config-subscribe-noemail": "Вы попытались подписаться на список рассылки уведомлений о новых выпусках без указания адреса электронной почты.\nУкажите адрес электронной почты, если вы хотите подписаться на список рассылки.",
+       "config-pingback": "Поделиться сведениями об этой установке с разработчикам MediaWiki.",
+       "config-pingback-help": "Если вы выберите этот вариант, MediaWiki будет периодически отправлять на https://www.mediawiki.org основные сведения об этом экземпляре MediaWiki. К этим данным относятся, в частности, тип операционной системы, версия PHP и выбранная СУБД. Фонда Викимедиа делится этими данными с разработчиками MediaWiki, чтобы помочь им в проведении будущих разработок.",
        "config-almost-done": "Вы почти у цели!\nОстальные настройки можно пропустить и приступить к установке вики.",
        "config-optional-continue": "Произвести тонкую настройку",
        "config-optional-skip": "Хватит, установить вики",
index 07e1fc3..84cea99 100644 (file)
        "config-subscribe": "订阅[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 发行公告邮件列表]。",
        "config-subscribe-help": "此低流量的邮件列表仅用于发行公告,其中包括重要安全公告。请订阅该列表以便在新的版本推出时升级您的MediaWiki。",
        "config-subscribe-noemail": "您选择了订阅发行公告邮件列表,但没有提供电子邮件地址。请提供一个电子邮件地址以订阅邮件列表。",
+       "config-pingback": "与MediaWiki开发人员分享有关此安装程序的数据。",
+       "config-pingback-help": "如果您选择此选项,MediaWiki将定期与https://www.mediawiki.org通信,传输与此MediaWiki实例相关的基础数据。此数据包括例如系统类型、PHP版本和选择的数据库后端。维基媒体基金会与MediaWiki开发人员分享此数据,以帮助引导将来的开发计划。",
        "config-almost-done": "您几乎已经完成了!现在您可以跳过剩下的配置流程并立即安装wiki。",
        "config-optional-continue": "多问我一些问题吧。",
        "config-optional-skip": "我已经不耐烦了,赶紧安装我的wiki。",
index ab702d5..2dc17bf 100644 (file)
@@ -759,6 +759,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @param array $opts Options map:
         *   - checkKeys: List of "check" keys. The key at $key will be seen as invalid when either
         *      touchCheckKey() or resetCheckKey() is called on any of these keys.
+        *      Default: [];
         *   - lowTTL: Consider pre-emptive updates when the current TTL (sec) of the key is less than
         *      this. It becomes more likely over time, becoming a certainty once the key is expired.
         *      Default: WANObjectCache::LOW_TTL seconds.
@@ -770,6 +771,11 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *      higher this is set, the higher the worst-case staleness can be.
         *      Use WANObjectCache::TSE_NONE to disable this logic.
         *      Default: WANObjectCache::TSE_NONE.
+        *   - busyValue: If no value exists and another thread is currently regenerating it, use this
+        *      as a fallback value (or a callback to generate such a value). This assures that cache
+        *      stampedes cannot happen if the value falls out of cache. This can be used as insurance
+        *      against cache regeneration becoming very slow for some reason (greater than the TTL).
+        *      Default: null.
         *   - pcTTL: Process cache the value in this PHP instance with this TTL. This avoids
         *      network I/O when a key is read several times. This will not cache if the callback
         *      returns false however. Note that any purges will not be seen while process cached;
@@ -861,6 +867,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $lowTTL = isset( $opts['lowTTL'] ) ? $opts['lowTTL'] : min( self::LOW_TTL, $ttl );
                $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
                $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
+               $busyValue = isset( $opts['busyValue'] ) ? $opts['busyValue'] : null;
                $minTime = isset( $opts['minTime'] ) ? $opts['minTime'] : 0.0;
                $versioned = isset( $opts['version'] );
 
@@ -882,11 +889,13 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $isTombstone = ( $curTTL !== null && $value === false );
                // Assume a key is hot if requested soon after invalidation
                $isHot = ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE );
+               // Use the mutex if there is no value and a busy fallback is given
+               $checkBusy = ( $busyValue !== null && $value === false );
                // Decide whether a single thread should handle regenerations.
                // This avoids stampedes when $checkKeys are bumped and when preemptive
                // renegerations take too long. It also reduces regenerations while $key
                // is tombstoned. This balances cache freshness with avoiding DB load.
-               $useMutex = ( $isHot || ( $isTombstone && $lockTSE > 0 ) );
+               $useMutex = ( $isHot || ( $isTombstone && $lockTSE > 0 ) || $checkBusy );
 
                $lockAcquired = false;
                if ( $useMutex ) {
@@ -908,6 +917,10 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
 
                                        return $value;
                                }
+                               // Use the busy fallback value if nothing else
+                               if ( $busyValue !== null ) {
+                                       return is_callable( $busyValue ) ? $busyValue() : $busyValue;
+                               }
                        }
                }
 
@@ -921,7 +934,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $asOf = microtime( true );
                // When delete() is called, writes are write-holed by the tombstone,
                // so use a special INTERIM key to pass the new value around threads.
-               if ( $useMutex && $value !== false && $ttl >= 0 ) {
+               if ( ( $isTombstone && $lockTSE > 0 ) && $value !== false && $ttl >= 0 ) {
                        $tempTTL = max( 1, (int)$lockTSE ); // set() expects seconds
                        $wrapped = $this->wrap( $value, $tempTTL, $asOf );
                        $this->cache->set( self::INTERIM_KEY_PREFIX . $key, $wrapped, $tempTTL );
index 0e11967..012288f 100644 (file)
  * Differences from DOM schema:
  *   * attribute nodes are children
  *   * "<h>" nodes that aren't at the top are replaced with <possible-h>
+ *
+ * Nodes are stored in a recursive array data structure. A node store is an
+ * array where each element may be either a scalar (representing a text node)
+ * or a "descriptor", which is a two-element array where the first element is
+ * the node name and the second element is the node store for the children.
+ *
+ * Attributes are represented as children that have a node name starting with
+ * "@", and a single text node child.
+ *
+ * @todo: Consider replacing descriptor arrays with objects of a new class.
+ * Benchmark and measure resulting memory impact.
+ *
  * @ingroup Parser
  */
 // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
@@ -37,6 +49,7 @@ class Preprocessor_Hash extends Preprocessor {
        public $parser;
 
        const CACHE_PREFIX = 'preprocess-hash';
+       const CACHE_VERSION = 2;
 
        public function __construct( $parser ) {
                $this->parser = $parser;
@@ -65,23 +78,20 @@ class Preprocessor_Hash extends Preprocessor {
                $list = [];
 
                foreach ( $values as $k => $val ) {
-                       $partNode = new PPNode_Hash_Tree( 'part' );
-                       $nameNode = new PPNode_Hash_Tree( 'name' );
-
                        if ( is_int( $k ) ) {
-                               $nameNode->addChild( new PPNode_Hash_Attr( 'index', $k ) );
-                               $partNode->addChild( $nameNode );
+                               $store = [ [ 'part', [
+                                       [ 'name', [ [ '@index', [ $k ] ] ] ],
+                                       [ 'value', [ strval( $val ) ] ],
+                               ] ] ];
                        } else {
-                               $nameNode->addChild( new PPNode_Hash_Text( $k ) );
-                               $partNode->addChild( $nameNode );
-                               $partNode->addChild( new PPNode_Hash_Text( '=' ) );
+                               $store = [ [ 'part', [
+                                       [ 'name', [ strval( $k ) ] ],
+                                       '=',
+                                       [ 'value', [ strval( $val ) ] ],
+                               ] ] ];
                        }
 
-                       $valueNode = new PPNode_Hash_Tree( 'value' );
-                       $valueNode->addChild( new PPNode_Hash_Text( $val ) );
-                       $partNode->addChild( $valueNode );
-
-                       $list[] = $partNode;
+                       $list[] = new PPNode_Hash_Tree( $store, 0 );
                }
 
                $node = new PPNode_Hash_Array( $list );
@@ -90,7 +100,6 @@ class Preprocessor_Hash extends Preprocessor {
 
        /**
         * Preprocess some wikitext and return the document tree.
-        * This is the ghost of Parser::replace_variables().
         *
         * @param string $text The text to parse
         * @param int $flags Bitwise combination of:
@@ -104,17 +113,16 @@ class Preprocessor_Hash extends Preprocessor {
         * change in the DOM tree for a given text, must be passed through the section identifier
         * in the section edit link and thus back to extractSections().
         *
-        * The output of this function is currently only cached in process memory, but a persistent
-        * cache may be implemented at a later date which takes further advantage of these strict
-        * dependency requirements.
-        *
         * @throws MWException
         * @return PPNode_Hash_Tree
         */
        public function preprocessToObj( $text, $flags = 0 ) {
                $tree = $this->cacheGetTree( $text, $flags );
                if ( $tree !== false ) {
-                       return unserialize( $tree );
+                       $store = json_decode( $tree );
+                       if ( is_array( $store ) ) {
+                               return new PPNode_Hash_Tree( $store, 0 );
+                       }
                }
 
                $forInclusion = $flags & Parser::PTD_FOR_INCLUSION;
@@ -150,7 +158,7 @@ class Preprocessor_Hash extends Preprocessor {
 
                // Input pointer, starts out pointing to a pseudo-newline before the start
                $i = 0;
-               // Current accumulator
+               // Current accumulator. See the doc comment for Preprocessor_Hash for the format.
                $accum =& $stack->getAccum();
                // True to find equals signs in arguments
                $findEquals = false;
@@ -176,11 +184,11 @@ class Preprocessor_Hash extends Preprocessor {
                                $startPos = strpos( $text, '<onlyinclude>', $i );
                                if ( $startPos === false ) {
                                        // Ignored section runs to the end
-                                       $accum->addNodeWithText( 'ignore', substr( $text, $i ) );
+                                       $accum[] = [ 'ignore', [ substr( $text, $i ) ] ];
                                        break;
                                }
                                $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end
-                               $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i ) );
+                               $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i ) ] ];
                                $i = $tagEndPos;
                                $findOnlyinclude = false;
                        }
@@ -208,7 +216,7 @@ class Preprocessor_Hash extends Preprocessor {
                                # Output literal section, advance input counter
                                $literalLength = strcspn( $text, $search, $i );
                                if ( $literalLength > 0 ) {
-                                       $accum->addLiteral( substr( $text, $i, $literalLength ) );
+                                       self::addLiteral( $accum, substr( $text, $i, $literalLength ) );
                                        $i += $literalLength;
                                }
                                if ( $i >= $lengthText ) {
@@ -261,7 +269,7 @@ class Preprocessor_Hash extends Preprocessor {
                                // Determine element name
                                if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
                                        // Element name missing or not listed
-                                       $accum->addLiteral( '<' );
+                                       self::addLiteral( $accum, '<' );
                                        ++$i;
                                        continue;
                                }
@@ -278,7 +286,7 @@ class Preprocessor_Hash extends Preprocessor {
                                        if ( $endPos === false ) {
                                                // Unclosed comment in input, runs to end
                                                $inner = substr( $text, $i );
-                                               $accum->addNodeWithText( 'comment', $inner );
+                                               $accum[] = [ 'comment', [ $inner ] ];
                                                $i = $lengthText;
                                        } else {
                                                // Search backwards for leading whitespace
@@ -309,13 +317,16 @@ class Preprocessor_Hash extends Preprocessor {
                                                        && substr( $text, $wsEnd + 1, 1 ) == "\n"
                                                ) {
                                                        // Remove leading whitespace from the end of the accumulator
-                                                       // Sanity check first though
                                                        $wsLength = $i - $wsStart;
+                                                       $endIndex = count( $accum ) - 1;
+
+                                                       // Sanity check
                                                        if ( $wsLength > 0
-                                                               && $accum->lastNode instanceof PPNode_Hash_Text
-                                                               && strspn( $accum->lastNode->value, " \t", -$wsLength ) === $wsLength
+                                                               && $endIndex >= 0
+                                                               && is_string( $accum[$endIndex] )
+                                                               && strspn( $accum[$endIndex], " \t", -$wsLength ) === $wsLength
                                                        ) {
-                                                               $accum->lastNode->value = substr( $accum->lastNode->value, 0, -$wsLength );
+                                                               $accum[$endIndex] = substr( $accum[$endIndex], 0, -$wsLength );
                                                        }
 
                                                        // Dump all but the last comment to the accumulator
@@ -326,7 +337,7 @@ class Preprocessor_Hash extends Preprocessor {
                                                                        break;
                                                                }
                                                                $inner = substr( $text, $startPos, $endPos - $startPos );
-                                                               $accum->addNodeWithText( 'comment', $inner );
+                                                               $accum[] = [ 'comment', [ $inner ] ];
                                                        }
 
                                                        // Do a line-start run next time to look for headings after the comment
@@ -347,7 +358,7 @@ class Preprocessor_Hash extends Preprocessor {
                                                }
                                                $i = $endPos + 1;
                                                $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
-                                               $accum->addNodeWithText( 'comment', $inner );
+                                               $accum[] = [ 'comment', [ $inner ] ];
                                        }
                                        continue;
                                }
@@ -361,14 +372,14 @@ class Preprocessor_Hash extends Preprocessor {
                                        // Infinite backtrack
                                        // Disable tag search to prevent worst-case O(N^2) performance
                                        $noMoreGT = true;
-                                       $accum->addLiteral( '<' );
+                                       self::addLiteral( $accum, '<' );
                                        ++$i;
                                        continue;
                                }
 
                                // Handle ignored tags
                                if ( in_array( $lowerName, $ignoredTags ) ) {
-                                       $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i + 1 ) );
+                                       $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i + 1 ) ] ];
                                        $i = $tagEndPos + 1;
                                        continue;
                                }
@@ -401,7 +412,8 @@ class Preprocessor_Hash extends Preprocessor {
                                                } else {
                                                        // Don't match the tag, treat opening tag as literal and resume parsing.
                                                        $i = $tagEndPos + 1;
-                                                       $accum->addLiteral( substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
+                                                       self::addLiteral( $accum,
+                                                               substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
                                                        // Cache results, otherwise we have O(N^2) performance for input like <foo><foo><foo>...
                                                        $noMoreClosingTag[$name] = true;
                                                        continue;
@@ -410,7 +422,7 @@ class Preprocessor_Hash extends Preprocessor {
                                }
                                // <includeonly> and <noinclude> just become <ignore> tags
                                if ( in_array( $lowerName, $ignoredElements ) ) {
-                                       $accum->addNodeWithText( 'ignore', substr( $text, $tagStartPos, $i - $tagStartPos ) );
+                                       $accum[] = [ 'ignore', [ substr( $text, $tagStartPos, $i - $tagStartPos ) ] ];
                                        continue;
                                }
 
@@ -422,23 +434,23 @@ class Preprocessor_Hash extends Preprocessor {
                                        $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
                                }
 
-                               $extNode = new PPNode_Hash_Tree( 'ext' );
-                               $extNode->addChild( PPNode_Hash_Tree::newWithText( 'name', $name ) );
-                               $extNode->addChild( PPNode_Hash_Tree::newWithText( 'attr', $attr ) );
+                               $children = [
+                                       [ 'name', [ $name ] ],
+                                       [ 'attr', [ $attr ] ] ];
                                if ( $inner !== null ) {
-                                       $extNode->addChild( PPNode_Hash_Tree::newWithText( 'inner', $inner ) );
+                                       $children[] = [ 'inner', [ $inner ] ];
                                }
                                if ( $close !== null ) {
-                                       $extNode->addChild( PPNode_Hash_Tree::newWithText( 'close', $close ) );
+                                       $children[] = [ 'close', [ $close ] ];
                                }
-                               $accum->addNode( $extNode );
+                               $accum[] = [ 'ext', $children ];
                        } elseif ( $found == 'line-start' ) {
                                // Is this the start of a heading?
                                // Line break belongs before the heading element in any case
                                if ( $fakeLineStart ) {
                                        $fakeLineStart = false;
                                } else {
-                                       $accum->addLiteral( $curChar );
+                                       self::addLiteral( $accum, $curChar );
                                        $i++;
                                }
 
@@ -494,11 +506,15 @@ class Preprocessor_Hash extends Preprocessor {
                                        }
                                        if ( $count > 0 ) {
                                                // Normal match, output <h>
-                                               $element = new PPNode_Hash_Tree( 'possible-h' );
-                                               $element->addChild( new PPNode_Hash_Attr( 'level', $count ) );
-                                               $element->addChild( new PPNode_Hash_Attr( 'i', $headingIndex++ ) );
-                                               $element->lastChild->nextSibling = $accum->firstNode;
-                                               $element->lastChild = $accum->lastNode;
+                                               $element = [ [ 'possible-h',
+                                                       array_merge(
+                                                               [
+                                                                       [ '@level', [ $count ] ],
+                                                                       [ '@i', [ $headingIndex++ ] ]
+                                                               ],
+                                                               $accum
+                                                       )
+                                               ] ];
                                        } else {
                                                // Single equals sign on its own line, count=0
                                                $element = $accum;
@@ -513,11 +529,8 @@ class Preprocessor_Hash extends Preprocessor {
                                extract( $stack->getFlags() );
 
                                // Append the result to the enclosing accumulator
-                               if ( $element instanceof PPNode ) {
-                                       $accum->addNode( $element );
-                               } else {
-                                       $accum->addAccum( $element );
-                               }
+                               array_splice( $accum, count( $accum ), 0, $element );
+
                                // Note that we do NOT increment the input pointer.
                                // This is because the closing linebreak could be the opening linebreak of
                                // another heading. Infinite loops are avoided because the next iteration MUST
@@ -542,7 +555,7 @@ class Preprocessor_Hash extends Preprocessor {
                                        extract( $stack->getFlags() );
                                } else {
                                        # Add literal brace(s)
-                                       $accum->addLiteral( str_repeat( $curChar, $count ) );
+                                       self::addLiteral( $accum, str_repeat( $curChar, $count ) );
                                }
                                $i += $count;
                        } elseif ( $found == 'close' ) {
@@ -571,7 +584,7 @@ class Preprocessor_Hash extends Preprocessor {
                                if ( $matchingCount <= 0 ) {
                                        # No matching element found in callback array
                                        # Output a literal closing brace and continue
-                                       $accum->addLiteral( str_repeat( $curChar, $count ) );
+                                       self::addLiteral( $accum, str_repeat( $curChar, $count ) );
                                        $i += $count;
                                        continue;
                                }
@@ -579,77 +592,38 @@ class Preprocessor_Hash extends Preprocessor {
                                if ( $name === null ) {
                                        // No element, just literal text
                                        $element = $piece->breakSyntax( $matchingCount );
-                                       $element->addLiteral( str_repeat( $rule['end'], $matchingCount ) );
+                                       self::addLiteral( $element, str_repeat( $rule['end'], $matchingCount ) );
                                } else {
                                        # Create XML element
-                                       # Note: $parts is already XML, does not need to be encoded further
                                        $parts = $piece->parts;
                                        $titleAccum = $parts[0]->out;
                                        unset( $parts[0] );
 
-                                       $element = new PPNode_Hash_Tree( $name );
+                                       $children = [];
 
                                        # The invocation is at the start of the line if lineStart is set in
                                        # the stack, and all opening brackets are used up.
                                        if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
-                                               $element->addChild( new PPNode_Hash_Attr( 'lineStart', 1 ) );
+                                               $children[] = [ '@lineStart', [ 1 ] ];
                                        }
-                                       $titleNode = new PPNode_Hash_Tree( 'title' );
-                                       $titleNode->firstChild = $titleAccum->firstNode;
-                                       $titleNode->lastChild = $titleAccum->lastNode;
-                                       $element->addChild( $titleNode );
+                                       $titleNode = [ 'title', $titleAccum ];
+                                       $children[] = $titleNode;
                                        $argIndex = 1;
                                        foreach ( $parts as $part ) {
                                                if ( isset( $part->eqpos ) ) {
-                                                       // Find equals
-                                                       $lastNode = false;
-                                                       for ( $node = $part->out->firstNode; $node; $node = $node->nextSibling ) {
-                                                               if ( $node === $part->eqpos ) {
-                                                                       break;
-                                                               }
-                                                               $lastNode = $node;
-                                                       }
-                                                       if ( !$node ) {
-                                                               // if ( $cacheable ) { ... }
-                                                               throw new MWException( __METHOD__ . ': eqpos not found' );
-                                                       }
-                                                       if ( $node->name !== 'equals' ) {
-                                                               // if ( $cacheable ) { ... }
-                                                               throw new MWException( __METHOD__ . ': eqpos is not equals' );
-                                                       }
-                                                       $equalsNode = $node;
-
-                                                       // Construct name node
-                                                       $nameNode = new PPNode_Hash_Tree( 'name' );
-                                                       if ( $lastNode !== false ) {
-                                                               $lastNode->nextSibling = false;
-                                                               $nameNode->firstChild = $part->out->firstNode;
-                                                               $nameNode->lastChild = $lastNode;
-                                                       }
-
-                                                       // Construct value node
-                                                       $valueNode = new PPNode_Hash_Tree( 'value' );
-                                                       if ( $equalsNode->nextSibling !== false ) {
-                                                               $valueNode->firstChild = $equalsNode->nextSibling;
-                                                               $valueNode->lastChild = $part->out->lastNode;
-                                                       }
-                                                       $partNode = new PPNode_Hash_Tree( 'part' );
-                                                       $partNode->addChild( $nameNode );
-                                                       $partNode->addChild( $equalsNode->firstChild );
-                                                       $partNode->addChild( $valueNode );
-                                                       $element->addChild( $partNode );
+                                                       $equalsNode = $part->out[$part->eqpos];
+                                                       $nameNode = [ 'name', array_slice( $part->out, 0, $part->eqpos ) ];
+                                                       $valueNode = [ 'value', array_slice( $part->out, $part->eqpos + 1 ) ];
+                                                       $partNode = [ 'part', [ $nameNode, $equalsNode, $valueNode ] ];
+                                                       $children[] = $partNode;
                                                } else {
-                                                       $partNode = new PPNode_Hash_Tree( 'part' );
-                                                       $nameNode = new PPNode_Hash_Tree( 'name' );
-                                                       $nameNode->addChild( new PPNode_Hash_Attr( 'index', $argIndex++ ) );
-                                                       $valueNode = new PPNode_Hash_Tree( 'value' );
-                                                       $valueNode->firstChild = $part->out->firstNode;
-                                                       $valueNode->lastChild = $part->out->lastNode;
-                                                       $partNode->addChild( $nameNode );
-                                                       $partNode->addChild( $valueNode );
-                                                       $element->addChild( $partNode );
+                                                       $nameNode = [ 'name', [ [ '@index', [ $argIndex++ ] ] ] ];
+                                                       $valueNode = [ 'value', $part->out ];
+                                                       $partNode = [ 'part', [ $nameNode, $valueNode ] ];
+                                                       $children[] = $partNode;
                                                }
                                        }
+                                       $element = [ [ $name, $children ] ];
                                }
 
                                # Advance input pointer
@@ -669,18 +643,14 @@ class Preprocessor_Hash extends Preprocessor {
                                                $stack->push( $piece );
                                                $accum =& $stack->getAccum();
                                        } else {
-                                               $accum->addLiteral( str_repeat( $piece->open, $piece->count ) );
+                                               self::addLiteral( $accum, str_repeat( $piece->open, $piece->count ) );
                                        }
                                }
 
                                extract( $stack->getFlags() );
 
                                # Add XML element to the enclosing accumulator
-                               if ( $element instanceof PPNode ) {
-                                       $accum->addNode( $element );
-                               } else {
-                                       $accum->addAccum( $element );
-                               }
+                               array_splice( $accum, count( $accum ), 0, $element );
                        } elseif ( $found == 'pipe' ) {
                                $findEquals = true; // shortcut for getFlags()
                                $stack->addPart();
@@ -688,33 +658,44 @@ class Preprocessor_Hash extends Preprocessor {
                                ++$i;
                        } elseif ( $found == 'equals' ) {
                                $findEquals = false; // shortcut for getFlags()
-                               $accum->addNodeWithText( 'equals', '=' );
-                               $stack->getCurrentPart()->eqpos = $accum->lastNode;
+                               $accum[] = [ 'equals', [ '=' ] ];
+                               $stack->getCurrentPart()->eqpos = count( $accum ) - 1;
                                ++$i;
                        }
                }
 
                # Output any remaining unclosed brackets
                foreach ( $stack->stack as $piece ) {
-                       $stack->rootAccum->addAccum( $piece->breakSyntax() );
+                       array_splice( $stack->rootAccum, count( $stack->rootAccum ), 0, $piece->breakSyntax() );
                }
 
                # Enable top-level headings
-               for ( $node = $stack->rootAccum->firstNode; $node; $node = $node->nextSibling ) {
-                       if ( isset( $node->name ) && $node->name === 'possible-h' ) {
-                               $node->name = 'h';
+               foreach ( $stack->rootAccum as &$node ) {
+                       if ( is_array( $node ) && $node[PPNode_Hash_Tree::NAME] === 'possible-h' ) {
+                               $node[PPNode_Hash_Tree::NAME] = 'h';
                        }
                }
 
-               $rootNode = new PPNode_Hash_Tree( 'root' );
-               $rootNode->firstChild = $stack->rootAccum->firstNode;
-               $rootNode->lastChild = $stack->rootAccum->lastNode;
+               $rootStore = [ [ 'root', $stack->rootAccum ] ];
+               $rootNode = new PPNode_Hash_Tree( $rootStore, 0 );
 
                // Cache
-               $this->cacheSetTree( $text, $flags, serialize( $rootNode ) );
+               $tree = json_encode( $rootStore, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
+               if ( $tree !== false ) {
+                       $this->cacheSetTree( $text, $flags, $tree );
+               }
 
                return $rootNode;
        }
+
+       private static function addLiteral( array &$accum, $text ) {
+               $n = count( $accum );
+               if ( $n && is_string( $accum[$n - 1] ) ) {
+                       $accum[$n - 1] .= $text;
+               } else {
+                       $accum[] = $text;
+               }
+       }
 }
 
 /**
@@ -728,7 +709,7 @@ class PPDStack_Hash extends PPDStack {
        public function __construct() {
                $this->elementClass = 'PPDStackElement_Hash';
                parent::__construct();
-               $this->rootAccum = new PPDAccum_Hash;
+               $this->rootAccum = [];
        }
 }
 
@@ -748,7 +729,7 @@ class PPDStackElement_Hash extends PPDStackElement {
         * Get the accumulator that would result if the close is not found.
         *
         * @param int|bool $openingCount
-        * @return PPDAccum_Hash
+        * @return array
         */
        public function breakSyntax( $openingCount = false ) {
                if ( $this->open == "\n" ) {
@@ -757,16 +738,24 @@ class PPDStackElement_Hash extends PPDStackElement {
                        if ( $openingCount === false ) {
                                $openingCount = $this->count;
                        }
-                       $accum = new PPDAccum_Hash;
-                       $accum->addLiteral( str_repeat( $this->open, $openingCount ) );
+                       $accum = [ str_repeat( $this->open, $openingCount ) ];
+                       $lastIndex = 0;
                        $first = true;
                        foreach ( $this->parts as $part ) {
                                if ( $first ) {
                                        $first = false;
+                               } elseif ( is_string( $accum[$lastIndex] ) ) {
+                                       $accum[$lastIndex] .= '|';
                                } else {
-                                       $accum->addLiteral( '|' );
+                                       $accum[++$lastIndex] = '|';
+                               }
+                               foreach ( $part->out as $node ) {
+                                       if ( is_string( $node ) && is_string( $accum[$lastIndex] ) ) {
+                                               $accum[$lastIndex] .= $node;
+                                       } else {
+                                               $accum[++$lastIndex] = $node;
+                                       }
                                }
-                               $accum->addAccum( $part->out );
                        }
                }
                return $accum;
@@ -781,81 +770,12 @@ class PPDPart_Hash extends PPDPart {
        // @codingStandardsIgnoreEnd
 
        public function __construct( $out = '' ) {
-               $accum = new PPDAccum_Hash;
                if ( $out !== '' ) {
-                       $accum->addLiteral( $out );
-               }
-               parent::__construct( $accum );
-       }
-}
-
-/**
- * @ingroup Parser
- */
-// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
-class PPDAccum_Hash {
-       // @codingStandardsIgnoreEnd
-
-       public $firstNode, $lastNode;
-
-       public function __construct() {
-               $this->firstNode = $this->lastNode = false;
-       }
-
-       /**
-        * Append a string literal
-        * @param string $s
-        */
-       public function addLiteral( $s ) {
-               if ( $this->lastNode === false ) {
-                       $this->firstNode = $this->lastNode = new PPNode_Hash_Text( $s );
-               } elseif ( $this->lastNode instanceof PPNode_Hash_Text ) {
-                       $this->lastNode->value .= $s;
-               } else {
-                       $this->lastNode->nextSibling = new PPNode_Hash_Text( $s );
-                       $this->lastNode = $this->lastNode->nextSibling;
-               }
-       }
-
-       /**
-        * Append a PPNode
-        * @param PPNode $node
-        */
-       public function addNode( PPNode $node ) {
-               if ( $this->lastNode === false ) {
-                       $this->firstNode = $this->lastNode = $node;
-               } else {
-                       $this->lastNode->nextSibling = $node;
-                       $this->lastNode = $node;
-               }
-       }
-
-       /**
-        * Append a tree node with text contents
-        * @param string $name
-        * @param string $value
-        */
-       public function addNodeWithText( $name, $value ) {
-               $node = PPNode_Hash_Tree::newWithText( $name, $value );
-               $this->addNode( $node );
-       }
-
-       /**
-        * Append a PPDAccum_Hash
-        * Takes over ownership of the nodes in the source argument. These nodes may
-        * subsequently be modified, especially nextSibling.
-        * @param PPDAccum_Hash $accum
-        */
-       public function addAccum( $accum ) {
-               if ( $accum->lastNode === false ) {
-                       // nothing to add
-               } elseif ( $this->lastNode === false ) {
-                       $this->firstNode = $accum->firstNode;
-                       $this->lastNode = $accum->lastNode;
+                       $accum = [ $out ];
                } else {
-                       $this->lastNode->nextSibling = $accum->firstNode;
-                       $this->lastNode = $accum->lastNode;
+                       $accum = [];
                }
+               parent::__construct( $accum );
        }
 }
 
@@ -1050,130 +970,144 @@ class PPFrame_Hash implements PPFrame {
                        }
 
                        $newIterator = false;
+                       $contextName = false;
+                       $contextChildren = false;
 
                        if ( $contextNode === false ) {
                                // nothing to do
                        } elseif ( is_string( $contextNode ) ) {
                                $out .= $contextNode;
-                       } elseif ( is_array( $contextNode ) || $contextNode instanceof PPNode_Hash_Array ) {
+                       } elseif ( $contextNode instanceof PPNode_Hash_Array ) {
                                $newIterator = $contextNode;
                        } elseif ( $contextNode instanceof PPNode_Hash_Attr ) {
                                // No output
                        } elseif ( $contextNode instanceof PPNode_Hash_Text ) {
                                $out .= $contextNode->value;
                        } elseif ( $contextNode instanceof PPNode_Hash_Tree ) {
-                               if ( $contextNode->name == 'template' ) {
-                                       # Double-brace expansion
-                                       $bits = $contextNode->splitTemplate();
-                                       if ( $flags & PPFrame::NO_TEMPLATES ) {
-                                               $newIterator = $this->virtualBracketedImplode(
-                                                       '{{', '|', '}}',
-                                                       $bits['title'],
-                                                       $bits['parts']
-                                               );
-                                       } else {
-                                               $ret = $this->parser->braceSubstitution( $bits, $this );
-                                               if ( isset( $ret['object'] ) ) {
-                                                       $newIterator = $ret['object'];
-                                               } else {
-                                                       $out .= $ret['text'];
-                                               }
-                                       }
-                               } elseif ( $contextNode->name == 'tplarg' ) {
-                                       # Triple-brace expansion
-                                       $bits = $contextNode->splitTemplate();
-                                       if ( $flags & PPFrame::NO_ARGS ) {
-                                               $newIterator = $this->virtualBracketedImplode(
-                                                       '{{{', '|', '}}}',
-                                                       $bits['title'],
-                                                       $bits['parts']
-                                               );
-                                       } else {
-                                               $ret = $this->parser->argSubstitution( $bits, $this );
-                                               if ( isset( $ret['object'] ) ) {
-                                                       $newIterator = $ret['object'];
-                                               } else {
-                                                       $out .= $ret['text'];
-                                               }
-                                       }
-                               } elseif ( $contextNode->name == 'comment' ) {
-                                       # HTML-style comment
-                                       # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
-                                       # Not in RECOVER_COMMENTS mode (msgnw) though.
-                                       if ( ( $this->parser->ot['html']
-                                               || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
-                                               || ( $flags & PPFrame::STRIP_COMMENTS )
-                                               ) && !( $flags & PPFrame::RECOVER_COMMENTS )
-                                       ) {
-                                               $out .= '';
-                                       } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
-                                               # Add a strip marker in PST mode so that pstPass2() can
-                                               # run some old-fashioned regexes on the result.
-                                               # Not in RECOVER_COMMENTS mode (extractSections) though.
-                                               $out .= $this->parser->insertStripItem( $contextNode->firstChild->value );
+                               $contextName = $contextNode->name;
+                               $contextChildren = $contextNode->getRawChildren();
+                       } elseif ( is_array( $contextNode ) ) {
+                               // Node descriptor array
+                               if ( count( $contextNode ) !== 2 ) {
+                                       throw new MWException( __METHOD__.
+                                               ': found an array where a node descriptor should be' );
+                               }
+                               list( $contextName, $contextChildren ) = $contextNode;
+                       } else {
+                               throw new MWException( __METHOD__ . ': Invalid parameter type' );
+                       }
+
+                       // Handle node descriptor array or tree object
+                       if ( $contextName === false ) {
+                               // Not a node, already handled above
+                       } elseif ( $contextName[0] === '@' ) {
+                               // Attribute: no output
+                       } elseif ( $contextName === 'template' ) {
+                               # Double-brace expansion
+                               $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
+                               if ( $flags & PPFrame::NO_TEMPLATES ) {
+                                       $newIterator = $this->virtualBracketedImplode(
+                                               '{{', '|', '}}',
+                                               $bits['title'],
+                                               $bits['parts']
+                                       );
+                               } else {
+                                       $ret = $this->parser->braceSubstitution( $bits, $this );
+                                       if ( isset( $ret['object'] ) ) {
+                                               $newIterator = $ret['object'];
                                        } else {
-                                               # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
-                                               $out .= $contextNode->firstChild->value;
+                                               $out .= $ret['text'];
                                        }
-                               } elseif ( $contextNode->name == 'ignore' ) {
-                                       # Output suppression used by <includeonly> etc.
-                                       # OT_WIKI will only respect <ignore> in substed templates.
-                                       # The other output types respect it unless NO_IGNORE is set.
-                                       # extractSections() sets NO_IGNORE and so never respects it.
-                                       if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
-                                               || ( $flags & PPFrame::NO_IGNORE )
-                                       ) {
-                                               $out .= $contextNode->firstChild->value;
+                               }
+                       } elseif ( $contextName === 'tplarg' ) {
+                               # Triple-brace expansion
+                               $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
+                               if ( $flags & PPFrame::NO_ARGS ) {
+                                       $newIterator = $this->virtualBracketedImplode(
+                                               '{{{', '|', '}}}',
+                                               $bits['title'],
+                                               $bits['parts']
+                                       );
+                               } else {
+                                       $ret = $this->parser->argSubstitution( $bits, $this );
+                                       if ( isset( $ret['object'] ) ) {
+                                               $newIterator = $ret['object'];
                                        } else {
-                                               // $out .= '';
+                                               $out .= $ret['text'];
                                        }
-                               } elseif ( $contextNode->name == 'ext' ) {
-                                       # Extension tag
-                                       $bits = $contextNode->splitExt() + [ 'attr' => null, 'inner' => null, 'close' => null ];
-                                       if ( $flags & PPFrame::NO_TAGS ) {
-                                               $s = '<' . $bits['name']->firstChild->value;
-                                               if ( $bits['attr'] ) {
-                                                       $s .= $bits['attr']->firstChild->value;
-                                               }
-                                               if ( $bits['inner'] ) {
-                                                       $s .= '>' . $bits['inner']->firstChild->value;
-                                                       if ( $bits['close'] ) {
-                                                               $s .= $bits['close']->firstChild->value;
-                                                       }
-                                               } else {
-                                                       $s .= '/>';
-                                               }
-                                               $out .= $s;
-                                       } else {
-                                               $out .= $this->parser->extensionSubstitution( $bits, $this );
+                               }
+                       } elseif ( $contextName === 'comment' ) {
+                               # HTML-style comment
+                               # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
+                               # Not in RECOVER_COMMENTS mode (msgnw) though.
+                               if ( ( $this->parser->ot['html']
+                                       || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
+                                       || ( $flags & PPFrame::STRIP_COMMENTS )
+                                       ) && !( $flags & PPFrame::RECOVER_COMMENTS )
+                               ) {
+                                       $out .= '';
+                               } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
+                                       # Add a strip marker in PST mode so that pstPass2() can
+                                       # run some old-fashioned regexes on the result.
+                                       # Not in RECOVER_COMMENTS mode (extractSections) though.
+                                       $out .= $this->parser->insertStripItem( $contextChildren[0] );
+                               } else {
+                                       # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
+                                       $out .= $contextChildren[0];
+                               }
+                       } elseif ( $contextName === 'ignore' ) {
+                               # Output suppression used by <includeonly> etc.
+                               # OT_WIKI will only respect <ignore> in substed templates.
+                               # The other output types respect it unless NO_IGNORE is set.
+                               # extractSections() sets NO_IGNORE and so never respects it.
+                               if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
+                                       || ( $flags & PPFrame::NO_IGNORE )
+                               ) {
+                                       $out .= $contextChildren[0];
+                               } else {
+                                       // $out .= '';
+                               }
+                       } elseif ( $contextName === 'ext' ) {
+                               # Extension tag
+                               $bits = PPNode_Hash_Tree::splitRawExt( $contextChildren ) +
+                                       [ 'attr' => null, 'inner' => null, 'close' => null ];
+                               if ( $flags & PPFrame::NO_TAGS ) {
+                                       $s = '<' . $bits['name']->getFirstChild()->value;
+                                       if ( $bits['attr'] ) {
+                                               $s .= $bits['attr']->getFirstChild()->value;
                                        }
-                               } elseif ( $contextNode->name == 'h' ) {
-                                       # Heading
-                                       if ( $this->parser->ot['html'] ) {
-                                               # Expand immediately and insert heading index marker
-                                               $s = '';
-                                               for ( $node = $contextNode->firstChild; $node; $node = $node->nextSibling ) {
-                                                       $s .= $this->expand( $node, $flags );
+                                       if ( $bits['inner'] ) {
+                                               $s .= '>' . $bits['inner']->getFirstChild()->value;
+                                               if ( $bits['close'] ) {
+                                                       $s .= $bits['close']->getFirstChild()->value;
                                                }
-
-                                               $bits = $contextNode->splitHeading();
-                                               $titleText = $this->title->getPrefixedDBkey();
-                                               $this->parser->mHeadings[] = [ $titleText, $bits['i'] ];
-                                               $serial = count( $this->parser->mHeadings ) - 1;
-                                               $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
-                                               $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
-                                               $this->parser->mStripState->addGeneral( $marker, '' );
-                                               $out .= $s;
                                        } else {
-                                               # Expand in virtual stack
-                                               $newIterator = $contextNode->getChildren();
+                                               $s .= '/>';
                                        }
+                                       $out .= $s;
+                               } else {
+                                       $out .= $this->parser->extensionSubstitution( $bits, $this );
+                               }
+                       } elseif ( $contextName === 'h' ) {
+                               # Heading
+                               if ( $this->parser->ot['html'] ) {
+                                       # Expand immediately and insert heading index marker
+                                       $s = $this->expand( $contextChildren, $flags );
+                                       $bits = PPNode_Hash_Tree::splitRawHeading( $contextChildren );
+                                       $titleText = $this->title->getPrefixedDBkey();
+                                       $this->parser->mHeadings[] = [ $titleText, $bits['i'] ];
+                                       $serial = count( $this->parser->mHeadings ) - 1;
+                                       $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
+                                       $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
+                                       $this->parser->mStripState->addGeneral( $marker, '' );
+                                       $out .= $s;
                                } else {
-                                       # Generic recursive expansion
-                                       $newIterator = $contextNode->getChildren();
+                                       # Expand in virtual stack
+                                       $newIterator = $contextChildren;
                                }
                        } else {
-                               throw new MWException( __METHOD__ . ': Invalid parameter type' );
+                               # Generic recursive expansion
+                               $newIterator = $contextChildren;
                        }
 
                        if ( $newIterator !== false ) {
@@ -1689,17 +1623,85 @@ class PPCustomFrame_Hash extends PPFrame_Hash {
 class PPNode_Hash_Tree implements PPNode {
        // @codingStandardsIgnoreEnd
 
-       public $name, $firstChild, $lastChild, $nextSibling;
+       public $name;
+
+       /**
+        * The store array for children of this node. It is "raw" in the sense that
+        * nodes are two-element arrays ("descriptors") rather than PPNode_Hash_*
+        * objects.
+        */
+       private $rawChildren;
+
+       /**
+        * The store array for the siblings of this node, including this node itself.
+        */
+       private $store;
+
+       /**
+        * The index into $this->store which contains the descriptor of this node.
+        */
+       private $index;
+
+       /**
+        * The offset of the name within descriptors, used in some places for
+        * readability.
+        */
+       const NAME = 0;
+
+       /**
+        * The offset of the child list within descriptors, used in some places for
+        * readability.
+        */
+       const CHILDREN = 1;
+
+       /**
+        * Construct an object using the data from $store[$index]. The rest of the
+        * store array can be accessed via getNextSibling().
+        *
+        * @param array $store
+        * @param integer $index
+        */
+       public function __construct( array $store, $index ) {
+               $this->store = $store;
+               $this->index = $index;
+               list( $this->name, $this->rawChildren ) = $this->store[$index];
+       }
+
+       /**
+        * Construct an appropriate PPNode_Hash_* object with a class that depends
+        * on what is at the relevant store index.
+        *
+        * @param array $store
+        * @param integer $index
+        * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text
+        */
+       public static function factory( array $store, $index ) {
+               if ( !isset( $store[$index] ) ) {
+                       return false;
+               }
 
-       public function __construct( $name ) {
-               $this->name = $name;
-               $this->firstChild = $this->lastChild = $this->nextSibling = false;
+               $descriptor = $store[$index];
+               if ( is_string( $descriptor ) ) {
+                       $class = 'PPNode_Hash_Text';
+               } elseif ( is_array( $descriptor ) ) {
+                       if ( $descriptor[self::NAME][0] === '@' ) {
+                               $class = 'PPNode_Hash_Attr';
+                       } else {
+                               $class = 'PPNode_Hash_Tree';
+                       }
+               } else {
+                       throw new MWException( __METHOD__.': invalid node descriptor' );
+               }
+               return new $class( $store, $index );
        }
 
+       /**
+        * Convert a node to XML, for debugging
+        */
        public function __toString() {
                $inner = '';
                $attribs = '';
-               for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) {
+               for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
                        if ( $node instanceof PPNode_Hash_Attr ) {
                                $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
                        } else {
@@ -1713,55 +1715,67 @@ class PPNode_Hash_Tree implements PPNode {
                }
        }
 
-       /**
-        * @param string $name
-        * @param string $text
-        * @return PPNode_Hash_Tree
-        */
-       public static function newWithText( $name, $text ) {
-               $obj = new self( $name );
-               $obj->addChild( new PPNode_Hash_Text( $text ) );
-               return $obj;
-       }
-
-       public function addChild( $node ) {
-               if ( $this->lastChild === false ) {
-                       $this->firstChild = $this->lastChild = $node;
-               } else {
-                       $this->lastChild->nextSibling = $node;
-                       $this->lastChild = $node;
-               }
-       }
-
        /**
         * @return PPNode_Hash_Array
         */
        public function getChildren() {
                $children = [];
-               for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
-                       $children[] = $child;
+               foreach ( $this->rawChildren as $i => $child ) {
+                       $children[] = self::factory( $this->rawChildren, $i );
                }
                return new PPNode_Hash_Array( $children );
        }
 
+       /**
+        * Get the first child, or false if there is none. Note that this will
+        * return a temporary proxy object: different instances will be returned
+        * if this is called more than once on the same node.
+        *
+        * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|boolean
+        */
        public function getFirstChild() {
-               return $this->firstChild;
+               if ( !isset( $this->rawChildren[0] ) ) {
+                       return false;
+               } else {
+                       return self::factory( $this->rawChildren, 0 );
+               }
        }
 
+       /**
+        * Get the next sibling, or false if there is none. Note that this will
+        * return a temporary proxy object: different instances will be returned
+        * if this is called more than once on the same node.
+        *
+        * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|boolean
+        */
        public function getNextSibling() {
-               return $this->nextSibling;
+               return self::factory( $this->store, $this->index + 1 );
        }
 
+       /**
+        * Get an array of the children with a given node name
+        *
+        * @param string $name
+        * @return PPNode_Hash_Array
+        */
        public function getChildrenOfType( $name ) {
                $children = [];
-               for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
-                       if ( isset( $child->name ) && $child->name === $name ) {
-                               $children[] = $child;
+               foreach ( $this->rawChildren as $i => $child ) {
+                       if ( is_array( $child ) && $child[self::NAME] === $name ) {
+                               $children[] = self::factory( $this->rawChildren, $i );
                        }
                }
                return new PPNode_Hash_Array( $children );
        }
 
+       /**
+        * Get the raw child array. For internal use.
+        * @return array
+        */
+       public function getRawChildren() {
+               return $this->rawChildren;
+       }
+
        /**
         * @return bool
         */
@@ -1794,20 +1808,27 @@ class PPNode_Hash_Tree implements PPNode {
         * @return array
         */
        public function splitArg() {
+               return self::splitRawArg( $this->rawChildren );
+       }
+
+       /**
+        * Like splitArg() but for a raw child array. For internal use only.
+        */
+       public static function splitRawArg( array $children ) {
                $bits = [];
-               for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
-                       if ( !isset( $child->name ) ) {
+               foreach ( $children as $i => $child ) {
+                       if ( !is_array( $child ) ) {
                                continue;
                        }
-                       if ( $child->name === 'name' ) {
-                               $bits['name'] = $child;
-                               if ( $child->firstChild instanceof PPNode_Hash_Attr
-                                       && $child->firstChild->name === 'index'
+                       if ( $child[self::NAME] === 'name' ) {
+                               $bits['name'] = new self( $children, $i );
+                               if ( isset( $child[self::CHILDREN][0][self::NAME] )
+                                       && $child[self::CHILDREN][0][self::NAME] === '@index'
                                ) {
-                                       $bits['index'] = $child->firstChild->value;
+                                       $bits['index'] = $child[self::CHILDREN][0][self::CHILDREN][0];
                                }
-                       } elseif ( $child->name === 'value' ) {
-                               $bits['value'] = $child;
+                       } elseif ( $child[self::NAME] === 'value' ) {
+                               $bits['value'] = new self( $children, $i );
                        }
                }
 
@@ -1828,19 +1849,31 @@ class PPNode_Hash_Tree implements PPNode {
         * @return array
         */
        public function splitExt() {
+               return self::splitRawExt( $this->rawChildren );
+       }
+
+       /**
+        * Like splitExt() but for a raw child array. For internal use only.
+        */
+       public static function splitRawExt( array $children ) {
                $bits = [];
-               for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
-                       if ( !isset( $child->name ) ) {
+               foreach ( $children as $i => $child ) {
+                       if ( !is_array( $child ) ) {
                                continue;
                        }
-                       if ( $child->name == 'name' ) {
-                               $bits['name'] = $child;
-                       } elseif ( $child->name == 'attr' ) {
-                               $bits['attr'] = $child;
-                       } elseif ( $child->name == 'inner' ) {
-                               $bits['inner'] = $child;
-                       } elseif ( $child->name == 'close' ) {
-                               $bits['close'] = $child;
+                       switch ( $child[self::NAME] ) {
+                       case 'name':
+                               $bits['name'] = new self( $children, $i );
+                               break;
+                       case 'attr':
+                               $bits['attr'] = new self( $children, $i );
+                               break;
+                       case 'inner':
+                               $bits['inner'] = new self( $children, $i );
+                               break;
+                       case 'close':
+                               $bits['close'] = new self( $children, $i );
+                               break;
                        }
                }
                if ( !isset( $bits['name'] ) ) {
@@ -1859,15 +1892,22 @@ class PPNode_Hash_Tree implements PPNode {
                if ( $this->name !== 'h' ) {
                        throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
                }
+               return self::splitRawHeading( $this->rawChildren );
+       }
+
+       /**
+        * Like splitHeading() but for a raw child array. For internal use only.
+        */
+       public static function splitRawHeading( array $children ) {
                $bits = [];
-               for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
-                       if ( !isset( $child->name ) ) {
+               foreach ( $children as $i => $child ) {
+                       if ( !is_array( $child ) ) {
                                continue;
                        }
-                       if ( $child->name == 'i' ) {
-                               $bits['i'] = $child->value;
-                       } elseif ( $child->name == 'level' ) {
-                               $bits['level'] = $child->value;
+                       if ( $child[self::NAME] === '@i' ) {
+                               $bits['i'] = $child[self::CHILDREN][0];
+                       } elseif ( $child[self::NAME] === '@level' ) {
+                               $bits['level'] = $child[self::CHILDREN][0];
                        }
                }
                if ( !isset( $bits['i'] ) ) {
@@ -1883,20 +1923,29 @@ class PPNode_Hash_Tree implements PPNode {
         * @return array
         */
        public function splitTemplate() {
+               return self::splitRawTemplate( $this->rawChildren );
+       }
+
+       /**
+        * Like splitTemplate() but for a raw child array. For internal use only.
+        */
+       public static function splitRawTemplate( array $children ) {
                $parts = [];
                $bits = [ 'lineStart' => '' ];
-               for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
-                       if ( !isset( $child->name ) ) {
+               foreach ( $children as $i => $child ) {
+                       if ( !is_array( $child ) ) {
                                continue;
                        }
-                       if ( $child->name == 'title' ) {
-                               $bits['title'] = $child;
-                       }
-                       if ( $child->name == 'part' ) {
-                               $parts[] = $child;
-                       }
-                       if ( $child->name == 'lineStart' ) {
+                       switch ( $child[self::NAME] ) {
+                       case 'title':
+                               $bits['title'] = new self( $children, $i );
+                               break;
+                       case 'part':
+                               $parts[] = new self( $children, $i );
+                               break;
+                       case '@lineStart':
                                $bits['lineStart'] = '1';
+                               break;
                        }
                }
                if ( !isset( $bits['title'] ) ) {
@@ -1914,13 +1963,23 @@ class PPNode_Hash_Tree implements PPNode {
 class PPNode_Hash_Text implements PPNode {
        // @codingStandardsIgnoreEnd
 
-       public $value, $nextSibling;
+       public $value;
+       private $store, $index;
 
-       public function __construct( $value ) {
-               if ( is_object( $value ) ) {
+       /**
+        * Construct an object using the data from $store[$index]. The rest of the
+        * store array can be accessed via getNextSibling().
+        *
+        * @param array $store
+        * @param integer $index
+        */
+       public function __construct( array $store, $index ) {
+               $this->value = $store[$index];
+               if ( !is_scalar( $this->value ) ) {
                        throw new MWException( __CLASS__ . ' given object instead of string' );
                }
-               $this->value = $value;
+               $this->store = $store;
+               $this->index = $index;
        }
 
        public function __toString() {
@@ -1928,7 +1987,7 @@ class PPNode_Hash_Text implements PPNode {
        }
 
        public function getNextSibling() {
-               return $this->nextSibling;
+               return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
        }
 
        public function getChildren() {
@@ -1975,7 +2034,7 @@ class PPNode_Hash_Text implements PPNode {
 class PPNode_Hash_Array implements PPNode {
        // @codingStandardsIgnoreEnd
 
-       public $value, $nextSibling;
+       public $value;
 
        public function __construct( $value ) {
                $this->value = $value;
@@ -1998,7 +2057,7 @@ class PPNode_Hash_Array implements PPNode {
        }
 
        public function getNextSibling() {
-               return $this->nextSibling;
+               return false;
        }
 
        public function getChildren() {
@@ -2033,11 +2092,25 @@ class PPNode_Hash_Array implements PPNode {
 class PPNode_Hash_Attr implements PPNode {
        // @codingStandardsIgnoreEnd
 
-       public $name, $value, $nextSibling;
+       public $name, $value;
+       private $store, $index;
 
-       public function __construct( $name, $value ) {
-               $this->name = $name;
-               $this->value = $value;
+       /**
+        * Construct an object using the data from $store[$index]. The rest of the
+        * store array can be accessed via getNextSibling().
+        *
+        * @param array $store
+        * @param integer $index
+        */
+       public function __construct( array $store, $index ) {
+               $descriptor = $store[$index];
+               if ( $descriptor[PPNode_Hash_Tree::NAME][0] !== '@' ) {
+                       throw new MWException( __METHOD__.': invalid name in attribute descriptor' );
+               }
+               $this->name = substr( $descriptor[PPNode_Hash_Tree::NAME], 1 );
+               $this->value = $descriptor[PPNode_Hash_Tree::CHILDREN][0];
+               $this->store = $store;
+               $this->index = $index;
        }
 
        public function __toString() {
@@ -2049,7 +2122,7 @@ class PPNode_Hash_Attr implements PPNode {
        }
 
        public function getNextSibling() {
-               return $this->nextSibling;
+               return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
        }
 
        public function getChildren() {
index 0a86e94..3c81feb 100644 (file)
@@ -234,7 +234,7 @@ abstract class RevDelList extends RevisionListBase {
                }
 
                if ( $status->successCount == 0 ) {
-                       $dbw->rollback( __METHOD__ );
+                       $dbw->endAtomic( __METHOD__ );
                        return $status;
                }
 
@@ -244,8 +244,8 @@ abstract class RevDelList extends RevisionListBase {
                // Move files, if there are any
                $status->merge( $this->doPreCommitUpdates() );
                if ( !$status->isOK() ) {
-                       // Fatal error, such as no configured archive directory
-                       $dbw->rollback( __METHOD__ );
+                       // Fatal error, such as no configured archive directory or I/O failures
+                       wfGetLBFactory()->rollbackMasterChanges( __METHOD__ );
                        return $status;
                }
 
index 13db176..71ca57b 100644 (file)
@@ -140,7 +140,7 @@ abstract class BaseTemplate extends QuickTemplate {
                        if ( isset( $plink['active'] ) ) {
                                $ptool['active'] = $plink['active'];
                        }
-                       foreach ( [ 'href', 'class', 'text', 'dir' ] as $k ) {
+                       foreach ( [ 'href', 'class', 'text', 'dir', 'data' ] as $k ) {
                                if ( isset( $plink[$k] ) ) {
                                        $ptool['links'][0][$k] = $plink[$k];
                                }
@@ -318,6 +318,15 @@ abstract class BaseTemplate extends QuickTemplate {
         *
         * If you don't want an accesskey, set $item['tooltiponly'] = true;
         *
+        * If a "data" key is present, it must be an array, where the keys represent
+        * the data-xxx properties with their provided values. For example,
+        *  $item['data'] = array(
+        *       'foo' => 1,
+        *       'bar' => 'baz',
+        *  );
+        * will render as element properties:
+        *  data-foo='1' data-bar='baz'
+        *
         * @param array $options Can be used to affect the output of a link.
         * Possible options are:
         *   - 'text-wrapper' key to specify a list of elements to wrap the text of
@@ -363,6 +372,13 @@ abstract class BaseTemplate extends QuickTemplate {
                                unset( $attrs[$k] );
                        }
 
+                       if ( isset( $attrs['data'] ) ) {
+                               foreach ( $attrs['data'] as $key => $value ) {
+                                       $attrs[ 'data-' . $key ] = $value;
+                               }
+                               unset( $attrs[ 'data' ] );
+                       }
+
                        if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
                                $item['single-id'] = $item['id'];
                        }
index cea4a1c..d6cfcca 100644 (file)
        "lockdbsuccesstext": "База зьвестак была заблякаваная.<br />\nНе забудзьцеся [[Special:UnlockDB|зьняць блякаваньне]] пасьля сканчэньня абслугоўваньня.",
        "unlockdbsuccesstext": "База зьвестак была разблякаваная.",
        "lockfilenotwritable": "Немагчыма запісаць у файл блякаваньняў базы зьвестак.\nБлякаваньне ці разблякаваньне базы зьвестак патрабуе, каб вэб-сэрвэр меў дазвол на запіс у гэты файл.",
+       "databaselocked": "База зьвестак ужо заблякаваная.",
        "databasenotlocked": "База зьвестак не заблякаваная.",
        "lockedbyandtime": "($1 $2 у $3)",
        "move-page": "Перанесьці $1",
index a06ff33..5214d3f 100644 (file)
@@ -31,7 +31,8 @@
                        "Urbanecm",
                        "LordMsz",
                        "Matma Rex",
-                       "Dvorapa"
+                       "Dvorapa",
+                       "Walter Klosse"
                ]
        },
        "tog-underline": "Podtrhávat odkazy:",
index c6f8ec5..30421d7 100644 (file)
        "anoneditwarning": "<strong>चेतावनी:</strong> तमले प्रवेश अरेको नाइथिन । तमरो आइपि ठेगाना पाना सम्पादन इतिहासमि दर्ता गरिन्या छ र यो सब्बैले हेद्द सक्कान । यदि तमलाईँ <strong>[$1 लगईन]</strong> वा <strong>[$2 नयाँ खाता बनाउन्या] गर्याभण्या तमबठे गरियाको सम्पादन तमरो प्रयोगकर्तानाममि जोडिन्याछ ।",
        "missingsummary": "'''यादगर्या :''' तमीले सम्पादन सारांश दियाका छैनौ ।\nयदि तमीले \"{{int:savearticle}}\"  थिच्यौ भण्या , सारांश बिना नै सङ्ग्रहित गरिन्या छ ।",
        "selfredirect": "<strong>चेतावनी:</strong> तम यै पानालाई आफुमी पुनः निर्देशित गद्द लाग्याछौ ।\nहुनसक्छ तम अनुप्रेषितको लागि गलत लक्ष्य निर्दिष्ट गद्द लाग्याछौ, वा गलत पानाको सम्पादन गद्द लाग्याछौ ।\nतम पुनः एकपल्ट \"{{int:savearticle}}\" क्लिक गद्दाछौ, पुनः निर्देशित तसै लै बनाइन्याछ।",
-       "missingcommentheader": "'''याद गर :''' तमीले टिप्पणीमी विषय /शीर्ष पंक्ति  दियाका छैनौ ।\nतमीले फेरि \"{{int:savearticle}}\"  थिच्यौ भण्या , तमरो सम्पादन यसै रुपमी संग्रहित हुन्याछ ।",
+       "missingcommenttext": "कृपया तलतिर टिप्पणी राख ।",
+       "missingcommentheader": "'''याद गर :''' तमले टिप्पणीमी विषय /शीर्ष पंक्ति  दियाका छैनौ ।\nतमले फेरि \"{{int:savearticle}}\"  थिच्यौ भण्या , तमरो सम्पादन यसै रुपमी संग्रहित हुन्याछ ।",
        "summary-preview": "सारांश पूर्वालोकन:",
        "subject-preview": "विषय पूर्वरुप:",
        "previewerrortext": "तमरो परिवर्तनको पूर्वावलोकन बनाउन खोज्दा समस्या आयाको छ ।",
        "right-deletedtext": "मेट्याका संशोधन बीचका मेट्याका पाठ र परिवर्तनहरू हेद्या",
        "right-suppressionlog": "व्यक्तिगत लगहरू हेद्या",
        "right-block": "अरु प्रयोगकर्तानलाई सम्पादन गद्दाकी ब्लक गर",
+       "right-unblockself": "आफुलाई खुल्ला गर ।",
        "right-editprotected": "\"{{int:protect-level-sysop}}\" को हैसियतले सुरक्षित पानाहरू सम्पादन गद्या",
        "right-editusercssjs": "अन्य प्रयोगकर्ताको सी.एस.एस. रे जाभास्क्रिप्ट फाइलहरू सम्पादन गद्या",
        "right-editusercss": "अन्य प्रयोगकर्ताको सी. एस. एस. फाइलहरू सम्पादन गद्या",
index 5e76af6..f1cf530 100644 (file)
        "recentchanges-legend": "Chui-khiûn kiên-kói sién-hong",
        "recentchanges-summary": "Kiên-chiûng pún wiki sông ke chui-sîn kiên-kói.",
        "recentchanges-feed-description": "Kiên-chiûng pún thin-ye̍t chhai wiki sông ke chui-khiûn kiên-kói.",
-       "recentchanges-label-newpage": "Liá-chhṳ phiên-si̍p kien-li̍p hí yit-chak sîn ya̍p-mien",
+       "recentchanges-label-newpage": "Liá-ke phiên-siá kien-li̍p sîn ya̍p",
        "recentchanges-label-minor": "Liá-he yit-chak se-mì siû-kói",
        "recentchanges-label-bot": "Liá-chhṳ phiên-siá he yù kî-hi-ngìn chin-hàng",
        "recentchanges-label-unpatrolled": "Liá-chhṳ phiên-siá hàn-mò sùn-chhà ko",
        "recentchanges-label-plusminus": "Kâi ya̍p-mien kiên-kói ke thai-séu  (vi-ngièn-chû)",
        "recentchanges-legend-heading": "<strong>Chu-yi:</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (chhiáng chhâm-siòng [[Special:NewPages|sîn ya̍p-mien]])",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (chhâm-siòng [[Special:NewPages|sîn ya̍p]])",
        "rcnotefrom": "下背係從'''$2'''起嘅更改(最多展示'''$1'''):",
        "rclistfrom": "Chán-sṳ chhiùng $3 $2 yî-lòi ke sîn kiên-kói",
        "rcshowhideminor": "$1細微編寫",
index d6cc92f..5359a0d 100644 (file)
        "password-login-forbidden": "이 사용자 계정 이름과 비밀번호는 사용할 수 없습니다.",
        "mailmypassword": "비밀번호 재설정",
        "passwordremindertitle": "{{SITENAME}}의 새 임시 비밀번호",
-       "passwordremindertext": "$1 IP ì£¼ì\86\8cì\97\90ì\84\9c ë\88\84êµ°ê°\80ê°\80 ì\95\84ë§\88 ì\9e\90ì\8b ì\9d´ {{SITENAME}} ($4)ì\9d\98 ì\83\88 ë¹\84ë°\80ë²\88í\98¸ë¥¼ ì\9a\94ì²­í\96\88ì\8aµë\8b\88ë\8b¤.\n\"$2\" ì\82¬ì\9a©ì\9e\90ì\9d\98 ì\9e\84ì\8b\9c ë¹\84ë°\80ë²\88í\98¸ë\8a\94 \"$3\"ë¡\9c ì\84¤ì \95ë\90\98ì\97\88ì\8aµë\8b\88ë\8b¤. ì\9d´ê²\83ì\9d´ ì\9e\90ì\8b ì\9d´ ì\9d\98ë\8f\84í\95\9c ë°\94ë\9d¼ë©´\nì§\80ê¸\88 ë¡\9cê·¸ì\9d¸í\95\98ì\97¬ ì\83\88ë¡\9cì\9a´ ë¹\84ë°\80ë²\88í\98¸ë¥¼ ë§\8cë\93\9cì\84¸ì\9a\94.\nì\9e\84ì\8b\9c ë¹\84ë°\80ë²\88í\98¸ë\8a\94 {{PLURAL:$5|$5일}} 후에 만료됩니다.\n\n이 요청을 다른 사람이 했거나 이전 비밀번호를 기억해 내서 바꿀 필요가 없으면\n이 메시지를 무시하고 이전 비밀번호를 계속 사용할 수 있습니다.",
+       "passwordremindertext": "$1 IP ì£¼ì\86\8cì\97\90ì\84\9c ë\8b¹ì\8b ì\9d¼ ì\88\98ë\8f\84 ì\9e\88ë\8a\94 ë\88\84êµ°ê°\80ê°\80 {{SITENAME}} ($4)ì\9d\98 ì\83\88 ë¹\84ë°\80ë²\88í\98¸ë¥¼ ì\9a\94ì²­í\96\88ì\8aµë\8b\88ë\8b¤.\n\"$2\" ì\82¬ì\9a©ì\9e\90ì\9d\98 ì\9e\84ì\8b\9c ë¹\84ë°\80ë²\88í\98¸ë\8a\94 \"$3\"ë¡\9c ì\84¤ì \95ë\90\98ì\97\88ì\8aµë\8b\88ë\8b¤. ì\9d´ê²\83ì\9d´ ì\9e\90ì\8b ì\9d´ ì\9d\98ë\8f\84í\95\9c ë°\94ë\9d¼ë©´\nì§\80ê¸\88 ë¡\9cê·¸ì\9d¸í\95\98ì\97¬ ì\83\88ë¡\9cì\9a´ ë¹\84ë°\80ë²\88í\98¸ë¥¼ ë§\8cë\93\9cì\84¸ì\9a\94.\nì\9e\84ì\8b\9c ë¹\84ë°\80ë²\88í\98¸ë\8a\94 {{PLURAL:$5|1ì\9d¼|$5일}} 후에 만료됩니다.\n\n이 요청을 다른 사람이 했거나 이전 비밀번호를 기억해 내서 바꿀 필요가 없으면\n이 메시지를 무시하고 이전 비밀번호를 계속 사용할 수 있습니다.",
        "noemail": "\"$1\" 사용자는 이메일 주소를 등록하지 않았습니다.",
        "noemailcreate": "올바른 이메일 주소를 제공해야 합니다.",
        "passwordsent": "\"$1\" 계정의 새로운 비밀번호를 이메일로 보냈습니다.\n비밀번호를 받고 다시 로그인해 주세요.",
        "accountcreated": "계정이 만들어짐",
        "accountcreatedtext": "[[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|토론]]) 사용자 계정이 만들어졌습니다.",
        "createaccount-title": "{{SITENAME}} 계정 만들기",
-       "createaccount-text": "ë\88\84êµ°ê°\80ê°\80 {{SITENAME}} ($4)ì\97\90ì\84\9c ì\82¬ì\9a©ì\9e\90 ì\9d´ë¦\84 \"$2\", ë¹\84ë°\80ë²\88í\98¸ \"$3\"ë¡\9c ë\8b¹ì\8b ì\9d\98 ì\9d´ë©\94ì\9d¼ ì£¼ì\86\8cê°\80 ë\93±ë¡\9dë\90\9c ê³\84ì \95ì\9d\84 ë§\8cë\93¤ì\97\88ì\8aµë\8b\88ë\8b¤. \nì§\80ê¸\88 ë¡\9cê·¸ì\9d¸í\95\98ì\97¬ ë¹\84ë°\80ë²\88í\98¸ë¥¼ ë°\94꾸ì\8b­ì\8b\9cì\98¤.\n\nì\8b¤ì\88\98ë¡\9c ê³\84ì \95ì\9d\84 ì\9e\98못 ë§\8cë\93¤ì\97\88ë\8b¤ë©´ ì\9d´ ë©\94ì\8b\9cì§\80ë\8a\94 ë¬´ì\8b\9cí\95´ë\8f\84 ë\90©ë\8b\88ë\8b¤.",
+       "createaccount-text": "ë\88\84êµ°ê°\80ê°\80 {{SITENAME}} ($4)ì\97\90ì\84\9c ì\82¬ì\9a©ì\9e\90 ì\9d´ë¦\84 \"$2\", ë¹\84ë°\80ë²\88í\98¸ \"$3\"ë¡\9c ë\8b¹ì\8b ì\9d\98 ì\9d´ë©\94ì\9d¼ ì£¼ì\86\8cê°\80 ë\93±ë¡\9dë\90\9c ê³\84ì \95ì\9d\84 ë§\8cë\93¤ì\97\88ì\8aµë\8b\88ë\8b¤. \nì§\80ê¸\88 ë¡\9cê·¸ì\9d¸í\95\98ì\97¬ ë¹\84ë°\80ë²\88í\98¸ë¥¼ ë°\94꾸ì\85\94ì\95¼ í\95©ë\8b\88ë\8b¤.\n\nì\8b¤ì\88\98ë¡\9c ê³\84ì \95ì\9d\84 ì\9e\98못 ë§\8cë\93¤ì\97\88ë\8b¤ë©´ ì\9d´ ë©\94ì\8b\9cì§\80ë\8a\94 ë¬´ì\8b\9cí\95´ë\8f\84 ë\90©ë\8b\88ë\8b¤.",
        "login-throttled": "최근 너무 많이 로그인을 시도했습니다.\n$1 뒤에 다시 시도하세요.",
        "login-abort-generic": "로그인에 실패했습니다 - 중지됨",
        "login-migrated-generic": "당신의 계정이 마이그레이션되었으며, 당신의 사용자 이름이 더 이상 이 위키에 존재하지 않습니다.",
        "passwordreset-capture-help": "이 상자에 체크하면 이메일이 발송된 즉시 임시 비밀번호가 담긴 이메일을 볼 수 있습니다.",
        "passwordreset-email": "이메일 주소:",
        "passwordreset-emailtitle": "{{SITENAME}} 계정에 대한 자세한 정보",
-       "passwordreset-emailtext-ip": "$1 IP 주소를 사용하는 누군가가 아마 자신이 {{SITENAME}} ($4)의 비밀번호 재설정을 요청하였습니다.\n이 이메일 주소와 연관된 {{PLURAL:$3|계정}}의 목록입니다:\n\n$2\n\n{{PLURAL:$3|이 임시 비밀번호}}는 {{PLURAL:$5|$5일}} 후에 만료됩니다.\n이 비밀번호로 로그인한 후 비밀번호를 바꾸십시오. 만약 당신이 아닌 다른 사람이 요청하였거나,\n원래의 비밀번호를 기억해냈다면, 이 메시지를 무시하고\n이전의 비밀번호를 계속 사용할 수 있습니다.",
+       "passwordreset-emailtext-ip": "당신일 수도 있는 $1 IP 주소를 사용하는 사용자가 {{SITENAME}} ($4)의 비밀번호 재설정을 요청하였습니다.\n이 이메일 주소와 연관된 {{PLURAL:$3|계정}}의 목록입니다:\n\n$2\n\n{{PLURAL:$3|이 임시 비밀번호}}는 {{PLURAL:$5|1일|$5일}} 후에 만료됩니다.\n이 비밀번호로 로그인한 후 비밀번호를 바꾸십시오. 만약 당신이 아닌 다른 사람이 요청하였거나,\n원래의 비밀번호를 기억해냈다면, 이 메시지를 무시하고\n이전의 비밀번호를 계속 사용할 수 있습니다.",
        "passwordreset-emailtext-user": "{{SITENAME}} ($4)의 사용자 $1이 비밀번호 재설정을 요청하였습니다.\n이 이메일 주소와 연관된 {{PLURAL:$3|계정}}의 목록입니다:\n\n$2\n\n{{PLURAL:$3|이 임시 비밀번호}}는 {{PLURAL:$5|$5일}} 후에 만료됩니다.\n이 비밀번호로 로그인한 후 비밀번호를 바꾸십시오. 만약 당신이 아닌 다른 사람이 요청하였거나,\n원래의 비밀번호를 기억해냈다면, 이 메시지를 무시하고\n이전의 비밀번호를 계속 사용할 수 있습니다.",
        "passwordreset-emailelement": "사용자 이름: \n$1\n\n임시 비밀번호: \n$2",
        "passwordreset-emailsentemail": "당신의 계정과 연결된 이메일 주소가 있다면, 비밀번호 재설정 메일이 전해질 것입니다.",
        "img-auth-public": "img_auth.php는 개인 위키 파일을 바깥 사이트로 전송하는 기능입니다.\n이 기능은 기본적으로 공개적인 위키에서 사용하도록 설계되어 있습니다.\n보안적인 문제로 기본적으로 img_auth.php 기능은 비활성화되어 있습니다.",
        "img-auth-noread": "\"$1\" 파일을 볼 권한이 없습니다.",
        "http-invalid-url": "잘못된 URL: $1",
-       "http-invalid-scheme": "\"$1\"로 시작하는 URL은 지원되지 않습니다.",
+       "http-invalid-scheme": "\"$1\"(으)로 시작하는 URL은 지원되지 않습니다.",
        "http-request-error": "알 수 없는 오류로 HTTP 요청에 실패했습니다.",
        "http-read-error": "HTTP 읽기 오류입니다.",
        "http-timed-out": "HTTP 요청 시간 초과입니다.",
        "protectedarticle": "사용자가 \"[[$1]]\" 문서를 보호했습니다",
        "modifiedarticleprotection": "사용자가 \"[[$1]]\" 문서의 보호 설정을 바꿨습니다",
        "unprotectedarticle": "사용자가 \"[[$1]]\" 문서를 보호 해제했습니다",
-       "movedarticleprotection": "사용자가 문서의 보호 설정을 \"[[$2]]\"에서 \"[[$1]]\"으로 변경했습니다",
+       "movedarticleprotection": "사용자가 문서의 보호 설정을 \"[[$2]]\"에서 \"[[$1]]\"(으)로 이동했습니다",
        "protect-title": "\"$1\" 보호하기",
        "protect-title-notallowed": "\"$1\" 문서의 보호 수준 보기",
        "prot_1movedto2": "[[$1]] 문서를 [[$2]] 문서로 옮김",
        "confirmemail_success": "이메일 주소가 인증되었습니다.\n이제 [[Special:UserLogin|로그인]]해서 위키를 사용하세요.",
        "confirmemail_loggedin": "이메일 주소가 인증되었습니다.",
        "confirmemail_subject": "{{SITENAME}} 이메일 주소 인증",
-       "confirmemail_body": "$1 IP 주소를 사용하는 사용자가\n{{SITENAME}}의 \"$2\" 계정에 이메일 인증 신청을 했습니다.\n\n이 계정이 당신의 계정이고 {{SITENAME}}에서 이메일 기능을 활성화하려면\n아래 주소를 열어서 이메일 인증을 해 주세요:\n\n$3\n\n당신의 계정이 아니라면,\n이메일 인증 신청을 취소하기 위해 아래의 주소를 열어주세요:\n\n$5\n\n인증 코드는 $4에 만료됩니다.",
-       "confirmemail_body_changed": "$1 IP 주소를 사용하는 사용자가\n{{SITENAME}}의 \"$2\" 계정의 이메일 주소를 바꾸었습니다.\n\n이 계정이 당신의 계정이고 {{SITENAME}}에서 이메일 기능을 활성화하려면\n아래 주소를 열어서 이메일 인증을 해 주세요:\n\n$3\n\n당신의 계정이 아니라면,\n이메일 인증 신청을 취소하기 위해 아래의 주소를 열어주세요:\n\n$5\n\n인증 코드는 $4에 만료됩니다.",
-       "confirmemail_body_set": "$1 IP 주소를 사용하는 사용자가\n{{SITENAME}}의 \"$2\" 계정의 이메일 주소를 지정하였습니다.\n\n이 계정이 당신의 계정이고 {{SITENAME}}에서 이메일 기능을\n활성화하려면 아래 주소를 열어서 이메일 인증을 해 주세요:\n\n$3\n\n당신의 계정이 아니라면,\n이메일 인증 신청을 취소하기 위해 아래의 주소를 열어주세요:\n\n$5\n\n인증 코드는 $4에 만료됩니다.",
+       "confirmemail_body": "당신일 수도 있는 $1 IP 주소를 사용하는 사용자가\n{{SITENAME}}의 \"$2\" 계정에 이메일 인증 신청을 했습니다.\n\n이 계정이 당신의 계정이고 {{SITENAME}}에서 이메일 기능을 활성화하려면\n아래 주소를 열어서 이메일 인증을 해 주세요:\n\n$3\n\n당신의 계정이 아니라면,\n이메일 인증 신청을 취소하기 위해 아래의 주소를 열어주세요:\n\n$5\n\n인증 코드는 $4에 만료됩니다.",
+       "confirmemail_body_changed": "당신일 수도 있는 $1 IP 주소를 사용하는 사용자가\n{{SITENAME}}의 \"$2\" 계정의 이메일 주소를 바꾸었습니다.\n\n이 계정이 당신의 계정이고 {{SITENAME}}에서 이메일 기능을 활성화하려면\n아래 주소를 열어서 이메일 인증을 해 주세요:\n\n$3\n\n당신의 계정이 아니라면,\n이메일 인증 신청을 취소하기 위해 아래의 주소를 열어주세요:\n\n$5\n\n인증 코드는 $4에 만료됩니다.",
+       "confirmemail_body_set": "당신일 수도 있는 $1 IP 주소를 사용하는 사용자가\n{{SITENAME}}의 \"$2\" 계정의 이메일 주소를 지정하였습니다.\n\n이 계정이 당신의 계정이고 {{SITENAME}}에서 이메일 기능을\n활성화하려면 아래 주소를 열어서 이메일 인증을 해 주세요:\n\n$3\n\n당신의 계정이 아니라면,\n이메일 인증 신청을 취소하기 위해 아래의 주소를 열어주세요:\n\n$5\n\n인증 코드는 $4에 만료됩니다.",
        "confirmemail_invalidated": "이메일 확인이 취소됨",
        "invalidateemail": "이메일 확인 취소",
        "notificationemail_subject_changed": "{{SITENAME}}의 등록된 이메일 주소가 변경되었습니다",
        "notificationemail_subject_removed": "{{SITENAME}}의 등록된 이메일 주소가 제거되었습니다",
-       "notificationemail_body_changed": "IP 주소 $1에 속하는 누군가가 {{SITENAME}}의 사용자 \"$2\" 계정의 이메일 주소를 \"$3\"으로 변경하였습니다.\n\n지금 이 글을 보고 계신 사용자로 추정되지만 만약 본인이 아닌 경우, 지금 바로 사이트 관리자에게 문의하십시오.",
-       "notificationemail_body_removed": "IP 주소 $1에 속하는 누군가가 {{SITENAME}}의 사용자 \"$2\" 계정의 이메일 주소를 제거하였습니다.\n\n지금 이 글을 보고 계신 사용자로 추정되지만 만약 본인이 아닌 경우, 지금 바로 사이트 관리자에게 문의하십시오.",
+       "notificationemail_body_changed": "당신일 수도 있는 IP 주소 $1에 속하는 사용자가\n{{SITENAME}}의 사용자 \"$2\" 계정의 이메일 주소를 \"$3\"(으)로 바꾸었습니다.\n\n지금 이 글을 보고 계신 사용자로 추정되지만 만약 본인이 아닌 경우, 지금 바로 사이트 관리자에게 문의하십시오.",
+       "notificationemail_body_removed": "당신일 수도 있는 IP 주소 $1에 속하는 사용자가 {{SITENAME}}의 사용자 \"$2\" 계정의 이메일 주소를 제거하였습니다.\n\n지금 이 글을 보고 계신 사용자로 추정되지만 만약 본인이 아닌 경우, 지금 바로 사이트 관리자에게 문의하십시오.",
        "scarytranscludedisabled": "[인터위키가 비활성되어 있습니다]",
        "scarytranscludefailed": "[$1 틀을 불러오는 데에 실패했습니다]",
        "scarytranscludefailed-httpstatus": "[$1 틀을 가져오는 데 실패했습니다: HTTP $2]",
        "table_pager_limit_submit": "확인",
        "table_pager_empty": "결과 없음",
        "autosumm-blank": "문서를 비움",
-       "autosumm-replace": "문ì\84\9c ë\82´ì\9a©ì\9d\84 \"$1\"ì\9c¼로 바꿈",
+       "autosumm-replace": "ë\82´ì\9a©ì\9d\84 \"$1\"(ì\9c¼)로 바꿈",
        "autoredircomment": "[[$1]] 문서로 넘겨주기",
        "autosumm-new": "새 문서: $1",
        "autosumm-newblank": "빈 문서를 만듦",
        "revdelete-unrestricted": "관리자에 대한 제한을 해제함",
        "logentry-block-block": "$1님이 {{GENDER:$4|$3}}님을 $5 {{GENDER:$2|차단했습니다}} $6",
        "logentry-block-unblock": "$1님이 {{GENDER:$4|$3}} 사용자의 {{GENDER:$2|차단을 해제했습니다}}",
-       "logentry-block-reblock": "$1님이 {{GENDER:$4|$3}} 사용자의 차단 기간을 $5 설정으로 {{GENDER:$2|바꾸었습니다}} $6",
+       "logentry-block-reblock": "$1님이 {{GENDER:$4|$3}}의 차단 기간을 $5 설정으로 {{GENDER:$2|바꾸었습니다}} $6",
        "logentry-suppress-block": "$1님이 {{GENDER:$4|$3}} 사용자를 $5 {{GENDER:$2|차단했습니다}} $6",
        "logentry-suppress-reblock": "$1님이 {{GENDER:$4|$3}}님의 차단 기간을 $5 설정으로 {{GENDER:$2|바꾸었습니다}} $6",
        "logentry-import-upload": "$1님이 $3 문서를 파일 올리기로 {{GENDER:$2|가져왔습니다}}",
index 08675d6..bdb0a1d 100644 (file)
        "filerevert-comment": "Raxon:",
        "filerevert-defaultcomment": "Ripristinou a verscion do $2, $1 ($3)",
        "filerevert-submit": "Ripristina",
+       "filerevert-success": "'''O file [[Media:$1|$1]]''' o l'è stæto ripristinou a-a [$4 verscion do $2, $3].",
+       "filerevert-badversion": "No gh'è de verscioin locali precedenti do file co-o timestamp provisto.",
+       "filedelete": "Scassa \"$1\"",
+       "filedelete-legend": "Scassa o file",
+       "filedelete-intro": "Ti stæ pe scassâ o file '''[[Media:$1|$1]]''' con tutta a so cronologia.",
+       "filedelete-intro-old": "T'ê aproeuvo a scassâ a vercsion de '''[[Media:$1|$1]]''' do [$4 $2, $3].",
+       "filedelete-comment": "Raxon:",
        "filedelete-submit": "Scassa",
+       "filedelete-success": "O file '''$1''' o l'è stæto scassou.",
+       "filedelete-success-old": "A verscion do file '''[[Media:$1|$1]]''' do $2, $3  a l'è stæta scassaa.",
+       "filedelete-nofile": "'''$1''' o no l'existe.",
+       "filedelete-nofile-old": "Verscioin d'archivio de '''$1''' co-e caratteristeghe indicæ no ghe n'è.",
+       "filedelete-otherreason": "Atra motivaçion ò motivaçion azontiva:",
+       "filedelete-reason-otherlist": "Un'atra raxon",
+       "filedelete-reason-dropdown": "*Raxoin comun-e de scançellaçion\n** Violaçion do drito d'aotô\n** File dupricou",
+       "filedelete-edit-reasonlist": "Modiffica e raxoin da scassatua",
+       "filedelete-maintenance": "Scançellaçion e ricuppero di file temporaniamente disattivæ durante a manutençion.",
+       "filedelete-maintenance-title": "Imposcibbile eliminâ o file",
        "mimesearch": "Çerca MIME",
+       "mimesearch-summary": "Questa pagina a consente de filtrâ i file in base a-o tipo MIME.\nInsei a stringa de riçerca inta forma tipo/sottotipo o tipo/*, pr'es. <code>image/jpeg</code>.",
+       "mimetype": "Tipo MIME:",
+       "download": "scarrega",
+       "unwatchedpages": "Paggine non öservæ",
        "listredirects": "Lista de rindirissamenti",
+       "listduplicatedfiles": "Lista di file doggi",
+       "listduplicatedfiles-summary": "Questo o l'è un elenco di file, donde a verscion ciù reçente de 'n file a l'è un dupricou da verscion ciù reçente de 'n atro file. Se piggia in  conscideraçion solo che i file locali.",
+       "listduplicatedfiles-entry": "[[:File:$1|$1]] o g'ha [[$3|{{PLURAL:$2|un duplicou|$2 duplicæ}}]].",
        "unusedtemplates": "Template no ûtilissæ",
+       "unusedtemplatestext": "Inte sta pagina l'è elencou e pagine do namespace {{ns:template}} che no son incluse inte nisciun-a pagina. Primma de scassâle l'è öportun veificâ che i scingoli template no g'aggian di atri collegamenti intranti.",
+       "unusedtemplateswlh": "atri collegamenti",
        "randompage": "Pagina a brettìo",
+       "randompage-nopages": "Pagine {{PLURAL:$2|into seguente|inti seguenti}} namespace no ghe n'è: $1.",
+       "randomincategory": "Pagina a brettio inta categoria",
+       "randomincategory-invalidcategory": "\"$1\" o no l'è un nomme de categoria vallido.",
+       "randomincategory-nopages": "Paggine in [[:Category:$1]] no ghe n'è.",
+       "randomincategory-category": "Categoria:",
+       "randomincategory-legend": "Pagina a brettio inta categoria",
+       "randomincategory-submit": "Vanni",
        "randomredirect": "Ûn rindirissamento a brettîo",
+       "randomredirect-nopages": "Into namespace \"$1\" redirect no ghe n'è.",
        "statistics": "Statistiche",
+       "statistics-header-pages": "Statistiche de pagine",
+       "statistics-header-edits": "Statistiche de modifiche",
+       "statistics-header-users": "Statistiche di utenti",
+       "statistics-header-hooks": "Atre statistiche",
+       "statistics-articles": "Paggine de contegnui",
+       "statistics-pages": "Paggine",
+       "statistics-pages-desc": "Tutte e paggine do scito, compreiso e paggine de discuscion, i redirect, etç.",
+       "statistics-files": "File caregæ",
+       "statistics-edits": "Modifiche a partî dal l'installaçion de {{SITENAME}}",
+       "statistics-edits-average": "Meddia de modiffiche pe paggina",
+       "statistics-users": "[[Special:ListUsers|Utenti]] registræ",
+       "statistics-users-active": "Utenti attivi",
+       "statistics-users-active-desc": "Utenti che han effettuou un'açion {{PLURAL:$1|inte l'urtimo giorno|inti urtimi $1 giorni}}",
+       "pageswithprop": "Paggine co-ina propietæ de paggina",
+       "pageswithprop-legend": "Paggine co-ina propietæ de paggina",
+       "pageswithprop-text": "Questa pagina a fa 'na lista de paggine ch'adoeuvian una particolâ propietæ de paggina.",
+       "pageswithprop-prop": "Nomme da propietæ:",
+       "pageswithprop-submit": "Vanni",
+       "pageswithprop-prophidden-long": "valô testoale longo da propietæ ascoso ($1)",
+       "pageswithprop-prophidden-binary": "valô binaio da propietæ ascoso ($1)",
        "doubleredirects": "Rindirissamenti doggi",
+       "doubleredirectstext": "In questa pagina gh'è elencou e paggine che rendiriççan a di atre paggine de redirect.\nOgni riga a conten i collegamenti a-o primmo e a-o segondo redirect, oltre a-a primma riga de testo do segondo redirect che a l'uso o conten a paggina de destinaçion \"corretta\" a-a quæ doviæ puntâ o primmo redirect ascì.\nI redirect <del>scassæ</del> son stæti corretti.",
+       "double-redirect-fixed-move": "[[$1]] o l'è stæto mesciou.\nO l'è stato aggiornou aotomaticamente e oua o l'è un redirect a [[$2]].",
+       "double-redirect-fixed-maintenance": "Corretto aotomaticamente o redirect doggio da [[$1]] a [[$2]] into travaggio de manutençion.",
+       "double-redirect-fixer": "Correttô di redirect",
        "brokenredirects": "Rindirissamenti sballiæ",
-       "brokenredirectstext": "De sotta unn-a lista de reindirissi a pagine che non existàn:",
+       "brokenredirectstext": "I rendriççi chì de sotta colegan a de paggine inexistente:",
        "brokenredirects-edit": "cangia",
        "brokenredirects-delete": "scassa",
        "withoutinterwiki": "Paggine sensa interwiki",
        "withoutinterwiki-summary": "'Ste paggine chì inzû no g'han nisciûn collegamento co-e verscioîn in âtre lengoe:",
+       "withoutinterwiki-legend": "Prefisso",
+       "withoutinterwiki-submit": "Mostra",
        "fewestrevisions": "Voxi con meno revixoîn",
        "nbytes": "$1 {{PLURAL:$1|byte|byte}}",
+       "ncategories": "$1 {{PLURAL:$1|categoria|categorie}}",
+       "ninterwikis": "$1 {{PLURAL:$1|interwiki}}",
        "nlinks": "$1 {{PLURAL:$1|collegamento|collegamenti}}",
        "nmembers": "$1 {{PLURAL:$1|elemento|elementi}}",
+       "nmemberschanged": "$1 → $2 {{PLURAL:$2|elemento|elementi}}",
+       "nrevisions": "$1 {{PLURAL:$1|revixon|revixoin}}",
+       "nimagelinks": "Doeuviou inte $1 {{PLURAL:$1|paggina|paggine}}",
+       "ntransclusions": "Doeuviou inte $1 {{PLURAL:$1|paggina|paggine}}",
+       "specialpage-empty": "Questa paggina speciale a l'è attualmente voeua.",
        "lonelypages": "Paggine orfane",
+       "lonelypagestext": "E seguente paggine no son incluse ni colegæ a di atre paggine de {{SITENAME}}.",
        "uncategorizedpages": "Paggine sensa categorîa",
        "uncategorizedcategories": "Categorîe sensa categorîa",
        "uncategorizedimages": "Immaggini sensa categorîa",
        "unusedimages": "Archivvi no ûtilissæ",
        "wantedcategories": "Categorîe domandæ",
        "wantedpages": "Paggine domandæ",
+       "wantedpages-summary": "Lista de paggine inexistente co-o ciu gran nummero de collegamenti a lô, escludendo e pagine ch'han solo che i rendiriççi che-e collegan. Pe 'n elenco de pagine inexistente che g'han di rendriççi che-e collegan, amia [[{{#special:BrokenRedirects}}|a lista di rendriççi erræ]].",
+       "wantedpages-badtitle": "Tittolo invallido into groppo di risultæ: $1",
+       "wantedfiles": "File domandæ",
+       "wantedfiletext-cat": "I seguenti file son in doeuvia, ma no existan. I file ospitæ inte di repository esterni porieivan esighe elencæ sciben che existan. Questi fasci poxitivi saian <del>barræ</del>. E pagine che incòrpoan i file che no existan son elencæ in [[:$1]].",
+       "wantedfiletext-cat-noforeign": "I seguenti file son in doeuvia, ma no existan. Inoltre, e pagine che incòrpoan questi file son elencæ inta [[:$1]].",
+       "wantedfiletext-nocat": "I seguenti file son reciamæ da wikilink, ma no existan. I file ospitæ inte di repository esterni porieivan ese elencæ sciben existenti. Questi fasci poxitivi saian <del>barræ</del>.",
+       "wantedfiletext-nocat-noforeign": "I seguenti file son in doeuvia, ma no existan.",
+       "wantedtemplates": "Template domandæ",
        "mostlinked": "Paggine ciû collegæ",
        "mostlinkedcategories": "Categorîe ciû collegæ",
-       "mostlinkedtemplates": "Template ciû dêuviæ",
+       "mostlinkedtemplates": "Paggine ciu incluse",
        "mostcategories": "Voxi con ciû categorîe",
        "mostimages": "Immaggini con ciû collegamenti",
+       "mostinterwikis": "Paggine con ciu interwiki",
        "mostrevisions": "Voxi con ciû revixoîn",
        "prefixindex": "Indiçe arfabetico de voxe",
+       "prefixindex-namespace": "Tutte e paggine co-o prefisso do namespace $1",
+       "prefixindex-submit": "Mostra",
+       "prefixindex-strip": "Ascondi o prefisso inta lista",
        "shortpages": "Paggine ciû cûrte",
        "longpages": "Paggine ciû longhe",
        "deadendpages": "Paggine sensa sciortîa",
+       "deadendpagestext": "E seguente paggine no son collegæ a di atre paggine de {{SITENAME}}.",
        "protectedpages": "Paggine protette",
+       "protectedpages-indef": "Solo proteçioin indefinie",
+       "protectedpages-summary": "Questa paggina a l'elenca e paggine existente ch'en attoalmente protezue. Pe 'n elenco di tittoli protezui da-a creaçion, amia [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].",
+       "protectedpages-cascade": "Solo proteçioin ricorscive",
+       "protectedpages-noredirect": "Ascondi redirect",
+       "protectedpagesempty": "Paggine protette con sti parametri pe-o momento no ghe n'è.",
+       "protectedpages-timestamp": "Dæta e oa",
+       "protectedpages-page": "Paggina",
+       "protectedpages-expiry": "Scadença",
+       "protectedpages-performer": "Protetta da l'utente",
+       "protectedpages-params": "Parammetri de proteçion",
+       "protectedpages-reason": "Raxon",
+       "protectedpages-submit": "Mostra paggine",
+       "protectedpages-unknown-timestamp": "Sconosciuo",
+       "protectedpages-unknown-performer": "Utente sconosciuo",
        "protectedtitles": "Tittoli protezûi",
+       "protectedtitles-summary": "Questa pagina a l'elenca i titoli che son attualmente protetti da-a creaçion. Pe 'n elenco de pagine existente che son protette, amia [[{{#special:ProtectedPages}}|{{int:protectedpages}}]].",
+       "protectedtitlesempty": "Tittoli protezui con sti parammetri pe-o momento no ghe n'è.",
+       "protectedtitles-submit": "Mostra tittoli",
        "listusers": "Lista d'ûtenti",
+       "listusers-editsonly": "Mostra solo i utenti con di contributi",
+       "listusers-creationsort": "Ordina pe dæta de creaçion",
+       "listusers-desc": "Ordina in senso decrescente",
+       "usereditcount": "$1 {{PLURAL:$1|contributo|contributi}}",
        "usercreated": "{{GENDER:$3|Creòu/â}} o $1 a-a $2",
        "newpages": "Pagine ciù reçenti",
+       "newpages-submit": "Mostra",
+       "newpages-username": "Nomme utente",
        "ancientpages": "Paggine ciû vëgie",
        "move": "Mescia",
        "movethispage": "Mescia 'sta paggina",
+       "unusedimagestext": "I seguenti file existan ma no son doeuviæ inte nisciun-a paggina.\nNotta che di atri sciti web porieivan ese colegæ a 'n file co-in URL diretto, e coscì o poriæ ese inte sta lista sciuben ch'o segge in doeuvia.",
+       "unusedcategoriestext": "E seguente paggine de categoria existan, sciben che nisciun'atra paggina o categoria a-e doeuvie.",
+       "notargettitle": "Dæti mancanti",
+       "notargettext": "No t'hæ indicou una pagina o un utente con chi eseguî sta fonçion.",
+       "nopagetitle": "A pagina de destinaçion a no l'existe",
+       "nopagetext": "A pagina domandâ a no l'existe.",
        "pager-newer-n": "{{PLURAL:$1|1 ciù nêuvo|$1 ciù nêuvi}}",
        "pager-older-n": "{{PLURAL:$1|1 ciù vêgio|$1 ciù vêgi}}",
+       "suppress": "Soprimmi",
+       "querypage-disabled": "Questa pagina speciale a l'è disattivâ pe motivi de prestaçion.",
+       "apihelp": "Agiutto API",
+       "apihelp-no-such-module": "Moddulo \"$1\" non trovou.",
+       "apisandbox": "Paggina de proeuva API",
+       "apisandbox-jsonly": "Pe doeuviâ a paggina de proeuva API ghe voeu o JavaScript.",
+       "apisandbox-api-disabled": "E fonçionalitæ API son disabilitæ insce questo scito.",
+       "apisandbox-intro": "Doeuvia sta paggina pe fâ prattica co-e <strong>API web service MediaWiki</strong>.\nPe di urteioî detaggi de utilizzo de API, amia a [[mw:API:Main page|documentaçion API]]. Exempio: [https://www.mediawiki.org/wiki/API#A_simple_example ötegnî o contegnuo da paggina prinçipâ]. Seleçion-a un'açion pe vedde di atri exempi.\n\nNotta che, sciben che questa a segge 'na paggina pe-e proeuve, i açioin che ti esegui chì porieivan modificâ a wiki.",
+       "apisandbox-fullscreen": "Espandi pannello",
+       "apisandbox-fullscreen-tooltip": "Espandi o pannello sandbox pe impî o barcon do browser.",
+       "apisandbox-unfullscreen": "Mostra a pagina",
+       "apisandbox-unfullscreen-tooltip": "Reduxi o pannello sandbox, coscì che i collegamenti de navigaçion MediaWiki seggian disponibbili.",
+       "apisandbox-submit": "Inandia recesta",
+       "apisandbox-reset": "Nettezza",
+       "apisandbox-retry": "Ritenta",
+       "apisandbox-loading": "Caregamento de informaçioin pe-o moddulo API \"$1\"...",
+       "apisandbox-load-error": "S'è veificou un errô durante o caregamento de informaçioin pe-o moddulo API \"$1\": $2",
+       "apisandbox-no-parameters": "Questo modulo API o no g'ha de parammetri.",
+       "apisandbox-helpurls": "Collegamenti a-a guidda",
+       "apisandbox-examples": "Exempi",
+       "apisandbox-dynamic-parameters": "Parammetri azontivi",
+       "apisandbox-dynamic-parameters-add-label": "Azonzi parammetro:",
+       "apisandbox-dynamic-parameters-add-placeholder": "Nomme do parammetro",
+       "apisandbox-dynamic-error-exists": "Un parammetro denominou \"$1\" o l'existe za.",
+       "apisandbox-deprecated-parameters": "Parammetri sconsegiæ",
+       "apisandbox-fetch-token": "Aoto-compilla o token",
+       "apisandbox-submit-invalid-fields-title": "Gh'è di campi che n'en vallidi",
+       "apisandbox-submit-invalid-fields-message": "Correzi i campi evidençiæ e riproeuva.",
+       "apisandbox-results": "Risultæ",
+       "apisandbox-sending-request": "Invio recesta de API...",
+       "apisandbox-loading-results": "Riceçion di risultæ de API in corso...",
+       "apisandbox-results-error": "S'è veificou un errô durante o caregamento da risposta a l'interrogaçion API: $1",
+       "apisandbox-request-url-label": "URL de recesta:",
+       "apisandbox-request-time": "Tempo richiesto: {{PLURAL:$1|$1 ms}}",
+       "apisandbox-results-fixtoken": "Correzi token e reinvia",
+       "apisandbox-results-fixtoken-fail": "Imposcibile recuperâ o token \"$1\".",
+       "apisandbox-alert-page": "I campi insce questa pagina no son vallidi.",
+       "apisandbox-alert-field": "O valô de questo campo o no l'è vallido.",
        "booksources": "Fonte libraie",
        "booksources-search-legend": "Çerca e fonti",
        "booksources-isbn": "Codice ISBN:",
        "booksources-search": "Çerca",
        "booksources-text": "De sotta unn-a lista de inganci a di ätri sciti che vendan libbri neuvi e vegi e che porrieivan avei ciu informaçioin in scî libbri che ti te çerchi",
-       "specialloguserlabel": "Ûtente:",
-       "speciallogtitlelabel": "Tittolo:",
+       "booksources-invalid-isbn": "O ISBN inserio pâ no ese vallido; controlla che no ghe segge stæto di ari into copiâlo da-a fonte originale.",
+       "specialloguserlabel": "Açion effettuâ da:",
+       "speciallogtitlelabel": "Açion effettuâ sciu (tittolo da paggina ò {{ns:user}}:Nomme utente):",
        "log": "Log",
-       "all-logs-page": "Tûtti i registri",
-       "alllogstext": "Presentaçion unega de tutti i registri do scito {{SITENAME}}.\nTi te peu strinza a vista se ti te çerni un tipo de registro, un nomme de un utente o de pagina.",
+       "logeventslist-submit": "Mostra",
+       "all-logs-page": "Tutti i registri pubbrichi",
+       "alllogstext": "Presentaçion combinaa de tutti i registri de {{SITENAME}}.\nL'è poscibile restrenze i critei de riçerca seleçionando o tipo de registro, l'utente ch'o l'ha eseguio l'açion, e/ò a pagina interessâ (entrambi i campi son senscibbili a-o maiuscolo/minuscolo).",
+       "logempty": "O registro o no conten di elementi corespondenti a-a riçerca.",
+       "log-title-wildcard": "Riçerca di titoli che començan con",
+       "showhideselectedlogentries": "Mostra/ascondi e voxe de registro seleçionæ",
+       "log-edit-tags": "Modifica i etichette de voxe de registro seleçionæ",
+       "checkbox-select": "Seleçion-a: $1",
+       "checkbox-all": "Tutto",
+       "checkbox-none": "Nisciun",
+       "checkbox-invert": "Inverti",
        "allpages": "Tûtte e paggine",
        "nextpage": "Proscima paggina ($1)",
        "prevpage": "Paggina preçedente ($1)",
        "allpagesfrom": "Fanni vedde e paggine comensando da:",
+       "allpagesto": "Mostra e paggine scin a:",
        "allarticles": "Tùtte e pàgine",
        "allinnamespace": "Tutte e pagine ($1 namespace)",
        "allpagessubmit": "Vanni",
        "allpagesprefix": "Fanni vedde e paggine che inissian con:",
        "allpagesbadtitle": "O tittolo dæto a-a paggina o non va ben, òpû o conten di prefissi inter-lengua o inter-wiki. O porriæ ascì contegnî un o ciù caratteri che inti tittoli no se peuan deuviâ.",
        "allpages-bad-ns": "\"$1\" o no ghe in {{SITENAME}}.",
+       "allpages-hide-redirects": "Ascondi i redirect",
+       "cachedspecial-viewing-cached-ttl": "T'ê aproeuvo a 'miâ 'na verscion de sta paggina memorizzâ inta cache, ch'a poeu ese vegia de $1 a-o mascimo.",
+       "cachedspecial-viewing-cached-ts": "T'ê aproeuvo a 'miâ 'na verscion de sta paggina memorizzâ inta cache, ch'a poriæ no ese do tutto aggiornâ.",
+       "cachedspecial-refresh-now": "Mostra a ciù reçente.",
        "categories": "Categorîe",
+       "categories-submit": "Mostra",
+       "categoriespagetext": "{{PLURAL:$1|A seguente categoria a conten|E seguente categorie contengnan}} de pagine o di file murtimediali.\nE [[Special:UnusedCategories|categorie voeue]] no son mostræ chì.\nAmia ascì e [[Special:WantedCategories|categorie richieste]].",
+       "categoriesfrom": "Mostra e categorie a partî da:",
+       "deletedcontributions": "Contributi utente scassæ",
+       "deletedcontributions-title": "Contributi utente scassæ",
+       "sp-deletedcontributions-contribs": "contribuçioin",
+       "linksearch": "Çerchia di inganci esterni",
+       "linksearch-pat": "Pattern de riçerca:",
+       "linksearch-ns": "Namespace:",
+       "linksearch-ok": "Çerca",
+       "linksearch-text": "L'è poscibbile doeuviâ di metacaratteri comme \"*.wikipedia.org\".\nL'è necessaio a-o manco un dominnio di primmo livello, presempio \"*.org\".<br />\n{{PLURAL:$2|Protocollo supportou|Protocolli supportæ}}: $1 (predefinio http:// se no l'è specificou nisciun protocollo).",
        "linksearch-line": "$1 colegòu a-a pagina $2",
+       "linksearch-error": "I metacarattei poeuan ese usati solo a-o prinçippio de l'indiriçço.",
+       "listusersfrom": "Mostra i utenti a partî da:",
        "listusers-submit": "Fanni vedde",
        "listusers-noresult": "Utente non trovöo.",
+       "listusers-blocked": "(bloccou)",
+       "activeusers": "Lista di utenti attivi",
+       "activeusers-intro": "Questo o l'è un elenco di utenti ch'han avuo quarche tipo d'attivitæ da $1 {{PLURAL:$1|giorno|giorni}} a questa parte.",
+       "activeusers-count": "$1 {{PLURAL:$1|açione|açioin}} {{PLURAL:$3|inte l'urtimo giorno|inti urtimi $3 giorni}}",
+       "activeusers-from": "Mostra i utenti a partî da:",
+       "activeusers-hidebots": "Ascondi i bot",
+       "activeusers-hidesysops": "Ascondi i amministratoî",
+       "activeusers-noresult": "Nisciun utente o risponde a-i critei impostæ.",
+       "activeusers-submit": "Mostra utenti attivi",
+       "listgrouprights": "Driti do groppo utente",
+       "listgrouprights-summary": "De seguito l'è elencou i groppi utente definii pe questo scito, co-i so driti d'accesso associæ.\nPorieiva esighe di [[{{MediaWiki:Listgrouprights-helppage}}|urteioî informaçioin]] in scî driti individoali.",
+       "listgrouprights-key": "Legenda:\n* <span class=\"listgrouprights-granted\">Drito assegnou</span>\n* <span class=\"listgrouprights-revoked\">Drito revocou</span>",
+       "listgrouprights-group": "Groppo",
+       "listgrouprights-rights": "Driti",
+       "listgrouprights-helppage": "Help:Driti do groppo",
        "listgrouprights-members": "(Elenco di membri)",
+       "listgrouprights-addgroup": "O poeu azonze {{PLURAL:$2|a-o groppo|a-i groppi}}: $1",
+       "listgrouprights-removegroup": "O poeu rimoeuve {{PLURAL:$2|da-o groppo|da-i groppi}}: $1",
+       "listgrouprights-addgroup-all": "O poeu azonze a tutti i groppi",
+       "listgrouprights-removegroup-all": "O poeu rimoeuve da tutti i groppi",
+       "listgrouprights-addgroup-self": "O poeu azonzise {{PLURAL:$2|a-o groppo|a-i groppi}}: $1",
+       "listgrouprights-removegroup-self": "O poeu rimoeuvise {{PLURAL:$2|da-o groppo|da-i groppi}}: $1",
+       "listgrouprights-addgroup-self-all": "O poeu azonzise a tutti i groppi",
+       "listgrouprights-removegroup-self-all": "O poeu rimoeuvise da tutti i groppi",
+       "listgrouprights-namespaceprotection-header": "Restriçioin pe namespace",
+       "listgrouprights-namespaceprotection-namespace": "Namespace",
+       "listgrouprights-namespaceprotection-restrictedto": "Drito ch'o consente a l'utente de modificâ",
+       "listgrants": "Assegnaçioin",
+       "listgrants-summary": "De seguito l'è riportou un elenco de concescioin, co-i so driti utente associæ. I utenti poeuan aotorizzâ e appricaçioin a doeuviâ a proppia utença, ma con di aotorizzaçioin limitæ in base a-e assegnaçioin che l'utente o l'ha dæto a l'appricaçion. Tuttavia, un'appricaçion ch'a l'agisce pe conto de 'n utente a no poeu effettivamente doeuviâ i driti di quæ l'utente o no dispon-e.\nGhe poriæ ese di [[{{MediaWiki:Listgrouprights-helppage}}|urteioî informaçioin]] in scî driti individoali.",
+       "listgrants-grant": "Assegnaçion",
+       "listgrants-rights": "Driti",
+       "trackingcategories": "Categorie de monitoraggio",
+       "trackingcategories-summary": "Questa pagina a l'elenca e categorie de monitoraggio che vegnan popolæ aotomaticamente da-o software MediaWiki. I so nommi poeuan ese cangiæ modificando i relativi messaggi de scistema into namespace {{ns:8}}.",
+       "trackingcategories-msg": "Categoria de monitoraggio",
+       "trackingcategories-name": "Nomme do messaggio",
+       "trackingcategories-desc": "Critei pe l'incluxon intaa categoria",
+       "restricted-displaytitle-ignored": "Pagine con di tittoli da vixualizzâ ignoræ",
+       "restricted-displaytitle-ignored-desc": "A pagina a g'ha un <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> ignorou perché a no l'è equivalente a l'effettivo tittolo da pagina.",
+       "noindex-category-desc": "A pagina a no l'è indiçizzâ da-i robot perché a conten a paola magica <code><nowiki>__NOINDEX__</nowiki></code> e a s'atroeuva inte 'n namespace donde tâ flag a l'è consentia.",
+       "index-category-desc": "A pagina a conten <code><nowiki>__INDEX__</nowiki></code> (e a s'atroeuva inte 'n namespace donde tâ flag a l'è consentia) e quindi a l'è indiçizâ da-i robot, sci ben che normalmente a no-o saieiva.",
+       "post-expand-template-inclusion-category-desc": "A dimenscion da pagina a saiâ ciù grande de <code>$wgMaxArticleSize</code> doppo avei espanso tutti i template, e coscì çerti template no son stæti espansci.",
+       "post-expand-template-argument-category-desc": "A pagina a saiâ ciù grande de <code>$wgMaxArticleSize</code> doppo aver espanso o parametro de un template (quarcosa tra træ parentexi graffe, comme <code>{{{Foo}}}</code>).",
+       "expensive-parserfunction-category-desc": "A pagina a l'adoeuvia troppe fonçioin parser (come <code>#ifexist</code>). Amia [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgExpensiveParserFunctionLimit Manual:$wgExpensiveParserFunctionLimit].",
+       "broken-file-category-desc": "A pagina a conten un collegamento interotto a un file (un collegamento pe incorpoâ un file quande questo o no l'existe).",
+       "hidden-category-category-desc": "Questa categoria a conten <code><nowiki>__HIDDENCAT__</nowiki></code> inta so pagina, o quæ o l'impedisce ch'a segge mostrâ, in moddo predefinio, into riquaddro di collegamenti a-e categorie de pagine.",
+       "trackingcategories-nodesc": "Nisciun-a descriçion disponibbile.",
+       "trackingcategories-disabled": "A categoria a l'è disabilitâ",
+       "mailnologin": "Nisciun adreçço a chi mandâ o messaggio",
+       "mailnologintext": "Pe inviâ di messaggi e-mail a di atri utenti l'è necessaio [[Special:UserLogin|accede a-o scito]] e avei registrou un adreçço vallido inte proppie [[Special:Preferences|preferençe]].",
        "emailuser": "Invia 'na email a st'utente chi",
-       "defemailsubject": "{{SITENAME}} posta elettronega",
+       "emailuser-title-target": "Invia un'email a questo {{GENDER:$1|utente}}",
+       "emailuser-title-notarget": "Invia una email a un utente",
+       "emailpagetext": "Doeuvia o moddulo sottostante pe inviâ un messaggio e-mail a l'{{GENDER:$1|utente}} indicou. L'adreçço speçificou inte [[Special:Preferences|preferençe]] do mittente o l'appariâ into campo \"Da:\" do messaggio pe consentî a-o destinataio de risponde direttamente.",
+       "defemailsubject": "Messaggio da {{SITENAME}} da l'utente \"$1\"",
+       "usermaildisabled": "e-mail utente disabilitâ",
+       "usermaildisabledtext": "No l'è poscibbile inviâ de e-mail a di atri utenti insce questo wiki",
        "noemailtitle": "Nisciûn conto e-mail",
-       "emailfrom": "Da",
-       "emailto": "A",
-       "emailsubject": "Argumento",
-       "emailmessage": "Comunicaçion",
+       "noemailtext": "Questo utente o no l'ha indicou un adreçço e-mail vallido.",
+       "nowikiemailtext": "Questo utente o l'ha scerto de no riçeive messaggi de posta elettronica da-i atri utenti.",
+       "emailnotarget": "Nomme utente do destinataio inexistente o non vallido.",
+       "emailtarget": "Inseisci o nomme utente do destinataio",
+       "emailusername": "Nomme utente",
+       "emailusernamesubmit": "Invia",
+       "email-legend": "Invia un messaggio e-mail a un atro utente de {{SITENAME}}",
+       "emailfrom": "Da:",
+       "emailto": "A:",
+       "emailsubject": "Sogetto:",
+       "emailmessage": "Messaggio:",
        "emailsend": "Spèdi",
        "emailccme": "Mandame unn-a copia do messagio co unn-a lettìa elettronega.",
+       "emailccsubject": "Coppia do messaggio inviou a $1: $2",
        "emailsent": "Lettìa elettronega spèdïa",
        "emailsenttext": "A teua lettìa elettronega a l'è stæta spèdïa.",
+       "emailuserfooter": "Questa email a l'è stæta {{GENDER:$1|inviâ}} da $1 a {{GENDER:$2|$2}} a traverso a fonçion \"{{int:emailuser}}\" insce {{SITENAME}}.",
+       "usermessage-summary": "Messaggio de scistema",
+       "usermessage-editor": "Messaggê de scistema",
        "watchlist": "Sotta osservassion",
        "mywatchlist": "Sotta oservaçion",
        "watchlistfor2": "Pe $1 $2",
+       "nowatchlist": "A lista di öservæ speciali a l'è voeua.",
+       "watchlistanontext": "Pe vixualizzâ e modificâ l'elenco di öservæ l'è necessaio eseguî l'accesso.",
        "watchnologin": "Non ti t'æ entroö",
-       "addedwatchtext": "A paggina \"[[:$1]]\" a l'è stæta azzonta a-a pròpia [[Special:Watchlist|lista in osservaçion]]. De chì in avanti, i cangiamenti fæti a-a paggina e a-a sêu discûxon sajàn missi in lista lì; o tittolo da paggina o sajà scrîo in '''grascietto''' inta paggina di [[Special:RecentChanges|ûrtimi cangiamenti]] coscì ti o veddi megio. Se ti vêu eliminâla da-a lista in osservaçion ciû târdi, sciacca \"no seguî\" inscia barra de d'âto.",
-       "removedwatchtext": "A paggina \"[[:$1]]\" a l'è stæta scassâa da-a têu lista in osservaçion.",
+       "addwatch": "Azonzi a-a lista sotta öservaçion",
+       "addedwatchtext": "\"[[:$1]]\" e a so paggina de discuscion son stæte azonte a-a proppia [[Special:Watchlist|lista di öservæ]].",
+       "addedwatchtext-short": "A pagina \"$1\" a l'è stæata azonta a-a proppia lista di öservæ.",
+       "removewatch": "Rimoeuvi da-i öservæ speciali",
+       "removedwatchtext": "\"[[:$1]]\" e a so paggina de discuscion son stæte rimosse da-a proppia [[Special:Watchlist|lista di öservæ]].",
+       "removedwatchtext-short": "A pagina \"$1\" a l'è stæata rimossa da-a proppia lista di öservæ.",
        "watch": "Metti sotta oservaçion",
        "watchthispage": "Vigilâ 'sta paggina",
        "unwatch": "Leva da sott'oservaçion",
-       "watchlist-details": "A lista d'oservaçión speçiâle a contegne {{PLURAL:$1|ina pàgina (co-a seu pàgina de discusción)|$1 de pàgine (co-e so pàgine de discusción)}}.",
-       "wlshowlast": "Famme vedde e ûrtime $1 ôe $2 giorni",
+       "unwatchthispage": "Smetti de öservâ",
+       "notanarticle": "Questa paggina a no l'è una voxe",
+       "notvisiblerev": "L'urtima revixon a l'è stæta scassâ",
+       "watchlist-details": "A lista di öservæ speciali a conten {{PLURAL:$1|una pagina (e a rispettiva pagina de discuscion)|$1 pagine (e e rispettive pagine de discuscion)}}.",
+       "wlheader-enotif": "A notiffica via email a l'è attiva.",
+       "wlheader-showupdated": "E paggine che son stæte modificæ doppo l'urtima vixita son evidençiæ in '''grascetto'''.",
+       "wlnote": "De sotta {{PLURAL:$1|a l'è elencâ a modifica ciù reçente apportâ|son elencæ e <strong>$1</strong> modifiche ciù reçente apportæ}} {{PLURAL:$2|inte l'urtima oa|inti urtime <strong>$2</strong> oe}}; i dæti son aggiornæ a-e $4 do $3.",
+       "wlshowlast": "Mostra i urtime $1 oe $2 giorni",
+       "watchlist-hide": "Ascondi",
+       "watchlist-submit": "Mostra",
+       "wlshowtime": "Periodo de tempo da vixualizzâ:",
+       "wlshowhideminor": "cangiamenti menoî",
+       "wlshowhidebots": "Bot",
+       "wlshowhideliu": "utenti registræ",
+       "wlshowhideanons": "utenti anonnimi",
+       "wlshowhidepatr": "cangiaménti controllæ",
+       "wlshowhidemine": "e mæ modiffiche",
+       "wlshowhidecategorization": "categorizzaçion da paggina",
        "watchlist-options": "Inpostaçioìn di oservæ speciâli",
        "watching": "Inti osservæ speçiâli...",
        "unwatching": "Scassâ da-i osservæ speçiâli",
+       "watcherrortext": "S'è veificou 'n errô durante a modifica di öservæ pe \"$1\".",
+       "enotif_reset": "Marca tutte-e paggine comme za vixitæ",
+       "enotif_impersonal_salutation": "Utente de {{SITENAME}}",
+       "enotif_subject_deleted": "A paggina $1 de {{SITENAME}} a l'è stæta scassâ da {{gender:$2|$2}}",
+       "enotif_subject_created": "A pagina $1 de {{SITENAME}} a l'è stæta creâ da {{gender:$2|$2}}",
+       "enotif_subject_moved": "A pagina $1 de {{SITENAME}} a l'è stæta mesciâ da {{gender:$2|$2}}",
+       "enotif_subject_restored": "A paggina $1 de {{SITENAME}} a l'è stæta ripristinâ da {{gender:$2|$2}}",
+       "enotif_subject_changed": "A pagina $1 de {{SITENAME}} a l'è stæta modificâ da {{gender:$2|$2}}",
+       "enotif_body_intro_deleted": "A pagina $1 de {{SITENAME}} a l'è stæta scassâ da {{gender:$2|$2}} o $PAGEEDITDATE (amia $3 pe-a verscion attoale).",
+       "enotif_body_intro_created": "A pagina $1 de {{SITENAME}} a l'è stæta creâ da {{gender:$2|$2}} o $PAGEEDITDATE, amia $3 pe-a verscion attoale.",
+       "enotif_body_intro_moved": "A pagina $1 de {{SITENAME}} a l'è stæta mesciâ da {{gender:$2|$2}} o $PAGEEDITDATE, amia $3 pe-a verscion attoale.",
+       "enotif_body_intro_restored": "A pagina $1 de {{SITENAME}} a l'è stæta ripristinâ da {{gender:$2|$2}} o $PAGEEDITDATE, amia $3 pe-a verscion attoale.",
+       "enotif_body_intro_changed": "A pagina $1 de {{SITENAME}} a l'è stæta modificâ da {{gender:$2|$2}} o $PAGEEDITDATE, amia $3 pe-a verscion attoale.",
+       "enotif_lastvisited": "Vixita $1 pe vedde tutte e modiffiche da l'urtima vixita.",
+       "enotif_lastdiff": "Vixita $1 pe vedde a modiffica.",
        "enotif_anon_editor": "ûtente anònnimo $1",
+       "enotif_body": "Gentî $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nÖgetto de l'intervento, inseio da l'aotô: $PAGESUMMARY $PAGEMINOREDIT\n\nContatta l'aotô:\nvia posta eletronnica: $PAGEEDITOR_EMAIL\nin sciô scito: $PAGEEDITOR_WIKI\n\nNo saiâ mandou atre notiffiche in caxo de urteioî attivitæ, se no ti vixiti a pagina doppo avei effettuou l'accesso. Inoltre, l'è poscibbile modificâ e impostaçioin de notiffica pe tutte e paggine inta lista dei öservæ speciali.\n\nO scistema de notiffica de {{SITENAME}}, a-o to serviççio\n\n--\nPe modificâ e impostaçioin de notiffiche via posta elettronnica, vixita \n{{canonicalurl:{{#special:Preferences}}}}\n\nPe modificâ a lista di öservæ speciali, vixita \n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nPe rimoeuve a pagina da-a lista di öservæ speciali, vixita\n$UNWATCHURL\n\nPe commentâ e riçeive agiutto:\n$HELPPAGE",
        "changed": "cangiâ",
        "deletepage": "Scassa a paggina",
+       "confirm": "Conferma",
+       "excontent": "o contegnuo o l'ea: '$1'",
+       "excontentauthor": "o contegnuo o l'ea: '$1', e l'unnico contributô o l'ea \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|msg]])",
+       "exbeforeblank": "O contegnuo primma do svoeuamento o l'ea: '$1'",
        "delete-confirm": "Scassa \"$1\"",
        "delete-legend": "Scassa",
-       "historywarning": "Attension: A paggina c'a se sta pe scassâ a g'ha 'na cronologîa:",
+       "historywarning": "'''Attençion:''' a paggina che ti stæ pe scassâ a g'ha una cronologia con $1 {{PLURAL:$1|verscion|verscioin}}:",
+       "historyaction-submit": "Mostra",
        "confirmdeletetext": "Ti stæ pe scassâ pe sempre da-o database 'na paggina ò 'n'immaggine, assemme a tûtta a sêu cronologîa. Pe cortexia, conferma che davvei ti vêu andâ avanti con quella cancellassion, che ti capisci perfettamente e conseguense de 'st'assion e che a s'adatta a-e linnie guidda stabilîe in [[{{MediaWiki:Policy-url}}]].",
        "actioncomplete": "Açion completâ",
        "actionfailed": "Açión falîa",
        "deletedtext": "A paggina \"$1\" a l'è stæta scassâ. Consultâ o $2 pe 'na lista de paggine scassæ de reçente.",
        "dellogpage": "Registro de cose scassæ",
+       "dellogpagetext": "De sotta gh'è 'na lista co-e paggine scassæ ciu de reçente.",
+       "deletionlog": "registro de scassatue",
+       "reverted": "Ripristinou a verscion precedente",
        "deletecomment": "Raxon:",
        "deleteotherreason": "Ûn âtro motivo",
        "deletereasonotherlist": "Ûnn'âtra raxon",
+       "deletereason-dropdown": "* Motivaçioin ciù comun-e pe-a scançellaçion\n** Spam\n** Vandalismo\n** Violaçion do drito d'aotô\n** Recesta de l'aotô\n** Redirect rotto",
+       "delete-edit-reasonlist": "Modiffica e raxoin do scassamento",
+       "delete-toobig": "A cronologia de questa pagina a l'è ben longa (oltre $1 {{PLURAL:$1|verscion|verscioin}}). A so scançellaçion a l'è stæta limitâ pe evitâ de creâ açidentalmente di problemi de fonçionamento a-o database de {{SITENAME}}.",
+       "delete-warning-toobig": "A cronologia de questa pagina a l'è ben longa (oltre $1 {{PLURAL:$1|verscion|verscioin}}). A so scançellaçion a poeu creâ di problemi de fonçionamento a-o database do {{SITENAME}}; procede con caotella.",
+       "deleteprotected": "No ti poeu scassâ questa paggina perché a l'è stæta protetta.",
+       "deleting-backlinks-warning": "<strong>Attençion:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|di atre pagine]] contengnan di collegamenti o de incluxoin a-a paggina che t'ê aproeuvo a scassâ.",
+       "rollback": "Annulla e modiffiche",
        "rollbacklink": "rollback",
        "rollbacklinkcount": "rollback de {{PLURAL:$1|una modiffica|$1 modiffiche}}",
+       "rollbacklinkcount-morethan": "rollback de ciù de {{PLURAL:$1|una modiffica|$1 modiffiche}}",
+       "rollbackfailed": "Rollback fallio",
+       "rollback-missingparam": "Parammetri obrigatoi mancanti inta recesta.",
        "cantrollback": "No se peu tornâ inderê; l'utente ch'o l'ha fæto quelle modiffiche o l'è stæto l'unico contribuente.",
-       "alreadyrolled": "O no se peû tornâ inderê a-i ûrtimi cangiamenti da pagina [[:$1]]\nda [[User:$2|$2]] ([[User talk:$2|Ciæti]]); quarche âtro\no l'à cangiâ ò o l'è zà tornòu inderê.\nL'ûrtimo cangiamento o ghe l'à fæto [[User:$3|$3]] ([[User talk:$3|Ciæti]]).",
-       "revertpage": "E modificaçioin de [[Special:Contributions/$2|$2]] ([[User talk:$2|Ciæti]]) son stæte eliminæ; riportæ a verscion de primma de [[User:$1|$1]]",
+       "alreadyrolled": "No l'è poscibbile annullâ e modiffiche apportæ a-a pagina [[:$1]] da parte de [[User:$2|$2]] ([[User talk:$2|discuscion]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]); un atro utente o l'ha zà modificou a pagina oppù o l'ha effettuou o rollback.\n\nA modifica ciù reçente a.a paggina a l'è stæta apportâ da [[User:$3|$3]] ([[User talk:$3|discuscion]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
+       "editcomment": "L'ogetto da modiffica o l'ea: <em>$1</em>.",
+       "revertpage": "Annullou e modiffiche de [[Special:Contributions/$2|$2]] ([[User talk:$2|discuscion]]), riportâ a-a verscion precedente de [[User:$1|$1]]",
+       "revertpage-nouser": "Annullou e modiffiche de un utente ascoso, riportâ a-a verscion precedente de {{GENDER:$1|[[User:$1|$1]]}}",
+       "rollback-success": "Annullou e modiffiche de $1; paggina riportâ a l'urtima verscion de $2.",
+       "rollback-success-notify": "Annullou e modiffiche de $1;\npaggina riportâ a l'urtima revixon de $2. [$3 Mostra e modiffiche]",
+       "sessionfailure-title": "Sescion fallia",
+       "sessionfailure": "S'è veificou un problema inta sescion ch'a l'identiffica l'accesso; o scistema o no l'ha eseguio o comando impartio pe precauçion. Torna a-a paggina precedente co-o tasto 'Inderê' do to browser, recarega a paggina e riproeuva.",
+       "changecontentmodel": "Cangia o modello de contegnuo de 'na paggina",
+       "changecontentmodel-legend": "Cangia o modello de contegnuo",
+       "changecontentmodel-title-label": "Tittolo da paggina",
+       "changecontentmodel-model-label": "Noeuvo modello de contegnuo",
+       "changecontentmodel-reason-label": "Raxon:",
+       "changecontentmodel-submit": "Cangia",
+       "changecontentmodel-success-title": "O modello de contegnuo o l'è stæto modificou",
+       "changecontentmodel-success-text": "O tipo de contegnuo de [[:$1]] o l'è stæto modificou.",
+       "changecontentmodel-cannot-convert": "O contegnuo de [[:$1]] o no poeu ese convertio inte 'n tipo de $2.",
+       "changecontentmodel-nodirectediting": "O modello de contegnuo $1 o no supporta a modiffica diretta",
+       "changecontentmodel-emptymodels-title": "Nisciun modello de contegnuo disponibbile",
+       "changecontentmodel-emptymodels-text": "O contegnuo de [[:$1]] o no poeu ese convertio inte nisciun tipo.",
+       "log-name-contentmodel": "Modiffiche do modello di contegnui",
+       "log-description-contentmodel": "Eventi relativi a-o modello de contegnuo de 'na paggina",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|l'ha creou}} a paggina $3 doeuviando un modello de contegnuo non predefinio \"$5\"",
+       "logentry-contentmodel-change": "$1 {{GENDER:$2|l'ha modificou}} o modello de contegnuo da paggina $3 da \"$4\" a \"$5\"",
+       "logentry-contentmodel-change-revertlink": "ripristina",
+       "logentry-contentmodel-change-revert": "ripristina",
        "protectlogpage": "Protessioin",
+       "protectlogtext": "De sotta gh'è 'na lista di cangi a-e proteçioin de paggine.\nAmia a [[Special:ProtectedPages|lista de pagine protette]] pe l'elenco de proteçioin de pagina attoalmente attive.",
        "protectedarticle": "o l'à protetto \"[[$1]]\"",
+       "modifiedarticleprotection": "ha modificou o livello de proteçion de \"[[$1]]\"",
+       "unprotectedarticle": "o l'ha sprotezuo \"[[$1]]\"",
+       "movedarticleprotection": "o l'ha mesciou a proteçion da \"[[$2]]\" a \"[[$1]]\"",
+       "protect-title": "Cangio do livello de proteçion pe \"$1\"",
+       "protect-title-notallowed": "Veddi o livello de proteçion de \" $1 \"",
        "prot_1movedto2": "[[$1]] mesciòu a [[$2]]",
+       "protect-badnamespace-title": "Namespace non protezibbile",
+       "protect-badnamespace-text": "E pagine de questo namespace no poeuan ese protezue.",
+       "protect-norestrictiontypes-text": "Questa pagina a no poeu ese protetta perché non gh'è arcun tipo de restriçion disponibbile.",
+       "protect-norestrictiontypes-title": "Paggina non protezibbile",
        "protect-legend": "Confermâ protession",
        "protectcomment": "Raxon:",
        "protectexpiry": "Scadensa:",
        "protect_expiry_invalid": "Scadensa invalida.",
        "protect_expiry_old": "Dæta de scadensa into passòu.",
+       "protect-unchain-permissions": "Desblocca di urteioî opçioin de proteçion",
        "protect-text": "Chì o l'è poscibbile vedde e modificâ o livello de protession pe-a paggina '''$1'''.",
+       "protect-locked-blocked": "No ti poeu cangiâ i livelli de proteçion quande gh'è un blocco. E impostaçioin corrente pe-a pagina son '''$1''':",
+       "protect-locked-dblock": "Imposcibile modificâ i livelli de proteçion durante un blocco do database.\nE impostaçioin corrente pe-a paggina son '''$1''':",
        "protect-locked-access": "No ti g'hæ permisso pe modificâ i livelli de protession da paggina.\nQueste son e impostassioîn correnti pe 'sta paggina ('''$1'''):",
-       "protect-cascadeon": "Pe-o momento 'sta paggina chì a l'è bloccâa perché a l'è inclûsa {{PLURAL:$1|inta paggina indicâa apprêuvo, pe-a quæ|inte paggine indicæ apprêuvo, pe-e quæ}} a l'è attiva a protession recorsciva. O se pêu modificâ o livello de protession individuâle da paggina, ma l'impostassioîn derivanti da-a protession recorsciva no sajàn modificæ.",
-       "protect-default": "(predefinîo)",
-       "protect-fallback": "Besêugna avei permisso \"$1\"",
-       "protect-level-autoconfirmed": "Solo ûtenti registræ",
-       "protect-level-sysop": "Solo amministratoî",
+       "protect-cascadeon": "A-o momento questa pagina a l'è bloccâ perché inclusa {{PLURAL:$1|inta pagina indicâ de seguito, pe-a quæ|inte pagine indicæ de seguito, pe-e quæ}} l'è attiva a proteçion ricorsciva.\nE modifiche a-o livello de proteçion individoale da pagina, no avian effetto in sce-e impostaçioin derivante da-a proteçion ricorsciva.",
+       "protect-default": "Aotorizza tutti i utenti",
+       "protect-fallback": "Consentio solo a-i utenti con permisso \"$1\"",
+       "protect-level-autoconfirmed": "Consentio solo a-i utenti aotoconvalidæ",
+       "protect-level-sysop": "Consentio solo a-i amministratoî",
        "protect-summary-cascade": "recorsciva",
        "protect-expiring": "scadensa: $1 (UTC)",
+       "protect-expiring-local": "o descazze o $1",
+       "protect-expiry-indefinite": "indefinio",
        "protect-cascade": "Protession recorsciva (estende a protession a tûtte e paggine inclûse in questa chì).",
        "protect-cantedit": "Ti no ti pêu modificâ i livelli de protession pe-a paggina se no ti g'hæ i permissi pe modificâ a paggina mæxima.",
-       "protect-expiry-options": "2 ôe:2 hours,1 giorno:1 day,3 giorni:3 days,1 settemann-a:1 week,2 settemann-e:2 weeks,1 meise:1 month,3 meixi:3 months,6 meixi:6 months,1 anno:1 year,infinîo:infinite",
+       "protect-othertime": "Duata non in elenco:",
+       "protect-othertime-op": "duata non in elenco",
+       "protect-existing-expiry": "Scadença attoale: $2, $3",
+       "protect-existing-expiry-infinity": "Scadença attoale: infinio",
+       "protect-otherreason": "Atri motivi/detaggi:",
+       "protect-otherreason-op": "Un'atra raxon",
+       "protect-dropdown": "*Motivi comun de proteçion\n** Reiteræ vandalismi\n** Reiteræ inseimenti de spam\n** Guæra de modiffiche contra-produxente\n** Paggina con ato traffego",
+       "protect-edit-reasonlist": "Modiffica e raxoin pe-a proteçion",
+       "protect-expiry-options": "1 oa:1 hour,1 giorno:1 day,1 setteman-a:1 week,2 setteman-e:2 weeks,1 meise:1 month,3 meixi:3 months,6 meixi:6 months,1 anno:1 year,infinio:infinite",
        "restriction-type": "Permisso",
        "restriction-level": "Livello de restrission",
+       "minimum-size": "Dimenscion minnima",
+       "maximum-size": "Dimenscion mascima:",
+       "pagesize": "(byte)",
        "restriction-edit": "Cangia",
        "restriction-move": "Mescia",
+       "restriction-create": "Crea",
+       "restriction-upload": "Carrega",
+       "restriction-level-sysop": "protetta",
+       "restriction-level-autoconfirmed": "semi-protezua",
        "restriction-level-all": "Tutti i livelli",
        "undelete": "Amîa e paggine scassæ",
+       "undeletepage": "Veddi e recuppera e pagine scançellæ",
+       "undeletepagetitle": "'''Quanto segue o l'è composto da de revixoin scassæ de [[:$1|$1]]'''.",
+       "viewdeletedpage": "Veddi e paggine scassæ",
+       "undeletepagetext": "{{PLURAL:$1|A seguente pagina a l'è stæta scassâ, ma a l'è ancon in archivio e pertanto a poeu ese recuperâ|Le seguente pagine son stæte scassæ, ma son ancon in archivio e pertanto poeuan ese recuperæ}}. L'archivio o poeu ese vuou periodicamente.",
+       "undelete-fieldset-title": "Ripristina revixoin",
+       "undeleteextrahelp": "Pe recuperâ l'intrega cronologia da pagina, lascia tutte e caselle deseleçionæ e fanni clic insce '''''{{int:undeletebtn}}'''''.\nPe effettuâ un ripristino selettivo, seleçion-a e caselle corrispondente a-e verscioin da ripristinâ e fanni clic insce '''''{{int:undeletebtn}}'''''.",
+       "undeleterevisions": "{{PLURAL:$1|Una revixon scassâ|$1 revixoin scassæ}}",
+       "undeletehistory": "Recuperando questa pagina, tutte e so verscioin saian ripristinæ inta relativa cronologia.\nSe doppo la scançellaçione l'è stato creou una noeuva pagina co-o mæximo tittolo, e verscioin recuperæ saian inseie inta cronologia precedente.",
+       "undeleterevdel": "O ripristino o no saiâ effettuou s'o determina a scançellaçion parçiâ da verscion attoale da pagina o do file interessou. In tâ caxo, l'è necessaio smarcâ o levâ l'oscuramento da-e verscioin scassæ ciù reçenti.",
+       "undeletehistorynoadmin": "Questa pagina a l'è stæta scassâ.\nO motivo da scassatua o l'è mostrou chì sotta, insemme a-i detaggi de l'utente ch'o l'ha modificou questa pagina primma da scassatua.\nO testo contegnuo inte verscioin scassæ o l'è disponibile solo a-i amministratoî.",
+       "undelete-revision": "Verscion scassâ da pagina $1, inseia o $4 a $5 da $3:",
+       "undeleterevision-missing": "Verscion errâ o mancante. O collegamento o l'è errou o dunque a verscion a l'è stæta zà ripristinâ ò eliminâ da l'archivvio.",
+       "undelete-nodiff": "No l'è stæto trovou nisciun-a verscion precedente.",
        "undeletebtn": "Ristorâ",
        "undeletelink": "fanni védde/repìggia",
        "undeleteviewlink": "fanni védde",
-       "cannotundelete": "O repiggio de i dæti o non l'è riuscïo (i peun ese za stæti repiggiæ da quarchedun ätro).",
+       "undeleteinvert": "Inverti a seleçion",
+       "undeletecomment": "Raxon:",
+       "undeletedrevisions": "{{PLURAL:$1|Una verscion recuperâ|$1 verscioin recuperæ}}",
+       "undeletedrevisions-files": "{{PLURAL:$1|Una verscion|$1 verscioin}} e $2 file recuperæ",
+       "undeletedfiles": "{{PLURAL:$1|Un file recuperou|$1 file recuperæ}}",
+       "cannotundelete": "Ripristino non riuscio:\n$1",
+       "undeletedpage": "'''A pagina $1 a l'è stæta recuperâ'''\n\nConsurta o [[Special:Log/delete|registro de scançellaçioin]] pe vedde e scançellaçioin e i recupperi ciù reçente.",
+       "undelete-header": "Consurta o [[Special:Log/delete|registro de scançellaçioin]] pe vedde e scassatue ciù reçente.",
+       "undelete-search-title": "Çerca inte pagine scassæ",
+       "undelete-search-box": "Çerca e paggine scassæ",
+       "undelete-search-prefix": "Mostra e paggine che començan con:",
+       "undelete-search-submit": "Çerca",
+       "undelete-no-results": "Nisciun-a pagina corrispondente inte l'archivio de scançellaçioin.",
+       "undelete-filename-mismatch": "Imposcibbile annullâ a scançellaçion da verscion do file con timestamp $1: nomme do file non corrispondente.",
        "undelete-bad-store-key": "No se peu repiggiâ o file co-a dæta $1: o file o no gh'ea za ciu primma d'ese scassou.",
        "undelete-cleanup-error": "Errô into scassâ o file d'archivio non utilizzòu \"$1\".",
+       "undelete-missing-filearchive": "Imposcibile ripristinâ l'ID $1 de l'archivio file in quanto o no l'è presente into database. O poriæ ese stæto za ripristinou.",
+       "undelete-error": "Errô into ripristino da pagina",
        "undelete-error-short": "Errô repiggiando i dæti do file \"$1\".",
        "undelete-error-long": "Gh'è stæto di erroî inte l'annullâ a cançellaçion do file:\n\n$1",
+       "undelete-show-file-confirm": "T'ê seguo de voei amiâ a verscion scassâ do file \"<nowiki>$1</nowiki>\" do $2 a $3?",
+       "undelete-show-file-submit": "Sci",
        "namespace": "Namespace:",
        "invert": "Invertî a seleçión",
        "tooltip-invert": "Selession-a sta casella pe asconde e modiffiche a-e paggine a l'interno do namespace selessionou (e o namespace associou, se selessionou)",
+       "tooltip-whatlinkshere-invert": "Marca sta casella pe asconde i collegamenti da-e pagine a l'interno do namespace seleçionou",
        "namespace_association": "Namespace associou",
        "tooltip-namespace_association": "Selession-a sta casella pe includde ascì a paggina de discuscion ò l'oggetto do namespace associou co-o namespace selessionou",
        "blanknamespace": "(Prinçipâ)",
        "contributions-title": "Contribuçioìn de $1",
        "mycontris": "Contribuçioin",
        "anoncontribs": "Contribuçioin",
-       "contribsub2": "Pe $1 ($2)",
-       "uctop": "(ûrtima pe-a paggina)",
+       "contribsub2": "Pe {{GENDER:$3|$1}} ($2)",
+       "contributions-userdoesnotexist": "L'utença \"$1\" a no l'è registrâ.",
+       "nocontribs": "Cangi che soddisfan i critei de riçerca no se n'è atrovou.",
+       "uctop": "(attoale)",
        "month": "Partindo da-o meize (e precedénti):",
        "year": "Partindo da l'anno (e precedenti):",
        "sp-contributions-newbies": "Fanni védde sôlo e contribuçioìn di nêuvi utenti",
        "sp-contributions-newbies-sub": "Pe i nêuvi ûtenti",
+       "sp-contributions-newbies-title": "Contribuçioin di noeuvi utenti",
        "sp-contributions-blocklog": "Blòcchi",
+       "sp-contributions-suppresslog": "contributi utente soppresci",
+       "sp-contributions-deleted": "contributi utente scassæ",
        "sp-contributions-uploads": "caregaménti",
        "sp-contributions-logs": "log",
        "sp-contributions-talk": "Ciæti",
+       "sp-contributions-userrights": "manezzo di driti di utenti",
+       "sp-contributions-blocked-notice": "St'utente o l'è attualmente bloccòu.\nL'urtimo elemento into registro di blocchi o l'è riportòu chì de sotta pe rifeimento:",
+       "sp-contributions-blocked-notice-anon": "St'addreçço IP o l'è attoalmente bloccòu.\nL'urtimo elemento into registro di blocchi o l'è riportòu chì de sotta pe rifeimento:",
        "sp-contributions-search": "Riçerca contribuçioìn",
        "sp-contributions-username": "Indirìsso IP ò nómme utente:",
        "sp-contributions-toponly": "Fanni védde sôlo i cangiaménti ch'en i ùrtime revixoìn da pàgina",
+       "sp-contributions-newonly": "Fanni védde sôlo i cangiaménti ch'en de creaçioin de pàgina",
+       "sp-contributions-hideminor": "Ascondi e modifiche menoî",
        "sp-contributions-submit": "Çerca",
        "whatlinkshere": "Cöse se colega chì",
        "whatlinkshere-title": "Pàgine c'apontàn a $1",
        "whatlinkshere-page": "Pàgina:",
        "linkshere": "E pàgine segoenti apontan a '''[[:$1]]''':",
        "nolinkshere": "Nisciùnn-a pàgina a se collega con '''[[:$1]]'''.",
+       "nolinkshere-ns": "Pagine ch'apontan a '''[[:$1]]''' into namespace seleçionou no ghe n'è.",
        "isredirect": "Paggina de rindirissamento",
        "istemplate": "Incluxon",
        "isimage": "Colegamento a file",
        "whatlinkshere-hideredirs": "$1 i rendirissamenti",
        "whatlinkshere-hidetrans": "$1 Incluxoin",
        "whatlinkshere-hidelinks": "$1 colegaménti",
-       "whatlinkshere-hideimages": "$1 colegaménti di file",
+       "whatlinkshere-hideimages": "$1 colegamenti da file",
        "whatlinkshere-filters": "Filtri",
-       "blockip": "Blocca l'ûtente",
+       "whatlinkshere-submit": "Vanni",
+       "autoblockid": "Aotobrocco #$1",
+       "block": "Blocca utente",
+       "unblock": "Desblocca utente",
+       "blockip": "Blocca {{GENDER:$1|utente}}",
+       "blockip-legend": "Blocca l'utente",
+       "blockiptext": "Doeuvia o moddulo sottostante pe bloccâ l'accesso in scrittua a un speciffico addreçço IP ò a un utente registrou.\nO blocco o dev'ese doeuviou pe prevegnî di atti de vandalismo e in streita öservança de [[{{MediaWiki:Policy-url}}|reggole de {{SITENAME}}]].\nIndica o motivo speçiffico pe-o quæ se procede a-o blocco (presempio, çitando i tittoli di eventuæ paggine ögetto de vandalismo).\nTi poeu bloccâ di ntervalli de IP doeuviando a scintasci [https://it.wikipedia.org/wiki/CIDR CIDR]; l'intervallo ciù ampio consentio o l'è /$1 pe IPv4 e /$2 pe IPv6.",
+       "ipaddressorusername": "Adreçço IP ò nómme utente:",
+       "ipbexpiry": "Scadença:",
        "ipbreason": "Raxon:",
+       "ipbreason-dropdown": "*Motivaçioni ciù comun-e pe-i blocchi\n** Inseimento de informaçioin fase\n** Rimoçion di contegnti da-e paggine\n** Collegamenti promoçionæ a di sciti esterni\n** Inseimento di contegnui privi de senso\n** Comportamenti intimidatoi ò molestie\n** Uso indebbito de utençe murtiple\n** Nomme utente inaçettabbile",
+       "ipb-hardblock": "Impedisci a-i utenti registræ de contribuî da questo adreçço IP",
+       "ipbcreateaccount": "Impedisci a registraçion",
+       "ipbemailban": "Impedisci a l'utente l'invio di email",
+       "ipbenableautoblock": "Blocca aotomaticamente l'urtimo adreçço IP doeuviou da l'utente e i succescivi da-i quæ tentan e modiffiche",
+       "ipbsubmit": "Blocca st'utente",
+       "ipbother": "Duata non in elenco:",
        "ipboptions": "2 ôe:2 hours,1 giorno:1 day,3 giorni:3 days,1 settemann-a:1 week,2 settemann-e:2 weeks,1 meise:1 month,3 meixi:3 months,6 meixi:6 months,1 anno:1 year,infinîo:infinite",
+       "ipbhidename": "Ascondi o nomme utente da-e modiffiche e da-i elenchi.",
+       "ipbwatchuser": "Controlla e pagine e-e discuscioin utente de questo utente",
+       "ipb-disableusertalk": "Impedisci a questo utente de modificâ a proppia paggina de discuscion dementre ch'o l'è bloccou",
+       "ipb-change-block": "Re-blocca l'utente con queste impostaçioin",
+       "ipb-confirm": "Conferma o blocco",
        "badipaddress": "Indirisso IP non valido",
        "blockipsuccesssub": "Blocco ariêscîo",
-       "blockipsuccesstext": "[[Special:Contributions/$1|$1]] o l'è stæto bloccou.\n<br />Veddi [[Special:BlockList|Lista di addressi IP bloccæ]] pe vedde i blocchi attivi.",
+       "blockipsuccesstext": "[[Special:Contributions/$1|$1]] o l'è stæto bloccou.\n<br />Amia a [[Special:BlockList|lista di adreççi IP bloccæ]] pe vedde i blocchi attivi.",
+       "ipb-blockingself": "Ti stæ pe bloccâ ti mæximo! T'ê seguo die voeilo fâ?",
+       "ipb-confirmhideuser": "Ti stæ pe bloccâ un utente con l'opçion \"Ascondi utente\" abilitâ.\nDe sta mainea o nomme utente o saiâ sopresso da tutte le liste e-e voxe de registro.\nT'ê seguo de voeilo fâ?",
+       "ipb-confirmaction": "Se t'ê seguo de voeilo fâ davei, controlla o campo \"{{int:ipb-confirm}}\" in fondo.",
+       "ipb-edit-dropdown": "Modifica i motivi do blocco",
+       "ipb-unblock-addr": "Sblocca $1",
+       "ipb-unblock": "Sblocca un utente ò un adreçço IP",
+       "ipb-blocklist": "Elenca i blocchi attivi",
+       "ipb-blocklist-contribs": "Contribuçioin de {{GENDER:$1|$1}}",
+       "ipb-blocklist-duration-left": "$1 arestæ",
+       "unblockip": "Desblocca utente",
+       "unblockiptext": "Doeuvia o moddulo sottostante pe restituî l'accesso in scrittua a 'n utente ò adreçço IP bloccou.",
+       "ipusubmit": "Leva sto blocco",
+       "unblocked": "L'utente [[User:$1|$1]] o l'è stæto sbloccou",
+       "unblocked-range": "$1 o l'è stæto sbloccou",
+       "unblocked-id": "O blocco $1 o l'è stæto rimòsso",
+       "unblocked-ip": "[[Special:Contributions/$1|$1]] o l'è stæto sbloccou.",
+       "blocklist": "Utenti bloccæ",
        "ipblocklist": "Utenti blocæ",
+       "ipblocklist-legend": "Çerca un utente bloccou",
+       "blocklist-userblocks": "Ascondi i blocchi di utenti registræ",
+       "blocklist-tempblocks": "Ascondi i blocchi temporannei",
+       "blocklist-addressblocks": "Ascondi i blocchi de un solo IP",
+       "blocklist-rangeblocks": "Ascondi i blocchi de range",
+       "blocklist-timestamp": "Dæta e oa",
+       "blocklist-target": "Destinaçion",
+       "blocklist-expiry": "O descazze",
+       "blocklist-by": "Amministratô ch'o l'ha misso o blocco",
+       "blocklist-params": "Parammetri de blocco",
+       "blocklist-reason": "Raxon",
+       "ipblocklist-submit": "Çerca",
+       "ipblocklist-localblock": "Blocco locale",
+       "ipblocklist-otherblocks": "{{PLURAL:$1|Atro blocco|Atri blocchi}}",
+       "infiniteblock": "infinio",
+       "expiringblock": "o descazze o $1 a $2",
        "anononlyblock": "Solo anonnimi",
+       "noautoblockblock": "blocco aotomatico disabilitou",
+       "createaccountblock": "registraçion bloccâ",
        "emailblock": "posta elettronega bloccâ",
+       "blocklist-nousertalk": "o no poeu modificâ a proppia pagina de discuscion",
        "ipblocklist-empty": "A lista di blocchi a l'è veua.",
+       "ipblocklist-no-results": "L'adreçço IP ô o nomme utente domandou o no l'è bloccou.",
        "blocklink": "Blocca",
        "unblocklink": "sblòcca",
        "change-blocklink": "càngia blòcco",
        "contribslink": "Contribuçioìn",
-       "autoblocker": "Affermoö automaticamente perchè o teu indirisso IP o l'è stæto usöo da \"[[User:$1|$1]]\" neuvamente. A razon dæta pe affermâ $1 a l'è stæta:\n\"$2\"",
+       "emaillink": "manda e-mail",
+       "autoblocker": "Bloccou aotomaticamente perché l'adreçço IP o l'è condiviso con l'utente \"[[User:$1|$1]]\".\nO blocco de l'utente $1 o l'è stæto imposto pe-o seguente motivo: \"$2\".",
        "blocklogpage": "Blòcchi",
+       "blocklog-showlog": "Questo utente o l'è stæto bloccou in precedença. O registro di blocchi o l'è riportou de sotta pe rifeimento:",
+       "blocklog-showsuppresslog": "Questo utente o l'è stæto bloccou e nscoso in precedença. O registro de rimoçioin o l'è riportou de sotta pe rifeimento:",
        "blocklogentry": "blocòu [[$1]] pe in periodo de $2 $3",
-       "blocklogtext": "Sta chie a l'è unn-a lista de affermaçioin fæte e levæ.\nI indirissi IP affermæ automaticamente non son  consideræ.\nVeddi a [[Special:BlockList|Lista de i indirissi IP affermæ]] pe e informaçioin neuve.",
+       "reblock-logentry": "o l'ha cangiou e impostaçioin do blocco pe [[$1]] co-ina scadença de $2 $3",
+       "blocklogtext": "De sotta gh'è 'n registro di açioin de blocco e sblocco utenti.\nI adreççi IP bloccæ aotomaticamente no son elencæ.\nConsurta l'[[Special:BlockList|elenco di blocchi]] pe l'elenco di bandi o blocchi attoalmente opiativi.",
+       "unblocklogentry": "o l'ha sbloccou $1",
        "block-log-flags-anononly": "Utenti anonimmi soö",
        "block-log-flags-nocreate": "Neuve registrascioin non son permisse",
        "block-log-flags-noautoblock": "O blocco automatego o non l'è attïvo",
        "block-log-flags-noemail": "A posta elettronega a non l'è attïva",
+       "block-log-flags-nousertalk": "o no poeu modificâ a proppia pagina de discuscion",
+       "block-log-flags-angry-autoblock": "blocco aotomatico avançou attivo",
+       "block-log-flags-hiddenname": "nomme utente ascoso",
+       "range_block_disabled": "O drito d'aministratô de bloccâ di intervalli d'adreççi IP o l'è disabilitou.",
+       "ipb_expiry_invalid": "Scadença non vallida.",
+       "ipb_expiry_old": "Scadença za trascorsa.",
+       "ipb_expiry_temp": "I blocchi di nommi utenti ascoxi devan ese permanenti.",
        "databasenotlocked": "O database o no l'è bloccòu.",
        "move-page-legend": "Mescia a paggina",
        "movepagetext": "Chì o se pêu dâ 'n nêuvo nomme a 'na paggina, stramûando tûtta a sêu cronologîa a-o nêuvo nomme.\nA paggina attuâle a fa outomaticamente 'n rindirissamento a-o nêuvo tittolo.\nI collegamenti escistenti no sajàn aggiornæ; veriffica che 'sto stramûo o no l'agge creòu doggi rindirissamenti ò rindirissamenti sballiæ.\nA responsabilitæ pe tegnî i collegamenti sempre donde deivan andâ a l'è têu.\n\nA paggina a '''no''' sajà stramûâa se ghe foisse zà ûnn-a co-o nêuvo nomme, a meno c'a no segge vêua ò fæta solo da 'n rindirissamento a-a vegia e a no l'agge verscioîn preçedenti.\nIn caso de stramûo sballiòu o se pêu tornâ sûbbito a-o vegio tittolo, e o no l'è poscibbile sorvescrive pe errô 'na paggina zà escistente.\n\n'''ATTENSION:'''\n'N cangiamento coscì grande o porieiva creâ di controtempi e problemmi, sorvetûtto pe-e paggine ciû viscitæ.\nPensa ben e conseguense de 'sto stramûo primma d'andâ avanti!",
index 7600f64..1612248 100644 (file)
        "botpasswords-label-restrictions": "Ograniczenia użytkowania:",
        "botpasswords-label-grants-column": "Przyznane",
        "botpasswords-bad-appid": "Nazwa bota \"$1\" nie jest prawidłowa.",
+       "botpasswords-insert-failed": "Nie udało się dodać robota o nazwie \"$1\". Czy był już wcześniej dodany?",
+       "botpasswords-update-failed": "Nie udało się zmienić robota o nazwie \"$1\". Czy został usunięty?",
        "botpasswords-created-title": "Hasło bota stworzone",
        "botpasswords-created-body": "Hasło bota \"$1\" użytkownika \"$2\" zostało utworzone.",
        "botpasswords-updated-title": "Hasło bota zaktualizowane",
        "botpasswords-updated-body": "Hasło bota \"$1\" użytkownika \"$2\" zostało zaktualizowane.",
        "botpasswords-deleted-title": "Hasło bota usunięte",
        "botpasswords-deleted-body": "Hasło bota \"$1\" użytkownika \"$2\" zostało usunięte.",
+       "botpasswords-newpassword": "Nowe hasło do zalogowania przez <strong>$1</strong> to <strong>$2</strong>. <em>Proszę je zapisać w celu wykorzystania w przyszłości.</em>",
        "botpasswords-no-provider": "BotPasswordsSessionProvider nie jest dostępne.",
        "botpasswords-restriction-failed": "Logowanie nie powiodło się z powodu ograniczeń na hasło bota.",
+       "botpasswords-invalid-name": "Określona nazwa użytkownika nie zawiera separatora hasła bota (\"$1\").",
        "botpasswords-not-exist": "Użytkownik \"$1\" nie ma hasła dla bota o nazwie \"$2\".",
        "resetpass_forbidden": "Hasła nie mogą zostać zmienione",
        "resetpass_forbidden-reason": "Hasła nie mogą zostać zmienione: $1",
        "passwordreset-emailsentusername": "Jeśli z tym kontem powiązany jest adres e‐mail, zostanie na niego wysłany e-mail do odzyskiwania hasła.",
        "passwordreset-emailsent-capture": "Wyświetlony poniżej e‐mail pozwalający na zresetowanie hasła został wysłany.",
        "passwordreset-emailerror-capture": "Poniżej wyświetlony e‐mail pozwalający na zresetowanie hasła został wygenerowany, ale nie udało się wysłać go do {{GENDER:$2|użytkownika|użytkowniczki}}: $1",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|Został wysłany e-mail|Zostały wysłane e-mail}} z informacjami o resetowaniu hasła. {{PLURAL:$1|Użytkownik i hasło jest pokazany|Lista użytkowników i haseł jest pokazana}} poniżej.",
+       "passwordreset-emailerror-capture2": "Wysyłanie e-maila do {{GENDER:$2|użytkownika|użytkowniczki}} nie powiodło się: $1 {{PLURAL:$3|Użytkownik i hasło jest pokazany|Lista użytkowników i haseł jest pokazana}} poniżej.",
+       "passwordreset-nocaller": "Musi być podany wywołujący",
+       "passwordreset-nosuchcaller": "Wywołujący nie istnieje: $1",
        "passwordreset-invalideamil": "Nieprawidłowy adres e-mail",
        "passwordreset-nodata": "Nie podano ani nazwy użytkownika, ani adresu e-mail",
        "changeemail": "Zmiana lub usunięcie adresu e‐mail",
        "content-model-css": "CSS",
        "content-json-empty-object": "Pusty obiekt",
        "content-json-empty-array": "Pusta tablica",
+       "deprecated-self-close-category": "Strony zawierające nieprawidłowe samozamykające się znaczniki HTML",
+       "deprecated-self-close-category-desc": "Strona zawiera samozamykające się znaczniki HTML, takie jak <code>&lt;b/></code> lub <code>&lt;span/></code>. Ich zachowanie zmieni się na dostosowane do specyfikacji HTML5, więc ich użycie w wikikodzie jest zdeprecjonowane.",
        "duplicate-args-warning": "<strong>Ostrzeżenie:</strong> [[:$1]] wywołuje [[:$2]] z więcej niż jedną wartością dla parametru \"$3\". Tylko ostatnia podana wartość zostanie użyta.",
        "duplicate-args-category": "Strony zawierające wywołania szablonów z parametrami o takich samych nazwach",
        "duplicate-args-category-desc": "Strona zawiera szablony, które używają duplikatów argumentów, jak <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> lub <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "listgrants": "Uprawnienia",
        "listgrants-grant": "Uprawnienie",
        "listgrants-rights": "Uprawnienie",
-       "trackingcategories": "Śledzenie kategorii",
+       "trackingcategories": "Kategorie śledzące",
        "trackingcategories-summary": "Ta strona zawiera listę kategorii monitorujących wypełnianych automatycznie przez oprogramowanie MediaWiki. Nazwy kategorii można zmienić edytując odpowiednie komunikaty systemowe znajdujące się w przestrzeni nazw {{ns:8}}.",
        "trackingcategories-msg": "Śledzenie kategorii",
        "trackingcategories-name": "Nazwa komunikatu",
index 50d1937..3f7621d 100644 (file)
        "content-model-css": "CSS",
        "content-json-empty-object": "Пустой объект",
        "content-json-empty-array": "Пустой массив",
+       "deprecated-self-close-category": "Страницы, использующие недопустимые самозакрывающеся HTML-теги",
        "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=bar}}</nowiki></code>.",
        "trackingcategories-name": "Имя сообщения",
        "trackingcategories-desc": "Критерий включения в категорию",
        "restricted-displaytitle-ignored": "Страницы с игнорируемыми отображаемыми названиями",
+       "restricted-displaytitle-ignored-desc": "На странице есть игнорируемый <code><nowiki>{{DISPLAYTITLE}}</nowiki></code>, поскольку он не соответствует реальному названию страницы.",
        "noindex-category-desc": "Страница не индексируются поисковыми роботами, потому что на ней имеется «волшебное слово» <code><nowiki>__NOINDEX__</nowiki></code>, и она находится в пространстве имён, где разрешён этот флаг).",
        "index-category-desc": "На странице имеется «волшебное слово» <nowiki>__INDEX__</nowiki> (и страница находится в пространстве имён, где разрешён этот флаг), поэтому она индексируются поисковыми роботами в тех случаях, когда этого обычно не происходит.",
        "post-expand-template-inclusion-category-desc": "Размер страницы станет больше <code>$wgMaxArticleSize</code> после показа всех шаблонов, поэтому некоторые из них не были показаны полностью.",
index 5bc1c8d..6a3cd15 100644 (file)
@@ -221,18 +221,18 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                $checkKeys = [ wfRandomString() ]; // new check keys => force misses
                $ret = $cache->getWithSetCallback( $key, 30, $func,
                        [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
-               $this->assertEquals( $value, $ret );
+               $this->assertEquals( $value, $ret, 'Old value used' );
                $this->assertEquals( 1, $calls, 'Callback was not used' );
 
                $cache->delete( $key );
                $ret = $cache->getWithSetCallback( $key, 30, $func,
-                       [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] ); // should use interim value
-               $this->assertEquals( $value, $ret );
-               $this->assertEquals( 2, $calls, 'Callback was used' );
+                       [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
+               $this->assertEquals( $value, $ret, 'Callback was used; interim saved' );
+               $this->assertEquals( 2, $calls, 'Callback was used; interim saved' );
 
                $ret = $cache->getWithSetCallback( $key, 30, $func,
                        [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
-               $this->assertEquals( $value, $ret );
+               $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
                $this->assertEquals( 2, $calls, 'Callback was not used; used interim' );
        }
 
@@ -267,6 +267,59 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                $this->assertEquals( 1, $calls, 'Callback was not used' );
        }
 
+       /**
+        * @covers WANObjectCache::getWithSetCallback()
+        * @covers WANObjectCache::doGetWithSetCallback()
+        */
+       public function testBusyValue() {
+               $cache = $this->cache;
+               $key = wfRandomString();
+               $value = wfRandomString();
+               $busyValue = wfRandomString();
+
+               $calls = 0;
+               $func = function() use ( &$calls, $value ) {
+                       ++$calls;
+                       return $value;
+               };
+
+               $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] );
+               $this->assertEquals( $value, $ret );
+               $this->assertEquals( 1, $calls, 'Value was populated' );
+
+               // Acquire a lock to verify that getWithSetCallback uses busyValue properly
+               $this->internalCache->lock( $key, 0 );
+
+               $checkKeys = [ wfRandomString() ]; // new check keys => force misses
+               $ret = $cache->getWithSetCallback( $key, 30, $func,
+                       [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
+               $this->assertEquals( $value, $ret, 'Callback used' );
+               $this->assertEquals( 2, $calls, 'Callback used' );
+
+               $ret = $cache->getWithSetCallback( $key, 30, $func,
+                       [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
+               $this->assertEquals( $value, $ret, 'Old value used' );
+               $this->assertEquals( 2, $calls, 'Callback was not used' );
+
+               $cache->delete( $key ); // no value at all anymore and still locked
+               $ret = $cache->getWithSetCallback( $key, 30, $func,
+                       [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
+               $this->assertEquals( $busyValue, $ret, 'Callback was not used; used busy value' );
+               $this->assertEquals( 2, $calls, 'Callback was not used; used busy value' );
+
+               $this->internalCache->unlock( $key );
+               $ret = $cache->getWithSetCallback( $key, 30, $func,
+                       [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
+               $this->assertEquals( $value, $ret, 'Callback was used; saved interim' );
+               $this->assertEquals( 3, $calls, 'Callback was used; saved interim' );
+
+               $this->internalCache->lock( $key, 0 );
+               $ret = $cache->getWithSetCallback( $key, 30, $func,
+                       [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
+               $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
+               $this->assertEquals( 3, $calls, 'Callback was not used; used interim' );
+       }
+
        /**
         * @covers WANObjectCache::getMulti()
         */