Merge "Use current content model for blank page content (not title default)"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sat, 23 Jul 2016 00:57:15 +0000 (00:57 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sat, 23 Jul 2016 00:57:15 +0000 (00:57 +0000)
71 files changed:
RELEASE-NOTES-1.28
autoload.php
composer.json
includes/DefaultSettings.php
includes/EditPage.php
includes/FileDeleteForm.php
includes/Html.php
includes/Pingback.php [new file with mode: 0644]
includes/Setup.php
includes/Title.php
includes/api/i18n/cs.json
includes/api/i18n/ko.json
includes/collation/IcuCollation.php
includes/db/Database.php
includes/db/IDatabase.php
includes/deferred/AtomicSectionUpdate.php
includes/deferred/AutoCommitUpdate.php [new file with mode: 0644]
includes/deferred/CallableUpdate.php
includes/deferred/DeferrableCallback.php [new file with mode: 0644]
includes/deferred/DeferredUpdates.php
includes/diff/DifferenceEngine.php
includes/filebackend/lockmanager/DBLockManager.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/Installer.php
includes/installer/LocalSettingsGenerator.php
includes/installer/WebInstallerName.php
includes/installer/WebInstallerOutput.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/tcy.json
includes/installer/i18n/zh-hans.json
includes/libs/objectcache/RESTBagOStuff.php
includes/libs/objectcache/WANObjectCache.php
includes/parser/Preprocessor_Hash.php
includes/resourceloader/ResourceLoader.php
includes/revisiondelete/RevDelList.php
includes/skins/BaseTemplate.php
includes/tidy/Balancer.php
languages/i18n/be-tarask.json
languages/i18n/cs.json
languages/i18n/dty.json
languages/i18n/fr.json
languages/i18n/hak.json
languages/i18n/khw.json
languages/i18n/ko.json
languages/i18n/lij.json
languages/i18n/nn.json
languages/i18n/pl.json
languages/i18n/ru.json
languages/i18n/tcy.json
languages/i18n/ur.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
maintenance/deleteArchivedFiles.php
resources/src/mediawiki/page/startup.js
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
tests/phpunit/includes/tidy/BalancerTest.php

index adf596f..a20bec0 100644 (file)
@@ -22,6 +22,10 @@ production.
 * The deprecated $wgEditEncoding variable has been removed; it was only used
   for Esperanto language character conversion. You are now recommended to use
   input methods provided by the UniversalLanguageSelector extension.
+* When $wgPingback is true, MediaWiki will periodically ping
+  https://www.mediawiki.org/beacon with basic information about the local
+  MediaWiki installation.  This data includes, for example, the type of system,
+  PHP version, and chosen database backend. This behavior is off by default.
 
 === New features in 1.28 ===
 * User::isBot() method for checking if an account is a bot role account.
@@ -70,6 +74,7 @@ changes to languages because of Phabricator reports.
 * SiteConfiguration::isLocalVHost() was removed (deprecated since 1.25).
 * The 'UserLoginComplete' hook has a new parameter to differentiate between actual
   login and visiting the login page while already logged in.
+* ResourceLoader::makeLoaderURL() was removed (deprecated since 1.24).
 
 == Compatibility ==
 
index ecbc9b3..8c16adf 100644 (file)
@@ -157,6 +157,7 @@ $wgAutoloadLocalClasses = [
        'AuthManagerSpecialPage' => __DIR__ . '/includes/specialpage/AuthManagerSpecialPage.php',
        'AuthPlugin' => __DIR__ . '/includes/AuthPlugin.php',
        'AuthPluginUser' => __DIR__ . '/includes/AuthPlugin.php',
+       'AutoCommitUpdate' => __DIR__ . '/includes/deferred/AutoCommitUpdate.php',
        'AutoLoader' => __DIR__ . '/includes/AutoLoader.php',
        'AutoloadGenerator' => __DIR__ . '/includes/utils/AutoloadGenerator.php',
        'Autopromote' => __DIR__ . '/includes/Autopromote.php',
@@ -329,6 +330,7 @@ $wgAutoloadLocalClasses = [
        'DateFormats' => __DIR__ . '/maintenance/language/date-formats.php',
        'DateFormatter' => __DIR__ . '/includes/parser/DateFormatter.php',
        'DeadendPagesPage' => __DIR__ . '/includes/specials/SpecialDeadendpages.php',
+       'DeferrableCallback' => __DIR__ . '/includes/deferred/DeferrableCallback.php',
        'DeferrableUpdate' => __DIR__ . '/includes/deferred/DeferrableUpdate.php',
        'DeferredStringifier' => __DIR__ . '/includes/libs/DeferredStringifier.php',
        'DeferredUpdates' => __DIR__ . '/includes/deferred/DeferredUpdates.php',
@@ -986,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',
@@ -1038,6 +1039,7 @@ $wgAutoloadLocalClasses = [
        'PermissionsError' => __DIR__ . '/includes/exception/PermissionsError.php',
        'PhpHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
        'PhpXmlBugTester' => __DIR__ . '/includes/installer/PhpBugTests.php',
+       'Pingback' => __DIR__ . '/includes/Pingback.php',
        'PoolCounter' => __DIR__ . '/includes/poolcounter/PoolCounter.php',
        'PoolCounterRedis' => __DIR__ . '/includes/poolcounter/PoolCounterRedis.php',
        'PoolCounterWork' => __DIR__ . '/includes/poolcounter/PoolCounterWork.php',
index d5bf93c..54eb3c0 100644 (file)
@@ -40,7 +40,7 @@
                "wikimedia/relpath": "1.0.3",
                "wikimedia/running-stat": "1.1.0",
                "wikimedia/utfnormal": "1.0.3",
-               "wikimedia/wrappedstring": "2.1.1",
+               "wikimedia/wrappedstring": "2.2.0",
                "zordius/lightncandy": "0.23"
        },
        "require-dev": {
index 16c335c..1e60302 100644 (file)
@@ -8340,6 +8340,21 @@ $wgEventRelayerConfig = [
        ]
 ];
 
+/**
+ * Share data about this installation with MediaWiki developers
+ *
+ * When set to true, 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.
+ *
+ * For details about what data is sent, see: https://www.mediawiki.org/wiki/Pingback
+ *
+ * @var bool
+ * @since 1.28
+ */
+$wgPingback = false;
+
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index 79c8b51..362f905 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  */
 
+use MediaWiki\Logger\LoggerFactory;
+
 /**
  * The edit page/HTML interface (split from Article)
  * The actual database and text munging is still in Article,
@@ -1249,9 +1251,31 @@ class EditPage {
 
                        return $handler->makeEmptyContent();
                } else {
-                       # nasty side-effect, but needed for consistency
-                       $this->contentModel = $rev->getContentModel();
-                       $this->contentFormat = $rev->getContentFormat();
+                       // Content models should always be the same since we error
+                       // out if they are different before this point.
+                       $logger = LoggerFactory::getInstance( 'editpage' );
+                       if ( $this->contentModel !== $rev->getContentModel() ) {
+                               $logger->warning( "Overriding content model from current edit {prev} to {new}", [
+                                       'prev' => $this->contentModel,
+                                       'new' => $rev->getContentModel(),
+                                       'title' => $this->getTitle()->getPrefixedDBkey(),
+                                       'method' => __METHOD__
+                               ] );
+                               $this->contentModel = $rev->getContentModel();
+                       }
+
+                       // Given that the content models should match, the current selected
+                       // format should be supported.
+                       if ( !$content->isSupportedFormat( $this->contentFormat ) ) {
+                               $logger->warning( "Current revision content format unsupported. Overriding {prev} to {new}", [
+
+                                       'prev' => $this->contentFormat,
+                                       'new' => $rev->getContentFormat(),
+                                       'title' => $this->getTitle()->getPrefixedDBkey(),
+                                       'method' => __METHOD__
+                               ] );
+                               $this->contentFormat = $rev->getContentFormat();
+                       }
 
                        return $content;
                }
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 a5567fc..7cb75bb 100644 (file)
@@ -935,13 +935,7 @@ class Html {
                        $attribs['version'] = $wgHtml5Version;
                }
 
-               $html = self::openElement( 'html', $attribs );
-
-               if ( $html ) {
-                       $html .= "\n";
-               }
-
-               $ret .= $html;
+               $ret .= self::openElement( 'html', $attribs );
 
                return $ret;
        }
diff --git a/includes/Pingback.php b/includes/Pingback.php
new file mode 100644 (file)
index 0000000..10d2904
--- /dev/null
@@ -0,0 +1,258 @@
+<?php
+/**
+ * Send information about this MediaWiki instance to MediaWiki.org.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use Psr\Log\LoggerInterface;
+use MediaWiki\Logger\LoggerFactory;
+
+/**
+ * Send information about this MediaWiki instance to MediaWiki.org.
+ *
+ * @since 1.28
+ */
+class Pingback {
+
+       /**
+        * @var int Revision ID of the JSON schema that describes the pingback
+        *   payload. The schema lives on MetaWiki, at
+        *   <https://meta.wikimedia.org/wiki/Schema:MediaWikiPingback>.
+        */
+       const SCHEMA_REV = 15781718;
+
+       /** @var LoggerInterface */
+       protected $logger;
+
+       /** @var Config */
+       protected $config;
+
+       /** @var string updatelog key (also used as cache/db lock key) */
+       protected $key;
+
+       /** @var string Randomly-generated identifier for this wiki */
+       protected $id;
+
+       /**
+        * @param Config $config
+        * @param LoggerInterface $logger
+        */
+       public function __construct( Config $config = null, LoggerInterface $logger = null ) {
+               $this->config = $config ?: RequestContext::getMain()->getConfig();
+               $this->logger = $logger ?: LoggerFactory::getInstance( __CLASS__ );
+               $this->key = 'Pingback-' . $this->config->get( 'Version' );
+       }
+
+       /**
+        * Should a pingback be sent?
+        * @return bool
+        */
+       private function shouldSend() {
+               return $this->config->get( 'Pingback' ) && !$this->checkIfSent();
+       }
+
+       /**
+        * Has a pingback already been sent for this MediaWiki version?
+        * @return bool
+        */
+       private function checkIfSent() {
+               $dbr = wfGetDB( DB_SLAVE );
+               $sent = $dbr->selectField(
+                       'updatelog', '1', [ 'ul_key' => $this->key ], __METHOD__ );
+               return $sent !== false;
+       }
+
+       /**
+        * Record the fact that we have sent a pingback for this MediaWiki version,
+        * to ensure we don't submit data multiple times.
+        */
+       private function markSent() {
+               $dbw = wfGetDB( DB_MASTER );
+               return $dbw->insert(
+                       'updatelog', [ 'ul_key' => $this->key ], __METHOD__, 'IGNORE' );
+       }
+
+       /**
+        * Acquire lock for sending a pingback
+        *
+        * This ensures only one thread can attempt to send a pingback at any given
+        * time and that we wait an hour before retrying failed attempts.
+        *
+        * @return bool Whether lock was acquired
+        */
+       private function acquireLock() {
+               $cache = ObjectCache::getLocalClusterInstance();
+               if ( !$cache->add( $this->key, 1, 60 * 60 ) ) {
+                       return false;  // throttled
+               }
+
+               $dbw = wfGetDB( DB_MASTER );
+               if ( !$dbw->lock( $this->key, __METHOD__, 0 ) ) {
+                       return false;  // already in progress
+               }
+
+               return true;
+       }
+
+       /**
+        * Collect basic data about this MediaWiki installation and return it
+        * 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
+        */
+       public function getSystemInfo() {
+               $event = [
+                       'database'   => $this->config->get( 'DBtype' ),
+                       'MediaWiki'  => $this->config->get( 'Version' ),
+                       'PHP'        => PHP_VERSION,
+                       'OS'         => PHP_OS . ' ' . php_uname( 'r' ),
+                       'arch'       => PHP_INT_SIZE === 8 ? 64 : 32,
+                       'machine'    => php_uname( 'm' ),
+               ];
+
+               if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
+                       $event['serverSoftware'] = $_SERVER['SERVER_SOFTWARE'];
+               }
+
+               $limit = ini_get( 'memory_limit' );
+               if ( $limit && $limit != -1 ) {
+                       $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'            => $this->getSystemInfo(),
+               ];
+       }
+
+       /**
+        * Get a unique, stable identifier for this wiki
+        *
+        * If the identifier does not already exist, create it and save it in the
+        * database. The identifier is randomly-generated.
+        *
+        * @return string 32-character hex string
+        */
+       private function getOrCreatePingbackId() {
+               if ( !$this->id ) {
+                       $id = wfGetDB( DB_SLAVE )->selectField(
+                               'updatelog', 'ul_value', [ 'ul_key' => 'PingBack' ] );
+
+                       if ( $id == false ) {
+                               $id = MWCryptRand::generateHex( 32 );
+                               $dbw = wfGetDB( DB_MASTER );
+                               $dbw->insert(
+                                       'updatelog',
+                                       [ 'ul_key' => 'PingBack', 'ul_value' => $id ],
+                                       __METHOD__,
+                                       'IGNORE'
+                               );
+
+                               if ( !$dbw->affectedRows() ) {
+                                       $id = $dbw->selectField(
+                                               'updatelog', 'ul_value', [ 'ul_key' => 'PingBack' ] );
+                               }
+                       }
+
+                       $this->id = $id;
+               }
+
+               return $this->id;
+       }
+
+       /**
+        * Serialize pingback data and send it to MediaWiki.org via a POST
+        * to its event beacon endpoint.
+        *
+        * The data encoding conforms to the expectations of EventLogging,
+        * a software suite used by the Wikimedia Foundation for logging and
+        * processing analytic data.
+        *
+        * Compare:
+        * <https://github.com/wikimedia/mediawiki-extensions-EventLogging/
+        *   blob/7e5fe4f1ef/includes/EventLogging.php#L32-L74>
+        *
+        * @param data Pingback data as an associative array
+        * @return bool true on success, false on failure
+        */
+       private function postPingback( array $data ) {
+               $json = FormatJson::encode( $data );
+               $queryString = rawurlencode( str_replace( ' ', '\u0020', $json ) ) . ';';
+               $url = 'https://www.mediawiki.org/beacon/event?' . $queryString;
+               return Http::post( $url ) !== false;
+       }
+
+       /**
+        * Send information about this MediaWiki instance to MediaWiki.org.
+        *
+        * The data is structured and serialized to match the expectations of
+        * EventLogging, a software suite used by the Wikimedia Foundation for
+        * logging and processing analytic data.
+        *
+        * Compare:
+        * <https://github.com/wikimedia/mediawiki-extensions-EventLogging/
+        *   blob/7e5fe4f1ef/includes/EventLogging.php#L32-L74>
+        *
+        * The schema for the data is located at:
+        * <https://meta.wikimedia.org/wiki/Schema:MediaWikiPingback>
+        */
+       public function sendPingback() {
+               if ( !$this->acquireLock() ) {
+                       $this->logger->debug( __METHOD__ . ": couldn't acquire lock" );
+                       return false;
+               }
+
+               $data = $this->getData();
+               if ( !$this->postPingback( $data ) ) {
+                       $this->logger->warning( __METHOD__ . ": failed to send pingback; check 'http' log" );
+                       return false;
+               }
+
+               $this->markSent();
+               $this->logger->debug( __METHOD__ . ": pingback sent OK ({$this->key})" );
+               return true;
+       }
+
+       /**
+        * Schedule a deferred callable that will check if a pingback should be
+        * sent and (if so) proceed to send it.
+        */
+       public static function schedulePingback() {
+               DeferredUpdates::addCallableUpdate( function () {
+                       $instance = new Pingback;
+                       if ( $instance->shouldSend() ) {
+                               $instance->sendPingback();
+                       }
+               } );
+       }
+}
index da224a0..6c5de90 100644 (file)
@@ -871,6 +871,10 @@ if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
        unset( $sessionUser );
 }
 
+if ( !$wgCommandLineMode ) {
+       Pingback::schedulePingback();
+}
+
 wfDebug( "Fully initialised\n" );
 $wgFullyInitialised = true;
 
index 62f4060..8aa8cb7 100644 (file)
@@ -4367,18 +4367,23 @@ class Title implements LinkTarget {
                        return true; // avoid gap locking if we know it's not there
                }
 
-               $method = __METHOD__;
-               $dbw = wfGetDB( DB_MASTER );
                $conds = $this->pageCond();
-               $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method, $purgeTime ) {
-                       $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
-                       $dbw->update(
-                               'page',
-                               [ 'page_touched' => $dbTimestamp ],
-                               $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
-                               $method
-                       );
-               } );
+               DeferredUpdates::addUpdate(
+                       new AutoCommitUpdate(
+                               wfGetDB( DB_MASTER ),
+                               __METHOD__,
+                               function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
+                                       $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
+                                       $dbw->update(
+                                               'page',
+                                               [ 'page_touched' => $dbTimestamp ],
+                                               $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
+                                               $fname
+                                       );
+                               }
+                       ),
+                       DeferredUpdates::PRESEND
+               );
 
                return true;
        }
index 8437abb..605fcf5 100644 (file)
@@ -23,7 +23,7 @@
        "apihelp-main-param-requestid": "Libovolná zde uvedená hodnota bude zahrnuta v odpovědi. Lze použít pro rozlišení požadavků.",
        "apihelp-main-param-servedby": "Zahrnout do odpovědi název hostitele, který požadavek obsloužil.",
        "apihelp-main-param-curtimestamp": "Zahrnout do odpovědi aktuální časové razítko.",
-       "apihelp-main-param-origin": "Pokud k API přistupujete pomocí mezidoménového AJAXového požadavku (CORS), nastavte tento parametr na doménu původu. Musí být součástí všech předběžných požadavků, takže musí být součástí URI požadavku (nikoli těla POSTu). Hodnota musí přesně odpovídat jednomu z původů v hlavičce <code>Origin</code>, takže musí být nastavena na něco jako <kbd>https://en.wikipedia.org</kbd> nebo <kbd>https://meta.wikimedia.org</kbd>. Pokud parametr neodpovídá hlavičce <code>Origin</code>, bude vrácena odpověď 403. Pokud parametr odpovídá hlavičce <code>Origin</code> a tento původ je na bílé listině, bude nastavena hlavička <code>Access-Control-Allow-Origin</code>.",
+       "apihelp-main-param-origin": "Pokud k API přistupujete pomocí mezidoménového AJAXového požadavku (CORS), nastavte tento parametr na doménu původu. Musí být součástí všech předběžných požadavků, takže musí být součástí URI požadavku (nikoli těla POSTu).\n\nU ověřených požadavků hodnota musí přesně odpovídat jednomu z původů v hlavičce <code>Origin</code>, takže musí být nastavena na něco jako <kbd>https://en.wikipedia.org</kbd> nebo <kbd>https://meta.wikimedia.org</kbd>. Pokud parametr neodpovídá hlavičce <code>Origin</code>, bude vrácena odpověď 403. Pokud parametr odpovídá hlavičce <code>Origin</code> a tento původ je na bílé listině, hlavičky <code>Access-Control-Allow-Origin</code> a <code>Access-Control-Allow-Credentials</code>budou nastaveny.\n\nU neověřených požadavků specifikujte hodnotu <kbd>*</kbd>. To pomůže nastavit hlavičku <code>Access-Control-Allow-Origin</code>, ale hlavička <code>Access-Control-Allow-Credentials</code> bude <code>false</code> a všechna data uživatelů budou omezena.",
        "apihelp-main-param-uselang": "Jazyk, který se má použít pro překlad hlášení. Pomocí <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> se <kbd>siprop=languages</kbd> získáte seznam jazykových kódů nebo zadejte „<kbd>user</kbd>“ pro použití předvoleného jazyka aktuálního uživatele či „<kbd>content</kbd>“ pro použití jazyka obsahu této wiki.",
        "apihelp-block-description": "Zablokovat uživatele.",
        "apihelp-block-param-user": "Uživatelské jméno, IP adresa nebo rozsah IP adres, které chcete zablokovat.",
index d5f70d8..1ec5b50 100644 (file)
@@ -41,7 +41,7 @@
        "apihelp-block-example-ip-simple": "IP <kbd>192.0.2.5</kbd>에 대해 <kbd>First strike</kbd>라는 이유로 3일 간 차단하기",
        "apihelp-block-example-user-complex": "사용자 <kbd>Vandal</kbd>을 <kbd>Vandalism</kbd>이라는 이유로 무기한 차단하며 계정 생성 및 이메일 발송을 막기",
        "apihelp-changeauthenticationdata-description": "현재 사용자의 인증 데이터를 변경합니다.",
-       "apihelp-changeauthenticationdata-example-password": "í\98\84ì\9e¬ ì\82¬ì\9a©ì\9e\90ì\9d\98 ë¹\84ë°\80ë²\88í\98¸ë¥¼ <kbd>ExamplePassword</kbd>ë¡\9c ë³\80ê²½í\95\98는 것을 시도합니다.",
+       "apihelp-changeauthenticationdata-example-password": "í\98\84ì\9e¬ ì\82¬ì\9a©ì\9e\90ì\9d\98 ë¹\84ë°\80ë²\88í\98¸ë¥¼ <kbd>ExamplePassword</kbd>ë¡\9c ë°\94꾸는 것을 시도합니다.",
        "apihelp-checktoken-description": "<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>의 토큰의 유효성을 확인합니다.",
        "apihelp-checktoken-param-type": "테스트되는 토큰의 종류.",
        "apihelp-checktoken-param-token": "테스트할 토큰",
        "apihelp-emailuser-param-subject": "제목 헤더.",
        "apihelp-emailuser-param-text": "메일 본문.",
        "apihelp-emailuser-param-ccme": "자신에게 메일의 복사본을 보냅니다.",
-       "apihelp-emailuser-example-email": "<kbd>WikiSysop</kbd> 사용자에게 텍스트 <kbd>콘텐츠</kbd>로 이메일을 보냅니다.",
+       "apihelp-emailuser-example-email": "<kbd>WikiSysop</kbd> 사용자에게 텍스트 <kbd>Content</kbd>로 이메일을 보냅니다.",
        "apihelp-expandtemplates-description": "모든 틀을 위키텍스트로 확장.",
        "apihelp-expandtemplates-param-title": "문서 제목",
        "apihelp-expandtemplates-param-text": "변환할 위키텍스트.",
        "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 b956d4b..c2e8b24 100644 (file)
@@ -451,6 +451,13 @@ class IcuCollation extends Collation {
                $versionPrefix = substr( $icuVersion, 0, 3 );
                // Source: http://site.icu-project.org/download
                $map = [
+                       '57.' => '8.0',
+                       '56.' => '8.0',
+                       '55.' => '7.0',
+                       '54.' => '7.0',
+                       '53.' => '6.3',
+                       '52.' => '6.3',
+                       '51.' => '6.2',
                        '50.' => '6.2',
                        '49.' => '6.1',
                        '4.8' => '6.0',
index 16ea219..9b73584 100644 (file)
@@ -2460,7 +2460,7 @@ abstract class DatabaseBase implements IDatabase {
        final public function onTransactionIdle( callable $callback ) {
                $this->mTrxIdleCallbacks[] = [ $callback, wfGetCaller() ];
                if ( !$this->mTrxLevel ) {
-                       $this->runOnTransactionIdleCallbacks();
+                       $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
                }
        }
 
@@ -2475,9 +2475,10 @@ abstract class DatabaseBase implements IDatabase {
        /**
         * Actually any "on transaction idle" callbacks.
         *
+        * @param integer $trigger IDatabase::TRIGGER_* constant
         * @since 1.20
         */
-       protected function runOnTransactionIdleCallbacks() {
+       protected function runOnTransactionIdleCallbacks( $trigger ) {
                $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
 
                $e = $ePrior = null; // last exception
@@ -2492,7 +2493,7 @@ abstract class DatabaseBase implements IDatabase {
                                try {
                                        list( $phpCallback ) = $callback;
                                        $this->clearFlag( DBO_TRX ); // make each query its own transaction
-                                       call_user_func( $phpCallback );
+                                       call_user_func_array( $phpCallback, [ $trigger ] );
                                        if ( $autoTrx ) {
                                                $this->setFlag( DBO_TRX ); // restore automatic begin()
                                        } else {
@@ -2563,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 ) {
@@ -2623,7 +2624,7 @@ abstract class DatabaseBase implements IDatabase {
                                        $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
                        }
 
-                       $this->runOnTransactionIdleCallbacks();
+                       $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
                }
 
                // Avoid fatals if close() was called
@@ -2699,7 +2700,7 @@ abstract class DatabaseBase implements IDatabase {
                                $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
                }
 
-               $this->runOnTransactionIdleCallbacks();
+               $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
        }
 
        /**
@@ -2739,7 +2740,7 @@ abstract class DatabaseBase implements IDatabase {
 
                $this->mTrxIdleCallbacks = []; // clear
                $this->mTrxPreCommitCallbacks = []; // clear
-               $this->runOnTransactionIdleCallbacks();
+               $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
        }
 
        /**
index 36772b8..c024632 100644 (file)
  * @ingroup Database
  */
 interface IDatabase {
+       /* Constants to onTransactionResolution() callbacks */
+       const TRIGGER_IDLE = 1;
+       const TRIGGER_COMMIT = 2;
+       const TRIGGER_ROLLBACK = 3;
+
        /**
         * A string describing the current software version, and possibly
         * other details in a user-friendly way. Will be listed on Special:Version, etc.
@@ -1223,6 +1228,9 @@ interface IDatabase {
         *
         * This is useful for combining cooperative locks and DB transactions.
         *
+        * The callback takes one argument:
+        * How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK)
+        *
         * @param callable $callback
         * @return mixed
         * @since 1.28
@@ -1242,6 +1250,9 @@ interface IDatabase {
         *
         * Updates will execute in the order they were enqueued.
         *
+        * The callback takes one argument:
+        * How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_IDLE)
+        *
         * @param callable $callback
         * @since 1.20
         */
index a9921b3..0da5d7d 100644 (file)
@@ -4,31 +4,43 @@
  * Deferrable Update for closure/callback updates via IDatabase::doAtomicSection()
  * @since 1.27
  */
-class AtomicSectionUpdate implements DeferrableUpdate {
+class AtomicSectionUpdate implements DeferrableUpdate, DeferrableCallback {
        /** @var IDatabase */
        private $dbw;
        /** @var string */
        private $fname;
-       /** @var Closure|callable */
+       /** @var callable */
        private $callback;
 
        /**
         * @param IDatabase $dbw
         * @param string $fname Caller name (usually __METHOD__)
         * @param callable $callback
-        * @throws InvalidArgumentException
         * @see IDatabase::doAtomicSection()
         */
-       public function __construct( IDatabase $dbw, $fname, $callback ) {
+       public function __construct( IDatabase $dbw, $fname, callable $callback ) {
                $this->dbw = $dbw;
                $this->fname = $fname;
-               if ( !is_callable( $callback ) ) {
-                       throw new InvalidArgumentException( 'Not a valid callback/closure!' );
-               }
                $this->callback = $callback;
+
+               if ( $this->dbw->trxLevel() ) {
+                       $this->dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ] );
+               }
        }
 
        public function doUpdate() {
-               $this->dbw->doAtomicSection( $this->fname, $this->callback );
+               if ( $this->callback ) {
+                       $this->dbw->doAtomicSection( $this->fname, $this->callback );
+               }
+       }
+
+       public function cancelOnRollback( $trigger ) {
+               if ( $trigger === IDatabase::TRIGGER_ROLLBACK ) {
+                       $this->callback = null;
+               }
+       }
+
+       public function getOrigin() {
+               return $this->fname;
        }
 }
diff --git a/includes/deferred/AutoCommitUpdate.php b/includes/deferred/AutoCommitUpdate.php
new file mode 100644 (file)
index 0000000..ef5903b
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * Deferrable Update for closure/callback updates that should use auto-commit mode
+ * @since 1.28
+ */
+class AutoCommitUpdate implements DeferrableUpdate, DeferrableCallback {
+       /** @var IDatabase */
+       private $dbw;
+       /** @var string */
+       private $fname;
+       /** @var callable */
+       private $callback;
+
+       /**
+        * @param IDatabase $dbw
+        * @param string $fname Caller name (usually __METHOD__)
+        * @param callable $callback Callback that takes (IDatabase, method name string)
+        */
+       public function __construct( IDatabase $dbw, $fname, callable $callback ) {
+               $this->dbw = $dbw;
+               $this->fname = $fname;
+               $this->callback = $callback;
+
+               if ( $this->dbw->trxLevel() ) {
+                       $this->dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ] );
+               }
+       }
+
+       public function doUpdate() {
+               if ( !$this->callback ) {
+                       return;
+               }
+
+               $autoTrx = $this->dbw->getFlag( DBO_TRX );
+               $this->dbw->clearFlag( DBO_TRX );
+               try {
+                       /** @var Exception $e */
+                       $e = null;
+                       call_user_func_array( $this->callback, [ $this->dbw, $this->fname ] );
+               } catch ( Exception $e ) {
+               }
+               if ( $autoTrx ) {
+                       $this->dbw->setFlag( DBO_TRX );
+               }
+               if ( $e ) {
+                       throw $e;
+               }
+       }
+
+       public function cancelOnRollback( $trigger ) {
+               if ( $trigger === IDatabase::TRIGGER_ROLLBACK ) {
+                       $this->callback = null;
+               }
+       }
+
+       public function getOrigin() {
+               return $this->fname;
+       }
+}
index 4b19c20..d63c292 100644 (file)
@@ -3,22 +3,26 @@
 /**
  * Deferrable Update for closure/callback
  */
-class MWCallableUpdate implements DeferrableUpdate {
-       /** @var Closure|callable */
+class MWCallableUpdate implements DeferrableUpdate, DeferrableCallback {
+       /** @var callable */
        private $callback;
+       /** @var string */
+       private $fname;
 
        /**
         * @param callable $callback
-        * @throws InvalidArgumentException
+        * @param string $fname Calling method
         */
-       public function __construct( $callback ) {
-               if ( !is_callable( $callback ) ) {
-                       throw new InvalidArgumentException( 'Not a valid callback/closure!' );
-               }
+       public function __construct( callable $callback, $fname = 'unknown' ) {
                $this->callback = $callback;
+               $this->fname = $fname;
        }
 
        public function doUpdate() {
                call_user_func( $this->callback );
        }
+
+       public function getOrigin() {
+               return $this->fname;
+       }
 }
diff --git a/includes/deferred/DeferrableCallback.php b/includes/deferred/DeferrableCallback.php
new file mode 100644 (file)
index 0000000..2eb0d5d
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * Callback wrapper that has an originating method
+ *
+ * @since 1.28
+ */
+interface DeferrableCallback {
+       /**
+        * @return string Originating method name
+        */
+       function getOrigin();
+}
index 1552777..9768838 100644 (file)
@@ -61,7 +61,7 @@ class DeferredUpdates {
        }
 
        /**
-        * Add a callable update.  In a lot of cases, we just need a callback/closure,
+        * Add a callable update. In a lot of cases, we just need a callback/closure,
         * defining a new DeferrableUpdate object is not necessary
         *
         * @see MWCallableUpdate::__construct()
@@ -70,7 +70,7 @@ class DeferredUpdates {
         * @param integer $type DeferredUpdates constant (PRESEND or POSTSEND) (since 1.27)
         */
        public static function addCallableUpdate( $callable, $type = self::POSTSEND ) {
-               self::addUpdate( new MWCallableUpdate( $callable ), $type );
+               self::addUpdate( new MWCallableUpdate( $callable, wfGetCaller() ), $type );
        }
 
        /**
@@ -143,7 +143,11 @@ class DeferredUpdates {
                                } else {
                                        $otherUpdates[] = $update;
                                }
-                               $stats->increment( 'deferred_updates.' . $method . '.' . get_class( $update ) );
+
+                               $name = $update instanceof DeferrableCallback
+                                       ? get_class( $update ) . '-' . $update->getOrigin()
+                                       : get_class( $update );
+                               $stats->increment( 'deferred_updates.' . $method . '.' . $name );
                        }
 
                        // Delegate DataUpdate execution to the DataUpdate class
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 f4410ca..e5ded45 100644 (file)
@@ -37,7 +37,7 @@
  * @since 1.19
  */
 abstract class DBLockManager extends QuorumLockManager {
-       /** @var array Map of DB names to server config */
+       /** @var array[] Map of DB names to server config */
        protected $dbServers; // (DB name => server config array)
        /** @var BagOStuff */
        protected $statusCache;
@@ -46,7 +46,7 @@ abstract class DBLockManager extends QuorumLockManager {
        protected $safeDelay; // integer number of seconds
 
        protected $session = 0; // random integer
-       /** @var array Map Database connections (DB name => Database) */
+       /** @var IDatabase[] Map Database connections (DB name => Database) */
        protected $conns = [];
 
        /**
@@ -113,6 +113,8 @@ abstract class DBLockManager extends QuorumLockManager {
                return $status;
        }
 
+       abstract protected function doGetLocksOnServer( $lockSrv, array $paths, $type );
+
        protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
                return Status::newGood();
        }
index cab9316..e35af75 100644 (file)
@@ -1637,16 +1637,20 @@ class LocalFile extends File {
                // Purge the source and target files...
                $oldTitleFile = wfLocalFile( $this->title );
                $newTitleFile = wfLocalFile( $target );
-               // Hack: the lock()/unlock() pair is nested in a transaction so the locking is not
-               // tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
-               $this->getRepo()->getMasterDB()->onTransactionIdle(
-                       function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
-                               $oldTitleFile->purgeEverything();
-                               foreach ( $archiveNames as $archiveName ) {
-                                       $oldTitleFile->purgeOldThumbnails( $archiveName );
+               // To avoid slow purges in the transaction, move them outside...
+               DeferredUpdates::addUpdate(
+                       new AutoCommitUpdate(
+                               $this->getRepo()->getMasterDB(),
+                               __METHOD__,
+                               function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
+                                       $oldTitleFile->purgeEverything();
+                                       foreach ( $archiveNames as $archiveName ) {
+                                               $oldTitleFile->purgeOldThumbnails( $archiveName );
+                                       }
+                                       $newTitleFile->purgeEverything();
                                }
-                               $newTitleFile->purgeEverything();
-                       }
+                       ),
+                       DeferredUpdates::PRESEND
                );
 
                if ( $status->isOK() ) {
@@ -1682,7 +1686,7 @@ class LocalFile extends File {
 
                $this->lock(); // begin
                $batch->addCurrent();
-               # Get old version relative paths
+               // Get old version relative paths
                $archiveNames = $batch->addOlds();
                $status = $batch->execute();
                $this->unlock(); // done
@@ -1691,16 +1695,19 @@ class LocalFile extends File {
                        DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => -1 ] ) );
                }
 
-               // Hack: the lock()/unlock() pair is nested in a transaction so the locking is not
-               // tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
-               $that = $this;
-               $this->getRepo()->getMasterDB()->onTransactionIdle(
-                       function () use ( $that, $archiveNames ) {
-                               $that->purgeEverything();
-                               foreach ( $archiveNames as $archiveName ) {
-                                       $that->purgeOldThumbnails( $archiveName );
+               // To avoid slow purges in the transaction, move them outside...
+               DeferredUpdates::addUpdate(
+                       new AutoCommitUpdate(
+                               $this->getRepo()->getMasterDB(),
+                               __METHOD__,
+                               function () use ( $archiveNames ) {
+                                       $this->purgeEverything();
+                                       foreach ( $archiveNames as $archiveName ) {
+                                               $this->purgeOldThumbnails( $archiveName );
+                                       }
                                }
-                       }
+                       ),
+                       DeferredUpdates::PRESEND
                );
 
                // Purge the CDN
@@ -2286,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.
@@ -2305,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 4d5aa7a..5e3758d 100644 (file)
@@ -180,6 +180,7 @@ abstract class Installer {
                'wgUseInstantCommons',
                'wgUpgradeKey',
                'wgDefaultSkin',
+               'wgPingback',
        ];
 
        /**
index ced7b93..1d7c7f2 100644 (file)
@@ -64,7 +64,7 @@ class LocalSettingsGenerator {
                                'wgRightsText', '_MainCacheType', 'wgEnableUploads',
                                '_MemCachedServers', 'wgDBserver', 'wgDBuser',
                                'wgDBpassword', 'wgUseInstantCommons', 'wgUpgradeKey', 'wgDefaultSkin',
-                               'wgMetaNamespace', 'wgLogo', 'wgAuthenticationTokenVersion',
+                               'wgMetaNamespace', 'wgLogo', 'wgAuthenticationTokenVersion', 'wgPingback',
                        ],
                        $db->getGlobalNames()
                );
@@ -72,7 +72,8 @@ class LocalSettingsGenerator {
                $unescaped = [ 'wgRightsIcon', 'wgLogo' ];
                $boolItems = [
                        'wgEnableEmail', 'wgEnableUserEmail', 'wgEnotifUserTalk',
-                       'wgEnotifWatchlist', 'wgEmailAuthentication', 'wgEnableUploads', 'wgUseInstantCommons'
+                       'wgEnotifWatchlist', 'wgEmailAuthentication', 'wgEnableUploads', 'wgUseInstantCommons',
+                       'wgPingback',
                ];
 
                foreach ( $confItems as $c ) {
@@ -372,6 +373,11 @@ ${serverSetting}
 # InstantCommons allows wiki to use images from https://commons.wikimedia.org
 \$wgUseInstantCommons = {$this->values['wgUseInstantCommons']};
 
+# Periodically send a pingback to https://www.mediawiki.org/ with basic data
+# about this MediaWiki instance. The Wikimedia Foundation shares this data
+# with MediaWiki developers to help guide future development efforts.
+\$wgPingback = {$this->values['wgPingback']};
+
 ## If you use ImageMagick (or any other shell command) on a
 ## Linux server, this will need to be set to the name of an
 ## available UTF-8 locale
index dcd30cf..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',
@@ -100,6 +105,15 @@ class WebInstallerName extends WebInstallerPage {
                                'label' => 'config-subscribe',
                                'help' => $this->parent->getHelpBox( 'config-subscribe-help' )
                        ] ) .
+                       $this->parent->getCheckBox( [
+                               'var' => 'wgPingback',
+                               'label' => 'config-pingback',
+                               'help' => $this->parent->getHelpBox(
+                                       'config-pingback-help',
+                                       FormatJson::encode( $pingbackInfo, true )
+                               ),
+                               'value' => true,
+                       ] ) .
                        $this->getFieldsetEnd() .
                        $this->parent->getInfoBox( wfMessage( 'config-almost-done' )->text() ) .
                        // getRadioSet() builds a set of labeled radio buttons.
@@ -129,7 +143,7 @@ class WebInstallerName extends WebInstallerPage {
                $retVal = true;
                $this->parent->setVarsFromRequest( [ 'wgSitename', '_NamespaceType',
                        '_AdminName', '_AdminPassword', '_AdminPasswordConfirm', '_AdminEmail',
-                       '_Subscribe', '_SkipOptional', 'wgMetaNamespace' ] );
+                       '_Subscribe', '_SkipOptional', 'wgMetaNamespace', 'wgPingback' ] );
 
                // Validate site name
                if ( strval( $this->getVar( 'wgSitename' ) ) === '' ) {
index f8dc8ee..62fe785 100644 (file)
@@ -267,6 +267,7 @@ class WebInstallerOutput {
                }
 ?>
 <?php echo Html::htmlHeader( $this->getHeadAttribs() ); ?>
+
 <head>
        <meta name="robots" content="noindex, nofollow" />
        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
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 79383f3..3f3032b 100644 (file)
        "config-subscribe": "Subscribe to the [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release announcements mailing list].",
        "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. 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 f23fbed..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": "지겨워요, 그냥 위키를 설치할래요.",
        "config-memcache-needservers": "캐시 종류로 Memcached를 선택했지만 어떠한 서버도 지정하지 않았습니다.",
        "config-memcache-badip": "Memcached에 대해 잘못된 IP 주소를 입력했습니다: $1.",
        "config-memcache-noport": "Memcached 서버에 사용할 포트를 지정하지 않았습니다: $1.\n포트를 모를 경우 기본 값은 11211입니다.",
-       "config-memcache-badport": "Memcached 포트 번호는 $1(와)과 $2 사이여야 합니다.",
+       "config-memcache-badport": "Memcached 포트 번호는 $1와(과) $2 사이여야 합니다.",
        "config-extensions": "확장 기능",
        "config-extensions-help": "위에 나열된 확장 기능이 <code>./extensions</code>에서 발견되었습니다.\n\n추가적인 설정이 필요할 수 있습니다만 지금 활성화시킬 수 있습니다.",
        "config-skins": "스킨",
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 69a6830..833e7d6 100644 (file)
        "config-subscribe": "Used as label for the installer checkbox",
        "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. $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 6965a15..914618b 100644 (file)
@@ -1,18 +1,22 @@
 {
        "@metadata": {
                "authors": [
-                       "VASANTH S.N."
+                       "VASANTH S.N.",
+                       "Vishwanatha Badikana"
                ]
        },
-       "config-title": "ಮೀಡಿಯಾವಿಕಿ ಆವೃತ್ತಿ $1 ರ ಅನುಸ್ಥಾಪನೆ",
+       "config-desc": "ಮೀಡಿಯ ವಿಕಿದ ಪ್ರತಿಸ್ಟಾಪನೆ",
+       "config-title": "ಮೀಡಿಯಾವಿಕಿ ಆವೃತ್ತಿ $1 ರ ಪ್ರತಿಸ್ಟಾಪನೆ",
        "config-information": "ಮಾಹಿತಿ",
-       "config-localsettings-key": "ಉನ್ನತೀಕರಣ ಕೀಲಿ",
-       "config-session-error": "ಅವಧಿ ಪ್ರಾರಂಭದ ದೋಷ: $1",
+       "config-localsettings-upgrade": "ಒಂಜಿ<code>LocalSettings.php</code>ದ ಕಡತೊ ಪತ್ತೆ ಆತ್ಂಡ್.  ಈ ಪ್ರತಿಸ್ಟಾಪನೆನ್ ಪೊಸತ್ ಮಲ್ಪೆರೆ, ದಯದೀಡ್ದ್ ತಿರ್ತ್‌ದ ಪಟ್ಟಿಗೆದ <code>$wgUpgradeKey</code>ದ ಬಿಲೆನ್ ನಮೂದಿಸಲೆ.\nಈರ್ <code>LocalSettings.php</code>ನ್ ನಾಡೊಲಿ",
+       "config-localsettings-cli-upgrade": "ಒಂಜಿ<code>LocalSettings.php</code>ದ ಕಡತೊ ಪತ್ತೆ ಆತ್ಂಡ್.  ಈ ಪ್ರತಿಸ್ಟಾಪನೆನ್ ಪೊಸತ್ ಮಲ್ಪೆರೆ, ದಯದೀಡ್ದ್ ತಿರ್ತ್‌ದ ಪಟ್ಟಿಗೆದ <code>$wgUpgradeKey</code>ದ ಬಿಲೆನ್ ನಮೂದಿಸಲೆ.\nದಯಮಲ್ತ್ <code>LocalSettings.php</code>ಗ್ ಬದಲಾದ್  ಬಲಿಪಾಲೆ",
+       "config-localsettings-key": "ಏಲಿಗೆದ ಕೀ",
+       "config-session-error": "ಸಬೆ ಸುರುಮಲ್ಪುನ ದೋಸೊ: $1",
        "config-your-language": "ಇರೆನಾ ಬಾಸೆ",
-       "config-wiki-language": "ವಿà²\95ಿ à²­à²¾à²·ೆ:",
+       "config-wiki-language": "ವಿà²\95ಿ à²¬à²¾à²¸ೆ:",
        "config-back": "← ಪಿರ",
        "config-continue": "ಮುಂದುವರೆಸಾಲೆ →",
-       "config-page-language": "ಭಾಸೆ",
+       "config-page-language": "ಬಾಸೆ",
        "config-page-welcome": "ಮಾಧ್ಯಮವಿಕಿಗ್ ಸ್ವಾಗತ",
        "config-page-dbconnect": "ದತ್ತಾಂಶಸಂಚಯಗ್ ಸಂಪರ್ಕಕೊರ್ಲೆ",
        "config-page-name": "ಪುದರ್",
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 1bbfc56..9fc3fe1 100644 (file)
@@ -1,16 +1,24 @@
 <?php
 
 /**
- * Interface to key-value storage on HTTP RESTful server, such as RESTBase.
- * Uses URL of the form URL/{KEY} to store/fetch/delete.
- * E.g., when base URL is /v1/sessions/ then the store would do:
- * PUT /v1/sessions/12345758
+ * Interface to key-value storage behind an HTTP server.
+ *
+ * Uses URL of the form "baseURL/{KEY}" to store, fetch, and delete values.
+ *
+ * E.g., when base URL is `/v1/sessions/`, then the store would do:
+ *
+ * `PUT /v1/sessions/12345758`
+ *
  * and fetch would do:
- * GET /v1/sessions/12345758
+ *
+ * `GET /v1/sessions/12345758`
+ *
  * delete would do:
- * DELETE /v1/sessions/12345758
+ *
+ * `DELETE /v1/sessions/12345758`
  *
  * Configure with:
+ *
  * @code
  * $wgObjectCaches['sessions'] = array(
  *     'class' => 'RESTBagOStuff',
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 ccf764b..6596112 100644 (file)
@@ -1468,34 +1468,6 @@ MESSAGE;
                return wfAppendQuery( $script, $query );
        }
 
-       /**
-        * Build a load.php URL
-        * @deprecated since 1.24 Use createLoaderURL() instead
-        * @param array $modules Array of module names (strings)
-        * @param string $lang Language code
-        * @param string $skin Skin name
-        * @param string|null $user User name. If null, the &user= parameter is omitted
-        * @param string|null $version Versioning timestamp
-        * @param bool $debug Whether the request should be in debug mode
-        * @param string|null $only &only= parameter
-        * @param bool $printable Printable mode
-        * @param bool $handheld Handheld mode
-        * @param array $extraQuery Extra query parameters to add
-        * @return string URL to load.php. May be protocol-relative if $wgLoadScript is, too.
-        */
-       public static function makeLoaderURL( $modules, $lang, $skin, $user = null,
-               $version = null, $debug = false, $only = null, $printable = false,
-               $handheld = false, $extraQuery = []
-       ) {
-               global $wgLoadScript;
-
-               $query = self::makeLoaderQuery( $modules, $lang, $skin, $user, $version, $debug,
-                       $only, $printable, $handheld, $extraQuery
-               );
-
-               return wfAppendQuery( $wgLoadScript, $query );
-       }
-
        /**
         * Helper for createLoaderURL()
         *
@@ -1505,7 +1477,7 @@ MESSAGE;
         * @param array $extraQuery
         * @return array
         */
-       public static function createLoaderQuery( ResourceLoaderContext $context, $extraQuery = [] ) {
+       protected static function createLoaderQuery( ResourceLoaderContext $context, $extraQuery = [] ) {
                return self::makeLoaderQuery(
                        $context->getModules(),
                        $context->getLanguage(),
@@ -1522,7 +1494,7 @@ MESSAGE;
 
        /**
         * Build a query array (array representation of query string) for load.php. Helper
-        * function for makeLoaderURL().
+        * function for createLoaderURL().
         *
         * @param array $modules
         * @param string $lang
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 9e96b14..b2d6ba1 100644 (file)
@@ -43,6 +43,11 @@ use \Sanitizer;
 # as soon as possible (usually as soon as the tag is closed) to reduce
 # its memory footprint.
 
+# We've been gradually lifting some of these restrictions to handle
+# non-sanitized output generated by extensions, but we shortcut the tokenizer
+# for speed (primarily by splitting on `<`) and so rely on syntactic
+# well-formedness.
+
 # On the other hand, I've been pretty careful to note with comments in the
 # code the places where this implementation omits features of the spec or
 # depends on the MediaWiki Sanitizer.  Perhaps in the future we'll want to
@@ -69,10 +74,10 @@ class BalanceSets {
        public static $unsupportedSet = [
                self::HTML_NAMESPACE => [
                        'html' => true, 'head' => true, 'body' => true, 'frameset' => true,
-                       'form' => true, 'frame' => true,
-                       'plaintext' => true, 'isindex' => true, 'textarea' => true,
+                       'frame' => true,
+                       'plaintext' => true, 'isindex' => true,
                        'xmp' => true, 'iframe' => true, 'noembed' => true,
-                       'noscript' => true, 'select' => true, 'script' => true,
+                       'noscript' => true, 'script' => true,
                        'title' => true
                ]
        ];
@@ -87,6 +92,12 @@ class BalanceSets {
                ]
        ];
 
+       public static $extraLinefeedSet = [
+               self::HTML_NAMESPACE => [
+                       'pre' => true, 'textarea' => true, 'listing' => true,
+               ]
+       ];
+
        public static $headingSet = [
                self::HTML_NAMESPACE => [
                        'h1' => true, 'h2' => true, 'h3' => true,
@@ -185,7 +196,14 @@ class BalanceSets {
                ]
        ];
 
-       # OMITTED: formAssociatedSet, since we don't allow <form>
+       // See https://html.spec.whatwg.org/multipage/forms.html#form-associated-element
+       public static $formAssociatedSet = [
+               self::HTML_NAMESPACE => [
+                       'button' => true, 'fieldset' => true, 'input' => true,
+                       'keygen' => true, 'object' => true, 'output' => true,
+                       'select' => true, 'textarea' => true, 'img' => true
+               ]
+       ];
 
        public static $inScopeSet = [
                self::HTML_NAMESPACE => [
@@ -228,6 +246,12 @@ class BalanceSets {
                ]
        ];
 
+       public static $inInvertedSelectScopeSet = [
+               self::HTML_NAMESPACE => [
+                       'option' => true, 'optgroup' => true
+               ]
+       ];
+
        public static $mathmlTextIntegrationPointSet = [
                self::MATHML_NAMESPACE => [
                        'mi' => true, 'mo' => true, 'mn' => true, 'ms' => true,
@@ -495,11 +519,21 @@ class BalanceElement {
                }
                if ( !$this->isA( BalanceSets::$emptyElementSet ) ) {
                        $out = "<{$this->localName}{$encAttribs}>";
+                       $len = strlen( $out );
                        // flatten children
                        foreach ( $this->children as $elt ) {
                                $out .= "{$elt}";
                        }
                        $out .= "</{$this->localName}>";
+                       if (
+                               $this->isA( BalanceSets::$extraLinefeedSet ) &&
+                               $out[$len] === "\n"
+                       ) {
+                               // Double the linefeed after pre/listing/textarea
+                               // according to the HTML5 fragment serialization algorithm.
+                               $out = substr( $out, 0, $len + 1 ) .
+                                       substr( $out, $len );
+                       }
                } else {
                        $out = "<{$this->localName}{$encAttribs} />";
                        Assert::invariant(
@@ -663,19 +697,29 @@ class BalanceStack implements IteratorAggregate {
                return $out;
        }
 
+       /**
+        * Insert a comment at the appropriate place for inserting a node.
+        * @param string $value Content of the comment.
+        * @see https://html.spec.whatwg.org/multipage/syntax.html#insert-a-comment
+        */
+       public function insertComment( $value ) {
+               // Just another type of text node, except for tidy p-wrapping.
+               return $this->insertText( '<!--' . $value . '-->', true );
+       }
+
        /**
         * Insert text at the appropriate place for inserting a node.
         * @param string $value
         * @see https://html.spec.whatwg.org/multipage/syntax.html#appropriate-place-for-inserting-a-node
         */
-       public function insertText( $value ) {
+       public function insertText( $value, $isComment = false ) {
                if (
                        $this->fosterParentMode &&
                        $this->currentNode->isA( BalanceSets::$tableSectionRowSet )
                ) {
                        $this->fosterParent( $value );
                } elseif (
-                       $this->tidyCompat &&
+                       $this->tidyCompat && !$isComment &&
                        $this->currentNode->isA( BalanceSets::$tidyPWrapSet )
                ) {
                        $this->insertHTMLELement( 'mw:p-wrap', [] );
@@ -784,6 +828,26 @@ class BalanceStack implements IteratorAggregate {
                return $this->inSpecificScope( $tag, BalanceSets::$inTableScopeSet );
        }
 
+       /**
+        * Determine if the stack has $tag in select scope.
+        * @param BalanceElement|array|string $tag
+        * @return bool
+        * @see https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-select-scope
+        */
+       public function inSelectScope( $tag ) {
+               // Can't use inSpecificScope to implement this, since it involves
+               // *inverting* a set of tags.  Implement manually.
+               foreach ( $this as $elt ) {
+                       if ( $elt->isA( $tag ) ) {
+                               return true;
+                       }
+                       if ( !$elt->isA( BalanceSets::$inInvertedSelectScopeSet ) ) {
+                               return false;
+                       }
+               }
+               return false;
+       }
+
        /**
         * Determine if the stack has $tag in a specific scope, $set.
         * @param BalanceElement|array|string $tag
@@ -1613,9 +1677,11 @@ class BalanceActiveFormattingElements {
 
                // Loop backward through the list until we find a marker or an
                // open element
+               $foundit = false;
                while ( $entry->prevAFE ) {
                        $entry = $entry->prevAFE;
                        if ( $entry instanceof BalanceMarker || $stack->indexOf( $entry ) >= 0 ) {
+                               $foundit = true;
                                break;
                        }
                }
@@ -1624,7 +1690,7 @@ class BalanceActiveFormattingElements {
                // the first element if we didn't find a marker or open element),
                // recreating formatting elements and pushing them back onto the list
                // of open elements.
-               if ( $entry->prevAFE ) {
+               if ( $foundit ) {
                        $entry = $entry->nextAFE;
                }
                do {
@@ -1690,18 +1756,19 @@ class BalanceActiveFormattingElements {
  * - The document is never in "quirks mode".
  * - All occurrences of < and > have been entity escaped, so we
  *   can parse tags by simply splitting on those two characters.
+ *   (This also simplifies the handling of < inside <textarea>.)
+ *   The character < must not appear inside comments.
  *   Similarly, all attributes have been "cleaned" and are double-quoted
  *   and escaped.
- * - All comments and null characters are assumed to have been removed.
- * - We don't alter linefeeds after <pre>/<listing>.
+ * - All null characters are assumed to have been removed.
  * - The following elements are disallowed: <html>, <head>, <body>, <frameset>,
- *   <form>, <frame>, <plaintext>, <isindex>, <textarea>, <xmp>, <iframe>,
- *   <noembed>, <noscript>, <select>, <script>, <title>.  As a result,
+ *   <frame>, <plaintext>, <isindex>, <xmp>, <iframe>,
+ *   <noembed>, <noscript>, <script>, <title>.  As a result,
  *   further simplifications can be made:
  *   - `frameset-ok` is not tracked.
- *   - `form element pointer` is not tracked.
  *   - `head element pointer` is not tracked (but presumed non-null)
- *   - Tokenizer has only a single mode.
+ *   - Tokenizer has only a single mode. (<textarea> wants RCDATA and
+ *     <style>/<noframes> want RAWTEXT modes which we only loosely emulate.)
  *
  *   We generally mark places where we omit cases from the spec due to
  *   disallowed elements with a comment: `# OMITTED: <element-name>`.
@@ -1723,11 +1790,47 @@ class Balancer {
        private $stack;
        private $strict;
        private $tidyCompat;
+       private $allowComments;
 
-       private $textIntegrationMode = false;
+       private $textIntegrationMode;
        private $pendingTableText;
        private $originalInsertionMode;
        private $fragmentContext;
+       private $formElementPointer;
+       private $ignoreLinefeed;
+       private $inRCDATA;
+       private $inRAWTEXT;
+
+       /**
+        * Valid HTML5 comments.
+        * Regex borrowed from Tim Starling's "remex-html" project.
+        */
+       const VALID_COMMENT_REGEX = "~ !--
+               (                             # 1. Comment match detector
+                       > | -> | # Invalid short close
+                       (                         # 2. Comment contents
+                               (?:
+                                       (?! --> )
+                                       (?! --!> )
+                                       (?! --! \z )
+                                       (?! -- \z )
+                                       (?! - \z )
+                                       .
+                               )*+
+                       )
+                       (                         # 3. Comment close
+                               --> |   # Normal close
+                               --!> |  # Comment end bang
+                               (                     # 4. Indicate matches requiring EOF
+                                       --! |   # EOF in comment end bang state
+                                       -- |    # EOF in comment end state
+                                       -  |    # EOF in comment end dash state
+                                               # EOF in comment state
+                               )
+                       )
+               )
+               ([^<]*) \z                    # 5. Non-tag text after the comment
+               ~xs";
 
        /**
         * Create a new Balancer.
@@ -1747,16 +1850,23 @@ class Balancer {
         *         program: <p>-wrapping is done to the children of
         *         <body> and <blockquote> elements, and empty elements
         *         are removed.
+        *     'allowComments': boolean, defaults to true.
+        *         When true, allows HTML comments in the input.
+        *         The Sanitizer generally strips all comments, so if you
+        *         are running on sanitized output you can set this to
+        *         false to get a bit more performance.
         */
        public function __construct( array $config = [] ) {
                $config = $config + [
                        'strict' => false,
                        'allowedHtmlElements' => null,
                        'tidyCompat' => false,
+                       'allowComments' => true,
                ];
                $this->allowedHtmlElements = $config['allowedHtmlElements'];
                $this->strict = $config['strict'];
                $this->tidyCompat = $config['tidyCompat'];
+               $this->allowComments = $config['allowComments'];
                if ( $this->allowedHtmlElements !== null ) {
                        # Sanity check!
                        $bad = array_uintersect_assoc(
@@ -1800,11 +1910,23 @@ class Balancer {
                $this->processingCallback = $processingCallback;
                $this->processingArgs = $processingArgs;
 
+               $this->textIntegrationMode =
+                       $this->ignoreLinefeed =
+                       $this->inRCDATA =
+                       $this->inRAWTEXT = false;
+
                # The stack is constructed with an <html> element already on it.
                # Set this up as a fragment parsed with <body> as the context.
                $this->fragmentContext =
                        new BalanceElement( BalanceSets::HTML_NAMESPACE, 'body', [] );
                $this->resetInsertionMode();
+               $this->formElementPointer = null;
+               for ( $e = $this->fragmentContext; $e != null; $e = $e->parent ) {
+                       if ( $e->isHtmlNamed( 'form' ) ) {
+                               $this->formElementPointer = $e;
+                               break;
+                       }
+               }
 
                // First element is text not tag
                $x = $this->bitsIterator->current();
@@ -1821,6 +1943,7 @@ class Balancer {
                $this->afe = null;
                $this->stack = null;
                $this->fragmentContext = null;
+               $this->formElementPointer = null;
                return $result;
        }
 
@@ -1844,6 +1967,19 @@ class Balancer {
                        # Don't actually inject the empty string as a text token.
                        return true;
                }
+               // Support pre/listing/textarea by suppressing initial linefeed
+               if ( $this->ignoreLinefeed ) {
+                       $this->ignoreLinefeed = false;
+                       if ( $token === 'text' ) {
+                               if ( $value[0] === "\n" ) {
+                                       if ( $value === "\n" ) {
+                                               # Nothing would be left, don't inject the empty string.
+                                               return true;
+                                       }
+                                       $value = substr( $value, 1 );
+                               }
+                       }
+               }
                // Some hoops we have to jump through
                $adjusted = $this->stack->adjustedCurrentNode( $this->fragmentContext );
 
@@ -1987,12 +2123,27 @@ class Balancer {
 
        /**
         * Grab the next "token" from $bitsIterator.  This is either a open/close
-        * tag or text, depending on whether the Sanitizer approves.
+        * tag or text or a comment, depending on whether the Sanitizer approves.
         */
        private function advance() {
                $x = $this->bitsIterator->current();
                $this->bitsIterator->next();
                $regs = [];
+               # Handle comments.  These won't be generated by mediawiki (they
+               # are stripped in the Sanitizer) but may be generated by extensions.
+               if (
+                       $this->allowComments &&
+                       !( $this->inRCDATA || $this->inRAWTEXT ) &&
+                       preg_match( Balancer::VALID_COMMENT_REGEX, $x, $regs, PREG_OFFSET_CAPTURE ) &&
+                       /* verify EOF condition where necessary */
+                       ( $regs[4][1] < 0 || !$this->bitsIterator->valid() )
+               ) {
+                       $contents = $regs[2][0];
+                       $rest = $regs[5][0];
+                       $this->insertToken( 'comment', $contents );
+                       $this->insertToken( 'text', str_replace( '>', '&gt;', $rest ) );
+                       return;
+               }
                # $slash: Does the current element start with a '/'?
                # $t: Current element name
                # $attribStr: String between element name and >
@@ -2017,6 +2168,22 @@ class Balancer {
                        $slash = $t = $attribStr = $brace = $rest = null;
                }
                $goodtag = $t;
+               if ( $this->inRCDATA ) {
+                       if ( $slash && $t === $this->inRCDATA ) {
+                               $this->inRCDATA = false;
+                       } else {
+                               // No tags allowed; this emulates the "rcdata" tokenizer mode.
+                               $goodtag = false;
+                       }
+               }
+               if ( $this->inRAWTEXT ) {
+                       if ( $slash && $t === $this->inRAWTEXT ) {
+                               $this->inRAWTEXT = false;
+                       } else {
+                               // No tags allowed, no entity-escaping done.
+                               $goodtag = false;
+                       }
+               }
                $sanitize = $this->allowedHtmlElements !== null;
                if ( $sanitize ) {
                        $goodtag = $t && isset( $this->allowedHtmlElements[$t] );
@@ -2043,6 +2210,8 @@ class Balancer {
                if ( $goodtag ) {
                        $rest = str_replace( '>', '&gt;', $rest );
                        $this->insertToken( 'text', str_replace( '>', '&gt;', $rest ) );
+               } elseif ( $this->inRAWTEXT ) {
+                       $this->insertToken( 'text', "<$x" );
                } else {
                        # bad tag; serialize entire thing as text.
                        $this->insertToken( 'text', '&lt;' . str_replace( '>', '&gt;', $x ) );
@@ -2074,8 +2243,6 @@ class Balancer {
                        }
                        if ( $node->isHtml() ) {
                                switch ( $node->localName ) {
-                               # OMITTED: <select>
-                               /*
                                case 'select':
                                        $stacklen = $this->stack->length();
                                        for ( $j = $i + 1; $j < $stacklen-1; $j++ ) {
@@ -2090,7 +2257,6 @@ class Balancer {
                                        }
                                        $this->switchMode( 'inSelectMode' );
                                        return;
-                               */
                                case 'tr':
                                        $this->switchMode( 'inRowMode' );
                                        return;
@@ -2151,7 +2317,7 @@ class Balancer {
 
        private function parseRawText( $value, $attribs = null ) {
                $this->stack->insertHTMLElement( $value, $attribs );
-               // XXX switch tokenizer to rawtext state?
+               $this->inRAWTEXT = $value;
                $this->originalInsertionMode = $this->switchMode( 'inTextMode' );
                return true;
        }
@@ -2232,6 +2398,9 @@ class Balancer {
                                // ignore any other end tag
                                return true;
                        }
+               } elseif ( $token === 'comment' ) {
+                       $this->stack->insertComment( $value );
+                       return true;
                }
 
                // If not handled above
@@ -2319,12 +2488,25 @@ class Balancer {
                                        $this->inBodyMode( 'endtag', 'p' );
                                }
                                $this->stack->insertHTMLElement( $value, $attribs );
-                               # As described in "simplifications" above:
-                               # 1. We don't touch the next token, even if it's a linefeed.
-                               # 2. OMITTED: frameset_ok
+                               $this->ignoreLinefeed = true;
+                               # OMITTED: frameset_ok
                                return true;
 
-                       # OMITTED: <form>
+                       case 'form':
+                               if (
+                                       $this->formElementPointer &&
+                                       $this->stack->indexOf( 'template' ) < 0
+                               ) {
+                                       return true; // in a form, not in a template.
+                               }
+                               if ( $this->stack->inButtonScope( "p" ) ) {
+                                       $this->inBodyMode( 'endtag', 'p' );
+                               }
+                               $elt = $this->stack->insertHTMLElement( $value, $attribs );
+                               if ( $this->stack->indexOf( 'template' ) < 0 ) {
+                                       $this->formElementPointer = $elt;
+                               }
+                               return true;
 
                        case 'li':
                                # OMITTED: frameset_ok
@@ -2481,14 +2663,19 @@ class Balancer {
                                return $this->inBodyMode( $token, 'img', $attribs, $selfclose );
 
                        # OMITTED: <isindex>
-                       # OMITTED: <textarea>
+
+                       case 'textarea':
+                               $this->stack->insertHTMLElement( $value, $attribs );
+                               $this->ignoreLinefeed = true;
+                               $this->inRCDATA = $value; // emulate rcdata tokenizer mode
+                               # OMITTED: frameset_ok
+                               return true;
+
                        # OMITTED: <xmp>
                        # OMITTED: <iframe>
                        # OMITTED: <noembed>
                        # OMITTED: <noscript>
 
-                       # OMITTED: <select>
-                       /*
                        case 'select':
                                $this->afe->reconstruct( $this->stack );
                                $this->stack->insertHTMLElement( $value, $attribs );
@@ -2504,7 +2691,6 @@ class Balancer {
                                        $this->switchMode( 'inSelectMode' );
                                        return true;
                                }
-                       */
 
                        case 'optgroup':
                        case 'option':
@@ -2621,7 +2807,26 @@ class Balancer {
                                $this->stack->popTag( $value );
                                return true;
 
-                       # OMITTED: <form>
+                       case 'form':
+                               if ( $this->stack->indexOf( 'template' ) < 0 ) {
+                                       $openform = $this->formElementPointer;
+                                       $this->formElementPointer = null;
+                                       if ( !$openform || !$this->stack->inScope( $openform ) ) {
+                                               return true;
+                                       }
+                                       $this->stack->generateImpliedEndTags();
+                                       // Don't flatten yet if we're removing a <form> element
+                                       // out-of-order. (eg. `<form><div></form>`)
+                                       $flatten = ( $this->stack->currentNode === $openform );
+                                       $this->stack->removeElement( $openform, $flatten );
+                               } else {
+                                       if ( !$this->stack->inScope( 'form' ) ) {
+                                               return true;
+                                       }
+                                       $this->stack->generateImpliedEndTags();
+                                       $this->stack->popTag( 'form' );
+                               }
+                               return true;
 
                        case 'p':
                                if ( !$this->stack->inButtonScope( 'p' ) ) {
@@ -2656,7 +2861,7 @@ class Balancer {
                        case 'h5':
                        case 'h6':
                                if ( !$this->stack->inScope( BalanceSets::$headingSet ) ) {
-                                       return;
+                                       return true; # ignore
                                }
                                $this->stack->generateImpliedEndTags();
                                $this->stack->popTag( BalanceSets::$headingSet );
@@ -2712,6 +2917,9 @@ class Balancer {
                                }
                        }
                        return true;
+               } elseif ( $token === 'comment' ) {
+                       $this->stack->insertComment( $value );
+                       return true;
                } else {
                        Assert::invariant( false, "Bad token type: $token" );
                }
@@ -2777,7 +2985,17 @@ class Balancer {
                                $this->stack->pop();
                                return true;
 
-                       # OMITTED: <form>
+                       case 'form':
+                               if (
+                                       $this->formElementPointer ||
+                                       $this->stack->indexOf( 'template' ) >= 0
+                               ) {
+                                       return true; // ignore this token
+                               }
+                               $this->formElementPointer =
+                                       $this->stack->insertHTMLElement( $value, $attribs );
+                               $this->stack->popTag( $this->formElementPointer );
+                               return true;
                        }
                        // Fall through for "anything else" clause.
                } elseif ( $token === 'endtag' ) {
@@ -2805,6 +3023,9 @@ class Balancer {
                                return $this->inHeadMode( $token, $value, $attribs, $selfclose );
                        }
                        // Fall through for "anything else" clause.
+               } elseif ( $token === 'comment' ) {
+                       $this->stack->insertComment( $value );
+                       return true;
                }
                // This is the "anything else" case:
                $this->stack->fosterParentMode = true;
@@ -2932,6 +3153,9 @@ class Balancer {
                        // Fall through for "anything else".
                } elseif ( $token === 'eof' ) {
                        return $this->inBodyMode( $token, $value, $attribs, $selfclose );
+               } elseif ( $token === 'comment' ) {
+                       $this->stack->insertComment( $value );
+                       return true;
                }
 
                // Anything else
@@ -3141,19 +3365,109 @@ class Balancer {
                return $this->inBodyMode( $token, $value, $attribs, $selfclose );
        }
 
-       # OMITTED: <select>
-       /*
        private function inSelectMode( $token, $value, $attribs = null, $selfclose = false ) {
-               Assert::invariant( false, 'Unimplemented' );
+               if ( $token === 'text' ) {
+                       $this->stack->insertText( $value );
+                       return true;
+               } elseif ( $token === 'eof' ) {
+                       return $this->inBodyMode( $token, $value, $attribs, $selfclose );
+               } elseif ( $token === 'tag' ) {
+                       switch ( $value ) {
+                       # OMITTED: <html>
+                       case 'option':
+                               if ( $this->stack->currentNode->isHtmlNamed( 'option' ) ) {
+                                       $this->stack->pop();
+                               }
+                               $this->stack->insertHTMLElement( $value, $attribs );
+                               return true;
+                       case 'optgroup':
+                               if ( $this->stack->currentNode->isHtmlNamed( 'option' ) ) {
+                                       $this->stack->pop();
+                               }
+                               if ( $this->stack->currentNode->isHtmlNamed( 'optgroup' ) ) {
+                                       $this->stack->pop();
+                               }
+                               $this->stack->insertHTMLElement( $value, $attribs );
+                               return true;
+                       case 'select':
+                               $this->inSelectMode( 'endtag', $value ); // treat it like endtag
+                               return true;
+                       case 'input':
+                       case 'keygen':
+                       case 'textarea':
+                               if ( !$this->stack->inSelectScope( 'select' ) ) {
+                                       return true; // ignore token (fragment case)
+                               }
+                               $this->inSelectMode( 'endtag', 'select' );
+                               return $this->insertToken( $token, $value, $attribs, $selfclose );
+                       case 'script':
+                       case 'template':
+                               return $this->inHeadMode( $token, $value, $attribs, $selfclose );
+                       }
+               } elseif ( $token === 'endtag' ) {
+                       switch ( $value ) {
+                       case 'optgroup':
+                               if (
+                                       $this->stack->currentNode->isHtmlNamed( 'option' ) &&
+                                       $this->stack->length() >= 2 &&
+                                       $this->stack->node( $this->stack->length() - 2 )->isHtmlNamed( 'optgroup' )
+                               ) {
+                                       $this->stack->pop();
+                               }
+                               if ( $this->stack->currentNode->isHtmlNamed( 'optgroup' ) ) {
+                                       $this->stack->pop();
+                               }
+                               return true;
+                       case 'option':
+                               if ( $this->stack->currentNode->isHtmlNamed( 'option' ) ) {
+                                       $this->stack->pop();
+                               }
+                               return true;
+                       case 'select':
+                               if ( !$this->stack->inSelectScope( $value ) ) {
+                                       return true; // fragment case
+                               }
+                               $this->stack->popTag( $value );
+                               $this->resetInsertionMode();
+                               return true;
+                       case 'template':
+                               return $this->inHeadMode( $token, $value, $attribs, $selfclose );
+                       }
+               } elseif ( $token === 'comment' ) {
+                       $this->stack->insertComment( $value );
+                       return true;
+               }
+               // anything else: just ignore the token
+               return true;
        }
 
        private function inSelectInTableMode( $token, $value, $attribs = null, $selfclose = false ) {
-               Assert::invariant( false, 'Unimplemented' );
+               switch ( $value ) {
+               case 'caption':
+               case 'table':
+               case 'tbody':
+               case 'tfoot':
+               case 'thead':
+               case 'tr':
+               case 'td':
+               case 'th':
+                       if ( $token === 'tag' ) {
+                               $this->inSelectInTableMode( 'endtag', 'select' );
+                               return $this->insertToken( $token, $value, $attribs, $selfclose );
+                       } elseif ( $token === 'endtag' ) {
+                               if ( $this->stack->inTableScope( $value ) ) {
+                                       $this->inSelectInTableMode( 'endtag', 'select' );
+                                       return $this->insertToken( $token, $value, $attribs, $selfclose );
+                               }
+                               return true;
+                       }
+               }
+               // anything else
+               return $this->inSelectMode( $token, $value, $attribs, $selfclose );
        }
-       */
 
        private function inTemplateMode( $token, $value, $attribs = null, $selfclose = false ) {
-               if ( $token === 'text' ) {
+               if ( $token === 'text' || $token === 'comment' ) {
                        return $this->inBodyMode( $token, $value, $attribs, $selfclose );
                } elseif ( $token === 'eof' ) {
                        if ( $this->stack->indexOf( 'template' ) < 0 ) {
index ffd7a17..d6cfcca 100644 (file)
        "changecontentmodel-emptymodels-text": "Зьмест на [[:$1]] ня можа быць ператвораны ні ў які тып.",
        "log-name-contentmodel": "Журнал зьменаў мадэляў зьместу",
        "log-description-contentmodel": "Падзеі, зьвязаныя з мадэлямі зьместу старонак",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|стварыў|стварыла}} старонку $3 з дапамогай нестандартнай мадэлі зьместу «$5»",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|зьмяніў|зьмяніла}} мадэль зьместу старонкі $3 з «$4» на «$5»",
        "logentry-contentmodel-change-revertlink": "адкаціць",
        "logentry-contentmodel-change-revert": "адкат",
        "block-log-flags-hiddenname": "імя ўдзельніка схаванае",
        "range_block_disabled": "Адміністратарам забаронена блякаваць дыяпазоны.",
        "ipb_expiry_invalid": "Няслушны тэрмін блякаваньня.",
+       "ipb_expiry_old": "Час сканчэньня ўжо мінуў.",
        "ipb_expiry_temp": "Блякаваньні са схаваньнем імя ўдзельніка павінны быць бестэрміновымі.",
        "ipb_hide_invalid": "Немагчыма схаваць гэты рахунак; зь яго зроблена больш чым {{PLURAL:$1|$1 рэдагаваньне|$1 рэдагаваньні|$1 рэдагаваньняў}}.",
        "ipb_already_blocked": "«$1» ужо заблякаваны",
        "lockdbsuccesstext": "База зьвестак была заблякаваная.<br />\nНе забудзьцеся [[Special:UnlockDB|зьняць блякаваньне]] пасьля сканчэньня абслугоўваньня.",
        "unlockdbsuccesstext": "База зьвестак была разблякаваная.",
        "lockfilenotwritable": "Немагчыма запісаць у файл блякаваньняў базы зьвестак.\nБлякаваньне ці разблякаваньне базы зьвестак патрабуе, каб вэб-сэрвэр меў дазвол на запіс у гэты файл.",
+       "databaselocked": "База зьвестак ужо заблякаваная.",
        "databasenotlocked": "База зьвестак не заблякаваная.",
        "lockedbyandtime": "($1 $2 у $3)",
        "move-page": "Перанесьці $1",
index d002c60..5214d3f 100644 (file)
@@ -31,7 +31,8 @@
                        "Urbanecm",
                        "LordMsz",
                        "Matma Rex",
-                       "Dvorapa"
+                       "Dvorapa",
+                       "Walter Klosse"
                ]
        },
        "tog-underline": "Podtrhávat odkazy:",
        "content-model-css": "CSS",
        "content-json-empty-object": "Prázdný objekt",
        "content-json-empty-array": "Prázdné pole",
-       "deprecated-self-close-category": "Stránky s neplatnými sebeuzavírajícími HTML značkami",
-       "deprecated-self-close-category-desc": "Stránka obsahuje neplatné sebeuzavírající HTML značky, například <code>&lt;b/></code> nebo <code>&lt;span/></code>. Jejich chování se v zájmu konzistence se specifikací HTML5 brzy změní, proto je jejich použití ve wikitextu zastaralé.",
+       "deprecated-self-close-category": "Stránky s neplatnými sebeuzavírajícími se HTML značkami",
+       "deprecated-self-close-category-desc": "Stránka obsahuje neplatné sebeuzavírající se HTML značky, například <code>&lt;b/></code> nebo <code>&lt;span/></code>. Jejich chování se v zájmu konzistence se specifikací HTML5 brzy změní, proto je jejich použití ve wikitextu zastaralé.",
        "duplicate-args-warning": "<strong>Upozornění:</strong> Stránka [[:$1]] volá [[:$2]] s více než jednou hodnotou parametru „$3“. Použije se jen poslední uvedená hodnota.",
        "duplicate-args-category": "Stránky s duplicitními argumenty ve voláních šablon",
        "duplicate-args-category-desc": "Stránka obsahuje volání šablony, které používá duplicitní argumenty, např. <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> nebo <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "tooltip-namespace_association": "Zaškrtnutím tohoto políčka zahrnete i diskusní či obsahový jmenný prostor příslušný k vybranému jmennému prostoru",
        "blanknamespace": "(Hlavní)",
        "contributions": "Příspěvky {{GENDER:$1|uživatele|uživatelky}}",
-       "contributions-title": "Příspěvky {{GENDER:$1|uživatele|uživatelky}} $1",
+       "contributions-title": "Příspěvky {{GENDER:$1|uživatele|uživatelky|uživatele/uživatelky}} $1",
        "mycontris": "Příspěvky",
        "anoncontribs": "Příspěvky",
        "contribsub2": "{{GENDER:$3|uživatele|uživatelky}} $1 ($2)",
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 c47c118..4d7507b 100644 (file)
        "userpage-userdoesnotexist": "Le compte utilisateur « <nowiki>$1</nowiki> » n’est pas enregistré. Veuillez vérifier que vous voulez créer cette page.",
        "userpage-userdoesnotexist-view": "Le compte utilisateur « $1 » n'est pas enregistré.",
        "blocked-notice-logextract": "Cet utilisateur est actuellement bloqué.\nLa dernière entrée du journal des blocages est indiquée ci-dessous à titre d’information :",
-       "clearyourcache": "<strong>Note :</strong> après avoir enregistré vos modifications, il se peut que vous deviez forcer le rechargement complet du cache de votre navigateur pour voir les changements.\n* <strong>Firefox / Safari :</strong> maintenez la touche <em>Maj</em> (<em>Shift</em>) en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> ou <em>Ctrl-R</em> (<em>⌘-R</em> sur un Mac) ;\n* <strong>Google Chrome :</strong> appuyez sur <em>Ctrl-Maj-R</em> (<em>⌘-Shift-R</em> sur un Mac) ;\n* <strong>Internet Explorer :</strong> maintenez la touche <em>Ctrl</em> en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> ;\n* <strong>Opera :</strong> allez dans <em>Menu → Settings</em> (<em>Opera → Préférences</em> sur un Mac) et ensuite à <em>Confidentialité & sécurité → Effacer les données d’exploration → Images et fichiers en cache</em>.",
+       "clearyourcache": "<strong>Note :</strong> après avoir enregistré vos modifications, il se peut que vous deviez forcer le rechargement complet du cache de votre navigateur pour voir les changements.\n* <strong>Firefox / Safari :</strong> maintenez la touche <em>Maj</em> (<em>Shift</em>) en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> ou <em>Ctrl-R</em> (<em>⌘-R</em> sur un Mac) \n* <strong>Google Chrome :</strong> appuyez sur <em>Ctrl-Maj-R</em> (<em>⌘-Shift-R</em> sur un Mac) \n* <strong>Internet Explorer :</strong> maintenez la touche <em>Ctrl</em> en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> \n* <strong>Opera :</strong> allez dans <em>Menu → Settings</em> (<em>Opera → Préférences</em> sur un Mac) et ensuite à <em>Confidentialité & sécurité → Effacer les données d’exploration → Images et fichiers en cache</em>.",
        "usercssyoucanpreview": "<strong>Astuce :</strong> utilisez le bouton « {{int:showpreview}} » pour tester votre nouvelle feuille CSS avant de l’enregistrer.",
        "userjsyoucanpreview": "<strong>Astuce :</strong> utilisez le bouton « {{int:showpreview}} » pour tester votre nouvelle feuille JavaScript avant de l’enregistrer.",
        "usercsspreview": "<strong>Rappelez-vous que vous ne faites que prévisualiser votre propre feuille CSS. \nElle n’a pas encore été enregistrée !</strong>",
        "editingold": "<strong>Attention : vous êtes en train de modifier une ancienne version de cette page.</strong>\nSi vous la publiez, toutes les modifications effectuées depuis cette version seront perdues.",
        "yourdiff": "Différences",
        "copyrightwarning": "Toutes les contributions à {{SITENAME}} sont considérées comme publiées sous les termes de la $2 (voir $1 pour plus de détails). \nSi vous ne désirez pas que vos écrits soient modifiés et distribués à volonté, merci de ne pas les soumettre ici.<br /> \nVous nous promettez aussi que vous avez écrit ceci vous-même, ou que vous l’avez copié d’une source provenant du domaine public ou d’une ressource libre similaire. \n<strong>N’UTILISEZ PAS DE TRAVAUX SOUS DROIT D’AUTEUR SANS AUTORISATION EXPRESSE !</strong>",
-       "copyrightwarning2": "Toutes les contributions à {{SITENAME}} peuvent être modifiées ou supprimées par d’autres utilisateurs. Si vous ne désirez pas que vos écrits soient modifiés et distribués à volonté, merci de ne pas les soumettre ici.<br \n/>Vous nous promettez aussi que vous avez écrit ceci vous-même, ou que vous l’avez copié d’une source provenant du domaine public, ou d’une ressource libre. (voir $1 pour plus de détails).\n<strong>N’UTILISEZ PAS DE TRAVAUX SOUS DROIT D’AUTEUR SANS AUTORISATION EXPRESSE !<strong>",
+       "copyrightwarning2": "Notez bien que toutes les contributions à {{SITENAME}} peuvent être modifiées, transformées ou supprimées par d’autres utilisateurs. \nSi vous ne désirez pas que vos écrits soient modifiés contre votre gré, merci de ne pas les soumettre ici.<br /> \nVous nous promettez aussi que vous avez écrit ceci vous-même, ou que vous l’avez copié d’une source provenant du domaine public, ou d’une ressource libre. (voir $1 pour plus de détails).\n<strong>N’UTILISEZ PAS DE TRAVAUX SOUS DROIT D’AUTEUR SANS AUTORISATION EXPRESSE !</strong>",
        "editpage-cannot-use-custom-model": "Le modèle de contenu de cette page ne peut pas être modifié.",
        "longpageerror": "<strong>Erreur : Le texte que vous avez soumis fait {{PLURAL:$1|un Kio|$1 Kio}}, ce qui dépasse la limite fixée à {{PLURAL:$2|un Kio|$2 Kio}}.</strong>\nIl ne peut pas être sauvegardé.",
        "readonlywarning": "<strong>AVERTISSEMENT : la base de données a été verrouillée pour des opérations de maintenance. Vous ne pouvez donc pas publier vos modifications pour l’instant.</strong>\nVous pouvez copier et coller votre texte dans un fichier texte et l’enregistrer pour plus tard.\n\nL’administrateur système ayant verrouillé la base de données a donné l’explication suivante : $1",
index c7d7099..f1cf530 100644 (file)
@@ -28,7 +28,7 @@
        "tog-showtoolbar": "Chán-sṳ phiên-siá kûng-khí-làn",
        "tog-editondblclick": "Sûng-khim phiên-siá ya̍p-mien",
        "tog-editsectiononrightclick": "Yún-hí yu-khim phiêu-thì phiên-siá thon-lo̍k",
-       "tog-watchcreations": "Kâ ngài kien-li̍p ke ya̍p-mien lâu sông-chhòn ke vùn-khien kâ-ngi̍p ngài-ke kâm-sṳ lie̍t-péu",
+       "tog-watchcreations": "Kâ ngài kien-li̍p ke ya̍p-mien lâu sông-chhòn ke tóng-on kâ-ngi̍p ngài-ke kâm-sṳ lie̍t-péu",
        "tog-watchdefault": "Chiông ngài phiên-siá ke ya̍p-mien lâu tóng-on kâ-ngi̍p ngài-ke kâm-sṳ lie̍t-péu",
        "tog-watchmoves": "Chiông ngài yì-thûng ke ya̍p-mien lâu tóng-on kâ-ngi̍p ngài-ke kâm-sṳ lie̍t-péu",
        "tog-watchdeletion": "加亻厓刪除嘅頁面撈文件入亻厓嘅監視列表",
        "edit": "Phiên-siá",
        "create": "Kien-li̍p",
        "create-local": "Sîn-chen pún-thi sot-mìn",
-       "editthispage": "Phiên-siá pún-ya̍p",
+       "editthispage": "Phiên-siá liá ya̍p",
        "create-this-page": "Kien-li̍p pún-ya̍p",
        "delete": "San-chhù",
        "deletethispage": "San-chhù pún-ya̍p",
        "talkpagelinktext": "kâu-liù",
        "specialpage": "Thi̍t-sû ya̍p-mien",
        "personaltools": "Sṳ̂-ngìn kûng-khí",
-       "articlepage": "Chhà-khon nui-yùng ya̍p-mien",
+       "articlepage": "Khon nui-yùng ya̍p",
        "talk": "Thó-lun",
        "views": "Chhà-khon-sú",
        "toolbox": "Kûng-khí-siông",
        "youhavenewmessagesmulti": "Ngì chhai $1-tú yû sîn sêu-sit",
        "editsection": "phiên-siá",
        "editold": "phiên-siá",
-       "viewsourceold": "chhà-khon ngièn-ma",
+       "viewsourceold": "Khon ngièn-sṳ́-mâ",
        "editlink": "phiên-siá",
-       "viewsourcelink": "chhà-khon ngièn-ma",
+       "viewsourcelink": "Khon ngièn-sṳ́-mâ",
        "editsectionhint": "Phiên-siá chông-chiet: $1",
        "toc": "Muk-liu̍k",
        "showtoc": "Chán-sṳ",
        "perfcached": "下列係緩存數據,因此可能毋係最新嘅。最多{{PLURAL:$1|單淨有1嘅結果|$1嘅結果}}可用。",
        "perfcachedts": "下列係緩存數據,其最後更新時間係$1。單淨有{{PLURAL:$4|一嘅結果|$4嘅結果}}會畀顯示。",
        "querypage-no-updates": "當前禁止對邇頁面進行更新。\n邇位嘅數據將做毋得分立即重新整理。",
-       "viewsource": "Chhà-khon ngièn-ma",
-       "viewsource-title": "查看$1嘅源代碼",
+       "viewsource": "Khon ngièn-sṳ́-mâ",
+       "viewsource-title": "Khon $1 ke ngièn-sṳ́-mâ",
        "actionthrottled": "動作已經壓制",
        "actionthrottledtext": "基於反垃圾嘅考量,短時間內毋可以多次重複某操作,今下汝已經超過邇隻上限。\n請在數分鐘後再嘗試。",
        "protectedpagetext": "邇隻頁面已經分人保護以防止編輯或其他操作。",
        "group-bureaucrat-member": "行政員",
        "grouppage-bot": "{{ns:project}}:機器人",
        "grouppage-sysop": "{{ns:project}}:管理員",
-       "right-upload": "Sông-chhòn vùn-khien",
+       "right-upload": "Sông-chhòn tóng-on",
        "right-writeapi": "Sṳ́-yung siá-ngi̍p API",
        "newuserlogpage": "Sîn-kien yung-fu miàng-chhak",
        "newuserlogpagetext": "邇係一隻最近人創建用戶嘅新日誌",
        "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細微編寫",
        "recentchangeslinked-summary": "邇一隻特殊頁面列示''由''所分出嘅一隻頁面之鏈接到頁面嘅最近更改(或者是對於指定分類嘅成員)。\n在[[Special:Watchlist|汝嘅監視列表]]肚嘅頁面會用'''粗體'''顯示。",
        "recentchangeslinked-page": "Ya̍p-mien miàng:",
        "recentchangeslinked-to": "Chán-sṳ lièn-to só fûn-chhut ke ya̍p-mien",
-       "upload": "Sông-chhòn vùn-khien",
-       "uploadbtn": "Sông-chhòn vùn-khien",
+       "upload": "Sông-chhòn tóng-on",
+       "uploadbtn": "Sông-chhòn tóng-on",
        "reuploaddesc": "取消上載並返回上載表單",
        "uploadnologin": "還吂登入",
        "uploadnologintext": "汝必須先[[Special:UserLogin|登入]]\n正做得上傳文件。",
        "uploaderror": "上傳差錯",
        "uploadtext": "Sṳ́-yung ha-mien ke péu-tân lòi song-chhòn yung-chhai vùn-chông nui sîn-ke thù-hìn tóng-on. Yeu kiám-sṳ fe̍t-chá sêu-chhà yî-chhièn song-chhòn ke thù-phién khó-yî chin-ngi̍p [[Special:FileList|Thù-hìn chhîn-tân]], song-chhòn lâu chhù-hi chiông-chhai [[Special:Log/upload|Song-chhòn ngit-ki]] chûng ki-liu̍k. Yeu-chhai vùn-chông chûng kâ-ngi̍p thù-hiong, sṳ́-yung yî-ha hìn-sṳt ke lièn-chiap: '''<nowiki>[[{{ns:file}}:file.jpg]]</nowiki>''', '''<nowiki>[[{{ns:file}}:file.png|Thi-von vùn-sṳ]]</nowiki>''' fe̍t-he '''<nowiki>[[{{ns:media}}:file.ogg]]</nowiki>'''.",
        "uploadlogpage": "上傳日誌",
-       "uploadlogpagetext": "Yî-ha he chui-khiûn song-chhòn vùn-khien ke chúng-péu.",
+       "uploadlogpagetext": "Yî-ha he chui-khiûn sông-chhòn tóng-on ke chúng-péu.",
        "filename": "文件名",
        "filedesc": "Vùn-khien sot-mìn",
        "fileuploadsummary": "文件摘要:",
        "pager-older-n": "khiu $1-chhṳ",
        "booksources": "Mióng-lok sû-ngièn",
        "booksources-search-legend": "Chhìm-cháu mióng-lok sû-ngièn",
-       "booksources-search": "Chhìm-cháu",
+       "booksources-search": "Chhìm",
        "booksources-text": "下背係一份銷售新書或二手書嘅列表,並可能有汝尋找緊嘅書嘅進一步信息:",
        "specialloguserlabel": "Yung-fu:",
        "speciallogtitlelabel": "Phêu-thì:",
        "sp-contributions-username": "IP地址或用戶名:",
        "sp-contributions-toponly": "單淨展示最新修訂版本嘅編寫",
        "sp-contributions-submit": "搜尋",
-       "whatlinkshere": "Lièn-chiap ngi̍p ya̍p-mien",
+       "whatlinkshere": "Nâi-têu lièn to liá-têu",
        "whatlinkshere-title": "Lièn-chiap to \"$1\" ke ya̍p-mien",
        "whatlinkshere-page": "Ya̍p-mien:",
        "linkshere": "Hâ-poi ya̍p-mien lièn-chiap to <strong>[[:$1]]</strong>:",
        "tooltip-pt-mycontris": "{{GENDER:|Ngì ke}} kung-hien lie̍t-péu",
        "tooltip-pt-login": "Kien-ngi ngì tên-ngi̍p, than-he pin fî pit-sî ke",
        "tooltip-pt-logout": "Tên-chhut",
-       "tooltip-ca-talk": "Kôan-yî ya̍p-mien chang-vùn ke thó-lun",
+       "tooltip-ca-talk": "Liá ya̍p ke thó-lun",
        "tooltip-ca-edit": "Phiên-siá pún-ya̍p",
        "tooltip-ca-addsection": "Khôi-sṳ́ yit-chak sîn thon-lo̍k",
-       "tooltip-ca-viewsource": "Pún ya̍p-mien su-to pó-fu. \nNgì cho-tet chhà-khon khì ke ngièn-ma.",
-       "tooltip-ca-history": "Pún ya̍p-mien chó-siên ke siû-thin pán-pún",
+       "tooltip-ca-viewsource": "Liá-ke ya̍p-mien su-to pó-fu. \nNgì cho-tet khon khì ke ngièn-sṳ́-mâ.",
+       "tooltip-ca-history": "Liá ya̍p chó-siên ke pán-pún",
        "tooltip-ca-protect": "保護邇頁",
        "tooltip-ca-delete": "刪除邇頁",
        "tooltip-ca-undelete": "Chiông liá-ke vùn-chông fî-fu̍k to pûn chhù-hi yî-chhièn ke chhong-khóng",
        "tooltip-feed-atom": "Thin-ye̍t liá-ya̍p ke Atom ngièn",
        "tooltip-t-contributions": "Chhà-khon {{GENDER:$1|liá-chak yung-fu}} ke kung-hien lie̍t-péu",
        "tooltip-t-emailuser": "向邇隻用戶發送電子郵件",
-       "tooltip-t-upload": "Sông-chhòn vùn-khien",
+       "tooltip-t-upload": "Sông-chhòn tóng-on",
        "tooltip-t-specialpages": "Chhiòn-phu thi̍t-sû vùn-chông ke lie̍t-péu",
        "tooltip-t-print": "Pún ya̍p-mien cho-tet tá-yin ke pán-pún",
        "tooltip-t-permalink": "Liá-chak ya̍p-mien pán-pún ke ku-thin lièn-kiet",
-       "tooltip-ca-nstab-main": "Chhà-khon nui-yùng ya̍p",
+       "tooltip-ca-nstab-main": "Khon nui-yùng ya̍p",
        "tooltip-ca-nstab-user": "Chhà-khon yung-fu ya̍p-mien",
        "tooltip-ca-nstab-media": "Chhà-khon hìn-thí-chông",
        "tooltip-ca-nstab-special": "Pún ya̍p-mien he thi̍t-sû ya̍p-mien, ngì cho-m̀-tet phiên-siá pún-ya̍p",
        "newimages": "Sîn-kien thù-chhiong ke va̍k-lòng",
        "imagelisttext": "Yî-ha he on $2 phài-lie̍t ke $1-ke tóng-on lie̍t-péu.",
        "noimages": "Mò-khó kiám-sṳ thù-chhiong.",
-       "ilsubmit": "Chhìm-cháu",
+       "ilsubmit": "Chhìm",
        "bydate": "on-cheu ngit-khì",
        "sp-newimages-showfrom": "Chhiùng $1 khôi-sṳ́ hién-sṳ sîn thù-phién",
        "bad_image_list": "請按照下列格式編寫:\n\n單淨係有(以*開頭)列出嘅項目會分考慮。\n每一行嘅第一條鏈接必須係損壞文件嘅鏈接。\n然後同一行後方嘅鏈接會分看做例外,也就係講邇文件做得在哪兜頁面肚分顯示。",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|kâu-liù]])",
        "duplicate-defaultsort": "'''警告:'''默認排序關鍵字“$2”蓋過矣先前嘅默認排序關鍵字“$1”。",
        "version": "Pán-pún",
-       "fileduplicatesearch-submit": "Chhìm-cháu",
+       "fileduplicatesearch-submit": "Chhìm",
        "specialpages": "Thi̍t-sû ya̍p",
        "external_image_whitelist": " #留下撈邇行一樣嘅文字<pre>\n#在下背(//中間部份)輸入正則表達式\n#邇兜將會撈外部(已超鏈接嘅)圖片配合\n#遐兜配合上嘅會顯示成圖片,否則就單淨會顯示成鏈接\n#有#開頭嘅行會當成意見\n#大小寫並無區分\n\n#在邇行上片輸入全部正則表達式。留下撈邇行一樣嘅文字</pre>",
        "tag-filter": "[[Special:Tags|Phiêu-chhiam]] ko-lì-hi:",
        "logentry-upload-upload": "$1 {{GENDER:$2|yí-kîn sông-chhòn}} $3",
        "rightsnone": "(無)",
        "revdelete-summary": "piên-sip tsak-yêu",
-       "searchsuggest-search": "Chhìm-cháu"
+       "searchsuggest-search": "Chhìm"
 }
index e8d7ee9..b01984c 100644 (file)
        "history-show-deleted": "صرفی حذف شدہ",
        "histfirst": "قدیم ترین",
        "histlast": "تازہ ترین",
-       "historysize": "({{PLURAL:$1|1 بائٹ|$1 بائٹس}})",
+       "historysize": "({{PLURAL:$1|1 بائٹ|$1 بائٹ}})",
        "historyempty": "(خالی)",
        "history-feed-title": "تاریخچۂ نظرثانی",
        "history-feed-description": "ویکیپیڈیا ھیہ صفحو تاریخچۂ نظرثانی",
index e3218de..5359a0d 100644 (file)
        "internalerror": "내부 오류",
        "internalerror_info": "내부 오류: $1",
        "internalerror-fatal-exception": "종류 \"$1\"에서 심각한 오류",
-       "filecopyerror": "\"$1\" 파일을 \"$2\"로 복사할 수 없습니다.",
-       "filerenameerror": "\"$1\" 파일을 \"$2\"로 옮길 수 없습니다.",
+       "filecopyerror": "\"$1\" 파일을 \"$2\"(으)로 복사할 수 없습니다.",
+       "filerenameerror": "\"$1\" 파일을 \"$2\"(으)로 이름을 바꿀 수 없습니다.",
        "filedeleteerror": "\"$1\" 파일을 삭제할 수 없습니다.",
        "directorycreateerror": "\"$1\" 디렉터리를 만들 수 없습니다.",
        "directoryreadonlyerror": "\"$1\" 디렉터리는 읽기 전용입니다.",
        "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": "당신의 계정이 마이그레이션되었으며, 당신의 사용자 이름이 더 이상 이 위키에 존재하지 않습니다.",
        "botpasswords-updated-body": "사용자 \"$2\"의 \"$1\"라는 이름의 봇 비밀번호가 업데이트되었습니다.",
        "botpasswords-deleted-title": "봇 비밀번호 제거",
        "botpasswords-deleted-body": "사용자 \"$2\"의 \"$1\"라는 이름의 봇 비밀번호가 삭제되었습니다.",
-       "botpasswords-newpassword": "<strong>$1</strong> 계정의 비밀번호가 <strong>$2</strong>로 변경되었습니다. <em>잊어버리지 않도록 기록해두시기 바랍니다.</em>",
+       "botpasswords-newpassword": "<strong>$1</strong>님으로 로그인하기 위한 새 비밀번호가 <strong>$2</strong>입니다. <em>잊어버리지 않도록 기록해두시기 바랍니다.</em>",
        "botpasswords-no-provider": "'BotPasswordsSessionProvider'는 이용할 수 없습니다.",
        "botpasswords-restriction-failed": "봇 비밀번호 제한으로 인해 로그인할 수 없습니다.",
        "botpasswords-invalid-name": "지정된 사용자 이름은 봇 비밀번호 구분자(\"$1\")를 포함하고 있지 않습니다.",
        "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": "당신의 계정과 연결된 이메일 주소가 있다면, 비밀번호 재설정 메일이 전해질 것입니다.",
        "loginreqlink": "로그인",
        "loginreqpagetext": "다른 문서를 보기 위해서는 $1해야 합니다.",
        "accmailtitle": "비밀번호를 보냈습니다",
-       "accmailtext": "[[User talk:$1|$1]] 사용자의 비밀번호를 임의로 만들어 $2(으)로 보냈습니다. 로그인하고 나서 <em>[[Special:ChangePassword|비밀번호를 바꿀]]</em> 수 있습니다.",
+       "accmailtext": "[[User talk:$1|$1]]의 비밀번호를 임의로 만들어 $2(으)로 보냈습니다. 로그인하고 나서 <em>[[Special:ChangePassword|비밀번호를 바꿀]]</em> 수 있습니다.",
        "newarticle": "(새 문서)",
        "newarticletext": "아직 없는 문서의 링크를 따라왔습니다.\n새 문서를 만들려면 아래 상자에 내용을 입력하면 됩니다. (자세한 내용은 [$1 도움말 문서]를 참조하세요)\n만약 잘못 찾아왔다면, 브라우저의 '''뒤로''' 버튼을 눌러 주세요.",
        "anontalkpagetext": "----\n여기는 계정을 만들지 않았거나 사용하고 있지 않은 익명 사용자를 위한 토론 문서입니다.\n익명 사용자를 구별하기 위해서는 숫자로 된 IP 주소를 사용해야만 합니다.\nIP 주소는 여러 사용자가 공유할 수 있습니다.\n자신과 관계없는 의견이 자신에게 남겨져 있어 불쾌하다고 생각하는 익명 사용자는 [[Special:CreateAccount|계정을 만들고]] [[Special:UserLogin|로그인해서]] 나중에 다른 익명 사용자에게 줄 혼란을 줄일 수 있습니다.",
        "content-not-allowed-here": "\"$1\" 내용은 [[$2]] 문서예 허용하지 않습니다",
        "editwarning-warning": "이 페이지에서 벗어나면 저장하지 않은 바뀜이 모두 사라집니다.\n로그인을 했다면, 환경 설정의 \"{{int:편집 상자}}\"에서 이 경고를 띄우지 않도록 설정할 수 있습니다.",
        "editpage-notsupportedcontentformat-title": "지원하지 않는 내용 형식",
-       "editpage-notsupportedcontentformat-text": "내용 형식 $1(은)는 $2 내용 모델에서 지원하지 않습니다.",
+       "editpage-notsupportedcontentformat-text": "내용 형식 $1은(는) $2 내용 모델에서 지원하지 않습니다.",
        "content-model-wikitext": "위키텍스트",
        "content-model-text": "일반 텍스트",
        "content-model-javascript": "자바스크립트",
        "minlength1": "파일 이름은 적어도 1글자 이상이어야 합니다.",
        "illegalfilename": "파일 이름 \"$1\"에는 문서 제목에 허용되지 않는 글자가 포함되어 있습니다.\n이름을 바꾸어 다시 시도해 주세요.",
        "filename-toolong": "파일 이름은 240바이트를 넘을 수 없습니다.",
-       "badfilename": "파일 이름이 \"$1\"로 바뀌었습니다.",
+       "badfilename": "파일 이름이 \"$1\"(으)로 바뀌었습니다.",
        "filetype-mime-mismatch": "\".$1\" 파일 확장자와 이 파일의 MIME 형식($2)이 일치하지 않습니다.",
        "filetype-badmime": "\"$1\" MIME 형식을 가진 파일은 올릴 수 없습니다.",
-       "filetype-bad-ie-mime": "이 파일을 올릴 수 없습니다. Internet Explorer가 잠재적으로 위험한 파일 형식으로 판단하여 사용이 금지된 \"$1\"로 인식할 수 있습니다.",
+       "filetype-bad-ie-mime": "이 파일을 올릴 수 없습니다. Internet Explorer가 잠재적으로 위험한 파일 형식으로 판단하여 사용이 금지된 \"$1\"(으)로 인식할 수 있습니다.",
        "filetype-unwanted-type": "'''\".$1\"''' 확장자는 권장하지 않습니다.\n권장하는 {{PLURAL:$3|파일 확장자}}는 $2입니다.",
        "filetype-banned-type": "'''\".$1\"''' {{PLURAL:$4|형식의 파일은 올릴 수 없습니다}}.\n$2 {{PLURAL:$3|형식만 사용할 수 있습니다}}.",
        "filetype-missing": "파일에 확장자(\".jpg\" 등)가 없습니다.",
        "backend-fail-notexists": "$1 파일이 존재하지 않습니다.",
        "backend-fail-hashes": "비교 해시값을 얻지 못했습니다.",
        "backend-fail-notsame": "\"$1\" 파일과 같은 이름을 가진 다른 파일이 존재합니다.",
-       "backend-fail-invalidpath": "\"$1\"(은)는 올바른 저장소 경로가 아닙니다.",
+       "backend-fail-invalidpath": "\"$1\"은(는) 올바른 저장소 경로가 아닙니다.",
        "backend-fail-delete": "\"$1\" 파일을 삭제할 수 없습니다.",
        "backend-fail-describe": "\"$1\" 파일에 대한 메타데이터를 바꿀 수 없습니다.",
        "backend-fail-alreadyexists": "\"$1\" 파일이 이미 존재합니다.",
        "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 요청 시간 초과입니다.",
        "randompage": "임의 문서로",
        "randompage-nopages": "{{PLURAL:$2|다음}} 이름공간에는 문서가 없습니다: $1",
        "randomincategory": "분류 안의 임의 문서",
-       "randomincategory-invalidcategory": "\"$1\"(은)는 올바른 분류 이름이 아닙니다.",
+       "randomincategory-invalidcategory": "\"$1\"은(는) 올바른 분류 이름이 아닙니다.",
        "randomincategory-nopages": "[[:Category:$1]]에 문서가 없습니다.",
        "randomincategory-category": "분류:",
        "randomincategory-legend": "분류 안의 임의 문서",
        "enotif_subject_created": "{{SITENAME}} $1 문서를 $2 사용자가 {{GENDER:$2|만들었습니다}}",
        "enotif_subject_moved": "{{SITENAME}} $1 문서를 $2 사용자가 {{GENDER:$2|이동하였습니다}}",
        "enotif_subject_restored": "{{SITENAME}} $1 문서를 $2 사용자가 {{GENDER:$2|되살렸습니다}}",
-       "enotif_subject_changed": "{{SITENAME}} $1 문서를 $2 사용자가 {{GENDER:$2|바꾸었습니다}}",
+       "enotif_subject_changed": "{{SITENAME}} $1 문서를 $2님이 {{GENDER:$2|바꾸었습니다}}",
        "enotif_body_intro_deleted": "{{SITENAME}} $1 문서를 $PAGEEDITDATE에 $2 사용자가 {{GENDER:$2|삭제했으며}} $3 에서 볼 수 있습니다.",
        "enotif_body_intro_created": "{{SITENAME}} $1 문서를 $PAGEEDITDATE에 $2 사용자가 {{GENDER:$2|만들었으며}} 현재 판은 $3 에서 볼 수 있습니다.",
        "enotif_body_intro_moved": "{{SITENAME}} $1 문서를 $PAGEEDITDATE에 $2 사용자가 {{GENDER:$2|이동하였으며}} 현재 판은 $3 에서 볼 수 있습니다.",
        "log-name-contentmodel": "콘텐츠 모델 변경 기록",
        "log-description-contentmodel": "페이지의 콘텐츠 모델과 관련된 행위",
        "logentry-contentmodel-new": "$1님이 비 기본값 \"$5\" 콘텐츠 모델을 사용해  $3 문서를 {{GENDER:$2|만들었습니다}}",
-       "logentry-contentmodel-change": "$1님이 $3 문서의 콘텐츠 모델을 \"$4\"에서 \"$5\"로 {{GENDER:$2|바꾸었습니다}}",
+       "logentry-contentmodel-change": "$1님이 $3 문서의 콘텐츠 모델을 \"$4\"에서 \"$5\"(으)로 {{GENDER:$2|바꾸었습니다}}",
        "logentry-contentmodel-change-revertlink": "되돌리기",
        "logentry-contentmodel-change-revert": "되돌리기",
        "protectlogpage": "문서 보호 기록",
        "protectedarticle": "사용자가 \"[[$1]]\" 문서를 보호했습니다",
        "modifiedarticleprotection": "사용자가 \"[[$1]]\" 문서의 보호 설정을 바꿨습니다",
        "unprotectedarticle": "사용자가 \"[[$1]]\" 문서를 보호 해제했습니다",
-       "movedarticleprotection": "사용자가 문서의 보호 설정을 \"[[$2]]\"에서 \"[[$1]]\"으로 변경했습니다",
+       "movedarticleprotection": "사용자가 문서의 보호 설정을 \"[[$2]]\"에서 \"[[$1]]\"(으)로 이동했습니다",
        "protect-title": "\"$1\" 보호하기",
        "protect-title-notallowed": "\"$1\" 문서의 보호 수준 보기",
        "prot_1movedto2": "[[$1]] 문서를 [[$2]] 문서로 옮김",
        "anonymous": "{{SITENAME}} 익명 {{PLURAL:$1|사용자}}",
        "siteuser": "{{SITENAME}} 사용자 $1",
        "anonuser": "{{SITENAME}} 익명 사용자 $1",
-       "lastmodifiedatby": "이 문서는 $3 사용자가 $1 $2에 마지막으로 바꾸었습니다.",
+       "lastmodifiedatby": "이 문서는 $3님이 $1 $2에 마지막으로 바꾸었습니다.",
        "othercontribs": "$1의 작업을 바탕으로 합니다.",
        "others": "기타",
        "siteusers": "{{SITENAME}} {{PLURAL:$2|{{GENDER:$1|사용자}}}} $1",
        "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": "빈 문서를 만듦",
        "logentry-suppress-delete": "$1님이 $3 문서를 {{GENDER:$2|숨겼습니다}}",
        "logentry-suppress-event": "$1님이 비공개적으로 $3의 {{PLURAL:$5|기록 $5개}}에 대해 보이기 설정을 {{GENDER:$2|바꾸었습니다}}: $4",
        "logentry-suppress-revision": "$1님이 비공개적으로 $3 문서의 {{PLURAL:$5|판 $5개}}에 대해 보이기 설정을 {{GENDER:$2|바꾸었습니다}}: $4",
-       "logentry-suppress-event-legacy": "$1님이 비공개적으로 $3의 항목에 대한 보이기 설정을 {{GENDER:$2|바꾸었습니다}}",
+       "logentry-suppress-event-legacy": "$1님이 비공개적으로 $3의 기록 항목에 대한 보이기 설정을 {{GENDER:$2|바꾸었습니다}}",
        "logentry-suppress-revision-legacy": "$1님이 비공개적으로 $3 문서의 특정 판에 대한 보이기 설정을 {{GENDER:$2|바꾸었습니다}}",
        "revdelete-content-hid": "내용 숨겨짐",
        "revdelete-summary-hid": "편집 요약 숨겨짐",
        "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-suppress-reblock": "$1님이 {{GENDER:$4|$3}}님의 차단 기간을 $5 설정으로 {{GENDER:$2|바꾸었습니다}} $6",
        "logentry-import-upload": "$1님이 $3 문서를 파일 올리기로 {{GENDER:$2|가져왔습니다}}",
        "logentry-import-upload-details": "$1님이 $3 문서 ({{PLURAL:$4|판}} $4개)를 파일 올리기로 {{GENDER:$2|가져왔습니다}}",
        "logentry-import-interwiki": "$1님이 $3 문서를 다른 위키에서 {{GENDER:$2|가져왔습니다}}",
        "logentry-protect-protect-cascade": "$1님이 $3 문서를 {{GENDER:$2|보호했습니다}} $4 [연쇄적]",
        "logentry-protect-modify": "$1님이 $3 문서의 보호 수준을 {{GENDER:$2|바꾸었습니다}} $4",
        "logentry-protect-modify-cascade": "$1님이 $3 문서의 보호 수준을 {{GENDER:$2|바꾸었습니다}} $4 [연쇄적]",
-       "logentry-rights-rights": "$1님이 $3 사용자의 권한을 $4에서 $5(으)로 {{GENDER:$2|바꾸었습니다}}",
+       "logentry-rights-rights": "$1님이 {{GENDER:$6|$3}}님의 권한을 $4에서 $5(으)로 {{GENDER:$2|바꾸었습니다}}",
        "logentry-rights-rights-legacy": "$1님이 $3 사용자의 권한을 {{GENDER:$2|바꾸었습니다}}",
        "logentry-rights-autopromote": "$1님이 권한을 자동적으로 $4에서 $5으로 {{GENDER:$2|바꾸었습니다}}",
        "logentry-upload-upload": "$1님이 $3 파일을 {{GENDER:$2|올렸습니다}}",
index 2907aa1..bdb0a1d 100644 (file)
        "rev-deleted-no-diff": "No ti peu amiâ sto diff percose un-a de verscioin a l'è stæta '''scassâ'''.\nConsurta a [{{fullurl:{{#Special:Log}}/delete|page={{PAGENAMEE}}}} lista de cançellaçioin] pe-i dettaggi.",
        "rev-suppressed-no-diff": "No ti peu fâ sto confronto tra verscioin perché un-a a l'è stæta '''scassâ'''.",
        "rev-deleted-unhide-diff": "Un-a de verscioin de sto confronto a l'è stæta '''scassâ'''.\nConsurta a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} lista de cançellaçioin] pe-i dettaggi.\nTi ti peu ancon [$1 fâ sto confronto] se necessaio.",
+       "rev-suppressed-unhide-diff": "Un-a de verscioin de sto confronto a l'è stæta '''sopressa'''.\nConsurta a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} lista de cançellaçioin] pe-i dettaggi.\nTi peu ancon [$1 fâ sto confronto] se necessaio.",
+       "rev-deleted-diff-view": "Un-a de verscioin de sto confronto a l'è stæta '''scassâ'''.\nTi ti peu amiala; consurta o [{{fullurl:{{#Special:Log}}/suppress|page={{PAGENAMEE}}}} recistro de rimoçioin] pe-i dettaggi.",
+       "rev-suppressed-diff-view": "Un-a de verscioin de sto confronto a l'è stæta '''sopressa'''.\nTi ti peu amiâla; consurta o [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} registro de soprescioin] pe-i dettaggi.",
        "rev-delundel": "fanni védde/ascondi",
        "rev-showdeleted": "mostra",
        "revisiondelete": "Scassa ò ripristina verscioin",
        "revdelete-nooldid-title": "Verscion non specificâ",
+       "revdelete-nooldid-text": "No t'hæ speçificou nisciun-a revixon in sciâ quæ eseguî questa fonçion, oppû a revixon speçificâ a no l'existe, ò che donque t'ê aproeuvo a çercâ d'asconde a revixon attuale.",
        "revdelete-no-file": "O file specificou o no l'existe.",
        "revdelete-show-file-confirm": "T'ê seguo de voei amiâ a verscion scassâ do file \"<nowiki>$1</nowiki>\" do $2 a $3?",
        "revdelete-show-file-submit": "Sci",
        "diff-multi-sameuser": "({{PLURAL:$1|Una verscion intermedia|$1 De verscioin intermedie}} de 'n mæximo utente {{PLURAL:$1|a no l'è mostrâ|no son mostræ}})",
        "diff-multi-otherusers": "({{PLURAL:$1|Una verscion intermedia|$1 De verscioin intermedie}} de {{PLURAL:$2|'n atro utente|$2 utenti}} {{PLURAL:$1|a no l'è mostrâ|no son mostræ}})",
        "diff-multi-manyusers": "({{PLURAL:$1|Una verscion intermedia|$1 verscioin intermedie}} de ciu che $2 {{PLURAL:$2|utente|utenti}} non {{PLURAL:$1|mostrâ|mostræ}})",
+       "difference-missing-revision": "{{PLURAL:$2|Una verscion|$2 verscioin}} de questa differença ($1) {{PLURAL:$2|a no l'è stæta atrovâ|no son stæte atrovæ}}.\n\nQuesto succede a l'uso se inta stoia ti sciacchi un vegio ingancio a una paggina scassâ.\n\nI dettaggi ti-i peu attrovâ into [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de scançellaçioin].",
        "searchresults": "Resultati da reçerca",
        "searchresults-title": "Rezoltati da riçerca de \"$1\"",
        "titlematches": "Corispondençe into tittolo de paggine",
        "saveusergroups": "Sarva groppi {{GENDER:$1|utente}}",
        "userrights-groupsmember": "Membro de:",
        "userrights-groupsmember-auto": "Membro impliçito de:",
+       "userrights-groups-help": "L'è poscibile modificâ i groppi de st'utente:\n* Una casella marcâ voeu dî che l'utente o l'è inte quello groppo.\n* Una casella smarcâ voeu dî che l'utente o no l'è inte quello groppo.\n* O scimbolo * o voeu dî che no ti poeu smarcâ o groppo una votta che ti l'hæ azonto (ò viçeversa).",
        "userrights-reason": "Raxon:",
        "userrights-no-interwiki": "No ti g'hæ i permissi pe modificâ i driti di utenti insce di atre wiki.",
        "userrights-nodatabase": "O database $1 o no l'esiste ò o no l'è un database locale.",
        "right-managechangetags": "Crea e attiva/disattiva i [[Special:Tags|etichette]]",
        "right-applychangetags": "Apprica di [[Special:Tags|etichette]] a-e proppie modiffiche",
        "right-changetags": "Azonze e leva de specifiche [[Special:Tags|etichette]] insce scingole verscioin o voxe de registro",
+       "right-deletechangetags": "Scassa i [[Special:Tags|etichette]] da-o database",
+       "grant-generic": "Pacchetto diritti \"$1\"",
+       "grant-group-page-interaction": "Interagisce co-e paggine",
+       "grant-group-file-interaction": "Interagisce co-i file murtimediali",
+       "grant-group-watchlist-interaction": "Interagisce con i to oservæ speçiali",
+       "grant-group-email": "Invia email",
+       "grant-group-high-volume": "Esegue açioin mascive",
+       "grant-group-customization": "Personalizzaçion e preferençe",
+       "grant-group-administration": "Esegue açioin amministrative",
+       "grant-group-other": "Attivitæ varrie",
+       "grant-blockusers": "Blocca e sblocca utenti",
+       "grant-createaccount": "Crea un'utença",
+       "grant-createeditmovepage": "Crea, modiffica e mescia e pagine",
+       "grant-delete": "Scassa pagine, revixoin, e voxe de registro",
+       "grant-editinterface": "Modifica o namespace MediaWiki e i CSS/JavaScript di utenti",
+       "grant-editmycssjs": "Modifica i CSS/JavaScript da to utença",
+       "grant-editmyoptions": "Modifica e preferençe da to utença",
+       "grant-editmywatchlist": "Modiffica a to lista di öservæ",
+       "grant-editpage": "Modifica pagine existente",
+       "grant-editprotected": "Modifica pagine protette",
+       "grant-highvolume": "Modiffiche mascive",
+       "grant-oversight": "Asconde i utenti e soprimme e revixoin",
+       "grant-patrol": "Marca e modifiche a-e paggine comme veificæ",
+       "grant-protect": "Proteze e sproteze e paggine",
+       "grant-rollback": "Rollback de modifiche a-e pagine",
+       "grant-sendemail": "Manda di email a di atri utenti",
+       "grant-uploadeditmovefile": "Carega, sostituisce e mescia i file",
+       "grant-uploadfile": "Carrega di noeuvi file",
+       "grant-basic": "Driti de base",
+       "grant-viewdeleted": "Vedde i file e e pagine scassæ",
+       "grant-viewmywatchlist": "Vedde i to öservæ speçiali",
        "newuserlogpage": "Nêuvi utenti",
+       "newuserlogpagetext": "Questo o l'è un registro de creaçioin di utençe.",
        "rightslog": "Diritti d'ûtente",
+       "rightslogtext": "Questo o l'è un registro di cangi a-i driti di utenti.",
+       "action-read": "leze sta paggina",
        "action-edit": "càngia sta pàgina",
+       "action-createpage": "creâ sta paggina",
+       "action-createtalk": "creâ sta paggina de discuscion",
+       "action-createaccount": "creâ st'utença",
+       "action-autocreateaccount": "creâ aotomaticamente st'utença esterna",
+       "action-history": "vedde a cronologia de sta pagina",
+       "action-minoredit": "marcâ sta modifica comme menô",
+       "action-move": "mesciâ sta pagina",
+       "action-move-subpages": "mesciâ sta pagina e e relative sottopagine",
+       "action-move-rootuserpages": "mesciâ e paggine prinçipæ di utenti",
+       "action-move-categorypages": "mesciâ e categorie",
+       "action-movefile": "mesciâ sto file",
+       "action-upload": "caregâ sto file",
+       "action-reupload": "sorvescrive sto file existente",
+       "action-reupload-shared": "soviascrive sto file presente inte l'archivio condiviso",
+       "action-upload_by_url": "caregâ sto file da un addreçço URL",
+       "action-writeapi": "deuviâ l'API in scrittua",
+       "action-delete": "scassâ 'sta paggina",
+       "action-deleterevision": "scassâ sta verscion",
+       "action-deletedhistory": "vixualizzâ a cronologia scassâ de sta pagina",
+       "action-browsearchive": "çercâ paggine scassæ",
        "action-undelete": "Recuppera sta paggina",
        "action-suppressrevision": "rivedde e ripristinâ e modiffiche ascose",
+       "action-suppressionlog": "vedde questo registro privou",
+       "action-block": "bloccâ st'utente in scrittua",
+       "action-protect": "modificâ i livelli de proteçion pe questa pagina",
+       "action-rollback": "annullâ rapidamente e modifiche de l'urtimo utente ch'o l'ha modificou una pagina determinâ",
+       "action-import": "importâ de pagine da 'n'atra wiki",
+       "action-importupload": "importâ de pagine tramite upload da file",
+       "action-patrol": "marcâ e modifiche di atri utenti comme controllæ",
+       "action-autopatrol": "marcâ e proppie modifiche comme controllæ",
+       "action-unwatchedpages": "vixonâ a lista de pagine non öservæ",
+       "action-mergehistory": "unî a cronologia de sta pagina",
+       "action-userrights": "modificâ tutti i driti di utenti",
+       "action-userrights-interwiki": "modificâ i driti di utenti insce di atre wiki",
+       "action-siteadmin": "broccâ e sbroccâ o database",
+       "action-sendemail": "mandâ di e-mail",
+       "action-editmywatchlist": "modificâ a to lista di öservæ",
+       "action-viewmywatchlist": "vedde i to öservæ speçiali",
+       "action-viewmyprivateinfo": "vedde i proppi dæti personali",
+       "action-editmyprivateinfo": "modificâ i proppi dæti personali",
+       "action-editcontentmodel": "modificâ o modello de contegnuo de 'na paggina",
+       "action-managechangetags": "creâ e attivâ/disattivâ i etichette",
+       "action-applychangetags": "appricâ di etichette a-e to modiffiche",
+       "action-changetags": "Azonze e levâ de specifiche etichette sciu scingole verscioin o voxe de registro",
+       "action-deletechangetags": "scassâ i etichette da-o database",
        "nchanges": "$1 {{PLURAL:$1|modiffica|modiffiche}}",
+       "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|da l'urtima vixita}}",
        "enhancedrc-history": "cronologia",
        "recentchanges": "Ùrtimi cangiamenti",
        "recentchanges-legend": "Opçioin di ùrtimi cangiaménti",
        "recentchanges-summary": "Questa pàgina a g'ha di càngi ciù reçenti a-i contegnûi do scîto.",
+       "recentchanges-noresult": "Nisciun-a modiffica durante o periodo inseio ch'a soddisfe sti critei.",
        "recentchanges-feed-description": "Questo feed o g'ha di cangiaménti ciù reçenti a-i contegnûi do scîto.",
        "recentchanges-label-newpage": "Sto cangiaménto o l'à creòu 'na pàgina nêuva",
        "recentchanges-label-minor": "Cangiamento minô (m)",
        "recentchanges-label-plusminus": "Variassion da paggina in nummero de byte",
        "recentchanges-legend-heading": "<strong>Legenda:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (veddi e [[Special:NewPages|neuve paggine]])",
-       "rcnotefrom": "Chì sotta gh'è i cangiamenti fæti comensando da '''$2''' (scin a '''$1''').",
+       "recentchanges-submit": "Fanni vedde",
+       "rcnotefrom": "Chì sotta gh'è {{PLURAL:$5|o cangiamento|i cangiamenti}} a partî da <strong>$3, $4</strong> (scin a '''$1''').",
        "rclistfrom": "Fanni vedde e modiffiche apportæ partindo da $3 $2",
        "rcshowhideminor": "$1 cangiaménti minoi",
        "rcshowhideminor-show": "Fanni vedde",
        "rcshowhidebots-show": "Fanni vedde",
        "rcshowhidebots-hide": "Ascondi",
        "rcshowhideliu": "$1 i utenti registræ",
+       "rcshowhideliu-show": "Fanni vedde",
        "rcshowhideliu-hide": "Ascondi",
        "rcshowhideanons": "$1 utenti anonnimi",
        "rcshowhideanons-show": "Fanni vedde",
        "rcshowhideanons-hide": "Ascondi",
        "rcshowhidepatr": "$1 i cangiaménti controllæ",
+       "rcshowhidepatr-show": "Fanni vedde",
+       "rcshowhidepatr-hide": "Ascondi",
        "rcshowhidemine": "$1 i mæ cangiamenti",
        "rcshowhidemine-show": "Fanni vedde",
        "rcshowhidemine-hide": "Ascondi",
+       "rcshowhidecategorization": "$1 categorizzaçion da pagina",
+       "rcshowhidecategorization-show": "Fanni vedde",
+       "rcshowhidecategorization-hide": "Ascondi",
        "rclinks": "Fanni vedde i $1 cangiaménti ciù reçenti fæti inti ùrtimi $2 giorni<br />$3",
        "diff": "diff",
        "hist": "stö",
        "minoreditletter": "m",
        "newpageletter": "N",
        "boteditletter": "b",
+       "number_of_watching_users_pageview": "[osservâ da {{PLURAL:$1|un utente|$1 utenti}}]",
+       "rc_categories": "Limite a-e categorie (separæ da \"|\"):",
        "rc_categories_any": "Quâ-se-sæ fra quelle indicæ",
        "rc-change-size-new": "$1 {{PLURAL:$1|byte|bytes}} doppo a modiffica",
        "newsectionsummary": "/* $1 */ neuva seçion",
        "recentchangeslinked-page": "Nómme da pàgina:",
        "recentchangeslinked-to": "Fanni védde sôlo i cangiaménti a-e pàgine colegæ a-a pàgina specificâ",
        "recentchanges-page-added-to-category": "[[:$1]] azonto a-a categoria",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] e [[Special:WhatLinksHere/$1|{{PLURAL:$2|una paggina a l'è azonta|$2 paggine son azonte}}]] a-a categoria",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] azonta a-a categoria, [[Special:WhatLinksHere/$1|questa pagina a l'è inclusa a l'interno di atre pagine]]",
        "recentchanges-page-removed-from-category": "[[:$1]] rimosso da-a categoria",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] e {{PLURAL:$2|una paggina a l'è rimossa|$2 paggine son rimosse}} da-a categoria",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] rimossa da-a categoria, [[Special:WhatLinksHere/$1|questa pagina a l'è inclusa a l'interno di atre pagine]]",
        "autochange-username": "Modiffica aotomattica MediaWiki",
        "upload": "Carrega 'n file",
        "uploadbtn": "Carrega 'n file",
        "reuploaddesc": "Torna a-o moddulo pe-o caregamento.",
        "upload-tryagain": "Invia a descrission do file modificou",
        "uploadnologin": "No t'ê introu",
+       "uploadnologintext": "Pe caregaâ  di file bezoeugna $1.",
+       "upload_directory_missing": "A directory de upload ($1) a no l'existe e a no poeu ese creâ da-o server web.",
+       "upload_directory_read_only": "O server web o no l'è in graddo de scrive inta directory de upload ($1).",
+       "uploaderror": "Errô into caregamento",
+       "upload-recreate-warning": "<strong>Attençion: un file con questo nomme o l'è stæto scassou o mesciou.</strong>\nO registro de scassatue e di stramui de questa pagina o l'è riportou chì pe comoditæ:",
+       "uploadtext": "Doeuviâ o modulo sottostante pe caregâ di noeuvi file. Pe visualizzâ ò çercâ i file za caregæ, consultâ o [[Special:FileList|log di file caregæ]]. Caregamenti de file e de noeuve verscioin de file son registræ into [[Special:Log/upload|log di upload]], e scassatue into [[Special:Log/delete|log de scassatue]].\n\nPe insei un file a l'interno de 'na pagina, fanni un collegamento de questo tipo:\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code>''' pe doeuviâ a verscion completa do file\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|testo alternativo]]</nowiki></code>''' pe doeuviâ una verscion larga 200 pixel inseia inte 'n box, alliniâ a scinistra e con 'testo alternativo' comme didascalia\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>''' pe generâ un collegamento diretto a-o file sença vixualizzâlo",
+       "upload-permitted": "{{PLURAL:$2|Tipo de file consentio|Tipi de file consentii}}: $1.",
+       "upload-preferred": "{{PLURAL:$2|Tipo de file consegiou|Tipi de file consegiæ}}: $1.",
+       "upload-prohibited": "{{PLURAL:$2|Tipo de file non consentio|Tipi de file non consentii}}: $1.",
        "uploadlogpage": "Log di file caregæ",
+       "uploadlogpagetext": "De sotta gh'è una lista di urtimi file caregæ.\nConsultâ a [[Special:NewFiles|galleria di noeuvi file]] pe 'na vixon d'insemme.",
        "filename": "Nomme do file",
        "filedesc": "Detaggi",
+       "fileuploadsummary": "Detaggi do file:",
+       "filereuploadsummary": "Cangiamenti a-o file:",
+       "filestatus": "Informaçioin in sciô copyright:",
        "filesource": "Reixe:",
+       "ignorewarning": "Ignora l'avviso e sarva comunque o file",
+       "ignorewarnings": "Ignora i messaggi de avvertimento do scistema",
+       "minlength1": "O nome do file o dev'ese a-o manco de una lettia.",
+       "illegalfilename": "O nomme \"$1\" o conten di caratteri non ammissi inti tittoli de paggine. Dagghe 'n atro nomme e proeuva torna a caregâlo.",
+       "filename-toolong": "I nommi di file no poeuan superâ i 240 byte.",
        "badfilename": "O nomme do file o l'è stæto cangiòu in \"$1\".",
-       "fileexists": "Un papê co sto nomme o existe de zà, pe piaxei da unn'euggiâ a <strong>[[:$1]]</strong> se non ti tei seguo de voleilo cangiâ.\n[[$1|thumb]]",
-       "fileexists-forbidden": "Un papê co sto nomme o existe de zà, pe piaxei vanni in derrê e carega sto papê co un ätro nomme. [[File:$1|thumb|center|$1]]",
+       "filetype-mime-mismatch": "L'estenscion do file \".$1\" a no corrisponde a-o tipo MIME rilevou da-o file ($2).",
+       "filetype-badmime": "No l'è consentio de caregâ di file de tipo MIME \"$1\".",
+       "filetype-bad-ie-mime": "Imposcibile caregâ o file perché Internet Explorer o rilevieiva comme \"$1\", ch'o l'è un tipo de file non consentio e potençialmente peigoso.",
+       "filetype-unwanted-type": "Caregâ di file de tipo '''\".$1\"''' l'è sconsegiou. {{PLURAL:$3|O tipo de file consegiou o l'è|I tipi de file consegiæ son}} $2.",
+       "filetype-banned-type": "'''\".$1\"''' {{PLURAL:$4|o no l'è un tipo de file consentio|no son di tipi de file consentii}}. {{PLURAL:$3|O tipo de file consentio o l'è|I tipi de file consentii son}} $2.",
+       "filetype-missing": "O file o no g'ha d'estenscion (pres. \".jpg\").",
+       "empty-file": "O file che t'hæ inviou o l'è voeuo.",
+       "file-too-large": "O file che t'hæ inviou o l'è troppo grande.",
+       "filename-tooshort": "O nomme do file o l'è troppo curto.",
+       "filetype-banned": "Questo tipo de file o l'è proibio.",
+       "verification-error": "Questo file o no l'ha superou a veriffica.",
+       "hookaborted": "A modiffica che t'hæ çercou de fâ a l'è stæta interrotta da un'estenscion.",
+       "illegal-filename": "O nomme do file o no l'è ammisso.",
+       "overwrite": "Soviascrive un file existente no l'è permisso.",
+       "unknown-error": "Gh'è stæto un aro sconosciuo.",
+       "tmp-create-error": "Imposcibbile creâ o file temporannio.",
+       "tmp-write-error": "Errô de scrittua do file temporannio.",
+       "large-file": "Se raccomanda de no superâ e dimenscioin de $1 pe ciascun file; questo file o l'è grande $2.",
+       "largefileserver": "O file o suppera e dimenscioin consentie da-a configuaçion do server.",
+       "emptyfile": "O file apen-a caregou pâ esee voeuo. Questo poriæ ese dovuo a un aro into nomme do file. Controlla se ti voeu davei caregâ sto file.",
+       "windows-nonascii-filename": "Questo wiki o no supporta di nommi de file con di caratteri speciali.",
+       "fileexists": "Un papê con sto nomme o l'existe za, pe piaxei danni 'n'euggiâ a <strong>[[:$1]]</strong> se no ti t'ê seguo de voeilo cangiâ.\n[[$1|thumb]]",
+       "filepageexists": "A pagina de descriçion de questo file aq l'è za stæta creâ a l'adreçço <strong>[[:$1]]</strong>, sciben che no ghe segge ancon un file con questo nomme. A descriçion de l'oggetto inseia in fase de caregamento a no l'appariâ in scia pagina de descriçion. Pe fâ scì che l'oggetto o comparisce in scia pagina de descriçion, saiâ necessaio modificala manualmente.\n[[$1|thumb]]",
+       "fileexists-extension": "Un file co-in nomme scimile a questo o l'esiste za: [[$2|thumb]]\n* Nomme do file caregou: <strong>[[:$1]]</strong>\n* Nomme do file existente: <strong>[[:$2]]</strong>\nTi voeu miga çerne un nomme ciu caratteristego?",
+       "fileexists-thumbnail-yes": "O file caregou o pâ ese una miniatua ''(thumbnail)''. [[$1|thumb]]\nVerifica, pe confronto, il file <strong>[[:$1]]</strong>.\nSe se tratta da mæxima inmaggine, inte dimenscioin originale, no l'è necessaio caregâne un'atra in miniatua.",
+       "file-thumbnail-no": "O nomme do file comença con <strong>$1</strong>; pâ ch'o segge un'inmaggine de dimenscioin redute ''(miniatua)''.\nSe ti dispon-i del'immaggine inta risoluçion originale, carreghila. Sedunque, pe piaxei, cangighe o nomme.",
+       "fileexists-forbidden": "Un file con questo nomme o l'existe za e o no poeu ese soviascrito. Se ti voeu caregâ o to file, torna inderê e dagghe un atro nomme. [[File:$1|thumb|center|$1]]",
+       "fileexists-shared-forbidden": "Un file con questo nomme o l'esiste za inte l'archivio de risorse multimediæ condivise. Se ti dexiddei ancon caregâ o file, torna inderê e modifica o nomme co-o quæ caregâ o file. [[File:$1|thumb|center|$1]]",
+       "file-exists-duplicate": "Questo file o l'è un dupricou {{PLURAL:$1|do seguente|di seguenti}} file:",
+       "file-deleted-duplicate": "Un file identico a questo ([[:$1]]) o l'è stæto scassou into passou. Verifica a cronologia de scassatue primma de caregâlo torna.",
+       "file-deleted-duplicate-notitle": "Un file identico a questo o l'è stæto scassou into passou, e o tittolo o l'è stæto soppresso. Domanda a quarcun ch'o g'ha a poscibilitæ de vedde i file soppresci de esaminâ a scituaçion primma de procede torna a-o caregamento.",
+       "uploadwarning": "Avviso de caregamento",
+       "uploadwarning-text": "Pe piaxei modifica chi de sotta a descriçion do file e proeuva torna.",
        "savefile": "Sarva o file",
-       "uploaddisabledtext": "In {{SITENAME}} non se peu caregâ de papê.",
+       "uploaddisabled": "O caregamento di file o l'è disabilitou.",
+       "copyuploaddisabled": "O caricamento tramite URL o l'è disabilitou.",
+       "uploaddisabledtext": "O caricamento di file o l'è disabilitou.",
+       "php-uploaddisabledtext": "O caregamento di file tramite PHP o l'è disabilitou. Controlla a configuaçion de file_uploads.",
+       "uploadscripted": "Questo file o conten un codiçe HTML ò de script, ch'o poriæ ese interpretou erroniamente da un browser web.",
+       "upload-scripted-pi-callback": "Imposcibile caregâ un file ch'o conten un'instruçion de elaboaçion in XML-stylesheet.",
+       "uploaded-script-svg": "Trovou elemento de script \"$1\" into file caregou in formato SVG.",
+       "uploaded-hostile-svg": "Trovou CSS no seguo inte l'elemento de stile do file in formato SVG caregou.",
+       "uploaded-event-handler-on-svg": "Impostâ i attributi de gestion di eventi <code>$1=\"$2\"</code> no l'è consentio inti file SGV",
+       "uploaded-href-attribute-svg": "i attributi href inti file SVG poeuan collegâse solo verso e destinaçioin http:// o https://, trovou <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "uploaded-href-unsafe-target-svg": "Trovou href a dæti non segui: destinaçion URI <code>&lt;$1 $2=\"$3\"&gt;</code> caregou into file SVG",
+       "uploaded-animate-svg": "Trovou o tag \"animate\" ch'o poriæ cangiâ href, doeuviando l'attributo \"from\" <code>&lt;$1 $2=\"$3\"&gt;</code> into file SVG caregou.",
+       "uploaded-setting-event-handler-svg": "A configuaçion di attributi pe-o gestô di eventi a l'è bloccâ, trovou <code>&lt;$1 $2=\"$3\"&gt;</code> into file SVG caregou.",
+       "uploaded-setting-href-svg": "L'utilizzo do tag \"set\" pe azonze l'attributo \"href\" a l'elemento parentâ o l'è bloccou.",
+       "uploaded-wrong-setting-svg": "L'uso de l'elemento \"set\" pe azonze una destinaçion remote/data/script pe qua-se-sæ attributo o l'è bloccou. Trovou <code>&lt;set to=\"$1\"&gt;</code> into file SVG caregou.",
+       "uploaded-setting-handler-svg": "o SVG ch'o l'imposta l'attributo \"handler\" con remote/data/script o l'è bloccou. Trovou <code>$1=\"$2\"</code>into file SVG caregou.",
+       "uploaded-remote-url-svg": "o SVG ch'o l'imposta qua-se-sæ attributo de stile con di URL remoti o l'è bloccato. Trovou <code>$1=\"$2\"</code> into file SVG caregou.",
+       "uploaded-image-filter-svg": "Trovou filtro immaggine con URL: <code>&lt;$1 $2=\"$3\"&gt;</code> into file in formato SVG caregou.",
+       "uploadscriptednamespace": "Questo file SVG o conten un namespace '$1' non consentio",
+       "uploadinvalidxml": "O codiçe XML into file caregou o no poeu ese elaboou.",
        "uploadvirus": "Questo file o conten un virus! Dettaggi: $1",
+       "uploadjava": "Questo file o l'è un file ZIP ch'o conten un file .class Java.\nCaregâ i file Java no l'è consentio, perché poeuan caosâ l'aggiamento de restriçioin de segueçça.",
+       "upload-source": "File de origine",
        "sourcefilename": "Nomme do file d'origgine:",
+       "sourceurl": "URL de origine:",
        "destfilename": "Nomme do file de destinassion:",
+       "upload-maxfilesize": "Dimenscion mascima do file: $1",
+       "upload-description": "Descriçion do file",
+       "upload-options": "Opçioin de caregamento",
+       "watchthisupload": "Metti sotta oservaçion",
+       "filewasdeleted": "Un file con questo nomme o l'è stæto za caregou e scassou into passou. Controlla $1 primma de caregâlo torna.",
+       "filename-thumb-name": "Sto chì o pâ un tittolo da miniatua. Pe piaxei no stanni a caregâ e miniatue in scia mæxima wiki. O dunque, corezi o nomme do file de moddo ch'o segge ciù scignificativo e o no l'agge o prefisso da miniatua.",
+       "filename-bad-prefix": "O nomme do file che t'ê aproeuvo a caregâ o comença con '''\"$1\"''', ch'o l'è un nomme generico scimile a quelli assegnæ aotomaticamente da-e fotocammie digitale. Pe piaxei çerni un nomme ciù descrittivo pe-o to file.",
+       "upload-proto-error": "Protocollo errou",
+       "upload-proto-error-text": "Pe l'upload remoto l'è necessaio speçificâ di URL che començan con <code>http://</code> oppû <code>ftp://</code>.",
        "upload-file-error": "Errô interno",
+       "upload-file-error-text": "S'è veificou un errô interno durante a creaçion de 'n file temporannio in sciô server. Contatta un [[Special:ListUsers/sysop|amministratô]].",
+       "upload-misc-error": "Errô de caregamento sconosciuo",
+       "upload-misc-error-text": "S'è veificou un errô non identificou durante o caregamento do file. Controlla che a URL a segge corretta e accescibile e proeuva torna. Se o problema o persciste, contatta un [[Special:ListUsers/sysop|amministratô]].",
+       "upload-too-many-redirects": "L'URL o contegniva troppi redirect",
+       "upload-http-error": "S'è verificou un errô HTTP: $1",
+       "upload-copy-upload-invalid-domain": "No l'è consentio o caregamento de coppie da questo dominnio.",
+       "upload-foreign-cant-upload": "Questo wiki o no l'è configuou pe caregâ i file into repository de file esterno domandou.",
+       "upload-dialog-disabled": "O caregamento di file tramite questo barcon de dialogo o l'è disabilitou inte questo wiki.",
+       "upload-dialog-title": "Carrega file",
+       "upload-dialog-button-cancel": "Anulla",
+       "upload-dialog-button-done": "Fæto",
+       "upload-dialog-button-save": "Sarva",
+       "upload-dialog-button-upload": "Carrega",
+       "upload-form-label-infoform-title": "Detaggi",
+       "upload-form-label-infoform-name": "Nomme",
+       "upload-form-label-infoform-name-tooltip": "Un tittolo unnico e descritivo pe-o file, ch'o serviâ da nomme. Doeuviâ pure e toeu parolle e i spaççi. No stagh'a mette l'estenscion.",
+       "upload-form-label-infoform-description": "Descriçion",
+       "upload-form-label-infoform-description-tooltip": "Descrivi scinteticamente tutto quanto sæ degno de notta a propoxito de quest'oeuvia.\nPe-e foto, indica e cose prinçipæ che gh'en rappresentæ, l'öcaxon e/ò o posto.",
+       "upload-form-label-usage-title": "Utilizzo",
+       "upload-form-label-usage-filename": "Nomme do file",
+       "upload-form-label-own-work": "Questo o l'è travaggio mæ",
+       "upload-form-label-infoform-categories": "Categorie",
+       "upload-form-label-infoform-date": "Dæta",
+       "upload-form-label-own-work-message-generic-local": "Confermo che son aproeuvo a caregâ sto file segondo e condiçioin de serviçio e e polittiche in sce e liçençe de {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Se non t'ê in grou de caregâ o file segondo e politiche de {{SITENAME}}, særa sto barcon e proeuva un atro mettodo.",
+       "upload-form-label-not-own-work-local-generic-local": "Proeuva ascì a [[Special:Upload|pagina de caregamento predefinia]].",
+       "upload-form-label-own-work-message-generic-foreign": "Ho acapio che son aproeuvo a caregâ questo file inte 'n archivio condiviso. Confermo che-o façço segondo e so condiçioin de serviççio e-e so polittiche in sce e liçençe.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Se non t'ê in grou de caregâ o file segondo e politiche de l'archivvio condiviso, særa sto barcon e proeuva un atro mettodo.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Ti poeu provâ ascì a doeuviâ a [[Special:Upload|pagina de caregamento insce {{SITENAME}}]], se questo file o poeu ese caregou segondo e soeu politiche.",
+       "backend-fail-stream": "Imposcibile trasmette o file $1.",
+       "backend-fail-backup": "Imposcibile eseguî o backup do file $1 .",
+       "backend-fail-notexists": "O file $1 o no l'existe.",
+       "backend-fail-hashes": "Imposcibile ötegnî l'hash di file pe 'n confronto.",
+       "backend-fail-notsame": "Existe za un file non identico a \"$1\".",
+       "backend-fail-invalidpath": "$1 o no l'è un percorso de archiviaçion vallido.",
+       "backend-fail-delete": "Imposcibile scassâ o file $1.",
+       "backend-fail-describe": "Imposcibile modificâ i metadæti do file \"$1\".",
+       "backend-fail-alreadyexists": "O file $1 o l'existe za.",
+       "backend-fail-store": "Imposcibbile memorizzâ o file  $1  in  $2 .",
+       "backend-fail-copy": "Imposcibile copiâ o file  $1  in  $2 .",
+       "backend-fail-move": "Imposcibile messciâ o file  $1  in  $2 .",
+       "backend-fail-opentemp": "Imposcibbile arvî o file temporannio.",
+       "backend-fail-writetemp": "Imposcibbile scrive o file temporannio.",
+       "backend-fail-closetemp": "Imposcibbile serâ o file temporannio.",
+       "backend-fail-read": "Imposcibile leze o file  $1 .",
+       "backend-fail-create": "Imposcibile scrive o file $1.",
+       "backend-fail-maxsize": "Imposcibile scrive o file $1 perché o l'è ciù grande de {{PLURAL:$2|un|$2}} byte.",
+       "backend-fail-readonly": "O backend de memoia \"$1\" o l'è attualmente de sola lettua. A raxon indicâ a l'è: <em>$2</em>",
+       "backend-fail-synced": "O file \"$1\" o l'è inte 'n stato non coerente inti backend de memoia interna.",
+       "backend-fail-connect": "Imposcibile connettise a-o backend de memoia \"$1\".",
+       "backend-fail-internal": "S'è verificou un errô sconosciuo into backend de memoia \"$1\".",
+       "backend-fail-contenttype": "Imposcibile determinâ a tipologia do file da archiviâ inte \"$1\".",
+       "backend-fail-batchsize": "O backend de memoia o l'ha programmou una serie de $1 {{PLURAL:$1|opiaçion|opiaçioin}} sciu file; o limmite o l'è de $2 {{PLURAL:$2|opiacion|opiacioin}}.",
+       "backend-fail-usable": "Imposcibile leze ò scrive o file \"$1\" a caosa de aetorizzaçioin insuffixenti ò directory/contegnitoî mancanti.",
+       "filejournal-fail-dbconnect": "Imposcibile connettise a-o database journal pe l'archiviaçion back-end \"$1\".",
+       "filejournal-fail-dbquery": "Imposcibile aggiornâ o database journal pe l'archiviaçion back-end \"$1\".",
+       "lockmanager-notlocked": "Imposcibile sbloccâ \"$1\"; o no l'è bloccou.",
+       "lockmanager-fail-closelock": "Imposcibbile serâ o file de blocco pe \"$1\".",
+       "lockmanager-fail-deletelock": "Imposcibile scassâ o file de blocco pe $1.",
+       "lockmanager-fail-acquirelock": "Imposcibbile aquixî o blocco pe $1.",
+       "lockmanager-fail-openlock": "Imposcibbile arvî o file de blocco pe \"$1\".",
+       "lockmanager-fail-releaselock": "Imposcibile revocâ o blocco pe $1.",
+       "lockmanager-fail-db-bucket": "Imposcibile contattâ i necessai database de blocco into bucket $1.",
+       "lockmanager-fail-db-release": "Imposcibile revocâ i blocchi in sciô database $1.",
+       "lockmanager-fail-svr-acquire": "Imposcibile acquixî i blocchi in sciô server $1.",
+       "lockmanager-fail-svr-release": "Imposcibile revocâ i blocchi in sciô server $1.",
+       "zip-file-open-error": "S'è verificou 'n errô durante l'avertua do file pe-i controlli ZIP.",
+       "zip-wrong-format": "O file specificou o no l'ea un file ZIP.",
+       "zip-bad": "O file o l'è un file ZIP corrotto ò atrimenti illezibbile.\nO no poeu ese controllou comme se dè pe-a segueçça.",
+       "zip-unsupported": "O file o l'è un file ZIP ch'o l'adoeuvia de caratteristeghe ZIP non supportæ da MediaWiki.\nO no poeu ese ben controllou pe-a segueçça.",
+       "uploadstash": "Carrega stash",
+       "uploadstash-summary": "Questa pagina a consente l'accesso a-i file che son stæti caregæ (o son in fase de caregamento) ma che n'en ancon stæti pubbricæ in sciô wiki. Questi file son vixibbili solo a l'utente ch'o i ha caregæ.",
+       "uploadstash-clear": "Elimmina i file in stash",
+       "uploadstash-nofiles": "No ti g'hæ de file into stash.",
+       "uploadstash-badtoken": "Esecuçion de l'açion non riuscia, foscia perché e to credençiale de modiffica son descheite. Proeuva ancon.",
+       "uploadstash-errclear": "O nettezzo di file o no l'è ariescio.",
+       "uploadstash-refresh": "Aggiorna l'elenco di file",
+       "uploadstash-thumbnail": "veddi miniatua",
+       "invalid-chunk-offset": "Offset d'a parte non vallido.",
+       "img-auth-accessdenied": "Accesso negou",
+       "img-auth-nopathinfo": "PATH_INFO mancante.\nO server o no l'è impostou pe passâ quest'informaçion.\nO poriæ ese basou insce CGI e o no poeu supportâ img_auth.\nAmia https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
+       "img-auth-notindir": "O percorso domandou o no l'è inta directory de upload configuâ.",
+       "img-auth-badtitle": "Imposcibile construe un tittolo vallido da \"$1\".",
+       "img-auth-nologinnWL": "No t'ê introu e \"$1\" o no l'è inta whitelist.",
+       "img-auth-nofile": "O file \"$1\" o no l'existe.",
+       "img-auth-isdir": "Ti çerchi d'accede a 'na directory \"$1\".\nL'è consentio solo l'accesso a-i file.",
+       "img-auth-streaming": "\"$1\" in streaming.",
+       "img-auth-public": "La fonçion de img_auth.php a l'è de dâ in output di file da un scito wiki privou.\nQuesto scito o l'è configuou comme un wiki pubbrico.\nPe 'na segueçça ottimale, img_auth.php o l'è disattivou.",
+       "img-auth-noread": "L'utente o no g'ha accesso a-a lettua de \"$1\".",
+       "http-invalid-url": "URL non vallido: $1",
+       "http-invalid-scheme": "Di URL co-o prefisso \"$1\" no son supportæ.",
+       "http-request-error": "Recesta HTTP fallia a caosa de 'n aro sconosciuo.",
+       "http-read-error": "Errô de lettua HTTP.",
+       "http-timed-out": "Recesta HTTP descheita.",
+       "http-curl-error": "Errô durante o recuppero de l'URL: $1",
+       "http-bad-status": "S'è verificou un problema durante a recesta HTTP: $1 $2",
+       "upload-curl-error6": "URL no razonzibbile",
+       "upload-curl-error6-text": "Imposcibile razonze a URL specificâ. Verifica che a URL a sæ scrita correttamente e che o scito in question o sæ attivo.",
+       "upload-curl-error28": "Tempo descheito pe l'upload",
+       "upload-curl-error28-text": "O scito remoto o l'ha impiegou troppo tempo a risponde. Verifica che o sito o sæ attivo, attendi quarche menuto e proeuva torna, se mai inte 'n momento con meno traffego.",
        "license": "Licensa:",
        "license-header": "Licensa",
        "nolicense": "Nisciûnn-a liçensa indicâa",
+       "licenses-edit": "Modiffica opçioin de liçença",
+       "license-nopreview": "(Anteprimma non disponibbile)",
+       "upload_source_url": "(un file da un URL vallido e accescibile pubbricamente)",
+       "upload_source_file": "(un file da-o to computer)",
+       "listfiles-delete": "scassa",
+       "listfiles-summary": "Questa pagina speciale a mostra tutti i file caregæ.",
        "listfiles_search_for": "Çerca pe nomme de l'imàgine:",
+       "listfiles-userdoesnotexist": "L'utença \"$1\" a no l'è registrâ.",
        "imgfile": "file",
        "listfiles": "Lista d'archivvi",
+       "listfiles_thumb": "Miniatua",
        "listfiles_date": "Dæta",
+       "listfiles_name": "Nomme",
+       "listfiles_user": "Utente",
+       "listfiles_size": "Dimenscion",
+       "listfiles_description": "Descriçion",
+       "listfiles_count": "Verscioin",
+       "listfiles-show-all": "Includdi e vege verscioin de inmaggine",
+       "listfiles-latestversion": "Verscion corente",
+       "listfiles-latestversion-yes": "Sci",
+       "listfiles-latestversion-no": "No",
        "file-anchor-link": "file",
        "filehist": "Stöia do file",
        "filehist-help": "Sciacca insce dæta/ôa pe amiâ o file comm'o s'apresentâva into momento indicòu.",
+       "filehist-deleteall": "scassa tutto",
+       "filehist-deleteone": "scassa",
        "filehist-revert": "Ripristina",
        "filehist-current": "Corrente",
        "filehist-datetime": "Dæta/Ôa",
        "filehist-thumb": "Miniatua",
        "filehist-thumbtext": "Miniatua da versción de $1",
+       "filehist-nothumb": "Nisciun-a miniatua",
        "filehist-user": "Utente",
        "filehist-dimensions": "Dimenscioin",
        "filehist-filesize": "Dimension de l'archivvio",
        "filehist-comment": "Coménti",
        "imagelinks": "Ûzo do file",
        "linkstoimage": "{{PLURAL:$1|A segoente pàgina a contegne|E segoenti $1 pàgine contegnan}} colegaménti a-o file:",
+       "linkstoimage-more": "Ciù de $1 {{PLURAL:$1|pagina aponta|pagine apontan}} a questo file.\nA seguente lista a mostra {{PLURAL:$1|a primma paggina ch'a l'aponta|e primme $1 paggine ch'apontan}} a sto file.\nL'è disponibile un [[Special:WhatLinksHere/$2|elenco completo]].",
        "nolinkstoimage": "No gh'è nisciûnn-a pàgina collegâ con 'sto file.",
-       "sharedupload": "'St'archivvio o l'è condiviso; sajeiva a dî c'o pêu ese dêuviòu da ciû progetti wiki.",
+       "morelinkstoimage": "Vixualizza [[Special:WhatLinksHere/$1|di atri inganci]] a questo file.",
+       "linkstoimage-redirect": "$1 (rendriççamento file) $2",
+       "duplicatesoffile": "{{PLURAL:$1|O seguente file o l'è un dupricou|I seguenti $1 file son di dupricæ}} de questo file ([[Special:FileDuplicateSearch/$2|urteioî detaggi]]):",
+       "sharedupload": "Questo file o proven da $1 e o poeu ese doeuviou da di atri progetti.",
+       "sharedupload-desc-there": "Questo file o proven da $1 e o poeu ese doeuviou da di atri progetti.\nConsurta a [$2 pagina de descriçion do file] pe di urteioî informaçioin.",
        "sharedupload-desc-here": "Sto file o vegne da $1 e o peu êse dêuviòu inti âtri progetti.\nChì apreuvo ti peu védde a descriçión inta [$2 pàgina de descriçión do file].",
+       "sharedupload-desc-edit": "Questo file o proven da $1 e o poeu ese doeuviou da di atri progetti.\nFoscia ti voeu modificâ a descriçion presente inta [$2 pagina de descriçion do file].",
+       "sharedupload-desc-create": "Questo file o proven da $1 e o poeu ese doeuviou da di atri progetti.\nFoscia ti voeu modificâ a descriçion presente inta [$2 pagina de descriçion do file].",
+       "filepage-nofile": "No existe un file con sto nomme.",
+       "filepage-nofile-link": "No existe un file con sto nomme, ma l'è poscibile [$1 caregâlo].",
        "uploadnewversion-linktext": "Carrega 'na neuva verscion de sto file",
+       "shared-repo-from": "da $1",
+       "shared-repo": "un archivio condiviso",
        "upload-disallowed-here": "Imposcibbile sorvescrive sto file.",
+       "filerevert": "Ripristina $1",
+       "filerevert-legend": "Ripristina file",
+       "filerevert-intro": "Ti stæ pe ripristinâ o file '''[[Media:$1|$1]]''' a-a [$4 verscion do $2, $3].",
+       "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 93bed08..8182a4f 100644 (file)
        "nstab-category": "Kategori",
        "mainpage-nstab": "Hovudside",
        "nosuchaction": "Funksjonen finst ikkje",
-       "nosuchactiontext": "Handlinga som er oppgjeven i adressa er ugyldig.\nDu har kanskje stava adressa feil, eller følgt ei feil lenkja.\nDette kan òg skuldast ein feil i programvara som er nytta av {{SITENAME}}.",
+       "nosuchactiontext": "Handlinga som er oppgjeven i adressa er ugyldig.\nDu har kanskje stava adressa feil, eller følgt ei feil lenkje.\nDette kan òg skuldast ein feil i programvara som er nytta av {{SITENAME}}.",
        "nosuchspecialpage": "Det finst inga slik spesialside",
        "nospecialpagetext": "Du har bede om ei spesialside som ikkje finst. Lista over spesialsider finn du [[Special:SpecialPages|her]].",
        "error": "Feil",
        "sectioneditnotsupported-text": "Endring av bolkar er ikkje støtta på denne sida.",
        "permissionserrors": "Løyvefeil",
        "permissionserrorstext": "Du har ikkje tilgang til å gjere dette, {{PLURAL:$1|grunnen|grunnane}} til det finn du her:",
-       "permissionserrorstext-withaction": "Du har ikkje løyve til å $2 {{PLURAL:$1|på grunn av|av desse grunnane}}:",
+       "permissionserrorstext-withaction": "Du har ikkje løyve til å $2 {{PLURAL:$1|av di|av desse grunnane}}:",
        "recreate-moveddeleted-warn": "'''Åtvaring: Du attopprettar ei side som tidlegare har vorte sletta.'''\n\nDu bør tenkje over om det er høveleg å halde fram med å endre denne sida.\nSletteloggen for sida finn du her:",
        "moveddeleted-notice": "Sida er vorten sletta. Sletteloggen og flytteloggen er viste nedanfor for referanse.",
        "log-fulllog": "Sjå full loggføring",
        "search-relatedarticle": "Relatert",
        "searchrelated": "relatert",
        "searchall": "alle",
-       "showingresults": "Nedanfor er opp til {{PLURAL:$1|'''eitt'''|'''$1'''}} resultat som byrjar med nummer '''$2''' vist{{PLURAL:$1||e}}.",
+       "showingresults": "Nedanfor er opp til {{PLURAL:$1|<strong>eitt</strong>|<strong>$1</strong>}} resultat som byrjar med nummer <strong>$2</strong> vist{{PLURAL:$1||e}}.",
        "search-showingresults": "Resultat <strong>{{PLURAL:$4|$1|$1–$2}}</strong> av <strong>$3</strong>",
        "search-nonefound": "Ingen resultat svarte til førespurnaden.",
        "search-nonefound-thiswiki": "Det var ingen resultat som passa til spørjinga på denne nettstaden.",
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 8aece73..068b570 100644 (file)
        "mainpage-description": "ಮುಖ್ಯ ಪುಟ",
        "policy-url": "Project:ನಿಯಮಾವಳಿ",
        "portal": "ಸಮುದಾಯೊ ಪುಟೊ",
-       "portal-url": "Project:ಸಮುದಾಯ ಪುಟ",
+       "portal-url": "Project:ಸಮುದಾಯ ಪುಟ",
        "privacy": "ಕಾಸಗಿ ಕಾರ್ಯೊನೀತಿ",
        "privacypage": "Project:ಕಾಸಗಿ ಕಾರ್ಯೊನೀತಿ",
        "badaccess": "ಅನುಮತಿ ದೋಷ",
        "createacct-submit": "ಪೊಸ ಕಾತೆ ಸುರು ಮಲ್ಪುಲೆ",
        "createacct-another-submit": "ಪೊಸ ಕಾತೆ ಸುರು ಮಲ್ಪುಲೆ",
        "createacct-benefit-heading": "{{SITENAME}}ನಿಕ್ಲೆನಂಚಿತ್ತಿನ ಜನೊಕ್ಲೆಡ್ದ್ ಉಂಡಾಪುಂಡು.",
-       "createacct-benefit-body1": "{{PLURAL:$1|ಸà²\82ಪದನà³\86|ಸà²\82ಪದನೆಲು}}",
+       "createacct-benefit-body1": "{{PLURAL:$1|ಸà²\82ಪà³\8aದನà³\86|ಸà²\82ಪದà³\8aನೆಲು}}",
        "createacct-benefit-body2": "{{PLURAL:$1|ಪುಟೊ|ಪುಟೊಕ್ಕುಲು}}",
        "createacct-benefit-body3": "{{PLURAL:$1|ಇನಾಮು|ಇನಾಮುಲು}}",
        "badretype": "ಈರ್ ಕೊರ್ನ ಪ್ರವೇಶ ಪದೆ ಬೇತೆ ಬೇತೆ ಅತ್ಂಡ್",
        "minoredit": "ಉಂದು ಎಲ್ಯ ಬದಲಾವಣೆ",
        "watchthis": "ಈ ಪುಟೊನು ತೂಲೆ",
        "savearticle": "ಪುಟೊನು ಒರಿಪಾಲೆ",
-       "savechanges": "ಬದಲಾವಣà³\86ನà³\8d à²\92ರಿಪಾಲà³\87",
+       "savechanges": "ಬದಲಾವನà³\86ನà³\8d à²\92ರಿಪಾಲà³\86",
        "publishpage": "ಪುಟೋನು ಪ್ರಕಟಿಸಲೇ",
-       "publishchanges": "ಬದಲಾವಣà³\86ನà³\8d à²¤à³\8bà²\9cಾಲà³\87",
+       "publishchanges": "ಬದಲಾವನà³\86ನà³\8d à²¤à³\8bà²\9cಾಲà³\86",
        "preview": "ಮುನ್ನೋಟ",
        "showpreview": "ಮುನ್ನೋಟೊ ತೋಜಾವು",
        "showdiff": "ಬದಲಾವಣೆಲೆನ್ ತೋಜಾವ್",
        "mergehistory-reason": "ಕಾರಣ:",
        "revertmerge": "ಅನ್-ಮರ್ಜ್ ಮಲ್ಪುಲೆ",
        "history-title": "\"$1\" ಪುಟೊತ ಆವೃತ್ತಿ ಇತಿಹಾಸೊ",
-       "difference-title": "ಪಿರ à²ªà²°à²¿à²¸à³\80ಲನà³\86ದ à²¨à²¡à³\81ತ à²µà³\8dಯತà³\8dವಾಸೊ \"$1\"",
+       "difference-title": "ಪಿರ à²ªà²°à²¿à²¸à³\80ಲನà³\86ದ à²¨à²¡à³\81ತ à²µà³\8dಯತà³\8dಯಾಸೊ \"$1\"",
        "lineno": "$1ನೇ ಸಾಲ್:",
        "compareselectedversions": "ಆಯ್ಕೆ ಮಲ್ತಿನ ಆವೃತ್ತಿಲೆನ್ ಹೊಂದಾಣಿಕೆ ಮಲ್ತ್ ತೂಲೆ",
        "editundo": "ದುಂಬುದಲೆಕೊ",
index a063d40..fdc8ec5 100644 (file)
        "nologin": "کیا آپ نے کھاتہ نہیں بنایا ہوا؟ '''$1'''۔",
        "nologinlink": "کھاتا بنائیں",
        "createaccount": "کھاتہ کھولیں",
-       "gotaccount": "پہلے سے کھاتہ بنا ہوا ہے? '''$1'''.",
+       "gotaccount": "پہلے سے کھاتہ بنا ہوا ہے؟ '''$1'''۔",
        "gotaccountlink": "داخل ہوجائیے",
        "userlogin-resetlink": "داخلِ نوشتہ ہونے کی تفاصیل بھول گئے ہیں؟",
        "userlogin-resetpassword-link": "کلمہ شناخت بھول گئے؟",
        "history-show-deleted": "صرف حذف شدہ",
        "histfirst": "قدیم ترین",
        "histlast": "تازہ ترین",
-       "historysize": "({{PLURAL:$1|1 لکمہ|$1 لکم}})",
+       "historysize": "({{PLURAL:$1|1 بائٹ|$1 بائٹ}})",
        "historyempty": "(خالی)",
        "history-feed-title": "تاریخچۂ نظرثانی",
        "history-feed-description": "ویکی پر اِس صفحہ کا تاریخچۂ نظرثانی",
        "newpageletter": "نیا ..",
        "boteditletter": " خودکار",
        "rc_categories_any": "کوئی بھی منتخب",
-       "rc-change-size-new": "$1 {{PLURAL:$1|بائٹ|بائٹس}} تبدیلی کے بعد",
+       "rc-change-size-new": "$1 {{PLURAL:$1|بائٹ|بائٹ}} تبدیلی کے بعد",
        "rc-enhanced-expand": "تفصیلات دکھائیں",
        "rc-enhanced-hide": "تفصیلات چھپائیے",
        "recentchangeslinked": "متعلقہ تبدیلیاں",
        "ilsubmit": "تلاش",
        "bydate": "بالحاظ تاریخ",
        "weeks": "{{PLURAL:$1|$1ہفتہ| $1  ہفتے}}",
+       "ago": "$1 قبل",
+       "minutes-ago": "$1 {{PLURAL:$1|منٹ|منٹ}} قبل",
+       "seconds-ago": "$1 {{PLURAL:$1|سیکنڈ|سیکنڈ}} قبل",
        "bad_image_list": "شکلبند درج ذیل ہے:\n\nصرف فہرستی عناصر (* سے شروع ہونے والی لکیری) شامل کی جاتی ہیں۔\nکسی لکیر میں پہلا ربط کوئی خراب ملف کا ہونا چاہئے۔\nاُسی لکیر میں باقی آنے والے ربط کو مستثنیٰ قرار دیا جاتا ہے، مثلاً صفحات جہاں ملف لکیر کے وسط میں آسکتا ہے۔",
        "metadata": "میٹا ڈیٹا",
        "metadata-help": "اِس ملف میں اِضافی معلومات شامل ہیں، جو کہ شاید اُس رقمی کیمرے یا سکینر سے آئے ہیں جس کے ذریعے یہ ملف بنائی گئی تھی۔\nاگر ملف اپنی اصل حالت میں نہیں رہی ہے تو کچھ تفاصیل ترمیم شدہ ملف کی مکمل طور پر عکاسی نہیں کرپائیں گے۔",
index e46859d..a0f38be 100644 (file)
        "markaspatrolleddiff": "标记为已巡查",
        "markaspatrolledtext": "标记此页面为已巡查",
        "markaspatrolledtext-file": "将此文件版本标记为已巡查",
-       "markedaspatrolled": "标记为已查",
+       "markedaspatrolled": "标记为已查",
        "markedaspatrolledtext": "[[:$1]]的已选中版本已被标识为已巡查。",
        "rcpatroldisabled": "最近更改巡查已禁用",
        "rcpatroldisabledtext": "最近更改巡查功能目前已关闭。",
-       "markedaspatrollederror": "不能标志为已检查",
+       "markedaspatrollederror": "不能标记为已巡查",
        "markedaspatrollederrortext": "您需要指定一个版本以标记为已巡查。",
-       "markedaspatrollederror-noautopatrol": "你不能把自己的更改标记为已检查。",
+       "markedaspatrollederror-noautopatrol": "您不被允许将您自己的更改标记为已巡查。",
        "markedaspatrollednotify": "$1的更改已被标记为已巡查。",
        "markedaspatrollederrornotify": "标记为已巡查失败。",
        "patrol-log-page": "巡查日志",
        "watchlistedit-raw-submit": "更新监视列表",
        "watchlistedit-raw-done": "您的监视列表已经更新。",
        "watchlistedit-raw-added": "$1个标题被添加:",
-       "watchlistedit-raw-removed": "$1个标题被除:",
+       "watchlistedit-raw-removed": "$1个标题被除:",
        "watchlistedit-clear-title": "清空监视列表",
        "watchlistedit-clear-legend": "清空监视列表",
        "watchlistedit-clear-explain": "所有标题将从您的监视列表中移除",
index 42f8f05..23f0357 100644 (file)
        "watchlistedit-normal-legend": "從監視清單中移除標題",
        "watchlistedit-normal-explain": "下方顯示在您監視清單中的標題。\n要移除標題,請勾選該標題旁的核選方塊並點選 \"{{int:Watchlistedit-normal-submit}}\"。\n您也可 [[Special:EditWatchlist/raw|編輯原始監視清單]]。",
        "watchlistedit-normal-submit": "移除標題",
-       "watchlistedit-normal-done": "已於您的監視清單中移除 $1 個標題:",
+       "watchlistedit-normal-done": "已於您的監視清單中移除 {{PLURAL:$1|$1}} 個標題:",
        "watchlistedit-raw-title": "編輯原始監視清單",
        "watchlistedit-raw-legend": "編輯原始監視清單",
        "watchlistedit-raw-explain": "下方顯示在您監視清單中的標題,您可透過編輯來新增與移除清單項目,一個標題一行。\n完成編輯後,請點選 \"{{int:Watchlistedit-raw-submit}}\"。\n您也可 [[Special:EditWatchlist|使用標準編輯器]]。",
index d07a8d4..af05a81 100644 (file)
@@ -54,7 +54,7 @@ class DeleteArchivedFiles extends Maintenance {
                $this->output( "Searching for and deleting archived files...\n" );
                $res = $dbw->select(
                        'filearchive',
-                       [ 'fa_id', 'fa_storage_group', 'fa_storage_key', 'fa_sha1' ],
+                       [ 'fa_id', 'fa_storage_group', 'fa_storage_key', 'fa_sha1', 'fa_name' ],
                        '',
                        __METHOD__
                );
@@ -67,9 +67,19 @@ class DeleteArchivedFiles extends Maintenance {
                                continue;
                        }
 
+                       /** @var LocalFile $file */
+                       $file = $repo->newFile( $row->fa_name );
+                       try {
+                               $file->lock();
+                       } catch ( LocalFileLockError $e ) {
+                               $this->error( "Could not acquire lock on '{$row->fa_name}', skipping\n" );
+                               continue;
+                       }
+
                        $group = $row->fa_storage_group;
                        $id = $row->fa_id;
-                       $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
+                       $path = $repo->getZonePath( 'deleted' ) .
+                               '/' . $repo->getDeletedHashPath( $key ) . $key;
                        if ( isset( $row->fa_sha1 ) ) {
                                $sha1 = $row->fa_sha1;
                        } else {
@@ -96,6 +106,7 @@ class DeleteArchivedFiles extends Maintenance {
                                $this->output( "Notice - file '$key' is still in use\n" );
                        } elseif ( !$repo->quickPurge( $path ) ) {
                                $this->output( "Unable to remove file $path, skipping\n" );
+                               $file->unlock();
                                continue; // don't delete even with --force
                        } else {
                                $needForce = false;
@@ -105,12 +116,14 @@ class DeleteArchivedFiles extends Maintenance {
                                if ( $this->hasOption( 'force' ) ) {
                                        $this->output( "Got --force, deleting DB entry\n" );
                                } else {
+                                       $file->unlock();
                                        continue;
                                }
                        }
 
                        $count++;
                        $dbw->delete( 'filearchive', [ 'fa_id' => $id ], __METHOD__ );
+                       $file->unlock();
                }
 
                $this->commitTransaction( $dbw, __METHOD__ );
index cd37e33..282799a 100644 (file)
@@ -1,10 +1,5 @@
 ( function ( mw, $ ) {
 
-       // Support: MediaWiki < 1.26
-       // Cached HTML will not yet have this from OutputPage::getHeadScripts.
-       document.documentElement.className = document.documentElement.className
-               .replace( /(^|\s)client-nojs(\s|$)/, '$1client-js$2' );
-
        mw.page = {};
 
        $( function () {
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()
         */
index 7b94e40..f69ecaf 100644 (file)
@@ -15,18 +15,12 @@ class BalancerTest extends MediaWikiTestCase {
                        'strict' => false, /* not strict */
                        'allowedHtmlElements' => null, /* no sanitization */
                        'tidyCompat' => false, /* standard parser */
+                       'allowComments' => true, /* comment parsing */
                ] );
        }
 
        /**
-        * Anything cleanup you need to do should go here.
-        */
-       protected function tearDown() {
-               parent::tearDown();
-       }
-
-       /**
-        * @covers Balancer::balance
+        * @covers MediaWiki\Tidy\Balancer::balance
         * @dataProvider provideBalancerTests
         */
        public function testBalancer( $description, $input, $expected ) {
@@ -47,15 +41,16 @@ class BalancerTest extends MediaWikiTestCase {
                // for providers, and filter out HTML constructs which
                // the balancer doesn't support.
                $tests = [];
-               $start = '<html><head></head><body>';
-               $end = '</body></html>';
+               $okre = "~ \A
+                       (?i:<!DOCTYPE\ html>)?
+                       <html><head></head><body>
+                       .*
+                       </body></html>
+               \z ~xs";
                foreach ( $json as $filename => $cases ) {
                        foreach ( $cases as $case ) {
                                $html = $case['document']['html'];
-                               if (
-                                       substr( $html, 0, strlen( $start ) ) !== $start ||
-                                       substr( $html, -strlen( $end ) ) !== $end
-                               ) {
+                               if ( !preg_match( $okre, $html ) ) {
                                        // Skip tests which involve stuff in the <head> or
                                        // weird doctypes.
                                        continue;
@@ -69,35 +64,43 @@ class BalancerTest extends MediaWikiTestCase {
                                $html = $case['document']['noQuirksBodyHtml'];
                                // Normalize case of SVG attributes.
                                $html = str_replace( 'foreignObject', 'foreignobject', $html );
+                               // Normalize case of MathML attributes.
+                               $html = str_replace( 'definitionURL', 'definitionurl', $html );
 
-                               if ( isset( $case['document']['props']['comment'] ) ) {
-                                       // Skip tests which include HTML comments, which
-                                       // the balancer requires to have been stripped.
+                               if (
+                                       isset( $case['document']['props']['comment'] ) &&
+                                       preg_match( ',<!--[^>]*<,', $html )
+                               ) {
+                                       // Skip tests which include HTML comments containing
+                                       // the < character, which we don't support.
                                        continue;
                                }
                                if ( strpos( $case['data'], '<![CDATA[' ) !== false ) {
                                        // Skip tests involving <![CDATA[ ]]> quoting.
                                        continue;
                                }
-                               if ( stripos( $case['data'], '<!DOCTYPE' ) !== false ) {
-                                       // Skip tests involving doctypes.
+                               if (
+                                       stripos( $case['data'], '<!DOCTYPE' ) !== false &&
+                                       stripos( $case['data'], '<!DOCTYPE html>' ) === false
+                               ) {
+                                       // Skip tests involving unusual doctypes.
                                        continue;
                                }
-                               if ( preg_match( ',</?(html|head|body|frame|plaintext)>|<rdar:,i', $case['data'] ) ) {
+                               $literalre = "~ <rdar: | <isindex | < /? (
+                                       html | head | body | frame | frameset | plaintext
+                               ) > ~xi";
+                               if ( preg_match( $literalre, $case['data'] ) ) {
                                        // Skip tests involving some literal tags, which are
                                        // unsupported but don't show up in the expected output.
                                        continue;
                                }
                                if (
-                                       isset( $case['document']['props']['tags']['form'] ) ||
                                        isset( $case['document']['props']['tags']['iframe'] ) ||
                                        isset( $case['document']['props']['tags']['noembed'] ) ||
                                        isset( $case['document']['props']['tags']['noscript'] ) ||
                                        isset( $case['document']['props']['tags']['script'] ) ||
-                                       isset( $case['document']['props']['tags']['select'] ) ||
                                        isset( $case['document']['props']['tags']['svg script'] ) ||
                                        isset( $case['document']['props']['tags']['svg title'] ) ||
-                                       isset( $case['document']['props']['tags']['textarea'] ) ||
                                        isset( $case['document']['props']['tags']['title'] ) ||
                                        isset( $case['document']['props']['tags']['xmp'] )
                                ) {
@@ -118,7 +121,8 @@ class BalancerTest extends MediaWikiTestCase {
                                        isset( $case['document']['props']['tagWithLt'] ) ||
                                        isset( $case['document']['props']['attrWithFunnyChar'] ) ||
                                        preg_match( ':^(</b test|<di|<foo bar=qux/>)$:', $case['data'] ) ||
-                                       preg_match( ':</p<p>:', $case['data'] )
+                                       preg_match( ':</p<p>:', $case['data'] ) ||
+                                       preg_match( ':<b &=&amp>|<p/x/y/z>:', $case['data'] )
                                ) {
                                        // Skip tests with funny tag or attribute names,
                                        // which are really tests of the HTML tokenizer, not
@@ -126,7 +130,7 @@ class BalancerTest extends MediaWikiTestCase {
                                        continue;
                                }
                                if (
-                                       stripos( $case['data'], 'encoding=" text/html "' ) !== false
+                                       preg_match( ':encoding=" text/html "|type=" hidden":', $case['data'] )
                                ) {
                                        // The Sanitizer normalizes whitespace in attribute
                                        // values, which makes this test case invalid.
@@ -136,9 +140,12 @@ class BalancerTest extends MediaWikiTestCase {
                                        // Skip tests with ASCII null, etc.
                                        continue;
                                }
+                               $data = preg_replace(
+                                       '~<!DOCTYPE html>~i', '', $case['data']
+                               );
                                $tests[] = [
                                        $filename, # use better description?
-                                       $case['data'],
+                                       $data,
                                        $html
                                ];
                        }